Vue CLI3搭建的项目中路径相关问题的解决


Posted in Javascript onSeptember 17, 2018

这是开头

最近在试水 Vue CLI 3,并且尝试配置一个多页面(多应用)项目出来,期间又遇到各种路径问题,于是...于是有了下面的唠叨。

以下都是基于 Vue CLI 3 来举例说明的,使用 2.x 版本的其实也类似

首先,参考 官方文档对静态资源处理的说明,并通过自己的实践,可以总结出以下内容

静态资源可以通过两种方式进行处理:

1、以下情况下,资源不会被 webpack 处理,而是被直接拷贝:

  1. 放置在 public 目录下,即使未被使用。
  2. 通过绝对路径被引用,即以 / 开头的路径。

2、以下情况下,资源会被 webpack 处理(URL的resolve、minify、uglify、转 base64 等):

  1. 使用 JavaScript 导入。
  2. 在 template/CSS 中通过相对路径(即以 . 开头或直接以文件(夹)名开头)被引用。
  3. URL 以 ~ 开头,其后的任何内容都会作为一个模块请求被解析。
  4. URL 以 @ 开头,它也会作为一个模块请求被解析(@ 是在 webpack 设置的 alias)。

我们应该根据实际情况去选择我们要引用的资源是否要被处理,然后用对应的、正确的方式去引用它们以达到目的。以下对使用绝对路径和相对路径的方法和注意事项进行描述。

使用绝对路径

默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上(对应选项 baseUrl: '/'),例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 baseUrl 为 /my-app/。正因为以上的可能情况,我们应该在打算引用纯静态资源(那些不被webpack处理的资源,一般就是 public 目录下的资源)的时候,都确保使用 baseUrl 作为 URL 的开头,以下列举在不同文件中配合 baseUrl 选项写绝对路径的使用方法和注意事项:

在入口html文件中使用

我们可以使用lodash template 语法插入 baseUrl:

<link rel="icon" href="<%= BASE_URL %>favicon.ico" rel="external nofollow" >

在 *.vue 中使用

我们可以通过 Vue CLI 提供的客户端环境变量 process.env.BASE_URL 来获取 baseUrl:

/* 在需要的组件中定义 baseUrl,然后在 <template> 下使用 */
<template>
  <div id="app">
    <img :src="imgUrl">
    <img :src="`${baseUrl}imgs/my_image.png`">
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      baseUrl: process.env.BASE_URL,
      isBigImg: Math.random() > 0.5
    }
  }
  computed: {
    // 动态地获取不同的静态资源
    imgUrl() {
      if (this.isBigImg) {
        return `${baseUrl}imgs/my_image_big.png`
      } else {
        return `${baseUrl}imgs/my_image.png`
      }
    }
  }
};
</script>

 
/* 个人建议可以在全局定义,减去在每个组件内定义的麻烦
Vue.prototype.$baseUrl = process.env.BASE_URL
// 在 <template> 下使用
<img :src="`${$baseUrl}imgs/my_image.png`">

在其他 js 模块中使用

import axios from 'axios';

const baseUrl = process.env.BASE_URL;

axios.defaults.baseURL = `http://www.example.com${baseUrl}api/`

在样式文件中使用(以 sass/scss 为例)

因为 sass 文件中无法获取环境变量或 webpack 内的配置,于是最直接的方法就是自定义一个变量,然后在每个需要使用到它的文件引用它。

// config.scss
$baseUrl: "/";

// icon.scss

@import "config"

.icon-test {
  display: inline-block;
  background: url($baseUrl + 'imgs/icon_test.png') no-repeat;
  width: 10px;
  height: 10px;
}

这样做还是有比较大的麻烦:

  • 如果生产环境和开发环境的 baseUrl 不同,每次转换环境去编译都要去手动修改这个变量,十分之麻烦而且可能出现错误;
  • 两处地方相同的定义,不方便代码的维护;
  • 在后续讲到的关于 相对路径 的坑会涉及到,每次引用 config.scss 的路径并不一定是一样的,且很容易出现编译错误;

那么,有没有什么办法能避免人工操作、避免多次的定义并且避免使用可能潜在错误的引用呢?幸亏的确是有的! sass-loader 提供了一个 data 选项,可以为全局注入变量或样式文件;

// vue.config.js

const baseUrl = process.env.NODE_ENV === 'production' ? '/sub/' : '/';

module.exports = {
  baseUrl,

  css: {
    loaderOptions: {
      sass: {
        data: `$baseUrl: "${baseUrl}";`
      }
    }
  }
}

这样我们就可以在全局的 `sass` 文件中使用 `$baseUrl` 这个变量了,而且在只定义一次的情况下,能根据编译环境变化而变化。

使用相对路径

使用相对路径也会存在一些坑,接下来会列举常见的关于相对路径的坑与解决方法:

JavaScript 动态引用资源,编译没报错,但页面上请求返回 404

有时候我们需要使用 JavaScript 动态的引用某些资源,且希望这些资源被 webpack 一同打包,我们先看这种做法:

computed: {
  background () {
    return `./bgs/${this.id}.jpg`
  }
}

我们会发现打包没报错,但是在页面上可以发现这些资源的请求都是 404。这是因为类似 ./bgs/${this.id}.jpg 这样的动态字符串在打包阶段不会被 webpack 识别为依赖,资源也就不会被打包了。为了让 webpack 识别这些依赖,我们可以这样做:

computed: {
  background () {
    return require('./bgs/' + this.id + '.jpg')
  }
}

通过使用 require() 让 webpack 将括号内的 URL 识别为一个依赖并传入对应的 loader 进行处理。

要特别注意,以上的例子中,./bgs/ 目录下的所有图片都会被打包,因为 webpack 无法得知页面在运行时会使用哪张图片,所以 webpack 会把所有的图片都打包了。

在 sass 中使用相对路径引用图片或字体文件,编译报错

先来看一个例子:

// 文件目录
// src
// |--assets
// |  |
// |  |-fonts
// |  |  |- iconfont.eot
// |  |
// |  |-css
// |    |
// |    |-iconfont.scss
// |
// |--app.vue
// iconfont.scss
@font-face {
  font-family: "iconfont";
  src: url("../fonts/iconfont.eot");
  ...
}
// app.vue
<style lang="scss">
@import './assets/css/iconfont.scss'
</style>

往往我们在打包的时候会报错(以上例子会报错),说找不到 iconfont.eot。 sass-loader 文档中有对 url() 进行了单独的说明:

Since Sass/libsass does not provide url rewriting, all linked assets must be relative to the output.

If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.
If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).

大致意思就是, sass-loader 并不提供 url 的重写,所有的 scss 文件被 sass-loader 处理成最终的 CSS 后(编译过程中 url 不会被重写即保持原样),再传递给 css-loader 处理。也就是说,所有的 url 都是相对于输出的!在 Vue CLI 搭建的项目中,它们都是相对于使用这些 scss 文件的 vue 文件的。对于上例,是相对于 app.vue 的,因此报错。我们会很自然的会希望路径的引用是相对于 scss 文件本身的,sass-loader 文档中也给出了解决方案:

Add the missing url rewriting using the resolve-url-loader. Place it before the sass-loader in the loader chain.
Library authors usually provide a variable to modify the asset path. bootstrap-sass for example has an $icon-font-path. Check out this working bootstrap example.

第一个方法:使用 resolve-url-loader 来弥补 sass-loader 缺失的 url 重写功能,注意要放到 sass-loader 以前调用。

第二个方法:Library 作者一般都会提供变量,用来设置资源路径,如 bootstrap-sass 可以通过 $icon-font-path 来设置。参见this working bootstrap example。

这样看来解决的思路有两种:

  1. 写 url 的时候就写 vue 文件相对于资源的路径。这种方法较为暴力,当项目层级复杂了之后容易写错路径(加上现有的编辑器、IDE应该认为你写的路径是错误的)。当同个 scss 文件被多个不同层级的 vue 文件引用的时候,这种暴力的方法就行不通了!
  2. 使用第三方库补充 sass-loader 的路径重写功能,让路径的引用是相对于当前 scss 文件本身的。这个方法能较好的解决问题。

在这里提供一下我喜欢的方法。与其考虑 让路径的引用是相对于 scss 文件本身 或 让路径直接相对于 vue 文件,我们可以换个思路,让所有路径都是以根目录往下找,并让 webpack 对路径进行重写,但是直接用 /src/ 这种绝对路径的写法会让这些资源不被 webpack 打包。在前文提及到的,webpack 有个强大的机制,也就是 ~,通过在 url 前面添加 ~ 可以告诉 webpack 要把它当做一个模块来处理,也就是会被 webpack 打包。配合 webpack 提供的别名 @(/src),我们可以对上例做修改:

// iconfont.scss
@font-face {
  font-family: "iconfont";
  src: url("~@/assets/fonts/iconfont.eot");
  ...
}

这样子,通过 webpack 对模块的处理,可以正确通过编译!这样做的好处是可大大避免书写相对路径可能产生的错误,每次只需“无脑”从根目录往下找就是了,又可以减小依赖、减少配置项。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Exitjs获取DataView中图片文件名
Nov 26 Javascript
使用js声明数组,对象在jsp页面中(获得ajax得到json数据)
Nov 05 Javascript
jQuery操作表单常用控件方法小结
Mar 23 Javascript
javascript检测两个数组是否相似
May 19 Javascript
简单三步实现报表页面集成天气
Dec 15 Javascript
无法获取隐藏元素宽度和高度的解决方案
Mar 07 Javascript
jQuery插件HighCharts绘制2D柱状图、折线图的组合双轴图效果示例【附demo源码下载】
Mar 09 Javascript
纯js封装的ajax功能函数与用法示例
May 14 Javascript
基于vue通用表单解决方案的思考与分析
Mar 16 Javascript
ES6知识点整理之Proxy的应用实例详解
Apr 16 Javascript
vue中动态select的使用方法示例
Oct 28 Javascript
Nest.js环境变量配置与序列化详解
Feb 21 Javascript
浅谈webpack SplitChunksPlugin实用指南
Sep 17 #Javascript
vue的过滤器filter实例详解
Sep 17 #Javascript
一步一步的了解webpack4的splitChunk插件(小结)
Sep 17 #Javascript
React Router V4使用指南(精讲)
Sep 17 #Javascript
关于vue编译版本引入的问题的解决
Sep 17 #Javascript
理顺8个版本vue的区别(小结)
Sep 17 #Javascript
vue.js编译时给生成的文件增加版本号
Sep 17 #Javascript
You might like
很实用的一个完整email发送程序
2006/10/09 PHP
PHP中开发XML应用程序之基础篇 添加节点 删除节点 查询节点 查询节
2010/07/09 PHP
PHP中通过加号合并数组的一个简单方法分享
2011/01/27 PHP
PHP入门之常量简介和系统常量
2014/05/12 PHP
php错误日志简单配置方法
2016/07/11 PHP
Ext 今日学习总结
2010/09/19 Javascript
15个款优秀的 jQuery 图片特效插件推荐
2011/11/21 Javascript
jquery设置元素的readonly和disabled的写法
2013/09/22 Javascript
js与jQuery 获取父窗、子窗的iframe
2013/12/20 Javascript
jQuery中nextUntil()方法用法实例
2015/01/07 Javascript
浅谈下拉菜单中的Option对象
2015/05/10 Javascript
基于AngularJS实现页面滚动到底自动加载数据的功能
2015/10/16 Javascript
jquery获取url参数及url加参数的方法
2015/10/26 Javascript
jQuery Validate表单验证深入学习
2015/12/18 Javascript
bootstrap实现二级下拉菜单效果
2017/11/23 Javascript
[01:10:57]Liquid vs OG 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
[01:09:19]DOTA2-DPC中国联赛 正赛 VG vs Aster BO3 第二场 2月28日
2021/03/11 DOTA
python数据结构之二叉树的统计与转换实例
2014/04/29 Python
python批量修改文件编码格式的方法
2018/05/31 Python
对python程序内存泄漏调试的记录
2018/06/11 Python
Python3 使用cookiejar管理cookie的方法
2018/12/28 Python
pandas 如何分割字符的实现方法
2019/07/29 Python
通过Python编写一个简单登录功能过程解析
2019/09/04 Python
python圣诞树编写实例详解
2020/02/13 Python
Python下使用Trackbar实现绘图板
2020/10/27 Python
python 实现学生信息管理系统的示例
2020/11/28 Python
纽约服装和生活方式品牌:Saturdays NYC
2017/08/13 全球购物
欧洲顶级体育电子商务网站:SportsShoes.com
2018/03/27 全球购物
外企办公室竞聘演讲稿
2013/12/29 职场文书
2014新年寄语
2014/01/20 职场文书
竞选学委演讲稿
2014/09/13 职场文书
党员考试作弊检讨书1000字
2015/02/16 职场文书
我的1919观后感
2015/06/03 职场文书
党性修养心得体会2016
2016/01/21 职场文书
2016春季运动会开幕词
2016/03/04 职场文书
Vue2.0搭建脚手架
2022/03/13 Vue.js