Skip to content

Commit 75a902c

Browse files
committed
feat: support process.env.NODE_ENV
Statically resolve conditional code on process.env.NODE_ENV when possible. Store process.env.NODE_ENV value at bundling time for runtime consumption.
1 parent bdfab64 commit 75a902c

8 files changed

Lines changed: 404 additions & 25 deletions

File tree

lib/trace.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22
const hackMoment = require('./transformers/hack-moment');
3+
const processEnv = require('./transformers/process-env');
4+
const nodeEnvCondition = require('./transformers/node-env-condition');
35
const alias = require('./transformers/alias');
46
const conventionalAlias = require('./transformers/conventional-alias');
57
const text = require('./transformers/text');
@@ -28,6 +30,7 @@ module.exports = function(unit, opts = {}) {
2830

2931
if (cache) {
3032
const key = [
33+
process.env.NODE_ENV || 'development',
3134
version,
3235
path,
3336
moduleId,
@@ -49,6 +52,8 @@ module.exports = function(unit, opts = {}) {
4952
if (extname === '.js') {
5053
transformers.push(
5154
hackMoment,
55+
processEnv,
56+
nodeEnvCondition,
5257
esmToCjs,
5358
replace,
5459
cjsToAmd,

lib/transform.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function mergeUnit(unit, newUnit) {
3030
const {path, contents, sourceMap, moduleId, packageName, defined, deps, ...others} = newUnit;
3131
const merged = {...unit, ...others};
3232

33-
if (contents && unit.contents !== contents) {
33+
if (typeof contents === 'string' && unit.contents !== contents) {
3434
merged.contents = contents;
3535
}
3636

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Eliminate code branch like if (process.env.NODE_ENV !== 'production') { ... }
2+
// Note we don't deal with if (...) {} else if (...) {}
3+
//
4+
// For the missed usage of process.env.NODE_ENV (like _process.env.NODE_ENV),
5+
// it will be resolved at runtime with correct NODE_ENV value packed at build time.
6+
// The runtime value is handled by ./process-env.js
7+
const astMatcher = require('ast-matcher');
8+
const ensureParsed = astMatcher.ensureParsed;
9+
require('../ensure-parser-set')();
10+
const modifyCode = require('modify-code').default;
11+
12+
const ifStatement = astMatcher('if ( __any_condition ) { __anl }');
13+
const ifElseStatement = astMatcher('if ( __any_condition ) { __anl } else { __anl }');
14+
15+
const envMatch1 = astMatcher('process.env.NODE_ENV');
16+
const envMatch2 = astMatcher("process.env['NODE_ENV']");
17+
18+
module.exports = function(unit, _nodeEnv /* for test */) {
19+
if (!_nodeEnv) {
20+
_nodeEnv = process.env.NODE_ENV;
21+
}
22+
23+
const {contents, sourceMap, path} = unit;
24+
const parsed = ensureParsed(contents);
25+
const filename = sourceMap && sourceMap.file || path;
26+
const m = modifyCode(contents, filename);
27+
28+
const ifMatch = ifStatement(parsed);
29+
if (ifMatch) {
30+
ifMatch.forEach(result => {
31+
const {condition} = result.match;
32+
const wanted = nodeEnvCheck(condition, _nodeEnv);
33+
if (typeof wanted === 'boolean') {
34+
if (wanted) {
35+
// fix the condition to true
36+
m.replace(condition.start, condition.end, 'true');
37+
} else {
38+
// remove the whole block
39+
m.delete(result.node.start, result.node.end);
40+
}
41+
}
42+
});
43+
}
44+
45+
const ifElseMatch = ifElseStatement(parsed);
46+
if (ifElseMatch) {
47+
ifElseMatch.forEach(result => {
48+
const {condition} = result.match;
49+
const wanted = nodeEnvCheck(condition, _nodeEnv);
50+
51+
if (typeof wanted === 'boolean') {
52+
const branch = wanted ?
53+
// retain consequent branch
54+
contents.slice(result.node.consequent.start, result.node.consequent.end) :
55+
// retain alternate branch
56+
contents.slice(result.node.alternate.start, result.node.alternate.end);
57+
m.replace(result.node.start, result.node.end, 'if (true) ' + branch);
58+
}
59+
});
60+
}
61+
62+
const result = m.transform();
63+
if (result.code === contents) {
64+
// no change
65+
return;
66+
}
67+
68+
return {
69+
contents: result.code,
70+
sourceMap: result.map
71+
};
72+
};
73+
74+
// returns undefined if the exp is not a check on NODE_ENV
75+
function nodeEnvCheck(exp, _nodeEnv) {
76+
const {type, operator, left, right} = exp;
77+
if (type !== 'BinaryExpression') return;
78+
79+
let targetValue;
80+
let possibleRef;
81+
if (left.type === 'Literal') {
82+
targetValue === left.value;
83+
possibleRef === right;
84+
} else if (right.type === 'Literal') {
85+
targetValue = right.value;
86+
possibleRef = left;
87+
}
88+
89+
if (!isNodeEnvExpression(possibleRef)) return;
90+
// eslint-disable-next-line no-new-func
91+
return (new Function(`return ${JSON.stringify(_nodeEnv)} ${operator} ${JSON.stringify(targetValue)};`))();
92+
}
93+
94+
function isNodeEnvExpression(exp) {
95+
const env1 = envMatch1(exp);
96+
if (env1 && env1.length === 1 && env1[0].node === exp) return true;
97+
const env2 = envMatch2(exp);
98+
if (env2 && env2.length === 1 && env2[0].node === exp) return true;
99+
return false;
100+
}

lib/transformers/process-env.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Fill up NODE_ENV (and more possibly) to process.env.
2+
module.exports = function(unit, _env /* for test */) {
3+
const {contents, packageName} = unit;
4+
// only for the process stub
5+
if (packageName === 'process') {
6+
if (!_env) {
7+
_env = process.env;
8+
}
9+
const fillup = {
10+
NODE_ENV: _env.NODE_ENV || ''
11+
// Note: whenever add a new env variable here,
12+
// have to add the variable to cache key in ../trace.js too.
13+
// Because env var is a factor affecting transform result.
14+
};
15+
return {
16+
contents: contents + `\nprocess.env = ${JSON.stringify(fillup)};\n`
17+
};
18+
}
19+
};

test/all-browser-spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ require('./transformers/replace.spec');
2121
require('./transformers/shim-amd.spec');
2222
require('./transformers/text.spec');
2323
require('./transformers/wasm.spec');
24+
require('./transformers/node-env-condition.spec');
25+
require('./transformers/process-env.spec');
2426
require('./shared.spec');
2527
require('./stub-module.spec');
2628
require('./inject-css.spec');

0 commit comments

Comments
 (0)