Skip to content

Commit f88bced

Browse files
sapphi-redunderfin
andcommitted
wip: full bundle dev env
Co-Authored-By: underfin <[email protected]>
1 parent 3776762 commit f88bced

22 files changed

+540
-142
lines changed

packages/vite/src/client/client.ts

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare const __HMR_BASE__: string
2020
declare const __HMR_TIMEOUT__: number
2121
declare const __HMR_ENABLE_OVERLAY__: boolean
2222
declare const __WS_TOKEN__: string
23+
declare const __FULL_BUNDLE_MODE__: boolean
2324

2425
console.debug('[vite] connecting...')
2526

@@ -37,6 +38,7 @@ const directSocketHost = __HMR_DIRECT_TARGET__
3738
const base = __BASE__ || '/'
3839
const hmrTimeout = __HMR_TIMEOUT__
3940
const wsToken = __WS_TOKEN__
41+
const isFullBundleMode = __FULL_BUNDLE_MODE__
4042

4143
const transport = normalizeModuleRunnerTransport(
4244
(() => {
@@ -140,32 +142,53 @@ const hmrClient = new HMRClient(
140142
debug: (...msg) => console.debug('[vite]', ...msg),
141143
},
142144
transport,
143-
async function importUpdatedModule({
144-
acceptedPath,
145-
timestamp,
146-
explicitImportRequired,
147-
isWithinCircularImport,
148-
}) {
149-
const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`)
150-
const importPromise = import(
151-
/* @vite-ignore */
152-
base +
153-
acceptedPathWithoutQuery.slice(1) +
154-
`?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${
155-
query ? `&${query}` : ''
156-
}`
157-
)
158-
if (isWithinCircularImport) {
159-
importPromise.catch(() => {
160-
console.info(
161-
`[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. ` +
162-
`To debug and break the circular import, you can run \`vite --debug hmr\` to log the circular dependency path if a file change triggered it.`,
145+
isFullBundleMode
146+
? async function importUpdatedModule({
147+
url,
148+
acceptedPath,
149+
isWithinCircularImport,
150+
}) {
151+
const importPromise = import(base + url!).then(() =>
152+
// @ts-expect-error globalThis.__rolldown_runtime__
153+
globalThis.__rolldown_runtime__.loadExports(acceptedPath),
163154
)
164-
pageReload()
165-
})
166-
}
167-
return await importPromise
168-
},
155+
if (isWithinCircularImport) {
156+
importPromise.catch(() => {
157+
console.info(
158+
`[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. ` +
159+
`To debug and break the circular import, you can run \`vite --debug hmr\` to log the circular dependency path if a file change triggered it.`,
160+
)
161+
pageReload()
162+
})
163+
}
164+
return await importPromise
165+
}
166+
: async function importUpdatedModule({
167+
acceptedPath,
168+
timestamp,
169+
explicitImportRequired,
170+
isWithinCircularImport,
171+
}) {
172+
const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`)
173+
const importPromise = import(
174+
/* @vite-ignore */
175+
base +
176+
acceptedPathWithoutQuery.slice(1) +
177+
`?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${
178+
query ? `&${query}` : ''
179+
}`
180+
)
181+
if (isWithinCircularImport) {
182+
importPromise.catch(() => {
183+
console.info(
184+
`[hmr] ${acceptedPath} failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. ` +
185+
`To debug and break the circular import, you can run \`vite --debug hmr\` to log the circular dependency path if a file change triggered it.`,
186+
)
187+
pageReload()
188+
})
189+
}
190+
return await importPromise
191+
},
169192
)
170193
transport.connect!(createHMRHandler(handleMessage))
171194

@@ -459,3 +482,69 @@ export function injectQuery(url: string, queryToInject: string): string {
459482
}
460483

461484
export { ErrorOverlay }
485+
486+
if (isFullBundleMode) {
487+
class DevRuntime {
488+
modules: Record<string, { exports: any }> = {}
489+
490+
static getInstance() {
491+
// @ts-expect-error __rolldown_runtime__
492+
let instance = globalThis.__rolldown_runtime__
493+
if (!instance) {
494+
instance = new DevRuntime()
495+
// @ts-expect-error __rolldown_runtime__
496+
globalThis.__rolldown_runtime__ = instance
497+
}
498+
return instance
499+
}
500+
501+
createModuleHotContext(moduleId: string) {
502+
const ctx = createHotContext(moduleId)
503+
// @ts-expect-error TODO: support CSS
504+
ctx._internal = {
505+
updateStyle,
506+
removeStyle,
507+
}
508+
return ctx
509+
}
510+
511+
applyUpdates(_boundaries: string[]) {
512+
//
513+
}
514+
515+
registerModule(
516+
id: string,
517+
module: { exports: Record<string, () => unknown> },
518+
) {
519+
this.modules[id] = module
520+
}
521+
522+
loadExports(id: string) {
523+
const module = this.modules[id]
524+
if (module) {
525+
return module.exports
526+
} else {
527+
console.warn(`Module ${id} not found`)
528+
return {}
529+
}
530+
}
531+
532+
// __esmMin
533+
// @ts-expect-error need to add typing
534+
createEsmInitializer = (fn, res) => () => (fn && (res = fn((fn = 0))), res)
535+
// __commonJSMin
536+
// @ts-expect-error need to add typing
537+
createCjsInitializer = (cb, mod) => () => (
538+
mod || cb((mod = { exports: {} }).exports, mod), mod.exports
539+
)
540+
// @ts-expect-error it is exits
541+
__toESM = __toESM
542+
// @ts-expect-error it is exits
543+
__toCommonJS = __toCommonJS
544+
// @ts-expect-error it is exits
545+
__export = __export
546+
}
547+
548+
// @ts-expect-error __rolldown_runtime__
549+
globalThis.__rolldown_runtime__ ||= new DevRuntime()
550+
}

packages/vite/src/node/build.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,11 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
475475
pre: Plugin[]
476476
post: Plugin[]
477477
}> {
478+
const isBuild = config.command === 'build'
478479
return {
479480
pre: [
480481
completeSystemWrapPlugin(),
481-
prepareOutDirPlugin(),
482+
...(isBuild ? [prepareOutDirPlugin()] : []),
482483
perEnvironmentPlugin(
483484
'vite:rollup-options-plugins',
484485
async (environment) =>
@@ -500,8 +501,8 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
500501
: []),
501502
]
502503
: []),
503-
terserPlugin(config),
504-
...(!config.isWorker
504+
...(isBuild ? [terserPlugin(config)] : []),
505+
...(isBuild && !config.isWorker
505506
? [
506507
manifestPlugin(config),
507508
ssrManifestPlugin(),
@@ -542,10 +543,10 @@ function resolveConfigToBuild(
542543
)
543544
}
544545

545-
function resolveRolldownOptions(
546+
export function resolveRolldownOptions(
546547
environment: Environment,
547548
chunkMetadataMap: Map<string, ChunkMetadata>,
548-
) {
549+
): RolldownOptions {
549550
const { root, packageCache, build: options } = environment.config
550551
const libOptions = options.lib
551552
const { logger } = environment
@@ -841,7 +842,7 @@ async function buildEnvironment(
841842
}
842843
}
843844

844-
function enhanceRollupError(e: RollupError) {
845+
export function enhanceRollupError(e: RollupError): void {
845846
const stackOnly = extractStack(e)
846847

847848
let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message)
@@ -1007,7 +1008,7 @@ const dynamicImportWarningIgnoreList = [
10071008
`statically analyzed`,
10081009
]
10091010

1010-
function clearLine() {
1011+
export function clearLine(): void {
10111012
const tty = process.stdout.isTTY && !process.env.CI
10121013
if (tty) {
10131014
process.stdout.clearLine(0)

packages/vite/src/node/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ import {
117117
BasicMinimalPluginContext,
118118
basePluginContextMeta,
119119
} from './server/pluginContainer'
120+
import { FullBundleDevEnvironment } from './server/environments/fullBundleEnvironment'
120121

121122
const debug = createDebugger('vite:config', { depth: 10 })
122123
const promisifiedRealpath = promisify(fs.realpath)
@@ -230,6 +231,13 @@ function defaultCreateClientDevEnvironment(
230231
config: ResolvedConfig,
231232
context: CreateDevEnvironmentContext,
232233
) {
234+
if (config.experimental.fullBundleMode) {
235+
return new FullBundleDevEnvironment(name, config, {
236+
hot: true,
237+
transport: context.ws,
238+
})
239+
}
240+
233241
return new DevEnvironment(name, config, {
234242
hot: true,
235243
transport: context.ws,
Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'node:path'
2+
import fs from 'node:fs'
23
import type { Plugin } from '../plugin'
34
import type { ResolvedConfig } from '../config'
45
import { CLIENT_ENTRY, ENV_ENTRY } from '../constants'
@@ -33,66 +34,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
3334
return {
3435
name: 'vite:client-inject',
3536
async buildStart() {
36-
const resolvedServerHostname = (await resolveHostname(config.server.host))
37-
.name
38-
const resolvedServerPort = config.server.port!
39-
const devBase = config.base
40-
41-
const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}`
42-
43-
let hmrConfig = config.server.hmr
44-
hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined
45-
const host = hmrConfig?.host || null
46-
const protocol = hmrConfig?.protocol || null
47-
const timeout = hmrConfig?.timeout || 30000
48-
const overlay = hmrConfig?.overlay !== false
49-
const isHmrServerSpecified = !!hmrConfig?.server
50-
const hmrConfigName = path.basename(config.configFile || 'vite.config.js')
51-
52-
// hmr.clientPort -> hmr.port
53-
// -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port
54-
let port = hmrConfig?.clientPort || hmrConfig?.port || null
55-
if (config.server.middlewareMode && !isHmrServerSpecified) {
56-
port ||= 24678
57-
}
58-
59-
let directTarget = hmrConfig?.host || resolvedServerHostname
60-
directTarget += `:${hmrConfig?.port || resolvedServerPort}`
61-
directTarget += devBase
62-
63-
let hmrBase = devBase
64-
if (hmrConfig?.path) {
65-
hmrBase = path.posix.join(hmrBase, hmrConfig.path)
66-
}
67-
68-
const modeReplacement = escapeReplacement(config.mode)
69-
const baseReplacement = escapeReplacement(devBase)
70-
const serverHostReplacement = escapeReplacement(serverHost)
71-
const hmrProtocolReplacement = escapeReplacement(protocol)
72-
const hmrHostnameReplacement = escapeReplacement(host)
73-
const hmrPortReplacement = escapeReplacement(port)
74-
const hmrDirectTargetReplacement = escapeReplacement(directTarget)
75-
const hmrBaseReplacement = escapeReplacement(hmrBase)
76-
const hmrTimeoutReplacement = escapeReplacement(timeout)
77-
const hmrEnableOverlayReplacement = escapeReplacement(overlay)
78-
const hmrConfigNameReplacement = escapeReplacement(hmrConfigName)
79-
const wsTokenReplacement = escapeReplacement(config.webSocketToken)
80-
81-
injectConfigValues = (code: string) => {
82-
return code
83-
.replace(`__MODE__`, modeReplacement)
84-
.replace(/__BASE__/g, baseReplacement)
85-
.replace(`__SERVER_HOST__`, serverHostReplacement)
86-
.replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement)
87-
.replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement)
88-
.replace(`__HMR_PORT__`, hmrPortReplacement)
89-
.replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement)
90-
.replace(`__HMR_BASE__`, hmrBaseReplacement)
91-
.replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement)
92-
.replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement)
93-
.replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement)
94-
.replace(`__WS_TOKEN__`, wsTokenReplacement)
95-
}
37+
injectConfigValues = await createClientConfigValueReplacer(config)
9638
},
9739
async transform(code, id) {
9840
const ssr = this.environment.config.consumer === 'server'
@@ -120,3 +62,83 @@ function escapeReplacement(value: string | number | boolean | null) {
12062
const jsonValue = JSON.stringify(value)
12163
return () => jsonValue
12264
}
65+
66+
async function createClientConfigValueReplacer(
67+
config: ResolvedConfig,
68+
): Promise<(code: string) => string> {
69+
const resolvedServerHostname = (await resolveHostname(config.server.host))
70+
.name
71+
const resolvedServerPort = config.server.port!
72+
const devBase = config.base
73+
74+
const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}`
75+
76+
let hmrConfig = config.server.hmr
77+
hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined
78+
const host = hmrConfig?.host || null
79+
const protocol = hmrConfig?.protocol || null
80+
const timeout = hmrConfig?.timeout || 30000
81+
const overlay = hmrConfig?.overlay !== false
82+
const isHmrServerSpecified = !!hmrConfig?.server
83+
const hmrConfigName = path.basename(config.configFile || 'vite.config.js')
84+
85+
// hmr.clientPort -> hmr.port
86+
// -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port
87+
let port = hmrConfig?.clientPort || hmrConfig?.port || null
88+
if (config.server.middlewareMode && !isHmrServerSpecified) {
89+
port ||= 24678
90+
}
91+
92+
let directTarget = hmrConfig?.host || resolvedServerHostname
93+
directTarget += `:${hmrConfig?.port || resolvedServerPort}`
94+
directTarget += devBase
95+
96+
let hmrBase = devBase
97+
if (hmrConfig?.path) {
98+
hmrBase = path.posix.join(hmrBase, hmrConfig.path)
99+
}
100+
101+
const modeReplacement = escapeReplacement(config.mode)
102+
const baseReplacement = escapeReplacement(devBase)
103+
const serverHostReplacement = escapeReplacement(serverHost)
104+
const hmrProtocolReplacement = escapeReplacement(protocol)
105+
const hmrHostnameReplacement = escapeReplacement(host)
106+
const hmrPortReplacement = escapeReplacement(port)
107+
const hmrDirectTargetReplacement = escapeReplacement(directTarget)
108+
const hmrBaseReplacement = escapeReplacement(hmrBase)
109+
const hmrTimeoutReplacement = escapeReplacement(timeout)
110+
const hmrEnableOverlayReplacement = escapeReplacement(overlay)
111+
const hmrConfigNameReplacement = escapeReplacement(hmrConfigName)
112+
const wsTokenReplacement = escapeReplacement(config.webSocketToken)
113+
const fullBundleModeReplacement = escapeReplacement(
114+
config.experimental.fullBundleMode || false,
115+
)
116+
117+
return (code) =>
118+
code
119+
.replace(`__MODE__`, modeReplacement)
120+
.replace(/__BASE__/g, baseReplacement)
121+
.replace(`__SERVER_HOST__`, serverHostReplacement)
122+
.replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement)
123+
.replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement)
124+
.replace(`__HMR_PORT__`, hmrPortReplacement)
125+
.replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement)
126+
.replace(`__HMR_BASE__`, hmrBaseReplacement)
127+
.replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement)
128+
.replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement)
129+
.replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement)
130+
.replace(`__WS_TOKEN__`, wsTokenReplacement)
131+
.replaceAll(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement)
132+
}
133+
134+
export async function getHmrImplementation(
135+
config: ResolvedConfig,
136+
): Promise<string> {
137+
const content = fs.readFileSync(normalizedClientEntry, 'utf-8')
138+
const replacer = await createClientConfigValueReplacer(config)
139+
return (
140+
replacer(content)
141+
// the rolldown runtime shouldn't be importer a module
142+
.replace(/import\s*['"]@vite\/env['"]/, '')
143+
)
144+
}

0 commit comments

Comments
 (0)