Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate build process with GCC #11483

Merged
merged 3 commits into from
Nov 8, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-inject": "^2.0.0",
"rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-prettier": "^0.3.0",
"rollup-plugin-replace": "^1.1.1",
"rollup-plugin-uglify": "^1.0.1",
"rollup-plugin-strip-banner": "^0.2.0",
"run-sequence": "^1.1.4",
"through2": "^2.0.0",
"tmp": "~0.0.28",
"typescript": "~1.8.10",
"uglify-js": "^2.5.0",
"yargs": "^6.3.0"
},
"devEngines": {
Expand Down
276 changes: 88 additions & 188 deletions scripts/rollup/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

const rollup = require('rollup').rollup;
const babel = require('rollup-plugin-babel');
const closure = require('rollup-plugin-closure-compiler-js');
const commonjs = require('rollup-plugin-commonjs');
const alias = require('rollup-plugin-alias');
const uglify = require('rollup-plugin-uglify');
const prettier = require('rollup-plugin-prettier');
const replace = require('rollup-plugin-replace');
const stripBanner = require('rollup-plugin-strip-banner');
const chalk = require('chalk');
const join = require('path').join;
const resolve = require('path').resolve;
Expand All @@ -24,7 +26,6 @@ const syncReactNativeRT = require('./sync').syncReactNativeRT;
const syncReactNativeCS = require('./sync').syncReactNativeCS;
const Packaging = require('./packaging');
const Header = require('./header');
const closure = require('rollup-plugin-closure-compiler-js');

const UMD_DEV = Bundles.bundleTypes.UMD_DEV;
const UMD_PROD = Bundles.bundleTypes.UMD_PROD;
Expand All @@ -51,31 +52,16 @@ const errorCodeOpts = {
errorMapFilePath: 'scripts/error-codes/codes.json',
};

function getHeaderSanityCheck(bundleType, globalName) {
switch (bundleType) {
case FB_DEV:
case FB_PROD:
case RN_DEV:
case RN_PROD:
let hasteFinalName = globalName;
switch (bundleType) {
case FB_DEV:
case RN_DEV:
hasteFinalName += '-dev';
break;
case FB_PROD:
case RN_PROD:
hasteFinalName += '-prod';
break;
}
return hasteFinalName;
case UMD_DEV:
case UMD_PROD:
return reactVersion;
default:
return null;
}
}
const closureOptions = {
compilationLevel: 'SIMPLE',
languageIn: 'ECMASCRIPT5_STRICT',
languageOut: 'ECMASCRIPT5_STRICT',
env: 'CUSTOM',
warningLevel: 'QUIET',
applyInputSourceMaps: false,
useTypesForOptimization: false,
processCommonJsModules: false,
};

function getBanner(bundleType, globalName, filename, moduleType) {
if (moduleType === RECONCILER) {
Expand Down Expand Up @@ -261,7 +247,6 @@ function getRollupOutputOptions(
return Object.assign(
{},
{
banner: getBanner(bundleType, globalName, filename, moduleType),
destDir: 'build/',
file:
'build/' +
Expand All @@ -270,7 +255,6 @@ function getRollupOutputOptions(
filename,
globalName
),
footer: getFooter(bundleType, filename, moduleType),
format,
globals,
interop: false,
Expand All @@ -280,13 +264,6 @@ function getRollupOutputOptions(
);
}

function stripEnvVariables(production) {
return {
__DEV__: production ? 'false' : 'true',
'process.env.NODE_ENV': production ? "'production'" : "'development'",
};
}

function getFormat(bundleType) {
switch (bundleType) {
case UMD_DEV:
Expand Down Expand Up @@ -323,86 +300,21 @@ function getFilename(name, globalName, bundleType) {
}
}

function getUglifyConfig(configs) {
var mangle = configs.mangle;
var preserveVersionHeader = configs.preserveVersionHeader;
var removeComments = configs.removeComments;
var headerSanityCheck = configs.headerSanityCheck;
return {
warnings: false,
compress: {
screw_ie8: true,
dead_code: true,
unused: true,
drop_debugger: true,
// we have a string literal <script> that we don't want to evaluate
// for FB prod bundles (where we disable mangling)
evaluate: mangle,
booleans: true,
// Our www inline transform combined with Jest resetModules is confused
// in some rare cases unless we keep all requires at the top:
hoist_funs: mangle,
},
output: {
beautify: !mangle,
comments(node, comment) {
if (preserveVersionHeader && comment.pos === 0 && comment.col === 0) {
// Keep the very first comment (the bundle header) in prod bundles.
if (
headerSanityCheck &&
comment.value.indexOf(headerSanityCheck) === -1
) {
// Sanity check: this doesn't look like the bundle header!
throw new Error(
'Expected the first comment to be the file header but got: ' +
comment.value
);
}
return true;
}
return !removeComments;
},
},
mangle: mangle
? {
toplevel: true,
screw_ie8: true,
}
: false,
};
}

// FB uses require('React') instead of require('react').
// We can't set up a forwarding module due to case sensitivity issues.
function rewriteFBReactImport() {
return {
transformBundle(source) {
return source.replace(/require\(['"]react['"]\)/g, "require('React')");
},
};
}

// Strip 'use strict' directives in individual modules
// because we always emit them in the file headers.
// The whole bundle is strict.
function stripUseStrict() {
return {
transform(source) {
return source.replace(/['"]use strict['"']/g, '');
},
};
}

// Plugin that writes to the error code file so that by the time it is picked
// up by Babel, the errors are already extracted.
function writeErrorCodes() {
const flush = extractErrorCodes(errorCodeOpts);
return {
transform(source) {
flush(source);
return source;
},
};
function isProductionBundleType(bundleType) {
switch (bundleType) {
case UMD_DEV:
case NODE_DEV:
case FB_DEV:
case RN_DEV:
return false;
case UMD_PROD:
case NODE_PROD:
case FB_PROD:
case RN_PROD:
return true;
default:
throw new Error(`Unknown type: ${bundleType}`);
}
}

function getPlugins(
Expand All @@ -416,88 +328,77 @@ function getPlugins(
modulesToStub,
featureFlags
) {
const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
const shims = Modules.getShims(bundleType, entry, featureFlags);
const plugins = [
const isProduction = isProductionBundleType(bundleType);
const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure this is the best name. Maybe isUMDBundle?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be more bundle types where top-level things are declared in global scope. In theory. :-)

const isFBBundle = bundleType === FB_DEV || bundleType === FB_PROD;
const isRNBundle = bundleType === RN_DEV || bundleType === RN_PROD;
const shouldStayReadable = isFBBundle || isRNBundle;
return [
// Extract error codes from invariant() messages into a file.
shouldExtractErrors && writeErrorCodes(),
shouldExtractErrors && {
transform(source) {
findAndRecordErrorCodes(source);
return source;
},
},
// Shim some modules for www custom behavior and optimizations.
alias(shims),
// Use Node resolution mechanism.
resolvePlugin({
skip: externals,
}),
// Remove license headers from individual modules
stripBanner({
exclude: 'node_modules/**/*',
}),
// Compile to ES5.
babel(getBabelConfig(updateBabelOptions, bundleType)),
stripUseStrict(),
].filter(Boolean);

const headerSanityCheck = getHeaderSanityCheck(bundleType, globalName);
switch (bundleType) {
case UMD_DEV:
case NODE_DEV:
plugins.push(replace(stripEnvVariables(false)), commonjs());
break;
case UMD_PROD:
case NODE_PROD:
plugins.push(
replace(stripEnvVariables(true)),
commonjs(),
closure({
compilationLevel: 'SIMPLE',
languageIn: 'ECMASCRIPT5_STRICT',
languageOut: 'ECMASCRIPT5_STRICT',
env: 'CUSTOM',
warningLevel: 'QUIET',
// Remove 'use strict' from individual source files.
{
transform(source) {
return source.replace(/['"]use strict['"']/g, '');
},
},
// Turn __DEV__ and process.env checks into constants.
replace({
__DEV__: isProduction ? 'false' : 'true',
'process.env.NODE_ENV': isProduction ? "'production'" : "'development'",
}),
// We still need CommonJS for external deps like object-assign.
commonjs(),
// www still needs require('React') rather than require('react')
isFBBundle && {
transformBundle(source) {
return source.replace(/require\(['"]react['"]\)/g, "require('React')");
},
},
// Apply dead code elimination and/or minification.
isProduction &&
closure(
Object.assign({}, closureOptions, {
// Don't let it create global variables in the browser.
// https://github.com/facebook/react/issues/10909
assumeFunctionWrapper: bundleType !== UMD_PROD,
applyInputSourceMaps: false,
useTypesForOptimization: false,
processCommonJsModules: false,
assumeFunctionWrapper: !isInGlobalScope,
// Works because `google-closure-compiler-js` is forked in Yarn lockfile.
// We can remove this if GCC merges my PR:
// https://github.com/google/closure-compiler/pull/2707
// and then the compiled version is released via `google-closure-compiler-js`.
renaming: !shouldStayReadable,
})
);
break;
case FB_DEV:
plugins.push(
replace(stripEnvVariables(false)),
commonjs(),
rewriteFBReactImport()
);
break;
case FB_PROD:
plugins.push(
replace(stripEnvVariables(true)),
commonjs(),
uglify(
getUglifyConfig({
mangle: bundleType !== FB_PROD,
preserveVersionHeader: bundleType === UMD_PROD,
// leave comments in for source map debugging purposes
// they will be stripped as part of FB's build process
removeComments: bundleType !== FB_PROD,
headerSanityCheck,
})
),
rewriteFBReactImport()
);
break;
case RN_DEV:
case RN_PROD:
plugins.push(
replace(stripEnvVariables(bundleType === RN_PROD)),
commonjs(),
uglify(
getUglifyConfig({
mangle: false,
preserveVersionHeader: true,
removeComments: true,
headerSanityCheck,
})
)
);
break;
}
plugins.push(
),
// Add the whitespace back if necessary.
shouldStayReadable && prettier(),
// License and haste headers, top-level `if` blocks.
{
transformBundle(source) {
const banner = getBanner(bundleType, globalName, filename, moduleType);
const footer = getFooter(bundleType, filename, moduleType);
return banner + '\n' + source + '\n' + footer;
},
},
// Record bundle size.
sizes({
getSize: (size, gzip) => {
const key = `${filename} (${bundleType})`;
Expand All @@ -506,9 +407,8 @@ function getPlugins(
gzip,
};
},
})
);
return plugins;
}),
].filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure this is super obvious as to what’s happening?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno, seems like a common pattern with plugin systems?

screen shot 2017-11-08 at 08 11 31

Happy to change to something explicit if you prefer.

}

function createBundle(bundle, bundleType) {
Expand Down
Loading