🧊

`import type { A }`と、`import { type A }`の違いで、Viteのチャンク配分が変わることもある

ということを学んだ。

こういうケース

// index.ts
import { type ParserOptions } from "oxc-parser";

const myFunc = async (opts: ParserOptions) => {
  const { parseSync } = await import("oxc-parser");
  return parseSync("test", "let a =1;", opts);
};

// Later...
myFunc({}).then((result) => console.log(result));

parseSync()は必要になるまでロードしたくないという意味で、Dynamic importをしてるとする。(ViteはDynamic importもtree-shakeしてくれて偉い)

同時に、型だけ使いたいからimport { type A }というtypeを内側に書くやり方をしてる。

一見、なにも問題ないように見える。が、実際にはそうではないらしかった。

問題

parseSync()の実装が、index.ts側にバンドルされちゃうみたいな出力結果になる。

vite v6.3.5 building for production...
✓ 9 modules transformed.
dist/index.html                                   0.38 kB │ gzip:   0.27 kB
dist/assets/wasi-worker-browser-DSdL8bZF.js     165.13 kB
dist/assets/parser.wasm32-wasi-DPiy-7BD.wasm  1,620.11 kB │ gzip: 471.17 kB
dist/assets/wasm-BTSMSx_C.js                      0.53 kB │ gzip:   0.33 kB
dist/assets/index-C0RdaAXH.js                   187.21 kB │ gzip:  43.73 kB
✓ built in 488ms

エントリーであるindex.jsが重たくなってるところが問題。

というわけで

// index.ts
import type { ParserOptions } from "oxc-parser";

const myFunc = async (opts: ParserOptions) => {
  const { parseSync } = await import("oxc-parser");
  return parseSync("test", "let a =1;", opts);
};

// Later...
myFunc({}).then((result) => console.log(result));

import type { A }方式にすると、想定した通りの結果になった。

vite v6.3.5 building for production...
✓ 9 modules transformed.
dist/index.html                                   0.38 kB │ gzip:   0.27 kB
dist/assets/wasi-worker-browser-DSdL8bZF.js     165.13 kB
dist/assets/parser.wasm32-wasi-DPiy-7BD.wasm  1,620.11 kB │ gzip: 471.17 kB
dist/assets/wasm-DBfLOrs_.js                    185.68 kB │ gzip:  43.00 kB
dist/assets/index-CeYCIOMS.js                     2.00 kB │ gzip:   1.02 kB
✓ built in 491ms

oxc-parserは現状CJSで書かれてる(つまりtree-shakeされない・・・)ので、もしかしたらそれのせいもあるかもしれない。 けど、まあこういうこともあるのだなあと。

個人的にはもともとimport type {}で分けて書け派なので気にしたことなかったけど、試しにAIに書かせてみたらこれに気付いたという話。