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 相关文章推荐
JavaScript使用过程中需要注意的地方和一些基本语法
Aug 26 Javascript
javascript算法学习(直接插入排序)
Apr 12 Javascript
jQuery EasyUI API 中文文档 - Documentation 文档
Sep 29 Javascript
JavaScript的类型、值和变量小结
Jul 09 Javascript
RequireJS多页面应用实例分析
Jun 29 Javascript
详解js实现线段交点的三种算法
Aug 09 Javascript
react学习笔记之state以及setState的使用
Dec 07 Javascript
在Vue项目中引入腾讯验证码服务的教程
Apr 03 Javascript
浅谈angular2子组件的事件传递(任意组件事件传递)
Sep 30 Javascript
微信小程序云开发之数据库操作
May 18 Javascript
java实现单链表增删改查的实例代码详解
Aug 30 Javascript
JavaScript 事件捕获冒泡与捕获详情
Nov 11 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
PHP4 与 MySQL 交互使用
2006/10/09 PHP
JpGraph php柱状图使用介绍
2011/08/23 PHP
浅谈PDO的rowCount函数
2015/06/18 PHP
Laravel使用Queue队列的技巧汇总
2019/09/02 PHP
JavaScript italics方法入门实例(把字符串显示为斜体)
2014/10/17 Javascript
实现js保留小数点后N位的代码
2014/11/13 Javascript
JavaScript判断前缀、后缀是否是空格的方法
2015/04/15 Javascript
javascript对象的创建和访问
2016/03/08 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
jQuery 生成svg矢量二维码
2016/08/09 Javascript
基于javascript实现按圆形排列DIV元素(二)
2016/12/02 Javascript
BootStrap实现轮播图效果(收藏)
2016/12/30 Javascript
angularjs实现简单的购物车功能
2017/09/21 Javascript
基于vue组件实现猜数字游戏
2020/05/28 Javascript
使用Angular CLI从蓝本生成代码详解
2018/03/24 Javascript
vue实现底部菜单功能
2018/07/24 Javascript
微信小程序实现保存图片到相册功能
2018/11/30 Javascript
Jquery高级应用Deferred对象原理及使用实例
2020/05/28 jQuery
js实现mp3录音通过websocket实时传送+简易波形图效果
2020/06/12 Javascript
解决await在forEach中不起作用的问题
2021/02/25 Javascript
Python的time模块中的常用方法整理
2015/06/18 Python
python实现的系统实用log类实例
2015/06/30 Python
Python正则表达式匹配日期与时间的方法
2019/07/07 Python
Tensorflow tensor 数学运算和逻辑运算方式
2020/06/30 Python
Python环境管理virtualenv&amp;virtualenvwrapper的配置详解
2020/07/01 Python
巴西最大的体育用品商城:Netshoes巴西
2016/11/29 全球购物
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
证券期货行业个人的自我评价
2013/12/26 职场文书
酒店总经理职务说明书
2014/02/26 职场文书
党员领导干部承诺书
2014/05/28 职场文书
英语三分钟演讲稿
2014/08/19 职场文书
2015大学自主招生自荐信范文
2015/03/04 职场文书
单方投资意向书
2015/05/11 职场文书
专项资金申请报告
2015/05/15 职场文书
golang goroutine顺序输出方式
2021/04/29 Golang
关于redisson缓存序列化几枚大坑说明
2021/08/04 Redis