Skip to content

Commit 1d6a1eb

Browse files
authoredAug 19, 2022
fix: legacy no resolve asset urls (#9507)
1 parent 21515f1 commit 1d6a1eb

15 files changed

+261
-72
lines changed
 

‎packages/vite/src/node/build.ts

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { watchPackageDataPlugin } from './packages'
5050
import { ensureWatchPlugin } from './plugins/ensureWatch'
5151
import { ESBUILD_MODULES_TARGET, VERSION } from './constants'
5252
import { resolveChokidarOptions } from './watch'
53+
import { completeSystemWrapPlugin } from './plugins/completeSystemWrap'
5354

5455
export interface BuildOptions {
5556
/**
@@ -305,6 +306,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
305306
commonjsOptions?.include.length !== 0
306307
return {
307308
pre: [
309+
completeSystemWrapPlugin(),
308310
...(options.watch ? [ensureWatchPlugin()] : []),
309311
watchPackageDataPlugin(config),
310312
...(usePluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []),
@@ -857,6 +859,7 @@ const relativeUrlMechanisms: Record<
857859
)} : ${getRelativeUrlFromDocument(relativePath)})`,
858860
es: (relativePath) => getResolveUrl(`'${relativePath}', import.meta.url`),
859861
iife: (relativePath) => getRelativeUrlFromDocument(relativePath),
862+
// NOTE: make sure rollup generate `module` params
860863
system: (relativePath) => getResolveUrl(`'${relativePath}', module.meta.url`),
861864
umd: (relativePath) =>
862865
`(typeof document === 'undefined' && typeof location === 'undefined' ? ${getResolveUrl(

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

+79-61
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import path from 'node:path'
22
import { parse as parseUrl } from 'node:url'
33
import fs, { promises as fsp } from 'node:fs'
44
import * as mrmime from 'mrmime'
5-
import type { OutputOptions, PluginContext, PreRenderedAsset } from 'rollup'
5+
import type {
6+
NormalizedOutputOptions,
7+
OutputOptions,
8+
PluginContext,
9+
PreRenderedAsset,
10+
RenderedChunk
11+
} from 'rollup'
612
import MagicString from 'magic-string'
713
import { toOutputFilePathInString } from '../build'
814
import type { Plugin } from '../plugin'
@@ -36,6 +42,76 @@ export function registerCustomMime(): void {
3642
mrmime.mimes['eot'] = 'application/vnd.ms-fontobject'
3743
}
3844

45+
export function renderAssetUrlInJS(
46+
ctx: PluginContext,
47+
config: ResolvedConfig,
48+
chunk: RenderedChunk,
49+
opts: NormalizedOutputOptions,
50+
code: string
51+
): MagicString | undefined {
52+
let match: RegExpExecArray | null
53+
let s: MagicString | undefined
54+
55+
// Urls added with JS using e.g.
56+
// imgElement.src = "__VITE_ASSET__5aa0ddc0__" are using quotes
57+
58+
// Urls added in CSS that is imported in JS end up like
59+
// var inlined = ".inlined{color:green;background:url(__VITE_ASSET__5aa0ddc0__)}\n";
60+
61+
// In both cases, the wrapping should already be fine
62+
63+
while ((match = assetUrlRE.exec(code))) {
64+
s ||= new MagicString(code)
65+
const [full, hash, postfix = ''] = match
66+
// some internal plugins may still need to emit chunks (e.g. worker) so
67+
// fallback to this.getFileName for that. TODO: remove, not needed
68+
const file = getAssetFilename(hash, config) || ctx.getFileName(hash)
69+
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
70+
const filename = file + postfix
71+
const replacement = toOutputFilePathInString(
72+
filename,
73+
'asset',
74+
chunk.fileName,
75+
'js',
76+
config,
77+
opts.format
78+
)
79+
const replacementString =
80+
typeof replacement === 'string'
81+
? JSON.stringify(replacement).slice(1, -1)
82+
: `"+${replacement.runtime}+"`
83+
s.overwrite(match.index, match.index + full.length, replacementString, {
84+
contentOnly: true
85+
})
86+
}
87+
88+
// Replace __VITE_PUBLIC_ASSET__5aa0ddc0__ with absolute paths
89+
90+
const publicAssetUrlMap = publicAssetUrlCache.get(config)!
91+
while ((match = publicAssetUrlRE.exec(code))) {
92+
s ||= new MagicString(code)
93+
const [full, hash] = match
94+
const publicUrl = publicAssetUrlMap.get(hash)!.slice(1)
95+
const replacement = toOutputFilePathInString(
96+
publicUrl,
97+
'public',
98+
chunk.fileName,
99+
'js',
100+
config,
101+
opts.format
102+
)
103+
const replacementString =
104+
typeof replacement === 'string'
105+
? JSON.stringify(replacement).slice(1, -1)
106+
: `"+${replacement.runtime}+"`
107+
s.overwrite(match.index, match.index + full.length, replacementString, {
108+
contentOnly: true
109+
})
110+
}
111+
112+
return s
113+
}
114+
39115
/**
40116
* Also supports loading plain strings with import text from './foo.txt?raw'
41117
*/
@@ -90,66 +166,8 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
90166
return `export default ${JSON.stringify(url)}`
91167
},
92168

93-
renderChunk(code, chunk, outputOptions) {
94-
let match: RegExpExecArray | null
95-
let s: MagicString | undefined
96-
97-
// Urls added with JS using e.g.
98-
// imgElement.src = "__VITE_ASSET__5aa0ddc0__" are using quotes
99-
100-
// Urls added in CSS that is imported in JS end up like
101-
// var inlined = ".inlined{color:green;background:url(__VITE_ASSET__5aa0ddc0__)}\n";
102-
103-
// In both cases, the wrapping should already be fine
104-
105-
while ((match = assetUrlRE.exec(code))) {
106-
s = s || (s = new MagicString(code))
107-
const [full, hash, postfix = ''] = match
108-
// some internal plugins may still need to emit chunks (e.g. worker) so
109-
// fallback to this.getFileName for that. TODO: remove, not needed
110-
const file = getAssetFilename(hash, config) || this.getFileName(hash)
111-
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
112-
const filename = file + postfix
113-
const replacement = toOutputFilePathInString(
114-
filename,
115-
'asset',
116-
chunk.fileName,
117-
'js',
118-
config,
119-
outputOptions.format
120-
)
121-
const replacementString =
122-
typeof replacement === 'string'
123-
? JSON.stringify(replacement).slice(1, -1)
124-
: `"+${replacement.runtime}+"`
125-
s.overwrite(match.index, match.index + full.length, replacementString, {
126-
contentOnly: true
127-
})
128-
}
129-
130-
// Replace __VITE_PUBLIC_ASSET__5aa0ddc0__ with absolute paths
131-
132-
const publicAssetUrlMap = publicAssetUrlCache.get(config)!
133-
while ((match = publicAssetUrlRE.exec(code))) {
134-
s = s || (s = new MagicString(code))
135-
const [full, hash] = match
136-
const publicUrl = publicAssetUrlMap.get(hash)!.slice(1)
137-
const replacement = toOutputFilePathInString(
138-
publicUrl,
139-
'public',
140-
chunk.fileName,
141-
'js',
142-
config,
143-
outputOptions.format
144-
)
145-
const replacementString =
146-
typeof replacement === 'string'
147-
? JSON.stringify(replacement).slice(1, -1)
148-
: `"+${replacement.runtime}+"`
149-
s.overwrite(match.index, match.index + full.length, replacementString, {
150-
contentOnly: true
151-
})
152-
}
169+
renderChunk(code, chunk, opts) {
170+
const s = renderAssetUrlInJS(this, config, chunk, opts, code)
153171

154172
if (s) {
155173
return {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Plugin } from '../plugin'
2+
3+
/**
4+
* make sure systemjs register wrap to had complete parameters in system format
5+
*/
6+
export function completeSystemWrapPlugin(): Plugin {
7+
const SystemJSWrapRE = /System.register\(.*\((exports)\)/g
8+
9+
return {
10+
name: 'vite:force-systemjs-wrap-complete',
11+
12+
renderChunk(code, chunk, opts) {
13+
if (opts.format === 'system') {
14+
return {
15+
code: code.replace(SystemJSWrapRE, (s, s1) =>
16+
s.replace(s1, 'exports, module')
17+
),
18+
map: null
19+
}
20+
}
21+
}
22+
}
23+
}

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

+17-9
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
publicAssetUrlCache,
5656
publicAssetUrlRE,
5757
publicFileToBuiltUrl,
58+
renderAssetUrlInJS,
5859
resolveAssetFileNames
5960
} from './asset'
6061
import type { ESBuildOptions } from './esbuild'
@@ -556,27 +557,34 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
556557
})
557558
chunk.viteMetadata.importedCss.add(this.getFileName(fileHandle))
558559
} else if (!config.build.ssr) {
559-
// legacy build and inline css
560-
561-
// __VITE_ASSET__ and __VITE_PUBLIC_ASSET__ urls are processed by
562-
// the vite:asset plugin, don't call resolveAssetUrlsInCss here
563560
chunkCSS = await finalizeCss(chunkCSS, true, config)
564-
561+
let cssString = JSON.stringify(chunkCSS)
562+
cssString =
563+
renderAssetUrlInJS(
564+
this,
565+
config,
566+
chunk,
567+
opts,
568+
cssString
569+
)?.toString() || cssString
565570
const style = `__vite_style__`
566571
const injectCode =
567572
`var ${style} = document.createElement('style');` +
568-
`${style}.innerHTML = ${JSON.stringify(chunkCSS)};` +
573+
`${style}.innerHTML = ${cssString};` +
569574
`document.head.appendChild(${style});`
575+
const wrapIdx = code.indexOf('System.register')
576+
const insertMark = "'use strict';"
577+
const insertIdx = code.indexOf(insertMark, wrapIdx)
578+
const s = new MagicString(code)
579+
s.appendLeft(insertIdx + insertMark.length, injectCode)
570580
if (config.build.sourcemap) {
571-
const s = new MagicString(code)
572-
s.prepend(injectCode)
573581
// resolve public URL from CSS paths, we need to use absolute paths
574582
return {
575583
code: s.toString(),
576584
map: s.generateMap({ hires: true })
577585
}
578586
} else {
579-
return { code: injectCode + code }
587+
return { code: s.toString() }
580588
}
581589
}
582590
} else {

‎playground/vitestSetup.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,15 @@ export async function startDefaultServe(): Promise<void> {
243243
watcher = rollupOutput as RollupWatcher
244244
await notifyRebuildComplete(watcher)
245245
}
246-
viteTestUrl = await startStaticServer(config)
246+
viteTestUrl = await startStaticServer(resolvedConfig, config)
247247
await page.goto(viteTestUrl)
248248
}
249249
}
250250

251-
function startStaticServer(config?: InlineConfig): Promise<string> {
251+
function startStaticServer(
252+
resolved: ResolvedConfig,
253+
config?: InlineConfig
254+
): Promise<string> {
252255
if (!config) {
253256
// check if the test project has base config
254257
const configFile = resolve(rootDir, 'vite.config.js')
@@ -267,6 +270,10 @@ function startStaticServer(config?: InlineConfig): Promise<string> {
267270
if (config && config.__test__) {
268271
// @ts-ignore
269272
config.__test__()
273+
// @ts-ignore
274+
} else if (resolved && resolved.__test__) {
275+
// @ts-ignore
276+
resolved.__test__()
270277
}
271278

272279
// start static file server

‎playground/vue-legacy/Main.vue

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
import { defineComponent, defineAsyncComponent } from 'vue'
3+
import css from './inline.css?inline'
4+
5+
export default defineComponent({
6+
components: {
7+
module: defineAsyncComponent(() => import('./module.vue'))
8+
},
9+
setup() {
10+
return {
11+
css
12+
}
13+
}
14+
})
15+
</script>
16+
<template>
17+
<div class="main">
18+
<p>Main.vue</p>
19+
<module />
20+
<code>
21+
{{ css }}
22+
</code>
23+
</div>
24+
</template>
25+
<style scoped>
26+
.main {
27+
height: 100vh;
28+
background: url('@/assets/asset.png') no-repeat;
29+
background-size: contain;
30+
background-position: center;
31+
}
32+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from 'vitest'
2+
import { getBg, untilUpdated } from '~utils'
3+
4+
test('vue legacy assets', async () => {
5+
await untilUpdated(() => getBg('.main'), 'assets/asset', true)
6+
})
7+
8+
test('async vue legacy assets', async () => {
9+
await untilUpdated(() => getBg('.module'), 'assets/asset', true)
10+
})
12.5 KB
Loading

‎playground/vue-legacy/env.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module '*.png'

‎playground/vue-legacy/index.html

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div id="app" style="background: 'white'"></div>
2+
<script type="module">
3+
import { createApp } from 'vue'
4+
import App from './Main.vue'
5+
6+
createApp(App).mount('#app')
7+
</script>

‎playground/vue-legacy/inline.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.inline-css {
2+
color: #0088ff;
3+
}

‎playground/vue-legacy/module.vue

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<div class="module">
3+
<p>module.vue</p>
4+
</div>
5+
</template>
6+
<style scoped>
7+
.module {
8+
height: 100vh;
9+
background: url('@/assets/asset.png') no-repeat;
10+
background-size: contain;
11+
background-position: center;
12+
}
13+
</style>

‎playground/vue-legacy/package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "test-vue-legacy",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"vue": "^3.2.37"
13+
},
14+
"devDependencies": {
15+
"@vitejs/plugin-vue": "workspace:*",
16+
"@vitejs/plugin-legacy": "workspace:*"
17+
}
18+
}

‎playground/vue-legacy/vite.config.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import path from 'node:path'
2+
import fs from 'node:fs'
3+
import { defineConfig } from 'vite'
4+
import vuePlugin from '@vitejs/plugin-vue'
5+
import legacyPlugin from '@vitejs/plugin-legacy'
6+
7+
export default defineConfig({
8+
base: '',
9+
resolve: {
10+
alias: {
11+
'@': __dirname
12+
}
13+
},
14+
plugins: [
15+
legacyPlugin({
16+
targets: ['defaults', 'not IE 11', 'chrome > 48']
17+
}),
18+
vuePlugin()
19+
],
20+
build: {
21+
minify: false
22+
},
23+
// special test only hook
24+
// for tests, remove `<script type="module">` tags and remove `nomodule`
25+
// attrs so that we run the legacy bundle instead.
26+
// @ts-ignore
27+
__test__() {
28+
const indexPath = path.resolve(__dirname, './dist/index.html')
29+
let index = fs.readFileSync(indexPath, 'utf-8')
30+
index = index
31+
.replace(/<script type="module".*?<\/script>/g, '')
32+
.replace(/<script nomodule/g, '<script')
33+
fs.writeFileSync(indexPath, index)
34+
}
35+
})

‎pnpm-lock.yaml

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.