@@ -14,8 +14,13 @@ import asyncLib from 'neo-async';
1414
1515import ModuleDependency from 'webpack/lib/dependencies/ModuleDependency' ;
1616import NullDependency from 'webpack/lib/dependencies/NullDependency' ;
17- import AsyncDependenciesBlock from 'webpack/lib/AsyncDependenciesBlock' ;
1817import Template from 'webpack/lib/Template' ;
18+ import {
19+ sources ,
20+ WebpackError ,
21+ Compilation ,
22+ AsyncDependenciesBlock ,
23+ } from 'webpack' ;
1924
2025import isArray from 'shared/isArray' ;
2126
@@ -34,6 +39,7 @@ class ClientReferenceDependency extends ModuleDependency {
3439// We use the Flight client implementation because you can't get to these
3540// without the client runtime so it's the first time in the loading sequence
3641// you might want them.
42+ const clientImportName = 'react-server-dom-webpack' ;
3743const clientFileName = require . resolve ( '../' ) ;
3844
3945type ClientReferenceSearchPath = {
@@ -97,33 +103,35 @@ export default class ReactFlightWebpackPlugin {
97103 }
98104
99105 apply ( compiler : any ) {
106+ const _this = this ;
100107 let resolvedClientReferences ;
101- const run = ( params , callback ) => {
102- // First we need to find all client files on the file system. We do this early so
103- // that we have them synchronously available later when we need them. This might
104- // not be needed anymore since we no longer need to compile the module itself in
105- // a special way. So it's probably better to do this lazily and in parallel with
106- // other compilation.
107- const contextResolver = compiler . resolverFactory . get ( 'context' , { } ) ;
108- this . resolveAllClientFiles (
109- compiler . context ,
110- contextResolver ,
111- compiler . inputFileSystem ,
112- compiler . createContextModuleFactory ( ) ,
113- ( err , resolvedClientRefs ) => {
114- if ( err ) {
115- callback ( err ) ;
116- return ;
117- }
118- resolvedClientReferences = resolvedClientRefs ;
119- callback ( ) ;
120- } ,
121- ) ;
122- } ;
108+ let clientFileNameFound = false ;
109+
110+ // Find all client files on the file system
111+ compiler . hooks . beforeCompile . tapAsync (
112+ PLUGIN_NAME ,
113+ ( { contextModuleFactory} , callback ) => {
114+ const contextResolver = compiler . resolverFactory . get ( 'context' , { } ) ;
115+
116+ _this . resolveAllClientFiles (
117+ compiler . context ,
118+ contextResolver ,
119+ compiler . inputFileSystem ,
120+ contextModuleFactory ,
121+ function ( err , resolvedClientRefs ) {
122+ if ( err ) {
123+ callback ( err ) ;
124+ return ;
125+ }
126+
127+ resolvedClientReferences = resolvedClientRefs ;
128+ callback ( ) ;
129+ } ,
130+ ) ;
131+ } ,
132+ ) ;
123133
124- compiler . hooks . run . tapAsync ( PLUGIN_NAME , run ) ;
125- compiler . hooks . watchRun . tapAsync ( PLUGIN_NAME , run ) ;
126- compiler . hooks . compilation . tap (
134+ compiler . hooks . thisCompilation . tap (
127135 PLUGIN_NAME ,
128136 ( compilation , { normalModuleFactory} ) => {
129137 compilation . dependencyFactories . set (
@@ -135,86 +143,140 @@ export default class ReactFlightWebpackPlugin {
135143 new NullDependency . Template ( ) ,
136144 ) ;
137145
138- compilation . hooks . buildModule . tap ( PLUGIN_NAME , module => {
146+ const handler = parser => {
139147 // We need to add all client references as dependency of something in the graph so
140148 // Webpack knows which entries need to know about the relevant chunks and include the
141149 // map in their runtime. The things that actually resolves the dependency is the Flight
142150 // client runtime. So we add them as a dependency of the Flight client runtime.
143151 // Anything that imports the runtime will be made aware of these chunks.
144- // TODO: Warn if we don't find this file anywhere in the compilation.
145- if ( module . resource !== clientFileName ) {
146- return ;
147- }
148- if ( resolvedClientReferences ) {
149- for ( let i = 0 ; i < resolvedClientReferences . length ; i ++ ) {
150- const dep = resolvedClientReferences [ i ] ;
151- const chunkName = this . chunkName
152- . replace ( / \[ i n d e x \] / g, '' + i )
153- . replace ( / \[ r e q u e s t \] / g, Template . toPath ( dep . userRequest ) ) ;
154-
155- const block = new AsyncDependenciesBlock (
156- {
157- name : chunkName ,
158- } ,
159- module ,
160- null ,
161- dep . require ,
162- ) ;
163- block . addDependency ( dep ) ;
164- module . addBlock ( block ) ;
152+ parser . hooks . program . tap ( PLUGIN_NAME , ( ) => {
153+ const module = parser . state . module ;
154+
155+ if ( module . resource !== clientFileName ) {
156+ return ;
165157 }
166- }
167- } ) ;
158+
159+ clientFileNameFound = true ;
160+
161+ if ( resolvedClientReferences ) {
162+ for ( let i = 0 ; i < resolvedClientReferences . length ; i ++ ) {
163+ const dep = resolvedClientReferences [ i ] ;
164+
165+ const chunkName = _this . chunkName
166+ . replace ( / \[ i n d e x \] / g, '' + i )
167+ . replace ( / \[ r e q u e s t \] / g, Template . toPath ( dep . userRequest ) ) ;
168+
169+ const block = new AsyncDependenciesBlock (
170+ {
171+ name : chunkName ,
172+ } ,
173+ null ,
174+ dep . request ,
175+ ) ;
176+
177+ block . addDependency ( dep ) ;
178+ module . addBlock ( block ) ;
179+ }
180+ }
181+ } ) ;
182+ } ;
183+
184+ normalModuleFactory . hooks . parser
185+ . for ( 'javascript/auto' )
186+ . tap ( 'HarmonyModulesPlugin' , handler ) ;
187+
188+ normalModuleFactory . hooks . parser
189+ . for ( 'javascript/esm' )
190+ . tap ( 'HarmonyModulesPlugin' , handler ) ;
191+
192+ normalModuleFactory . hooks . parser
193+ . for ( 'javascript/dynamic' )
194+ . tap ( 'HarmonyModulesPlugin' , handler ) ;
168195 } ,
169196 ) ;
170197
171- compiler . hooks . emit . tap ( PLUGIN_NAME , compilation => {
172- const json = { } ;
173- compilation . chunkGroups . forEach ( chunkGroup => {
174- const chunkIds = chunkGroup . chunks . map ( c => c . id ) ;
175-
176- function recordModule ( id , mod ) {
177- // TODO: Hook into deps instead of the target module.
178- // That way we know by the type of dep whether to include.
179- // It also resolves conflicts when the same module is in multiple chunks.
180- if ( ! / \. c l i e n t \. j s $ / . test ( mod . resource ) ) {
198+ compiler . hooks . make . tap ( PLUGIN_NAME , compilation => {
199+ compilation . hooks . processAssets . tap (
200+ {
201+ name : PLUGIN_NAME ,
202+ stage : Compilation . PROCESS_ASSETS_STAGE_REPORT ,
203+ } ,
204+ function ( ) {
205+ if ( clientFileNameFound === false ) {
206+ compilation . warnings . push (
207+ new WebpackError (
208+ `Client runtime at ${ clientImportName } was not found. React Server Components module map file ${ _this . manifestFilename } was not created.` ,
209+ ) ,
210+ ) ;
181211 return ;
182212 }
183- const moduleExports = { } ;
184- [ '' , '*' ] . concat ( mod . buildMeta . providedExports ) . forEach ( name => {
185- moduleExports [ name ] = {
186- id : id ,
187- chunks : chunkIds ,
188- name : name ,
189- } ;
190- } ) ;
191- const href = pathToFileURL ( mod . resource ) . href ;
192- if ( href !== undefined ) {
193- json [ href ] = moduleExports ;
194- }
195- }
196213
197- chunkGroup . chunks . forEach ( chunk => {
198- chunk . getModules ( ) . forEach ( mod => {
199- recordModule ( mod . id , mod ) ;
200- // If this is a concatenation, register each child to the parent ID.
201- if ( mod . modules ) {
202- mod . modules . forEach ( concatenatedMod => {
203- recordModule ( mod . id , concatenatedMod ) ;
204- } ) ;
214+ const json = { } ;
215+ compilation . chunkGroups . forEach ( function ( chunkGroup ) {
216+ const chunkIds = chunkGroup . chunks . map ( function ( c ) {
217+ return c . id ;
218+ } ) ;
219+
220+ function recordModule ( id , module ) {
221+ // TODO: Hook into deps instead of the target module.
222+ // That way we know by the type of dep whether to include.
223+ // It also resolves conflicts when the same module is in multiple chunks.
224+
225+ if ( ! / \. c l i e n t \. ( j s | t s ) x ? $ / . test ( module . resource ) ) {
226+ return ;
227+ }
228+
229+ const moduleProvidedExports = compilation . moduleGraph
230+ . getExportsInfo ( module )
231+ . getProvidedExports ( ) ;
232+
233+ const moduleExports = { } ;
234+ [ '' , '*' ]
235+ . concat (
236+ Array . isArray ( moduleProvidedExports )
237+ ? moduleProvidedExports
238+ : [ ] ,
239+ )
240+ . forEach ( function ( name ) {
241+ moduleExports [ name ] = {
242+ id,
243+ chunks : chunkIds ,
244+ name : name ,
245+ } ;
246+ } ) ;
247+ const href = pathToFileURL ( module . resource ) . href ;
248+
249+ if ( href !== undefined ) {
250+ json [ href ] = moduleExports ;
251+ }
205252 }
253+
254+ chunkGroup . chunks . forEach ( function ( chunk ) {
255+ const chunkModules = compilation . chunkGraph . getChunkModulesIterable (
256+ chunk ,
257+ ) ;
258+
259+ Array . from ( chunkModules ) . forEach ( function ( module ) {
260+ const moduleId = compilation . chunkGraph . getModuleId ( module ) ;
261+
262+ recordModule ( moduleId , module ) ;
263+ // If this is a concatenation, register each child to the parent ID.
264+ if ( module . modules ) {
265+ module . modules . forEach ( concatenatedMod => {
266+ recordModule ( moduleId , concatenatedMod ) ;
267+ } ) ;
268+ }
269+ } ) ;
270+ } ) ;
206271 } ) ;
207- } ) ;
208- } ) ;
209- const output = JSON . stringify ( json , null , 2 ) ;
210- compilation . assets [ this . manifestFilename ] = {
211- source ( ) {
212- return output ;
213- } ,
214- size ( ) {
215- return output . length ;
272+
273+ const output = JSON . stringify ( json , null , 2 ) ;
274+ compilation . emitAsset (
275+ _this . manifestFilename ,
276+ new sources . RawSource ( output , false ) ,
277+ ) ;
216278 } ,
217- } ;
279+ ) ;
218280 } ) ;
219281 }
220282
@@ -268,7 +330,8 @@ export default class ReactFlightWebpackPlugin {
268330 ( err2 : null | Error , deps : Array < ModuleDependency > ) => {
269331 if ( err2 ) return cb ( err2 ) ;
270332 const clientRefDeps = deps . map ( dep => {
271- const request = join ( resolvedDirectory , dep . request ) ;
333+ // use userRequest instead of request. request always end with undefined which is wrong
334+ const request = join ( resolvedDirectory , dep . userRequest ) ;
272335 const clientRefDep = new ClientReferenceDependency ( request ) ;
273336 clientRefDep . userRequest = dep . userRequest ;
274337 return clientRefDep ;
0 commit comments