前言
在 Vue 项目开发中,我们经常需要引入各类静态文件 ------ 图片、字体、JS/CSS 插件、JSON 数据等。很多开发者(尤其是新手)会遇到「文件路径正确却加载失败」「打包后文件丢失」「动态引入不生效」等问题,核心原因是没搞懂 Vue 项目中两个静态文件目录的差异,以及不同场景下的正确引入方式。
本文将从「静态文件目录核心区别」入手,详细讲解 Vue 中各类静态文件的引入方法,覆盖「模板中引入」「脚本中引入」「样式中引入」三大场景,同时梳理常见坑点与解决方案,Vue 2 和 Vue 3 项目通用,看完就能彻底解决静态文件引入的所有问题。
一、先搞懂:Vue 中的两个核心静态文件目录
Vue 项目(无论是 Vue CLI 搭建的 Vue 2 还是 Vue 3)默认提供两个存放静态文件的目录:public 和 src/assets,这两个目录的编译处理方式、访问规则、使用场景完全不同,这是解决静态文件引入问题的基础。
1. 目录 1:src/assets(源码内静态资源)
-
核心特性 :被
webpack(Vue CLI 底层构建工具)编译处理、打包压缩、路径重写。 -
具体行为 :
- 文件会被
webpack解析,图片等资源会被压缩优化(如图片体积压缩、重命名为哈希值文件名,防止缓存问题)。 - 打包后,文件会被放入
dist/assets目录下(文件名可能被修改,如logo.png→logo.2e8f9d.png)。 - 路径是「相对路径」,需要遵循
webpack的模块解析规则,不能直接通过绝对路径访问。
- 文件会被
-
使用场景:项目内使用的「业务相关静态资源」,如组件内图片、全局样式、自定义字体、业务相关 JSON 数据等。
-
示例目录结构 :
src/ ├── assets/ │ ├── images/ # 组件内使用的图片 │ │ ├── logo.png │ │ └── bg.jpg │ ├── styles/ # 全局样式文件 │ │ └── global.css │ └── fonts/ # 自定义字体文件 │ └── iconfont.ttf
2. 目录 2:public(纯静态资源)
-
核心特性 :不被
webpack编译处理,直接复制到打包后的dist根目录,保持原文件名和目录结构。 -
具体行为 :
- 文件会被「原样复制」到
dist根目录,不会被压缩、重命名,目录结构也会保留(如public/js/third.js→dist/js/third.js)。 - 可以通过「绝对路径」直接访问,根路径对应
public目录(即/等价于public/)。 - 不支持
webpack的模块解析,无法在src源码中通过import或require引入。
- 文件会被「原样复制」到
-
使用场景 :
- 第三方不可修改的静态资源(如第三方 JS 插件、CDN 备用文件)。
- 需要通过绝对路径访问的文件(如
favicon.ico、index.html中引入的全局 JS/CSS)。 - 动态拼接路径访问的静态资源(如后端返回文件名称,前端拼接路径加载图片)。
-
示例目录结构 :
public/ ├── favicon.ico # 网站图标 ├── js/ # 第三方 JS 插件 │ └── echarts.min.js └── images/ # 需绝对路径访问的图片 └── banner.png
3. 核心区别对比表
| 对比维度 | src/assets |
public |
|---|---|---|
| webpack 处理 | 编译、压缩、重命名 | 不处理,原样复制 |
| 打包后目录 | dist/assets/(哈希文件名) |
dist/ 根目录(原文件名) |
| 访问方式 | 相对路径(import/require) | 绝对路径(/ 开头) |
| 缓存处理 | 哈希文件名,自动防缓存 | 需手动配置缓存(如添加版本号) |
| 适用场景 | 项目内业务静态资源 | 第三方资源、绝对路径访问资源 |
二、实操:各类静态文件的正确引入方式
下面按「引入场景」和「文件类型」拆分,详细讲解每种静态文件的正确引入方法,涵盖 Vue 模板、脚本、样式三大核心场景。
场景 1:在 Vue 模板(template)中引入静态文件
模板中主要引入图片、视频等可视化资源,分 assets 目录和 public 目录两种情况。
1. 引入 src/assets 下的文件
模板中引入 assets 下的文件,有两种常用方法,核心是「通过相对路径绑定」。
方法 1:直接使用相对路径(推荐,简单场景)
语法:src="./assets/xxx/xxx"(路径相对于当前组件文件)。
注意:
./表示当前目录,../表示上级目录,需根据组件与assets的相对位置调整路径。
示例(组件 src/components/HelloWorld.vue):
<template>
<div class="hello">
<!-- 引入 src/assets/images/logo.png -->
<img src="../assets/images/logo.png" alt="Vue 图标" class="logo">
</div>
</template>
<style scoped>
.logo {
width: 100px;
height: 100px;
}
</style>
方法 2:通过 require() 绑定(推荐,动态场景)
语法::src="require('@/assets/xxx/xxx')"(使用 v-bind 动态绑定,@ 是 Vue CLI 配置的 src 目录别名)。
优势:支持动态拼接部分路径(有限制),更灵活;
@等价于src/,无需关心相对目录层级,避免路径写错。
<template>
<div class="hello">
<!-- 引入 src/assets/images/bg.jpg,@ 等价于 src/ -->
<img :src="require('@/assets/images/bg.jpg')" alt="背景图" class="bg">
<!-- 简单动态引入:根据变量切换图片(文件名动态,目录固定) -->
<img :src="require(`@/assets/images/${imgName}.png`)" alt="动态图片">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
imgName: 'logo' // 动态切换图片文件名
}
}
}
</script>
注意:
require()中目录部分必须是固定的 ,不能全部动态拼接(如require(@/assets/images/${path})中,@/assets/images/必须固定,${path}是文件名),否则 webpack 无法在编译时解析文件。
2. 引入 public 下的文件
模板中引入 public 下的文件,直接使用「绝对路径」(/ 开头,对应 public 目录),无需关心相对路径。
语法:src="/xxx/xxx"(/ 等价于 public/,后续路径对应 public 内的目录结构)。
<template>
<div class="hello">
<!-- 引入 public/images/banner.png -->
<img src="/images/banner.png" alt="横幅图" class="banner">
<!-- 引入 public/favicon.ico -->
<link rel="icon" href="/favicon.ico">
</div>
</template>
注意:不要在模板中使用
./或../访问public下的文件,必须以/开头,否则打包后会路径失效。
场景 2:在 Vue 脚本(script)中引入静态文件
脚本中主要引入 JSON 数据、图片路径(用于动态绑定)、JS 插件等,同样分 assets 和 public 目录。
1. 引入 src/assets 下的文件
(1)引入 JSON 数据文件
直接使用 import 引入,webpack 会自动解析 JSON 文件,返回解析后的对象。
示例(src/assets/data/user.json 存在 JSON 文件):
json
// src/assets/data/user.json
{
"name": "张三",
"age": 28,
"gender": "男"
}
vue
<script>
// 引入 src/assets/data/user.json
import userData from '@/assets/data/user.json';
export default {
name: 'HelloWorld',
mounted() {
console.log('用户数据:', userData); // 输出:{ name: "张三", age: 28, gender: "男" }
}
}
</script>
(2)引入图片 / 其他资源(用于动态绑定)
使用 require() 引入,返回文件的「打包后路径」,可赋值给变量用于模板动态绑定。
<script>
export default {
name: 'HelloWorld',
data() {
return {
// 引入 src/assets/images/logo.png,返回打包后的路径
logoUrl: require('@/assets/images/logo.png'),
// 动态切换图片(目录固定,文件名动态)
imgList: [
require('@/assets/images/img1.png'),
require('@/assets/images/img2.png')
]
}
}
}
</script>
<template>
<div>
<img :src="logoUrl" alt="图标">
<img :src="imgList[0]" alt="图片1">
</div>
</template>
(3)引入 JS/CSS 样式文件
使用 import 直接引入,webpack 会自动将 CSS 注入到页面,JS 执行并合并打包。
<script>
// 引入 src/assets/styles/global.css
import '@/assets/styles/global.css';
// 引入 src/assets/js/utils.js(自定义工具类)
import { formatDate } from '@/assets/js/utils.js';
export default {
name: 'HelloWorld',
mounted() {
console.log(formatDate(new Date())); // 调用工具类方法
}
}
</script>
2. 引入 public 下的文件
public 下的文件无法在脚本中通过 import 或 require 引入(不被 webpack 解析),若需在脚本中访问,只能通过「绝对路径字符串」拼接,适用于动态加载场景。
示例(动态加载 public/images/ 下的图片,文件名由后端返回):
<script>
export default {
name: 'HelloWorld',
data() {
return {
// 后端返回的文件名
imgFileName: 'banner.png',
// 拼接绝对路径(/ 对应 public 目录)
imgUrl: ''
}
},
mounted() {
this.imgUrl = `/images/${this.imgFileName}`; // 最终路径:/images/banner.png
}
}
</script>
<template>
<img :src="imgUrl" alt="动态加载图片">
</template>
补充:若需引入
public下的 JS 插件(如public/js/echarts.min.js),推荐在public/index.html中直接通过<script>标签引入,全局可用。
场景 3:在 CSS 样式中引入静态文件
CSS 中主要引入背景图、自定义字体等资源,分 assets 和 public 目录,路径写法有差异。
1. 引入 src/assets 下的文件
使用「相对路径」或「~@ 路径」(~ 表示 webpack 模块根目录,@ 等价于 src/),推荐使用 ~@,无需关心 CSS 文件与 assets 的相对层级。
/* 方法 1:使用 ~@ 路径(推荐,无需关心相对层级) */
.bg {
width: 100%;
height: 300px;
/* 引入 src/assets/images/bg.jpg */
background: url(~@/assets/images/bg.jpg) no-repeat center center;
background-size: cover;
}
/* 方法 2:使用相对路径(需根据 CSS 文件与 assets 的相对位置调整) */
/* 当前 CSS 文件路径:src/assets/styles/global.css */
/* 目标图片路径:src/assets/images/bg.jpg */
/* 相对路径:../images/bg.jpg(../ 回到 assets 目录,再进入 images) */
.bg2 {
width: 100%;
height: 300px;
background: url(../images/bg.jpg) no-repeat center center;
background-size: cover;
}
/* 引入自定义字体(src/assets/fonts/iconfont.ttf) */
@font-face {
font-family: 'iconfont';
src: url(~@/assets/fonts/iconfont.ttf) format('truetype');
}
.icon {
font-family: 'iconfont';
}
2. 引入 public 下的文件
CSS 中引入 public 下的文件,使用「绝对路径」(/ 开头,对应 public 目录),注意:CSS 中的绝对路径是「相对于页面根目录」,与模板中的绝对路径规则一致。
/* 引入 public/images/banner.png */
.banner {
width: 100%;
height: 400px;
background: url(/images/banner.png) no-repeat center center;
background-size: cover;
}
注意:CSS 中引入
public下的文件,不要使用http://或https://硬编码绝对路径,否则打包后部署到不同域名会失效,使用/开头的相对根路径即可。
三、常见坑点与避坑指南
坑点 1:assets 下的文件路径写错,导致加载失败
- 问题表现:本地开发时提示「Module not found」,或打包后图片显示 404。
- 原因 :相对路径层级错误,或未使用
@/require()引入。 - 解决方案 :
- 优先使用
@表示src/目录,避免相对路径层级错误(如@/assets/images/logo.png)。 - 动态引入时必须使用
require(),不能直接拼接字符串(如:src="@/assets/images/${imgName}.png"错误,应使用:src="require(@/assets/images/${imgName}.png)")。
- 优先使用
坑点 2:public 下的文件使用相对路径引入,打包后失效
- 问题表现:本地开发时能正常加载,打包后部署到服务器显示 404。
- 原因 :
public下的文件未使用/开头的绝对路径,而是使用./或../相对路径。 - 解决方案 :所有
public下的文件,引入时均以/开头(如/images/banner.png),对应打包后的dist根目录。
坑点 3:动态加载 assets 下的文件,全部路径动态拼接
-
问题表现:webpack 编译报错「Critical dependency: the request of a dependency is an expression」。
-
原因 :
require()中全部路径都是动态的,webpack 无法在编译时解析文件(webpack 需要固定目录,才能提前打包)。 -
解决方案 :
require()中目录部分必须固定 ,只有文件名可以动态拼接,示例:// 正确:目录固定(@/assets/images/),文件名动态(${imgName}) require(`@/assets/images/${imgName}.png`); // 错误:全部路径动态 require(`${imgPath}.png`);
坑点 4:打包后 assets 下的图片文件名变成哈希值,无法通过固定名称访问
- 问题表现 :打包后图片文件名变成
logo.2e8f9d.png,后端返回固定文件名logo.png,无法匹配。 - 原因 :
assets下的文件被 webpack 重命名优化,用于防缓存。 - 解决方案 :将这类需要「固定文件名访问」的图片移到
public目录,通过绝对路径访问,避免 webpack 重命名。
坑点 5:自定义字体文件引入后,打包后显示乱码 / 不生效
- 问题表现:本地开发时字体正常,打包后字体不生效,浏览器控制台提示字体文件 404。
- 原因:webpack 对字体文件的解析配置缺失,或字体文件路径写错。
- 解决方案 :
- 确保字体文件放在
src/assets/fonts/下,使用~@路径引入。 - Vue CLI 已默认配置字体文件解析,无需额外配置,若手动修改
vue.config.js,需确保包含字体文件后缀(ttf、woff、woff2等)。
- 确保字体文件放在
四、总结与核心要点回顾
- 两个核心目录 :
src/assets(webpack 编译处理,相对路径引入)、public(原样复制,绝对路径引入),根据文件使用场景选择对应目录。 - 引入核心原则 :
src/assets:模板中用相对路径 /require(),脚本中用import/require(),CSS 中用~@/ 相对路径。public:所有场景均用/开头的绝对路径,不支持import/require()。
- 避坑核心 :动态引入
assets目录文件时,目录必须固定;public目录文件必须用绝对路径;避免打包后路径失效。 - 最佳实践 :业务相关静态资源优先放入
src/assets,享受 webpack 优化;第三方资源、需要固定文件名访问的资源放入public。