From 668f970e08fa2ed6112bd5474e51c4260eacef8e Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:56:24 +0000 Subject: [PATCH 01/10] feat(worker): Support dynamic fields in worker options --- .../plugins/workerImportMetaUrl.spec.ts | 121 ++++++++++++++++++ .../src/node/plugins/workerImportMetaUrl.ts | 68 +++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts new file mode 100644 index 00000000000000..973686e8364d6c --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -0,0 +1,121 @@ +import { describe, expect, test } from 'vitest' +import { parseAst } from 'rollup/parseAst' +import { workerImportMetaUrlPlugin } from '../../plugins/workerImportMetaUrl' +import { resolveConfig } from '../../config' +import { PartialEnvironment } from '../../baseEnvironment' + +async function createWorkerImportMetaUrlPluginTransform() { + const config = await resolveConfig({ configFile: false }, 'serve') + const instance = workerImportMetaUrlPlugin(config) + const environment = new PartialEnvironment('client', config) + + return async (code: string) => { + // @ts-expect-error transform should exist + const result = await instance.transform.call( + { environment, parse: parseAst }, + code, + 'foo.ts', + ) + return result?.code || result + } +} + +describe('workerImportMetaUrlPlugin', async () => { + const transform = await createWorkerImportMetaUrlPluginTransform() + + test('without worker options', async () => { + expect( + await transform('new Worker(new URL(`./worker.js`, import.meta.url))'), + ).toMatchInlineSnapshot( + `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`, + ) + }) + + test('with shared worker', async () => { + expect( + await transform( + 'new SharedWorker(new URL(`./worker.js`, import.meta.url))', + ), + ).toMatchInlineSnapshot( + `"new SharedWorker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`, + ) + }) + + test('with static worker options', async () => { + expect( + await transform( + 'new Worker(new URL(`./worker.js`, import.meta.url), { type: "module", name: "worker1" })', + ), + ).toMatchInlineSnapshot( + `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { type: "module", name: "worker1" })"`, + ) + }) + + test('with dynamic name field in worker options', async () => { + expect( + await transform( + 'const id = 1; new Worker(new URL(`./worker.js`, import.meta.url), { name: "worker" + id })', + ), + ).toMatchInlineSnapshot( + `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url), { name: "worker" + id })"`, + ) + }) + + test('with dynamic name field and static type in worker options', async () => { + expect( + await transform( + 'const id = 1; new Worker(new URL(`./worker.js`, import.meta.url), { name: "worker" + id, type: "module" })', + ), + ).toMatchInlineSnapshot( + `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: "worker" + id, type: "module" })"`, + ) + }) + + test('throws an error when non-static worker options are provided', async () => { + await expect( + transform( + 'new Worker(new URL(`./worker.js`, import.meta.url), myWorkerOptions)', + ), + ).rejects.toThrow( + 'Vite is unable to parse the worker options as the value is not static. To ignore this error, please use /* @vite-ignore */ in the worker options.', + ) + }) + + test('throws an error when worker options are not an object', async () => { + await expect( + transform( + 'new Worker(new URL(`./worker.js`, import.meta.url), "notAnObject")', + ), + ).rejects.toThrow('Expected worker options to be an object, got string') + }) + + test('throws an error when non-literal type field in worker options', async () => { + await expect( + transform( + 'const type = "module"; new Worker(new URL(`./worker.js`, import.meta.url), { type })', + ), + ).rejects.toThrow( + 'Expected worker options type property to be a literal value.', + ) + }) + + test('throws an error when spread operator used without the type field', async () => { + await expect( + transform( + 'const options = { name: "worker1" }; new Worker(new URL(`./worker.js`, import.meta.url), { ...options })', + ), + ).rejects.toThrow( + 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.', + ) + }) + + test('throws an error when spread operator used after definition of type field', async () => { + await expect( + transform( + 'const options = { name: "worker1" }; new Worker(new URL(`./worker.js`, import.meta.url), { type: "module", ...options })', + ), + ).rejects.toThrow( + 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.', + ) + }) +}) diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 8d8d316a4ec214..a63a85a417270b 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -25,16 +25,68 @@ function err(e: string, pos: number) { return error } -function parseWorkerOptions( +function extractWorkerTypeFromAst(astNode: any, optsStartIndex: number) { + if (astNode.type !== 'ObjectExpression' || astNode.properties?.length < 1) { + return + } + + let lastSpreadElementIndex = -1 + const typePropertyIndex = astNode.properties.findLastIndex( + (property: any, index: number) => { + if (property.type === 'SpreadElement' && lastSpreadElementIndex === -1) { + lastSpreadElementIndex = index + return + } + + if (property.type === 'Property' && property.key.name === 'type') { + return true + } + }, + ) + + if (typePropertyIndex === -1 && lastSpreadElementIndex === -1) { + // No type property or spread element in use. Assume safe definition and default to classic worker + return 'classic' + } + + if (typePropertyIndex < lastSpreadElementIndex) { + throw err( + 'Expected object spread to be used before the definition of the type property. ' + + 'Vite needs a static value for the type property to correctly infer it.', + optsStartIndex, + ) + } + + const typeProperty = astNode.properties[typePropertyIndex] + if (typeProperty?.value?.type !== 'Literal') { + throw err( + 'Expected worker options type property to be a literal value.', + optsStartIndex, + ) + } + + return typeProperty?.value?.value +} + +async function parseWorkerOptions( rawOpts: string, optsStartIndex: number, -): WorkerOptions { +): Promise { let opts: WorkerOptions = {} try { opts = evalValue(rawOpts) } catch { + const { parseAstAsync } = await import('rollup/parseAst') + const optsNode = ((await parseAstAsync(`(${rawOpts})`)).body[0] as any) + .expression + + const type = extractWorkerTypeFromAst(optsNode, optsStartIndex) + if (type) { + return { type } + } + throw err( - 'Vite is unable to parse the worker options as the value is not static.' + + 'Vite is unable to parse the worker options as the value is not static. ' + 'To ignore this error, please use /* @vite-ignore */ in the worker options.', optsStartIndex, ) @@ -54,7 +106,11 @@ function parseWorkerOptions( return opts } -function getWorkerType(raw: string, clean: string, i: number): WorkerType { +async function getWorkerType( + raw: string, + clean: string, + i: number, +): Promise { const commaIndex = clean.indexOf(',', i) if (commaIndex === -1) { return 'classic' @@ -82,7 +138,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType { return 'classic' } - const workerOpts = parseWorkerOptions(workerOptString, commaIndex + 1) + const workerOpts = await parseWorkerOptions(workerOptString, commaIndex + 1) if ( workerOpts.type && (workerOpts.type === 'module' || workerOpts.type === 'classic') @@ -152,7 +208,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { } s ||= new MagicString(code) - const workerType = getWorkerType(code, cleanString, endIndex) + const workerType = await getWorkerType(code, cleanString, endIndex) const url = rawUrl.slice(1, -1) let file: string | undefined if (url[0] === '.') { From 9c487170db14df7cd6cae65cf9aae47a4851520b Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:49:16 +0000 Subject: [PATCH 02/10] chore(worker): remove vite-ignore comment from playground script --- playground/worker/worker/main-module.js | 1 - 1 file changed, 1 deletion(-) diff --git a/playground/worker/worker/main-module.js b/playground/worker/worker/main-module.js index ed694895296f0e..a659aa438fd5ca 100644 --- a/playground/worker/worker/main-module.js +++ b/playground/worker/worker/main-module.js @@ -140,7 +140,6 @@ const genWorkerName = () => 'module' const w2 = new SharedWorker( new URL('../url-shared-worker.js', import.meta.url), { - /* @vite-ignore */ name: genWorkerName(), type: 'module', }, From c286a32a5b1392e913f8dc0fb3aafd2ee633dbb1 Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:04:59 +0000 Subject: [PATCH 03/10] refactor: use basic for loop and minor tweaks --- .../plugins/workerImportMetaUrl.spec.ts | 20 +++++----- .../src/node/plugins/workerImportMetaUrl.ts | 39 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index 973686e8364d6c..be9439f56041e2 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -25,7 +25,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('without worker options', async () => { expect( - await transform('new Worker(new URL(`./worker.js`, import.meta.url))'), + await transform('new Worker(new URL("./worker.js", import.meta.url))'), ).toMatchInlineSnapshot( `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`, ) @@ -34,7 +34,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('with shared worker', async () => { expect( await transform( - 'new SharedWorker(new URL(`./worker.js`, import.meta.url))', + 'new SharedWorker(new URL("./worker.js", import.meta.url))', ), ).toMatchInlineSnapshot( `"new SharedWorker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`, @@ -44,7 +44,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('with static worker options', async () => { expect( await transform( - 'new Worker(new URL(`./worker.js`, import.meta.url), { type: "module", name: "worker1" })', + 'new Worker(new URL("./worker.js", import.meta.url), { type: "module", name: "worker1" })', ), ).toMatchInlineSnapshot( `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { type: "module", name: "worker1" })"`, @@ -54,7 +54,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('with dynamic name field in worker options', async () => { expect( await transform( - 'const id = 1; new Worker(new URL(`./worker.js`, import.meta.url), { name: "worker" + id })', + 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id })', ), ).toMatchInlineSnapshot( `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url), { name: "worker" + id })"`, @@ -64,7 +64,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('with dynamic name field and static type in worker options', async () => { expect( await transform( - 'const id = 1; new Worker(new URL(`./worker.js`, import.meta.url), { name: "worker" + id, type: "module" })', + 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id, type: "module" })', ), ).toMatchInlineSnapshot( `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: "worker" + id, type: "module" })"`, @@ -74,7 +74,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('throws an error when non-static worker options are provided', async () => { await expect( transform( - 'new Worker(new URL(`./worker.js`, import.meta.url), myWorkerOptions)', + 'new Worker(new URL("./worker.js", import.meta.url), myWorkerOptions)', ), ).rejects.toThrow( 'Vite is unable to parse the worker options as the value is not static. To ignore this error, please use /* @vite-ignore */ in the worker options.', @@ -84,7 +84,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('throws an error when worker options are not an object', async () => { await expect( transform( - 'new Worker(new URL(`./worker.js`, import.meta.url), "notAnObject")', + 'new Worker(new URL("./worker.js", import.meta.url), "notAnObject")', ), ).rejects.toThrow('Expected worker options to be an object, got string') }) @@ -92,7 +92,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('throws an error when non-literal type field in worker options', async () => { await expect( transform( - 'const type = "module"; new Worker(new URL(`./worker.js`, import.meta.url), { type })', + 'const type = "module"; new Worker(new URL("./worker.js", import.meta.url), { type })', ), ).rejects.toThrow( 'Expected worker options type property to be a literal value.', @@ -102,7 +102,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('throws an error when spread operator used without the type field', async () => { await expect( transform( - 'const options = { name: "worker1" }; new Worker(new URL(`./worker.js`, import.meta.url), { ...options })', + 'const options = { name: "worker1" }; new Worker(new URL("./worker.js", import.meta.url), { ...options })', ), ).rejects.toThrow( 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.', @@ -112,7 +112,7 @@ describe('workerImportMetaUrlPlugin', async () => { test('throws an error when spread operator used after definition of type field', async () => { await expect( transform( - 'const options = { name: "worker1" }; new Worker(new URL(`./worker.js`, import.meta.url), { type: "module", ...options })', + 'const options = { name: "worker1" }; new Worker(new URL("./worker.js", import.meta.url), { type: "module", ...options })', ), ).rejects.toThrow( 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.', diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index a63a85a417270b..6816b3bc075a3a 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -25,27 +25,34 @@ function err(e: string, pos: number) { return error } -function extractWorkerTypeFromAst(astNode: any, optsStartIndex: number) { - if (astNode.type !== 'ObjectExpression' || astNode.properties?.length < 1) { +function extractWorkerTypeFromAst( + astNode: any, + optsStartIndex: number, +): 'classic' | 'module' | undefined { + if (astNode.type !== 'ObjectExpression') { return } let lastSpreadElementIndex = -1 - const typePropertyIndex = astNode.properties.findLastIndex( - (property: any, index: number) => { - if (property.type === 'SpreadElement' && lastSpreadElementIndex === -1) { - lastSpreadElementIndex = index - return - } + let typeProperty = null + let typePropertyIndex = -1 - if (property.type === 'Property' && property.key.name === 'type') { - return true - } - }, - ) + for (let i = 0; i < astNode.properties.length; i++) { + const property = astNode.properties[i] + + if (property.type === 'SpreadElement') { + lastSpreadElementIndex = i + continue + } + + if (property.type === 'Property' && property.key?.name === 'type') { + typeProperty = property + typePropertyIndex = i + } + } if (typePropertyIndex === -1 && lastSpreadElementIndex === -1) { - // No type property or spread element in use. Assume safe definition and default to classic worker + // No type property or spread element in use. Assume safe usage and default to classic return 'classic' } @@ -57,7 +64,6 @@ function extractWorkerTypeFromAst(astNode: any, optsStartIndex: number) { ) } - const typeProperty = astNode.properties[typePropertyIndex] if (typeProperty?.value?.type !== 'Literal') { throw err( 'Expected worker options type property to be a literal value.', @@ -65,7 +71,8 @@ function extractWorkerTypeFromAst(astNode: any, optsStartIndex: number) { ) } - return typeProperty?.value?.value + // Silently default to classic type like the getWorkerType method + return typeProperty.value.value === 'module' ? 'module' : 'classic' } async function parseWorkerOptions( From 7cf1551940330f16782846026c5e3b3f78787351 Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Fri, 20 Dec 2024 03:54:55 +0000 Subject: [PATCH 04/10] fix(worker): correctly handle when worker options contain parenthesis --- .../plugins/workerImportMetaUrl.spec.ts | 32 +++++++++++++++++++ .../src/node/plugins/workerImportMetaUrl.ts | 14 +++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index be9439f56041e2..b18c088bcef212 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -71,6 +71,38 @@ describe('workerImportMetaUrlPlugin', async () => { ) }) + test('with parenthesis inside of worker options', async () => { + expect( + await transform( + 'const worker = new Worker(new URL("./worker.js", import.meta.url), { name: genName(), type: "module"})', + ), + ).toMatchInlineSnapshot( + `"const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: genName(), type: "module"})"`, + ) + }) + + test('with multi-line code and worker options', async () => { + expect( + await transform(` +const worker = new Worker(new URL("./worker.js", import.meta.url), { + name: genName(), + type: "module", + }, +) + +worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data))) +`), + ).toMatchInlineSnapshot(`" +const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { + name: genName(), + type: "module", + }, +) + +worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data))) +"`) + }) + test('throws an error when non-static worker options are provided', async () => { await expect( transform( diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 6816b3bc075a3a..5f94b7424961a4 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -25,6 +25,18 @@ function err(e: string, pos: number) { return error } +function findClosingParen(input: string, fromIndex: number) { + let count = 1 + + for (let i = fromIndex + 1; i < input.length; i++) { + if (input[i] === '(') count++ + if (input[i] === ')') count-- + if (count === 0) return i + } + + return -1 +} + function extractWorkerTypeFromAst( astNode: any, optsStartIndex: number, @@ -122,7 +134,7 @@ async function getWorkerType( if (commaIndex === -1) { return 'classic' } - const endIndex = clean.indexOf(')', i) + const endIndex = findClosingParen(clean, i) // case: ') ... ,' mean no worker options params if (commaIndex > endIndex) { From cc6af2c1f8135e8acceda57cc5a6c5b018c466da Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Sat, 21 Dec 2024 01:32:16 +0000 Subject: [PATCH 05/10] test: fix path resolution when run on windows --- .../src/node/__tests__/plugins/workerImportMetaUrl.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index b18c088bcef212..5fb8387dc6531f 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -1,3 +1,4 @@ +import path from 'node:path' import { describe, expect, test } from 'vitest' import { parseAst } from 'rollup/parseAst' import { workerImportMetaUrlPlugin } from '../../plugins/workerImportMetaUrl' @@ -5,7 +6,8 @@ import { resolveConfig } from '../../config' import { PartialEnvironment } from '../../baseEnvironment' async function createWorkerImportMetaUrlPluginTransform() { - const config = await resolveConfig({ configFile: false }, 'serve') + const root = path.join(import.meta.dirname, 'fixtures/worker') + const config = await resolveConfig({ configFile: false, root }, 'serve') const instance = workerImportMetaUrlPlugin(config) const environment = new PartialEnvironment('client', config) @@ -14,7 +16,7 @@ async function createWorkerImportMetaUrlPluginTransform() { const result = await instance.transform.call( { environment, parse: parseAst }, code, - 'foo.ts', + path.join(root, 'foo.ts'), ) return result?.code || result } From d64f37bf60440b910cb30eef14be36d1ca6897fe Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:05:17 +0000 Subject: [PATCH 06/10] fix(worker): correctly convert resolved files to unix paths --- packages/vite/src/node/plugins/workerImportMetaUrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 5f94b7424961a4..4b5af884b27f61 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -232,7 +232,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { let file: string | undefined if (url[0] === '.') { file = path.resolve(path.dirname(id), url) - file = tryFsResolve(file, fsResolveOptions) ?? file + file = slash(tryFsResolve(file, fsResolveOptions) ?? file) } else { workerResolver ??= createBackCompatIdResolver(config, { extensions: [], From 4c12166003cef9badba91ea655bfc42a5f7eb4c2 Mon Sep 17 00:00:00 2001 From: Jamie <5964236+jamsinclair@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:06:03 +0000 Subject: [PATCH 07/10] chore: configure unix root always for test --- .../node/__tests__/plugins/workerImportMetaUrl.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index 5fb8387dc6531f..ca033ac73bc9a7 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -5,8 +5,14 @@ import { workerImportMetaUrlPlugin } from '../../plugins/workerImportMetaUrl' import { resolveConfig } from '../../config' import { PartialEnvironment } from '../../baseEnvironment' +// @Note copied from packages/vite/src/shared/utils.ts +const windowsSlashRE = /\\/g +function slash(p: string): string { + return p.replace(windowsSlashRE, '/') +} + async function createWorkerImportMetaUrlPluginTransform() { - const root = path.join(import.meta.dirname, 'fixtures/worker') + const root = slash(path.join(import.meta.dirname, 'fixtures/worker')) const config = await resolveConfig({ configFile: false, root }, 'serve') const instance = workerImportMetaUrlPlugin(config) const environment = new PartialEnvironment('client', config) From 29bcef3a98618b0db8f7cdd08ae7bab847a4dde8 Mon Sep 17 00:00:00 2001 From: Jamie Sinclair Date: Tue, 31 Dec 2024 13:48:17 +0900 Subject: [PATCH 08/10] refactor: add types and handle literal keys --- .../src/node/plugins/workerImportMetaUrl.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 4b5af884b27f61..9c85e458ef961c 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -1,7 +1,9 @@ import path from 'node:path' import MagicString from 'magic-string' -import type { RollupError } from 'rollup' +import type { RollupAstNode, RollupError } from 'rollup' +import { parseAstAsync } from 'rollup/parseAst' import { stripLiteral } from 'strip-literal' +import type { Expression, ExpressionStatement } from 'estree' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' import { evalValue, injectQuery, transformStableResult } from '../utils' @@ -38,10 +40,10 @@ function findClosingParen(input: string, fromIndex: number) { } function extractWorkerTypeFromAst( - astNode: any, + expression: Expression, optsStartIndex: number, ): 'classic' | 'module' | undefined { - if (astNode.type !== 'ObjectExpression') { + if (expression.type !== 'ObjectExpression') { return } @@ -49,15 +51,19 @@ function extractWorkerTypeFromAst( let typeProperty = null let typePropertyIndex = -1 - for (let i = 0; i < astNode.properties.length; i++) { - const property = astNode.properties[i] + for (let i = 0; i < expression.properties.length; i++) { + const property = expression.properties[i] if (property.type === 'SpreadElement') { lastSpreadElementIndex = i continue } - if (property.type === 'Property' && property.key?.name === 'type') { + if ( + property.type === 'Property' && + ((property.key.type === 'Identifier' && property.key.name === 'type') || + (property.key.type === 'Literal' && property.key.value === 'type')) + ) { typeProperty = property typePropertyIndex = i } @@ -76,7 +82,7 @@ function extractWorkerTypeFromAst( ) } - if (typeProperty?.value?.type !== 'Literal') { + if (typeProperty?.value.type !== 'Literal') { throw err( 'Expected worker options type property to be a literal value.', optsStartIndex, @@ -84,7 +90,7 @@ function extractWorkerTypeFromAst( } // Silently default to classic type like the getWorkerType method - return typeProperty.value.value === 'module' ? 'module' : 'classic' + return typeProperty?.value.value === 'module' ? 'module' : 'classic' } async function parseWorkerOptions( @@ -95,9 +101,10 @@ async function parseWorkerOptions( try { opts = evalValue(rawOpts) } catch { - const { parseAstAsync } = await import('rollup/parseAst') - const optsNode = ((await parseAstAsync(`(${rawOpts})`)).body[0] as any) - .expression + const optsNode = ( + (await parseAstAsync(`(${rawOpts})`)) + .body[0] as RollupAstNode + ).expression const type = extractWorkerTypeFromAst(optsNode, optsStartIndex) if (type) { From 0d9424e36a84c4f628a219923fee1eb1402c2ea6 Mon Sep 17 00:00:00 2001 From: Jamie Sinclair Date: Tue, 31 Dec 2024 13:48:41 +0900 Subject: [PATCH 09/10] chore: remove setting of root in config for test --- .../__tests__/plugins/workerImportMetaUrl.spec.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index ca033ac73bc9a7..b18c088bcef212 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -1,19 +1,11 @@ -import path from 'node:path' import { describe, expect, test } from 'vitest' import { parseAst } from 'rollup/parseAst' import { workerImportMetaUrlPlugin } from '../../plugins/workerImportMetaUrl' import { resolveConfig } from '../../config' import { PartialEnvironment } from '../../baseEnvironment' -// @Note copied from packages/vite/src/shared/utils.ts -const windowsSlashRE = /\\/g -function slash(p: string): string { - return p.replace(windowsSlashRE, '/') -} - async function createWorkerImportMetaUrlPluginTransform() { - const root = slash(path.join(import.meta.dirname, 'fixtures/worker')) - const config = await resolveConfig({ configFile: false, root }, 'serve') + const config = await resolveConfig({ configFile: false }, 'serve') const instance = workerImportMetaUrlPlugin(config) const environment = new PartialEnvironment('client', config) @@ -22,7 +14,7 @@ async function createWorkerImportMetaUrlPluginTransform() { const result = await instance.transform.call( { environment, parse: parseAst }, code, - path.join(root, 'foo.ts'), + 'foo.ts', ) return result?.code || result } From af78197bbfd79642279cfa593970a554df742e86 Mon Sep 17 00:00:00 2001 From: Jamie Sinclair Date: Tue, 31 Dec 2024 13:49:19 +0900 Subject: [PATCH 10/10] test: add test case for literal properties --- .../__tests__/plugins/workerImportMetaUrl.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts index b18c088bcef212..201946b99207c0 100644 --- a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts @@ -41,7 +41,7 @@ describe('workerImportMetaUrlPlugin', async () => { ) }) - test('with static worker options', async () => { + test('with static worker options and identifier properties', async () => { expect( await transform( 'new Worker(new URL("./worker.js", import.meta.url), { type: "module", name: "worker1" })', @@ -51,6 +51,16 @@ describe('workerImportMetaUrlPlugin', async () => { ) }) + test('with static worker options and literal properties', async () => { + expect( + await transform( + 'new Worker(new URL("./worker.js", import.meta.url), { "type": "module", "name": "worker1" })', + ), + ).toMatchInlineSnapshot( + `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { "type": "module", "name": "worker1" })"`, + ) + }) + test('with dynamic name field in worker options', async () => { expect( await transform(