fix: execute classic worker in dev mode (#7099)

This commit is contained in:
yoho 2022-03-03 21:46:25 +08:00 committed by GitHub
parent 24bb3e40c1
commit 3c0a6091fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 164 additions and 50 deletions

View File

@ -10,3 +10,4 @@ pnpm-lock.yaml
pnpm-workspace.yaml
packages/playground/tsconfig-json-load-error/has-error/tsconfig.json
packages/playground/html/invalid.html
packages/playground/worker/classic-worker.js

View File

@ -56,7 +56,7 @@ if (isBuild) {
// assert correct files
test('inlined code generation', async () => {
const files = fs.readdirSync(assetsDir)
expect(files.length).toBe(6)
expect(files.length).toBe(8)
const index = files.find((f) => f.includes('index'))
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
const worker = files.find((f) => f.includes('my-worker'))
@ -88,3 +88,8 @@ if (isBuild) {
})
})
}
test('classic worker is run', async () => {
expect(await page.textContent('.classic-worker')).toMatch('A classic')
expect(await page.textContent('.classic-shared-worker')).toMatch('A classic')
})

View File

@ -0,0 +1,29 @@
// prettier-ignore
function text(el, text) {
document.querySelector(el).textContent = text
}
const classicWorker = new Worker(
new URL('./newUrl/classic-worker.js', import.meta.url) /* , */ ,
// test comment
)
classicWorker.addEventListener('message', ({ data }) => {
text('.classic-worker', data)
})
classicWorker.postMessage('ping')
const classicSharedWorker = new SharedWorker(
new URL('./newUrl/classic-shared-worker.js', import.meta.url),
{
type: 'classic'
}
)
classicSharedWorker.port.addEventListener('message', (ev) => {
text(
'.classic-shared-worker',
ev.data
)
})
classicSharedWorker.port.start()

View File

@ -20,18 +20,25 @@
<span class="tick-count">0</span>
</div>
<p>new Worker(new Url('path', import.meta.url))</p>
<p>new Worker(new Url('path', import.meta.url), { type: 'module' })</p>
<div class="worker-import-meta-url"></div>
<p>new SharedWorker(new Url('path', import.meta.url))</p>
<p>new SharedWorker(new Url('path', import.meta.url), { type: 'module' })</p>
<div class="shared-worker-import-meta-url"></div>
<p>new Worker(new Url('path', import.meta.url))</p>
<div class="classic-worker"></div>
<p>new Worker(new Url('path', import.meta.url), { type: 'classic' })</p>
<div class="classic-shared-worker"></div>
<script type="module">
import myWorker from './my-worker?worker'
import InlineWorker from './my-worker?worker&inline'
import mySharedWorker from './my-shared-worker?sharedworker&name=shared'
import TSOutputWorker from './possible-ts-output-worker?worker'
import { mode } from './workerImport'
import './classic-worker'
document.querySelector('.mode-true').textContent = mode
@ -78,11 +85,12 @@
function text(el, text) {
document.querySelector(el).textContent = text
}
const workerOptions = { type: 'module' }
// url import worker
const w = new Worker(new URL('./newUrl/url-worker.js', import.meta.url), {
type: 'module'
})
const w = new Worker(
new URL('./newUrl/url-worker.js', import.meta.url),
/* @vite-ignore */ workerOptions
)
w.addEventListener('message', (ev) =>
text(
'.worker-import-meta-url',
@ -90,9 +98,12 @@
)
)
const genWorkerName = () => 'module'
const w2 = new SharedWorker(
new URL('./newUrl/url-shared-worker.js', import.meta.url),
{
/* @vite-ignore */
name: genWorkerName(),
type: 'module'
}
)

View File

@ -0,0 +1,6 @@
importScripts('/classic.js')
self.onconnect = (event) => {
const port = event.ports[0]
port.postMessage(self.constant)
}

View File

@ -0,0 +1,5 @@
importScripts('/classic.js')
self.addEventListener('message', () => {
self.postMessage(self.constant)
})

View File

@ -0,0 +1 @@
self.constant = 'A classic'

View File

@ -8,7 +8,12 @@ import {
preloadMarker
} from './plugins/importAnalysisBuild'
import { isCSSRequest } from './plugins/css'
import { cleanUrl } from './utils'
import {
cleanUrl,
singlelineCommentsRE,
multilineCommentsRE,
blankReplacer
} from './utils'
import type { RollupError } from 'rollup'
export interface AssertOptions {
@ -182,45 +187,20 @@ function lexGlobPattern(
throw new Error('unknown import.meta.glob lexer state')
}
}
const noCommentCode = code
.slice(i + 1)
.replace(singlelineCommentsRE, blankReplacer)
.replace(multilineCommentsRE, blankReplacer)
const endIndex = noCommentCode.indexOf(')')
const options = noCommentCode.substring(0, endIndex)
const commaIndex = options.indexOf(',')
const endIndex = getEndIndex(code, i)
const options = code.substring(i + 1, endIndex)
const commaIndex = options.indexOf(`,`)
let assert = {}
if (commaIndex > -1) {
assert = JSON5.parse(options.substr(commaIndex + 1))
}
return [pattern, assert, endIndex + 1]
}
// reg without the 'g' option, only matches the first match
const multilineCommentsRE = /\/\*(.|[\r\n])*?\*\//m
const singlelineCommentsRE = /\/\/.*/
function getEndIndex(code: string, i: number): number {
const findStart = i
const endIndex = code.indexOf(`)`, findStart)
const subCode = code.substring(findStart)
const matched =
subCode.match(singlelineCommentsRE) ?? subCode.match(multilineCommentsRE)
if (!matched) {
return endIndex
}
const str = matched[0]
const index = matched.index
if (!index) {
return endIndex
}
const commentStart = findStart + index
const commentEnd = commentStart + str.length
if (endIndex > commentStart && endIndex < commentEnd) {
return getEndIndex(code, commentEnd)
} else {
return endIndex
assert = JSON5.parse(options.substring(commaIndex + 1))
}
return [pattern, assert, endIndex + i + 2]
}
function error(pos: number) {

View File

@ -1,7 +1,9 @@
import JSON5 from 'json5'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { getAssetHash, fileToUrl } from './asset'
import {
blankReplacer,
cleanUrl,
injectQuery,
multilineCommentsRE,
@ -10,22 +12,90 @@ import {
import path from 'path'
import { bundleWorkerEntry } from './worker'
import { parseRequest } from '../utils'
import { ENV_PUBLIC_PATH } from '../constants'
import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants'
import MagicString from 'magic-string'
import type { ViteDevServer } from '..'
type WorkerType = 'classic' | 'module' | 'ignore'
const WORKER_FILE_ID = 'worker_url_file'
function getWorkerType(
code: string,
noCommentsCode: string,
i: number
): WorkerType {
const commaIndex = noCommentsCode.indexOf(',', i)
if (commaIndex === -1) {
return 'classic'
}
const endIndex = noCommentsCode.indexOf(')', i)
// need to find in comment code
let workerOptsString = code.substring(commaIndex + 1, endIndex)
const hasViteIgnore = /\/\*\s*@vite-ignore\s*\*\//.test(workerOptsString)
if (hasViteIgnore) {
return 'ignore'
}
// need to find in no comment code
workerOptsString = noCommentsCode.substring(commaIndex + 1, endIndex)
if (!workerOptsString.trim().length) {
return 'classic'
}
let workerOpts: { type: WorkerType } = { type: 'classic' }
try {
workerOpts = JSON5.parse(workerOptsString)
} catch (e) {
// can't parse by JSON5, so the worker options had unexpect char.
throw new Error(
'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.'
)
}
if (['classic', 'module'].includes(workerOpts.type)) {
return workerOpts.type
}
return 'classic'
}
export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
const isBuild = config.command === 'build'
let server: ViteDevServer
return {
name: 'vite:worker-import-meta-url',
configureServer(_server) {
server = _server
},
async transform(code, id, options) {
const query = parseRequest(id)
if (query && query[WORKER_FILE_ID] != null) {
if (query && query[WORKER_FILE_ID] != null && query['type'] != null) {
const workerType = query['type'] as WorkerType
let injectEnv = ''
if (workerType === 'classic') {
injectEnv = `importScripts('${ENV_PUBLIC_PATH}')\n`
} else if (workerType === 'module') {
injectEnv = `import '${ENV_PUBLIC_PATH}'\n`
} else if (workerType === 'ignore') {
if (isBuild) {
injectEnv = ''
} else if (server) {
// dynamic worker type we can't know how import the env
// so we copy /@vite/env code of server transform result into file header
const { moduleGraph } = server
const module = moduleGraph.getModuleById(ENV_ENTRY)
injectEnv = module?.transformResult?.code || ''
}
}
return {
code: `import '${ENV_PUBLIC_PATH}'\n` + code
code: injectEnv + code
}
}
if (
@ -36,13 +106,12 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
const importMetaUrlRE =
/\bnew\s+(Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/g
const noCommentsCode = code
.replace(multilineCommentsRE, (m) => ' '.repeat(m.length))
.replace(singlelineCommentsRE, (m) => ' '.repeat(m.length))
.replace(multilineCommentsRE, blankReplacer)
.replace(singlelineCommentsRE, blankReplacer)
let match: RegExpExecArray | null
let s: MagicString | null = null
while ((match = importMetaUrlRE.exec(noCommentsCode))) {
const { 0: allExp, 2: exp, 3: rawUrl, index } = match
const urlIndex = allExp.indexOf(exp) + index
if (options?.ssr) {
@ -61,7 +130,11 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
}
s ||= new MagicString(code)
const workerType = getWorkerType(
code,
noCommentsCode,
index + allExp.length
)
const file = path.resolve(path.dirname(id), rawUrl.slice(1, -1))
let url: string
if (isBuild) {
@ -80,6 +153,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
} else {
url = await fileToUrl(cleanUrl(file), config, this)
url = injectQuery(url, WORKER_FILE_ID)
url = injectQuery(url, `type=${workerType}`)
}
s.overwrite(urlIndex, urlIndex + exp.length, JSON.stringify(url))
}

View File

@ -696,3 +696,5 @@ export function parseRequest(id: string): Record<string, string> | null {
}
return Object.fromEntries(new URLSearchParams(search.slice(1)))
}
export const blankReplacer = (match: string) => ' '.repeat(match.length)