From f4c12642ff4a7dbb1f2fa431b998d09cf2d6b33c Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 30 Nov 2022 14:41:12 +0800 Subject: [PATCH] refactor: use function to eval worker and glob options (#10999) --- packages/vite/LICENSE.md | 31 ---- packages/vite/package.json | 1 - .../plugins/importGlob/parse.test.ts | 22 ++- .../vite/src/node/plugins/importMetaGlob.ts | 158 ++++++++++-------- .../src/node/plugins/workerImportMetaUrl.ts | 61 ++++--- packages/vite/src/node/utils.ts | 8 + pnpm-lock.yaml | 2 - 7 files changed, 145 insertions(+), 138 deletions(-) diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index fc49a93047979f..05c752516169fe 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -1832,37 +1832,6 @@ Repository: git+https://github.com/isaacs/isexe.git --------------------------------------- -## json5 -License: MIT -By: Aseem Kishore, Max Nanasy, Andrew Eisenberg, Jordan Tucker -Repository: git+https://github.com/json5/json5.git - -> MIT License -> -> Copyright (c) 2012-2018 Aseem Kishore, and [others]. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. -> -> [others]: https://github.com/json5/json5/contributors - ---------------------------------------- - ## launch-editor License: MIT By: Evan You diff --git a/packages/vite/package.json b/packages/vite/package.json index 735150d6e03134..2899ada1a5511a 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -96,7 +96,6 @@ "etag": "^1.8.1", "fast-glob": "^3.2.12", "http-proxy": "^1.18.1", - "json5": "^2.2.1", "launch-editor-middleware": "^2.6.0", "magic-string": "^0.26.7", "micromatch": "^4.0.5", diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts index 7fb6286339d262..c6a2a3efa7fa2e 100644 --- a/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts +++ b/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts @@ -327,28 +327,26 @@ describe('parse negatives', async () => { expect( await runError('import.meta.glob("hey", hey)') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "Identifier"]' + '[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "Identifier"]' ) expect(await runError('import.meta.glob("hey", [])')).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "ArrayExpression"]' + '[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "ArrayExpression"]' ) }) it('options props', async () => { expect( await runError('import.meta.glob("hey", { hey: 1 })') - ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Unknown options hey]' - ) + ).toMatchInlineSnapshot('[Error: Unknown glob option "hey"]') expect( await runError('import.meta.glob("hey", { import: hey })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Could only use literals]' + '[Error: Vite is unable to parse the glob options as the value is not static]' ) expect( await runError('import.meta.glob("hey", { eager: 123 })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Expected the type of option "eager" to be "boolean", but got "number"]' + '[Error: Expected glob option "eager" to be of type boolean, but got number]' ) }) @@ -356,29 +354,29 @@ describe('parse negatives', async () => { expect( await runError('import.meta.glob("./*.js", { as: "raw", query: "hi" })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Options "as" and "query" cannot be used together]' + '[Error: Options "as" and "query" cannot be used together]' ) expect( await runError('import.meta.glob("./*.js", { query: 123 })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Expected query to be a string, but got "number"]' + '[Error: Expected glob option "query" to be of type object or string, but got number]' ) expect( await runError('import.meta.glob("./*.js", { query: { foo: {} } })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Could only use literals]' + '[Error: Expected glob option "query.foo" to be of type string, number, or boolean, but got object]' ) expect( await runError('import.meta.glob("./*.js", { query: { foo: hey } })') ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Could only use literals]' + '[Error: Vite is unable to parse the glob options as the value is not static]' ) expect( await runError( 'import.meta.glob("./*.js", { query: { foo: 123, ...a } })' ) ).toMatchInlineSnapshot( - '[Error: Invalid glob import syntax: Could only use literals]' + '[Error: Vite is unable to parse the glob options as the value is not static]' ) }) }) diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index 8911d36b5d3bc5..3f986f87ca49d7 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -14,6 +14,7 @@ import type { TemplateLiteral } from 'estree' import { parseExpressionAt } from 'acorn' +import type { RollupError } from 'rollup' import { findNodeAt } from 'acorn-walk' import MagicString from 'magic-string' import fg from 'fast-glob' @@ -24,6 +25,7 @@ import type { ViteDevServer } from '../server' import type { ModuleNode } from '../server/moduleGraph' import type { ResolvedConfig } from '../config' import { + evalValue, generateCodeFrame, normalizePath, slash, @@ -95,14 +97,87 @@ const importGlobRE = /\bimport\.meta\.(glob|globEager|globEagerDefault)(?:<\w+>)?\s*\(/g const knownOptions = { - as: 'string', - eager: 'boolean', - import: 'string', - exhaustive: 'boolean' -} as const + as: ['string'], + eager: ['boolean'], + import: ['string'], + exhaustive: ['boolean'], + query: ['object', 'string'] +} const forceDefaultAs = ['raw', 'url'] +function err(e: string, pos: number) { + const error = new Error(e) as RollupError + error.pos = pos + return error +} + +function parseGlobOptions( + rawOpts: string, + optsStartIndex: number +): GeneralImportGlobOptions { + let opts: GeneralImportGlobOptions = {} + try { + opts = evalValue(rawOpts) + } catch { + throw err( + 'Vite is unable to parse the glob options as the value is not static', + optsStartIndex + ) + } + + if (opts == null) { + return {} + } + + for (const key in opts) { + if (!(key in knownOptions)) { + throw err(`Unknown glob option "${key}"`, optsStartIndex) + } + const allowedTypes = knownOptions[key as keyof typeof knownOptions] + const valueType = typeof opts[key as keyof GeneralImportGlobOptions] + if (!allowedTypes.includes(valueType)) { + throw err( + `Expected glob option "${key}" to be of type ${allowedTypes.join( + ' or ' + )}, but got ${valueType}`, + optsStartIndex + ) + } + } + + if (typeof opts.query === 'object') { + for (const key in opts.query) { + const value = opts.query[key] + if (!['string', 'number', 'boolean'].includes(typeof value)) { + throw err( + `Expected glob option "query.${key}" to be of type string, number, or boolean, but got ${typeof value}`, + optsStartIndex + ) + } + } + } + + if (opts.as && forceDefaultAs.includes(opts.as)) { + if (opts.import && opts.import !== 'default' && opts.import !== '*') + throw err( + `Option "import" can only be "default" or "*" when "as" is "${opts.as}", but got "${opts.import}"`, + optsStartIndex + ) + opts.import = opts.import || 'default' + } + + if (opts.as && opts.query) + throw err( + 'Options "as" and "query" cannot be used together', + optsStartIndex + ) + + if (opts.as) opts.query = opts.as + + return opts +} + export async function parseImportGlob( code: string, importer: string | undefined, @@ -205,82 +280,19 @@ export async function parseImportGlob( } // arg2 - const options: GeneralImportGlobOptions = {} + let options: GeneralImportGlobOptions = {} if (arg2) { if (arg2.type !== 'ObjectExpression') throw err( - `Expected the second argument o to be a object literal, but got "${arg2.type}"` - ) - - for (const property of arg2.properties) { - if ( - property.type === 'SpreadElement' || - (property.key.type !== 'Identifier' && - property.key.type !== 'Literal') + `Expected the second argument to be an object literal, but got "${arg2.type}"` ) - throw err('Could only use literals') - - const name = ((property.key as any).name || - (property.key as any).value) as keyof GeneralImportGlobOptions - if (name === 'query') { - if (property.value.type === 'ObjectExpression') { - const data: Record = {} - for (const prop of property.value.properties) { - if ( - prop.type === 'SpreadElement' || - prop.key.type !== 'Identifier' || - prop.value.type !== 'Literal' - ) - throw err('Could only use literals') - data[prop.key.name] = prop.value.value as any - } - options.query = data - } else if (property.value.type === 'Literal') { - if (typeof property.value.value !== 'string') - throw err( - `Expected query to be a string, but got "${typeof property.value - .value}"` - ) - options.query = property.value.value - } else { - throw err('Could only use literals') - } - continue - } - - if (!(name in knownOptions)) throw err(`Unknown options ${name}`) - if (property.value.type !== 'Literal') - throw err('Could only use literals') - - const valueType = typeof property.value.value - if (valueType === 'undefined') continue - - if (valueType !== knownOptions[name]) - throw err( - `Expected the type of option "${name}" to be "${knownOptions[name]}", but got "${valueType}"` - ) - options[name] = property.value.value as any - } - } - - if (options.as && forceDefaultAs.includes(options.as)) { - if ( - options.import && - options.import !== 'default' && - options.import !== '*' + options = parseGlobOptions( + code.slice(arg2.range![0], arg2.range![1]), + arg2.range![0] ) - throw err( - `Option "import" can only be "default" or "*" when "as" is "${options.as}", but got "${options.import}"` - ) - options.import = options.import || 'default' } - if (options.as && options.query) - throw err('Options "as" and "query" cannot be used together') - - if (options.as) options.query = options.as - const end = ast.range![1] const globsResolved = await Promise.all( diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 61b9b4750ac06f..6a8574bd370703 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -1,5 +1,4 @@ import path from 'node:path' -import JSON5 from 'json5' import MagicString from 'magic-string' import type { RollupError } from 'rollup' import { stripLiteral } from 'strip-literal' @@ -7,6 +6,7 @@ import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' import { cleanUrl, + evalValue, injectQuery, parseRequest, slash, @@ -20,13 +20,46 @@ import { fileToUrl } from './asset' const ignoreFlagRE = /\/\*\s*@vite-ignore\s*\*\// -function getWorkerType(raw: string, clean: string, i: number): WorkerType { - function err(e: string, pos: number) { - const error = new Error(e) as RollupError - error.pos = pos - throw error +interface WorkerOptions { + type?: WorkerType +} + +function err(e: string, pos: number) { + const error = new Error(e) as RollupError + error.pos = pos + return error +} + +function parseWorkerOptions( + rawOpts: string, + optsStartIndex: number +): WorkerOptions { + let opts: WorkerOptions = {} + try { + opts = evalValue(rawOpts) + } catch { + throw err( + '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 + ) + } + + if (opts == null) { + return {} + } + + if (typeof opts !== 'object') { + throw err( + `Expected worker options to be an object, got ${typeof opts}`, + optsStartIndex + ) } + return opts +} + +function getWorkerType(raw: string, clean: string, i: number): WorkerType { const commaIndex = clean.indexOf(',', i) if (commaIndex === -1) { return 'classic' @@ -54,21 +87,11 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType { return 'classic' } - let workerOpts: { type: WorkerType } = { type: 'classic' } - try { - workerOpts = JSON5.parse(workerOptString) - } catch (e) { - // can't parse by JSON5, so the worker options had unexpect char. - err( - '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.', - commaIndex + 1 - ) - } - - if (['classic', 'module'].includes(workerOpts.type)) { + const workerOpts = parseWorkerOptions(workerOptString, commaIndex + 1) + if (workerOpts.type && ['classic', 'module'].includes(workerOpts.type)) { return workerOpts.type } + return 'classic' } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 8123ec08c68dea..f288cffd8effac 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1267,3 +1267,11 @@ export function arrayEqual(a: any[], b: any[]): boolean { } return true } + +export function evalValue(rawValue: string): T { + const fn = new Function(` + var console, exports, global, module, process, require + return (\n${rawValue}\n) + `) + return fn() +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a02a27483154ad..04ee15016b947b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,7 +244,6 @@ importers: fast-glob: ^3.2.12 fsevents: ~2.3.2 http-proxy: ^1.18.1 - json5: ^2.2.1 launch-editor-middleware: ^2.6.0 magic-string: ^0.26.7 micromatch: ^4.0.5 @@ -310,7 +309,6 @@ importers: etag: 1.8.1 fast-glob: 3.2.12 http-proxy: 1.18.1_debug@4.3.4 - json5: 2.2.1 launch-editor-middleware: 2.6.0 magic-string: 0.26.7 micromatch: 4.0.5