'use strict'; 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 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; const resolvePlugin = require('rollup-plugin-node-resolve'); const fs = require('fs'); const rimraf = require('rimraf'); const argv = require('minimist')(process.argv.slice(2)); const Modules = require('./modules'); const Bundles = require('./bundles'); const sizes = require('./plugins/sizes-plugin'); const Stats = require('./stats'); const extractErrorCodes = require('../error-codes/extract-errors'); const syncReactDom = require('./sync').syncReactDom; const syncReactNative = require('./sync').syncReactNative; const syncReactNativeRT = require('./sync').syncReactNativeRT; const syncReactNativeCS = require('./sync').syncReactNativeCS; const Packaging = require('./packaging'); const Wrappers = require('./wrappers'); const UMD_DEV = Bundles.bundleTypes.UMD_DEV; const UMD_PROD = Bundles.bundleTypes.UMD_PROD; const NODE_DEV = Bundles.bundleTypes.NODE_DEV; const NODE_PROD = Bundles.bundleTypes.NODE_PROD; const FB_DEV = Bundles.bundleTypes.FB_DEV; const FB_PROD = Bundles.bundleTypes.FB_PROD; const RN_DEV = Bundles.bundleTypes.RN_DEV; const RN_PROD = Bundles.bundleTypes.RN_PROD; const requestedBundleTypes = (argv.type || '') .split(',') .map(type => type.toUpperCase()); const requestedBundleNames = (argv._[0] || '') .split(',') .map(type => type.toLowerCase()); const syncFbsource = argv['sync-fbsource']; const syncWww = argv['sync-www']; const shouldExtractErrors = argv['extract-errors']; const errorCodeOpts = { errorMapFilePath: 'scripts/error-codes/codes.json', }; const closureOptions = { compilationLevel: 'SIMPLE', languageIn: 'ECMASCRIPT5_STRICT', languageOut: 'ECMASCRIPT5_STRICT', env: 'CUSTOM', warningLevel: 'QUIET', applyInputSourceMaps: false, useTypesForOptimization: false, processCommonJsModules: false, }; function getBabelConfig(updateBabelOptions, bundleType, filename) { let options = { exclude: 'node_modules/**', presets: [], plugins: [], }; if (updateBabelOptions) { options = updateBabelOptions(options); } switch (bundleType) { case FB_DEV: case FB_PROD: case RN_DEV: case RN_PROD: return Object.assign({}, options, { plugins: options.plugins.concat([ // Wrap warning() calls in a __DEV__ check so they are stripped from production. require('./plugins/wrap-warning-with-env-check'), ]), }); case UMD_DEV: case UMD_PROD: case NODE_DEV: case NODE_PROD: return Object.assign({}, options, { plugins: options.plugins.concat([ // Use object-assign polyfill in open source resolve('./scripts/babel/transform-object-assign-require'), // Minify invariant messages require('../error-codes/replace-invariant-error-codes'), // Wrap warning() calls in a __DEV__ check so they are stripped from production. require('./plugins/wrap-warning-with-env-check'), ]), }); default: return options; } } function handleRollupWarnings(warning) { if (warning.code === 'UNRESOLVED_IMPORT') { console.error(warning.message); process.exit(1); } if (warning.code === 'UNUSED_EXTERNAL_IMPORT') { const match = warning.message.match(/external module '([^']+)'/); if (!match || typeof match[1] !== 'string') { throw new Error( 'Could not parse a Rollup warning. ' + 'Fix this method.' ); } const importSideEffects = Modules.getImportSideEffects(); const path = match[1]; if (typeof importSideEffects[path] !== 'boolean') { throw new Error( 'An external module "' + path + '" is used in a DEV-only code path ' + 'but we do not know if it is safe to omit an unused require() to it in production. ' + 'Please add it to the `importSideEffects` list in `scripts/rollup/modules.js`.' ); } // Don't warn. We will remove side effectless require() in a later pass. return; } console.warn(warning.message || warning); } function getRollupOutputOptions( filename, format, bundleType, globals, globalName, moduleType ) { return Object.assign( {}, { destDir: 'build/', file: 'build/' + Packaging.getOutputPathRelativeToBuildFolder( bundleType, filename, globalName ), format, globals, interop: false, name: globalName, sourcemap: false, } ); } function getFormat(bundleType) { switch (bundleType) { case UMD_DEV: case UMD_PROD: return `umd`; case NODE_DEV: case NODE_PROD: case FB_DEV: case FB_PROD: case RN_DEV: case RN_PROD: return `cjs`; } } function getFilename(name, globalName, bundleType) { // we do this to replace / to -, for react-dom/server name = name.replace('/', '-'); switch (bundleType) { case UMD_DEV: return `${name}.development.js`; case UMD_PROD: return `${name}.production.min.js`; case NODE_DEV: return `${name}.development.js`; case NODE_PROD: return `${name}.production.min.js`; case FB_DEV: case RN_DEV: return `${globalName}-dev.js`; case FB_PROD: case RN_PROD: return `${globalName}-prod.js`; } } 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( entry, externals, updateBabelOptions, filename, bundleType, globalName, moduleType, modulesToStub, featureFlags ) { const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts); const shims = Modules.getShims(bundleType, entry, featureFlags); const isProduction = isProductionBundleType(bundleType); const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD; 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 && { 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)), // 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: !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, }) ), // Add the whitespace back if necessary. shouldStayReadable && prettier(), // License and haste headers, top-level `if` blocks. { transformBundle(source) { return Wrappers.wrapBundle( source, bundleType, globalName, filename, moduleType ); }, }, // Record bundle size. sizes({ getSize: (size, gzip) => { const key = `${filename} (${bundleType})`; Stats.currentBuildResults.bundleSizes[key] = { size, gzip, }; }, }), ].filter(Boolean); } function createBundle(bundle, bundleType) { const shouldSkipBundleType = bundle.bundleTypes.indexOf(bundleType) === -1; if (shouldSkipBundleType) { return Promise.resolve(); } if (requestedBundleTypes.length > 0) { const isAskingForDifferentType = requestedBundleTypes.every( requestedType => bundleType.indexOf(requestedType) === -1 ); if (isAskingForDifferentType) { return Promise.resolve(); } } if (requestedBundleNames.length > 0) { const isAskingForDifferentNames = requestedBundleNames.every( requestedName => bundle.label.indexOf(requestedName) === -1 ); if (isAskingForDifferentNames) { return Promise.resolve(); } } const filename = getFilename(bundle.entry, bundle.global, bundleType); const logKey = chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`); const format = getFormat(bundleType); const packageName = Packaging.getPackageName(bundle.entry); let resolvedEntry = require.resolve(bundle.entry); if (bundleType === FB_DEV || bundleType === FB_PROD) { const resolvedFBEntry = resolvedEntry.replace('.js', '.fb.js'); if (fs.existsSync(resolvedFBEntry)) { resolvedEntry = resolvedFBEntry; } } const shouldBundleDependencies = bundleType === UMD_DEV || bundleType === UMD_PROD; const peerGlobals = Modules.getPeerGlobals( bundle.externals, bundle.moduleType ); let externals = Object.keys(peerGlobals); if (!shouldBundleDependencies) { const deps = Modules.getDependencies(bundleType, bundle.entry); externals = externals.concat(deps); } const importSideEffects = Modules.getImportSideEffects(); const pureExternalModules = Object.keys(importSideEffects).filter( module => !importSideEffects[module] ); console.log(`${chalk.bgYellow.black(' BUILDING ')} ${logKey}`); return rollup({ input: resolvedEntry, pureExternalModules, external(id) { const containsThisModule = pkg => id === pkg || id.startsWith(pkg + '/'); const isProvidedByDependency = externals.some(containsThisModule); if (!shouldBundleDependencies && isProvidedByDependency) { return true; } return !!peerGlobals[id]; }, onwarn: handleRollupWarnings, plugins: getPlugins( bundle.entry, externals, bundle.babel, filename, bundleType, bundle.global, bundle.moduleType, bundle.modulesToStub, bundle.featureFlags ), // We can't use getters in www. legacy: bundleType === FB_DEV || bundleType === FB_PROD, }) .then(result => result.write( getRollupOutputOptions( filename, format, bundleType, peerGlobals, bundle.global, bundle.moduleType ) ) ) .then(() => Packaging.createNodePackage(bundleType, packageName, filename)) .then(() => { console.log(`${chalk.bgGreen.black(' COMPLETE ')} ${logKey}\n`); }) .catch(error => { if (error.code) { console.error(`\x1b[31m-- ${error.code} (${error.plugin}) --`); console.error(error.message); console.error(error.loc); console.error(error.codeFrame); } else { console.error(error); } process.exit(1); }); } // clear the build directory rimraf('build', () => { // create a new build directory fs.mkdirSync('build'); // create the packages folder for NODE+UMD bundles fs.mkdirSync(join('build', 'packages')); // create the dist folder for UMD bundles fs.mkdirSync(join('build', 'dist')); const tasks = [ Packaging.createFacebookWWWBuild, Packaging.createReactNativeBuild, Packaging.createReactNativeRTBuild, Packaging.createReactNativeCSBuild, ]; for (const bundle of Bundles.bundles) { tasks.push( () => createBundle(bundle, UMD_DEV), () => createBundle(bundle, UMD_PROD), () => createBundle(bundle, NODE_DEV), () => createBundle(bundle, NODE_PROD), () => createBundle(bundle, FB_DEV), () => createBundle(bundle, FB_PROD), () => createBundle(bundle, RN_DEV), () => createBundle(bundle, RN_PROD) ); } if (syncFbsource) { tasks.push(() => syncReactNative(join('build', 'react-native'), syncFbsource) ); tasks.push(() => syncReactNativeRT(join('build', 'react-rt'), syncFbsource) ); tasks.push(() => syncReactNativeCS(join('build', 'react-cs'), syncFbsource) ); } else if (syncWww) { tasks.push(() => syncReactDom(join('build', 'facebook-www'), syncWww)); } // rather than run concurrently, opt to run them serially // this helps improve console/warning/error output // and fixes a bunch of IO failures that sometimes occurred return runWaterfall(tasks) .then(() => { // output the results console.log(Stats.printResults()); // save the results for next run Stats.saveResults(); if (shouldExtractErrors) { console.warn( '\nWarning: this build was created with --extract-errors enabled.\n' + 'this will result in extremely slow builds and should only be\n' + 'used when the error map needs to be rebuilt.\n' ); } }) .catch(err => { console.error(err); process.exit(1); }); }); function runWaterfall(promiseFactories) { if (promiseFactories.length === 0) { return Promise.resolve(); } const head = promiseFactories[0]; const tail = promiseFactories.slice(1); const nextPromiseFactory = head; const nextPromise = nextPromiseFactory(); if (!nextPromise || typeof nextPromise.then !== 'function') { throw new Error('runWaterfall() received something that is not a Promise.'); } return nextPromise.then(() => { return runWaterfall(tail); }); }