Skip to content

Commit 9d09dfe

Browse files
patak-devjamsinclairbluwy
authored
fix!: worker.plugins is a function (#14685)
Co-authored-by: jamsinclair <[email protected]> Co-authored-by: Bjorn Lu <[email protected]>
1 parent cc6ac1e commit 9d09dfe

21 files changed

+237
-110
lines changed

docs/config/worker-options.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ Output format for worker bundle.
1111

1212
## worker.plugins
1313

14-
- **Type:** [`(Plugin | Plugin[])[]`](./shared-options#plugins)
14+
- **Type:** [`() => (Plugin | Plugin[])[]`](./shared-options#plugins)
1515

16-
Vite plugins that apply to worker bundle. Note that [config.plugins](./shared-options#plugins) only applies to workers in dev, it should be configured here instead for build.
16+
Vite plugins that apply to the worker bundles. Note that [config.plugins](./shared-options#plugins) only applies to workers in dev, it should be configured here instead for build.
17+
The function should return new plugin instances as they are used in parallel rollup worker builds. As such, modifying `config.worker` options in the `config` hook will be ignored.
1718

1819
## worker.rollupOptions
1920

docs/guide/migration.md

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ See the [troubleshooting guide](/guide/troubleshooting.html#vite-cjs-node-api-de
3434

3535
## General Changes
3636

37+
### `worker.plugins` is now a function
38+
39+
In Vite 4, `worker.plugins` accepted an array of plugins (`(Plugin | Plugin[])[]`). From Vite 5, it needs to be configured as a function that returns an array of plugins (`() => (Plugin | Plugin[])[]`). This change is required so parallel worker builds run more consistently and predictably.
40+
3741
### Allow path containing `.` to fallback to index.html
3842

3943
In Vite 4, accessing a path containing `.` did not fallback to index.html even if `appType` is set to `'SPA'` (default).

packages/vite/src/node/config.ts

+76-54
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,11 @@ export interface UserConfig {
259259
*/
260260
format?: 'es' | 'iife'
261261
/**
262-
* Vite plugins that apply to worker bundle
262+
* Vite plugins that apply to worker bundle. The plugins retured by this function
263+
* should be new instances every time it is called, because they are used for each
264+
* rollup worker bundling process.
263265
*/
264-
plugins?: PluginOption[]
266+
plugins?: () => PluginOption[]
265267
/**
266268
* Rollup options to build worker bundle
267269
*/
@@ -316,9 +318,9 @@ export interface LegacyOptions {
316318
*/
317319
}
318320

319-
export interface ResolvedWorkerOptions extends PluginHookUtils {
321+
export interface ResolvedWorkerOptions {
320322
format: 'es' | 'iife'
321-
plugins: Plugin[]
323+
plugins: () => Promise<Plugin[]>
322324
rollupOptions: RollupOptions
323325
}
324326

@@ -440,12 +442,6 @@ export async function resolveConfig(
440442
return p.apply === command
441443
}
442444
}
443-
// Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example).
444-
// And Plugins may also have cached that could be corrupted by being used in these extra rollup calls.
445-
// So we need to separate the worker plugin from the plugin that vite needs to run.
446-
const rawWorkerUserPlugins = (
447-
(await asyncFlatten(config.worker?.plugins || [])) as Plugin[]
448-
).filter(filterPlugin)
449445

450446
// resolve plugins
451447
const rawUserPlugins = (
@@ -659,27 +655,74 @@ export async function resolveConfig(
659655

660656
const BASE_URL = resolvedBase
661657

662-
// resolve worker
663-
let workerConfig = mergeConfig({}, config)
664-
const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] =
665-
sortUserPlugins(rawWorkerUserPlugins)
658+
let resolved: ResolvedConfig
659+
660+
let createUserWorkerPlugins = config.worker?.plugins
661+
if (Array.isArray(createUserWorkerPlugins)) {
662+
// @ts-expect-error backward compatibility
663+
createUserWorkerPlugins = () => config.worker?.plugins
664+
665+
logger.warn(
666+
colors.yellow(
667+
`worker.plugins is now a function that returns an array of plugins. ` +
668+
`Please update your Vite config accordingly.\n`,
669+
),
670+
)
671+
}
672+
673+
const createWorkerPlugins = async function () {
674+
// Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example).
675+
// And Plugins may also have cached that could be corrupted by being used in these extra rollup calls.
676+
// So we need to separate the worker plugin from the plugin that vite needs to run.
677+
const rawWorkerUserPlugins = (
678+
(await asyncFlatten(createUserWorkerPlugins?.() || [])) as Plugin[]
679+
).filter(filterPlugin)
680+
681+
// resolve worker
682+
let workerConfig = mergeConfig({}, config)
683+
const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] =
684+
sortUserPlugins(rawWorkerUserPlugins)
685+
686+
// run config hooks
687+
const workerUserPlugins = [
688+
...workerPrePlugins,
689+
...workerNormalPlugins,
690+
...workerPostPlugins,
691+
]
692+
workerConfig = await runConfigHook(
693+
workerConfig,
694+
workerUserPlugins,
695+
configEnv,
696+
)
697+
698+
const workerResolved: ResolvedConfig = {
699+
...workerConfig,
700+
...resolved,
701+
isWorker: true,
702+
mainConfig: resolved,
703+
}
704+
const resolvedWorkerPlugins = await resolvePlugins(
705+
workerResolved,
706+
workerPrePlugins,
707+
workerNormalPlugins,
708+
workerPostPlugins,
709+
)
710+
711+
// run configResolved hooks
712+
createPluginHookUtils(resolvedWorkerPlugins)
713+
.getSortedPluginHooks('configResolved')
714+
.map((hook) => hook(workerResolved))
715+
716+
return resolvedWorkerPlugins
717+
}
666718

667-
// run config hooks
668-
const workerUserPlugins = [
669-
...workerPrePlugins,
670-
...workerNormalPlugins,
671-
...workerPostPlugins,
672-
]
673-
workerConfig = await runConfigHook(workerConfig, workerUserPlugins, configEnv)
674719
const resolvedWorkerOptions: ResolvedWorkerOptions = {
675-
format: workerConfig.worker?.format || 'iife',
676-
plugins: [],
677-
rollupOptions: workerConfig.worker?.rollupOptions || {},
678-
getSortedPlugins: undefined!,
679-
getSortedPluginHooks: undefined!,
720+
format: config.worker?.format || 'iife',
721+
plugins: createWorkerPlugins,
722+
rollupOptions: config.worker?.rollupOptions || {},
680723
}
681724

682-
const resolvedConfig: ResolvedConfig = {
725+
resolved = {
683726
configFile: configFile ? normalizePath(configFile) : undefined,
684727
configFileDependencies: configFileDependencies.map((name) =>
685728
normalizePath(path.resolve(name)),
@@ -741,11 +784,10 @@ export async function resolveConfig(
741784
getSortedPlugins: undefined!,
742785
getSortedPluginHooks: undefined!,
743786
}
744-
const resolved: ResolvedConfig = {
787+
resolved = {
745788
...config,
746-
...resolvedConfig,
789+
...resolved,
747790
}
748-
749791
;(resolved.plugins as Plugin[]) = await resolvePlugins(
750792
resolved,
751793
prePlugins,
@@ -754,32 +796,12 @@ export async function resolveConfig(
754796
)
755797
Object.assign(resolved, createPluginHookUtils(resolved.plugins))
756798

757-
const workerResolved: ResolvedConfig = {
758-
...workerConfig,
759-
...resolvedConfig,
760-
isWorker: true,
761-
mainConfig: resolved,
762-
}
763-
resolvedConfig.worker.plugins = await resolvePlugins(
764-
workerResolved,
765-
workerPrePlugins,
766-
workerNormalPlugins,
767-
workerPostPlugins,
768-
)
769-
Object.assign(
770-
resolvedConfig.worker,
771-
createPluginHookUtils(resolvedConfig.worker.plugins),
772-
)
773-
774799
// call configResolved hooks
775-
await Promise.all([
776-
...resolved
800+
await Promise.all(
801+
resolved
777802
.getSortedPluginHooks('configResolved')
778803
.map((hook) => hook(resolved)),
779-
...resolvedConfig.worker
780-
.getSortedPluginHooks('configResolved')
781-
.map((hook) => hook(workerResolved)),
782-
])
804+
)
783805

784806
// validate config
785807

@@ -804,7 +826,7 @@ export async function resolveConfig(
804826
plugins: resolved.plugins.map((p) => p.name),
805827
worker: {
806828
...resolved.worker,
807-
plugins: resolved.worker.plugins.map((p) => p.name),
829+
plugins: `() => plugins`,
808830
},
809831
})
810832

packages/vite/src/node/plugins/worker.ts

+2-25
Original file line numberDiff line numberDiff line change
@@ -41,30 +41,7 @@ function saveEmitWorkerAsset(
4141
workerMap.assets.set(fileName, asset)
4242
}
4343

44-
// Ensure that only one rollup build is called at the same time to avoid
45-
// leaking state in plugins between worker builds.
46-
// TODO: Review if we can parallelize the bundling of workers.
47-
const workerConfigSemaphore = new WeakMap<
48-
ResolvedConfig,
49-
Promise<OutputChunk>
50-
>()
51-
export async function bundleWorkerEntry(
52-
config: ResolvedConfig,
53-
id: string,
54-
query: Record<string, string> | null,
55-
): Promise<OutputChunk> {
56-
const processing = workerConfigSemaphore.get(config)
57-
if (processing) {
58-
await processing
59-
return bundleWorkerEntry(config, id, query)
60-
}
61-
const promise = serialBundleWorkerEntry(config, id, query)
62-
workerConfigSemaphore.set(config, promise)
63-
promise.then(() => workerConfigSemaphore.delete(config))
64-
return promise
65-
}
66-
67-
async function serialBundleWorkerEntry(
44+
async function bundleWorkerEntry(
6845
config: ResolvedConfig,
6946
id: string,
7047
query: Record<string, string> | null,
@@ -75,7 +52,7 @@ async function serialBundleWorkerEntry(
7552
const bundle = await rollup({
7653
...rollupOptions,
7754
input: cleanUrl(id),
78-
plugins,
55+
plugins: await plugins(),
7956
onwarn(warning, warn) {
8057
onRollupWarning(warning, warn, config)
8158
},

packages/vite/src/node/utils.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,16 @@ export function removeComments(raw: string): string {
10121012
return raw.replace(multilineCommentsRE, '').replace(singlelineCommentsRE, '')
10131013
}
10141014

1015+
function backwardCompatibleWorkerPlugins(plugins: any) {
1016+
if (Array.isArray(plugins)) {
1017+
return plugins
1018+
}
1019+
if (typeof plugins === 'function') {
1020+
return plugins()
1021+
}
1022+
return []
1023+
}
1024+
10151025
function mergeConfigRecursively(
10161026
defaults: Record<string, any>,
10171027
overrides: Record<string, any>,
@@ -1045,6 +1055,12 @@ function mergeConfigRecursively(
10451055
) {
10461056
merged[key] = true
10471057
continue
1058+
} else if (key === 'plugins' && rootPath === 'worker') {
1059+
merged[key] = () => [
1060+
...backwardCompatibleWorkerPlugins(existing),
1061+
...backwardCompatibleWorkerPlugins(value),
1062+
]
1063+
continue
10481064
}
10491065

10501066
if (Array.isArray(existing) || Array.isArray(value)) {

playground/worker/__tests__/es/es-worker.spec.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,30 @@ test('worker emitted and import.meta.url in nested worker (serve)', async () =>
8080
)
8181
})
8282

83+
test('deeply nested workers', async () => {
84+
await untilUpdated(
85+
async () => page.textContent('.deeply-nested-worker'),
86+
/Hello\sfrom\sroot.*\/es\/.+deeply-nested-worker\.js/,
87+
true,
88+
)
89+
await untilUpdated(
90+
async () => page.textContent('.deeply-nested-second-worker'),
91+
/Hello\sfrom\ssecond.*\/es\/.+second-worker\.js/,
92+
true,
93+
)
94+
await untilUpdated(
95+
async () => page.textContent('.deeply-nested-third-worker'),
96+
/Hello\sfrom\sthird.*\/es\/.+third-worker\.js/,
97+
true,
98+
)
99+
})
100+
83101
describe.runIf(isBuild)('build', () => {
84102
// assert correct files
85103
test('inlined code generation', async () => {
86104
const assetsDir = path.resolve(testDir, 'dist/es/assets')
87105
const files = fs.readdirSync(assetsDir)
88-
expect(files.length).toBe(28)
106+
expect(files.length).toBe(32)
89107
const index = files.find((f) => f.includes('main-module'))
90108
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
91109
const worker = files.find((f) => f.includes('my-worker'))

playground/worker/__tests__/iife/iife-worker.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ describe.runIf(isBuild)('build', () => {
7474
test('inlined code generation', async () => {
7575
const assetsDir = path.resolve(testDir, 'dist/iife/assets')
7676
const files = fs.readdirSync(assetsDir)
77-
expect(files.length).toBe(16)
77+
expect(files.length).toBe(20)
7878
const index = files.find((f) => f.includes('main-module'))
7979
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
80-
const worker = files.find((f) => f.includes('my-worker'))
80+
const worker = files.find((f) => f.includes('worker_entry-my-worker'))
8181
const workerContent = fs.readFileSync(
8282
path.resolve(assetsDir, worker),
8383
'utf-8',

playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => {
1010

1111
const files = fs.readdirSync(assetsDir)
1212
// should have 2 worker chunk
13-
expect(files.length).toBe(32)
13+
expect(files.length).toBe(40)
1414
const index = files.find((f) => f.includes('main-module'))
1515
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
1616
const indexSourcemap = getSourceMapUrl(content)

playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => {
1010

1111
const files = fs.readdirSync(assetsDir)
1212
// should have 2 worker chunk
13-
expect(files.length).toBe(16)
13+
expect(files.length).toBe(20)
1414
const index = files.find((f) => f.includes('main-module'))
1515
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
1616
const indexSourcemap = getSourceMapUrl(content)

playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe.runIf(isBuild)('build', () => {
99
const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap/assets')
1010
const files = fs.readdirSync(assetsDir)
1111
// should have 2 worker chunk
12-
expect(files.length).toBe(32)
12+
expect(files.length).toBe(40)
1313
const index = files.find((f) => f.includes('main-module'))
1414
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
1515
const indexSourcemap = getSourceMapUrl(content)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
self.postMessage({
2+
type: 'deeplyNestedSecondWorker',
3+
data: [
4+
'Hello from second level nested worker',
5+
import.meta.env.BASE_URL,
6+
self.location.url,
7+
import.meta.url,
8+
].join(' '),
9+
})
10+
11+
const deeplyNestedThirdWorker = new Worker(
12+
new URL('deeply-nested-third-worker.js', import.meta.url),
13+
{ type: 'module' },
14+
)
15+
deeplyNestedThirdWorker.addEventListener('message', (ev) => {
16+
self.postMessage(ev.data)
17+
})
18+
19+
console.log('deeply-nested-second-worker.js')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
self.postMessage({
2+
type: 'deeplyNestedThirdWorker',
3+
data: [
4+
'Hello from third level nested worker',
5+
import.meta.env.BASE_URL,
6+
self.location.url,
7+
import.meta.url,
8+
].join(' '),
9+
})
10+
11+
console.log('deeply-nested-third-worker.js')

0 commit comments

Comments
 (0)