# Vue-CLI 搭建 Web 框架配置工具

前段时间用 Vue-CLI@4 搭建了一个 admin 后台管理系统,对里面用到的配置工具进行总结。

# css-loader (opens new window)

css-loader interprets @import and url() like import/require() and will resolve them.

css-loader 解析 css 的 @importurl() ,就像 js 解析 import/require() 一样

url(image.png) => require('./image.png')
@import 'style.css' => require('./style.css')
1
2

此外还支持 CSS Modules (opens new window)

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        loader: "css-loader",
        options: {
          modules: true,
        },
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# style-loader (opens new window)

style-loader Inject CSS into the DOM, 即将 vue 文件里面的样式通过 <style> 标签注入到 HTML head.

// style.css
body {
  background: green;
}

// components.js
import './style.css';

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# mini-css-extract-plugin (opens new window)

将 vue 文件里面的样式提取到单独的 css 文件

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS.

development 模式使用 style-loader

production 模式使用 mini-css-extract-plugin

# file-loader (opens new window)

解析文件地址

resolves import/require() on a file into a url and emits the file into the output directory.

// file.js
import img from './file.png'

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# url-loader (opens new window)

将 files 转换成 base64 数据,减少 HTTP 请求次数

A loader for webpack which transforms files into base64 URIs.

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192, // Specifying the maximum size of a file in bytes.
            },
          },
        ],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Vue-CLI 通过 file-loader版本哈希值 和正确的 公共基础路径 来决定最终的文件路径,再用 url-loader 将小于 4kb 的资源内联,以减少 HTTP 请求次数。

// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('images')
        .use('url-loader')
          .loader('url-loader')
          .tap(options => Object.assign(options, { limit: 4096 }))
  }
}
1
2
3
4
5
6
7
8
9
10

# svg-sprite-loader (opens new window)

Webpack loader for creating SVG sprites.

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
            },
          },
        ],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Sass (opens new window)

Sass is an extension of CSS, adding nested rules, variables, mixins, selector inheritance, and more.

是对 CSS 扩展,类似的还有 less (opens new window), Stylus (opens new window).

文档:英文 (opens new window)/中文 (opens new window)

# sass-loader (opens new window)

npm install sass-loader sass -D
1
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# PostCSS (opens new window)

A tool for transforming CSS with JavaScript.

是一个用 JavaScript 转换 CSS 的工具,有很多插件可供使用,例如:Autoprefixer, postcss-preset-env, stylelint。

# postcss-loader (opens new window)

Loader to process CSS with PostCSS

npm install postcss-loader postcss -D
1

# Autoprefixer (opens new window)

自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮你自动为 CSS 规则添加前缀。

Autoprefixer CSS online (opens new window)

# postcss-preset-env (opens new window)

使用新的 CSS 语法,官网 (opens new window)

# stylelint (opens new window)

一个现代化 CSS 代码检查工具,官网 (opens new window)

# browserslist (opens new window)

确定项目支持的浏览器和Node.js的版本

The config to share target browsers and Node.js versions between different front-end tools.

"browserslist": [
  "> 1%",
  "last 2 versions",
  "not dead"
]
1
2
3
4
5
## Check what browsers will be selected by some query
npx browserslist "last 1 version, >1%"

## In your project directory to see project’s target browsers
npx browserslist
1
2
3
4
5

# Can I Use (opens new window)

查看哪些浏览器支持某些特性,或者使用 compat-table (opens new window)

# CSS Modules (opens new window)

CSS Modules 用法教程 (opens new window)

加入了局部作用域和模块依赖。

CSS Modules 必须通过向 css-loader 传入 modules: true 来开启,默认是如果文件名匹配 /\.module\.\w+$/i, 则开启

<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>
1
2
3
4
5
6
7
8

Vue 模板可以使用 <scoped>

# style-resources-loader (opens new window)

注入 css 共用的 variables, mixins, functions

This loader is a CSS processor resources loader for webpack, which injects your style resources (e.g. variables, mixins) into multiple imported css, sass, scss, less, stylus modules.

It's mainly used to

  • share your variables, mixins, functions across all style files, so you don't need to @import them manually.
  • override variables in style files provided by other libraries (e.g. ant-design (opens new window)) and customize your own theme.
module.exports = {
  chainWebpack: config => {
    const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
    types.forEach(type => addStyleResource(config.module.rule('stylus').oneOf(type)))
  },
}

function addStyleResource (rule) {
  rule.use('style-resource')
    .loader('style-resources-loader')
    .options({
      patterns: [
        path.resolve(__dirname, './src/styles/imports.styl'),
      ],
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

也可以使用 vue-cli-plugin-style-resources-loader (opens new window)

Sass 也可以使用 sass-resources-loader

# webpack-chain (opens new window)

使用一个链式 API 来生成和简化 webpack 的配置的修改。

# Babel (opens new window)

是一个 JavaScript 编译器,能让你使用最新的 JavaScript 语法。

# @babel/core (opens new window)

The core functionality of Babel.

# @babel/cli (opens new window)

A tool that allows you to use babel from the terminal.

# @babel/polyfill (opens new window)

Babel includes a polyfill that includes a custom regenerator runtime (opens new window) and core-js (opens new window).

Deprecated

直接使用 core-js/stable (to polyfill ECMAScript features) and regenerator-runtime/runtime (needed to use transpiled generator functions)

# core-js (opens new window)

Modular standard library for JavaScript. Includes polyfills for ECMAScript up to 2021 (opens new window): promises (opens new window), symbols (opens new window), collections (opens new window), iterators, typed arrays (opens new window), many other features, ECMAScript proposals (opens new window), some cross-platform WHATWG / W3C features and proposals (opens new window) like URL (opens new window). You can load only required features or use it without global namespace pollution.

# regenerator (opens new window)

This package implements a fully-functional source transformation that takes the syntax for generators/yield from ECMAScript 2015 or ES2015 (opens new window) and Asynchronous Iteration (opens new window) proposal and spits out efficient JS-of-today (ES5) that behaves the same way.

# @babel/preset-env (opens new window)

preset is pre-determined set of plugins.

@babel/preset-env是一个智能预设,基于项目支持的浏览器,允许您使用最新的 JavaScript 语法。

它推荐使用 .browserslistrc (opens new window) 来确定目标浏览器

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
        },
        "useBuiltIns": "usage",
      }
    ]
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

"useBuiltIns: usage" will only include the polyfills you need.

# Stage 0,1,2,3,4 (opens new window)

  1. Strawman:任何尚未提交为正式提案的讨论、想法、改变或对已有规范的补充建议
  2. Proposal:升级为正式化的提案
  3. Draft:有了初始的规范,此阶段通过 polyfill,开发者可以开始使用这一阶段的草案了,一些浏览器引擎也会逐步对这一阶段的规范的提供原生支持,此外通过使用构建工具也可以编译源代码为现有引擎可以执行的代码,这些方法都使得这一阶段的草案可以开始使用了
  4. Candidate:候选推荐规范,这一阶段之后变化就不会那么大了
  5. Finished:将包含在 ECMAScript 的下一个修订版中

# @vue/cli-plugin-babel (opens new window)

Uses Babel 7 + babel-loader (opens new window) + @vue/babel-preset-app (opens new window) by default, but can be configured via babel.config.js to use any other Babel presets or plugins.

@vue/babel-preset-app (opens new window) 是通过 @babel/preset-env (opens new window)browserslist (opens new window) 配置来决定项目需要的 polyfill (opens new window). 这是在所有 Vue CLI 项目中使用的默认Babel预设。

# html-webpack-plugin (opens new window)

根据模板生成 index.html 文件

# minify

If the minify option is set to true (the default when webpack's mode is 'production'), the generated HTML will be minified using html-minifier-terser (opens new window) and the following options:

{
  collapseWhitespace: true,
  keepClosingSlash: true,
  removeComments: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  useShortDoctype: true
}
1
2
3
4
5
6
7
8
9

# html-minifier-terser (opens new window)

HTML minifier. 为了能压缩模板 index.html 里自己写的 <style> (例如自定义开机 loading),需要配置 minifyCSS: true

config.plugin('html')
  .tap(args => {
  	args[0].minify = {
      collapseWhitespace: true,
      keepClosingSlash: true,
      removeComments: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      useShortDoctype: true,
      minifyCSS: true // here
    }
    return args
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# script-ext-html-webpack-plugin (opens new window)

This is an extension plugin for the webpack (opens new window) plugin html-webpack-plugin (opens new window)

This plugin simplifies the creation of HTML files to serve your webpack bundles.

比如 inline runtime 模块

config
  .plugin('ScriptExtHtmlWebpackPlugin')
  .after('html')
  .use('script-ext-html-webpack-plugin', [{
    // 匹配 runtimeChunk name. 格式 "runtime.hash.js"
    inline: /runtime\..*\.js$/
  }])
  .end()
1
2
3
4
5
6
7
8

# style-ext-html-webpack-plugin (opens new window)

This extension plugin builds on this by moving the CSS content generated by the extract plugins from an external CSS file to an internal <style> element.

# compression-webpack-plugin (opens new window)

gzip 压缩

一般 nginx 服务器配置会开启 gzip,所以前端一般不需要压缩。

# webpack-bundle-analyzer (opens new window)

Visualize size of webpack output files with an interactive zoomable treemap.

Vue-CLI 内置 bundle analyzer

"scripts": {
  "report": "vue-cli-service build --report",
}
1
2
3

# babel-plugin-dynamic-import-node (opens new window)

Babel plugin to transpile import() to a deferred require(), for node

有何用处? (opens new window)

# sass-resources-loader (opens new window)

Load your SASS resources into every required SASS module. So you can use your shared variables, mixins and functions across all SASS styles without manually loading them in each file.

简而言之,通过这个 plugin,就不需要到处 import sass 样式文件了

// vue.config.js
const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach(item => {
  item
    .use('sass-resources-loader')
    .loader('sass-resources-loader')
    .options({
      // Provide path to the file with resources
      resources: resolve('src/styles/variables.scss'),
    })
    .end()
})
1
2
3
4
5
6
7
8
9
10
11
12

# ProvidePlugin (opens new window)

Automatically load modules instead of having to import or require them everywhere.

简而言之,通过这个 plugin,就不需要到处 import 或者 require ,例如 uni-app 里的 uni 对象

new webpack.ProvidePlugin({
  identifier: ['module1', 'property1'],
  // ...
});
1
2
3
4

By default, module resolution path is current folder (./**) and node_modules

It is also possible to specify full path:

const path = require('path');

new webpack.ProvidePlugin({
  identifier: path.resolve(path.join(__dirname, 'src/module1')),
  // ...
});
1
2
3
4
5
6

配置

// vue.config.js
const webpack = require('webpack')
const path = require('path')
function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  chainWebpack: config => {
    config
      .plugin('provide')
      .use(webpack.ProvidePlugin, [{
        uni: resolve('src/utils/uni/index.js'),
      }])
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# DefinePlugin (opens new window)

创建一个在编译时可以配置的全局常量,Vue CLI 其实就是使用这个定义 环境变量 (opens new window)

# lint-staged (opens new window)

提交代码前进行 lint 检查。

@vue/cli-service 也会安装 yorkie (opens new window),它会让你在 package.jsongitHooks 字段中方便地指定 Git hook:

yorkie fork 自 husky (opens new window) 并且与后者不兼容。

"gitHooks": {
  "pre-commit": "lint-staged"
},
"lint-staged": {
  "src/**/*.{js,vue}": [
    "vue-cli-service lint",
    "prettier --write"
  ]
}
1
2
3
4
5
6
7
8
9

# HashedModuleIdsPlugin (opens new window)

固定住 Module Id, 参考手摸手,带你用合理的姿势使用webpack4(下) (opens new window)

Webpack 5 使用 optimization.moduleIds: 'deterministic',更多详情请参考 optimization.moduleIds (opens new window)

# NamedModulesPlugin (opens new window)

NamedModulesPluginHashedModuleIdsPlugin 原理是相同的,将文件路径作为 Module Id, 用于开发环境。

Webpack 5 使用 optimization.moduleIds: 'named'

# NamedChunksPlugin

固定住 Chuck Id, 参考手摸手,带你用合理的姿势使用webpack4(下) (opens new window)

# 自定义 nameResolver

const seen = new Set();
const nameLength = 4;

new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  const modules = Array.from(chunk.modulesIterable);
  if (modules.length > 1) {
    const hash = require("hash-sum");
    const joinedHash = hash(modules.map(m => m.id).join("_"));
    let len = nameLength;
    while (seen.has(joinedHash.substr(0, len))) len++;
    seen.add(joinedHash.substr(0, len));
    return `chunk-${joinedHash.substr(0, len)}`;
  } else {
    return modules[0].id;
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Webpack 5 生成环境使用 optimization.chunkIds: 'deterministic',开发环境使用 optimization.chunkIds: 'named'

# ESLint (opens new window)

代码格式校验

# @vue/cli-plugin-eslint (opens new window)

ESLint plugin for vue-cli. Inject vue-cli-service lint command and install eslint and eslint-plugin-vue (opens new window).

Lints and fixes files.

# eslint-plugin-vue (opens new window)

Official ESLint plugin for Vue.js.

This plugin allows us to check the <template> and <script> of .vue files with ESLint, as well as Vue code in .js files.

  • Finds syntax errors.
  • Finds the wrong use of Vue.js Directives
  • Finds the violation for Vue.js Style Guide

三个分类

  1. 必要 plugin:vue/essential
  2. 强烈推荐 plugin:vue/strongly-recommended
  3. 推荐 plugin:vue/recommended

# babel-eslint (opens new window)

Allows you to lint all valid Babel code with the fantastic ESLint (opens new window).

babel-eslint is now @babel/eslint-parser

# Vue CLI 的配置

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    "plugin:vue/essential",  // eslint-plugin-vue
    "eslint:recommended",    // eslint
    "@vue/prettier"          // @vue/eslint-config-prettier
  ],
  parserOptions: {
    parser: "babel-eslint"
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# prettier (opens new window)

代码格式化。另一个是 js-beautify (opens new window).

官网 (opens new window)

# 安装

npm install --save-dev --save-exact prettier
1

可以设置 webstormprettier 格式化代码,setting 里面搜索 pretter.

# eslint-plugin-prettier (opens new window)

Runs Prettier (opens new window) as an ESLint (opens new window) rule and reports differences as individual ESLint issues.

# @vue/eslint-config-prettier (opens new window)

Turns off all rules that are unnecessary or might conflict with Prettier (opens new window).

是用于 Vue CLI 的 eslint-config-prettier (opens new window)

# 问题

prettier 相对于我的习惯来说有以下几个问题,可以持续关注一下 prettier 的更新

  1. 设置 "jsxBracketSameLine": true, 对 vue template 不起作用
  2. 对象数组导致行数太多
  3. then() 希望能换到新的一行去
  4. 需要注意,因为设置了 "htmlWhitespaceSensitivity": "ignore", 可能导致 html 显示错误,这个时候就需要 <!-- prettier-ignore -->

# webpack-bundle-analyzer (opens new window)

bundle 分析

Vue-CLI 集成了这个插件,可以这样使用

$ vue-cli-service build --report
1