@@ -14,8 +14,13 @@ import asyncLib from 'neo-async';
14
14
15
15
import ModuleDependency from 'webpack/lib/dependencies/ModuleDependency' ;
16
16
import NullDependency from 'webpack/lib/dependencies/NullDependency' ;
17
- import AsyncDependenciesBlock from 'webpack/lib/AsyncDependenciesBlock' ;
18
17
import Template from 'webpack/lib/Template' ;
18
+ import {
19
+ sources ,
20
+ WebpackError ,
21
+ Compilation ,
22
+ AsyncDependenciesBlock ,
23
+ } from 'webpack' ;
19
24
20
25
import isArray from 'shared/isArray' ;
21
26
@@ -34,6 +39,7 @@ class ClientReferenceDependency extends ModuleDependency {
34
39
// We use the Flight client implementation because you can't get to these
35
40
// without the client runtime so it's the first time in the loading sequence
36
41
// you might want them.
42
+ const clientImportName = 'react-server-dom-webpack' ;
37
43
const clientFileName = require . resolve ( '../' ) ;
38
44
39
45
type ClientReferenceSearchPath = {
@@ -97,33 +103,35 @@ export default class ReactFlightWebpackPlugin {
97
103
}
98
104
99
105
apply ( compiler : any ) {
106
+ const _this = this ;
100
107
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
+ ) ;
123
133
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 (
127
135
PLUGIN_NAME ,
128
136
( compilation , { normalModuleFactory} ) => {
129
137
compilation . dependencyFactories . set (
@@ -135,86 +143,140 @@ export default class ReactFlightWebpackPlugin {
135
143
new NullDependency . Template ( ) ,
136
144
) ;
137
145
138
- compilation . hooks . buildModule . tap ( PLUGIN_NAME , module => {
146
+ const handler = parser => {
139
147
// We need to add all client references as dependency of something in the graph so
140
148
// Webpack knows which entries need to know about the relevant chunks and include the
141
149
// map in their runtime. The things that actually resolves the dependency is the Flight
142
150
// client runtime. So we add them as a dependency of the Flight client runtime.
143
151
// 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 ;
165
157
}
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 ) ;
168
195
} ,
169
196
) ;
170
197
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
+ ) ;
181
211
return ;
182
212
}
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
- }
196
213
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
+ }
205
252
}
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
+ } ) ;
206
271
} ) ;
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
+ ) ;
216
278
} ,
217
- } ;
279
+ ) ;
218
280
} ) ;
219
281
}
220
282
@@ -268,7 +330,8 @@ export default class ReactFlightWebpackPlugin {
268
330
( err2 : null | Error , deps : Array < ModuleDependency > ) => {
269
331
if ( err2 ) return cb ( err2 ) ;
270
332
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 ) ;
272
335
const clientRefDep = new ClientReferenceDependency ( request ) ;
273
336
clientRefDep . userRequest = dep . userRequest ;
274
337
return clientRefDep ;
0 commit comments