From 54f0e0f7308b4d0d51e52e149d3f7e5a207991ee Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 14 Oct 2022 23:29:17 -0400 Subject: [PATCH] Scaffolding for react-dom/unstable_external-server-runtime (#25482) * Scaffolding for react-dom/unstable_external-server-runtime Implements a new bundle type for in our build config called BROWSER_SCRIPT. This is intended for scripts that get delivered straight to the browser without needing to be processed by a bundler. (And also doesn't include any extra UMD crap.) Right now there's only a single use case so I didn't stress about making it general purpose. The use case is: a script that loads the Fizz browser runtime, and sets up a MutationObserver to receive instructions as HTML streams in. This will be an alternative option to the default Fizz behavior of sending the runtime down as inline script tags, to accommodate environments where inline script tags are not allowed. There's no development version of this bundle because it doesn't contain any warnings or run any user code. None of the actual implementation is in this PR; it just sets up the build infra. Co-authored-by: Mofei Zhang * Set BUNDLE_SCRIPT's GCC output format to ES5 This removes the automatic 'use strict' directive, which we don't need. Co-authored-by: Mofei Zhang --- packages/react-dom/package.json | 1 + .../server/ReactDOMServerExternalRuntime.js | 18 +++++++ scripts/rollup/build.js | 48 ++++++++++--------- scripts/rollup/bundles.js | 16 +++++++ scripts/rollup/packaging.js | 20 +++++++- scripts/rollup/wrappers.js | 8 ++++ scripts/shared/inlinedHostConfigs.js | 2 + 7 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 packages/react-dom/src/server/ReactDOMServerExternalRuntime.js diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index c39432aa32046..baa9032ef4c62 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -38,6 +38,7 @@ "server-rendering-stub.js", "test-utils.js", "unstable_testing.js", + "unstable_server-external-runtime.js", "cjs/", "umd/" ], diff --git a/packages/react-dom/src/server/ReactDOMServerExternalRuntime.js b/packages/react-dom/src/server/ReactDOMServerExternalRuntime.js new file mode 100644 index 0000000000000..dccb1bb3debe9 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMServerExternalRuntime.js @@ -0,0 +1,18 @@ +// TODO: Add Flow types +import { + clientRenderBoundary, + completeBoundaryWithStyles, + completeBoundary, + completeSegment, +} from 'react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet'; + +// Intentionally does nothing. Implementation will be added in future PR. +// eslint-disable-next-line no-unused-vars +const observer = new MutationObserver(mutations => { + // These are only called so I can check what the module output looks like. The + // code is unreachable. + clientRenderBoundary(); + completeBoundaryWithStyles(); + completeBoundary(); + completeSegment(); +}); diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 52c0b7dc9fe29..97b949b560f15 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -60,6 +60,7 @@ const { RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING, + BROWSER_SCRIPT, } = Bundles.bundleTypes; const {getFilename} = Bundles; @@ -93,19 +94,6 @@ const isWatchMode = argv.watch; const syncFBSourcePath = argv['sync-fbsource']; const syncWWWPath = argv['sync-www']; -const closureOptions = { - compilation_level: 'SIMPLE', - language_in: 'ECMASCRIPT_2015', - language_out: 'ECMASCRIPT5_STRICT', - env: 'CUSTOM', - warning_level: 'QUIET', - apply_input_source_maps: false, - use_types_for_optimization: false, - process_common_js_modules: false, - rewrite_polyfills: false, - inject_libraries: false, -}; - // Non-ES2015 stuff applied before closure compiler. const babelPlugins = [ // These plugins filter out non-ES2015. @@ -224,6 +212,8 @@ function getFormat(bundleType) { return `cjs`; case NODE_ESM: return `es`; + case BROWSER_SCRIPT: + return `iife`; } } @@ -247,6 +237,7 @@ function isProductionBundleType(bundleType) { case RN_OSS_PROFILING: case RN_FB_PROD: case RN_FB_PROFILING: + case BROWSER_SCRIPT: return true; default: throw new Error(`Unknown type: ${bundleType}`); @@ -267,6 +258,7 @@ function isProfilingBundleType(bundleType) { case RN_OSS_PROD: case UMD_DEV: case UMD_PROD: + case BROWSER_SCRIPT: return false; case FB_WWW_PROFILING: case NODE_PROFILING: @@ -371,14 +363,24 @@ function getPlugins( isUMDBundle && entry === 'react-art' && commonjs(), // 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 - assume_function_wrapper: !isUMDBundle, - renaming: !shouldStayReadable, - }) - ), + closure({ + compilation_level: 'SIMPLE', + language_in: 'ECMASCRIPT_2015', + language_out: + bundleType === BROWSER_SCRIPT ? 'ECMASCRIPT5' : 'ECMASCRIPT5_STRICT', + env: 'CUSTOM', + warning_level: 'QUIET', + apply_input_source_maps: false, + use_types_for_optimization: false, + process_common_js_modules: false, + rewrite_polyfills: false, + inject_libraries: false, + + // Don't let it create global variables in the browser. + // https://github.com/facebook/react/issues/10909 + assume_function_wrapper: !isUMDBundle, + renaming: !shouldStayReadable, + }), // HACK to work around the fact that Rollup isn't removing unused, pure-module imports. // Note that this plugin must be called after closure applies DCE. isProduction && stripUnusedImports(pureExternalModules), @@ -582,6 +584,7 @@ async function createBundle(bundle, bundleType) { }, }; const mainOutputPath = Packaging.getBundleOutputPath( + bundle, bundleType, filename, packageName @@ -724,7 +727,8 @@ async function buildEverything() { [bundle, RN_OSS_PROFILING], [bundle, RN_FB_DEV], [bundle, RN_FB_PROD], - [bundle, RN_FB_PROFILING] + [bundle, RN_FB_PROFILING], + [bundle, BROWSER_SCRIPT] ); } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 1f7297a8f2174..81072166a0269 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -25,6 +25,7 @@ const bundleTypes = { RN_FB_DEV: 'RN_FB_DEV', RN_FB_PROD: 'RN_FB_PROD', RN_FB_PROFILING: 'RN_FB_PROFILING', + BROWSER_SCRIPT: 'BROWSER_SCRIPT', }; const { @@ -45,6 +46,7 @@ const { RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING, + BROWSER_SCRIPT, } = bundleTypes; const moduleTypes = { @@ -351,6 +353,18 @@ const bundles = [ externals: ['react', 'util', 'stream', 'react-dom'], }, + /******* React DOM Fizz Server External Runtime *******/ + { + bundleTypes: [BROWSER_SCRIPT], + moduleType: RENDERER, + entry: 'react-dom/src/server/ReactDOMServerExternalRuntime.js', + outputPath: 'unstable_server-external-runtime.js', + global: 'ReactDOMServerExternalRuntime', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: [], + }, + /******* React DOM Server Render Stub *******/ { bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], @@ -1030,6 +1044,8 @@ function getOriginalFilename(bundle, bundleType) { case RN_FB_PROFILING: case RN_OSS_PROFILING: return `${globalName}-profiling.js`; + case BROWSER_SCRIPT: + return `${name}.js`; } } diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index bd66d0b634730..717210ec45521 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -33,6 +33,7 @@ const { RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING, + BROWSER_SCRIPT, } = Bundles.bundleTypes; function getPackageName(name) { @@ -42,7 +43,7 @@ function getPackageName(name) { return name; } -function getBundleOutputPath(bundleType, filename, packageName) { +function getBundleOutputPath(bundle, bundleType, filename, packageName) { switch (bundleType) { case NODE_ES2015: return `build/node_modules/${packageName}/cjs/${filename}`; @@ -88,6 +89,23 @@ function getBundleOutputPath(bundleType, filename, packageName) { default: throw new Error('Unknown RN package.'); } + case BROWSER_SCRIPT: { + // Bundles that are served as browser scripts need to be able to be sent + // straight to the browser with any additional bundling. We shouldn't use + // a module to re-export. Depending on how they are served, they also may + // not go through package.json module resolution, so we shouldn't rely on + // that either. We should consider the output path as part of the public + // contract, and explicitly specify its location within the package's + // directory structure. + const outputPath = bundle.outputPath; + if (!outputPath) { + throw new Error( + 'Bundles with type BROWSER_SCRIPT must specific an explicit ' + + 'output path.' + ); + } + return `build/node_modules/${packageName}/${outputPath}`; + } default: throw new Error('Unknown bundle type.'); } diff --git a/scripts/rollup/wrappers.js b/scripts/rollup/wrappers.js index 44d83e4f58ee0..e73fb01546312 100644 --- a/scripts/rollup/wrappers.js +++ b/scripts/rollup/wrappers.js @@ -22,6 +22,7 @@ const { RN_FB_DEV, RN_FB_PROD, RN_FB_PROFILING, + BROWSER_SCRIPT, } = bundleTypes; const {RECONCILER} = moduleTypes; @@ -384,6 +385,12 @@ function wrapBundle( } } + 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; + } + if (moduleType === RECONCILER) { // Standalone reconciler is only used by third-party renderers. // It is handled separately. @@ -395,6 +402,7 @@ function wrapBundle( } return wrapper(source, globalName, filename, moduleType); } + // All the other packages. const wrapper = wrappers[bundleType]; if (typeof wrapper !== 'function') { diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 1113bad1247d0..027c71aa173b3 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -15,6 +15,7 @@ module.exports = [ 'react-dom/src/server/ReactDOMFizzServerNode.js', 'react-dom/static.node', 'react-dom/server-rendering-stub', + 'react-dom/src/server/ReactDOMServerExternalRuntime.js', 'react-server-dom-webpack/writer.node.server', 'react-server-dom-webpack', ], @@ -51,6 +52,7 @@ module.exports = [ 'react-dom/src/server/ReactDOMFizzServerBrowser.js', 'react-dom/static.browser', 'react-dom/server-rendering-stub', + 'react-dom/src/server/ReactDOMServerExternalRuntime.js', 'react-server-dom-webpack/writer.browser.server', 'react-server-dom-webpack', ],