mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
feat(vite): handle multiple/custom public dirs (#26163)
This commit is contained in:
parent
25617516fb
commit
b102d04185
@ -1,8 +1,7 @@
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import type { NuxtConfigLayer } from 'nuxt/schema'
|
||||
import { resolveAlias } from '@nuxt/kit'
|
||||
import { join, normalize, relative } from 'pathe'
|
||||
import { normalize } from 'pathe'
|
||||
import MagicString from 'magic-string'
|
||||
|
||||
interface LayerAliasingOptions {
|
||||
@ -17,21 +16,16 @@ const ALIAS_RE = /(?<=['"])[~@]{1,2}(?=\/)/g
|
||||
const ALIAS_RE_SINGLE = /(?<=['"])[~@]{1,2}(?=\/)/
|
||||
|
||||
export const LayerAliasingPlugin = createUnplugin((options: LayerAliasingOptions) => {
|
||||
const aliases: Record<string, { aliases: Record<string, string>, prefix: string, publicDir: false | string }> = {}
|
||||
const aliases: Record<string, Record<string, string>> = {}
|
||||
for (const layer of options.layers) {
|
||||
const srcDir = layer.config.srcDir || layer.cwd
|
||||
const rootDir = layer.config.rootDir || layer.cwd
|
||||
const publicDir = join(srcDir, layer.config?.dir?.public || 'public')
|
||||
|
||||
aliases[srcDir] = {
|
||||
aliases: {
|
||||
'~': layer.config?.alias?.['~'] || srcDir,
|
||||
'@': layer.config?.alias?.['@'] || srcDir,
|
||||
'~~': layer.config?.alias?.['~~'] || rootDir,
|
||||
'@@': layer.config?.alias?.['@@'] || rootDir
|
||||
},
|
||||
prefix: relative(options.root, publicDir),
|
||||
publicDir: !options.dev && existsSync(publicDir) && publicDir
|
||||
'~': layer.config?.alias?.['~'] || srcDir,
|
||||
'@': layer.config?.alias?.['@'] || srcDir,
|
||||
'~~': layer.config?.alias?.['~~'] || rootDir,
|
||||
'@@': layer.config?.alias?.['@@'] || rootDir
|
||||
}
|
||||
}
|
||||
const layers = Object.keys(aliases).sort((a, b) => b.length - a.length)
|
||||
@ -48,13 +42,7 @@ export const LayerAliasingPlugin = createUnplugin((options: LayerAliasingOptions
|
||||
const layer = layers.find(l => importer.startsWith(l))
|
||||
if (!layer) { return }
|
||||
|
||||
const publicDir = aliases[layer].publicDir
|
||||
if (id.startsWith('/') && publicDir && readdirSync(publicDir).some(file => file === id.slice(1) || id.startsWith('/' + file + '/'))) {
|
||||
const resolvedId = '/' + join(aliases[layer].prefix, id.slice(1))
|
||||
return await this.resolve(resolvedId, importer, { skipSelf: true })
|
||||
}
|
||||
|
||||
const resolvedId = resolveAlias(id, aliases[layer].aliases)
|
||||
const resolvedId = resolveAlias(id, aliases[layer])
|
||||
if (resolvedId !== id) {
|
||||
return await this.resolve(resolvedId, importer, { skipSelf: true })
|
||||
}
|
||||
@ -76,7 +64,7 @@ export const LayerAliasingPlugin = createUnplugin((options: LayerAliasingOptions
|
||||
if (!layer || !ALIAS_RE_SINGLE.test(code)) { return }
|
||||
|
||||
const s = new MagicString(code)
|
||||
s.replace(ALIAS_RE, r => aliases[layer].aliases[r as '~'] || r)
|
||||
s.replace(ALIAS_RE, r => aliases[layer][r as '~'] || r)
|
||||
|
||||
if (s.hasChanged()) {
|
||||
return {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { consola } from 'consola'
|
||||
import { resolve } from 'pathe'
|
||||
import { isTest } from 'std-env'
|
||||
import { withoutLeadingSlash } from 'ufo'
|
||||
import { defineUntypedSchema } from 'untyped'
|
||||
@ -36,11 +35,11 @@ export default defineUntypedSchema({
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
publicDir: {
|
||||
$resolve: async (val, get) => {
|
||||
$resolve: (val) => {
|
||||
if (val) {
|
||||
consola.warn('Directly configuring the `vite.publicDir` option is not supported. Instead, set `dir.public`. You can read more in `https://nuxt.com/docs/api/nuxt-config#public`.')
|
||||
}
|
||||
return val ?? await Promise.all([get('srcDir') as Promise<string>, get('dir') as Promise<Record<string, string>>]).then(([srcDir, dir]) => resolve(srcDir, dir.public))
|
||||
return false
|
||||
}
|
||||
},
|
||||
vue: {
|
||||
|
65
packages/vite/src/plugins/public-dirs.ts
Normal file
65
packages/vite/src/plugins/public-dirs.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { useNitro } from '@nuxt/kit'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { withLeadingSlash, withTrailingSlash } from 'ufo'
|
||||
import { dirname, relative } from 'pathe'
|
||||
|
||||
const PREFIX = 'virtual:public?'
|
||||
|
||||
export const VitePublicDirsPlugin = createUnplugin(() => {
|
||||
const nitro = useNitro()
|
||||
|
||||
function resolveFromPublicAssets (id: string) {
|
||||
for (const dir of nitro.options.publicAssets) {
|
||||
if (!id.startsWith(withTrailingSlash(dir.baseURL || '/'))) { continue }
|
||||
const path = id.replace(withTrailingSlash(dir.baseURL || '/'), withTrailingSlash(dir.dir))
|
||||
if (existsSync(path)) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'nuxt:vite-public-dir-resolution',
|
||||
vite: {
|
||||
load: {
|
||||
enforce: 'pre',
|
||||
handler (id, options) {
|
||||
if (id.startsWith(PREFIX)) {
|
||||
const helper = !options?.ssr || nitro.options.imports !== false ? '' : 'globalThis.'
|
||||
return `export default ${helper}__publicAssetsURL(${JSON.stringify(decodeURIComponent(id.slice(PREFIX.length)))})`
|
||||
}
|
||||
}
|
||||
},
|
||||
resolveId: {
|
||||
enforce: 'post',
|
||||
handler (id) {
|
||||
if (id === '/__skip_vite' || !id.startsWith('/') || id.startsWith('/@fs')) { return }
|
||||
|
||||
if (resolveFromPublicAssets(id)) {
|
||||
return PREFIX + encodeURIComponent(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
generateBundle (outputOptions, bundle) {
|
||||
for (const file in bundle) {
|
||||
const chunk = bundle[file]
|
||||
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
||||
|
||||
let css = chunk.source.toString()
|
||||
let wasReplaced = false
|
||||
for (const [full, url] of css.matchAll(/url\((\/[^)]+)\)/g)) {
|
||||
if (resolveFromPublicAssets(url)) {
|
||||
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
|
||||
css = css.replace(full, `url(${relativeURL})`)
|
||||
wasReplaced = true
|
||||
}
|
||||
}
|
||||
if (wasReplaced) {
|
||||
chunk.source = css
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -17,6 +17,7 @@ import { resolveCSSOptions } from './css'
|
||||
import { composableKeysPlugin } from './plugins/composable-keys'
|
||||
import { logLevelMap } from './utils/logger'
|
||||
import { ssrStylesPlugin } from './plugins/ssr-styles'
|
||||
import { VitePublicDirsPlugin } from './plugins/public-dirs'
|
||||
|
||||
export interface ViteBuildContext {
|
||||
nuxt: Nuxt
|
||||
@ -98,6 +99,8 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
// add resolver for files in public assets directories
|
||||
VitePublicDirsPlugin.vite(),
|
||||
composableKeysPlugin.vite({
|
||||
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
|
||||
rootDir: nuxt.options.rootDir,
|
||||
|
@ -1815,6 +1815,14 @@ describe.runIf(isDev() && (!isWindows || !isCI))('detecting invalid root nodes',
|
||||
})
|
||||
})
|
||||
|
||||
describe('public directories', () => {
|
||||
it('should directly return public directory paths', async () => {
|
||||
const html = await $fetch('/assets-custom')
|
||||
expect(html).toContain('"/public.svg"')
|
||||
expect(html).toContain('"/custom/file.svg"')
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: dynamic paths in dev
|
||||
describe.skipIf(isDev())('dynamic paths', () => {
|
||||
it('should work with no overrides', async () => {
|
||||
|
18
test/fixtures/basic/custom-public/file.svg
vendored
Normal file
18
test/fixtures/basic/custom-public/file.svg
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<svg viewBox="0 0 221 65" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-8">
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="currentColor"
|
||||
d="M82.5623 18.5705h7.3017l15.474 24.7415V18.5705h6.741v35.0576h-7.252L89.3025 28.938v24.6901h-6.7402V18.5705ZM142.207 53.628h-6.282v-3.916c-1.429 2.7559-4.339 4.3076-8.015 4.3076-5.822 0-9.603-4.1069-9.603-10.0175V28.3847h6.282v14.3251c0 3.4558 2.146 5.8592 5.362 5.8592 3.524 0 5.974-2.7044 5.974-6.4099V28.3847h6.282V53.628ZM164.064 53.2289l-6.026-8.4144-6.027 8.4144h-6.69l9.296-13.1723-8.58-12.0709h6.843l5.158 7.2641 5.106-7.2641h6.895l-8.632 12.0709 9.295 13.1723h-6.638ZM183.469 20.7726v7.6116h7.149v5.1593h-7.149v12.5311c0 .4208.17.8245.473 1.1223.303.2978.715.4654 1.144.4661h5.532v5.9547h-4.137c-5.617 0-9.293-3.2062-9.293-8.8109V33.5484h-5.056v-5.1642h3.172c1.479 0 2.34-.8639 2.34-2.2932v-5.3184h5.825Z">
|
||||
</path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M30.1185 11.5456c-1.8853-3.24168-6.5987-3.24169-8.484 0L1.08737 46.8747c-1.885324 3.2417.47133 7.2938 4.24199 7.2938H21.3695c-1.6112-1.4081-2.2079-3.8441-.9886-5.9341l15.5615-26.675-5.8239-10.0138Z"
|
||||
fill="#80EEC0"></path>
|
||||
<path
|
||||
d="M43.1374 19.2952c1.5603-2.6523 5.461-2.6523 7.0212 0l17.0045 28.9057c1.5603 2.6522-.39 5.9676-3.5106 5.9676h-34.009c-3.1206 0-5.0709-3.3154-3.5106-5.9676l17.0045-28.9057ZM209.174 53.8005H198.483c0-1.8514.067-3.4526 0-6.0213h10.641c1.868 0 3.353.1001 4.354-.934 1-1.0341 1.501-2.3351 1.501-3.9029 0-1.8347-.667-3.2191-2.002-4.1532-1.301-.9674-2.985-1.4511-5.054-1.4511h-2.601v-5.2539h2.652c1.701 0 3.119-.4003 4.253-1.2009 1.134-.8006 1.701-1.9849 1.701-3.5527 0-1.301-.434-2.3351-1.301-3.1023-.834-.8007-2.001-1.201-3.503-1.201-1.634 0-2.918.4837-3.853 1.4511-.9.9674-1.401 2.1517-1.501 3.5527h-6.254c.133-3.2358 1.251-5.7877 3.352-7.6558 2.135-1.868 4.887-2.8021 8.256-2.8021 2.402 0 4.42.4337 6.055 1.301 1.668.834 2.919 1.9515 3.753 3.3525.867 1.4011 1.301 2.9523 1.301 4.6536 0 1.9681-.551 3.636-1.651 5.0037-1.068 1.3344-2.402 2.235-4.004 2.7021 1.969.4003 3.57 1.3677 4.804 2.9022 1.234 1.5011 1.852 3.4025 1.852 5.7043 0 1.9347-.468 3.7028-1.402 5.304-.934 1.6012-2.301 2.8855-4.103 3.8529-1.768.9674-3.953 1.4511-6.555 1.4511Z"
|
||||
fill="#00DC82"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h221v65H0z"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
6
test/fixtures/basic/nuxt.config.ts
vendored
6
test/fixtures/basic/nuxt.config.ts
vendored
@ -46,6 +46,12 @@ export default defineNuxtConfig({
|
||||
'./extends/node_modules/foo'
|
||||
],
|
||||
nitro: {
|
||||
publicAssets: [
|
||||
{
|
||||
dir: '../custom-public',
|
||||
baseURL: '/custom'
|
||||
}
|
||||
],
|
||||
esbuild: {
|
||||
options: {
|
||||
// in order to test bigint serialization
|
||||
|
6
test/fixtures/basic/pages/assets-custom.vue
vendored
Normal file
6
test/fixtures/basic/pages/assets-custom.vue
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<img src="/public.svg">
|
||||
<img src="/custom/file.svg">
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user