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(css): no emit assets in html style tag (use map) (fix #5968) #6181

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ba5ac17
test: add url import assets in style tag / inline style tests
poyoho Dec 9, 2021
93ef3f3
fix: compile style / inline style in index.html
poyoho Dec 9, 2021
4736fe8
test: @import test
poyoho Dec 9, 2021
5004fc3
chore: test unit
poyoho Dec 10, 2021
6bc8dfa
feat: ignore repeat to create resolve
poyoho Dec 10, 2021
26dc100
chore: format
poyoho Dec 10, 2021
ab61747
Merge branch 'main' into fix/assets-in-style
poyoho Dec 10, 2021
1005c06
fix: never used import
poyoho Dec 10, 2021
827e94d
fix: fix resolve importer id
poyoho Dec 10, 2021
f1ef753
feat: define `createHTMLCSSCompiler` return type
poyoho Dec 11, 2021
610d9f8
feat: find the css plugin instance re-using transform method
poyoho Dec 12, 2021
8cf63a3
refactor: `import` proxy handle css tag in html
poyoho Dec 17, 2021
380e0f5
Merge branch 'main' into fix/assets-in-style
poyoho Dec 17, 2021
676b01b
chore: format
poyoho Dec 17, 2021
013071f
feat commit for style tag in html emit asset pipline
poyoho Dec 17, 2021
c16aec7
feat: commit for style tag in html emit asset pipline
poyoho Dec 17, 2021
e62234d
Merge branch 'vitejs:main' into fix/assets-in-style
poyoho Dec 17, 2021
6e02f9b
feat: comment
poyoho Dec 17, 2021
b41e001
fix: html-inline-script-style-proxy -> html-inline-proxy
poyoho Dec 18, 2021
d533d2a
fix: html-inline-script-style-proxy -> html-inline-proxy
poyoho Dec 18, 2021
b2c6756
fix: html-inline-script-style-proxy -> html-inline-proxy
poyoho Dec 18, 2021
a19bdda
fix: html-inline-script-style-proxy -> html-inline-proxy
poyoho Dec 18, 2021
1a0400c
chore: format
poyoho Dec 19, 2021
fc82dec
refactor: use map to resolve inline style
poyoho Dec 19, 2021
5229ab3
fix: overwrite inlineCSSRE
poyoho Dec 19, 2021
ed42247
chore: format
poyoho Dec 19, 2021
30255a5
fix: use error variable to overwrite
poyoho Dec 19, 2021
54a3347
Merge branch 'main' into fix/assets-in-style2
poyoho Dec 19, 2021
c2d0508
chore: nice naming
poyoho Dec 20, 2021
fd5ed80
fix: use diff name and rewrite comment
poyoho Dec 23, 2021
def2835
chore: remove useless comment
poyoho Dec 23, 2021
57499ad
fix: error comment
poyoho Dec 23, 2021
57cb5ce
fix: htmlProxyRE
poyoho Dec 23, 2021
071fc65
chore: comment
poyoho Dec 23, 2021
e0e59e6
feat: add a comment for handling scripts
poyoho Dec 23, 2021
b57905c
perf: move these regex-tests down a bit, we could potentially safe so…
poyoho Dec 26, 2021
2264c0c
chore: use new syntax
poyoho Dec 26, 2021
a639f1c
feat: `addToHTMLProxyTransformResult` export type
poyoho Dec 26, 2021
9237d9f
chore: format
poyoho Dec 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/playground/assets/css/import.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.import-css {
color: #0088ff;
}
29 changes: 29 additions & 0 deletions packages/playground/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,35 @@ <h2>new URL(`./${dynamic}`, import.meta.url)</h2>
<code class="dynamic-import-meta-url-2"></code>
</p>

<h2>url in style tag</h2>
<h3>url</h3>
<style class="style-url">
.style-url-assets {
background: url('./nested/asset.png');
background-size: 10px 10px;
}
</style>
<div style="background: url('./nested/asset.png'); background-size: 10px 10px">
inline style
</div>
<div class="style-url-assets">use style class</div>

<h3>base64</h3>
<style class="style-base64">
.style-base64-assets {
background: url('./static/icon.png');
}
</style>
<p class="inline-style-base64" style="background: url(./static/icon.png)">
inline style
</p>
<p class="style-base64-assets">use style class</p>

<h3 class="import-css">@import</h3>
<style class="style-import">
@import url('./css/import.css');
</style>

<script type="module">
import './css/fonts.css'
import './css/css-url.css'
Expand Down
18 changes: 17 additions & 1 deletion packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
isDataUrl,
isObject,
normalizePath,
processSrcSet
processSrcSet,
parseRequest
} from '../utils'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
Expand Down Expand Up @@ -43,6 +44,7 @@ import type Less from 'less'
import type { Alias } from 'types/alias'
import type { ModuleNode } from '../server/moduleGraph'
import { transform, formatMessages } from 'esbuild'
import { addToHTMLProxyTransformResult } from './html'

// const debug = createDebugger('vite:css')

Expand Down Expand Up @@ -86,8 +88,10 @@ const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)`
const cssLangRE = new RegExp(cssLangs)
const cssModuleRE = new RegExp(`\\.module${cssLangs}`)
const directRequestRE = /(\?|&)direct\b/
const htmlProxyRE = /(\?|&)html-proxy\b/
const commonjsProxyRE = /\?commonjs-proxy/
const inlineRE = /(\?|&)inline\b/
const inlineCSSRE = /(\?|&)inline-css\b/
const usedRE = /(\?|&)used\b/

const enum PreprocessLang {
Expand Down Expand Up @@ -312,6 +316,18 @@ 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 query = parseRequest(id)
const isHTMLProxy = htmlProxyRE.test(id)
if (inlineCSS && isHTMLProxy) {
addToHTMLProxyTransformResult(
`${cleanUrl(id)}_${Number.parseInt(query!.index)}`,
css
)
return `export default ''`
}
if (!inlined) {
styles.set(id, css)
}
Expand Down
88 changes: 80 additions & 8 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import { modulePreloadPolyfillId } from './modulePreloadPolyfill'
import type {
AttributeNode,
NodeTransform,
TextNode,
ElementNode,
CompilerError
} from '@vue/compiler-dom'
import { NodeTypes } from '@vue/compiler-dom'

const htmlProxyRE = /\?html-proxy&index=(\d+)\.js$/
const htmlProxyRE = /\?html-proxy[&inline\-css]*&index=(\d+)\.(js|css)$/
const inlineCSSRE = /__VITE_INLINE_CSS__([^_]+_\d+)__/g
export const isHTMLProxy = (id: string): boolean => htmlProxyRE.test(id)

// HTML Proxy Caches are stored by config -> filePath -> index
Expand All @@ -43,9 +45,14 @@ export const htmlProxyMap = new WeakMap<
Map<string, Array<string>>
>()

export function htmlInlineScriptProxyPlugin(config: ResolvedConfig): Plugin {
// HTML Proxy Transform result are stored by config
// `${importer}_${query.index}` -> transformed css code
// PS: key like `/vite/packages/playground/assets/index.html_1`
export const htmlProxyResult = new Map<string, string>()

export function htmlInlineProxyPlugin(config: ResolvedConfig): Plugin {
return {
name: 'vite:html-inline-script-proxy',
name: 'vite:html-inline-proxy',

resolveId(id) {
if (htmlProxyRE.test(id)) {
Expand Down Expand Up @@ -74,7 +81,6 @@ export function htmlInlineScriptProxyPlugin(config: ResolvedConfig): Plugin {
}
}

/** Add script to cache */
export function addToHTMLProxyCache(
config: ResolvedConfig,
filePath: string,
Expand All @@ -90,6 +96,13 @@ export function addToHTMLProxyCache(
htmlProxyMap.get(config)!.get(filePath)![index] = code
}

export function addToHTMLProxyTransformResult(
hash: string,
code: string
): void {
htmlProxyResult.set(hash, code)
}

// this extends the config in @vue/compiler-sfc with <link href>
export const assetAttrsConfig: Record<string, string[]> = {
link: ['href'],
Expand Down Expand Up @@ -308,6 +321,48 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
}
}
}
// <tag style="... url(...) ..."></tag>
// extract inline styles as virtual css and add class attribute to tag for selecting
const inlineStyle = node.props.find(
(prop) =>
prop.name === 'style' &&
prop.type === NodeTypes.ATTRIBUTE &&
prop.value &&
prop.value.content.includes('url(') // only url(...) in css need to emit file
) as AttributeNode
if (inlineStyle) {
inlineModuleIndex++
// replace `inline style` to class
// and import css in js code
const styleNode = inlineStyle.value!
const code = styleNode.content!
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&index=${inlineModuleIndex}.css"`

// will transfrom in `applyHtmlTransforms`
s.overwrite(
styleNode.loc.start.offset,
styleNode.loc.end.offset,
`"__VITE_INLINE_CSS__${cleanUrl(id)}_${inlineModuleIndex}__"`
)
}

// <style>...</style>
if (node.tag === 'style' && node.children.length) {
const styleNode = node.children.pop() as TextNode
const filePath = id.replace(normalizePath(config.root), '')
inlineModuleIndex++
addToHTMLProxyCache(
config,
filePath,
inlineModuleIndex,
styleNode.content
)
js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.css"`
shouldRemove = true
}

if (shouldRemove) {
// remove the script tag from the html. we are going to inject new
Expand Down Expand Up @@ -450,10 +505,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
for (const [id, html] of processedHtml) {
const isAsync = isAsyncScriptMap.get(config)!.get(id)!

// resolve asset url references
let result = html.replace(assetUrlRE, (_, fileHash, postfix = '') => {
return config.base + getAssetFilename(fileHash, config) + postfix
})
let result = html

// find corresponding entry chunk
const chunk = Object.values(bundle).find(
Expand Down Expand Up @@ -505,12 +557,32 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
}

const shortEmitName = path.posix.relative(config.root, id)
// no use assets plugin because it will emit file
let match: RegExpExecArray | null
let s: MagicString | undefined
while ((match = inlineCSSRE.exec(result))) {
s ||= new MagicString(result)
const { 0: full, 1: scopedName } = match
const cssTransformedCode = htmlProxyResult.get(scopedName)!
s.overwrite(
match.index,
match.index + full.length,
cssTransformedCode
)
}
if (s) {
result = s.toString()
}
result = await applyHtmlTransforms(result, postHooks, {
path: '/' + shortEmitName,
filename: id,
bundle,
chunk
})
// resolve asset url references
result = result.replace(assetUrlRE, (_, fileHash, postfix = '') => {
return config.base + getAssetFilename(fileHash, config) + postfix
})

if (chunk && canInlineEntry) {
// all imports from entry have been inlined to html, prevent rollup from outputting it
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { importAnalysisPlugin } from './importAnalysis'
import { cssPlugin, cssPostPlugin } from './css'
import { assetPlugin } from './asset'
import { clientInjectionsPlugin } from './clientInjections'
import { htmlInlineScriptProxyPlugin } from './html'
import { htmlInlineProxyPlugin } from './html'
import { wasmPlugin } from './wasm'
import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill'
import { webWorkerPlugin } from './worker'
Expand Down Expand Up @@ -45,7 +45,7 @@ export async function resolvePlugins(
asSrc: true
}),
config.build.ssr ? ssrRequireHookPlugin(config) : null,
htmlInlineScriptProxyPlugin(config),
htmlInlineProxyPlugin(config),
cssPlugin(config),
config.esbuild !== false ? esbuildPlugin(config.esbuild) : null,
jsonPlugin(
Expand Down
15 changes: 3 additions & 12 deletions packages/vite/src/node/plugins/worker.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { resolvePlugins } from '../plugins'
import { parse as parseUrl, URLSearchParams } from 'url'
import { fileToUrl, getAssetHash } from './asset'
import { cleanUrl, injectQuery } from '../utils'
import { cleanUrl, injectQuery, parseRequest } from '../utils'
import type Rollup from 'rollup'
import { ENV_PUBLIC_PATH } from '../constants'
import path from 'path'
import { onRollupWarning } from '../build'

function parseWorkerRequest(id: string): Record<string, string> | null {
const { search } = parseUrl(id)
if (!search) {
return null
}
return Object.fromEntries(new URLSearchParams(search.slice(1)))
}

const WorkerFileId = 'worker_file'

export function webWorkerPlugin(config: ResolvedConfig): Plugin {
Expand All @@ -27,7 +18,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {

load(id) {
if (isBuild) {
const parsedQuery = parseWorkerRequest(id)
const parsedQuery = parseRequest(id)
if (
parsedQuery &&
(parsedQuery.worker ?? parsedQuery.sharedworker) != null
Expand All @@ -38,7 +29,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
},

async transform(_, id) {
const query = parseWorkerRequest(id)
const query = parseRequest(id)
if (query && query[WorkerFileId] != null) {
return {
code: `import '${ENV_PUBLIC_PATH}'\n` + _
Expand Down
9 changes: 9 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
RawSourceMap
} from '@ampproject/remapping/dist/types/types'
import { performance } from 'perf_hooks'
import { parse as parseUrl, URLSearchParams } from 'url'

export function slash(p: string): string {
return p.replace(/\\/g, '/')
Expand Down Expand Up @@ -631,3 +632,11 @@ export const usingDynamicImport = typeof jest === 'undefined'
export const dynamicImport = usingDynamicImport
? new Function('file', 'return import(file)')
: require

export function parseRequest(id: string): Record<string, string> | null {
const { search } = parseUrl(id)
if (!search) {
return null
}
return Object.fromEntries(new URLSearchParams(search.slice(1)))
}