Skip to content

Commit 23172e2

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

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

@@ -571,3 +594,69 @@ export function injectQuery(url: string, queryToInject: string): string {
571594
}
572595

573596
export { ErrorOverlay }
597+
598+
if (isFullBundleMode) {
599+
class DevRuntime {
600+
modules: Record<string, { exports: any }> = {}
601+
602+
static getInstance() {
603+
// @ts-expect-error __rolldown_runtime__
604+
let instance = globalThis.__rolldown_runtime__
605+
if (!instance) {
606+
instance = new DevRuntime()
607+
// @ts-expect-error __rolldown_runtime__
608+
globalThis.__rolldown_runtime__ = instance
609+
}
610+
return instance
611+
}
612+
613+
createModuleHotContext(moduleId: string) {
614+
const ctx = createHotContext(moduleId)
615+
// @ts-expect-error TODO: support CSS
616+
ctx._internal = {
617+
updateStyle,
618+
removeStyle,
619+
}
620+
return ctx
621+
}
622+
623+
applyUpdates(_boundaries: string[]) {
624+
//
625+
}
626+
627+
registerModule(
628+
id: string,
629+
module: { exports: Record<string, () => unknown> },
630+
) {
631+
this.modules[id] = module
632+
}
633+
634+
loadExports(id: string) {
635+
const module = this.modules[id]
636+
if (module) {
637+
return module.exports
638+
} else {
639+
console.warn(`Module ${id} not found`)
640+
return {}
641+
}
642+
}
643+
644+
// __esmMin
645+
// @ts-expect-error need to add typing
646+
createEsmInitializer = (fn, res) => () => (fn && (res = fn((fn = 0))), res)
647+
// __commonJSMin
648+
// @ts-expect-error need to add typing
649+
createCjsInitializer = (cb, mod) => () => (
650+
mod || cb((mod = { exports: {} }).exports, mod), mod.exports
651+
)
652+
// @ts-expect-error it is exits
653+
__toESM = __toESM
654+
// @ts-expect-error it is exits
655+
__toCommonJS = __toCommonJS
656+
// @ts-expect-error it is exits
657+
__export = __export
658+
}
659+
660+
// @ts-expect-error __rolldown_runtime__
661+
globalThis.__rolldown_runtime__ ||= new DevRuntime()
662+
}

packages/vite/src/node/build.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,10 +479,11 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
479479
pre: Plugin[]
480480
post: Plugin[]
481481
}> {
482+
const isBuild = config.command === 'build'
482483
return {
483484
pre: [
484485
completeSystemWrapPlugin(),
485-
prepareOutDirPlugin(),
486+
...(isBuild ? [prepareOutDirPlugin()] : []),
486487
perEnvironmentPlugin(
487488
'vite:rollup-options-plugins',
488489
async (environment) =>
@@ -504,8 +505,8 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
504505
: []),
505506
]
506507
: []),
507-
terserPlugin(config),
508-
...(!config.isWorker
508+
...(isBuild ? [terserPlugin(config)] : []),
509+
...(isBuild && !config.isWorker
509510
? [
510511
manifestPlugin(config),
511512
ssrManifestPlugin(),
@@ -546,10 +547,10 @@ function resolveConfigToBuild(
546547
)
547548
}
548549

549-
function resolveRolldownOptions(
550+
export function resolveRolldownOptions(
550551
environment: Environment,
551552
chunkMetadataMap: ChunkMetadataMap,
552-
) {
553+
): RolldownOptions {
553554
const { root, packageCache, build: options } = environment.config
554555
const libOptions = options.lib
555556
const { logger } = environment
@@ -855,7 +856,7 @@ async function buildEnvironment(
855856
}
856857
}
857858

858-
function enhanceRollupError(e: RollupError) {
859+
export function enhanceRollupError(e: RollupError): void {
859860
const stackOnly = extractStack(e)
860861

861862
let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message)
@@ -1021,7 +1022,7 @@ const dynamicImportWarningIgnoreList = [
10211022
`statically analyzed`,
10221023
]
10231024

1024-
function clearLine() {
1025+
export function clearLine(): void {
10251026
const tty = process.stdout.isTTY && !process.env.CI
10261027
if (tty) {
10271028
process.stdout.clearLine(0)

packages/vite/src/node/config.ts

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

122123
const debug = createDebugger('vite:config', { depth: 10 })
123124
const promisifiedRealpath = promisify(fs.realpath)
@@ -231,6 +232,13 @@ function defaultCreateClientDevEnvironment(
231232
config: ResolvedConfig,
232233
context: CreateDevEnvironmentContext,
233234
) {
235+
if (config.experimental.fullBundleMode) {
236+
return new FullBundleDevEnvironment(name, config, {
237+
hot: true,
238+
transport: context.ws,
239+
})
240+
}
241+
234242
return new DevEnvironment(name, config, {
235243
hot: true,
236244
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)