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

feat(css): provide filename for css-minify error #16006

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cd7610a
feat: provide filename for css-minify error
chaejunlee Feb 22, 2024
ccd4cbd
feat: also handle `generateBundle()`
chaejunlee Feb 22, 2024
de30d83
fix(css): track error by fileMap
chaejunlee Feb 28, 2024
ccb18ae
refactor(css): rename variables
chaejunlee Feb 28, 2024
626bde6
fix(css): make the numbers more precise
chaejunlee Feb 28, 2024
589b3e2
fefactor(css): reduce redundant if check
chaejunlee Feb 28, 2024
a0cc001
chore(css): add comment for better explanation)
chaejunlee Feb 28, 2024
2cf9ea8
fix(css) apply review
chaejunlee Mar 1, 2024
bc90159
refactor: reduce unnecessary changes
chaejunlee Mar 1, 2024
7196772
Merge branch 'main' of https://github.com/vitejs/vite into fix/css-er…
chaejunlee Mar 1, 2024
39330c7
fix: set the fallback to the original default
chaejunlee Mar 2, 2024
86ec814
fix: apply review
chaejunlee Mar 2, 2024
7435ee4
refactor: clean up
chaejunlee Mar 2, 2024
2fb1493
refactor
chaejunlee Mar 6, 2024
6a59086
fix: correctly handle import line parsing
chaejunlee Mar 6, 2024
b0cf404
fix: narrow down the type of `collected`
chaejunlee Mar 6, 2024
3519c6a
Update packages/vite/src/node/plugins/css.ts
chaejunlee Mar 6, 2024
91e399c
Update packages/vite/src/node/plugins/css.ts
chaejunlee Mar 6, 2024
d9b237b
Update packages/vite/src/node/plugins/css.ts
chaejunlee Mar 6, 2024
e886065
Update packages/vite/src/node/plugins/css.ts
chaejunlee Mar 6, 2024
d42c20c
fix: calculating imported end lines when multiple imports
chaejunlee Mar 6, 2024
22c12a2
refactore: simplify logic
chaejunlee Mar 12, 2024
5113ff4
refactor: remove unused function
chaejunlee Mar 12, 2024
07cc0cd
fix: early return when content is null
chaejunlee Mar 12, 2024
39838f2
fix: 1-based line + concat css w/o `\n`
chaejunlee Mar 14, 2024
01aa47f
fix: refactor + line count
chaejunlee Mar 14, 2024
a3295ff
fix: new `start` should be before prev `end` (not concating with `\n`)
chaejunlee Mar 14, 2024
c1d624d
fix: concat with `\n`
chaejunlee Mar 14, 2024
58fcc12
chore: simplify
sapphi-red Mar 17, 2024
7c9d3e0
fix: line count of empty css is now 1
chaejunlee Mar 17, 2024
00e1b76
fix: don't push `content` if it is `null` or `''`
chaejunlee Apr 3, 2024
3f141d2
chore: reduce diff
sapphi-red May 4, 2024
f519538
chore: reduce diff
sapphi-red May 4, 2024
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
170 changes: 134 additions & 36 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import postcssrc from 'postcss-load-config'
import type {
ExistingRawSourceMap,
ModuleFormat,
OutputAsset,
OutputChunk,
RenderedChunk,
RollupError,
Expand Down Expand Up @@ -64,11 +65,12 @@ import {
removeUrlQuery,
requireResolveFromRootWithFallback,
slash,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this line to resolve the merge conflict. Before merging, this needs to be double checked!

splitRE,
stripBase,
stripBomTag,
urlRE,
} from '../utils'
import type { Logger } from '../logger'
import { type Logger } from '../logger'
import { addToHTMLProxyTransformResult } from './html'
import {
assetUrlRE,
Expand Down Expand Up @@ -245,6 +247,14 @@ function encodePublicUrlsInCSS(config: ResolvedConfig) {
return config.command === 'build'
}

function getLineCount(str: string): number {
const lines = str.match(splitRE)
if (lines == null) {
return 0
}
return lines.length + 1
}

const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g

/**
Expand Down Expand Up @@ -530,7 +540,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
} else if (inlined) {
let content = css
if (config.build.cssMinify) {
content = await minifyCSS(content, config, true)
content = await minifyCSS(content, config, true, id, [
{ file: id, end: getLineCount(content) },
])
}
code = `export default ${JSON.stringify(content)}`
} else {
Expand All @@ -549,13 +561,18 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

async renderChunk(code, chunk, opts) {
let chunkCSS = ''
let line = 0
const concatCssEndLines: Array<{ file: string; end: number }> = []
let isPureCssChunk = true
const ids = Object.keys(chunk.modules)
for (const id of ids) {
if (styles.has(id)) {
// ?transform-only is used for ?url and shouldn't be included in normal CSS chunks
if (!transformOnlyRE.test(id)) {
chunkCSS += styles.get(id)
const content = styles.get(id)!
chunkCSS += content
line += getLineCount(content)
concatCssEndLines.push({ file: id, end: line })
// a css module contains JS, so it makes this not a pure css chunk
if (cssModuleRE.test(id)) {
isPureCssChunk = false
Expand Down Expand Up @@ -675,7 +692,18 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
await urlEmitQueue.run(async () =>
Promise.all(
urlEmitTasks.map(async (info) => {
info.content = await finalizeCss(info.content, true, config)
info.content = await finalizeCss(
info.content,
true,
config,
info.originalFilename,
[
{
file: info.originalFilename,
end: getLineCount(info.content),
},
],
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
)
}),
),
)
Expand Down Expand Up @@ -720,22 +748,24 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

if (chunkCSS) {
if (config.build.cssCodeSplit) {
const cssFullAssetName = ensureFileExt(chunk.name, '.css')
// if facadeModuleId doesn't exist or doesn't have a CSS extension,
// that means a JS entry file imports a CSS file.
// in this case, only use the filename for the CSS chunk name like JS chunks.
const cssAssetName =
chunk.isEntry &&
(!chunk.facadeModuleId || !isCSSRequest(chunk.facadeModuleId))
? path.basename(cssFullAssetName)
: cssFullAssetName

sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
if (opts.format === 'es' || opts.format === 'cjs') {
if (isPureCssChunk) {
// this is a shared CSS-only chunk that is empty.
pureCssChunks.add(chunk)
}

const isEntry = chunk.isEntry && isPureCssChunk
const cssFullAssetName = ensureFileExt(chunk.name, '.css')
// if facadeModuleId doesn't exist or doesn't have a CSS extension,
// that means a JS entry file imports a CSS file.
// in this case, only use the filename for the CSS chunk name like JS chunks.
const cssAssetName =
chunk.isEntry &&
(!chunk.facadeModuleId || !isCSSRequest(chunk.facadeModuleId))
? path.basename(cssFullAssetName)
: cssFullAssetName

const originalFilename = getChunkOriginalFileName(
chunk,
config.root,
Expand All @@ -746,7 +776,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

// wait for previous tasks as well
chunkCSS = await codeSplitEmitQueue.run(async () => {
return finalizeCss(chunkCSS, true, config)
return finalizeCss(
chunkCSS,
true,
config,
cssAssetName,
concatCssEndLines,
)
})

// emit corresponding css file
Expand All @@ -768,7 +804,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
// But because entry chunk can be imported by dynamic import,
// we shouldn't remove the inlined CSS. (#10285)

chunkCSS = await finalizeCss(chunkCSS, true, config)
chunkCSS = await finalizeCss(
chunkCSS,
true,
config,
cssAssetName,
concatCssEndLines,
)
let cssString = JSON.stringify(chunkCSS)
cssString =
renderAssetUrlInJS(
Expand Down Expand Up @@ -886,38 +928,73 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
})
}

function extractCss() {
async function extractCss() {
let css = ''
const collected = new Set<OutputChunk>()
let line = 0
const collected = new Set<OutputAsset | OutputChunk>()
const concatCssEndLines: Array<{ file: string; end: number }> = []
const prelimaryNameToChunkMap = new Map(
Object.values(bundle)
.filter((chunk): chunk is OutputChunk => chunk.type === 'chunk')
.map((chunk) => [chunk.preliminaryFileName, chunk]),
)

function collect(fileName: string) {
function collect(fileName: string): string {
const chunk = bundle[fileName]
if (!chunk || chunk.type !== 'chunk' || collected.has(chunk)) return
collected.add(chunk)
if (!chunk || chunk.type !== 'chunk') {
collected.add(chunk)
return ''
}
if (collected.has(chunk)) {
return ''
}
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved

const css = chunkCSSMap.get(chunk.preliminaryFileName) ?? ''
const importedCSS = chunk.imports.reduce((css, file) => {
const imported = collect(file)
if (imported.length > 0) {
return css + '\n' + imported
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
}
return css
}, '')

chunk.imports.forEach(collect)
css += chunkCSSMap.get(chunk.preliminaryFileName) ?? ''
return importedCSS + '\n' + css
}

for (const chunkName of chunkCSSMap.keys())
collect(prelimaryNameToChunkMap.get(chunkName)?.fileName ?? '')
for (const chunkName of chunkCSSMap.keys()) {
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
const filename = prelimaryNameToChunkMap.get(chunkName)?.fileName
if (!filename) {
continue
}

return css
const cssCode = collect(filename)

if (cssCode) {
line += getLineCount(cssCode)
concatCssEndLines.push({ file: filename, end: line })

css += cssCode
}
}
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved

return await finalizeCss(
css,
false,
config,
undefined,
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
concatCssEndLines,
)
}
let extractedCss = !hasEmitted && extractCss()
if (extractedCss) {
hasEmitted = true
extractedCss = await finalizeCss(extractedCss, true, config)
this.emitFile({
name: cssBundleName,
type: 'asset',
source: extractedCss,
})
if (!hasEmitted) {
const extractedCss = await extractCss()
if (extractedCss) {
hasEmitted = true
this.emitFile({
name: cssBundleName,
type: 'asset',
source: extractedCss,
})
}
}
},
}
Expand Down Expand Up @@ -1530,13 +1607,15 @@ async function finalizeCss(
css: string,
minify: boolean,
config: ResolvedConfig,
filename: string | undefined,
concatCssEndLines: Array<{ file: string; end: number }>,
) {
// hoist external @imports and @charset to the top of the CSS chunk per spec (#1845 and #6333)
if (css.includes('@import') || css.includes('@charset')) {
css = await hoistAtRules(css)
}
if (minify && config.build.cssMinify) {
css = await minifyCSS(css, config, false)
css = await minifyCSS(css, config, false, filename, concatCssEndLines)
}
return css
}
Expand Down Expand Up @@ -1771,6 +1850,8 @@ async function minifyCSS(
css: string,
config: ResolvedConfig,
inlined: boolean,
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
filename: string | undefined,
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
concatCssEndLines: Array<{ file: string; end: number }>,
) {
// We want inlined CSS to not end with a linebreak, while ensuring that
// regular CSS assets do end with a linebreak.
Expand All @@ -1781,7 +1862,7 @@ async function minifyCSS(
...config.css?.lightningcss,
targets: convertTargets(config.build.cssTarget),
cssModules: undefined,
filename: cssBundleName,
filename: filename || 'style.css',
code: Buffer.from(css),
minify: true,
})
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -1804,6 +1885,23 @@ async function minifyCSS(
...resolveMinifyCssEsbuildOptions(config.esbuild || {}),
chaejunlee marked this conversation as resolved.
Show resolved Hide resolved
})
if (warnings.length) {
if (concatCssEndLines && concatCssEndLines.length > 0) {
for (const warning of warnings) {
if (warning.location) {
const { line } = warning.location
let start = 1
for (const { file, end } of concatCssEndLines) {
// reassign the file and line number to the original file
if (start <= line && line <= end) {
warning.location.file = file
warning.location.line = line - start + 1
break
}
start = end
}
}
}
}
const msgs = await formatMessages(warnings, { kind: 'warning' })
config.logger.warn(
colors.yellow(`warnings when minifying css:\n${msgs.join('\n')}`),
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ export function isFilePathESM(
}
}

const splitRE = /\r?\n/
export const splitRE = /\r?\n/g

const range: number = 2

Expand Down