自作ライブラリ内の冗長なバリデートや console などを、アプリ開発時には生かしておいて本番ビルド時に除去したいなーと思っていたところ、React の __DEV__ 疑似グローバル変数がヒントになりました。
Rollup でバンドル時に NODE_ENV 環境変数を見て development / production で切り替える仕組みを考えました。
(サイズリミットのあるコンペに参加しているので少しでも小さくしたいです)
パッケージ構成
├── dist/
│ └── bundle.js // ビルド成果物
├── src/
│ ├── lib/
│ │ └── someTool.js // ライブラリ
│ ├── validators/
│ │ └── inputCheck.js // バリデータ
│ └── app.js // 開発対象アプリ
├── test/
│ ├── lib/
│ │ └── someTool.test.js // ライブラリのテスト
│ └── validators/
│ └── inputCheck.test.js // バリデータのテスト
├── package.json // npm スクリプト設定
└── rollup.config.js // replace・terser プラグイン設定
devDependencies
npm i -D rollup @rollup/plugin-replace cross-env
- rollup:モジュールバンドラ
- @rollup/plugin-replace:バンドル時に文字列置換してくれる
- cross-env:npm スクリプト実行時に環境変数を設定( OS の差を吸収)
package.json
script を追加。( Mac や Linux は cross-env が無くても動く)
{
"type": "module",
"devDependencies": {
"@rollup/plugin-replace": "^6.0.3",
"cross-env": "^10.1.0",
"rollup": "^4.62.2"
},
"scripts": {
"_build": "rollup -c ./rollup.config.js",
"debug": "cross-env NODE_ENV=development npm run _build",
"release": "cross-env NODE_ENV=production npm run _build",
"test": "node --test"
}
}
rollup.config.js
バンドル時に replace プラグインが __DEV__ を true / false に文字列置換する。
NODE_ENV 環境変数 が development なら true 、それ以外は false に。
import replace from '@rollup/plugin-replace';
export default () => {
return {
input: './src/app.js',
output: [
{
file: './dist/bundle.js',
format: 'esm',
sourcemap: true,
},
],
plugins: [
replace({
__DEV__: String(process.env.NODE_ENV === 'development'),
}),
],
};
};
サンプルコード
ライブラリ( someTool.js )
除去したい箇所に __DEV__ 判定を入れる。
import { inputCheck } from '../validators/inputCheck.js';
export function someTool(param) {
// 1行の場合
if (__DEV__) inputCheck(param);
// ブロックの場合
if (__DEV__) {
console.log('param = ' + param);
}
return 'some ' + param;
}
バリデータ( inputCheck.js )
アプリの開発時ビルド時に生かして、リリースビルド時に除去する。
export function inputCheck(param) {
if (param === undefined) throw new Error('param is a required');
}
開発対象アプリ( app.js )
こちらは特に意識せずライブラリを使用。
import { someTool } from './lib/someTool.js';
const someInput = document.querySelector('#someInput');
let result = someTool(someInput.value);
someInput.value = result;
ビルドする
アプリ開発時
npm run debug
バリデータや console が残っている。
無駄な if (true) は消してくれる。ブロックは残る。
function inputCheck(param) {
if (param === undefined) throw new Error('param is a required');
}
function someTool(param) {
// 1行の場合
inputCheck(param);
// ブロックの場合
{
console.log('param = ' + param);
}
return 'some ' + param;
}
const someInput = document.querySelector('#someInput');
let result = someTool(someInput.value);
someInput.value = result;
//# sourceMappingURL=bundle.js.map
本番ビルド時
npm run release
バリデータや console が除去される。
function inputCheck は使われないのでツリーシェイクされる。
function someTool(param) {
return 'some ' + param;
}
const someInput = document.querySelector('#someInput');
let result = someTool(someInput.value);
someInput.value = result;
//# sourceMappingURL=bundle.js.map
テスト
テスト時は __DEV__ をグローバル変数として扱い、常に true を設定
ライブラリのテスト( someTool.test.js )
import test from 'node:test';
import assert from 'node:assert/strict';
import { someTool } from '../../src/lib/someTool.js';
global.__DEV__ = true; // __DEV__ をグローバル変数として扱う
test('Some kind of test', () => {
assert.strictEqual(someTool('one'), 'some one');
});
バリデータのテスト( inputCheck.test.js )
import test from 'node:test';
import assert from 'node:assert/strict';
import { inputCheck } from '../../src/validators/inputCheck.js';
global.__DEV__ = true; // __DEV__ をグローバル変数として扱う
test('Positive test', () => {
assert.doesNotThrow(() => inputCheck('one'));
});
test('Undefined is illegal', () => {
assert.throws(() => inputCheck());
});
参考