Skip to content

Commit 804d752

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

21 files changed

+536
-146
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: 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
@@ -831,7 +832,7 @@ async function buildEnvironment(
831832
}
832833
}
833834

834-
function enhanceRollupError(e: RollupError) {
835+
export function enhanceRollupError(e: RollupError): void {
835836
const stackOnly = extractStack(e)
836837

837838
let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message)
@@ -997,7 +998,7 @@ const dynamicImportWarningIgnoreList = [
997998
`statically analyzed`,
998999
]
9991000

1000-
function clearLine() {
1001+
export function clearLine(): void {
10011002
const tty = process.stdout.isTTY && !process.env.CI
10021003
if (tty) {
10031004
process.stdout.clearLine(0)

packages/vite/src/node/config.ts

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

120121
const debug = createDebugger('vite:config', { depth: 10 })
121122
const promisifiedRealpath = promisify(fs.realpath)
@@ -229,6 +230,13 @@ function defaultCreateClientDevEnvironment(
229230
config: ResolvedConfig,
230231
context: CreateDevEnvironmentContext,
231232
) {
233+
if (config.experimental.fullBundleMode) {
234+
return new FullBundleDevEnvironment(name, config, {
235+
hot: true,
236+
transport: context.ws,
237+
})
238+
}
239+
232240
return new DevEnvironment(name, config, {
233241
hot: true,
234242
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)