Skip to content

Commit

Permalink
fix(html): move importmap before module scripts (#9392)
Browse files Browse the repository at this point in the history
Co-authored-by: 翠 / green <green@sapphi.red>
  • Loading branch information
bluwy and sapphi-red committed Aug 21, 2022
1 parent 31c2926 commit b386fba
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 8 deletions.
52 changes: 52 additions & 0 deletions packages/vite/src/node/plugins/html.ts
Expand Up @@ -7,6 +7,7 @@ import type {
SourceMapInput
} from 'rollup'
import MagicString from 'magic-string'
import colors from 'picocolors'
import type {
AttributeNode,
CompilerError,
Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -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('#') ||
Expand Down Expand Up @@ -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[]] {
Expand Down
24 changes: 18 additions & 6 deletions packages/vite/src/node/server/middlewares/indexHtml.ts
Expand Up @@ -11,6 +11,8 @@ import {
applyHtmlTransforms,
assetAttrsConfig,
getScriptInfo,
postImportMapHook,
preImportMapHook,
resolveHtmlTransforms,
traverseHtml
} from '../../plugins/html'
Expand Down Expand Up @@ -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
}
)
}
}

Expand Down
8 changes: 7 additions & 1 deletion playground/external/__tests__/external.spec.ts
@@ -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 () => {
Expand Down
16 changes: 15 additions & 1 deletion playground/html/__tests__/html.spec.ts
@@ -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 () => {
Expand Down Expand Up @@ -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.'
)
})
19 changes: 19 additions & 0 deletions playground/html/vite.config.js
Expand Up @@ -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'
}
]
}
}
]
}

0 comments on commit b386fba

Please sign in to comment.