From 4029378035c2741debdaee38a3904349c21e0902 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Sat, 17 May 2014 15:13:59 -0700 Subject: [PATCH] When packaging, copy the code and inline `__DEV__` Fixes #812. Previously, this code module.exports = moo(); function moo() { return __DEV__; } would be transformed to module.exports = moo(); function moo() { return "production" !== process.env.NODE_ENV; } Now, it's transformed to: if ("production" !== process.env.NODE_ENV) { var moo = function() { return true; }; module.exports = moo(); } else { var moo = function() { return false; }; module.exports = moo(); } which reduces the getter cost to one test at require time instead of inline for every `__DEV__` check, warning, and invariant. The unminified build is about twice as large now (but it's about the same after gzipping) and the minified build is the same size. --- package.json | 2 +- vendor/constants.js | 146 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 128 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index ba7639ee462ef..beeedc0e0a4e5 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ }, "preferGlobal": true, "commonerConfig": { - "version": 4 + "version": 5 }, "scripts": { "test": "./node_modules/.bin/grunt test" diff --git a/vendor/constants.js b/vendor/constants.js index 3eba587d67e33..47a4626d3a680 100644 --- a/vendor/constants.js +++ b/vendor/constants.js @@ -42,6 +42,96 @@ var DEV_EXPRESSION = builders.binaryExpression( function transform(ast, constants) { constants = constants || {}; + var devProgram = copyAst(ast.program); + var prodProgram = copyAst(ast.program); + + devProgram = transformDev(hoistFunctions(devProgram), constants); + prodProgram = transformProd(hoistFunctions(prodProgram), constants); + + return builders.program([ + builders.ifStatement( + DEV_EXPRESSION, + builders.blockStatement(devProgram.body), + builders.blockStatement(prodProgram.body) + ) + ]); +} + +function copyAst(node) { + if (node instanceof RegExp) { + return node; + } else if (node instanceof Array) { + return node.map(copyAst); + } else if (typeof node === "object" && node != null) { + var newNode = Object.create(Object.getPrototypeOf(node)); + for (var key in node) { + if (!Object.prototype.hasOwnProperty.call(node, key)) { + continue; + } + if (namedTypes.Node.check(node)) { + newNode[key] = copyAst(node[key]); + } else { + newNode[key] = node[key]; + } + } + + Object.defineProperty(newNode, "original", { + value: node.original, + configurable: false, + enumerable: false, + writable: true + }); + + return newNode; + } else { + return node; + } +} + +function isUseStrict(node) { + return node && + namedTypes.ExpressionStatement.check(node) && + namedTypes.Literal.check(node.expression) && + node.expression.value === "use strict"; +} + +function hoistFunctions(program) { + var functionVariableDeclarations = []; + + var body = program.body.slice(); + for (var i = 0; i < body.length; i++) { + var node = body[i]; + if (namedTypes.FunctionDeclaration.check(node)) { + functionVariableDeclarations.push( + builders.variableDeclaration("var", [ + builders.variableDeclarator( + node.id, + builders.functionExpression( + null, + node.params, + node.body, + node.generator, + node.expression, + // Switch to node.async after upgrading esprima-fb + false + ) + ) + ]) + ); + body.splice(i, 1); + i--; + } + } + + // Insert functions after "use strict", if present + body.splice.apply( + body, + [isUseStrict(body[0]) ? 1 : 0, 0].concat(functionVariableDeclarations) + ); + return builders.program(body); +} + +function transformDev(ast, constants) { return types.traverse(ast, function(node, traverse) { if (namedTypes.Identifier.check(node)) { // If the identifier is the property of a member expression @@ -53,12 +143,40 @@ function transform(ast, constants) { return false; } + if (node.name === '__DEV__') { + // Replace __DEV__ with 'true' + this.replace(builders.literal(true)); + return false; + // There could in principle be a constant called "hasOwnProperty", // so be careful always to use Object.prototype.hasOwnProperty. + } else if (hasOwn.call(constants, node.name)) { + this.replace(builders.literal(constants[node.name])); + return false; + } + } + }); +} + +function transformProd(ast, constants) { + return types.traverse(ast, function(node, traverse) { + if (namedTypes.Identifier.check(node)) { + // If the identifier is the property of a member expression + // (e.g. object.property), then it definitely is not a constant + // expression that we want to replace. + if (namedTypes.MemberExpression.check(this.parent.node) && + this.name === 'property' && + !this.parent.node.computed) { + return false; + } + if (node.name === '__DEV__') { - // replace __DEV__ with process.env.NODE_ENV !== 'production' - this.replace(DEV_EXPRESSION); + // Replace __DEV__ with 'false' + this.replace(builders.literal(false)); return false; + + // There could in principle be a constant called "hasOwnProperty", + // so be careful always to use Object.prototype.hasOwnProperty. } else if (hasOwn.call(constants, node.name)) { this.replace(builders.literal(constants[node.name])); return false; @@ -67,30 +185,20 @@ function transform(ast, constants) { } else if (namedTypes.CallExpression.check(node)) { if (namedTypes.Identifier.check(node.callee) && node.callee.name === 'invariant') { - // Truncate the arguments of invariant(condition, ...) - // statements to just the condition based on NODE_ENV - // (dead code removal will remove the extra bytes). + // Truncate the arguments of invariant(condition, ...) statements to + // just the condition this.replace( - builders.conditionalExpression( - DEV_EXPRESSION, - node, - builders.callExpression( - node.callee, - [node.arguments[0]] - ) + builders.callExpression( + node.callee, + [node.arguments[0]] ) ); return false; } else if (namedTypes.Identifier.check(node.callee) && node.callee.name === 'warning') { - // Eliminate warning(condition, ...) statements based on NODE_ENV - // (dead code removal will remove the extra bytes). + // Eliminate warning(condition, ...) statements this.replace( - builders.conditionalExpression( - DEV_EXPRESSION, - node, - builders.literal(null) - ) + builders.literal(null) ); } }