10年くらい前に作った WEB サイトの改修案件が発生して、構成が古すぎて手に負えなくなっていたのでコレを気にビルド環境を見直しました。
元のものが Grunt で JS を結合したり SCSS をビルドしている構成だったので Grunt を Gulp に置き換え、SCSS のビルドはそのまま JS は順番を決めて結合するのではなくバンドルするようにしたい。Vite のビルドに Rollup が使われていたので webpack ではなく Rollup を使う構成にしてみました。(JS だけなら Gulp を使う必然性がないのですが SCSS の微都度と一緒に watch して JS, CSS どりらでもビルドできるという理由で導入しました)
Rollup で JS をビルドできる環境を作る
install packages
$ npm i -D rollup # Babel 関連 $ npm i -D @babel/core @babel/preset-env @rollup/plugin-babel # npm install したパッケージをバンドルするためのプラグイン $ npm i -D @rollup/plugin-node-resolve
note
rollup-plugin-node-resolve
は古いので@rollup/plugin-node-resolve
を使う- babel ではなく
buble
を使っている記事もあったが、 buble は開発が止まりメンテナンスモードなっているため babel を選択した
設定ファイル
.babelrc.json
{ "presets": [ [ "@babel/preset-env", { "loose": true, "targets": "> 1%, not dead", "modules": false } ] ] }
⚠ module: false
でないと Rollup のエラーになる
rollup.config.js
// npm modules を bundle するためのプラグイン import { nodeResolve } from '@rollup/plugin-node-resolve'; // babel import { babel } from '@rollup/plugin-babel'; const config = { input: './src/index.js' output: { file: './build/assets/js/main.js', // Web サイトでの利用なので即時関数で出力される iife を指定 format: 'iife', } plugins: [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ], }; export default config;
cf. format に関して こちらの記事 がわかりやすかったです
Rollup でバンドルできているかのチェック
$ npx rollup --config
上記コマンドを実行しビルドされた JS ファイルが出力されていれば OK
Gulp から Rollup を実行する
$ npm i -D gulp $ touch gulpfile.js
gulp の設定ファイルを作成
gulpfile.js
'use strict'; const { watch, parallel, series } = require('rollup'); const { rollup } = require('rollup'); const config = require('./rollup.config'); const buildJS = async (cb) => { await rollup(config) .then(async (bundle) => { // ファイルの生成も非同期なので生成完了まで await する await bundle.write(config.output); Promise.resolve(); }) .catch((error) => { if (cb) { cb(); } console.error(error); }); cb(); }; const buildJSTask = series(buildJS); exports.default = () => { watch(['./src/**/*.js'], buildJSTask); }; exports.build = parallel(buildJSTask);
直接 Rollup を呼び出す方法を採用しました。
npx gulp
でファイル監視, npx gulp build
でビルドコマンドが実行されます。
cf. Webフロントエンド - GulpでRollupを使う / rollup-stream、rollup | ブログ | STUDIO BUS STOP | 香川県高松市
rollup.config.js を CommonJS の形式に修正する
gulp が CommonJS の形式なので rollup.config.js
内で import / export
を使用しているとエラーになってしまいます。
gulpfile 内で import / export
を使えるようにする方法もあるが、gulp のプラグインなど対応してないものがあると面倒そうなので rollup.config.js
を CommonJS 形式に書き換えます。
rollup.config.js
- import { nodeResolve } from '@rollup/plugin-node-resolve'; + const { nodeResolve } = require('@rollup/plugin-node-resolve'); - import { babel } from '@rollup/plugin-babel'; + const { babel } = require('@rollup/plugin-babel'); const config = { input: './src/index.js' output: { file: './build/assets/js/main.js', format: 'iife', } plugins: [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ], }; - export default config; + module.exports = config;
npm script の作成
gulp を実行する npm script を作成
package.json
{ "scripts": { "gulp:build": "npx gulp build", "gulp:dev": "npx gulp" }, }
npm run gulp:build
を実行して JS のビルドができれば OK
Options
minifiy
- (gulp-uglify)https://www.npmjs.com/package/gulp-uglify
- Last publish:
3 years ago
, ☆1.2k
- Last publish:
- (rollup-plugin-terser)https://www.npmjs.com/package/rollup-plugin-terser
- Last publish:
2 years ago
, ☆587
- Last publish:
gulp で行なうほうが十分に枯れている気もしますが、Rollup のプラグインは minify したものとして無いものを別々に出力できそうだったのと後述の console.log
の削除が Rollup で行った方が良さそうだったので Rollup のプラグインを採用しました。
$ npm i -D rollup-plugin-terser
rollup.config.js
const { nodeResolve } = require('@rollup/plugin-node-resolve'); const { babel } = require('@rollup/plugin-babel'); const { terser } = require('rollup-plugin-terser'); const config = { input: './src/index.js' output: [ { file: './build/assets/js/main.js', format: 'iife', }, { file: '/build/assets/js/main.min.js', format: 'iife', plugins: [terser()], }, ] plugins: [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ], }; module.exports = config;
⚠ rollup-plugin-terser
プラグインは sourcemap を出力できない cf. Allow configurable sourcemaps. · Issue #64 · TrySound/rollup-plugin-terser · GitHub
console.log
の削除
min 化したファイルからは console.log
を取り除きたい
- (gulp-remove-logging)https://www.npmjs.com/package/gulp-remove-logging
- Last publish:
6 years ago
, ☆6
- Last publish:
- (@rollup/plugin-strip)https://www.npmjs.com/package/@rollup/plugin-strip
- Last publish: `a year ago, rollup/plugins 内で開発されている
gulp の方は開発がかなり前に止まっているし Star も少ないので Rollup のプラグインを使うのが良さそうです。
⚠ @rollup/plugin-strip
プラグインは output 内の plugin として使えないようです
rollup.config.js
const { nodeResolve } = require('@rollup/plugin-node-resolve'); const { babel } = require('@rollup/plugin-babel'); const { terser } = require('rollup-plugin-terser'); + const strip = require('@rollup/plugin-strip'); const config = { input: './src/index.js' output: [ { file: './build/assets/js/main.js', format: 'iife', }, { file: '/build/assets/js/main.min.js', format: 'iife', - plugins: [terser()], + plugins: [terser(), strip()], }, ] plugins: [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ], }; module.exports = config;
上記の様に output 内の plugin で使用しようとしても正しく動作しません 👇
$ npx npx rollup --config ./src/index.js → ./build/assets/js/main.js, ./build/assets/js/main.min.js... (!) The "transform" hook used by the output plugin strip is a build time hook and will not be run for that plugin. Either this plugin cannot be used as an output plugin, or it should have an option to configure it as an output plugin. created ./build/assets/js/main.js, ./build/assets/js/main.min.js
出力された main.min.js
の console.log
は削除されず残ったまま…
どうやら @rollup/plugin-strip は全体の plugin としてしか動作しないようです。
production / development で場合分けをする
Rollup は --environment <values>
で環境変数を渡せるので、これを利用する
rollup.config.js
const { nodeResolve } = require('@rollup/plugin-node-resolve'); const { babel } = require('@rollup/plugin-babel'); const { terser } = require('rollup-plugin-terser'); const strip = require('@rollup/plugin-strip'); const isProduction = process.env.MODE === 'production' || false; const input = './src/index.js'; const outputFile = isProduction ? '/build/assets/js/main.min.js' : './build/assets/js/main.js'; const format = 'iife'; const plugins = [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ]; const config = () => { if (isProduction) { return { input, output: { file: outputFile, format, plugins: [terser()], }, plugins: [...plugins, strip()], }; } else { return { input, output: { file: outputFile, format, }, plugins: [...plugins], }; } }; module.exports = config();
npm script
{ "scripts": { "build:dev": "npx rollup --config", "build:prod": "npx rollup --config --environment MODE:production", "build": "npm run build:dev && npm run build:prod" }, }
👇
$ npm run build > npx rollup --config ./src/index.js → ./build/assets/js/main.js... created ./build/assets/js/main.js > npx rollup --config --environment MODE:production ./src/index.js → ./build/assets/js/main.min.js... created ./build/assets/js/main.min.js
build が 2回実行されてしまいますが、minify する際のみ console.log
を取り除くことができました!
gulp からの呼び出し
gulp から呼び出す場合 Rollup の --environment <values>
をコマンドから渡すことができません。また gulp 内で使っている rollup
メソッドは設定ファイルを引数に取るので環境変数を渡すことはできなそうです。
export function rollup(options: RollupOptions): Promise<RollupBuild>; export interface RollupOptions extends InputOptions { // This is included for compatibility with config files but ignored by rollup.rollup output?: OutputOptions | OutputOptions[]; }
gulp から呼び出す場合は process.env.NODE_ENV
を gulp に渡して rollup
メソッドに渡す設定ファイルを変更するのが良さそうです
方針
NODE_ENV=production npx gulp build
=> minify する Rollup の設定を使うnpx gulp build
=> minify しない Rollup の設定を使う
/root |- /rollup | |- config.common.js # 共通設定 | |- config.dev.js # development用の設定 | |- config.prod.js # production用の共通設定 |- gulpfile.js
Rollup の設定を分割する
config.common.js
require { nodeResolve } = require('@rollup/plugin-node-resolve'); require { babel } = require('@rollup/plugin-babel'); exports.input = './src/index.js'; exports.outputDir = './build/assets/js'; const outputFile = `${outputDir}/main.js`; exports.output = { file: outputFile, // esm, cjs, amd, system, iife, umd format: 'iife', }; exports.plugins = [nodeResolve(), babel({ babelHelpers: 'bundled' })];
config.dev.js
const { input, output, plugins } = require('./config.common)'; const config = { input, output: { ...output, }, plugins: [...plugins], }; module.exports = config;
config.prod.js
const { terser } = require('rollup-plugin-terser'); const strip = require('@rollup/plugin-strip'); const { input, output, outputDir, plugins } = require('./config.common'); const config = { input, output: { ...output, file: `${outputDir}/main.min.js`, plugins: [terser()], }, plugins: [ ...plugins, strip({ labels: ['unittest'], }), ], }; module.exports = config;
gulpfile の設定を修正する
gulpfile.js
'use strict'; const { watch, parallel, series } = require('rollup'); const { rollup } = require('rollup'); - const config = require('./rollup.config'); + // 場合分けして Dynamic import もできると思うが複雑になりそうなので試さない + const configDev = require('./rollup/config.dev.mjs'); + const configProd = require('./rollup/config.prod.mjs'); + const isProduction = process.env.NODE_ENV === 'production' || false; + const config = isProduction ? configProd : configDev; const buildJS = async (cb) => { await rollup(config) .then((bundle) => { bundle.write(config.output); }) .catch((error) => { if (cb) { cb(); } console.error(error); }); cb(); }; const buildJSTask = series(buildJS); exports.default = () => { watch(['./src/**/*.js'], buildJSTask); }; exports.build = parallel(buildJSTask);
gulp を実行する npm script
package.json
{ "scripts": { + "gulp:build:dev": "npx gulp build", + "gulp:build:prod": "NODE_ENV=production npx gulp build", + "gulp:build": "npm run gulp:build:dev && npm run gulp:build:prod", + "gulp:dev": "npx gulp", + "gulp:dev:prod": "NODE_ENV=production npx gulp", "build:dev": "npx rollup --config", "build:prod": "npx rollup --config --environment MODE:production", "build": "npm run build:dev && npm run build:prod" }, }
npm run gulp:build
を実行して main.js
と minify され console.log
が取り除かれた main.min.js
ができていればOK
gizip
- (gulp-gzip)https://www.npmjs.com/package/gulp-gzip
- Last publish:
5 years ago
, ☆177
- Last publish:
- (rollup-plugin-gzip)https://www.npmjs.com/package/rollup-plugin-gzip
- Last publish:
4 months ago
, ☆36
- Last publish:
どっちもどっちと言う感じです。CSS も gzip 化するのであれば gulp のプラグインにすればインストールするパッケージが 1 つで済みそうです
Rollup で gzip 化
$ npm i -D rollup-plugin-gzip # Brotil 化 $ npm i -D zlib util
rollup.config.js
const { nodeResolve } = require('@rollup/plugin-node-resolve'); const { babel } = require('@rollup/plugin-babel'); const { terser } = require('rollup-plugin-terser'); const strip = require('@rollup/plugin-strip'); + const gzipPlugin = require('rollup-plugin-gzip'); + const { brotliCompress } = require('zlib'); + const { promisify } = require('util'); const isProduction = process.env.MODE === 'production' || false; const input = './src/index.js'; const outputFile = isProduction ? '/build/assets/js/main.min.js' : './build/assets/js/main.js'; const format = 'iife'; const plugins = [ nodeResolve(), babel({ babelHelpers: 'bundled' }), ]; const config = () => { if (isProduction) { return { input, output: { file: outputFile, format, - plugins: [terser()], + plugins: [ + terser(), + gzipPlugin({ + fileName: '.gz', + }), + // Brotil compression as .br files + gzipPlugin({ + customCompression: (content) => brotliPromise(Buffer.from(content)), + fileName: '.br', + }), + ], }, plugins: [...plugins, strip()], }; } else { return { input, output: { file: outputFile, format, }, plugins: [...plugins], }; } }; module.exports = config();
gulp で gzip 化
$ npm i -D gulp-gzip # Brotli 化 $ npm i -D gulp-brotli zlib util
gulpfile.js
'use strict'; const { watch, parallel, series, src, dest } = require('rollup'); const { rollup } = require('rollup'); + const gzip = require('gulp-gzip'); + const gulpBrotli = require('gulp-brotli'); const configDev = require('./rollup/config.dev.mjs'); const configProd = require('./rollup/config.prod.mjs'); const isProduction = process.env.NODE_ENV === 'production' || false; const config = isProduction ? configProd : configDev; const buildJS = async (cb) => { /// 略 }; + const destDir = './build/assets/js'; + const gzipJS = (cb) => { + return src(`${destDir}/*.min.js`) + .pipe(gzip()) + .pipe(dest(`${destDir}`)); + }; + const brotliJS = (cb) => { + return src(`${destDir}/main.min.js`) + .pipe( + gulpBrotli.compress({ + extension: 'br', + }) + ) + .pipe(dest(`${destDir}`)); + }; - const buildJSTask = series(buildJS); + const buildJSTask = isProduction + ? series(buildJS, parallel(gzipJS, brotliJS)) + : series(buildJS); exports.default = () => { watch(['./src/**/*.js'], buildJSTask); }; exports.build = parallel(buildJSTask);
npm run gulp:build
(npm run NODE_ENV=production npx gulp
) を実行して main.min.js.gzip
と main.min.js.br
が生成されていればOK
所管
ココまで書いてて何だけど、gulp は総じて開発が止まっている印象ですし、CommonJS とかで結構めんどくさいから Gulp を使わない方針にするか Rollup 諦めて webpack でゴリッとやってしまったほうが楽だったかも知れない… (cf. webpack 5 CSS だけコンパイルしたい - かもメモ) Rollup についての知見をつけることができたのでよし!
[参考]
- 本番環境用のビルド | Vite
- Quick Start | gulp.js
- Rollup
- Gulp
- SCSS