Webpack (webpack-stream) で エントリーポイントを動的に変更する

Scss に続いて JS でも動的に読み込むファイルを切り替えたいと考えました。

今回の方法

ディレクトリ構造

サンプルとして、以下のようなディレクトリ構造にしたとします。

    /
   ├ dist/
   │   └ js/
   │      ├ app.min.js    // app.js からビルド
   │      └ parts.min.js  // parts/ ディレクトリ下のjsをバンドル
   └ src/
       └ js/
          ├ app.js
          │
          └ parts/
              ├ parts1.js // 単独
              ├ parts2.js // parts3.js に依存
              └ parts3.js
  • src/js/app.js: (存在すれば)常に出力、通常の処理
    • dist/js/app.min.js にビルド
  • src/js/parts/ディレクトリ下: フラグによって動的に読み込むファイルを切り替える
    • dist/js/parts.min.js にバンドル

状況的にはこのようにしたいと考えています。

webpack.config.js

以上を踏まえて webpack.config.js

const webpackTerser = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path          = require('path');
const glob          = require('glob');

const devFlag = true;

// dynamic import test
const partsPath = 'src/js/parts';
const flag = {
    parts1: true,
    parts2: true
};

const entry = () => {
    const entries = glob
        .sync(
            '**/*.js',
            {
                cwd: 'src/js',
                //ignore
                ignore: [
                    'parts/**/*.js',
                    '**/_*.js'
                ]
            }
        )
        .map(function (key) {
            return [key, path.resolve('src/js', key)];
        });
        //  `{ 'app.js': 'PATH/TO/PROJECT/src/js/app.js' }` のようなオブジェクトを entriesObj とする
        let entriesObj = Object.fromEntries(entries);
        // 以下、 parts に関する処理
        partsCount = 0;
        // flag の中で true になっている個数をカウント
        Object.keys(flag).forEach(function (index) {
            if (flag[index]) {
                partsCount++;
            }
        });
        if (partsCount > 0) {
            // flag の中で true になっている個数が少なくとも1つある場合は、 `parts.js` キーを entriesObj に追加
            entriesObj[`parts.js`] = [];
            // entriesObj の `parts.js` キー に配列で複数のファイル (parts1.js, parts2.js) を指定
            Object.keys(flag).forEach(function (index) {
                if (flag[index]) {
                    entriesObj[`parts.js`].push(path.resolve(partsPath, `${index}.js`));
                }
            });
        }
    return entriesObj;
};
const configs = {
    mode: development,
    entry: entry(),
    output: {
        filename: '[name]',
    },
    devtool: 'inline-source-map',
    plugins: [
        new CleanWebpackPlugin({
            cleanOnceBeforeBuildPatterns: [
                'dist/js/**/*.js'
            ],
        }),
    ],
    optimization: {
            minimizer: [
            new webpackTerser({
                extractComments: 'some',
                terserOptions: {
                    compress: {
                        drop_console: false,
                    },
                },
            }),
        ],
    },
};

module.exports = configs;
  • エントリーポイントには glob.sync から始まる処理で指定
    • src/js下 の .js ファイルを { 'app.js': 'PATH/TO/PROJECT/src/js/app.js' } のようにキーはファイル名、値がファイルへのフルパス、となるようなオブジェクトを作っています
      • ignoresrc/js/parts/下 は最初は除外しています
      • 本題からは逸れますが _*.js_ 始まりのファイルも ignore 指定しています
  • オブジェクト flag のキーと src/js/parts/下 のファイル名を一致させている、という条件下でフラグの true or false によりエントリーポイントを変化させています
    • 今回の出力は dist/js/parts.min.js でひとまとめにする前提にしています。 app.min.js の中には混ぜません
    • そのため、エントリーポイントを { 'app.js': [ 'PATH/TO/PROJECT/src/js/app.js', 'PATH/TO/PROJECT/src/js/parts/parts1.js', ... ] } とするのではなく、 { 'app.js': 'PATH/TO/PROJECT/src/js/app.js', 'parts.js': [ 'PATH/TO/PROJECT/src/js/parts1.js', 'PATH/TO/PROJECT/src/js/parts/parts2.js', ... ] } となるように処理を加えました

Gulpタスク (JavaScriptに関する部分)

const { dest }      = require('gulp');
const plumber       = require('gulp-plumber');
const notify        = require('gulp-notify');
const rename        = require('gulp-rename');
const webpackStream = require('webpack-stream');
const webpackConfig = require('webpack.config');

const jsBuild = () => {
    return webpackStream(webpackConfig)
        .pipe(plumber({
            errorHandler: notify.onError({
                message: 'Error: <%= error.message %>',
                title: 'jsLibBuild'
            })
        }))
        .pipe(rename((path) => {
            path.basename += '.min'
            path.extname = '.js'
        }))
        .pipe(dest('dist/js'));
};

module.exports = jsBuild;

これでひとまず意図した出力が得られました。

参考

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。