From c47c306a7a23d3c796b148d303764e2832da480c Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 9 Nov 2023 16:00:21 +0000 Subject: [PATCH] refactor[ci/build]: preserve header format in artifacts (#27671) In order to make Haste work with React's artifacts, It is important to keep headers in this format: ``` /** * ... ... * ... */ ``` For optimization purposes, Closure compiler will actually modify these headers by removing * prefixes, which is expected. We should pass sources to the compiler without license headers, with these changes the current flow will be: 1. Apply top-level definitions. For UMD-bundles, for example, or DEV-only bundles (e. g. `if (__DEV__) { ...`) 2. Apply licence headers for artifacts with sourcemaps: oss-production and oss-profiling bundles, they don't need to preserve the header format to comply with Haste. We need to apply these headers before passing sources to Closure, so it can build correct mappings for sourcemaps. 3. Pass these sources to closure compiler for minification and sourcemaps building. 4. Apply licence headers for artifacts without sourcemaps: dev bundles, fb bundles. This way the header style will be preserved and not changed by Closure. --- scripts/rollup/build.js | 39 ++++- scripts/rollup/wrappers.js | 320 ++++++++++++++++++++++++------------- 2 files changed, 246 insertions(+), 113 deletions(-) diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 0851ea3b04..1ee8328f02 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -470,11 +470,10 @@ function getPlugins( // I'm going to port "art" to ES modules to avoid this problem. // Please don't enable this for anything else! isUMDBundle && entry === 'react-art' && commonjs(), - // License and haste headers, top-level `if` blocks. { - name: 'license-and-headers', + name: 'top-level-definitions', renderChunk(source) { - return Wrappers.wrapBundle( + return Wrappers.wrapWithTopLevelDefinitions( source, bundleType, globalName, @@ -484,6 +483,21 @@ function getPlugins( ); }, }, + // License and haste headers for artifacts with sourcemaps + // For artifacts with sourcemaps we apply these headers + // before passing sources to the Closure compiler, which will be building sourcemaps + needsSourcemaps && { + name: 'license-and-signature-header-for-artifacts-with-sourcemaps', + renderChunk(source) { + return Wrappers.wrapWithLicenseHeader( + source, + bundleType, + globalName, + filename, + moduleType + ); + }, + }, // Apply dead code elimination and/or minification. // closure doesn't yet support leaving ESM imports intact needsMinifiedByClosure && @@ -527,7 +541,7 @@ function getPlugins( }), needsSourcemaps && { name: 'generate-prod-bundle-sourcemaps', - async renderChunk(codeAfterLicense, chunk, options, meta) { + async renderChunk(minifiedCodeWithChangedHeader, chunk, options, meta) { // We want to generate a sourcemap that shows the production bundle source // as it existed before Closure Compiler minified that chunk, rather than // showing the "original" individual source files. This better shows @@ -583,7 +597,7 @@ function getPlugins( // Add the sourcemap URL to the actual bundle, so that tools pick it up const sourceWithMappingUrl = - codeAfterLicense + + minifiedCodeWithChangedHeader + `\n//# sourceMappingURL=${finalSourcemapFilename}`; return { @@ -592,6 +606,21 @@ function getPlugins( }; }, }, + // License and haste headers for artifacts without sourcemaps + // Primarily used for FB-artifacts, which should preserve specific format of the header + // Which potentially can be changed by Closure minification + !needsSourcemaps && { + name: 'license-and-signature-header-for-artifacts-without-sourcemaps', + renderChunk(source) { + return Wrappers.wrapWithLicenseHeader( + source, + bundleType, + globalName, + filename, + moduleType + ); + }, + }, // Record bundle size. sizes({ getSize: (size, gzip) => { diff --git a/scripts/rollup/wrappers.js b/scripts/rollup/wrappers.js index 0b30e3b929..f8202d8ad9 100644 --- a/scripts/rollup/wrappers.js +++ b/scripts/rollup/wrappers.js @@ -53,7 +53,178 @@ const license = ` * Copyright (c) Meta Platforms, Inc. and affiliates. * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree.`; -const wrappers = { +const topLevelDefinitionWrappers = { + /***************** NODE_ES2015 *****************/ + [NODE_ES2015](source, globalName, filename, moduleType) { + return `'use strict'; + +${source}`; + }, + + /***************** ESM_DEV *****************/ + [ESM_DEV](source, globalName, filename, moduleType) { + return source; + }, + + /***************** ESM_PROD *****************/ + [ESM_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /***************** BUN_DEV *****************/ + [BUN_DEV](source, globalName, filename, moduleType) { + return source; + }, + + /***************** BUN_PROD *****************/ + [BUN_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /***************** UMD_DEV *****************/ + [UMD_DEV](source, globalName, filename, moduleType) { + return source; + }, + + /***************** UMD_PROD *****************/ + [UMD_PROD](source, globalName, filename, moduleType) { + return `(function(){${source}})();`; + }, + + /***************** UMD_PROFILING *****************/ + [UMD_PROFILING](source, globalName, filename, moduleType) { + return `(function(){${source}})();`; + }, + + /***************** NODE_DEV *****************/ + [NODE_DEV](source, globalName, filename, moduleType) { + return `'use strict'; + +if (process.env.NODE_ENV !== "production") { + (function() { +${source} + })(); +}`; + }, + + /***************** NODE_PROD *****************/ + [NODE_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /***************** NODE_PROFILING *****************/ + [NODE_PROFILING](source, globalName, filename, moduleType) { + return source; + }, + + /****************** FB_WWW_DEV ******************/ + [FB_WWW_DEV](source, globalName, filename, moduleType) { + return `'use strict'; + +if (__DEV__) { + (function() { +${source} + })(); +}`; + }, + + /****************** FB_WWW_PROD ******************/ + [FB_WWW_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /****************** FB_WWW_PROFILING ******************/ + [FB_WWW_PROFILING](source, globalName, filename, moduleType) { + return source; + }, + + /****************** RN_OSS_DEV ******************/ + [RN_OSS_DEV](source, globalName, filename, moduleType) { + return `'use strict'; + +if (__DEV__) { + (function() { +${source} + })(); +}`; + }, + + /****************** RN_OSS_PROD ******************/ + [RN_OSS_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /****************** RN_OSS_PROFILING ******************/ + [RN_OSS_PROFILING](source, globalName, filename, moduleType) { + return source; + }, + + /****************** RN_FB_DEV ******************/ + [RN_FB_DEV](source, globalName, filename, moduleType) { + return `'use strict'; + +if (__DEV__) { + (function() { +${source} + })(); +}`; + }, + + /****************** RN_FB_PROD ******************/ + [RN_FB_PROD](source, globalName, filename, moduleType) { + return source; + }, + + /****************** RN_FB_PROFILING ******************/ + [RN_FB_PROFILING](source, globalName, filename, moduleType) { + return source; + }, +}; + +const reconcilerWrappers = { + /***************** NODE_DEV (reconciler only) *****************/ + [NODE_DEV](source, globalName, filename, moduleType) { + return `'use strict'; + +if (process.env.NODE_ENV !== "production") { + module.exports = function $$$reconciler($$$config) { + var exports = {}; +${source} + return exports; + }; + module.exports.default = module.exports; + Object.defineProperty(module.exports, "__esModule", { value: true }); +} +`; + }, + + /***************** NODE_PROD (reconciler only) *****************/ + [NODE_PROD](source, globalName, filename, moduleType) { + return `module.exports = function $$$reconciler($$$config) { + + var exports = {}; +${source} + return exports; +}; +module.exports.default = module.exports; +Object.defineProperty(module.exports, "__esModule", { value: true }); +`; + }, + + /***************** NODE_PROFILING (reconciler only) *****************/ + [NODE_PROFILING](source, globalName, filename, moduleType) { + return `module.exports = function $$$reconciler($$$config) { + var exports = {}; +${source} + return exports; +}; +module.exports.default = module.exports; +Object.defineProperty(module.exports, "__esModule", { value: true }); +`; + }, +}; + +const licenseHeaderWrappers = { /***************** NODE_ES2015 *****************/ [NODE_ES2015](source, globalName, filename, moduleType) { return `/** @@ -63,8 +234,6 @@ const wrappers = { ${license} */ -'use strict'; - ${source}`; }, @@ -107,7 +276,7 @@ ${source}`; /***************** BUN_PROD *****************/ [BUN_PROD](source, globalName, filename, moduleType) { return `/** -* @license React + * @license React * ${filename} * ${license} @@ -124,6 +293,7 @@ ${source}`; * ${license} */ + ${source}`; }, @@ -135,7 +305,8 @@ ${source}`; * ${license} */ -(function(){${source}})();`; + +${source}`; }, /***************** UMD_PROFILING *****************/ @@ -146,7 +317,8 @@ ${license} * ${license} */ -(function(){${source}})();`; + +${source}`; }, /***************** NODE_DEV *****************/ @@ -158,13 +330,7 @@ ${license} ${license} */ -'use strict'; - -if (process.env.NODE_ENV !== "production") { - (function() { -${source} - })(); -}`; +${source}`; }, /***************** NODE_PROD *****************/ @@ -175,6 +341,7 @@ ${source} * ${license} */ + ${source}`; }, @@ -186,13 +353,13 @@ ${source}`; * ${license} */ + ${source}`; }, /****************** FB_WWW_DEV ******************/ [FB_WWW_DEV](source, globalName, filename, moduleType) { return `/** - * @preserve ${license} * * @noflow @@ -201,19 +368,12 @@ ${license} * @preserve-invariant-messages */ -'use strict'; - -if (__DEV__) { - (function() { -${source} - })(); -}`; +${source}`; }, /****************** FB_WWW_PROD ******************/ [FB_WWW_PROD](source, globalName, filename, moduleType) { return `/** - * @preserve ${license} * * @noflow @@ -228,7 +388,6 @@ ${source}`; /****************** FB_WWW_PROFILING ******************/ [FB_WWW_PROFILING](source, globalName, filename, moduleType) { return `/** - * @preserve ${license} * * @noflow @@ -243,7 +402,6 @@ ${source}`; /****************** RN_OSS_DEV ******************/ [RN_OSS_DEV](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -253,19 +411,12 @@ ${license} * ${getSigningToken()} */ -'use strict'; - -if (__DEV__) { - (function() { -${source} - })(); -}`); +${source}`); }, /****************** RN_OSS_PROD ******************/ [RN_OSS_PROD](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -281,7 +432,6 @@ ${source}`); /****************** RN_OSS_PROFILING ******************/ [RN_OSS_PROFILING](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -297,7 +447,6 @@ ${source}`); /****************** RN_FB_DEV ******************/ [RN_FB_DEV](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -306,19 +455,12 @@ ${license} * ${getSigningToken()} */ -'use strict'; - -if (__DEV__) { - (function() { -${source} - })(); -}`); +${source}`); }, /****************** RN_FB_PROD ******************/ [RN_FB_PROD](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -333,7 +475,6 @@ ${source}`); /****************** RN_FB_PROFILING ******************/ [RN_FB_PROFILING](source, globalName, filename, moduleType) { return signFile(`/** - * @preserve ${license} * * @noflow @@ -346,69 +487,7 @@ ${source}`); }, }; -const reconcilerWrappers = { - /***************** NODE_DEV (reconciler only) *****************/ - [NODE_DEV](source, globalName, filename, moduleType) { - return `/** - * @license React - * ${filename} - * -${license} - */ - -'use strict'; - -if (process.env.NODE_ENV !== "production") { - module.exports = function $$$reconciler($$$config) { - var exports = {}; -${source} - return exports; - }; - module.exports.default = module.exports; - Object.defineProperty(module.exports, "__esModule", { value: true }); -} -`; - }, - - /***************** NODE_PROD (reconciler only) *****************/ - [NODE_PROD](source, globalName, filename, moduleType) { - return `/** - * @license React - * ${filename} - * -${license} - */ -module.exports = function $$$reconciler($$$config) { - - var exports = {}; -${source} - return exports; -}; -module.exports.default = module.exports; -Object.defineProperty(module.exports, "__esModule", { value: true }); -`; - }, - - /***************** NODE_PROFILING (reconciler only) *****************/ - [NODE_PROFILING](source, globalName, filename, moduleType) { - return `/** - * @license React - * ${filename} - * -${license} - */ -module.exports = function $$$reconciler($$$config) { - var exports = {}; -${source} - return exports; -}; -module.exports.default = module.exports; -Object.defineProperty(module.exports, "__esModule", { value: true }); -`; - }, -}; - -function wrapBundle( +function wrapWithTopLevelDefinitions( source, bundleType, globalName, @@ -457,17 +536,42 @@ function wrapBundle( `Unsupported build type for the reconciler package: ${bundleType}.` ); } + return wrapper(source, globalName, filename, moduleType); } // All the other packages. - const wrapper = wrappers[bundleType]; + const wrapper = topLevelDefinitionWrappers[bundleType]; if (typeof wrapper !== 'function') { throw new Error(`Unsupported build type: ${bundleType}.`); } + + return wrapper(source, globalName, filename, moduleType); +} + +function wrapWithLicenseHeader( + source, + bundleType, + globalName, + filename, + moduleType +) { + if (bundleType === BROWSER_SCRIPT) { + // Bundles of type BROWSER_SCRIPT get sent straight to the browser without + // additional processing. So we should exclude any extra wrapper comments. + return source; + } + + // All the other packages. + const wrapper = licenseHeaderWrappers[bundleType]; + if (typeof wrapper !== 'function') { + throw new Error(`Unsupported build type: ${bundleType}.`); + } + return wrapper(source, globalName, filename, moduleType); } module.exports = { - wrapBundle, + wrapWithTopLevelDefinitions, + wrapWithLicenseHeader, };