静态资源
Nuxt 提供两种方式管理静态资源。
Nuxt 使用两个目录来处理样式表、字体、图片等静态资源。
public/目录的内容会原样挂载在服务器根路径下。app/assets/目录按约定存放需要构建工具(Vite 或 webpack)处理的资源。
Public 目录
public/ 目录用作静态资源的公共服务器,文件通过应用的指定 URL 公开访问。
你可以在应用代码或浏览器中通过根路径 / 访问 public/ 目录下的文件。
示例
比如,引用 public/img/ 目录下的图片文件,它的静态 URL 是 /img/nuxt.png:
<template>
<img
src="/img/nuxt.png"
alt="发现 Nuxt"
>
</template>
Assets 目录
Nuxt 使用 Vite(默认)或 webpack 来构建和打包应用。这些构建工具的主要功能是处理 JavaScript 文件,但可以通过 Vite 插件或 webpack loader扩展来处理其他类型的资源,比如样式表、字体或 SVG。这一步会转换原始文件,主要是为了优化性能或缓存(比如样式表压缩或浏览器缓存失效)。
按约定,Nuxt 使用 app/assets/ 目录存放这些文件,但这个目录不会被自动扫描,你可以用任何其他名字。
在应用代码中,你可以通过 ~/assets/ 路径引用 app/assets/ 目录下的文件。
示例
比如,引用一个会被构建工具处理的图片文件(前提是配置了对应的文件扩展名处理规则):
<template>
<img
src="~/assets/img/nuxt.png"
alt="发现 Nuxt"
>
</template>
Nuxt 不会把 app/assets/ 目录下的文件通过 /assets/my-file.png 这样的静态 URL 提供。如果你需要静态 URL,请使用 public/ 目录。
静态 vs. 动态 src
当 src 在模板中是静态字符串字面量时,构建工具会将其重写为运行时辅助函数来解析最终 URL。像 /img/nuxt.png 这样的公共路径会被包裹,使得页面渲染时应用你的 app.baseURL;而像 ~/assets/img/nuxt.png 这样的打包路径则会变成一个 import,解析到带 hash 的输出文件。
<template>
<!-- 静态路径会被重写:运行时应用 app.baseURL,打包文件带 hash。 -->
<img src="/img/nuxt.png">
<img src="~/assets/img/nuxt.png">
</template>
因为 app.baseURL 是在运行时应用的,所以即使 base URL 只在部署时才知道(比如通过 NUXT_APP_BASE_URL 设置),静态公共路径也能正常工作,无论文件是否被构建工具处理。这种解析只发生在构建工具能看到的字面路径上。
如果绑定的 :src 值是在运行时拼接的,构建工具对此是不可见的,所以不会有任何重写。字符串会原样使用:
<template>
<!-- 这样不行:路径是运行时构建的,Vite 永远不会把它识别为 import。 -->
<img :src="`~/assets/img/${name}.png`">
</template>
因此,运行时构建的公共路径如 /img/${name}.png 不会自动加上 app.baseURL 前缀。如果你的应用部署在域名子路径下,需要自己用 useRuntimeConfig().app.baseURL 加上前缀(比如通过 joinURL)。
下面介绍路径只在运行时知道的情况下如何处理。
公共资源
如果文件不需要处理或 hash,放在 public/ 目录下,通过 URL 引用:
<script setup lang="ts">
const props = defineProps<{
name: string
}>()
const imageUrl = computed(() => `/img/${props.name}.png`)
</script>
<template>
<img
:src="imageUrl"
:alt="props.name"
>
</template>
public/ 目录下的文件保持原始文件名。
用 Vite 打包资源
以下方法是 Vite(Nuxt 默认构建工具)特有的。
当可能的文件已知时,显式列出它们的 import:
<script setup lang="ts">
const props = defineProps<{
theme: 'light' | 'dark'
}>()
const logos = {
light: () => import('./assets/img/logo-light.png?url'),
dark: () => import('./assets/img/logo-dark.png?url'),
}
const logoUrl = (await logos[props.theme]()).default
</script>
<template>
<img
:src="logoUrl"
alt="Nuxt"
>
</template>
每个 import 都有字面路径,所以 Vite 在构建时能找到这两个文件,运行时只加载选中的模块。
当很多文件共享同一目录和扩展名时,使用变量动态 import,而不是逐个列出:
async function getImageUrl (name: string) {
const image = await import(`./assets/img/${name}.png?url`)
return image.default
}
这个例子中只有文件名可以是动态的。目录和扩展名保持在 import 中,Vite 才能在构建时找到可能的文件。
对于更广泛的模式或可用文件的显式映射,使用 import.meta.glob:
const images = import.meta.glob<string>('./assets/img/*.{png,jpg,svg}', {
query: '?url',
import: 'default',
})
async function getImageUrl (name: string) {
const load = images[`./assets/img/${name}.png`]
if (!load) {
throw new Error(`未知图片: ${name}`)
}
return await load()
}
Glob import 默认是懒加载的。如果 URL 需要同步可用,加上 eager: true:
const images = import.meta.glob<string>('./assets/img/*.{png,jpg,svg}', {
query: '?url',
import: 'default',
eager: true,
})
每个匹配的资源仍然会包含在构建产物中。懒加载 import 按需加载每个匹配项,而 eager glob 会预先加载所有匹配项,可能增加初始 JavaScript 体积或将小资源内联。
在 SSR 标记中使用懒加载 import 的 URL 之前,一定要 await。Vite 的 new URL(..., import.meta.url) 模式在 SSR 下不工作。