Skip to content

Commit 3615c68

Browse files
authored
fix: esbuild glob resolve error (#14533)
1 parent 5004d00 commit 3615c68

File tree

1 file changed

+116
-104
lines changed
  • packages/vite/src/node/optimizer

1 file changed

+116
-104
lines changed

packages/vite/src/node/optimizer/scan.ts

Lines changed: 116 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
BuildContext,
88
BuildOptions,
99
Loader,
10+
OnLoadArgs,
1011
OnLoadResult,
1112
Plugin,
1213
} from 'esbuild'
@@ -382,116 +383,127 @@ function esbuildScanPlugin(
382383
}
383384
})
384385

385-
// extract scripts inside HTML-like files and treat it as a js module
386-
build.onLoad(
387-
{ filter: htmlTypesRE, namespace: 'html' },
388-
async ({ path }) => {
389-
let raw = await fsp.readFile(path, 'utf-8')
390-
// Avoid matching the content of the comment
391-
raw = raw.replace(commentRE, '<!---->')
392-
const isHtml = path.endsWith('.html')
393-
scriptRE.lastIndex = 0
394-
let js = ''
395-
let scriptId = 0
396-
let match: RegExpExecArray | null
397-
while ((match = scriptRE.exec(raw))) {
398-
const [, openTag, content] = match
399-
const typeMatch = openTag.match(typeRE)
400-
const type =
401-
typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
402-
const langMatch = openTag.match(langRE)
403-
const lang =
404-
langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
405-
// skip non type module script
406-
if (isHtml && type !== 'module') {
407-
continue
408-
}
409-
// skip type="application/ld+json" and other non-JS types
410-
if (
411-
type &&
412-
!(
413-
type.includes('javascript') ||
414-
type.includes('ecmascript') ||
415-
type === 'module'
416-
)
417-
) {
418-
continue
419-
}
420-
let loader: Loader = 'js'
421-
if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
422-
loader = lang
423-
} else if (path.endsWith('.astro')) {
424-
loader = 'ts'
425-
}
426-
const srcMatch = openTag.match(srcRE)
427-
if (srcMatch) {
428-
const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
429-
js += `import ${JSON.stringify(src)}\n`
430-
} else if (content.trim()) {
431-
// The reason why virtual modules are needed:
432-
// 1. There can be module scripts (`<script context="module">` in Svelte and `<script>` in Vue)
433-
// or local scripts (`<script>` in Svelte and `<script setup>` in Vue)
434-
// 2. There can be multiple module scripts in html
435-
// We need to handle these separately in case variable names are reused between them
436-
437-
// append imports in TS to prevent esbuild from removing them
438-
// since they may be used in the template
439-
const contents =
440-
content +
441-
(loader.startsWith('ts') ? extractImportPaths(content) : '')
442-
443-
const key = `${path}?id=${scriptId++}`
444-
if (contents.includes('import.meta.glob')) {
445-
scripts[key] = {
446-
loader: 'js', // since it is transpiled
447-
contents: await doTransformGlobImport(contents, path, loader),
448-
pluginData: {
449-
htmlType: { loader },
450-
},
451-
}
452-
} else {
453-
scripts[key] = {
454-
loader,
455-
contents,
456-
pluginData: {
457-
htmlType: { loader },
458-
},
459-
}
386+
const htmlTypeOnLoadCallback: (
387+
args: OnLoadArgs,
388+
) => Promise<OnLoadResult | null | undefined> = async ({ path: p }) => {
389+
let raw = await fsp.readFile(p, 'utf-8')
390+
// Avoid matching the content of the comment
391+
raw = raw.replace(commentRE, '<!---->')
392+
const isHtml = p.endsWith('.html')
393+
scriptRE.lastIndex = 0
394+
let js = ''
395+
let scriptId = 0
396+
let match: RegExpExecArray | null
397+
while ((match = scriptRE.exec(raw))) {
398+
const [, openTag, content] = match
399+
const typeMatch = openTag.match(typeRE)
400+
const type =
401+
typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
402+
const langMatch = openTag.match(langRE)
403+
const lang =
404+
langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
405+
// skip non type module script
406+
if (isHtml && type !== 'module') {
407+
continue
408+
}
409+
// skip type="application/ld+json" and other non-JS types
410+
if (
411+
type &&
412+
!(
413+
type.includes('javascript') ||
414+
type.includes('ecmascript') ||
415+
type === 'module'
416+
)
417+
) {
418+
continue
419+
}
420+
let loader: Loader = 'js'
421+
if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
422+
loader = lang
423+
} else if (p.endsWith('.astro')) {
424+
loader = 'ts'
425+
}
426+
const srcMatch = openTag.match(srcRE)
427+
if (srcMatch) {
428+
const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
429+
js += `import ${JSON.stringify(src)}\n`
430+
} else if (content.trim()) {
431+
// The reason why virtual modules are needed:
432+
// 1. There can be module scripts (`<script context="module">` in Svelte and `<script>` in Vue)
433+
// or local scripts (`<script>` in Svelte and `<script setup>` in Vue)
434+
// 2. There can be multiple module scripts in html
435+
// We need to handle these separately in case variable names are reused between them
436+
437+
// append imports in TS to prevent esbuild from removing them
438+
// since they may be used in the template
439+
const contents =
440+
content +
441+
(loader.startsWith('ts') ? extractImportPaths(content) : '')
442+
443+
const key = `${p}?id=${scriptId++}`
444+
if (contents.includes('import.meta.glob')) {
445+
scripts[key] = {
446+
loader: 'js', // since it is transpiled
447+
contents: await doTransformGlobImport(contents, p, loader),
448+
resolveDir: normalizePath(path.dirname(p)),
449+
pluginData: {
450+
htmlType: { loader },
451+
},
460452
}
461-
462-
const virtualModulePath = JSON.stringify(
463-
virtualModulePrefix + key,
464-
)
465-
466-
const contextMatch = openTag.match(contextRE)
467-
const context =
468-
contextMatch &&
469-
(contextMatch[1] || contextMatch[2] || contextMatch[3])
470-
471-
// Especially for Svelte files, exports in <script context="module"> means module exports,
472-
// exports in <script> means component props. To avoid having two same export name from the
473-
// star exports, we need to ignore exports in <script>
474-
if (path.endsWith('.svelte') && context !== 'module') {
475-
js += `import ${virtualModulePath}\n`
476-
} else {
477-
js += `export * from ${virtualModulePath}\n`
453+
} else {
454+
scripts[key] = {
455+
loader,
456+
contents,
457+
resolveDir: normalizePath(path.dirname(p)),
458+
pluginData: {
459+
htmlType: { loader },
460+
},
478461
}
479462
}
480-
}
481463

482-
// This will trigger incorrectly if `export default` is contained
483-
// anywhere in a string. Svelte and Astro files can't have
484-
// `export default` as code so we know if it's encountered it's a
485-
// false positive (e.g. contained in a string)
486-
if (!path.endsWith('.vue') || !js.includes('export default')) {
487-
js += '\nexport default {}'
488-
}
464+
const virtualModulePath = JSON.stringify(virtualModulePrefix + key)
489465

490-
return {
491-
loader: 'js',
492-
contents: js,
466+
const contextMatch = openTag.match(contextRE)
467+
const context =
468+
contextMatch &&
469+
(contextMatch[1] || contextMatch[2] || contextMatch[3])
470+
471+
// Especially for Svelte files, exports in <script context="module"> means module exports,
472+
// exports in <script> means component props. To avoid having two same export name from the
473+
// star exports, we need to ignore exports in <script>
474+
if (p.endsWith('.svelte') && context !== 'module') {
475+
js += `import ${virtualModulePath}\n`
476+
} else {
477+
js += `export * from ${virtualModulePath}\n`
478+
}
493479
}
494-
},
480+
}
481+
482+
// This will trigger incorrectly if `export default` is contained
483+
// anywhere in a string. Svelte and Astro files can't have
484+
// `export default` as code so we know if it's encountered it's a
485+
// false positive (e.g. contained in a string)
486+
if (!p.endsWith('.vue') || !js.includes('export default')) {
487+
js += '\nexport default {}'
488+
}
489+
490+
return {
491+
loader: 'js',
492+
contents: js,
493+
}
494+
}
495+
496+
// extract scripts inside HTML-like files and treat it as a js module
497+
build.onLoad(
498+
{ filter: htmlTypesRE, namespace: 'html' },
499+
htmlTypeOnLoadCallback,
500+
)
501+
// the onResolve above will use namespace=html but esbuild doesn't
502+
// call onResolve for glob imports and those will use namespace=file
503+
// https://github.com/evanw/esbuild/issues/3317
504+
build.onLoad(
505+
{ filter: htmlTypesRE, namespace: 'file' },
506+
htmlTypeOnLoadCallback,
495507
)
496508

497509
// bare imports: record and externalize ----------------------------------

0 commit comments

Comments
 (0)