feat: support configuring publicDir via config

close #1799
This commit is contained in:
Evan You 2021-01-29 14:01:36 -05:00
parent f4719e4441
commit 470ceb8276
15 changed files with 52 additions and 33 deletions

View File

@ -94,7 +94,7 @@ export default ({ command, mode }) => {
- **Type:** `string`
- **Default:** `process.cwd()`
Project root directory. Can be an absolute path, or a path relative from the location of the config file itself.
Project root directory (where `index.html` is located). Can be an absolute path, or a path relative from the location of the config file itself.
See [Project Root](/guide/#project-root) for more details.
@ -111,6 +111,13 @@ export default ({ command, mode }) => {
See [Public Base Path](/guide/build#public-base-path) for more details.
### publicDir
- **Type:** `string`
- **Default:** `"public"`
Directory to serve as plain static assets. Files in this directory are served at `/` during dev and copied to the root of `outDir` during build, and are always served or copied as-is without transform. The value can be either an absolute file system path or a path relative to project root.
### mode
- **Type:** `string`

View File

@ -182,6 +182,8 @@ If you have assets that are:
Then you can place the asset in a special `public` directory under your project root. Assets in this directory will be served at root path `/` during dev, and copied to the root of the dist directory as-is.
The directory defaults to `<root>/public`, but can be configured via the [`publicDir` option](/config/#publicdir).
Note that:
- You should always reference `public` assets using root absolute path - for example, `public/icon.png` should be referenced in source code as `/icon.png`.

View File

@ -48,7 +48,7 @@ One thing you may have noticed is that in a Vite project, `index.html` is front-
Vite treats `index.html` as source code and part of the module graph. It resolves `<script type="module" src="...">` that references your JavaScript source code. Even inline `<script type="module">` and CSS referenced via `<link href>` also enjoy Vite-specific features. In addition, URLs inside `index.html` are automatically rebased so there's no need for special `%PUBLIC_URL%` placeholders.
Similar to static http servers, Vite has the concept of a "root directory" from which your files are served from. Absolute URLs in your source code will be resolved using the project root as base, so you can write code as if you are working with a normal static file server (except way more powerful!). Vite is also capable of handling dependencies that resolve to out-of-root file system locations, which makes it usable even in a monorepo-based setup.
Similar to static http servers, Vite has the concept of a "root directory" from which your files are served from. You will see it referenced as `<root>` throughout the rest of the docs. Absolute URLs in your source code will be resolved using the project root as base, so you can write code as if you are working with a normal static file server (except way more powerful!). Vite is also capable of handling dependencies that resolve to out-of-root file system locations, which makes it usable even in a monorepo-based setup.
Vite also supports [multi-page apps](./build#multi-page-app) with multiple `.html` entry points.

View File

@ -6,18 +6,20 @@
<p class="base"></p>
<h2>Raw References from /public</h2>
<h2>Raw References from publicDir</h2>
<ul>
<li class="raw-js"></li>
<script src="/raw.js"></script>
<li class="raw-css">Raw CSS from /public should load (this should be red)</li>
<li class="raw-css">
Raw CSS from publicDir should load (this should be red)
</li>
</ul>
<h2>Asset Imports from JS</h2>
<ul>
<li>Relative: <code class="asset-import-relative"></code></li>
<li>Absolute: <code class="asset-import-absolute"></code></li>
<li>From /public: <code class="public-import"></code></li>
<li>From publicDir: <code class="public-import"></code></li>
</ul>
<h2>CSS url references</h2>

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -3,6 +3,7 @@
*/
module.exports = {
base: '/foo/',
publicDir: 'static',
build: {
outDir: 'dist/foo'
}

View File

@ -307,7 +307,6 @@ async function doBuild(
}
const outDir = resolve(options.outDir)
const publicDir = resolve('public')
// inject ssr arg to plugin load/transform hooks
const plugins = (ssr
@ -395,8 +394,8 @@ async function doBuild(
)
}
}
if (fs.existsSync(publicDir)) {
copyDir(publicDir, outDir)
if (fs.existsSync(config.publicDir)) {
copyDir(config.publicDir, outDir)
}
}

View File

@ -64,6 +64,13 @@ export interface UserConfig {
* @default '/'
*/
base?: string
/**
* Directory to serve as plain static assets. Files in this directory are
* served and copied to build dist dir as-is without transform. The value
* can be either an absolute file system path or a path relative to <root>.
* @default 'public'
*/
publicDir?: string
/**
* Explicitly set a mode to run in. This will override the default mode for
* each command, and can be overridden by the command line --mode option.
@ -146,6 +153,8 @@ export type ResolvedConfig = Readonly<
configFile: string | undefined
inlineConfig: UserConfig
root: string
base: string
publicDir: string
command: 'build' | 'serve'
mode: string
isProduction: boolean
@ -157,7 +166,6 @@ export type ResolvedConfig = Readonly<
build: ResolvedBuildOptions
assetsInclude: (file: string) => boolean
logger: Logger
base: string
createResolver: (options?: {
asSrc?: boolean
tryIndex?: boolean | string
@ -339,6 +347,8 @@ export async function resolveConfig(
configFile: configFile ? normalizePath(configFile) : undefined,
inlineConfig,
root: resolvedRoot,
base: BASE_URL,
publicDir: path.resolve(resolvedRoot, config.publicDir || 'public'),
command,
mode,
isProduction,
@ -358,7 +368,6 @@ export async function resolveConfig(
return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)
},
logger,
base: BASE_URL,
createResolver
}

View File

@ -30,7 +30,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
}
// imports to absolute urls pointing to files in /public
// will fail to resolve in the main resolver. handle them here.
const publicFile = checkPublicFile(id, config.root)
const publicFile = checkPublicFile(id, config)
if (publicFile) {
return id
}
@ -43,7 +43,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
// raw requests, read from disk
if (/(\?|&)raw\b/.test(id)) {
const file = checkPublicFile(id, config.root) || cleanUrl(id)
const file = checkPublicFile(id, config) || cleanUrl(id)
// raw query, read file and return as string
return `export default ${JSON.stringify(
await fsp.readFile(file, 'utf-8')
@ -100,13 +100,16 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
}
}
export function checkPublicFile(url: string, root: string): string | undefined {
export function checkPublicFile(
url: string,
{ publicDir }: ResolvedConfig
): string | undefined {
// note if the file is in /public, the resolver would have returned it
// as-is so it's not going to be a fully resolved path.
if (!url.startsWith('/')) {
return
}
const publicFile = path.posix.join(root, 'public', cleanUrl(url))
const publicFile = path.join(publicDir, cleanUrl(url))
if (fs.existsSync(publicFile)) {
return publicFile
} else {
@ -126,20 +129,20 @@ export function fileToUrl(
}
}
function fileToDevUrl(id: string, { root, base }: ResolvedConfig) {
function fileToDevUrl(id: string, config: ResolvedConfig) {
let rtn: string
if (checkPublicFile(id, root)) {
if (checkPublicFile(id, config)) {
// in public dir, keep the url as-is
rtn = id
} else if (id.startsWith(root)) {
} else if (id.startsWith(config.root)) {
// in project root, infer short public path
rtn = '/' + path.posix.relative(root, id)
rtn = '/' + path.posix.relative(config.root, id)
} else {
// outside of project root, use absolute fs path
// (this is special handled by the serve static middleware
rtn = path.posix.join(FS_PREFIX + id)
}
return base + rtn.replace(/^\//, '')
return config.base + rtn.replace(/^\//, '')
}
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
@ -154,7 +157,7 @@ async function fileToBuiltUrl(
pluginContext: PluginContext,
skipPublicCheck = false
): Promise<string> {
if (!skipPublicCheck && checkPublicFile(id, config.root)) {
if (!skipPublicCheck && checkPublicFile(id, config)) {
return config.base + id.slice(1)
}
@ -206,7 +209,7 @@ export async function urlToBuiltUrl(
config: ResolvedConfig,
pluginContext: PluginContext
): Promise<string> {
if (checkPublicFile(url, config.root)) {
if (checkPublicFile(url, config)) {
return config.base + url.slice(1)
}
const file = url.startsWith('/')

View File

@ -126,7 +126,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
const [preHooks, postHooks] = resolveHtmlTransforms(config.plugins)
const processedHtml = new Map<string, string>()
const isExcludedUrl = (url: string) =>
isExternalUrl(url) || isDataUrl(url) || checkPublicFile(url, config.root)
isExternalUrl(url) || isDataUrl(url) || checkPublicFile(url, config)
return {
name: 'vite:build-html',
@ -154,7 +154,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
const { src, isModule } = getScriptInfo(node)
const url = src && src.value && src.value.content
if (url && checkPublicFile(url, config.root)) {
if (url && checkPublicFile(url, config)) {
// referencing public dir url, prefix with base
s.overwrite(
src!.value!.loc.start.offset,
@ -197,7 +197,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
} else {
assetUrls.push(p)
}
} else if (checkPublicFile(url, config.root)) {
} else if (checkPublicFile(url, config)) {
s.overwrite(
p.value.loc.start.offset,
p.value.loc.end.offset,

View File

@ -341,7 +341,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
url.startsWith('/') &&
!config.assetsInclude(cleanUrl(url)) &&
!url.endsWith('.json') &&
checkPublicFile(url, config.root)
checkPublicFile(url, config)
) {
throw new Error(
`Cannot import non-asset file ${url} which is inside /public.` +

View File

@ -399,7 +399,7 @@ export async function createServer(
// serve static files under /public
// this applies before the transform middleware so that these files are served
// as-is without transforms.
middlewares.use(servePublicMiddleware(path.join(root, 'public')))
middlewares.use(servePublicMiddleware(config.publicDir))
// main transform middleware
middlewares.use(transformMiddleware(server))

View File

@ -34,15 +34,11 @@ export interface TransformOptions {
export async function transformRequest(
url: string,
{
config: { root, logger },
pluginContainer,
moduleGraph,
watcher
}: ViteDevServer,
{ config, pluginContainer, moduleGraph, watcher }: ViteDevServer,
options: TransformOptions = {}
): Promise<TransformResult | null> {
url = removeTimestampQuery(url)
const { root, logger } = config
const prettyUrl = isDebug ? prettifyUrl(url, root) : ''
const ssr = !!options.ssr
@ -99,7 +95,7 @@ export async function transformRequest(
}
}
if (code == null) {
if (checkPublicFile(url, root)) {
if (checkPublicFile(url, config)) {
throw new Error(
`Failed to load url ${url} (resolved id: ${id}). ` +
`This file is in /public and will be copied as-is during build without ` +