Skip to content

Commit a30a548

Browse files
authored
fix: inline style hmr, transform style code inplace (#7869)
1 parent a9fa60b commit a30a548

File tree

9 files changed

+91
-74
lines changed

9 files changed

+91
-74
lines changed

packages/playground/assets/__tests__/assets.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { createHash } from 'crypto'
21
import {
32
findAssetFile,
43
getBg,
@@ -296,6 +295,11 @@ describe('css and assets in css in build watch', () => {
296295
}
297296
})
298297

298+
test('inline style test', async () => {
299+
expect(await getBg('.inline-style')).toMatch(assetMatch)
300+
expect(await getBg('.style-url-assets')).toMatch(assetMatch)
301+
})
302+
299303
if (!isBuild) {
300304
test('@import in html style tag hmr', async () => {
301305
await untilUpdated(() => getColor('.import-css'), 'rgb(0, 136, 255)')
@@ -304,6 +308,7 @@ if (!isBuild) {
304308
(code) => code.replace('#0088ff', '#00ff88'),
305309
true
306310
)
311+
await page.waitForNavigation()
307312
await untilUpdated(() => getColor('.import-css'), 'rgb(0, 255, 136)')
308313
})
309314
}

packages/playground/assets/index.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ <h3>url</h3>
207207
background-size: 10px 10px;
208208
}
209209
</style>
210-
<div style="background: url('./nested/asset.png'); background-size: 10px 10px">
210+
<div
211+
class="inline-style"
212+
style="background: url('./nested/asset.png'); background-size: 10px 10px"
213+
>
211214
inline style
212215
</div>
213216
<div class="style-url-assets">use style class</div>
@@ -235,6 +238,21 @@ <h3 id="foo">import module css</h3>
235238

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

241+
<h3>style in svg</h3>
242+
<svg viewBox="0 0 512 512" width="21" height="21" class="style-insvg">
243+
<style>
244+
.style-insvg-color {
245+
fill: #0088ff;
246+
}
247+
</style>
248+
<g class="style-insvg-color">
249+
<rect x="224" y="352" width="64" height="64" />
250+
<path
251+
d="M128 128v96h64v-96h96v96h-32v32h-32v64h64v-64h64V128h-32V96H160v32h-32z"
252+
/>
253+
</g>
254+
</svg>
255+
238256
<style>
239257
@import '/foo.css';
240258
</style>

packages/playground/css-sourcemap/__tests__/serve.spec.ts

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,68 +17,6 @@ if (!isBuild) {
1717
throw new Error('Not found')
1818
}
1919

20-
test('inline css', async () => {
21-
const css = await getStyleTagContentIncluding('.inline ')
22-
const map = extractSourcemap(css)
23-
expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(`
24-
Object {
25-
"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;",
26-
"sources": Array [
27-
"/root/index.html",
28-
],
29-
"sourcesContent": Array [
30-
"<link rel=\\"stylesheet\\" href=\\"./linked.css\\" />
31-
<link rel=\\"stylesheet\\" href=\\"./linked-with-import.css\\" />
32-
33-
<style>
34-
.inline {
35-
color: red;
36-
}
37-
</style>
38-
39-
<div class=\\"wrapper\\">
40-
<h1>CSS Sourcemap</h1>
41-
42-
<p class=\\"inline\\">&lt;inline&gt;</p>
43-
44-
<p class=\\"linked\\">&lt;linked&gt;: no import</p>
45-
<p class=\\"linked-with-import\\">&lt;linked&gt;: with import</p>
46-
47-
<p class=\\"imported\\">&lt;imported&gt;: no import</p>
48-
<p class=\\"imported-with-import\\">&lt;imported&gt;: with import</p>
49-
50-
<p class=\\"imported-sass\\">&lt;imported sass&gt;</p>
51-
<p class=\\"imported-sass-module\\">&lt;imported sass&gt; with module</p>
52-
53-
<p class=\\"imported-less\\">&lt;imported less&gt; with string additionalData</p>
54-
55-
<p class=\\"imported-stylus\\">&lt;imported stylus&gt;</p>
56-
</div>
57-
58-
<script type=\\"module\\">
59-
import './imported.css'
60-
import './imported-with-import.css'
61-
62-
import './imported.sass'
63-
import sassModule from './imported.module.sass'
64-
65-
document
66-
.querySelector('.imported-sass-module')
67-
.classList.add(sassModule['imported-sass-module'])
68-
69-
import './imported.less'
70-
71-
import './imported.styl'
72-
</script>
73-
74-
<iframe src=\\"virtual.html\\"></iframe>
75-
",
76-
],
77-
"version": 3,
78-
}
79-
`)
80-
})
81-
8220
test('linked css', async () => {
8321
const res = await page.request.get(
8422
new URL('./linked.css', page.url()).href,

packages/playground/hmr/__tests__/hmr.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isBuild, editFile, untilUpdated } from '../../testUtils'
1+
import { isBuild, editFile, untilUpdated, getBg } from '../../testUtils'
22

33
test('should render', async () => {
44
expect(await page.textContent('.app')).toBe('1')
@@ -195,6 +195,16 @@ if (!isBuild) {
195195
expect(await btn.textContent()).toBe('Counter 1')
196196
})
197197

198+
test('css in html hmr', async () => {
199+
await page.goto(viteTestUrl)
200+
expect(await getBg('.import-image')).toMatch('icon')
201+
await page.goto(viteTestUrl + '/foo/')
202+
expect(await getBg('.import-image')).toMatch('icon')
203+
editFile('index.html', (code) => code.replace('url("./icon.png")', ''))
204+
await page.waitForNavigation()
205+
expect(await getBg('.import-image')).toMatch('')
206+
})
207+
198208
test('HTML', async () => {
199209
await page.goto(viteTestUrl + '/counter/index.html')
200210
let btn = await page.$('button')

packages/playground/hmr/icon.png

3.32 KB
Loading

packages/playground/hmr/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
<link id="global-css" rel="stylesheet" href="./global.css?param=required" />
22
<script type="module" src="./hmr.ts"></script>
3+
<style>
4+
.import-image {
5+
width: 30px;
6+
height: 30px;
7+
background: url('./icon.png') no-repeat;
8+
background-size: contain;
9+
}
10+
</style>
311

412
<div class="app"></div>
513
<div class="dep"></div>
@@ -8,3 +16,4 @@
816
<div class="custom-communication"></div>
917
<div class="css-prev"></div>
1018
<div class="css-post"></div>
19+
<div class="import-image"></div>

packages/playground/ssr-html/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>SSR HTML</title>
7+
<style>
8+
body {
9+
background-color: white;
10+
}
11+
</style>
712
</head>
813
<body>
914
<h1>SSR Dynamic HTML</h1>

packages/vite/src/node/plugins/css.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
301301

302302
const inlined = inlineRE.test(id)
303303
const modules = cssModulesCache.get(config)!.get(id)
304+
const isHTMLProxy = htmlProxyRE.test(id)
304305
const modulesCode =
305306
modules && dataToEsm(modules, { namedExports: true, preferConst: true })
306307

@@ -323,6 +324,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
323324
cssContent = getCodeWithSourcemap('css', css, sourcemap)
324325
}
325326

327+
if (isHTMLProxy) {
328+
return cssContent
329+
}
330+
326331
return [
327332
`import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify(
328333
path.posix.join(config.base, CLIENT_PUBLIC_PATH)
@@ -347,7 +352,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
347352
// and then use the cache replace inline-style-flag when `generateBundle` in vite:build-html plugin
348353
const inlineCSS = inlineCSSRE.test(id)
349354
const query = parseRequest(id)
350-
const isHTMLProxy = htmlProxyRE.test(id)
351355
if (inlineCSS && isHTMLProxy) {
352356
addToHTMLProxyTransformResult(
353357
`${cleanUrl(id)}_${Number.parseInt(query!.index)}`,
@@ -718,12 +722,11 @@ async function compileCSS(
718722
postcssConfig && postcssConfig.plugins ? postcssConfig.plugins.slice() : []
719723

720724
if (needInlineImport) {
721-
const isHTMLProxy = htmlProxyRE.test(id)
722725
postcssPlugins.unshift(
723726
(await import('postcss-import')).default({
724727
async resolve(id, basedir) {
725728
const publicFile = checkPublicFile(id, config)
726-
if (isHTMLProxy && publicFile) {
729+
if (publicFile) {
727730
return publicFile
728731
}
729732

packages/vite/src/node/server/middlewares/indexHtml.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@ import {
1616
import type { ResolvedConfig, ViteDevServer } from '../..'
1717
import { send } from '../send'
1818
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
19-
import { cleanUrl, fsPathFromId, normalizePath, injectQuery } from '../../utils'
19+
import {
20+
cleanUrl,
21+
fsPathFromId,
22+
normalizePath,
23+
injectQuery,
24+
ensureWatchedFile
25+
} from '../../utils'
2026
import type { ModuleGraph } from '../moduleGraph'
2127

28+
interface AssetNode {
29+
start: number
30+
end: number
31+
code: string
32+
}
33+
2234
export function createDevHtmlTransformFn(
2335
server: ViteDevServer
2436
): (url: string, html: string, originalUrl: string) => Promise<string> {
2537
const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
26-
2738
return (url: string, html: string, originalUrl: string): Promise<string> => {
2839
return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], {
2940
path: url,
@@ -94,14 +105,15 @@ const devHtmlHook: IndexHtmlTransformHook = async (
94105
html,
95106
{ path: htmlPath, filename, server, originalUrl }
96107
) => {
97-
const { config, moduleGraph } = server!
108+
const { config, moduleGraph, watcher } = server!
98109
const base = config.base || '/'
99110

100111
const s = new MagicString(html)
101112
let inlineModuleIndex = -1
102113
const filePath = cleanUrl(htmlPath)
114+
const styleUrl: AssetNode[] = []
103115

104-
const addInlineModule = (node: ElementNode, ext: 'js' | 'css') => {
116+
const addInlineModule = (node: ElementNode, ext: 'js') => {
105117
inlineModuleIndex++
106118

107119
const url = filePath.replace(normalizePath(config.root), '')
@@ -128,7 +140,6 @@ const devHtmlHook: IndexHtmlTransformHook = async (
128140
if (module) {
129141
server?.moduleGraph.invalidateModule(module)
130142
}
131-
132143
s.overwrite(
133144
node.loc.start.offset,
134145
node.loc.end.offset,
@@ -154,7 +165,12 @@ const devHtmlHook: IndexHtmlTransformHook = async (
154165
}
155166

156167
if (node.tag === 'style' && node.children.length) {
157-
addInlineModule(node, 'css')
168+
const children = node.children[0] as TextNode
169+
styleUrl.push({
170+
start: children.loc.start.offset,
171+
end: children.loc.end.offset,
172+
code: children.content
173+
})
158174
}
159175

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

191+
await Promise.all(
192+
styleUrl.map(async ({ start, end, code }, index) => {
193+
const url = filename + `?html-proxy&${index}.css`
194+
195+
// ensure module in graph after successful load
196+
const mod = await moduleGraph.ensureEntryFromUrl(url, false)
197+
ensureWatchedFile(watcher, mod.file, config.root)
198+
199+
const result = await server!.pluginContainer.transform(code, url)
200+
s.overwrite(start, end, result?.code || '')
201+
})
202+
)
203+
175204
html = s.toString()
176205

177206
return {

0 commit comments

Comments
 (0)