资源管理

Nuxt 为你的资源提供两种选项。

Nuxt 使用两个目录来处理诸如样式表、字体或图像之类的资源。

  • public/ 目录中的内容将按原样在服务器根目录提供服务。
  • app/assets/ 目录按约定包含你希望构建工具(Vite 或 webpack)处理的所有资源。

Public 目录

public/ 目录作为公共服务器,用于静态资源,这些资源可通过应用的定义 URL 公共访问。

你可以通过根 URL / 从你的应用代码或浏览器中访问 public/ 目录中的文件。

示例

例如,引用 public/img/ 目录中的图像文件,该文件可通过静态 URL /img/nuxt.png 访问:

app/app.vue
<template>
  <img
    src="/img/nuxt.png"
    alt="发现 Nuxt"
  >
</template>

Assets 目录

Nuxt 使用 Vite(默认)或 webpack 构建和打包你的应用。这些构建工具的主要功能是处理 JavaScript 文件,但它们可以通过 插件(针对 Vite)或 加载器(针对 webpack)扩展来处理其它类型的资源,比如样式表、字体或 SVG。这一步会转换原始文件,主要是为了性能或缓存目的(例如样式表压缩或浏览器缓存失效)。

按约定,Nuxt 使用 app/assets/ 目录来存放这些文件,但此目录没有自动扫描功能,你也可以使用其它名称。

在你的应用代码中,可以使用 ~/assets/ 路径来引用位于 app/assets/ 目录中的文件。

示例

例如,引用一个图像文件,如果构建工具配置了处理该文件扩展名,则此文件将被处理:

app/app.vue
<template>
  <img
    src="~/assets/img/nuxt.png"
    alt="发现 Nuxt"
  >
</template>
Nuxt 不会以类似 /assets/my-file.png 的静态 URL 提供 app/assets/ 目录中的文件。如果你需要静态 URL,请使用 public/ 目录。

静态 vs. 动态 src

当模板中的 src 是静态字符串字面量时,构建工具会将其重写为一个运行时辅助函数,以解析最终 URL。像 /img/nuxt.png 这样的公共路径会被包装起来,以便在页面渲染时应用你的 app.baseURL,而像 ~/assets/img/nuxt.png 这样的打包路径还会进一步变成一个导入,解析为带哈希的输出文件。

<template>
  <!-- 静态路径会被重写:运行时会应用 app.baseURL,并且打包文件会带有哈希。 -->
  <img src="/img/nuxt.png">
  <img src="~/assets/img/nuxt.png">
</template>

由于 app.baseURL 是在运行时应用的,即使基础 URL 只在部署时才知道(例如通过 NUXT_APP_BASE_URL 设置),静态公共路径也能正常工作,而且无论该文件是否经过构建处理都可以使用。只有构建工具能够看到的字面量路径才会发生这种解析。

绑定的 :src 如果其值是在运行时组装出来的,对构建工具来说是不可见的,因此不会发生任何重写。该字符串会原样使用:

<template>
  <!-- 这样不起作用:路径是在运行时构建的,因此 Vite 从未将其视为导入。 -->
  <img :src="`~/assets/img/${name}.png`">
</template>

因此,像 /img/${name}.png 这样的运行时构建的公共路径不会自动添加 app.baseURL 前缀。如果你的应用部署在源站根路径下方,请自己使用 useRuntimeConfig().app.baseURL 为其添加前缀(例如通过 joinURL)。

下面的各节介绍了当路径只在运行时才知道时,如何处理每种情况。

公共资源

如果文件不需要被处理或哈希化,请将它们放在 public/ 目录中,并通过 URL 引用:

app/app.vue
<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 的默认构建器。

当可能的文件是已知的时,请显式列出它们的导入:

app/app.vue
<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>

每个导入都有一个字面量路径,因此 Vite 可以在构建时找到这两个文件,而运行时只加载选中的模块。

当许多文件共享同一目录和扩展名时,使用变量动态导入来代替逐个列出每个文件:

async function getImageUrl (name: string) {
  const image = await import(`./assets/img/${name}.png?url`)
  return image.default
}

在这个示例中,只有文件名可以是动态的。将目录和扩展名保留在导入中,可以让 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 导入默认是懒加载的。如果 URL 必须同步可用,请添加 eager: true

const images = import.meta.glob<string>('./assets/img/*.{png,jpg,svg}', {
  query: '?url',
  import: 'default',
  eager: true,
})

每个匹配的资源仍然会包含在构建输出中。懒加载导入会按需加载每个匹配项,而 eager glob 会一次性加载所有匹配项,可能会增加初始 JavaScript 体积,或者将小资源内联。

在将懒加载导入的 URL 用于服务端渲染的标记之前,请先 await 该导入。Vite 的 new URL(..., import.meta.url) 模式 不适用于 SSR。