Skip to content

Commit

Permalink
fix: legacy no resolve asset urls (#9507)
Browse files Browse the repository at this point in the history
  • Loading branch information
poyoho committed Aug 19, 2022
1 parent 21515f1 commit 1d6a1eb
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 72 deletions.
3 changes: 3 additions & 0 deletions packages/vite/src/node/build.ts
Expand Up @@ -50,6 +50,7 @@ import { watchPackageDataPlugin } from './packages'
import { ensureWatchPlugin } from './plugins/ensureWatch'
import { ESBUILD_MODULES_TARGET, VERSION } from './constants'
import { resolveChokidarOptions } from './watch'
import { completeSystemWrapPlugin } from './plugins/completeSystemWrap'

export interface BuildOptions {
/**
Expand Down Expand Up @@ -305,6 +306,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
commonjsOptions?.include.length !== 0
return {
pre: [
completeSystemWrapPlugin(),
...(options.watch ? [ensureWatchPlugin()] : []),
watchPackageDataPlugin(config),
...(usePluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []),
Expand Down Expand Up @@ -857,6 +859,7 @@ const relativeUrlMechanisms: Record<
)} : ${getRelativeUrlFromDocument(relativePath)})`,
es: (relativePath) => getResolveUrl(`'${relativePath}', import.meta.url`),
iife: (relativePath) => getRelativeUrlFromDocument(relativePath),
// NOTE: make sure rollup generate `module` params
system: (relativePath) => getResolveUrl(`'${relativePath}', module.meta.url`),
umd: (relativePath) =>
`(typeof document === 'undefined' && typeof location === 'undefined' ? ${getResolveUrl(
Expand Down
140 changes: 79 additions & 61 deletions packages/vite/src/node/plugins/asset.ts
Expand Up @@ -2,7 +2,13 @@ import path from 'node:path'
import { parse as parseUrl } from 'node:url'
import fs, { promises as fsp } from 'node:fs'
import * as mrmime from 'mrmime'
import type { OutputOptions, PluginContext, PreRenderedAsset } from 'rollup'
import type {
NormalizedOutputOptions,
OutputOptions,
PluginContext,
PreRenderedAsset,
RenderedChunk
} from 'rollup'
import MagicString from 'magic-string'
import { toOutputFilePathInString } from '../build'
import type { Plugin } from '../plugin'
Expand Down Expand Up @@ -36,6 +42,76 @@ export function registerCustomMime(): void {
mrmime.mimes['eot'] = 'application/vnd.ms-fontobject'
}

export function renderAssetUrlInJS(
ctx: PluginContext,
config: ResolvedConfig,
chunk: RenderedChunk,
opts: NormalizedOutputOptions,
code: string
): MagicString | undefined {
let match: RegExpExecArray | null
let s: MagicString | undefined

// Urls added with JS using e.g.
// imgElement.src = "__VITE_ASSET__5aa0ddc0__" are using quotes

// Urls added in CSS that is imported in JS end up like
// var inlined = ".inlined{color:green;background:url(__VITE_ASSET__5aa0ddc0__)}\n";

// In both cases, the wrapping should already be fine

while ((match = assetUrlRE.exec(code))) {
s ||= new MagicString(code)
const [full, hash, postfix = ''] = match
// some internal plugins may still need to emit chunks (e.g. worker) so
// fallback to this.getFileName for that. TODO: remove, not needed
const file = getAssetFilename(hash, config) || ctx.getFileName(hash)
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
const filename = file + postfix
const replacement = toOutputFilePathInString(
filename,
'asset',
chunk.fileName,
'js',
config,
opts.format
)
const replacementString =
typeof replacement === 'string'
? JSON.stringify(replacement).slice(1, -1)
: `"+${replacement.runtime}+"`
s.overwrite(match.index, match.index + full.length, replacementString, {
contentOnly: true
})
}

// Replace __VITE_PUBLIC_ASSET__5aa0ddc0__ with absolute paths

const publicAssetUrlMap = publicAssetUrlCache.get(config)!
while ((match = publicAssetUrlRE.exec(code))) {
s ||= new MagicString(code)
const [full, hash] = match
const publicUrl = publicAssetUrlMap.get(hash)!.slice(1)
const replacement = toOutputFilePathInString(
publicUrl,
'public',
chunk.fileName,
'js',
config,
opts.format
)
const replacementString =
typeof replacement === 'string'
? JSON.stringify(replacement).slice(1, -1)
: `"+${replacement.runtime}+"`
s.overwrite(match.index, match.index + full.length, replacementString, {
contentOnly: true
})
}

return s
}

/**
* Also supports loading plain strings with import text from './foo.txt?raw'
*/
Expand Down Expand Up @@ -90,66 +166,8 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
return `export default ${JSON.stringify(url)}`
},

renderChunk(code, chunk, outputOptions) {
let match: RegExpExecArray | null
let s: MagicString | undefined

// Urls added with JS using e.g.
// imgElement.src = "__VITE_ASSET__5aa0ddc0__" are using quotes

// Urls added in CSS that is imported in JS end up like
// var inlined = ".inlined{color:green;background:url(__VITE_ASSET__5aa0ddc0__)}\n";

// In both cases, the wrapping should already be fine

while ((match = assetUrlRE.exec(code))) {
s = s || (s = new MagicString(code))
const [full, hash, postfix = ''] = match
// some internal plugins may still need to emit chunks (e.g. worker) so
// fallback to this.getFileName for that. TODO: remove, not needed
const file = getAssetFilename(hash, config) || this.getFileName(hash)
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
const filename = file + postfix
const replacement = toOutputFilePathInString(
filename,
'asset',
chunk.fileName,
'js',
config,
outputOptions.format
)
const replacementString =
typeof replacement === 'string'
? JSON.stringify(replacement).slice(1, -1)
: `"+${replacement.runtime}+"`
s.overwrite(match.index, match.index + full.length, replacementString, {
contentOnly: true
})
}

// Replace __VITE_PUBLIC_ASSET__5aa0ddc0__ with absolute paths

const publicAssetUrlMap = publicAssetUrlCache.get(config)!
while ((match = publicAssetUrlRE.exec(code))) {
s = s || (s = new MagicString(code))
const [full, hash] = match
const publicUrl = publicAssetUrlMap.get(hash)!.slice(1)
const replacement = toOutputFilePathInString(
publicUrl,
'public',
chunk.fileName,
'js',
config,
outputOptions.format
)
const replacementString =
typeof replacement === 'string'
? JSON.stringify(replacement).slice(1, -1)
: `"+${replacement.runtime}+"`
s.overwrite(match.index, match.index + full.length, replacementString, {
contentOnly: true
})
}
renderChunk(code, chunk, opts) {
const s = renderAssetUrlInJS(this, config, chunk, opts, code)

if (s) {
return {
Expand Down
23 changes: 23 additions & 0 deletions packages/vite/src/node/plugins/completeSystemWrap.ts
@@ -0,0 +1,23 @@
import type { Plugin } from '../plugin'

/**
* make sure systemjs register wrap to had complete parameters in system format
*/
export function completeSystemWrapPlugin(): Plugin {
const SystemJSWrapRE = /System.register\(.*\((exports)\)/g

return {
name: 'vite:force-systemjs-wrap-complete',

renderChunk(code, chunk, opts) {
if (opts.format === 'system') {
return {
code: code.replace(SystemJSWrapRE, (s, s1) =>
s.replace(s1, 'exports, module')
),
map: null
}
}
}
}
}
26 changes: 17 additions & 9 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -55,6 +55,7 @@ import {
publicAssetUrlCache,
publicAssetUrlRE,
publicFileToBuiltUrl,
renderAssetUrlInJS,
resolveAssetFileNames
} from './asset'
import type { ESBuildOptions } from './esbuild'
Expand Down Expand Up @@ -556,27 +557,34 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
})
chunk.viteMetadata.importedCss.add(this.getFileName(fileHandle))
} else if (!config.build.ssr) {
// legacy build and inline css

// __VITE_ASSET__ and __VITE_PUBLIC_ASSET__ urls are processed by
// the vite:asset plugin, don't call resolveAssetUrlsInCss here
chunkCSS = await finalizeCss(chunkCSS, true, config)

let cssString = JSON.stringify(chunkCSS)
cssString =
renderAssetUrlInJS(
this,
config,
chunk,
opts,
cssString
)?.toString() || cssString
const style = `__vite_style__`
const injectCode =
`var ${style} = document.createElement('style');` +
`${style}.innerHTML = ${JSON.stringify(chunkCSS)};` +
`${style}.innerHTML = ${cssString};` +
`document.head.appendChild(${style});`
const wrapIdx = code.indexOf('System.register')
const insertMark = "'use strict';"
const insertIdx = code.indexOf(insertMark, wrapIdx)
const s = new MagicString(code)
s.appendLeft(insertIdx + insertMark.length, injectCode)
if (config.build.sourcemap) {
const s = new MagicString(code)
s.prepend(injectCode)
// resolve public URL from CSS paths, we need to use absolute paths
return {
code: s.toString(),
map: s.generateMap({ hires: true })
}
} else {
return { code: injectCode + code }
return { code: s.toString() }
}
}
} else {
Expand Down
11 changes: 9 additions & 2 deletions playground/vitestSetup.ts
Expand Up @@ -243,12 +243,15 @@ export async function startDefaultServe(): Promise<void> {
watcher = rollupOutput as RollupWatcher
await notifyRebuildComplete(watcher)
}
viteTestUrl = await startStaticServer(config)
viteTestUrl = await startStaticServer(resolvedConfig, config)
await page.goto(viteTestUrl)
}
}

function startStaticServer(config?: InlineConfig): Promise<string> {
function startStaticServer(
resolved: ResolvedConfig,
config?: InlineConfig
): Promise<string> {
if (!config) {
// check if the test project has base config
const configFile = resolve(rootDir, 'vite.config.js')
Expand All @@ -267,6 +270,10 @@ function startStaticServer(config?: InlineConfig): Promise<string> {
if (config && config.__test__) {
// @ts-ignore
config.__test__()
// @ts-ignore
} else if (resolved && resolved.__test__) {
// @ts-ignore
resolved.__test__()
}

// start static file server
Expand Down
32 changes: 32 additions & 0 deletions playground/vue-legacy/Main.vue
@@ -0,0 +1,32 @@
<script>
import { defineComponent, defineAsyncComponent } from 'vue'
import css from './inline.css?inline'
export default defineComponent({
components: {
module: defineAsyncComponent(() => import('./module.vue'))
},
setup() {
return {
css
}
}
})
</script>
<template>
<div class="main">
<p>Main.vue</p>
<module />
<code>
{{ css }}
</code>
</div>
</template>
<style scoped>
.main {
height: 100vh;
background: url('@/assets/asset.png') no-repeat;
background-size: contain;
background-position: center;
}
</style>
10 changes: 10 additions & 0 deletions playground/vue-legacy/__tests__/vue-legacy.spec.ts
@@ -0,0 +1,10 @@
import { test } from 'vitest'
import { getBg, untilUpdated } from '~utils'

test('vue legacy assets', async () => {
await untilUpdated(() => getBg('.main'), 'assets/asset', true)
})

test('async vue legacy assets', async () => {
await untilUpdated(() => getBg('.module'), 'assets/asset', true)
})
Binary file added playground/vue-legacy/assets/asset.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions playground/vue-legacy/env.d.ts
@@ -0,0 +1 @@
declare module '*.png'
7 changes: 7 additions & 0 deletions playground/vue-legacy/index.html
@@ -0,0 +1,7 @@
<div id="app" style="background: 'white'"></div>
<script type="module">
import { createApp } from 'vue'
import App from './Main.vue'

createApp(App).mount('#app')
</script>
3 changes: 3 additions & 0 deletions playground/vue-legacy/inline.css
@@ -0,0 +1,3 @@
.inline-css {
color: #0088ff;
}
13 changes: 13 additions & 0 deletions playground/vue-legacy/module.vue
@@ -0,0 +1,13 @@
<template>
<div class="module">
<p>module.vue</p>
</div>
</template>
<style scoped>
.module {
height: 100vh;
background: url('@/assets/asset.png') no-repeat;
background-size: contain;
background-position: center;
}
</style>
18 changes: 18 additions & 0 deletions playground/vue-legacy/package.json
@@ -0,0 +1,18 @@
{
"name": "test-vue-legacy",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.2.37"
},
"devDependencies": {
"@vitejs/plugin-vue": "workspace:*",
"@vitejs/plugin-legacy": "workspace:*"
}
}

0 comments on commit 1d6a1eb

Please sign in to comment.