Skip to content

Commit f6e233c

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

File tree

17 files changed

+516
-127
lines changed

17 files changed

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

@@ -458,3 +481,69 @@ export function injectQuery(url: string, queryToInject: string): string {
458481
}
459482

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

packages/vite/src/node/build.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,12 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
480480
pre: Plugin[]
481481
post: Plugin[]
482482
}> {
483+
const isBuild = config.command === 'build'
483484
const enableNativePlugin = config.experimental.enableNativePlugin
484485
return {
485486
pre: [
486487
completeSystemWrapPlugin(),
487-
prepareOutDirPlugin(),
488+
...(isBuild ? [prepareOutDirPlugin()] : []),
488489
perEnvironmentPlugin(
489490
'vite:rollup-options-plugins',
490491
async (environment) =>
@@ -519,8 +520,8 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
519520
: []),
520521
]
521522
: []),
522-
terserPlugin(config),
523-
...(!config.isWorker
523+
...(isBuild ? [terserPlugin(config)] : []),
524+
...(isBuild && !config.isWorker
524525
? [
525526
config.build.manifest && enableNativePlugin === true
526527
? perEnvironmentPlugin('native:manifest', (environment) => {
@@ -590,7 +591,9 @@ function resolveConfigToBuild(
590591
)
591592
}
592593

593-
function resolveRolldownOptions(environment: Environment) {
594+
export function resolveRolldownOptions(
595+
environment: Environment,
596+
): RolldownOptions {
594597
const { root, packageCache } = environment.config
595598
const options = environment.config.build
596599
const libOptions = options.lib
@@ -866,7 +869,7 @@ async function buildEnvironment(
866869
}
867870
}
868871

869-
function enhanceRollupError(e: RollupError) {
872+
export function enhanceRollupError(e: RollupError): void {
870873
const stackOnly = extractStack(e)
871874

872875
let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message)
@@ -1032,7 +1035,7 @@ const dynamicImportWarningIgnoreList = [
10321035
`statically analyzed`,
10331036
]
10341037

1035-
function clearLine() {
1038+
export function clearLine(): void {
10361039
const tty = process.stdout.isTTY && !process.env.CI
10371040
if (tty) {
10381041
process.stdout.clearLine(0)

packages/vite/src/node/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ import { runnerImport } from './ssr/runnerImport'
105105
import { getAdditionalAllowedHosts } from './server/middlewares/hostCheck'
106106
import { convertEsbuildPluginToRolldownPlugin } from './optimizer/pluginConverter'
107107
import { type OxcOptions, convertEsbuildConfigToOxcConfig } from './plugins/oxc'
108+
import { FullBundleDevEnvironment } from './server/environments/fullBundleEnvironment'
108109

109110
const debug = createDebugger('vite:config', { depth: 10 })
110111
const promisifiedRealpath = promisify(fs.realpath)
@@ -217,6 +218,13 @@ function defaultCreateClientDevEnvironment(
217218
config: ResolvedConfig,
218219
context: CreateDevEnvironmentContext,
219220
) {
221+
if (config.experimental.fullBundleMode) {
222+
return new FullBundleDevEnvironment(name, config, {
223+
hot: true,
224+
transport: context.ws,
225+
})
226+
}
227+
220228
return new DevEnvironment(name, config, {
221229
hot: true,
222230
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, options) {
9840
// TODO: Remove options?.ssr, Vitest currently hijacks this plugin
@@ -121,3 +63,83 @@ function escapeReplacement(value: string | number | boolean | null) {
12163
const jsonValue = JSON.stringify(value)
12264
return () => jsonValue
12365
}
66+
67+
async function createClientConfigValueReplacer(
68+
config: ResolvedConfig,
69+
): Promise<(code: string) => string> {
70+
const resolvedServerHostname = (await resolveHostname(config.server.host))
71+
.name
72+
const resolvedServerPort = config.server.port!
73+
const devBase = config.base
74+
75+
const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}`
76+
77+
let hmrConfig = config.server.hmr
78+
hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined
79+
const host = hmrConfig?.host || null
80+
const protocol = hmrConfig?.protocol || null
81+
const timeout = hmrConfig?.timeout || 30000
82+
const overlay = hmrConfig?.overlay !== false
83+
const isHmrServerSpecified = !!hmrConfig?.server
84+
const hmrConfigName = path.basename(config.configFile || 'vite.config.js')
85+
86+
// hmr.clientPort -> hmr.port
87+
// -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port
88+
let port = hmrConfig?.clientPort || hmrConfig?.port || null
89+
if (config.server.middlewareMode && !isHmrServerSpecified) {
90+
port ||= 24678
91+
}
92+
93+
let directTarget = hmrConfig?.host || resolvedServerHostname
94+
directTarget += `:${hmrConfig?.port || resolvedServerPort}`
95+
directTarget += devBase
96+
97+
let hmrBase = devBase
98+
if (hmrConfig?.path) {
99+
hmrBase = path.posix.join(hmrBase, hmrConfig.path)
100+
}
101+
102+
const modeReplacement = escapeReplacement(config.mode)
103+
const baseReplacement = escapeReplacement(devBase)
104+
const serverHostReplacement = escapeReplacement(serverHost)
105+
const hmrProtocolReplacement = escapeReplacement(protocol)
106+
const hmrHostnameReplacement = escapeReplacement(host)
107+
const hmrPortReplacement = escapeReplacement(port)
108+
const hmrDirectTargetReplacement = escapeReplacement(directTarget)
109+
const hmrBaseReplacement = escapeReplacement(hmrBase)
110+
const hmrTimeoutReplacement = escapeReplacement(timeout)
111+
const hmrEnableOverlayReplacement = escapeReplacement(overlay)
112+
const hmrConfigNameReplacement = escapeReplacement(hmrConfigName)
113+
const wsTokenReplacement = escapeReplacement(config.webSocketToken)
114+
const fullBundleModeReplacement = escapeReplacement(
115+
config.experimental.fullBundleMode || false,
116+
)
117+
118+
return (code) =>
119+
code
120+
.replace(`__MODE__`, modeReplacement)
121+
.replace(/__BASE__/g, baseReplacement)
122+
.replace(`__SERVER_HOST__`, serverHostReplacement)
123+
.replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement)
124+
.replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement)
125+
.replace(`__HMR_PORT__`, hmrPortReplacement)
126+
.replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement)
127+
.replace(`__HMR_BASE__`, hmrBaseReplacement)
128+
.replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement)
129+
.replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement)
130+
.replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement)
131+
.replace(`__WS_TOKEN__`, wsTokenReplacement)
132+
.replaceAll(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement)
133+
}
134+
135+
export async function getHmrImplementation(
136+
config: ResolvedConfig,
137+
): Promise<string> {
138+
const content = fs.readFileSync(normalizedClientEntry, 'utf-8')
139+
const replacer = await createClientConfigValueReplacer(config)
140+
return (
141+
replacer(content)
142+
// the rolldown runtime shouldn't be importer a module
143+
.replace(/import\s*['"]@vite\/env['"]/, '')
144+
)
145+
}

0 commit comments

Comments
 (0)