Skip to content

Commit 26b1b99

Browse files
fix(css): file or contents missing error in build watch (#3742) (#3747)
Co-authored-by: Shinigami <[email protected]>
1 parent 6e3653f commit 26b1b99

File tree

7 files changed

+94
-18
lines changed

7 files changed

+94
-18
lines changed

packages/playground/assets/__tests__/assets.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
isBuild,
77
listAssets,
88
readManifest,
9-
readFile
9+
readFile,
10+
editFile,
11+
notifyRebuildComplete
1012
} from '../../testUtils'
1113

1214
const assetMatch = isBuild
@@ -195,3 +197,15 @@ if (isBuild) {
195197
}
196198
})
197199
}
200+
describe('css and assets in css in build watch', () => {
201+
if (isBuild) {
202+
test('css will not be lost and css does not contain undefined', async () => {
203+
editFile('index.html', (code) => code.replace('Assets', 'assets'), true)
204+
await notifyRebuildComplete(watcher)
205+
const cssFile = findAssetFile(/index\.\w+\.css$/, 'foo')
206+
expect(cssFile).not.toBe('')
207+
expect(cssFile).not.toMatch(/undefined/)
208+
watcher?.close()
209+
})
210+
}
211+
})

packages/playground/assets/vite.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
},
1414
build: {
1515
outDir: 'dist/foo',
16-
manifest: true
16+
manifest: true,
17+
watch: {}
1718
}
1819
}

packages/playground/testEnv.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Page } from 'playwright-chromium'
2+
import { RollupWatcher } from 'rollup'
23

34
declare global {
45
// injected by the custom jest env in scripts/jestEnv.js
@@ -7,4 +8,5 @@ declare global {
78
// injected in scripts/jestPerTestSetup.ts
89
const browserLogs: string[]
910
const viteTestUrl: string
11+
const watcher: RollupWatcher
1012
}

packages/playground/testUtils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,12 @@ export function readFile(filename: string) {
6666
return fs.readFileSync(path.resolve(testDir, filename), 'utf-8')
6767
}
6868

69-
export function editFile(filename: string, replacer: (str: string) => string) {
70-
if (isBuild) return
69+
export function editFile(
70+
filename: string,
71+
replacer: (str: string) => string,
72+
runInBuild: boolean = false
73+
): void {
74+
if (isBuild && !runInBuild) return
7175
filename = path.resolve(testDir, filename)
7276
const content = fs.readFileSync(filename, 'utf-8')
7377
const modified = replacer(content)
@@ -122,3 +126,8 @@ export async function untilUpdated(
122126
}
123127
}
124128
}
129+
130+
/**
131+
* Send the rebuild complete message in build watch
132+
*/
133+
export { notifyRebuildComplete } from '../../scripts/jestPerTestSetup'

packages/vite/src/node/plugins/asset.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,21 @@ const assetHashToFilenameMap = new WeakMap<
2727
ResolvedConfig,
2828
Map<string, string>
2929
>()
30+
// save hashes of the files that has been emitted in build watch
31+
const emittedHashMap = new WeakMap<ResolvedConfig, Set<string>>()
3032

3133
/**
3234
* Also supports loading plain strings with import text from './foo.txt?raw'
3335
*/
3436
export function assetPlugin(config: ResolvedConfig): Plugin {
37+
// assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined
38+
assetHashToFilenameMap.set(config, new Map())
3539
return {
3640
name: 'vite:asset',
3741

3842
buildStart() {
3943
assetCache.set(config, new Map())
40-
assetHashToFilenameMap.set(config, new Map())
44+
emittedHashMap.set(config, new Set())
4145
},
4246

4347
resolveId(id) {
@@ -202,8 +206,6 @@ async function fileToBuiltUrl(
202206
}
203207

204208
const file = cleanUrl(id)
205-
const { search, hash } = parseUrl(id)
206-
const postfix = (search || '') + (hash || '')
207209
const content = await fsp.readFile(file)
208210

209211
let url
@@ -223,21 +225,26 @@ async function fileToBuiltUrl(
223225
// https://bundlers.tooling.report/hashing/asset-cascade/
224226
// https://github.com/rollup/rollup/issues/3415
225227
const map = assetHashToFilenameMap.get(config)!
226-
227228
const contentHash = getAssetHash(content)
229+
const { search, hash } = parseUrl(id)
230+
const postfix = (search || '') + (hash || '')
231+
const basename = path.basename(file)
232+
const ext = path.extname(basename)
233+
const fileName = path.posix.join(
234+
config.build.assetsDir,
235+
`${basename.slice(0, -ext.length)}.${contentHash}${ext}`
236+
)
228237
if (!map.has(contentHash)) {
229-
const basename = path.basename(file)
230-
const ext = path.extname(basename)
231-
const fileName = path.posix.join(
232-
config.build.assetsDir,
233-
`${basename.slice(0, -ext.length)}.${contentHash}${ext}`
234-
)
235238
map.set(contentHash, fileName)
239+
}
240+
const emittedSet = emittedHashMap.get(config)!
241+
if (!emittedSet.has(contentHash)) {
236242
pluginContext.emitFile({
237243
fileName,
238244
type: 'asset',
239245
source: content
240246
})
247+
emittedSet.add(contentHash)
241248
}
242249

243250
url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}`

packages/vite/src/node/plugins/css.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
223223
* Plugin applied after user plugins
224224
*/
225225
export function cssPostPlugin(config: ResolvedConfig): Plugin {
226-
let styles: Map<string, string>
226+
// styles initialization in buildStart causes a styling loss in watch
227+
const styles: Map<string, string> = new Map<string, string>()
227228
let pureCssChunks: Set<string>
228229

229230
// when there are multiple rollup outputs and extracting CSS, only emit once,
@@ -236,7 +237,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
236237

237238
buildStart() {
238239
// Ensure new caches for every build (i.e. rebuilding in watch mode)
239-
styles = new Map<string, string>()
240240
pureCssChunks = new Set<string>()
241241
outputToExtractedCSSMap = new Map<NormalizedOutputOptions, string>()
242242
},

scripts/jestPerTestSetup.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@ import fs from 'fs-extra'
22
import * as http from 'http'
33
import { resolve, dirname } from 'path'
44
import sirv from 'sirv'
5-
import { createServer, build, ViteDevServer, UserConfig } from 'vite'
5+
import {
6+
createServer,
7+
build,
8+
ViteDevServer,
9+
UserConfig,
10+
PluginOption,
11+
ResolvedConfig
12+
} from 'vite'
613
import { Page } from 'playwright-chromium'
14+
// eslint-disable-next-line node/no-extraneous-import
15+
import { RollupWatcher, RollupWatcherEvent } from 'rollup'
716

817
const isBuildTest = !!process.env.VITE_TEST_BUILD
918

@@ -18,6 +27,7 @@ declare global {
1827
interface Global {
1928
page?: Page
2029
viteTestUrl?: string
30+
watcher?: RollupWatcher
2131
}
2232
}
2333
}
@@ -99,7 +109,22 @@ beforeAll(async () => {
99109
await page.goto(url)
100110
} else {
101111
process.env.VITE_INLINE = 'inline-build'
102-
await build(options)
112+
// determine build watch
113+
let resolvedConfig: ResolvedConfig
114+
const resolvedPlugin: () => PluginOption = () => ({
115+
name: 'vite-plugin-watcher',
116+
configResolved(config) {
117+
resolvedConfig = config
118+
}
119+
})
120+
options.plugins = [resolvedPlugin()]
121+
const rollupOutput = await build(options)
122+
const isWatch = !!resolvedConfig!.build.watch
123+
// in build watch,call startStaticServer after the build is complete
124+
if (isWatch) {
125+
global.watcher = rollupOutput as RollupWatcher
126+
await notifyRebuildComplete(global.watcher)
127+
}
103128
const url = (global.viteTestUrl = await startStaticServer())
104129
await page.goto(url)
105130
}
@@ -168,3 +193,21 @@ function startStaticServer(): Promise<string> {
168193
})
169194
})
170195
}
196+
197+
/**
198+
* Send the rebuild complete message in build watch
199+
*/
200+
export async function notifyRebuildComplete(
201+
watcher: RollupWatcher
202+
): Promise<RollupWatcher> {
203+
let callback: (event: RollupWatcherEvent) => void
204+
await new Promise((resolve, reject) => {
205+
callback = (event) => {
206+
if (event.code === 'END') {
207+
resolve(true)
208+
}
209+
}
210+
watcher.on('event', callback)
211+
})
212+
return watcher.removeListener('event', callback)
213+
}

0 commit comments

Comments
 (0)