Skip to content

Commit

Permalink
fix: inline style hmr, transform style code inplace (#7869)
Browse files Browse the repository at this point in the history
  • Loading branch information
poyoho authored Apr 30, 2022
1 parent a9fa60b commit a30a548
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 74 deletions.
7 changes: 6 additions & 1 deletion packages/playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Loading
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,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 @@ -323,6 +324,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 @@ -347,7 +352,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 @@ -718,12 +722,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
Original file line number Diff line number Diff line change
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

0 comments on commit a30a548

Please sign in to comment.