From 82452f131b866fcc21689bf0bb4228dd2040d490 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 12 Nov 2020 04:21:22 +0000 Subject: [PATCH] [Flight] Minimal webpack plugin --- fixtures/flight/.gitignore | 1 + fixtures/flight/server/handler.server.js | 51 ++++--------------- fixtures/flight/src/index.js | 2 +- .../src/ReactFlightWebpackPlugin.js | 37 +++++++++++++- scripts/rollup/bundles.js | 2 +- scripts/rollup/modules.js | 3 ++ 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/fixtures/flight/.gitignore b/fixtures/flight/.gitignore index 4d29575de8048..800f3a80c3481 100644 --- a/fixtures/flight/.gitignore +++ b/fixtures/flight/.gitignore @@ -10,6 +10,7 @@ # production /build +/dist # misc .DS_Store diff --git a/fixtures/flight/server/handler.server.js b/fixtures/flight/server/handler.server.js index 86476bb2c2d71..cb9bcbf306e1d 100644 --- a/fixtures/flight/server/handler.server.js +++ b/fixtures/flight/server/handler.server.js @@ -1,51 +1,20 @@ 'use strict'; import {pipeToNodeWritable} from 'react-transport-dom-webpack/server'; +import {readFileSync} from 'fs'; +import {resolve} from 'path'; import * as React from 'react'; -import url from 'url'; - -function resolve(path) { - return url.pathToFileURL(require.resolve(path)).href; -} - module.exports = async function(req, res) { - res.setHeader('Access-Control-Allow-Origin', '*'); const m = await import('../src/App.server.js'); // const m = require('../src/App.server.js'); const App = m.default.default || m.default; - pipeToNodeWritable(, res, { - // TODO: Read from a map on the disk. - [resolve('../src/Counter.client.js')]: { - Counter: { - id: './src/Counter.client.js', - chunks: ['2'], - name: 'Counter', - }, - }, - [resolve('../src/Counter2.client.js')]: { - Counter: { - id: './src/Counter2.client.js', - chunks: ['1'], - name: 'Counter', - }, - }, - [resolve('../src/ShowMore.client.js')]: { - default: { - id: './src/ShowMore.client.js', - chunks: ['3'], - name: 'default', - }, - '': { - id: './src/ShowMore.client.js', - chunks: ['3'], - name: '', - }, - '*': { - id: './src/ShowMore.client.js', - chunks: ['3'], - name: '*', - }, - }, - }); + res.setHeader('Access-Control-Allow-Origin', '*'); + const moduleMap = JSON.parse( + readFileSync( + resolve(__dirname, '../dist/react-transport-manifest.json'), + 'utf8' + ) + ); + pipeToNodeWritable(, res, moduleMap); }; diff --git a/fixtures/flight/src/index.js b/fixtures/flight/src/index.js index ce9595dbe5482..9fc13a76aa892 100644 --- a/fixtures/flight/src/index.js +++ b/fixtures/flight/src/index.js @@ -19,5 +19,5 @@ ReactDOM.render( ); // Create entry points for Client Components. -// TODO: Webpack plugin should do this and write a map to disk. +// TODO: Webpack plugin should do this. require.context('./', true, /\.client\.js$/, 'lazy'); diff --git a/packages/react-transport-dom-webpack/src/ReactFlightWebpackPlugin.js b/packages/react-transport-dom-webpack/src/ReactFlightWebpackPlugin.js index 3b55509170174..a954f08935e03 100644 --- a/packages/react-transport-dom-webpack/src/ReactFlightWebpackPlugin.js +++ b/packages/react-transport-dom-webpack/src/ReactFlightWebpackPlugin.js @@ -7,7 +7,42 @@ * @flow */ +import {mkdirSync, writeFileSync} from 'fs'; +import {dirname, resolve} from 'path'; +import {pathToFileURL} from 'url'; + export default class ReactFlightWebpackPlugin { constructor(options: {isServer: boolean}) {} - apply(compiler: any) {} + + apply(compiler: any) { + compiler.hooks.emit.tap('React Transport Plugin', compilation => { + const json = {}; + compilation.chunks.forEach(chunk => { + chunk.getModules().forEach(mod => { + if (!/\.client\.js$/.test(mod.resource)) { + return; + } + const moduleExports = {}; + ['', '*'].concat(mod.buildMeta.providedExports).forEach(name => { + moduleExports[name] = { + id: mod.id, + chunks: chunk.ids, + name: name, + }; + }); + const href = pathToFileURL(mod.resource).href; + if (href !== undefined) { + json[href] = moduleExports; + } + }); + }); + const output = JSON.stringify(json, null, 2); + const filename = resolve( + compiler.options.output.path, + 'react-transport-manifest.json', + ); + mkdirSync(dirname(filename), {recursive: true}); + writeFileSync(filename, output); + }); + } } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index db8a8b8002cfb..37fdafa3408a1 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -283,7 +283,7 @@ const bundles = [ moduleType: RENDERER_UTILS, entry: 'react-transport-dom-webpack/plugin', global: 'ReactFlightWebpackPlugin', - externals: [], + externals: ['fs', 'path', 'url'], }, /******* React Transport DOM Webpack Node.js Loader *******/ diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index e89b12f7dbc0e..226ea5be5b77c 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -9,6 +9,8 @@ const {UMD_DEV, UMD_PROD, UMD_PROFILING} = require('./bundles').bundleTypes; const HAS_NO_SIDE_EFFECTS_ON_IMPORT = false; // const HAS_SIDE_EFFECTS_ON_IMPORT = true; const importSideEffects = Object.freeze({ + fs: HAS_NO_SIDE_EFFECTS_ON_IMPORT, + path: HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface': HAS_NO_SIDE_EFFECTS_ON_IMPORT, scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT, @@ -17,6 +19,7 @@ const importSideEffects = Object.freeze({ 'react/jsx-dev-runtime': HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'react-fetch/node': HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'react-dom': HAS_NO_SIDE_EFFECTS_ON_IMPORT, + url: HAS_NO_SIDE_EFFECTS_ON_IMPORT, }); // Bundles exporting globals that other modules rely on.