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 面向对象编程 聊聊对象的事
Sep 17 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
Feb 21 Javascript
Javascript函数的参数
Jul 16 Javascript
jQuery图片轮播(二)利用构造函数和原型创建对象以实现继承
Dec 06 Javascript
jQuery删除当前节点元素
Dec 07 Javascript
Bootstrap 网格系统布局详解
Mar 19 Javascript
删除table表格行的实例讲解
Sep 21 Javascript
vue中element-ui表格缩略图悬浮放大功能的实例代码
Jun 26 Javascript
JS实现方形抽奖效果
Aug 27 Javascript
如何解决React官方脚手架不支持Less的问题(小结)
Sep 12 Javascript
vue 移动端适配方案详解
Nov 15 Javascript
微信小程序 wx.getUserInfo引导用户授权问题实例分析
Mar 09 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
一个图形显示IP的PHP程序代码
2007/10/19 PHP
PHP优于Node.js的五大理由分享
2012/09/15 PHP
PHP判断浏览器、判断语言代码分享
2015/03/05 PHP
PHP6连接SQLServer2005的三部曲
2016/04/15 PHP
PHP使用微信开发模式实现搜索已发送图文及匹配关键字回复的方法
2017/09/13 PHP
laravel框架中路由设置,路由参数和路由命名实例分析
2019/11/23 PHP
php7 新增功能实例总结
2020/05/25 PHP
javascript面向对象包装类Class封装类库剖析
2013/01/24 Javascript
禁止选中文字兼容IE、Chrome、FF等
2013/09/04 Javascript
实现只能输入数字的input不用replace方法
2013/09/12 Javascript
js判断手机和pc端选择不同执行事件的方法
2015/01/30 Javascript
浅谈javascript中的call、apply、bind
2016/03/06 Javascript
基于JS实现Android,iOS一个手势动画效果
2016/04/27 Javascript
瀑布流的实现方式(原生js+jquery+css3)
2020/06/28 Javascript
JS实现图文并茂的tab选项卡效果示例【附demo源码下载】
2016/09/21 Javascript
Angular使用ng-messages与PHP进行表单数据验证
2016/12/28 Javascript
webpack打包单页面如何引用的js
2017/06/07 Javascript
ionic2自定义cordova插件开发以及使用(Android)
2017/06/19 Javascript
Bootstrap与Angularjs的模态框实例代码
2017/08/03 Javascript
解决vue页面刷新或者后退参数丢失的问题
2018/03/13 Javascript
详解SPA中前端路由基本原理与实现方式
2018/09/12 Javascript
el-table树形表格表单验证(列表生成序号)
2020/05/31 Javascript
python设置值及NaN值处理方法
2018/07/03 Python
Selenium元素的常用操作方法分析
2018/08/10 Python
Python爬虫——爬取豆瓣电影Top250代码实例
2019/04/17 Python
Python基础之字符串常见操作经典实例详解
2020/02/26 Python
英国领先的豪华时尚家居网上商店:Amara
2019/08/12 全球购物
设计师个人求职信范文
2014/02/02 职场文书
合同协议书格式
2014/04/18 职场文书
个人委托书怎么写
2014/09/17 职场文书
民事诉讼代理授权委托书范本
2014/10/08 职场文书
店长岗位职责
2015/02/11 职场文书
创业计划书之干洗店
2019/09/10 职场文书
详解CSS伪元素的妙用单标签之美
2021/05/25 HTML / CSS
PostgreSQL自动更新时间戳实例代码
2021/11/27 PostgreSQL
Spring Security动态权限的实现方法详解
2022/06/16 Java/Android