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(build): invalidate chunk hash when css changed #11475

Merged
merged 2 commits into from Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 118 additions & 2 deletions packages/vite/src/node/__tests__/build.spec.ts
Expand Up @@ -3,14 +3,107 @@ import { fileURLToPath } from 'node:url'
import colors from 'picocolors'
import type { Logger } from 'vite'
import { describe, expect, test, vi } from 'vitest'
import type { OutputOptions } from 'rollup'
import type { OutputChunk, OutputOptions, RollupOutput } from 'rollup'
import type { LibraryFormats, LibraryOptions } from '../build'
import { resolveBuildOutputs, resolveLibFilename } from '../build'
import { build, resolveBuildOutputs, resolveLibFilename } from '../build'
import { createLogger } from '../logger'

const __dirname = resolve(fileURLToPath(import.meta.url), '..')

type FormatsToFileNames = [LibraryFormats, string][]

describe('build', () => {
test('file hash should change when css changes for dynamic entries', async () => {
const buildProject = async (cssColor: string) => {
return (await build({
root: resolve(__dirname, 'packages/build-project'),
logLevel: 'silent',
build: {
write: false,
},
plugins: [
{
name: 'test',
resolveId(id) {
if (
id === 'entry.js' ||
id === 'subentry.js' ||
id === 'foo.css'
) {
return '\0' + id
}
},
load(id) {
if (id === '\0entry.js') {
return `window.addEventListener('click', () => { import('subentry.js') });`
}
if (id === '\0subentry.js') {
return `import 'foo.css'`
}
if (id === '\0foo.css') {
return `.foo { color: ${cssColor} }`
}
},
},
],
})) as RollupOutput
}
const result = await Promise.all([
buildProject('red'),
buildProject('blue'),
])
assertOutputHashContentChange(result[0], result[1])
})

test('file hash should change when pure css chunk changes', async () => {
const buildProject = async (cssColor: string) => {
return (await build({
root: resolve(__dirname, 'packages/build-project'),
logLevel: 'silent',
build: {
write: false,
},
plugins: [
{
name: 'test',
resolveId(id) {
if (
id === 'entry.js' ||
id === 'foo.js' ||
id === 'bar.js' ||
id === 'baz.js' ||
id === 'foo.css' ||
id === 'bar.css' ||
id === 'baz.css'
) {
return '\0' + id
}
},
load(id) {
if (id === '\0entry.js') {
return `
window.addEventListener('click', () => { import('foo.js') });
window.addEventListener('click', () => { import('bar.js') });`
}
if (id === '\0foo.js') return `import 'foo.css'; import 'baz.js'`
if (id === '\0bar.js') return `import 'bar.css'; import 'baz.js'`
if (id === '\0baz.js') return `import 'baz.css'`
if (id === '\0foo.css') return `.foo { color: red }`
if (id === '\0bar.css') return `.foo { color: green }`
if (id === '\0baz.css') return `.foo { color: ${cssColor} }`
},
},
],
})) as RollupOutput
}
const result = await Promise.all([
buildProject('yellow'),
buildProject('blue'),
])
assertOutputHashContentChange(result[0], result[1])
})
})

const baseLibOptions: LibraryOptions = {
fileName: 'my-lib',
entry: 'mylib.js',
Expand Down Expand Up @@ -439,3 +532,26 @@ describe('resolveBuildOutputs', () => {
)
})
})

/**
* for each chunks in output1, if there's a chunk in output2 with the same fileName,
* ensure that the chunk code is the same. if not, the chunk hash should have changed.
*/
function assertOutputHashContentChange(
output1: RollupOutput,
output2: RollupOutput,
) {
for (const chunk of output1.output) {
if (chunk.type === 'chunk') {
const chunk2 = output2.output.find(
(c) => c.type === 'chunk' && c.fileName === chunk.fileName,
) as OutputChunk | undefined
if (chunk2) {
expect(
chunk.code,
`the ${chunk.fileName} chunk has the same hash but different contents between builds`,
).toEqual(chunk2.code)
}
}
}
}
@@ -0,0 +1,3 @@
<h1>Hello world</h1>

<script type="module" src="entry.js"></script>
10 changes: 10 additions & 0 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -622,6 +622,16 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
return null
},

augmentChunkHash(chunk) {
if (chunk.viteMetadata?.importedCss.size) {
let hash = ''
for (const id of chunk.viteMetadata.importedCss) {
hash += id
}
return hash
}
},

async generateBundle(opts, bundle) {
// @ts-expect-error asset emits are skipped in legacy bundle
if (opts.__vite_skip_asset_emit__) {
Expand Down