fix(html): move importmap before module scripts (#9392)

Co-authored-by: 翠 / green <green@sapphi.red>
This commit is contained in:
Bjorn Lu 2022-08-22 01:26:13 +08:00 committed by GitHub
parent 31c2926c68
commit b386fba49e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 8 deletions

View File

@ -7,6 +7,7 @@ import type {
SourceMapInput
} from 'rollup'
import MagicString from 'magic-string'
import colors from 'picocolors'
import type {
AttributeNode,
CompilerError,
@ -54,6 +55,10 @@ const inlineImportRE =
/(?<!(?<!\.\.)\.)\bimport\s*\(("([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*')\)/g
const htmlLangRE = /\.(html|htm)$/
const importMapRE =
/[ \t]*<script[^>]*type\s*=\s*["']?importmap["']?[^>]*>.*?<\/script>/is
const moduleScriptRE = /[ \t]*<script[^>]*type\s*=\s*["']?module["']?[^>]*>/is
export const isHTMLProxy = (id: string): boolean => htmlProxyRE.test(id)
export const isHTMLRequest = (request: string): boolean =>
@ -225,6 +230,8 @@ function handleParseError(
*/
export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
const [preHooks, postHooks] = resolveHtmlTransforms(config.plugins)
preHooks.unshift(preImportMapHook(config))
postHooks.push(postImportMapHook())
const processedHtml = new Map<string, string>()
const isExcludedUrl = (url: string) =>
url.startsWith('#') ||
@ -796,6 +803,51 @@ export type IndexHtmlTransform =
transform: IndexHtmlTransformHook
}
export function preImportMapHook(
config: ResolvedConfig
): IndexHtmlTransformHook {
return (html, ctx) => {
const importMapIndex = html.match(importMapRE)?.index
if (importMapIndex === undefined) return
const moduleScriptIndex = html.match(moduleScriptRE)?.index
if (moduleScriptIndex === undefined) return
if (moduleScriptIndex < importMapIndex) {
const relativeHtml = normalizePath(
path.relative(config.root, ctx.filename)
)
config.logger.warnOnce(
colors.yellow(
colors.bold(
`(!) <script type="importmap"> should come before <script type="module"> in /${relativeHtml}`
)
)
)
}
}
}
/**
* Move importmap before the first module script
*/
export function postImportMapHook(): IndexHtmlTransformHook {
return (html) => {
if (!moduleScriptRE.test(html)) return
let importMap: string | undefined
html = html.replace(importMapRE, (match) => {
importMap = match
return ''
})
if (importMap) {
html = html.replace(moduleScriptRE, (match) => `${importMap}\n${match}`)
}
return html
}
}
export function resolveHtmlTransforms(
plugins: readonly Plugin[]
): [IndexHtmlTransformHook[], IndexHtmlTransformHook[]] {

View File

@ -11,6 +11,8 @@ import {
applyHtmlTransforms,
assetAttrsConfig,
getScriptInfo,
postImportMapHook,
preImportMapHook,
resolveHtmlTransforms,
traverseHtml
} from '../../plugins/html'
@ -43,12 +45,22 @@ export function createDevHtmlTransformFn(
): (url: string, html: string, originalUrl: string) => Promise<string> {
const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
return (url: string, html: string, originalUrl: string): Promise<string> => {
return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], {
path: url,
filename: getHtmlFilename(url, server),
server,
originalUrl
})
return applyHtmlTransforms(
html,
[
preImportMapHook(server.config),
...preHooks,
devHtmlHook,
...postHooks,
postImportMapHook()
],
{
path: url,
filename: getHtmlFilename(url, server),
server,
originalUrl
}
)
}
}

View File

@ -1,5 +1,11 @@
import { describe, expect, test } from 'vitest'
import { isBuild, page } from '~utils'
import { browserLogs, isBuild, page } from '~utils'
test('importmap', () => {
expect(browserLogs).not.toContain(
'An import map is added after module script load was triggered.'
)
})
describe.runIf(isBuild)('build', () => {
test('should externalize imported packages', async () => {

View File

@ -1,5 +1,13 @@
import { beforeAll, describe, expect, test } from 'vitest'
import { editFile, getColor, isBuild, isServe, page, viteTestUrl } from '~utils'
import {
browserLogs,
editFile,
getColor,
isBuild,
isServe,
page,
viteTestUrl
} from '~utils'
function testPage(isNested: boolean) {
test('pre transform', async () => {
@ -242,3 +250,9 @@ describe.runIf(isServe)('invalid', () => {
expect(content).toBeTruthy()
})
})
test('importmap', () => {
expect(browserLogs).not.toContain(
'An import map is added after module script load was triggered.'
)
})

View File

@ -160,6 +160,25 @@ ${
}
]
}
},
{
name: 'head-prepend-importmap',
transformIndexHtml() {
return [
{
tag: 'script',
attrs: { type: 'importmap' },
children: `
{
"imports": {
"vue": "https://unpkg.com/vue@3.2.0/dist/vue.runtime.esm-browser.js"
}
}
`,
injectTo: 'head'
}
]
}
}
]
}