Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: style hmr reduce replace style code #7869

Merged
merged 22 commits into from Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/playground/assets/__tests__/assets.spec.ts
@@ -1,4 +1,3 @@
import { createHash } from 'crypto'
import {
findAssetFile,
getBg,
Expand Down Expand Up @@ -296,6 +295,11 @@ describe('css and assets in css in build watch', () => {
}
})

test('inline style test', async () => {
expect(await getBg('.inline-style')).toMatch(assetMatch)
expect(await getBg('.style-url-assets')).toMatch(assetMatch)
})

if (!isBuild) {
test('@import in html style tag hmr', async () => {
await untilUpdated(() => getColor('.import-css'), 'rgb(0, 136, 255)')
Expand All @@ -304,6 +308,7 @@ if (!isBuild) {
(code) => code.replace('#0088ff', '#00ff88'),
true
)
await page.waitForNavigation()
await untilUpdated(() => getColor('.import-css'), 'rgb(0, 255, 136)')
})
}
Expand Down
20 changes: 19 additions & 1 deletion packages/playground/assets/index.html
Expand Up @@ -207,7 +207,10 @@ <h3>url</h3>
background-size: 10px 10px;
}
</style>
<div style="background: url('./nested/asset.png'); background-size: 10px 10px">
<div
class="inline-style"
style="background: url('./nested/asset.png'); background-size: 10px 10px"
>
inline style
</div>
<div class="style-url-assets">use style class</div>
Expand Down Expand Up @@ -235,6 +238,21 @@ <h3 id="foo">import module css</h3>

<h3 class="raw-query"></h3>

<h3>style in svg</h3>
<svg viewBox="0 0 512 512" width="21" height="21" class="style-insvg">
<style>
.style-insvg-color {
fill: #0088ff;
}
</style>
<g class="style-insvg-color">
<rect x="224" y="352" width="64" height="64" />
<path
d="M128 128v96h64v-96h96v96h-32v32h-32v64h64v-64h64V128h-32V96H160v32h-32z"
/>
</g>
</svg>

<style>
@import '/foo.css';
</style>
Expand Down
62 changes: 0 additions & 62 deletions packages/playground/css-sourcemap/__tests__/serve.spec.ts
Expand Up @@ -17,68 +17,6 @@ if (!isBuild) {
throw new Error('Not found')
}

test('inline css', async () => {
const css = await getStyleTagContentIncluding('.inline ')
const map = extractSourcemap(css)
expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(`
Object {
"mappings": "AAGO;AACP,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC,CAAC;",
"sources": Array [
"/root/index.html",
],
"sourcesContent": Array [
"<link rel=\\"stylesheet\\" href=\\"./linked.css\\" />
<link rel=\\"stylesheet\\" href=\\"./linked-with-import.css\\" />

<style>
.inline {
color: red;
}
</style>

<div class=\\"wrapper\\">
<h1>CSS Sourcemap</h1>

<p class=\\"inline\\">&lt;inline&gt;</p>

<p class=\\"linked\\">&lt;linked&gt;: no import</p>
<p class=\\"linked-with-import\\">&lt;linked&gt;: with import</p>

<p class=\\"imported\\">&lt;imported&gt;: no import</p>
<p class=\\"imported-with-import\\">&lt;imported&gt;: with import</p>

<p class=\\"imported-sass\\">&lt;imported sass&gt;</p>
<p class=\\"imported-sass-module\\">&lt;imported sass&gt; with module</p>

<p class=\\"imported-less\\">&lt;imported less&gt; with string additionalData</p>

<p class=\\"imported-stylus\\">&lt;imported stylus&gt;</p>
</div>

<script type=\\"module\\">
import './imported.css'
import './imported-with-import.css'

import './imported.sass'
import sassModule from './imported.module.sass'

document
.querySelector('.imported-sass-module')
.classList.add(sassModule['imported-sass-module'])

import './imported.less'

import './imported.styl'
</script>

<iframe src=\\"virtual.html\\"></iframe>
",
],
"version": 3,
}
`)
})

test('linked css', async () => {
const res = await page.request.get(
new URL('./linked.css', page.url()).href,
Expand Down
12 changes: 11 additions & 1 deletion packages/playground/hmr/__tests__/hmr.spec.ts
@@ -1,4 +1,4 @@
import { isBuild, editFile, untilUpdated } from '../../testUtils'
import { isBuild, editFile, untilUpdated, getBg } from '../../testUtils'

test('should render', async () => {
expect(await page.textContent('.app')).toBe('1')
Expand Down Expand Up @@ -195,6 +195,16 @@ if (!isBuild) {
expect(await btn.textContent()).toBe('Counter 1')
})

test('css in html hmr', async () => {
await page.goto(viteTestUrl)
expect(await getBg('.import-image')).toMatch('icon')
await page.goto(viteTestUrl + '/foo/')
expect(await getBg('.import-image')).toMatch('icon')
editFile('index.html', (code) => code.replace('url("./icon.png")', ''))
await page.waitForNavigation()
expect(await getBg('.import-image')).toMatch('')
})

test('HTML', async () => {
await page.goto(viteTestUrl + '/counter/index.html')
let btn = await page.$('button')
Expand Down
Binary file added packages/playground/hmr/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions packages/playground/hmr/index.html
@@ -1,5 +1,13 @@
<link id="global-css" rel="stylesheet" href="./global.css?param=required" />
<script type="module" src="./hmr.ts"></script>
<style>
.import-image {
width: 30px;
height: 30px;
background: url('./icon.png') no-repeat;
background-size: contain;
}
</style>

<div class="app"></div>
<div class="dep"></div>
Expand All @@ -8,3 +16,4 @@
<div class="custom-communication"></div>
<div class="css-prev"></div>
<div class="css-post"></div>
<div class="import-image"></div>
5 changes: 5 additions & 0 deletions packages/playground/ssr-html/index.html
Expand Up @@ -4,6 +4,11 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SSR HTML</title>
<style>
body {
background-color: white;
}
</style>
</head>
<body>
<h1>SSR Dynamic HTML</h1>
Expand Down
9 changes: 6 additions & 3 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -300,6 +300,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

const inlined = inlineRE.test(id)
const modules = cssModulesCache.get(config)!.get(id)
const isHTMLProxy = htmlProxyRE.test(id)
const modulesCode =
modules && dataToEsm(modules, { namedExports: true, preferConst: true })

Expand All @@ -322,6 +323,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
cssContent = getCodeWithSourcemap('css', css, sourcemap)
}

if (isHTMLProxy) {
return cssContent
}

return [
`import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify(
path.posix.join(config.base, CLIENT_PUBLIC_PATH)
Expand All @@ -346,7 +351,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
// and then use the cache replace inline-style-flag when `generateBundle` in vite:build-html plugin
const inlineCSS = inlineCSSRE.test(id)
const query = parseRequest(id)
const isHTMLProxy = htmlProxyRE.test(id)
if (inlineCSS && isHTMLProxy) {
addToHTMLProxyTransformResult(
`${cleanUrl(id)}_${Number.parseInt(query!.index)}`,
Expand Down Expand Up @@ -717,12 +721,11 @@ async function compileCSS(
postcssConfig && postcssConfig.plugins ? postcssConfig.plugins.slice() : []

if (needInlineImport) {
const isHTMLProxy = htmlProxyRE.test(id)
postcssPlugins.unshift(
(await import('postcss-import')).default({
async resolve(id, basedir) {
const publicFile = checkPublicFile(id, config)
if (isHTMLProxy && publicFile) {
if (publicFile) {
return publicFile
}

Expand Down
41 changes: 35 additions & 6 deletions packages/vite/src/node/server/middlewares/indexHtml.ts
Expand Up @@ -16,14 +16,25 @@ import {
import type { ResolvedConfig, ViteDevServer } from '../..'
import { send } from '../send'
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
import { cleanUrl, fsPathFromId, normalizePath, injectQuery } from '../../utils'
import {
cleanUrl,
fsPathFromId,
normalizePath,
injectQuery,
ensureWatchedFile
} from '../../utils'
import type { ModuleGraph } from '../moduleGraph'

interface AssetNode {
start: number
end: number
code: string
}

export function createDevHtmlTransformFn(
server: ViteDevServer
): (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,
Expand Down Expand Up @@ -94,14 +105,15 @@ const devHtmlHook: IndexHtmlTransformHook = async (
html,
{ path: htmlPath, filename, server, originalUrl }
) => {
const { config, moduleGraph } = server!
const { config, moduleGraph, watcher } = server!
const base = config.base || '/'

const s = new MagicString(html)
let inlineModuleIndex = -1
const filePath = cleanUrl(htmlPath)
const styleUrl: AssetNode[] = []

const addInlineModule = (node: ElementNode, ext: 'js' | 'css') => {
const addInlineModule = (node: ElementNode, ext: 'js') => {
inlineModuleIndex++

const url = filePath.replace(normalizePath(config.root), '')
Expand All @@ -128,7 +140,6 @@ const devHtmlHook: IndexHtmlTransformHook = async (
if (module) {
server?.moduleGraph.invalidateModule(module)
}

s.overwrite(
node.loc.start.offset,
node.loc.end.offset,
Expand All @@ -154,7 +165,12 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}

if (node.tag === 'style' && node.children.length) {
addInlineModule(node, 'css')
const children = node.children[0] as TextNode
styleUrl.push({
start: children.loc.start.offset,
end: children.loc.end.offset,
code: children.content
})
}

// elements with [href/src] attrs
Expand All @@ -172,6 +188,19 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}
})

await Promise.all(
styleUrl.map(async ({ start, end, code }, index) => {
const url = filename + `?html-proxy&${index}.css`

// ensure module in graph after successful load
const mod = await moduleGraph.ensureEntryFromUrl(url, false)
ensureWatchedFile(watcher, mod.file, config.root)

const result = await server!.pluginContainer.transform(code, url)
s.overwrite(start, end, result?.code || '')
})
)

html = s.toString()

return {
Expand Down