# vue-cli3.x 脚手架

作者: 王振州 时间: 2020-12-17

# 1 介绍

Vue cli 是一个基于 Vue.js 进行快速开发的完整系统, 有三个组件:

  • cli@vue/cli 全局安装的 npm 包,提供了终端里的 vue命令(如:vue create 、vue serve 、vue ui 等命令)

  • cli 服务@vue/cli-service 是一个开发环境依赖。构建于 webpack 和 webpack-dev-server 之上(提供 如:serve、build 和 inspect 命令)

  • cli 插件:给 Vue 项目提供可选功能的 npm 包 (如: Babel 转译、ESLint 集成、unit 和 e2e 测试 等)

# 2 安装

    1. 全局安装过旧版本的 vue-cli(1.x 或 2.x)要先卸载它,否则跳过此步:
npm uninstall vue-cli -g
# or
yarn global remove vue-cli
1
2
3
    1. 安装@vue/cliVue cli 3的包名称由 vue-cli 改成了 @vue/cli)
npm install -g @vue/cli
# or
yarn global add @vue/cli
1
2
3
    1. 通过查看版本号验证是否安装成功
vue -V
vue --version
1
2

# 3 搭建项目

# 3.1 创建

vue create <Project Name>
1

<Project Name>: 文件名 不支持驼峰(含大写字母)

# 3.2 Please pick a preset 选择预设

? Please pick a preset
❯ oms (vue-router, vuex, dart-sass, babel, typescript, pwa, unit-jest) // 自定义保存的预设(之前我保存的)
  micro-front-end (vue-router, vuex, dart-sass, babel, pwa, eslint, unit-mocha, e2e-cypress) // 自定义保存的预设(之前我保存的)
  default (babel, eslint) // 默认设置非常适合快速创建一个新项目的原型,没有带任何辅助功能的 npm 包
  Manually select features // 自定义配置是我们所需要的面向生产的项目,提供可选功能的 npm
1
2
3
4
5

括号中的 npm 包是选择项选择后自动配置到项目中的, 如果选择最后一个(Manually select features)的话, 就会进行下一步选择配置项, 否则就会使用选择的预设或配置项进行生成项目

# 3.3 Check the features needed for your project 手动选择需要添加的配置项

? Check the features needed for your project:
 ◉ Babel  // 转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。
 ◯ TypeScript  // TypeScript是一个JavaScript(后缀.js)的超集(后缀.ts)包含并扩展了 JavaScript 的语法,需要被编译输出为 JavaScript在浏览器运行,目前较少人再用
 ◯ Progressive Web App (PWA) Support  // 渐进式Web应用程序
 ◉ Router  // vue-router(vue路由)
 ◉ Vuex  // vuex(vue的状态管理模式)
 ◉ CSS Pre-processors   // CSS 预处理器(如:less、sass)
 ◉ Linter / Formatter   // 代码风格检查和格式化(如:ESlint)
❯◉ Unit Testing         // 单元测试(unit tests)
 ◯ E2E Testing          // e2e(end to end) 测试
1
2
3
4
5
6
7
8
9
10

选择完后直接enter,然后会提示你选择对应功能的具体工具包, 本项目选择的是上面的选项

# 3.4 Use history mode for router 路由模式是否使用 history(选 Y), hash(选 N)

Vue-Router 利用了浏览器自身的 hash 模式和 history 模式的特性来实现前端路由

# 3.5 Pick a CSS pre-processor 选择 CSS 预处理器

为什么要选 dart-sass, 而不是 node-sass?

vue-cli4 默认选择的是 dart-sass

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
  Sass/SCSS (with dart-sass)
❯ Sass/SCSS (with node-sass)
  Less
  Stylus
1
2
3
4
5

主要为 css 解决浏览器兼容、简化 CSS 代码 等问题, 本项目使用的是Sass/SCSS (with node-sass)

# 3.6 ESLint ( Pick a linter / formatter config )

? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
❯ ESLint + Prettier
1
2
3
4
5

提供一个插件化的 javascript 代码检测工具,ESLint + Prettier 使用较多

# 3.7 何时检测

❯◉ Lint on save // 保存时
 ◯ Lint and fix on commit  // commit时检测和修复
1
2

# 3.8 单元测试

?  Pick a unit testing solution:
   Mocha + Chai //mocha灵活,只提供简单的测试结构,如果需要其他功能需要添加其他库/插件完成。必须在全局环境中安装
❯◉ Jest //安装配置简单,容易上手。内置Istanbul,可以查看到测试覆盖率,相较于Mocha:配置简洁、测试代码简洁、易于和babel集成、内置丰富的expect
1
2
3

# 3.9 配置如何存放

Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files  // 单独文件存放
  In package.json // 存放在package.json中
1
2
3

# 3.10 是否保存本次配置(y:记录本次配置,然后需要你起个名; n:不记录本次配置)

Save this as a preset for future projects? (y/N)
1

# 3.11 项目搭建完成

等命令运行完成项目就搭建完, 可以进入(cd)到对应的目录运行(yarn serve)

# 4 vue.config.js

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的vue 字段,但是注意这种写法需要你严格遵照 JSON的格式来写。

在说配置之前插播一下 process.env.NODE_ENV 的值 process.env.NODE_ENV 的值是执行命令是注入的临时 env 环境变量, 在该项目中, 开发环境默认的development, 发布环境默认是production 设置方式: window 环境 set key=value Mac/Linux 环境: export key=value; 命令窗口关闭自动销毁

下面是项目中关于 vue.config.js 的配置

// 关于下面三个插件会在后面进行详细介绍
// 代码压缩(内容)混淆插件
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
// 文件压缩(编码方式)插件
const CompressionPlugin = require("compression-webpack-plugin");
// 打包文件分析插件
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer/lib/BundleAnalyzerPlugin");

const path = require("path");

// 成产环境gzip文件的后缀
const productionGzipExtensions = ["js", "css"];

// 拼接文件路径(相对路径==>绝对路径)
function resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  // 部署应用包时的基本 URL , (默认'/')
  baseUrl: process.env.NODE_ENV === "development" ? "/" : "./",
  outputDir: "dist", // 运行时生成的生产环境构建文件的目录(默认''dist'',构建之前会被清除)
  assetsDir: "", // 放置生成的静态资源(js、css、img、fonts)的(相对于 outputDir 的)目录(默认'')
  indexPath: "index.html", // 指定生成的 index.html 的输出路径(相对于 outputDir)也可以是一个绝对路径。
  pages: {
    //pages 里配置的路径和文件名在你的文档目录必须存在 否则启动服务会报错
    index: {
      //除了 entry 之外都是可选的
      entry: "src/index/main.js", // page 的入口,每个“page”应该有一个对应的 JavaScript 入口文件
      template: "public/index.html", // 模板来源
      filename: "index.html", // 在 dist/index.html 的输出
      title: "国土空间规划一张图实施监督系统", // 当使用 title 选项时,在 template 中使用:<title><%= htmlWebpackPlugin.options.title %></title>
      chunks: ["chunk-vendors", "chunk-common", "index"], // 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk
    },
  },
  // 开发环境本地服务配置
  devServer: {
    host: "0.0.0.0", // host
    port: 9096, // 端口号
    open: true, // 是否默认打开浏览器
  },
  // 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。
  // 如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。
  // vue-echarts-v3需要babel转码,才能兼容ie
  // 参考 https://github.com/xlsdg/vue-echarts-v3#usage
  transpileDependencies: ["vue-echarts-v3", "iview"],
  css: {
    // css预设器配置项
    loaderOptions: {
      // 给 sass-loader 传递选项
      sass: {
        // data引入公用文件或全局变量,多个用 ; 进行分割
        data: `@import "@/styles/index.scss";`,
      },
    },
  },
  // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建
  // 在配置中没有看到babel相关配置? 内部使用了cache-loader, 其实内部还是使用了babel-loader @babel/cli @babel/core @babel/preset-env
  parallel: false,
  // [链式操作](https://cli.vuejs.org/zh/guide/webpack.html#链式操作-高级)
  // 基于[webpack-chain](https://github.com/neutrinojs/webpack-chain), 这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件等,并有机会在后期进入这些规则并对它们的选项进行修改。它允许我们更细粒度的控制其内部配置。添加新的loader 删除loader 修改loader等
  chainWebpack: (config) => {
    // 设置别名
    config.resolve.alias
      .set("@", resolve("src"))
      .set("YZT", resolve("src/views/YZT"))
      .set("JDGL", resolve("src/views/JDGL"))
      .set("FZBZ", resolve("src/views/FZBZ"))
      .set("JCYJ", resolve("src/views/JCYJ"))
      .set("ZBMX", resolve("src/views/ZBMX"))
      .set("YWXT", resolve("src/views/YWXT"))
      .set("MXXT", resolve("src/views/MXXT"))
      .set("FZSC", resolve("src/views/FZSC"));

    // 鼠标指针样式
    config.module
      .rule("mouse")
      .test(/\.(ico|cur)(\?.*)?$/)
      .use("file-loader")
      .loader("file-loader")
      .options({
        name: "[path][name].[ext]",
      })
      .end();

    // worker-loader
    config.module
      .rule("worker")
      .test(/\.worker\.js$/)
      .use("worker-loader")
      .loader("worker-loader")
      .options({
        inline: true,
      })
      .end();
    config.module.rule("js").exclude.add(/\.worker\.js$/);

    config.output.globalObject("this");

    config.plugin("html").tap((args) => {
      if (process.env.NODE_ENV === "development") {
        args[0].favicon = path.resolve("public/favicon_dev.ico");
      }
      return args;
    });

    // use svg
    const svgRule = config.module.rule("svg");
    svgRule.uses.clear();
    svgRule.include
      .add(resolve("src/icon/svg"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      })
      .end();
    // image exclude svg
    const imagesRule = config.module.rule("images");
    imagesRule
      .test(/\.(png|jpe?g|gif|webp|svg)(\?.*)?$/)
      .exclude.add(resolve("src/icon/svg"))
      .end();
  },
  // 该配置会通过 webpack-merge 合并到最终的 webpack 配置中
  configureWebpack: {
    // 生成的source-map类型
    // source-map	原始代码 最好的sourcemap质量有完整的结果,但是会很慢
    // eval-source-map	原始代码 同样道理,但是最高的质量和最低的性能
    // cheap-module-eval-source-map	原始代码(只有行内) 同样道理,但是更高的质量和更低的性能
    // cheap-eval-source-map	转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl
    // eval	生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap
    // cheap-source-map	转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用
    // cheap-module-source-map	原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射
    //
    // eval	使用eval包裹模块代码
    // source-map	产生.map文件
    // cheap	不包含列信息, 也不包含loader的sourcemap
    // module	包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
    // inline	将.map作为DataURI嵌入,不单独生成.map文件
    devtool:
      process.env.NODE_ENV === "development"
        ? "cheap-module-eval-source-map"
        : "source-map",
    // 优化
    // 从 webpack 4 开始,会根据你选择的 mode 来执行不同的优化, 不过所有的优化还是可以手动配置和重写。
    optimization: {
      // 允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)。
      minimizer: [
        new UglifyJsPlugin({
          uglifyOptions: {
            warnings: false, // 是否抛出警告
            compress: {
              drop_console: false, // 生产环境是否自动去掉console
              drop_debugger: true, // 生产环境是否自动去掉debugger
            },
          },
          parallel: true,
          sourceMap: true,
        }),
      ],
    },
    // webpack 插件
    plugins:
      process.env.NODE_ENV === "production"
        ? [
            // gzip
            new CompressionPlugin({
              filename: "[path].gz[query]", // 文件名
              algorithm: "gzip", // 压缩方式
              test: new RegExp(
                "\\.(" + productionGzipExtensions.join("|") + ")$"
              ), // 处理所有匹配此 {RegExp} 的资源
              threshold: 10240, // 压缩阈值
              minRatio: 0.8, // 压缩比
            }),
            // webpack 依赖库分析
            process.env.npm_config_report
              ? new BundleAnalyzerPlugin({
                  analyzerMode: "static",
                })
              : function none() {},
          ]
        : [],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

# 5 插件

# 5.1 uglifyjs-webpack-plugin 压缩混淆代码(基于文件内容)

字面意思是使 js 丑陋的 webpack 插件, 顾名思义用来压缩 js 文件 使用 uglifyjs 作为核心库; uglifyjs-webpack-plugin 源码地址

// UglifyJsPluginOptions 插件初始化参数
 test?: RegExp | RegExp[]; 测试匹配哪些文件 默认 test = /\.js(\?.\*)?\$/i
 include?: RegExp | RegExp[]; 包含哪些文件
 exclude?: RegExp | RegExp[]; 排除哪些文件
 cache?: boolean | string; 是否启用文件缓存,默认缓存在 node_modules/.cache/uglifyjs-webpack-plugin.目录
 parallel?: boolean | number; 使用多进程并行运行来提高构建速度 true cpu 核数 - 1
 sourceMap?: boolean; 是否生成 sourceMap cheap-source-map 不适用该插件
 uglifyOptions?: UglifyJsOptions;
     uglifyOptions: {
          warnings: false, // 是否抛出警告
          compress: {
            drop_console: false, // 生产环境是否自动去掉console
            drop_debugger: true // 生产环境是否自动去掉debugger
          }
        }
 extractComments?: boolean | RegExp | ((node: object, comment: string) => boolean) | ExtractCommentsOptions;
 warningsFilter?: (source: string) => boolean;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// UglifyJsPluginOptions 默认值
const {
  minify,
  uglifyOptions = {},
  test = /\.js(\?.\*)?\$/i,
  chunkFilter = () => true,
  warningsFilter = () => true,
  extractComments = false,
  sourceMap = false,
  cache = false,
  cacheKeys = (defaultCacheKeys) => defaultCacheKeys,
  parallel = false,
  include,
  exclude,
} = options;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5.2 compression-webpack-plugin 压缩(基于文件压缩方式)

参数

Name Type Default Description
test {RegExp} . 处理所有匹配此 {RegExp} 的资源
asset {String} [path].gz[query] 目标资源名称[file] 会被替换成原资源。[path] 会被替换成原资源路径,[query] 替换成原查询字符串
filename {Function} false 一个 {Function} (asset) => asset 函数,接收原资源名(通过 asset 选项)返回新资源名
algorithm {String Function} gzip
threshold {Number} 0 只处理比这个值大的资源。按字节计算
minRatio {Number} 0.8 只有压缩率比这个值小的资源才会被处理
deleteOriginalAssets {Boolean} false 是否删除原资源

作用是用来提升网络传输速率达到优化 web 页面加载时间的目的

我们知道基于 HTTP 协议, 在发起请求时候会通过 HTTP 请求头告诉服务器它期待服务器采取什么形式的压缩内容, 方便后续的解压缩处理

  • 基本原理

    • 浏览器请求资源文件时会自动带一个 Accept-Encoding 的请求头告诉服务器支持的压缩编码类型
    • 服务器配置开启 gzip 选项:接收客户端资源文件请求,查看请求头 Content-encoding 支持的压缩编码格式,如果是包含 gzip 那么在每次响应资源请求之前进行 gzip 编码压缩后再响应返回资源文件(在响应头会带上 Content-encoding: gzip)
    • 浏览器接收到响应后查看请求头是否带有 Content-encoding:gzip,如果有进行对返回的资源文件进行解压缩然后再进行解析渲染

但是有一些问题: 压缩文件耗费服务器 CPU(服务器需要压缩文件、浏览器解压文件) 那么我们就可以借助 CompressionWebpackPlugin 插件来提前对文件进行 Gzip 压缩, 这样服务器查找到有与源文件同名的.gz 文件就会直接读取,不会主动压缩,降低 cpu 负载,优化了服务器性能

# 5.2.1 服务器启用 gzip

  • node 端 nodejs 很幸福,只需 use 一个 compress 模块

    var compression = require("compression");
    var app = express();
    //尽量在其他中间件前使用 compression
    app.use(compression());
    
    1
    2
    3
    4
  • tomcat 找到 tomcat 的 server.xml 文件,找到其中 Connector 节点然后进行配置修改,具体配置如下

    <Connector port="80"protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" maxPostSize="0" useBodyEncodingForURI="true" compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml,application/javascript,text/css,text/plain,image/jpeg,application/json"/>
    
    1

# 5.3 webpack-bundle-analyzer/lib/BundleAnalyzerPlugin

这个插件的功能是生成代码分析报告,帮助提升代码质量和网站性能, 它可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项,文件是否重复,压缩后大小如何,针对这些,我们可以进行文件分割等操作。

项目中配置了report命令npm_config_report=true vue-cli-service build, 用于触发 vue.config.js 中配置的该插件

// webpack 依赖库分析
process.env.npm_config_report
  ? new BundleAnalyzerPlugin({
      analyzerMode: "static",
    })
  : function none() {};
1
2
3
4
5
6

// options 参数
{
  //  可以是`server`,`static`或`disabled`。
  //  在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。
  //  在“static”模式下,会生成带有报告的单个HTML文件。
  //  在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
  "analyzerMode": "server",
  //  将在“服务器”模式下使用的主机启动HTTP服务器。
  "analyzerHost": "127.0.0.1",
  //  将在“服务器”模式下使用的端口启动HTTP服务器。
  "analyzerPort": 8888,
  //  路径捆绑,将在`static`模式下生成的报告文件。
  //  相对于捆绑输出目录。
  "reportFilename": "report.html",
  //  模块大小默认显示在报告中。
  //  应该是`stat`,`parsed`或者`gzip`中的一个。
  //  有关更多信息,请参见“定义”一节。
  "defaultSizes": "parsed",
  //  在默认浏览器中自动打开报告
  "openAnalyzer": true,
  //  如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成
  "generateStatsFile": false,
  //  如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。
  //  相对于捆绑输出目录。
  "statsFilename": "stats.json",
  //  stats.toJson()方法的选项。
  //  例如,您可以使用`source:false`选项排除统计文件中模块的来源。
  //  在这里查看更多选项:https:  //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
  "statsOptions": null,
  "logLevel": "info" // 日志级别。可以是'信息','警告','错误'或'沉默'。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31