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 相关文章推荐
10个基于浏览器的JavaScript调试工具分享
Feb 07 Javascript
jquery获得页面元素的坐标值实现思路及代码
Apr 15 Javascript
调用innerHTML之后onclick失效问题的解决方法
Jan 28 Javascript
js(JavaScript)实现TAB标签切换效果的简单实例
Feb 26 Javascript
Javascript中的高阶函数介绍
Mar 15 Javascript
js+canvas简单绘制圆圈的方法
Jan 28 Javascript
[原创]Javascript 实现广告后加载 可加载百度谷歌联盟广告
May 11 Javascript
jQuery实现简单的手风琴效果
Apr 17 jQuery
使用JQuery自动完成插件Auto Complete详解
Jun 18 jQuery
浅析Vue中拆分视图层代码的5点建议
Aug 15 Javascript
Angular封装表单控件及思想总结
Dec 11 Javascript
原生js实现表格翻页和跳转
Sep 29 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
phpmyadmin里面导入sql语句格式的大量数据的方法
2010/06/05 PHP
详细解读PHP的Yii框架中登陆功能的实现
2015/08/21 PHP
Ubuntu server 11.04安装memcache及php使用memcache来存储session的方法
2016/05/31 PHP
利用 fsockopen() 函数开放端口扫描器的实例
2017/08/19 PHP
js弹出层之1:JQuery.Boxy (二)
2011/10/06 Javascript
jQuery焦点图切换简易插件制作过程全纪录
2014/08/27 Javascript
js实现的类似于asp数据字典的数据类型代码实例
2014/09/03 Javascript
javascript框架设计读书笔记之数组的扩展与修复
2014/12/02 Javascript
nodejs实现遍历文件夹并统计文件大小
2015/05/28 NodeJs
JavaScript中创建对象的模式汇总
2016/04/19 Javascript
jQuery常用样式操作实例分析(获取、设置、追加、删除、判断等)
2016/09/08 Javascript
vue.js中$watch的用法示例
2016/10/04 Javascript
使用Vue.js创建一个时间跟踪的单页应用
2016/11/28 Javascript
深入浅析Vue全局组件与局部组件的区别
2018/06/15 Javascript
简单两步使用node发送qq邮件的方法
2019/03/01 Javascript
javascript中this的用法实践分析
2019/07/29 Javascript
微信小程序JS加载esmap地图的实例详解
2019/09/04 Javascript
layui表格 列自动适应大小失效的解决方法
2019/09/06 Javascript
JavaScript实现放大镜效果代码示例
2020/04/29 Javascript
video.js添加自定义组件的方法
2020/12/09 Javascript
[02:35]DOTA2英雄基础教程 末日使者
2013/12/04 DOTA
[01:07:53]RNG vs VG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python结合opencv实现人脸检测与跟踪
2015/06/08 Python
Python中的descriptor描述器简明使用指南
2016/06/02 Python
python fabric实现远程部署
2017/01/05 Python
Python常见异常分类与处理方法
2017/06/04 Python
django rest framework 实现用户登录认证详解
2019/07/29 Python
Python3.7安装keras和TensorFlow的教程图解
2020/06/18 Python
python的slice notation的特殊用法详解
2019/12/27 Python
tensorflow通过模型文件,使用tensorboard查看其模型图Graph方式
2020/01/23 Python
matplotlib事件处理基础(事件绑定、事件属性)
2021/02/03 Python
学校三八妇女节活动情况总结
2014/03/09 职场文书
英文感谢信格式
2015/01/21 职场文书
2015年仓库工作总结
2015/04/09 职场文书
2015年商场工作总结
2015/04/27 职场文书
如何利用golang运用mysql数据库
2022/03/13 Golang