From 00bc4466567bd5613d67abd1bdf46d9c9cb23a80 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 23 Jan 2021 15:48:07 -0500 Subject: [PATCH] fix: support empty, relative and external base values close #1669 --- docs/config/index.md | 8 +++- packages/vite/src/node/config.ts | 69 ++++++++++++++++++++++----- packages/vite/src/node/plugins/css.ts | 38 +++++++++++---- 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 9d46f835bd66dc..9515267dd0313c 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -103,7 +103,13 @@ export default ({ command, mode }) => { - **Type:** `string` - **Default:** `/` - Base public path when served in development or production. Note the path should start and end with `/`. See [Public Base Path](/guide/build#public-base-path) for more details. + Base public path when served in development or production. Valid values include: + + - Absolute URL pathname, e.g. `/foo/` + - Full URL, e.g. `https://foo.com/` + - Empty string or `./` (for embedded deployment) + + See [Public Base Path](/guide/build#public-base-path) for more details. ### mode diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 4111fc54341be5..1e05703c6b7452 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -5,7 +5,13 @@ import Rollup from 'rollup' import { BuildOptions, resolveBuildOptions } from './build' import { ServerOptions } from './server' import { CSSOptions } from './plugins/css' -import { createDebugger, isObject, lookupFile, normalizePath } from './utils' +import { + createDebugger, + isExternalUrl, + isObject, + lookupFile, + normalizePath +} from './utils' import { resolvePlugins } from './plugins' import chalk from 'chalk' import { @@ -23,6 +29,7 @@ import { createLogger, Logger, LogLevel } from './logger' import { DepOptimizationOptions } from './optimizer' import { createFilter } from '@rollup/pluginutils' import { ResolvedBuildOptions } from '.' +import { parse as parseUrl } from 'url' const debug = createDebugger('vite:config') @@ -238,16 +245,7 @@ export async function resolveConfig( config.base = config.build.base } - let BASE_URL = config.base || '/' - if (!BASE_URL.startsWith('/') || !BASE_URL.endsWith('/')) { - logger.warn( - chalk.bold.yellow( - `(!) "base" config option should start and end with "/".` - ) - ) - if (!BASE_URL.startsWith('/')) BASE_URL = '/' + BASE_URL - if (!BASE_URL.endsWith('/')) BASE_URL = BASE_URL + '/' - } + const BASE_URL = resolveBaseUrl(config.base, command === 'build', logger) const resolvedBuildOptions = resolveBuildOptions(config.build) @@ -339,6 +337,55 @@ export async function resolveConfig( return resolved } +/** + * Resolve base. Note that some users use Vite to build for non-web targets like + * electron or expects to deploy + */ +function resolveBaseUrl( + base: UserConfig['base'] = '/', + isBuild: boolean, + logger: Logger +): string { + // #1669 special treatment for empty for same dir relative base + if (base === '' || base === './') { + return isBuild ? base : '/' + } + if (base.startsWith('.')) { + logger.warn( + chalk.yellow.bold( + `(!) invalid "base" option: ${base}. The value can only be an absolute ` + + `URL, ./, or an empty string.` + ) + ) + base = '/' + } + + // external URL + if (isExternalUrl(base)) { + if (!isBuild) { + // get base from full url during dev + const parsed = parseUrl(base) + base = parsed.pathname || '/' + } + } else { + // ensure leading slash + if (!base.startsWith('/')) { + logger.warn( + chalk.yellow.bold(`(!) "base" option should start with a slash.`) + ) + base = '/' + base + } + } + + // ensure ending slash + if (!base.endsWith('/')) { + logger.warn(chalk.yellow.bold(`(!) "base" option should end with a slash.`)) + base += '/' + } + + return base +} + export function mergeConfig( a: Record, b: Record, diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index b17aa65b726677..dcfb561f7de83f 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -15,6 +15,7 @@ import postcssrc from 'postcss-load-config' import merge from 'merge-source-map' import { NormalizedOutputOptions, + PluginContext, RenderedChunk, RollupError, SourceMap @@ -238,21 +239,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { return null } - // replace asset url references with resolved url - chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileId, postfix = '') => { - return config.base + this.getFileName(fileId) + postfix - }) - if (config.build.cssCodeSplit) { if (!code.trim()) { // this is a shared CSS-only chunk that is empty. emptyChunks.add(chunk.fileName) } - // minify - if (config.build.minify) { - chunkCSS = await minifyCSS(chunkCSS, config) - } if (opts.format === 'es') { + chunkCSS = await processChunkCSS(chunkCSS, config, this, false) // emit corresponding css file const fileHandle = this.emitFile({ name: chunk.name + '.css', @@ -262,6 +255,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { chunkToEmittedCssFileMap.set(chunk, fileHandle) } else if (!config.build.ssr) { // legacy build, inline css + chunkCSS = await processChunkCSS(chunkCSS, config, this, true) const style = `__vite_style__` const injectCode = `var ${style} = document.createElement('style');` + @@ -284,6 +278,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { } } else { const extractedCss = outputToExtractedCSSMap.get(opts) || '' + chunkCSS = await processChunkCSS(chunkCSS, config, this, false) outputToExtractedCSSMap.set(opts, extractedCss + chunkCSS) return null } @@ -739,6 +734,31 @@ function rewriteCssUrls( }) } +async function processChunkCSS( + css: string, + config: ResolvedConfig, + pluginCtx: PluginContext, + isInlined: boolean +): Promise { + // replace asset url references with resolved url. + const isRelativeBase = config.base === '' || config.base.startsWith('.') + css = css.replace(assetUrlRE, (_, fileId, postfix = '') => { + const filename = pluginCtx.getFileName(fileId) + postfix + if (!isRelativeBase || isInlined) { + // absoulte base or relative base but inlined (injected as style tag into + // index.html) use the base as-is + return config.base + filename + } else { + // relative base + extracted CSS - asset file will be in the same dir + return `./${path.posix.basename(filename)}` + } + }) + if (config.build.minify) { + css = await minifyCSS(css, config) + } + return css +} + let CleanCSS: any async function minifyCSS(css: string, config: ResolvedConfig) {