Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support non entry chunks #13

Draft
wants to merge 10 commits into
base: test-mpa
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion packages/vite/misc/rolldown-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,30 @@ var __toBinary = /* @__PURE__ */ (() => {
}
})()

/**
* @typedef {(runtime: unknown) => void} ModuleFactory
* @typedef {Record<string, ModuleFactory>} ModuleFactoryMap
* @typedef {{ exports: unknown, parents: string[], hot: any }} ModuleCacheEntry
* @typedef {Record<string, ModuleCacheEntry>} ModuleCache
*/

self.__rolldown_runtime = {
/**
* @type {string[]}
*/
executeModuleStack: [],
/**
* @type {ModuleCache}
*/
moduleCache: {},
/**
* @type {ModuleFactoryMap}
*/
moduleFactoryMap: {},
/**
* @param {string} id
* @returns {unknown}
*/
require: function (id) {
const parent = this.executeModuleStack.at(-1)
if (this.moduleCache[id]) {
Expand Down Expand Up @@ -122,9 +142,10 @@ self.__rolldown_runtime = {
})
this.executeModuleStack.push(id)
factory({
require: this.require.bind(this),
module,
exports: module.exports,
require: this.require.bind(this),
ensureChunk: this.ensureChunk.bind(this),
__toCommonJS,
__toESM,
__export,
Expand All @@ -133,6 +154,9 @@ self.__rolldown_runtime = {
this.executeModuleStack.pop()
return module.exports
},
/**
* @param {ModuleFactoryMap} newModuleFactoryMap
*/
patch: function (newModuleFactoryMap) {
var boundaries = []
var invalidModuleIds = []
Expand Down Expand Up @@ -228,4 +252,22 @@ self.__rolldown_runtime = {
}
}
},
/** @type {{ chunks: Record<string, { fileName: string, imports: string[] }> }} */
manifest: {},
/**
* @param {string} chunkName
*/
async ensureChunk(chunkName) {
await this.ensureChunkDeps(chunkName)
const file = this.manifest.chunks[chunkName].fileName
await import(`/${file}`)
},
/**
* @param {string} chunkName
*/
async ensureChunkDeps(chunkName) {
for (const file of this.manifest.chunks[chunkName].imports) {
await import(`/${file}`)
}
},
}
180 changes: 119 additions & 61 deletions packages/vite/src/node/server/environments/rolldown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import MagicString from 'magic-string'
import type * as rolldown from 'rolldown'
import * as rolldownExperimental from 'rolldown/experimental'
import sirv from 'sirv'
import { createLogger } from '../../publicUtils'
import { createLogger, normalizePath } from '../../publicUtils'
import { DevEnvironment } from '../environment'
import type {
ConfigEnv,
Expand Down Expand Up @@ -294,20 +294,22 @@ class RolldownEnvironment extends DevEnvironment {
)
}

async buildHmr(
file: string,
): Promise<rolldown.RolldownOutputChunk | undefined> {
async buildHmr(file: string): Promise<{
manifest: ChunkManifest
chunk?: rolldown.RolldownOutputChunk
}> {
logger.info(`hmr '${file}'`, { timestamp: true })
console.time(`[rolldown:${this.name}:rebuild]`)
await this.buildInner()
console.timeEnd(`[rolldown:${this.name}:rebuild]`)
const manifest = getChunkManifest(this.result.output)
const chunk = this.result.output.find(
(v) => v.type === 'chunk' && v.name === 'hmr-update',
)
if (chunk) {
assert(chunk.type === 'chunk')
return chunk
}
return { manifest, chunk }
}

async handleUpdate(ctx: HmrContext): Promise<void> {
Expand All @@ -327,17 +329,32 @@ class RolldownEnvironment extends DevEnvironment {
this.rolldownDevOptions.hmr ||
this.rolldownDevOptions.ssrModuleRunner
) {
const chunk = await this.buildHmr(ctx.file)
if (!chunk) {
return
}
const result = await this.buildHmr(ctx.file)
if (this.name === 'client') {
ctx.server.ws.send('rolldown:hmr', chunk.fileName)
ctx.server.ws.send('rolldown:hmr', {
manifest: result.manifest,
fileName: result.chunk?.fileName,
})
// full reload on html
// TODO: what's the general way to handle this?
// should plugin (vite:build-html) be responsible of handling this?
if (ctx.file.endsWith('.html')) {
ctx.server.ws.send({
type: 'full-reload',
path:
'/' + normalizePath(path.relative(this.config.root, ctx.file)),
})
}
} else {
this.getRunner().evaluate(
chunk.code,
path.join(this.outDir, chunk.fileName),
)
// TODO: manifest
if (result.chunk) {
await (
await this.getRunner()
).evaluate(
result.chunk.code,
path.join(this.outDir, result.chunk.fileName),
)
}
}
} else {
await this.build()
Expand All @@ -349,20 +366,20 @@ class RolldownEnvironment extends DevEnvironment {

runner!: RolldownModuleRunner

getRunner() {
async getRunner() {
if (!this.runner) {
const output = this.result.output[0]
const filepath = path.join(this.outDir, output.fileName)
this.runner = new RolldownModuleRunner()
const code = fs.readFileSync(filepath, 'utf-8')
this.runner.evaluate(code, filepath)
await this.runner.evaluate(code, filepath)
}
return this.runner
}

async import(input: string): Promise<unknown> {
if (this.outputOptions.format === 'experimental-app') {
return this.getRunner().import(input)
return (await this.getRunner()).import(input)
}
// input is no use
const output = this.result.output[0]
Expand Down Expand Up @@ -390,22 +407,22 @@ class RolldownModuleRunner {
return mod.exports
}

evaluate(code: string, sourceURL: string) {
async evaluate(code: string, sourceURL: string) {
// extract sourcemap and move to the bottom
const sourcemap = code.match(/^\/\/# sourceMappingURL=.*/m)?.[0] ?? ''
if (sourcemap) {
code = code.replace(sourcemap, '')
}
code = `\
'use strict';(${Object.keys(this.context).join(',')})=>{{${code}
'use strict';async (${Object.keys(this.context).join(',')})=>{{${code}
}}
//# sourceURL=${sourceURL}
//# sourceMappingSource=rolldown-module-runner
${sourcemap}
`
const fn = (0, eval)(code)
try {
fn(...Object.values(this.context))
await fn(...Object.values(this.context))
} catch (e) {
console.error('[RolldownModuleRunner:ERROR]', e)
throw e
Expand All @@ -416,53 +433,91 @@ ${sourcemap}
function patchRuntimePlugin(environment: RolldownEnvironment): rolldown.Plugin {
return {
name: 'vite:rolldown-patch-runtime',
renderChunk(code, chunk) {
// TODO: this magic string is heavy

if (chunk.name === 'hmr-update') {
const output = new MagicString(code)
output.append(`
self.__rolldown_runtime.patch(__rolldown_modules);
`)
return {
code: output.toString(),
map: output.generateMap({ hires: 'boundary' }),
}
renderChunk(code) {
// TODO: source map is broken otherwise
const output = new MagicString(code)
return {
code: output.toString(),
map: output.generateMap({ hires: 'boundary' }),
}

if (chunk.isEntry) {
const output = new MagicString(code)
assert(chunk.facadeModuleId)
const stableId = path.relative(
environment.config.root,
chunk.facadeModuleId,
)
output.append(`
},
generateBundle(_options, bundle) {
// inject chunk manifest
const manifest = getChunkManifest(Object.values(bundle))
for (const chunk of Object.values(bundle)) {
if (chunk.type === 'chunk') {
if (chunk.isEntry) {
chunk.code +=
'\n;' +
fs.readFileSync(
path.join(VITE_PACKAGE_DIR, 'misc', 'rolldown-runtime.js'),
'utf-8',
)
if (environment.name === 'client') {
chunk.code += getRolldownClientCode()
}
if (environment.rolldownDevOptions.reactRefresh) {
chunk.code += getReactRefreshRuntimeCode()
}
chunk.code += `
self.__rolldown_runtime.manifest = ${JSON.stringify(manifest, null, 2)};
`
}
if (chunk.name === 'hmr-update') {
chunk.code += `
self.__rolldown_runtime.patch(__rolldown_modules);
`
} else {
// TODO: avoid top-level-await?
chunk.code += `
Object.assign(self.__rolldown_runtime.moduleFactoryMap, __rolldown_modules);
await self.__rolldown_runtime.ensureChunkDeps(${JSON.stringify(chunk.name)});
`
}
if (chunk.isEntry) {
assert(chunk.facadeModuleId)
const stableId = path.relative(
environment.config.root,
chunk.facadeModuleId,
)
chunk.code += `
self.__rolldown_runtime.require(${JSON.stringify(stableId)});
`)

// inject runtime
const runtimeCode = fs.readFileSync(
path.join(VITE_PACKAGE_DIR, 'misc', 'rolldown-runtime.js'),
'utf-8',
)
output.prepend(runtimeCode)
if (environment.name === 'client') {
output.prepend(getRolldownClientCode())
}
if (environment.rolldownDevOptions.reactRefresh) {
output.prepend(getReactRefreshRuntimeCode())
}
return {
code: output.toString(),
map: output.generateMap({ hires: 'boundary' }),
`
}
chunk.code = moveInlineSourcemapToEnd(chunk.code)
}
}
},
}
}

export type ChunkManifest = {
chunks: Record<string, { fileName: string; imports: string[] }>
}

function getChunkManifest(
outputs: (rolldown.RolldownOutputChunk | rolldown.RolldownOutputAsset)[],
): ChunkManifest {
const manifest: ChunkManifest = {
chunks: {},
}
for (const chunk of outputs) {
if (chunk.type === 'chunk') {
const { fileName, imports } = chunk
manifest.chunks[chunk.name] = { fileName, imports }
}
}
return manifest
}

function moveInlineSourcemapToEnd(code: string) {
const sourcemap = code.match(/^\/\/# sourceMappingURL=.*/m)?.[0]
if (sourcemap) {
code = code.replace(sourcemap, '') + '\n' + sourcemap
}
return code
}

// patch vite:css transform for hmr
function patchCssPlugin(): rolldown.Plugin {
return {
Expand Down Expand Up @@ -518,12 +573,15 @@ function getRolldownClientCode() {
code += `
const hot = createHotContext("/__rolldown");
hot.on("rolldown:hmr", (data) => {
import("/" + data + "?t=" + Date.now());
self.__rolldown_runtime.manifest = data.manifest;
if (data.fileName) {
import("/" + data.fileName + "?t=" + Date.now());
}
});
self.__rolldown_hot = hot;
self.__rolldown_updateStyle = updateStyle;
`
return `;(() => {/*** @vite/client ***/\n${code}}\n)();`
return `\n;(() => {/*** @vite/client ***/\n${code}}\n)();\n`
}

function reactRefreshPlugin(): rolldown.Plugin {
Expand Down Expand Up @@ -561,7 +619,7 @@ function getReactRefreshRuntimeCode() {
'utf-8',
)
const output = new MagicString(code)
output.prepend('self.__react_refresh_runtime = {};\n')
output.prepend('\n;self.__react_refresh_runtime = {};\n')
output.replaceAll('process.env.NODE_ENV !== "production"', 'true')
output.replaceAll(/\bexports\./g, '__react_refresh_runtime.')
output.append(`
Expand Down
3 changes: 3 additions & 0 deletions playground/rolldown-dev-mpa/__tests__/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { test } from 'vitest'
import { page } from '../../test-utils'

test('basic', async () => {
page.setDefaultTimeout(1000)
await page.getByRole('heading', { name: 'Home' }).click()
await page.getByText('Rendered by /index.js').click()
await page.getByText('shared: [ok]').click()
await page.getByRole('link', { name: 'About' }).click()
await page.waitForURL(/\/about/)
await page.getByRole('heading', { name: 'About' }).click()
await page.getByText('Rendered by /about/index.js').click()
await page.getByText('shared: [ok]').click()
})
2 changes: 1 addition & 1 deletion playground/rolldown-dev-mpa/about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<h1>About</h1>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/about/">About</a></li>
</ul>
</div>
<div id="root"></div>
Expand Down
3 changes: 3 additions & 0 deletions playground/rolldown-dev-mpa/about/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import shared from '../shared'

document.getElementById('root').innerHTML = `
<p>Rendered by /about/index.js: ${Math.random().toString(36).slice(2)}</p>
<pre>shared: ${shared}</pre>
`
2 changes: 1 addition & 1 deletion playground/rolldown-dev-mpa/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<h1>Home</h1>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/about/">About</a></li>
</ul>
<div id="root"></div>
<script type="module" src="./index.js"></script>
Expand Down
Loading
Loading