mirror of
https://github.com/vitejs/vite.git
synced 2024-11-21 22:59:10 +00:00
fix(worker): throw error when circular worker import is detected and support self referencing worker (#16103)
This commit is contained in:
parent
e92abe5816
commit
eef9da13d0
@ -328,7 +328,7 @@ export interface LegacyOptions {
|
||||
|
||||
export interface ResolvedWorkerOptions {
|
||||
format: 'es' | 'iife'
|
||||
plugins: () => Promise<Plugin[]>
|
||||
plugins: (bundleChain: string[]) => Promise<Plugin[]>
|
||||
rollupOptions: RollupOptions
|
||||
}
|
||||
|
||||
@ -357,6 +357,8 @@ export type ResolvedConfig = Readonly<
|
||||
// in nested worker bundle to find the main config
|
||||
/** @internal */
|
||||
mainConfig: ResolvedConfig | null
|
||||
/** @internal list of bundle entry id. used to detect recursive worker bundle. */
|
||||
bundleChain: string[]
|
||||
isProduction: boolean
|
||||
envDir: string
|
||||
env: Record<string, any>
|
||||
@ -689,7 +691,7 @@ export async function resolveConfig(
|
||||
)
|
||||
}
|
||||
|
||||
const createWorkerPlugins = async function () {
|
||||
const createWorkerPlugins = async function (bundleChain: string[]) {
|
||||
// Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example).
|
||||
// And Plugins may also have cached that could be corrupted by being used in these extra rollup calls.
|
||||
// So we need to separate the worker plugin from the plugin that vite needs to run.
|
||||
@ -719,6 +721,7 @@ export async function resolveConfig(
|
||||
...resolved,
|
||||
isWorker: true,
|
||||
mainConfig: resolved,
|
||||
bundleChain,
|
||||
}
|
||||
const resolvedWorkerPlugins = await resolvePlugins(
|
||||
workerResolved,
|
||||
@ -760,6 +763,7 @@ export async function resolveConfig(
|
||||
ssr,
|
||||
isWorker: false,
|
||||
mainConfig: null,
|
||||
bundleChain: [],
|
||||
isProduction,
|
||||
plugins: userPlugins,
|
||||
css: resolveCSSOptions(config.css),
|
||||
|
@ -5,7 +5,7 @@ import type { ResolvedConfig } from '../config'
|
||||
import type { Plugin } from '../plugin'
|
||||
import type { ViteDevServer } from '../server'
|
||||
import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants'
|
||||
import { getHash, injectQuery, urlRE } from '../utils'
|
||||
import { getHash, injectQuery, prettifyUrl, urlRE } from '../utils'
|
||||
import {
|
||||
createToImportMetaURLBasedRelativeRuntime,
|
||||
onRollupWarning,
|
||||
@ -50,13 +50,22 @@ async function bundleWorkerEntry(
|
||||
config: ResolvedConfig,
|
||||
id: string,
|
||||
): Promise<OutputChunk> {
|
||||
const input = cleanUrl(id)
|
||||
const newBundleChain = [...config.bundleChain, input]
|
||||
if (config.bundleChain.includes(input)) {
|
||||
throw new Error(
|
||||
'Circular worker imports detected. Vite does not support it. ' +
|
||||
`Import chain: ${newBundleChain.map((id) => prettifyUrl(id, config.root)).join(' -> ')}`,
|
||||
)
|
||||
}
|
||||
|
||||
// bundle the file as entry to support imports
|
||||
const { rollup } = await import('rollup')
|
||||
const { plugins, rollupOptions, format } = config.worker
|
||||
const bundle = await rollup({
|
||||
...rollupOptions,
|
||||
input: cleanUrl(id),
|
||||
plugins: await plugins(),
|
||||
input,
|
||||
plugins: await plugins(newBundleChain),
|
||||
onwarn(warning, warn) {
|
||||
onRollupWarning(warning, warn, config)
|
||||
},
|
||||
@ -262,8 +271,6 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
const workerMatch = workerOrSharedWorkerRE.exec(id)
|
||||
if (!workerMatch) return
|
||||
|
||||
// stringified url or `new URL(...)`
|
||||
let url: string
|
||||
const { format } = config.worker
|
||||
const workerConstructor =
|
||||
workerMatch[1] === 'sharedworker' ? 'SharedWorker' : 'Worker'
|
||||
@ -277,8 +284,11 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
name: options?.name
|
||||
}`
|
||||
|
||||
let urlCode: string
|
||||
if (isBuild) {
|
||||
if (inlineRE.test(id)) {
|
||||
if (isWorker && this.getModuleInfo(cleanUrl(id))?.isEntry) {
|
||||
urlCode = 'self.location.href'
|
||||
} else if (inlineRE.test(id)) {
|
||||
const chunk = await bundleWorkerEntry(config, id)
|
||||
const encodedJs = `const encodedJs = "${Buffer.from(
|
||||
chunk.code,
|
||||
@ -335,16 +345,17 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
map: { mappings: '' },
|
||||
}
|
||||
} else {
|
||||
url = await workerFileToUrl(config, id)
|
||||
urlCode = JSON.stringify(await workerFileToUrl(config, id))
|
||||
}
|
||||
} else {
|
||||
url = await fileToUrl(cleanUrl(id), config, this)
|
||||
let url = await fileToUrl(cleanUrl(id), config, this)
|
||||
url = injectQuery(url, `${WORKER_FILE_ID}&type=${workerType}`)
|
||||
urlCode = JSON.stringify(url)
|
||||
}
|
||||
|
||||
if (urlRE.test(id)) {
|
||||
return {
|
||||
code: `export default ${JSON.stringify(url)}`,
|
||||
code: `export default ${urlCode}`,
|
||||
map: { mappings: '' }, // Empty sourcemap to suppress Rollup warning
|
||||
}
|
||||
}
|
||||
@ -352,7 +363,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
return {
|
||||
code: `export default function WorkerWrapper(options) {
|
||||
return new ${workerConstructor}(
|
||||
${JSON.stringify(url)},
|
||||
${urlCode},
|
||||
${workerTypeOption}
|
||||
);
|
||||
}`,
|
||||
|
@ -165,22 +165,30 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
|
||||
: slash(path.resolve(path.dirname(id), url))
|
||||
}
|
||||
|
||||
let builtUrl: string
|
||||
if (isBuild) {
|
||||
builtUrl = await workerFileToUrl(config, file)
|
||||
if (
|
||||
isBuild &&
|
||||
config.isWorker &&
|
||||
this.getModuleInfo(cleanUrl(file))?.isEntry
|
||||
) {
|
||||
s.update(expStart, expEnd, 'self.location.href')
|
||||
} else {
|
||||
builtUrl = await fileToUrl(cleanUrl(file), config, this)
|
||||
builtUrl = injectQuery(
|
||||
builtUrl,
|
||||
`${WORKER_FILE_ID}&type=${workerType}`,
|
||||
let builtUrl: string
|
||||
if (isBuild) {
|
||||
builtUrl = await workerFileToUrl(config, file)
|
||||
} else {
|
||||
builtUrl = await fileToUrl(cleanUrl(file), config, this)
|
||||
builtUrl = injectQuery(
|
||||
builtUrl,
|
||||
`${WORKER_FILE_ID}&type=${workerType}`,
|
||||
)
|
||||
}
|
||||
s.update(
|
||||
expStart,
|
||||
expEnd,
|
||||
// add `'' +` to skip vite:asset-import-meta-url plugin
|
||||
`new URL('' + ${JSON.stringify(builtUrl)}, import.meta.url)`,
|
||||
)
|
||||
}
|
||||
s.update(
|
||||
expStart,
|
||||
expEnd,
|
||||
// add `'' +` to skip vite:asset-import-meta-url plugin
|
||||
`new URL('' + ${JSON.stringify(builtUrl)}, import.meta.url)`,
|
||||
)
|
||||
}
|
||||
|
||||
if (s) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { isBuild, page, testDir, untilUpdated } from '~utils'
|
||||
import { expectWithRetry, isBuild, page, testDir, untilUpdated } from '~utils'
|
||||
|
||||
test('normal', async () => {
|
||||
await untilUpdated(() => page.textContent('.pong'), 'pong', true)
|
||||
@ -111,7 +111,7 @@ describe.runIf(isBuild)('build', () => {
|
||||
test('inlined code generation', async () => {
|
||||
const assetsDir = path.resolve(testDir, 'dist/es/assets')
|
||||
const files = fs.readdirSync(assetsDir)
|
||||
expect(files.length).toBe(32)
|
||||
expect(files.length).toBe(34)
|
||||
const index = files.find((f) => f.includes('main-module'))
|
||||
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
|
||||
const worker = files.find((f) => f.includes('my-worker'))
|
||||
@ -228,3 +228,15 @@ test('import.meta.glob with eager in worker', async () => {
|
||||
true,
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference url worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-url-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import {
|
||||
expectWithRetry,
|
||||
isBuild,
|
||||
isServe,
|
||||
page,
|
||||
@ -74,7 +75,7 @@ describe.runIf(isBuild)('build', () => {
|
||||
test('inlined code generation', async () => {
|
||||
const assetsDir = path.resolve(testDir, 'dist/iife/assets')
|
||||
const files = fs.readdirSync(assetsDir)
|
||||
expect(files.length).toBe(20)
|
||||
expect(files.length).toBe(22)
|
||||
const index = files.find((f) => f.includes('main-module'))
|
||||
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
|
||||
const worker = files.find((f) => f.includes('worker_entry-my-worker'))
|
||||
@ -160,6 +161,18 @@ test('import.meta.glob eager in worker', async () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference url worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-url-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
||||
test.runIf(isServe)('sourcemap boundary', async () => {
|
||||
const response = page.waitForResponse(/my-worker.ts\?worker_file&type=module/)
|
||||
await page.goto(viteTestUrl)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { isBuild, page, testDir, untilUpdated } from '~utils'
|
||||
import { expectWithRetry, isBuild, page, testDir, untilUpdated } from '~utils'
|
||||
|
||||
test('normal', async () => {
|
||||
await untilUpdated(() => page.textContent('.pong'), 'pong', true)
|
||||
@ -161,3 +161,15 @@ test('import.meta.glob with eager in worker', async () => {
|
||||
true,
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
||||
test('self reference url worker', async () => {
|
||||
expectWithRetry(() => page.textContent('.self-reference-url-worker')).toBe(
|
||||
'pong: main\npong: nested\n',
|
||||
)
|
||||
})
|
||||
|
@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => {
|
||||
|
||||
const files = fs.readdirSync(assetsDir)
|
||||
// should have 2 worker chunk
|
||||
expect(files.length).toBe(40)
|
||||
expect(files.length).toBe(44)
|
||||
const index = files.find((f) => f.includes('main-module'))
|
||||
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
|
||||
const indexSourcemap = getSourceMapUrl(content)
|
||||
|
@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => {
|
||||
|
||||
const files = fs.readdirSync(assetsDir)
|
||||
// should have 2 worker chunk
|
||||
expect(files.length).toBe(20)
|
||||
expect(files.length).toBe(22)
|
||||
const index = files.find((f) => f.includes('main-module'))
|
||||
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
|
||||
const indexSourcemap = getSourceMapUrl(content)
|
||||
|
@ -9,7 +9,7 @@ describe.runIf(isBuild)('build', () => {
|
||||
const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap/assets')
|
||||
const files = fs.readdirSync(assetsDir)
|
||||
// should have 2 worker chunk
|
||||
expect(files.length).toBe(40)
|
||||
expect(files.length).toBe(44)
|
||||
const index = files.find((f) => f.includes('main-module'))
|
||||
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
|
||||
const indexSourcemap = getSourceMapUrl(content)
|
||||
|
@ -152,6 +152,18 @@
|
||||
</p>
|
||||
<code class="importMetaGlobEager-worker"></code>
|
||||
|
||||
<p>
|
||||
self reference worker
|
||||
<span class="classname">.self-reference-worker</span>
|
||||
</p>
|
||||
<code class="self-reference-worker"></code>
|
||||
|
||||
<p>
|
||||
new Worker(new URL('../self-reference-url-worker.js', import.meta.url))
|
||||
<span class="classname">.self-reference-url-worker</span>
|
||||
</p>
|
||||
<code class="self-reference-url-worker"></code>
|
||||
|
||||
<p>
|
||||
new Worker(new URL('../deeply-nested-worker.js', import.meta.url), { type:
|
||||
'module' })
|
||||
|
13
playground/worker/self-reference-url-worker.js
Normal file
13
playground/worker/self-reference-url-worker.js
Normal file
@ -0,0 +1,13 @@
|
||||
self.addEventListener('message', (e) => {
|
||||
if (e.data === 'main') {
|
||||
const selfWorker = new Worker(
|
||||
new URL('./self-reference-url-worker.js', import.meta.url),
|
||||
)
|
||||
selfWorker.postMessage('nested')
|
||||
selfWorker.addEventListener('message', (e) => {
|
||||
self.postMessage(e.data)
|
||||
})
|
||||
}
|
||||
|
||||
self.postMessage(`pong: ${e.data}`)
|
||||
})
|
13
playground/worker/self-reference-worker.js
Normal file
13
playground/worker/self-reference-worker.js
Normal file
@ -0,0 +1,13 @@
|
||||
import SelfWorker from './self-reference-worker?worker'
|
||||
|
||||
self.addEventListener('message', (e) => {
|
||||
if (e.data === 'main') {
|
||||
const selfWorker = new SelfWorker()
|
||||
selfWorker.postMessage('nested')
|
||||
selfWorker.addEventListener('message', (e) => {
|
||||
self.postMessage(e.data)
|
||||
})
|
||||
}
|
||||
|
||||
self.postMessage(`pong: ${e.data}`)
|
||||
})
|
@ -5,6 +5,7 @@ import mySharedWorker from '../my-shared-worker?sharedworker&name=shared'
|
||||
import TSOutputWorker from '../possible-ts-output-worker?worker'
|
||||
import NestedWorker from '../worker-nested-worker?worker'
|
||||
import { mode } from '../modules/workerImport'
|
||||
import SelfReferenceWorker from '../self-reference-worker?worker'
|
||||
|
||||
function text(el, text) {
|
||||
document.querySelector(el).textContent = text
|
||||
@ -158,3 +159,18 @@ importMetaGlobEagerWorker.postMessage('1')
|
||||
importMetaGlobEagerWorker.addEventListener('message', (e) => {
|
||||
text('.importMetaGlobEager-worker', JSON.stringify(e.data))
|
||||
})
|
||||
|
||||
const selfReferenceWorker = new SelfReferenceWorker()
|
||||
selfReferenceWorker.postMessage('main')
|
||||
selfReferenceWorker.addEventListener('message', (e) => {
|
||||
document.querySelector('.self-reference-worker').textContent += `${e.data}\n`
|
||||
})
|
||||
|
||||
const selfReferenceUrlWorker = new Worker(
|
||||
new URL('../self-reference-url-worker.js', import.meta.url),
|
||||
)
|
||||
selfReferenceUrlWorker.postMessage('main')
|
||||
selfReferenceUrlWorker.addEventListener('message', (e) => {
|
||||
document.querySelector('.self-reference-url-worker').textContent +=
|
||||
`${e.data}\n`
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user