【npmライブラリ作成×webpack×Next.js】ReferenceError: document is not defined

Next.js

作成したnpmライブラリをNext.jsで読み込む時、ReferenceError: document is not definedというエラーに遭いました。

状況

作成していたnpmライブラリは、webpackを使用してバンドルし、そのJSファイルをエンドポイントとしてエクスポートしていました。

使用していたwebpackのローダーは下記の通りです。

  • babel-loader
  • css-loader
  • style-loader
  • fork-ts-checker-webpack-plugin
  • ts-loader

調べたところ、style-loaderがエラー原因のようでした。

style-loaderは読み込まれた際に、document.createElementを実行します。
一方、Next.jsは毎回サーバー側でプリフェッチを行い、各ページのHTMLを作成しています。その際は、サーバー側なのでDOM、documentは存在しません。その環境でstyle-loaderがdocument.createElementを行なっているため、エラーになります。

対応方法

1. mini-css-extract-pluginを使用する

style-loaderを使用せず、mini-css-extract-pluginというwebpackのプラグインを使用するやり方です。

普通はwebpackでバンドルすると、CSSも1つのJSファイルにまとめられます。しかし、このプラグインはCSSはJSファイルに含まず、CSSだけでバンドルして1つのCSSを出力します。

そして、別々でバンドルしたJSファイル、CSSファイルを別々でインポートします。イメージとしては、HTMLファイル内で、JSファイルはscriptタグで読み込み、CSSファイルはlinkタグで読み込む感じです。

私が今回作ったライブラリにはこのやり方はあいませんでした>< HTMLファイルを使用しない仕様でしたし、CSSファイルを別でエクスポートもしたくなかったためです。

2. webpackではない別のバンドラーを使用する

私が選択した対応方法は、別のバンドラーを使用することでした。

選んだのはrollupです。設定方法はwebpackとほぼほぼ同じで、プラグインを使いながらプロジェクトに合うバンドル方法を決める感じです。CommonJS、ESmoduleなど各モジュールシステムに対応するバンドルファイルも簡単に作ることができます。

使用した結果、今回のエラー「ReferenceError: document is not defined」を起こすことなく、CSSを1つのJSファイルにバンドルすることができました。

rollup設定

参考までにrollupの設定ファイルを残しておきます。
React, TypeScriptを使用しているプロジェクトで、CommonJS, ESmodule, UMDの各フォーマットのバンドルファイルを出力してます。

import resolve from "@rollup/plugin-node-resolve";
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import { terser } from "rollup-plugin-terser";
import postcss from "rollup-plugin-postcss";

const formatTypes = ["cjs", "esm", "umd"];

const config = formatTypes.map((formatType) => {
  return {
    input: "src/index.ts",
    output: {
      name: "lib-name", // ライブラリ名
      file: `dist/index.${formatType}.js`,
      format: formatType,
      exports: "auto",
      globals: {
        react: "React",
        "react-dom": "ReactDOM",
      },
    },
    external: ["react", "react-dom"],
    plugins: [
      resolve({ browser: true }),
      babel({
        exclude: "node_modules/**",
        babelHelpers: "bundled",
      }),
      commonjs({ extensions: [".js", ".ts"] }),
      typescript({ outDir: "dist" }),
      postcss({ extensions: [".css"] }),
      terser(),
    ],
  };
});

export default config;
タイトルとURLをコピーしました