Skip to content

Commit

Permalink
fix(html): inline style attribute not working in dev (vitejs#14592)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Oct 12, 2023
1 parent fffe16e commit a4a17b8
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 36 deletions.
32 changes: 17 additions & 15 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -423,6 +423,23 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

css = stripBomTag(css)

// cache css compile result to map
// and then use the cache replace inline-style-flag
// when `generateBundle` in vite:build-html plugin and devHtmlHook
const inlineCSS = inlineCSSRE.test(id)
const isHTMLProxy = htmlProxyRE.test(id)
if (inlineCSS && isHTMLProxy) {
const query = parseRequest(id)
if (styleAttrRE.test(id)) {
css = css.replace(/"/g, '"')
}
addToHTMLProxyTransformResult(
`${getHash(cleanUrl(id))}_${Number.parseInt(query!.index)}`,
css,
)
return `export default ''`
}

const inlined = inlineRE.test(id)
const modules = cssModulesCache.get(config)!.get(id)

Expand Down Expand Up @@ -475,21 +492,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
// build CSS handling ----------------------------------------------------

// record css
// cache css compile result to map
// and then use the cache replace inline-style-flag when `generateBundle` in vite:build-html plugin
const inlineCSS = inlineCSSRE.test(id)
const isHTMLProxy = htmlProxyRE.test(id)
const query = parseRequest(id)
if (inlineCSS && isHTMLProxy) {
if (styleAttrRE.test(id)) {
css = css.replace(/"/g, '"')
}
addToHTMLProxyTransformResult(
`${getHash(cleanUrl(id))}_${Number.parseInt(query!.index)}`,
css,
)
return `export default ''`
}
if (!inlined) {
styles.set(id, css)
}
Expand Down
36 changes: 22 additions & 14 deletions packages/vite/src/node/plugins/html.ts
Expand Up @@ -497,31 +497,22 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
}
}
}
// <tag style="... url(...) or image-set(...) ..."></tag>
// extract inline styles as virtual css and add class attribute to tag for selecting
const inlineStyle = node.attrs.find(
(prop) =>
prop.prefix === undefined &&
prop.name === 'style' &&
// only url(...) or image-set(...) in css need to emit file
(prop.value.includes('url(') ||
prop.value.includes('image-set(')),
)

const inlineStyle = findNeedTransformStyleAttribute(node)
if (inlineStyle) {
inlineModuleIndex++
// replace `inline style` to class
// replace `inline style` with __VITE_INLINE_CSS__**_**__
// and import css in js code
const code = inlineStyle.value
const code = inlineStyle.attr.value
const filePath = id.replace(normalizePath(config.root), '')
addToHTMLProxyCache(config, filePath, inlineModuleIndex, { code })
// will transform with css plugin and cache result with css-post plugin
js += `\nimport "${id}?html-proxy&inline-css&style-attr&index=${inlineModuleIndex}.css"`
const hash = getHash(cleanUrl(id))
// will transform in `applyHtmlTransforms`
const sourceCodeLocation = node.sourceCodeLocation!.attrs!['style']
overwriteAttrValue(
s,
sourceCodeLocation,
inlineStyle.location!,
`__VITE_INLINE_CSS__${hash}_${inlineModuleIndex}__`,
)
}
Expand Down Expand Up @@ -881,6 +872,23 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
}
}

// <tag style="... url(...) or image-set(...) ..."></tag>
// extract inline styles as virtual css
export function findNeedTransformStyleAttribute(
node: DefaultTreeAdapterMap['element'],
): { attr: Token.Attribute; location?: Token.Location } | undefined {
const attr = node.attrs.find(
(prop) =>
prop.prefix === undefined &&
prop.name === 'style' &&
// only url(...) or image-set(...) in css need to emit file
(prop.value.includes('url(') || prop.value.includes('image-set(')),
)
if (!attr) return undefined
const location = node.sourceCodeLocation?.attrs?.['style']
return { attr, location }
}

export interface HtmlTagDescriptor {
tag: string
attrs?: Record<string, string | boolean | undefined>
Expand Down
39 changes: 36 additions & 3 deletions packages/vite/src/node/server/middlewares/indexHtml.ts
Expand Up @@ -10,9 +10,11 @@ import {
addToHTMLProxyCache,
applyHtmlTransforms,
assetAttrsConfig,
findNeedTransformStyleAttribute,
getAttrKey,
getScriptInfo,
htmlEnvHook,
htmlProxyResult,
nodeIsElement,
overwriteAttrValue,
postImportMapHook,
Expand All @@ -27,6 +29,7 @@ import {
cleanUrl,
ensureWatchedFile,
fsPathFromId,
getHash,
injectQuery,
isJSRequest,
joinUrlSegments,
Expand All @@ -48,6 +51,12 @@ interface AssetNode {
code: string
}

interface InlineStyleAttribute {
index: number
location: Token.Location
code: string
}

export function createDevHtmlTransformFn(
server: ViteDevServer,
): (url: string, html: string, originalUrl: string) => Promise<string> {
Expand Down Expand Up @@ -179,6 +188,7 @@ const devHtmlHook: IndexHtmlTransformHook = async (
'',
)
const styleUrl: AssetNode[] = []
const inlineStyles: InlineStyleAttribute[] = []

const addInlineModule = (
node: DefaultTreeAdapterMap['element'],
Expand Down Expand Up @@ -245,6 +255,16 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}
}

const inlineStyle = findNeedTransformStyleAttribute(node)
if (inlineStyle) {
inlineModuleIndex++
inlineStyles.push({
index: inlineModuleIndex,
location: inlineStyle.location!,
code: inlineStyle.attr.value,
})
}

if (node.nodeName === 'style' && node.childNodes.length) {
const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode']
styleUrl.push({
Expand Down Expand Up @@ -273,8 +293,8 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}
})

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

// ensure module in graph after successful load
Expand All @@ -299,7 +319,20 @@ const devHtmlHook: IndexHtmlTransformHook = async (
}
s.overwrite(start, end, content)
}),
)
...inlineStyles.map(async ({ index, location, code }) => {
// will transform with css plugin and cache result with css-post plugin
const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css`

const mod = await moduleGraph.ensureEntryFromUrl(url, false)
ensureWatchedFile(watcher, mod.file, config.root)

await server?.pluginContainer.transform(code, mod.id!)

const hash = getHash(cleanUrl(mod.id!))
const result = htmlProxyResult.get(`${hash}_${index}`)
overwriteAttrValue(s, location, result ?? '')
}),
])

html = s.toString()

Expand Down
5 changes: 1 addition & 4 deletions playground/assets/__tests__/assets.spec.ts
Expand Up @@ -467,10 +467,7 @@ test('url() contains file in publicDir, in <style> tag', async () => {
expect(await getBg('.style-public-assets')).toContain(iconMatch)
})

test.skip('url() contains file in publicDir, as inline style', async () => {
// TODO: To investigate why `await getBg('.inline-style-public') === "url("http://localhost:5173/icon.png")"`
// It supposes to be `url("http://localhost:5173/foo/bar/icon.png")`
// (I built the playground to verify)
test('url() contains file in publicDir, as inline style', async () => {
expect(await getBg('.inline-style-public')).toContain(iconMatch)
})

Expand Down

0 comments on commit a4a17b8

Please sign in to comment.