@@ -63,6 +63,7 @@ export function rolldownDevHandleConfig(
63
63
createEnvironment : RolldownEnvironment . createFactory ( {
64
64
hmr : config . experimental ?. rolldownDev ?. hmr ,
65
65
reactRefresh : config . experimental ?. rolldownDev ?. reactRefresh ,
66
+ ssrModuleRunner : false ,
66
67
} ) ,
67
68
} ,
68
69
build : {
@@ -81,6 +82,7 @@ export function rolldownDevHandleConfig(
81
82
createEnvironment : RolldownEnvironment . createFactory ( {
82
83
hmr : false ,
83
84
reactRefresh : false ,
85
+ ssrModuleRunner : config . experimental ?. rolldownDev ?. ssrModuleRunner ,
84
86
} ) ,
85
87
} ,
86
88
} ,
@@ -134,6 +136,8 @@ class RolldownEnvironment extends DevEnvironment {
134
136
result ! : rolldown . RolldownOutput
135
137
outDir ! : string
136
138
buildTimestamp = Date . now ( )
139
+ inputOptions ! : rolldown . InputOptions
140
+ outputOptions ! : rolldown . OutputOptions
137
141
138
142
static createFactory (
139
143
rolldownDevOptioins : RolldownDevOptions ,
@@ -200,7 +204,7 @@ class RolldownEnvironment extends DevEnvironment {
200
204
plugins = plugins . map ( ( p ) => injectEnvironmentToHooks ( this as any , p ) )
201
205
202
206
console . time ( `[rolldown:${ this . name } :build]` )
203
- const inputOptions : rolldown . InputOptions = {
207
+ this . inputOptions = {
204
208
dev : this . rolldownDevOptions . hmr ,
205
209
input : this . config . build . rollupOptions . input ,
206
210
cwd : this . config . root ,
@@ -212,30 +216,34 @@ class RolldownEnvironment extends DevEnvironment {
212
216
} ,
213
217
plugins : [
214
218
...plugins ,
215
- patchRuntimePlugin ( this . rolldownDevOptions ) ,
219
+ patchRuntimePlugin ( this ) ,
216
220
patchCssPlugin ( ) ,
217
221
reactRefreshPlugin ( ) ,
218
222
] ,
219
223
moduleTypes : {
220
224
'.css' : 'js' ,
221
225
} ,
222
226
}
223
- this . instance = await rolldown . rolldown ( inputOptions )
227
+ this . instance = await rolldown . rolldown ( this . inputOptions )
224
228
225
- // `generate` should work but we use `write` so it's easier to see output and debug
226
- const outputOptions : rolldown . OutputOptions = {
229
+ const format : rolldown . ModuleFormat =
230
+ this . name === 'client' || this . rolldownDevOptions . ssrModuleRunner
231
+ ? 'app'
232
+ : 'esm'
233
+ this . outputOptions = {
227
234
dir : this . outDir ,
228
- format : this . rolldownDevOptions . hmr ? 'app' : 'esm' ,
235
+ format,
229
236
// TODO: hmr_rebuild returns source map file when `sourcemap: true`
230
237
sourcemap : 'inline' ,
231
238
// TODO: https://github.com/rolldown/rolldown/issues/2041
232
239
// handle `require("stream")` in `react-dom/server`
233
240
banner :
234
- this . name === 'ssr'
241
+ this . name === 'ssr' && format === 'esm'
235
242
? `import __nodeModule from "node:module"; const require = __nodeModule.createRequire(import.meta.url);`
236
243
: undefined ,
237
244
}
238
- this . result = await this . instance . write ( outputOptions )
245
+ // `generate` should work but we use `write` so it's easier to see output and debug
246
+ this . result = await this . instance . write ( this . outputOptions )
239
247
240
248
this . buildTimestamp = Date . now ( )
241
249
console . timeEnd ( `[rolldown:${ this . name } :build]` )
@@ -249,12 +257,22 @@ class RolldownEnvironment extends DevEnvironment {
249
257
if ( ! output . moduleIds . includes ( ctx . file ) ) {
250
258
return
251
259
}
252
- if ( this . rolldownDevOptions . hmr ) {
260
+ if (
261
+ this . rolldownDevOptions . hmr ||
262
+ this . rolldownDevOptions . ssrModuleRunner
263
+ ) {
253
264
logger . info ( `hmr '${ ctx . file } '` , { timestamp : true } )
254
265
console . time ( `[rolldown:${ this . name } :hmr]` )
255
266
const result = await this . instance . experimental_hmr_rebuild ( [ ctx . file ] )
267
+ if ( this . name === 'client' ) {
268
+ ctx . server . ws . send ( 'rolldown:hmr' , result )
269
+ } else {
270
+ this . getRunner ( ) . evaluate (
271
+ result [ 1 ] . toString ( ) ,
272
+ path . join ( this . outDir , result [ 0 ] ) ,
273
+ )
274
+ }
256
275
console . timeEnd ( `[rolldown:${ this . name } :hmr]` )
257
- ctx . server . ws . send ( 'rolldown:hmr' , result )
258
276
} else {
259
277
await this . build ( )
260
278
if ( this . name === 'client' ) {
@@ -263,40 +281,138 @@ class RolldownEnvironment extends DevEnvironment {
263
281
}
264
282
}
265
283
284
+ runner ! : RolldownModuleRunner
285
+
286
+ getRunner ( ) {
287
+ if ( ! this . runner ) {
288
+ const output = this . result . output [ 0 ]
289
+ const filepath = path . join ( this . outDir , output . fileName )
290
+ this . runner = new RolldownModuleRunner ( )
291
+ const code = fs . readFileSync ( filepath , 'utf-8' )
292
+ this . runner . evaluate ( code , filepath )
293
+ }
294
+ return this . runner
295
+ }
296
+
266
297
async import ( input : string ) : Promise < unknown > {
267
- const output = this . result . output . find ( ( o ) => o . name === input )
268
- assert ( output , `invalid import input '${ input } '` )
298
+ if ( this . outputOptions . format === 'app' ) {
299
+ return this . getRunner ( ) . import ( input )
300
+ }
301
+ // input is no use
302
+ const output = this . result . output [ 0 ]
269
303
const filepath = path . join ( this . outDir , output . fileName )
304
+ // TODO: source map not applied when adding `?t=...`?
305
+ // return import(`${pathToFileURL(filepath)}`)
270
306
return import ( `${ pathToFileURL ( filepath ) } ?t=${ this . buildTimestamp } ` )
271
307
}
272
308
}
273
309
274
- function patchRuntimePlugin (
275
- rolldownDevOptions : RolldownDevOptions ,
276
- ) : rolldown . Plugin {
310
+ class RolldownModuleRunner {
311
+ // intercept globals
312
+ private context = {
313
+ rolldown_runtime : { } as any ,
314
+ __rolldown_hot : {
315
+ send : ( ) => { } ,
316
+ } ,
317
+ // TODO: external require doesn't work in app format.
318
+ // TODO: also it should be aware of importer for non static require/import.
319
+ _require : require ,
320
+ }
321
+
322
+ // TODO: support resolution?
323
+ async import ( id : string ) : Promise < unknown > {
324
+ const mod = this . context . rolldown_runtime . moduleCache [ id ]
325
+ assert ( mod , `Module not found '${ id } '` )
326
+ return mod . exports
327
+ }
328
+
329
+ evaluate ( code : string , sourceURL : string ) {
330
+ const context = {
331
+ self : this . context ,
332
+ ...this . context ,
333
+ }
334
+ // extract sourcemap and move to the bottom
335
+ const sourcemap = code . match ( / ^ \/ \/ # s o u r c e M a p p i n g U R L = .* / m) ?. [ 0 ] ?? ''
336
+ if ( sourcemap ) {
337
+ code = code . replace ( sourcemap , '' )
338
+ }
339
+ code = `\
340
+ 'use strict';(${ Object . keys ( context ) . join ( ',' ) } )=>{{${ code }
341
+ // TODO: need to re-expose runtime utilities for now
342
+ self.__toCommonJS = __toCommonJS;
343
+ self.__export = __export;
344
+ self.__toESM = __toESM;
345
+ }}
346
+ //# sourceURL=${ sourceURL }
347
+ //# sourceMappingSource=rolldown-module-runner
348
+ ${ sourcemap }
349
+ `
350
+ const fn = ( 0 , eval ) ( code )
351
+ try {
352
+ fn ( ...Object . values ( context ) )
353
+ } catch ( e ) {
354
+ console . error ( '[RolldownModuleRunner:ERROR]' , e )
355
+ throw e
356
+ }
357
+ }
358
+ }
359
+
360
+ function patchRuntimePlugin ( environment : RolldownEnvironment ) : rolldown . Plugin {
277
361
return {
278
362
name : 'vite:rolldown-patch-runtime' ,
363
+ // TODO: external require doesn't work in app format.
364
+ // rewrite `require -> _require` and provide _require from module runner.
365
+ // for now just rewrite known ones in "react-dom/server".
366
+ transform : {
367
+ filter : {
368
+ code : {
369
+ include : [ / r e q u i r e \( [ ' " ] ( s t r e a m | u t i l ) [ ' " ] \) / ] ,
370
+ } ,
371
+ } ,
372
+ handler ( code ) {
373
+ if ( ! environment . rolldownDevOptions . ssrModuleRunner ) {
374
+ return
375
+ }
376
+ return code . replace (
377
+ / r e q u i r e ( \( [ ' " ] ( s t r e a m | u t i l ) [ ' " ] \) ) / g,
378
+ '_require($1)' ,
379
+ )
380
+ } ,
381
+ } ,
279
382
renderChunk ( code ) {
280
383
// patch rolldown_runtime to workaround a few things
281
384
// TODO: is there a robust way to inject code specifically to entry or runtime?
282
385
if ( code . includes ( '//#region rolldown:runtime' ) ) {
283
- // TODO: is this magic string heavy?
386
+ // TODO: this magic string is heavy
284
387
const output = new MagicString ( code )
285
- // replace hard-coded WebSocket setup with custom client
286
- output . replace ( / c o n s t s o c k e t = .* ?\n \} ; / s, getRolldownClientCode ( ) )
287
- // trigger full rebuild on non-accepting entry invalidation
288
388
output
389
+ // replace hard-coded WebSocket setup with custom client
390
+ . replace (
391
+ / c o n s t s o c k e t = .* ?\n \} ; / s,
392
+ environment . name === 'client' ? getRolldownClientCode ( ) : '' ,
393
+ )
394
+ // fix rolldown_runtime.patch
395
+ . replace (
396
+ 'this.executeModuleStack.length > 1' ,
397
+ 'this.executeModuleStack.length > 0' ,
398
+ )
289
399
. replace ( 'parents: [parent],' , 'parents: parent ? [parent] : [],' )
400
+ . replace (
401
+ 'if (module.parents.indexOf(parent) === -1) {' ,
402
+ 'if (parent && module.parents.indexOf(parent) === -1) {' ,
403
+ )
290
404
. replace (
291
405
'for (var i = 0; i < module.parents.length; i++) {' ,
292
406
`
293
- if (module.parents.length === 0) {
407
+ boundaries.push(moduleId);
408
+ invalidModuleIds.push(moduleId);
409
+ if (module.parents.filter(Boolean).length === 0) {
294
410
__rolldown_hot.send("rolldown:hmr-deadend", { moduleId });
295
411
break;
296
412
}
297
413
for (var i = 0; i < module.parents.length; i++) {` ,
298
414
)
299
- if ( rolldownDevOptions . reactRefresh ) {
415
+ if ( environment . rolldownDevOptions . reactRefresh ) {
300
416
output . prepend ( getReactRefreshRuntimeCode ( ) )
301
417
}
302
418
return {
0 commit comments