Skip to content

Commit 895a7d6

Browse files
authored
feat: experimental.renderBuiltUrl (revised build base options) (#8762)
1 parent d90409e commit 895a7d6

File tree

13 files changed

+415
-347
lines changed

13 files changed

+415
-347
lines changed

docs/guide/build.md

+19-30
Original file line numberDiff line numberDiff line change
@@ -197,45 +197,34 @@ A user may choose to deploy in three different paths:
197197
- The generated hashed assets (JS, CSS, and other file types like images)
198198
- The copied [public files](assets.md#the-public-directory)
199199

200-
A single static [base](#public-base-path) isn't enough in these scenarios. Vite provides experimental support for advanced base options during build, using `experimental.buildAdvancedBaseOptions`.
200+
A single static [base](#public-base-path) isn't enough in these scenarios. Vite provides experimental support for advanced base options during build, using `experimental.renderBuiltUrl`.
201201

202202
```js
203-
experimental: {
204-
buildAdvancedBaseOptions: {
205-
// Same as base: './'
206-
// type: boolean, default: false
207-
relative: true
208-
// Static base
209-
// type: string, default: undefined
210-
url: 'https://cdn.domain.com/'
211-
// Dynamic base to be used for paths inside JS
212-
// type: (url: string) => string, default: undefined
213-
runtime: (url: string) => `window.__toCdnUrl(${url})`
214-
},
203+
experimental: {
204+
renderBuiltUrl: (filename: string, { hostType: 'js' | 'css' | 'html' }) => {
205+
if (hostType === 'js') {
206+
return { runtime: `window.__toCdnUrl(${JSON.stringify(filename)})` }
207+
} else {
208+
return { relative: true }
209+
}
215210
}
211+
}
216212
```
217213

218-
When `runtime` is defined, it will be used for hashed assets and public files paths inside JS assets. Inside CSS and HTML generated files, paths will use `url` if defined or fallback to `config.base`.
219-
220-
If `relative` is true and `url` is defined, relative paths will be prefered for assets inside the same group (for example a hashed image referenced from a JS file). And `url` will be used for the paths in HTML entries and for paths between different groups (a public file referenced from a CSS file).
221-
222-
If the hashed assets and public files aren't deployed together, options for each group can be defined independently:
214+
If the hashed assets and public files aren't deployed together, options for each group can be defined independently using asset `type` included in the third `context` param given to the function.
223215

224216
```js
225217
experimental: {
226-
buildAdvancedBaseOptions: {
227-
assets: {
228-
relative: true
229-
url: 'https://cdn.domain.com/assets',
230-
runtime: (url: string) => `window.__assetsPath(${url})`
231-
},
232-
public: {
233-
relative: false
234-
url: 'https://www.domain.com/',
235-
runtime: (url: string) => `window.__publicPath + ${url}`
218+
renderBuiltUrl(filename: string, { hostType: 'js' | 'css' | 'html', type: 'public' | 'asset' }) {
219+
if (type === 'public') {
220+
return 'https://www.domain.com/' + filename
221+
}
222+
else if (path.extname(importer) === '.js') {
223+
return { runtime: `window.__assetsPath(${JSON.stringify(filename)})` }
224+
}
225+
else {
226+
return 'https://cdn.domain.com/assets/' + filename
236227
}
237228
}
238229
}
239230
```
240-
241-
Any option that isn't defined in the `public` or `assets` entry will be inherited from the main `buildAdvancedBaseOptions` config.

packages/plugin-legacy/src/index.ts

+55-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url'
66
import { build, normalizePath } from 'vite'
77
import MagicString from 'magic-string'
88
import type {
9-
BuildAdvancedBaseOptions,
109
BuildOptions,
1110
HtmlTagDescriptor,
1211
Plugin,
@@ -32,38 +31,71 @@ async function loadBabel() {
3231
return babel
3332
}
3433

35-
function getBaseInHTML(
36-
urlRelativePath: string,
37-
baseOptions: BuildAdvancedBaseOptions,
38-
config: ResolvedConfig
39-
) {
34+
// Duplicated from build.ts in Vite Core, at least while the feature is experimental
35+
// We should later expose this helper for other plugins to use
36+
function toOutputFilePathInHtml(
37+
filename: string,
38+
type: 'asset' | 'public',
39+
hostId: string,
40+
hostType: 'js' | 'css' | 'html',
41+
config: ResolvedConfig,
42+
toRelative: (filename: string, importer: string) => string
43+
): string {
44+
const { renderBuiltUrl } = config.experimental
45+
let relative = config.base === '' || config.base === './'
46+
if (renderBuiltUrl) {
47+
const result = renderBuiltUrl(filename, {
48+
hostId,
49+
hostType,
50+
type,
51+
ssr: !!config.build.ssr
52+
})
53+
if (typeof result === 'object') {
54+
if (result.runtime) {
55+
throw new Error(
56+
`{ runtime: "${result.runtime}" } is not supported for assets in ${hostType} files: ${filename}`
57+
)
58+
}
59+
if (typeof result.relative === 'boolean') {
60+
relative = result.relative
61+
}
62+
} else if (result) {
63+
return result
64+
}
65+
}
66+
if (relative && !config.build.ssr) {
67+
return toRelative(filename, hostId)
68+
} else {
69+
return config.base + filename
70+
}
71+
}
72+
function getBaseInHTML(urlRelativePath: string, config: ResolvedConfig) {
4073
// Prefer explicit URL if defined for linking to assets and public files from HTML,
4174
// even when base relative is specified
42-
return (
43-
baseOptions.url ??
44-
(baseOptions.relative
45-
? path.posix.join(
46-
path.posix.relative(urlRelativePath, '').slice(0, -2),
47-
'./'
48-
)
49-
: config.base)
50-
)
75+
return config.base === './' || config.base === ''
76+
? path.posix.join(
77+
path.posix.relative(urlRelativePath, '').slice(0, -2),
78+
'./'
79+
)
80+
: config.base
5181
}
5282

53-
function getAssetsBase(urlRelativePath: string, config: ResolvedConfig) {
54-
return getBaseInHTML(
55-
urlRelativePath,
56-
config.experimental.buildAdvancedBaseOptions.assets,
57-
config
58-
)
59-
}
6083
function toAssetPathFromHtml(
6184
filename: string,
6285
htmlPath: string,
6386
config: ResolvedConfig
6487
): string {
6588
const relativeUrlPath = normalizePath(path.relative(config.root, htmlPath))
66-
return getAssetsBase(relativeUrlPath, config) + filename
89+
const toRelative = (filename: string, hostId: string) =>
90+
getBaseInHTML(relativeUrlPath, config) + filename
91+
return toOutputFilePathInHtml(
92+
filename,
93+
'asset',
94+
htmlPath,
95+
'html',
96+
config,
97+
toRelative
98+
)
6799
}
68100

69101
// https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc

packages/vite/src/node/build.ts

+80-100
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type { RollupCommonJSOptions } from 'types/commonjs'
2222
import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
2323
import type { TransformOptions } from 'esbuild'
2424
import type { InlineConfig, ResolvedConfig } from './config'
25-
import { isDepsOptimizerEnabled, resolveBaseUrl, resolveConfig } from './config'
25+
import { isDepsOptimizerEnabled, resolveConfig } from './config'
2626
import { buildReporterPlugin } from './plugins/reporter'
2727
import { buildEsbuildPlugin } from './plugins/esbuild'
2828
import { terserPlugin } from './plugins/terser'
@@ -831,109 +831,89 @@ function injectSsrFlag<T extends Record<string, any>>(
831831
return { ...(options ?? {}), ssr: true } as T & { ssr: boolean }
832832
}
833833

834-
/*
835-
* If defined, these functions will be called for assets and public files
836-
* paths which are generated in JS assets. Examples:
837-
*
838-
* assets: { runtime: (url: string) => `window.__assetsPath(${url})` }
839-
* public: { runtime: (url: string) => `window.__publicPath + ${url}` }
840-
*
841-
* For assets and public files paths in CSS or HTML, the corresponding
842-
* `assets.url` and `public.url` base urls or global base will be used.
843-
*
844-
* When using relative base, the assets.runtime function isn't needed as
845-
* all the asset paths will be computed using import.meta.url
846-
* The public.runtime function is still useful if the public files aren't
847-
* deployed in the same base as the hashed assets
848-
*/
849-
850-
export interface BuildAdvancedBaseOptions {
851-
/**
852-
* Relative base. If true, every generated URL is relative and the dist folder
853-
* can be deployed to any base or subdomain. Use this option when the base
854-
* is unkown at build time
855-
* @default false
856-
*/
857-
relative?: boolean
858-
url?: string
859-
runtime?: (filename: string) => string
860-
}
861-
862-
export type BuildAdvancedBaseConfig = BuildAdvancedBaseOptions & {
863-
/**
864-
* Base for assets and public files in case they should be different
865-
*/
866-
assets?: string | BuildAdvancedBaseOptions
867-
public?: string | BuildAdvancedBaseOptions
868-
}
869-
870-
export type ResolvedBuildAdvancedBaseConfig = BuildAdvancedBaseOptions & {
871-
assets: BuildAdvancedBaseOptions
872-
public: BuildAdvancedBaseOptions
873-
}
874-
875-
/**
876-
* Resolve base. Note that some users use Vite to build for non-web targets like
877-
* electron or expects to deploy
878-
*/
879-
export function resolveBuildAdvancedBaseConfig(
880-
baseConfig: BuildAdvancedBaseConfig | undefined,
881-
resolvedBase: string,
882-
isBuild: boolean,
883-
logger: Logger
884-
): ResolvedBuildAdvancedBaseConfig {
885-
baseConfig ??= {}
886-
887-
const relativeBaseShortcut = resolvedBase === '' || resolvedBase === './'
888-
889-
const resolved = {
890-
relative: baseConfig?.relative ?? relativeBaseShortcut,
891-
url: baseConfig?.url
892-
? resolveBaseUrl(
893-
baseConfig?.url,
894-
isBuild,
895-
logger,
896-
'experimental.buildAdvancedBaseOptions.url'
897-
)
898-
: undefined,
899-
runtime: baseConfig?.runtime
834+
export type RenderBuiltAssetUrl = (
835+
filename: string,
836+
type: {
837+
type: 'asset' | 'public'
838+
hostId: string
839+
hostType: 'js' | 'css' | 'html'
840+
ssr: boolean
841+
}
842+
) => string | { relative?: boolean; runtime?: string } | undefined
843+
844+
export function toOutputFilePathInString(
845+
filename: string,
846+
type: 'asset' | 'public',
847+
hostId: string,
848+
hostType: 'js' | 'css' | 'html',
849+
config: ResolvedConfig,
850+
toRelative: (
851+
filename: string,
852+
hostType: string
853+
) => string | { runtime: string }
854+
): string | { runtime: string } {
855+
const { renderBuiltUrl } = config.experimental
856+
let relative = config.base === '' || config.base === './'
857+
if (renderBuiltUrl) {
858+
const result = renderBuiltUrl(filename, {
859+
hostId,
860+
hostType,
861+
type,
862+
ssr: !!config.build.ssr
863+
})
864+
if (typeof result === 'object') {
865+
if (result.runtime) {
866+
return { runtime: result.runtime }
867+
}
868+
if (typeof result.relative === 'boolean') {
869+
relative = result.relative
870+
}
871+
} else if (result) {
872+
return result
873+
}
900874
}
901-
902-
return {
903-
...resolved,
904-
assets: resolveBuildBaseSpecificOptions(
905-
baseConfig?.assets,
906-
resolved,
907-
isBuild,
908-
logger,
909-
'assets'
910-
),
911-
public: resolveBuildBaseSpecificOptions(
912-
baseConfig?.public,
913-
resolved,
914-
isBuild,
915-
logger,
916-
'public'
917-
)
875+
if (relative && !config.build.ssr) {
876+
return toRelative(filename, hostId)
918877
}
878+
return config.base + filename
919879
}
920880

921-
function resolveBuildBaseSpecificOptions(
922-
options: BuildAdvancedBaseOptions | string | undefined,
923-
parent: BuildAdvancedBaseOptions,
924-
isBuild: boolean,
925-
logger: Logger,
926-
optionName: string
927-
): BuildAdvancedBaseOptions {
928-
const urlConfigPath = `experimental.buildAdvancedBaseOptions.${optionName}.url`
929-
if (typeof options === 'string') {
930-
options = { url: options }
881+
export function toOutputFilePathWithoutRuntime(
882+
filename: string,
883+
type: 'asset' | 'public',
884+
hostId: string,
885+
hostType: 'js' | 'css' | 'html',
886+
config: ResolvedConfig,
887+
toRelative: (filename: string, hostId: string) => string
888+
): string {
889+
const { renderBuiltUrl } = config.experimental
890+
let relative = config.base === '' || config.base === './'
891+
if (renderBuiltUrl) {
892+
const result = renderBuiltUrl(filename, {
893+
hostId,
894+
hostType,
895+
type,
896+
ssr: !!config.build.ssr
897+
})
898+
if (typeof result === 'object') {
899+
if (result.runtime) {
900+
throw new Error(
901+
`{ runtime: "${result.runtime} }" is not supported for assets in ${hostType} files: ${filename}`
902+
)
903+
}
904+
if (typeof result.relative === 'boolean') {
905+
relative = result.relative
906+
}
907+
} else if (result) {
908+
return result
909+
}
931910
}
932-
return {
933-
relative: options?.relative ?? parent.relative,
934-
url: options?.url
935-
? resolveBaseUrl(options?.url, isBuild, logger, urlConfigPath)
936-
: parent.url,
937-
runtime: options?.runtime ?? parent.runtime
911+
if (relative && !config.build.ssr) {
912+
return toRelative(filename, hostId)
913+
} else {
914+
return config.base + filename
938915
}
939916
}
917+
918+
export const toOutputFilePathInCss = toOutputFilePathWithoutRuntime
919+
export const toOutputFilePathInHtml = toOutputFilePathWithoutRuntime

0 commit comments

Comments
 (0)