📚

rollup / vite で prebuild 済みのファイルをパースせずに読み込む

2021/09/07に公開

ビルド済みの依存がない 巨大な js を import するとき、バンドラーによる解析フェーズを飛ばしたいことがあります。巨大なファイルを別にビルドして、アプリケーションとしてその利用者になるときなどですね。単体で動く巨大なモジュールとして、 typescript や prettier が挙げられます。

というわけで、 webpack だと noparse オプションで解析をスキップできるのですが、 vite / rollup だとそれがないので無理やり実現するプラグインを作りました。

気が向いたら npm に投げますが、別に設定ファイルに直接書いてもいいぐらいの分量なので, vite.config.ts を置いときます。(vite に設定ファイルの ts 対応が入ってます)

// vite.config.ts
import type { Plugin } from "rollup";
import { defineConfig } from "vite";

const noparse = () =>
  ({
    name: "noparse",
    enforce: "pre", // for vite plugin order
    buildStart: () => console.time("noparse"),
    buildEnd: () => console.timeEnd("noparse"),
    transform(code: string, id: string) {
      if (id.endsWith("?noparse")) {
        const encoded = Buffer.from(
          unescape(encodeURIComponent(code))
        ).toString("base64");
        return {
          code: `const url = "data:text/javascript;base64," + "${encoded}";
const f = new Function("u", "return import(u)");
export default () => f(/* @vite-ignore */ url);`,
        };
      }
      if (id.endsWith("?noparse-umd")) {
        return {
          code: `const m = { exports: {}};
new Function('module', 'exports', ${JSON.stringify(code)})(m, m.exports);
export default m.exports`,
        };
      }
    },
  } as Plugin);

export default defineConfig({
  build: {
    minify: false,
    target: "esnext",
    lib: {
      entry: "src/main.ts",
      formats: ["es"],
    },
  },
  plugins: [noparse()],
});

これで自分の vite 環境下の typescript のビルドが 9000ms から 330ms になりました。

制約

ESM だと変換の都合で、必ず非同期の loader になります。

import load from "./esm-code.js?noparse"; // export default () => console.log('hello')
const mod = await load();
mod.default(); // => hello

これは ESM のコードを同期的に評価する方法がなく、 import("data:base64,..") に変換してるので、必ず非同期APIになってしまう、という事情で、 dynamic import と同じように扱う必要があります。また、node 環境下では割と最近のバージョンじゃないと data uri を評価できません。

base64 化のコストもなくはないので、実行コストが掛かります。キャッシュすれば早くなるでしょうが…
その代わりに、外部 chunk 化されるので watch 環境のビルドは早くなるでしょう。

cjs / umd の場合は new Function で同期的に評価します。

// --- with umd (always sync) ---
import ts from "typescript";

// noparse-umd
import ts from "typescript/lib/typescript.js?noparse-umd";

とはいえ、開発時に使うものを想定してるので、本番環境では eval / new Function を使う関係上、使用を避けたほうがいいでしょう。

普通に noparse ほしいよね

というのを rollup の issue に書いておきました。

https://github.com/rollup/rollup/issues/3873

Discussion