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!: relative base #7644

Merged
merged 39 commits into from May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cb42862
feat: relative base
patak-dev Apr 7, 2022
bdacc9f
fix: don't use template string in dev
patak-dev Apr 7, 2022
7e4484a
fix: windows
patak-dev Apr 8, 2022
7744ca3
chore: stringifyAsTemplateLiteral util
patak-dev Apr 8, 2022
3bd450d
chore: typo
patak-dev Apr 13, 2022
2db74e8
chore: merge main
patak-dev Apr 13, 2022
1491bb9
chore: remove unused regex
patak-dev Apr 14, 2022
77a05e3
fix: resolve worker TODO
patak-dev Apr 15, 2022
69d872f
fix: apply fix by @locriacyber
patak-dev Apr 29, 2022
41d9884
chore: typo
patak-dev Apr 29, 2022
d69f214
chore: merge main
patak-dev May 9, 2022
1d955db
chore: merge main
patak-dev May 9, 2022
80dacfb
chore: update
patak-dev May 9, 2022
e1f49c7
refactor: back to JSON.stringify
patak-dev May 12, 2022
a0b232c
chore: merge main
patak-dev May 12, 2022
2cbd9ef
chore: update
patak-dev May 12, 2022
a4cef7e
chore: comment
patak-dev May 12, 2022
51c863c
chore: merge main and refactor worker with relative base
patak-dev May 13, 2022
26eb46d
chore: update
patak-dev May 13, 2022
638945c
chore: update
patak-dev May 13, 2022
9681aca
chore: allow custom assetFileNames
patak-dev May 13, 2022
a354401
feat: support custom file names in module preload
patak-dev May 14, 2022
b2a9967
chore: remove unneded worker options formatting
patak-dev May 14, 2022
cba14d8
fix: rework assets and worker assets handling
patak-dev May 14, 2022
f61c30e
fix: import.meta.url param position
patak-dev May 15, 2022
7f7fbf9
chore: use absolute URL for assets in CSS
patak-dev May 15, 2022
c3d0821
fix: worker hash to url cache
patak-dev May 17, 2022
77e1f39
feat: absolute public urls in JS, asset paths in CSS
patak-dev May 17, 2022
7ee53f6
test: add relative base asset test cases
patak-dev May 17, 2022
96bc8c5
test: fix serve
patak-dev May 17, 2022
8afe78e
test: bump assetsInlineLimit to 8kb
patak-dev May 17, 2022
3a09a2c
chore: improve asset paths in CSS handling
patak-dev May 17, 2022
79240a7
fix: relative CSS path
patak-dev May 17, 2022
46c15af
feat: support assetFileNames functional form
patak-dev May 17, 2022
7629352
chore: naming
patak-dev May 17, 2022
f785bed
test: worker with relative base
patak-dev May 17, 2022
b5b0a84
chore: merge main
patak-dev May 17, 2022
7f7d763
test: skip invalid serve tests for relative base
patak-dev May 17, 2022
277f967
fix: urls in windows
patak-dev May 17, 2022
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
10 changes: 5 additions & 5 deletions packages/plugin-legacy/src/index.ts
Expand Up @@ -37,9 +37,9 @@ const legacyPolyfillId = 'vite-legacy-polyfill'
const legacyEntryId = 'vite-legacy-entry'
const systemJSInlineCode = `System.import(document.getElementById('${legacyEntryId}').getAttribute('data-src'))`

const detectDynamicImportVarName = '__vite_is_dynamic_import_support'
const detectDynamicImportCode = `try{import("_").catch(()=>1);}catch(e){}window.${detectDynamicImportVarName}=true;`
const dynamicFallbackInlineCode = `!function(){if(window.${detectDynamicImportVarName})return;console.warn("vite: loading legacy build because dynamic import is unsupported, syntax error above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}();`
const detectModernBrowserVarName = '__vite_is_modern_browser'
const detectModernBrowserCode = `try{import(new URL(import.meta.url).href).catch(()=>1);}catch(e){}window.${detectModernBrowserVarName}=true;`
const dynamicFallbackInlineCode = `!function(){if(window.${detectModernBrowserVarName})return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}();`

const forceDynamicImportUsage = `export function __vite_legacy_guard(){import('data:text/javascript,')};`

Expand Down Expand Up @@ -438,7 +438,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] {
tags.push({
tag: 'script',
attrs: { type: 'module' },
children: detectDynamicImportCode,
children: detectModernBrowserCode,
injectTo: 'head'
})
tags.push({
Expand Down Expand Up @@ -686,7 +686,7 @@ function wrapIIFEBabelPlugin(): BabelPlugin {
export const cspHashes = [
createHash('sha256').update(safari10NoModuleFix).digest('base64'),
createHash('sha256').update(systemJSInlineCode).digest('base64'),
createHash('sha256').update(detectDynamicImportCode).digest('base64'),
createHash('sha256').update(detectModernBrowserCode).digest('base64'),
createHash('sha256').update(dynamicFallbackInlineCode).digest('base64')
]

Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/build.ts
Expand Up @@ -245,11 +245,11 @@ export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions {
// Support browserslist
// "defaults and supports es6-module and supports es6-module-dynamic-import",
resolved.target = [
'es2019',
'es2020', // support import.meta.url
'edge88',
'firefox78',
'chrome87',
'safari13.1'
'safari13' // transpile nullish coalescing
]
} else if (resolved.target === 'esnext' && resolved.minify === 'terser') {
// esnext + terser: limit to es2019 so it can be minified by terser
Expand Down
63 changes: 57 additions & 6 deletions packages/vite/src/node/plugins/asset.ts
Expand Up @@ -6,7 +6,7 @@ import type { OutputOptions, PluginContext } from 'rollup'
import MagicString from 'magic-string'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { cleanUrl, getHash, normalizePath } from '../utils'
import { cleanUrl, getHash, isRelativeBase, normalizePath } from '../utils'
import { FS_PREFIX } from '../constants'

export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g
Expand All @@ -29,6 +29,7 @@ const emittedHashMap = new WeakMap<ResolvedConfig, Set<string>>()
export function assetPlugin(config: ResolvedConfig): Plugin {
// assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined
assetHashToFilenameMap.set(config, new Map())
const relativeBase = isRelativeBase(config.base)

// add own dictionary entry by directly assigning mrmine
// https://github.com/lukeed/mrmime/issues/3
Expand Down Expand Up @@ -82,8 +83,13 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
let match: RegExpExecArray | null
let s: MagicString | undefined

const absoluteUrlPathInterpolation = (filename: string) =>
`"+new URL(${JSON.stringify(
path.posix.relative(path.dirname(chunk.fileName), filename)
)},import.meta.url).href+"`

// Urls added with JS using e.g.
// imgElement.src = "my/file.png" are using quotes
// 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";
Expand All @@ -94,15 +100,33 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
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.
// 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 outputFilepath = config.base + file + postfix
const filename = file + postfix
const outputFilepath = relativeBase
? absoluteUrlPathInterpolation(filename)
: JSON.stringify(config.base + filename).slice(1, -1)
s.overwrite(match.index, match.index + full.length, outputFilepath, {
contentOnly: true
})
}

// Replace __VITE_PUBLIC_ASSET__5aa0ddc0__ with absolute paths

if (relativeBase) {
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)!
const replacement = absoluteUrlPathInterpolation(publicUrl.slice(1))
s.overwrite(match.index, match.index + full.length, replacement, {
contentOnly: true
})
}
}

if (s) {
return {
code: s.toString(),
Expand Down Expand Up @@ -258,6 +282,33 @@ export function assetFileNamesToFileName(
return fileName
}

export const publicAssetUrlCache = new WeakMap<
ResolvedConfig,
// hash -> url
Map<string, string>
>()

export const publicAssetUrlRE = /__VITE_PUBLIC_ASSET__([a-z\d]{8})__/g

export function publicFileToBuiltUrl(
url: string,
config: ResolvedConfig
): string {
if (!isRelativeBase(config.base)) {
return config.base + url.slice(1)
}
const hash = getHash(url)
let cache = publicAssetUrlCache.get(config)
if (!cache) {
cache = new Map<string, string>()
publicAssetUrlCache.set(config, cache)
}
if (!cache.get(hash)) {
cache.set(hash, url)
}
return `__VITE_PUBLIC_ASSET__${hash}__`
}

/**
* Register an asset to be emitted as part of the bundle (if necessary)
* and returns the resolved public URL
Expand All @@ -269,7 +320,7 @@ async function fileToBuiltUrl(
skipPublicCheck = false
): Promise<string> {
if (!skipPublicCheck && checkPublicFile(id, config)) {
return config.base + id.slice(1)
return publicFileToBuiltUrl(id, config)
}

const cache = assetCache.get(config)!
Expand Down Expand Up @@ -342,7 +393,7 @@ export async function urlToBuiltUrl(
pluginContext: PluginContext
): Promise<string> {
if (checkPublicFile(url, config)) {
return config.base + url.slice(1)
return publicFileToBuiltUrl(url, config)
}
const file = url.startsWith('/')
? path.join(config.root, url)
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/plugins/assetImportMetaUrl.ts
Expand Up @@ -4,6 +4,7 @@ import { stripLiteral } from 'strip-literal'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { fileToUrl } from './asset'
import { preloadHelperId } from './importAnalysisBuild'

/**
* Convert `new URL('./foo.png', import.meta.url)` to its resolved built URL
Expand All @@ -21,6 +22,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
async transform(code, id, options) {
if (
!options?.ssr &&
id !== preloadHelperId &&
code.includes('new URL') &&
code.includes(`import.meta.url`)
) {
Expand Down
124 changes: 83 additions & 41 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -38,6 +38,7 @@ import {
isDataUrl,
isExternalUrl,
isObject,
isRelativeBase,
normalizePath,
parseRequest,
processSrcSet
Expand All @@ -48,7 +49,10 @@ import {
assetUrlRE,
checkPublicFile,
fileToUrl,
getAssetFilename
getAssetFilename,
publicAssetUrlCache,
publicAssetUrlRE,
publicFileToBuiltUrl
} from './asset'

// const debug = createDebugger('vite:css')
Expand Down Expand Up @@ -106,6 +110,8 @@ const inlineCSSRE = /(\?|&)inline-css\b/
const usedRE = /(\?|&)used\b/
const varRE = /^var\(/i

const cssBundleName = 'style.css'

const enum PreprocessLang {
less = 'less',
sass = 'sass',
Expand Down Expand Up @@ -183,7 +189,11 @@ export function cssPlugin(config: ResolvedConfig): Plugin {

const urlReplacer: CssUrlReplacer = async (url, importer) => {
if (checkPublicFile(url, config)) {
return config.base + url.slice(1)
if (isRelativeBase(config.base)) {
return publicFileToBuiltUrl(url, config)
} else {
return config.base + url.slice(1)
}
}
const resolved = await resolveUrl(url, importer)
if (resolved) {
Expand Down Expand Up @@ -283,6 +293,30 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
let outputToExtractedCSSMap: Map<NormalizedOutputOptions, string>
let hasEmitted = false

const relativeBase = isRelativeBase(config.base)

const rollupOptionsOutput = config.build.rollupOptions.output
const assetFileNames = (
Array.isArray(rollupOptionsOutput)
? rollupOptionsOutput[0]
: rollupOptionsOutput
)?.assetFileNames
const getCssAssetDirname = (cssAssetName: string) => {
if (!assetFileNames) {
return config.build.assetsDir
} else if (typeof assetFileNames === 'string') {
return path.dirname(assetFileNames)
} else {
return path.dirname(
assetFileNames({
name: cssAssetName,
type: 'asset',
source: '/* vite internal call, ignore */'
})
)
}
}

return {
name: 'vite:css-post',

Expand Down Expand Up @@ -415,35 +449,42 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
return null
}

// resolve asset URL placeholders to their built file URLs and perform
// minification if necessary
const processChunkCSS = async (
css: string,
{
inlined,
minify
}: {
inlined: boolean
minify: boolean
}
) => {
const publicAssetUrlMap = publicAssetUrlCache.get(config)!

// resolve asset URL placeholders to their built file URLs
function resolveAssetUrlsInCss(chunkCSS: string, cssAssetName: string) {
const cssAssetDirname = relativeBase
? getCssAssetDirname(cssAssetName)
: undefined

// replace asset url references with resolved url.
const isRelativeBase = config.base === '' || config.base.startsWith('.')
css = css.replace(assetUrlRE, (_, fileHash, postfix = '') => {
chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash, postfix = '') => {
const filename = getAssetFilename(fileHash, config) + postfix
chunk.viteMetadata.importedAssets.add(cleanUrl(filename))
if (!isRelativeBase || inlined) {
// absolute base or relative base but inlined (injected as style tag into
// index.html) use the base as-is
return config.base + filename
if (relativeBase) {
// relative base + extracted CSS
const relativePath = path.posix.relative(cssAssetDirname!, filename)
return relativePath.startsWith('.')
? relativePath
: './' + relativePath
} else {
// relative base + extracted CSS - asset file will be in the same dir
return `./${path.posix.basename(filename)}`
// absolute base
return config.base + filename
}
})
// only external @imports and @charset should exist at this point
css = await finalizeCss(css, minify, config)
return css
// resolve public URL from CSS paths
if (relativeBase) {
const relativePathToPublicFromCSS = path.posix.relative(
cssAssetDirname!,
''
)
chunkCSS = chunkCSS.replace(
publicAssetUrlRE,
(_, hash) =>
relativePathToPublicFromCSS + publicAssetUrlMap.get(hash)!
)
}
return chunkCSS
}

if (config.build.cssCodeSplit) {
Expand All @@ -456,23 +497,25 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
opts.format === 'cjs' ||
opts.format === 'system'
) {
chunkCSS = await processChunkCSS(chunkCSS, {
inlined: false,
minify: true
})
const cssAssetName = chunk.name + '.css'

chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssAssetName)
chunkCSS = await finalizeCss(chunkCSS, true, config)

// emit corresponding css file
const fileHandle = this.emitFile({
name: chunk.name + '.css',
name: cssAssetName,
type: 'asset',
source: chunkCSS
})
chunk.viteMetadata.importedCss.add(this.getFileName(fileHandle))
} else if (!config.build.ssr) {
// legacy build, inline css
chunkCSS = await processChunkCSS(chunkCSS, {
inlined: true,
minify: true
})
// 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)

const style = `__vite_style__`
const injectCode =
`var ${style} = document.createElement('style');` +
Expand All @@ -481,6 +524,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
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 })
Expand All @@ -490,11 +534,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
}
}
} else {
// non-split extracted CSS will be minified together
chunkCSS = await processChunkCSS(chunkCSS, {
inlined: false,
minify: false
})
chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssBundleName)
// finalizeCss is called for the aggregated chunk in generateBundle

patak-dev marked this conversation as resolved.
Show resolved Hide resolved
outputToExtractedCSSMap.set(
opts,
(outputToExtractedCSSMap.get(opts) || '') + chunkCSS
Expand Down Expand Up @@ -558,7 +600,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
hasEmitted = true
extractedCss = await finalizeCss(extractedCss, true, config)
this.emitFile({
name: 'style.css',
name: cssBundleName,
type: 'asset',
source: extractedCss
})
Expand Down