一个易维护的 webpack 工程【step-by-step】

287 阅读28分钟

前言

很多前端同学在快速实现产品经理验证性需求的时候,往往把功能点写在一个 html 页面上。单独的 html 页面确实可以快速搭建一个 demo,但是随着迭代的推进,越来越多的功能堆砌在了这个页面上,最终导致整个工程变得难以扩展、难以维护。

不仅如此,很多前端同学会直接在 html 页面上,使用 ES6+语法或者新的CSS语法,也会凭着自己的心情随意转换代码风格。这就导致了同样一套代码在不同版本的浏览器上会有不同的表现,大大降低了页面的稳定性和代码的可维护性。

开发人员一定要有远见!无论什么项目都应使用一个易扩展、易维护的架构。

避免给自己挖坑,将来反复代码重构。

随着前端技术的快速发展,越来越多成熟稳定的开源工具可以应用到项目当中。这些工具可以帮助我们提升开发效率,增强代码的可读性、健壮性,例如babelpostcss 这样的代码转义工具,以及eslintstylelint 这样的语法审查工具。

readme.jpeg

如果添加新的插件后,引起了文件报错,不要慌~ 吃口药,还能救!。我们可以把出现的问题当成普通 bug,例如添加 eslint 之后,大部分的文件报错,我们就把每一个报错的文件当成是一个"bug"。相信我,前端同学花不了多长时间就能解决一个这样的"bug"。

架构未必最优!如有好的建议,欢迎指出~

【step-by-step】1. webpack 工程

目录:

这是根据 官方文档 中的示例搭建的工程,只是增加了几个常用的 plugin。在 /examples 目录下的所有后继示例工程都是基于这个工程扩展的。我们会逐步在这个工程中添加 babeleslintpostcssstylelint 等插件。

如果,前端同学对于这个工程有诸多不清楚的地方,那么我强烈建议你先系统地学习一下 webpack,可以参考 官方文档,也可以看一下中文文档,里面的教程非常棒!!!

注意:中文文档更新不及时,里面的很多示例不适用于新版本的 webpack。因此,建议可以多多查看官方文档

1.1 目录结构

示例工程的目录结构:

|-- examples
    |-- index.html // html模板
    |-- package.json
    |-- build // 配置文件文件夹
    |   |-- webpack.base.js // webpack的通用配置
    |   |-- webpack.dev.js // webpack开发环境的配置
    |   |-- webpack.prod.js // webpack生产环境的配置
    |-- src
        |-- index.js // 入口文件
        |-- assets
            |-- style.css // 样式

1.2 文件分析

1.2.1 webpack 配置

开发环境和生产环境的构建目标差异很大:

  • 在开发环境中,我们需要强大的 热更新 功能,及 dev serverproxy
  • 在生产环境中,我们的目标则转向于关注更小的 bundle、更轻量级的 source map优化资源,以改善加载时间。

热更新:

  • 保留在完全重新加载页面期间丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
配置

本例是参考了 官方的例子 ,并做了一点调整。 中文文档 中的例子有点老,不适用于 webpack 4+。

遵循逻辑分离,我们通常为每个环境编写彼此独立的webpack 配置

build/webpack.base.js:

const path = require('path')

module.exports = {
  entry: {
    app: './src/index.js'
  },
  output: {
    filename: '[name].[hash].js',
    path: path.resolve(__dirname, '../dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

webpack.base.js 中,我们设置了 entryoutput ,并且在文件中引入了两个环境共用的插件。

注意: output.filename 不能使用 [chunkhash],只能使用 [hash],否则在 开发模式(dev) 下会报错。

build/webpack.dev.js:

// 合并数组和对象,但不是覆盖!!!
// https://www.npmjs.com/package/webpack-merge
const { merge } = require('webpack-merge')

// 生成 html5,并在body中使用script标签引入打包后的js文件。
// https://www.npmjs.com/package/html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

const base = require('./webpack.base')

module.exports = merge(base, {
  mode: 'development', // 声明开发模式
  devServer: {
    contentBase: '../dist',
    hot: true, // 热模块更新 - 局部更新
    host: '0.0.0.0', // 设置后,其他机器可以通过ip访问
    // port: '8080', // 端口
    quiet: false,
    clientLogLevel: 'warning',
    proxy: {} // 跨域代理
  },
  // 'cheap-module-eval-source-map'低开销的source-map,但只映射行数。
  // 'eval-source-map',初始化source map的时候比较慢,但是重新构建时,提供比较快的速度,并能正确映射出报错的位置
  // https://www.webpackjs.com/configuration/devtool/
  devtool: 'cheap-module-eval-source-map',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 生成的文件名
      template: 'index.html', // 使用的模板文件
      inject: true // 生成的script插到body底部
    })
  ]
})

webpack.dev.js 中,我们将 mode 设置为 development,并且为此环境添加了强大的 devtool (强大的 source map)和 devServer

build/webpack.prod.js:

const path = require('path')
// 合并数组和对象,但不是覆盖!!!
// https://www.npmjs.com/package/webpack-merge
const { merge } = require('webpack-merge') // 合并数组和对象,但不是覆盖!!!

// 生成 gzip 文件
// https://www.npmjs.com/package/compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin')

// 分析打包出来的文件
// https://www.npmjs.com/package/webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

// 将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap
// https://www.npmjs.com/package/mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// 生成 html5,并在body中使用script标签引入打包后的js文件。
// https://www.npmjs.com/package/html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

// 清理 dist 文件夹
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// 压缩器 用于代替webpack自带的压缩器。
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

const base = require('./webpack.base')
const config = {
  bundleAnalyzerReport: false,
  productionGzip: true
}

const webpackConfig = merge(base, {
  mode: 'production',
  output: {
    filename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, '../dist')
  },
  // source-map: 整个source map 作为独立文件生成,并未bundle添加一个引用注释。
  // https://www.webpackjs.com/configuration/devtool/
  devtool: 'source-map',
  optimization: {
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
    splitChunks: { chunks: 'all' }
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              esModule: true
            }
          },
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].[chunkhash].css'
    }),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      minify: {
        removeComments: true, // 移除html中的注释
        collapseWhitespace: true, // 去掉留白部分
        removeAttributeQuotes: true // 去掉属性中的引号
      },
      inject: true
    })
  ]
})

// 生成 gzip 文件
// https://www.npmjs.com/package/compression-webpack-plugin
if (config.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')
  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      test: new RegExp('\\.(js|css)$'), //只打包 js和css 文件
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

// 分析打包出来的文件
// https://www.npmjs.com/package/webpack-bundle-analyzer
if (config.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

webpack.prod.js 中,添加了 optimization.slitChunks.chunks ( SlitChunksPlugin ) ,可以将公共的依赖模块抽象到 chunk 中,或者抽象到一个新生成的 chunk 中。

mode

webpack 4+ 提供了 mode 配置选项,能够自动调用 webpack 的优化策略。默认值为 production。

string = 'production': 'none' | 'development' | 'production'

webpack 配置文件中

module.exports = {
  mode: 'development' // 默认是 production
}
  • 在 development(开发模式)下,会自动引用 NamedChunksPlugin 和 NameModulesPlugin。
  • 在 production(生产模式)下,会自动引用 FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin 及 TerserPlugin。
devtool

devtool 选项控制是否生成以及如何生成 source map

Source map 就是一个信息文件,里面储存着位置信息。也就是说,source map 能把转换后代码还原成转换前的样子。有了 source map,debug工具可以把压缩代码显示为原始代码。详细可参考:阮一峰-JavaScript Source Map 详解

webpack.dev.js 中,推荐使用以下两种 source map

  • cheap-module-eval-source-map ✅ : 低开销的 source-map ,但只映射行数。(推荐)
  • eval-source-map: 初始化 source map 的时候比较慢,但是重新构建时,提供比较快的速度,并能正确映射出报错的位置。

webpack.prod.js 中,推荐使用以下三种 source map

  • source-map ✅ : 整个 source map 作为独立文件生成,并为 bundle 添加一个引用注释。(推荐!需在nginx中,设置可访问.map 文件的ip 白名单,以保护代码安全。)
  • hidden-source-map: hidden-source-map 与 source-map 相同,但是不会为 bundle 添加引用注释。
  • nosource-source-map: 创建的 source map 不包括 源代码内容。只映射客户端上的堆栈信息,不会暴露所有源代码。

1.2.2 package.json

通常而言,当我们阅读一个项目的源代码时,最好是从 package.json开始。这样做可以方便我们找到项目的入口,也就是起始位置。

package.json:

{
  "name": "01-base",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "dev": "webpack-dev-server --config build/webpack.dev.js",
    "build": "webpack --config build/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.11.4",
    "@babel/plugin-transform-runtime": "^7.11.0",
    "@babel/preset-env": "^7.11.0",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "compression-webpack-plugin": "^5.0.1",
    "css-loader": "^4.2.2",
    "html-webpack-plugin": "^4.3.0",
    "mini-css-extract-plugin": "^0.10.0",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "style-loader": "^1.2.1",
    "terser-webpack-plugin": "^4.1.0",
    "webpack": "^4.44.1",
    "webpack-bundle-analyzer": "^3.8.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.1.2"
  }
}

package.json 中,注册了两个 脚本命令 ,以便于快速进行启动 devServer 及 打包文件。

{
  "scripts": {
    "dev": "webpack-dev-server --config build/webpack.dev.js",
    "build": "webpack --config build/webpack.prod.js"
  }
}

同时,引用一些常用的plugin

1.2.3 其他文件

src/index.js:

import './assets/style.css'

function foo() {
  document.body.innerText = 'hello world'
}

foo()

src/assets/style.css:

body {
  background-color: red;
}

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>模板</title>
  </head>
  <body></body>
</html>

1.3 运行结果

运行以下命令,查看结果

> yarn
> npm run dev

打开 http://0.0.0.0:8080/ 地址查看结果:

01.png

1.4 示例工程

示例工程:

|-- examples
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js // 通用配置
    |   |-- webpack.dev.js // 开发环境下的配置
    |   |-- webpack.prod.js // 生成环境下的配置
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

【step-by-step】02. 使用 babel 转译 ES6 + 语法

本篇文档的目的是希望前端同学能够以 复制粘贴 的方式,快速在自己项目中添加插件。因此,一些说明性质的知识将以推荐阅读的方式推荐给大家。

示例工程:02-add-babel-loader

Babel 是一个 JavaScript 编译器,允许我们在项目中使用下一代 JS 语法(ES 6 7 8 9...)。Babel 将 ECMAScript 2015+ 版本的代码转译成旧版本浏览器也能运行的代码。

比如箭头函数 :

// Babel 输入: ES2015 箭头函数
;[1, 2, 3].map((n) => n + 1)

// Babel 输出: ES5 语法实现的同等功能
;[1, 2, 3].map(function (n) {
  return n + 1
})

你可以通过安装预设(presets,一系列同类插件组合)或 插件(plugins)告诉 Babel 应该如何进行代码转译,例如:@babel/preset-env (转译 ES6+ 的语法)、@babel/preset-react(转译 React )。

2.1 Babel 工具介绍

注意:如果安装 Babel不使用任何 plugin,那么Babel是不会转译 ES6+ 语法的。

@babel/preset-env

Babel 推崇功能的单一性,例如我们想使用 ES6 的箭头函数,需要对应的转化插件。

yarn add @babel/plugin-transform-arrow-functions -D

并在 babel.config.js 中注册

// babel.config.js

module.exports = {
  plugins: ['@babel/plugin-transform-arrow-functions']
}

但是我们不可能一个个的设置所有需要的plugin,例如 ES2015就包含了二十几个转译插件。为了解决这个问题,babel 还提供了一组插件的套餐,也就是 preset

我们在项目中添加的是 @babel/preset-env, 它会根据目标浏览器(2.3.4 指定 browserslist)自动引入对应的插件列表,然后再进行编译。可以简单理解,它是一堆 Plugin 的豪华全家桶,包含了我们常用的 ES2015、ES2016、ES2017 等最新语法的转化插件。

@babel/polyfill

由于 babel 只进行语法转换(如箭头函数),你可以使用 polyfill 来支持新的全局变量,如 PromiseSymbol 或 新的原生方法。

@babel/preset-env 与 @babel/polyfill

@babel/polyfill 在 @babel 7.4 已经废弃了,可以通过配置 @babel/preset-env 来引入 polyfill。

主要是以下两个参数:

  • targets: 本工程需要支持的目标浏览器列表。
  • useBuiltIns: 此参数决定了 babel 打包时如何处理@babel/polyfill 语句。
    • entry: 去掉目标浏览器已支持的 polyfill 模块,将浏览器不支持的都引入对应的 polyfill 模块。
    • usage ✅: 打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到部分 polyfill 模块
    • false (默认) : 不会自动引入 polyfill 模块,对 polyfill 模块屏蔽

建议使用 useBuiltIns: usage。Babel会根据目标浏览器的支持情况和源代码中出现的语言特性,按需引入用到的 polyfill 文件,这确保了最终包里 polyfill 数量的最小化。

@babel/plugin-transform-runtime

babel-polyfill 会污染全局变量,它给很多类的原型链上添加新的方法。如果我们也开发一套工具类库或者临时在原型链上添加了方法,那么这些自定义方法有可能会跟 polyfill 中的方法互相影响,使得整个工程中的代码都变得不可控。

@babel/plugin-transform-runtime以沙箱垫片的方式防止污染全局,并抽离公共的 helper function,以节省代码的冗余。

async/await 举例,如果不使用这个 plugin (即默认情况),转换后的代码大概是:

// babel 添加一个方法,把 async 转化为 generator
function _asyncToGenerator(fn) {
  return function () {
    // ....
  }
}

// 具体使用处
var _ref = _asyncToGenerator(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2)
})

在使用了 babel-plugin-transform-runtime 了之后,转化后的代码会变成

// 从直接定义改为引用,这样就不会重复定义了。
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator')
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2)

// 具体使用处是一样的
var _ref = _asyncToGenerator3(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2)
})

使用 babel-plugin-transform-runtime 避免了代码重复的问题。

推荐阅读:

2.2 添加步骤

根据 babel-loader 中的示例说明,我们在项目中添加 babel-loader 的步骤如下:

  • 安装依赖
  • 添加 babel-loader 的配置
  • 添加 .babelrc 配置文件
  • 指定 browserslist

2.3 具体流程

2.3.1 安装依赖

在 webpack 项目中使用 babel 转义 ES6 以上的语法,最好的方式当然是使用 babel-loader 了。babel-loader可以自动帮我们转译 ES6 以上的语法(Loader makes the life easier~ 🎉)。

运行以下命令来安装 babel-loader

yarn add babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime core-js -D

2.3.2 调整 webpack 配置

由于开发环境和生产环境都需要使用 babel 转译。因此,我们在 webpack.base.js 中添加 babel-loader 的配置。可参考 官方例子

build/webpack.base.js:

const path = require('path')

module.exports = {
  module: {
    // ...
    rules: [
      // ---- 在此添加 babel-loader 的配置 ----
      {
        test: /\.m?js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader'
        }
      }
      // **** 在此添加 babel-loader 的配置 END ****
    ]
  }
  // ...
}

2.3.3 添加配置文件

在根目录下添加 .babelrc 文件:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": "3"
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime"
    ]
  ]
}

在上面的配置中

  • @babel/preset-env:是一系列插件的集合,包含了我们在 babel6 中常用的 es2015、es2016、es2017 等最新的语法转换插件。允许我们使用最新的 js 语法,比如 let、const、箭头函数等。
  • "useBuiltIns": "usage":在项目中按需添加 polyfills 中的方法。由于 babel 只进行语法转换(如箭头函数),你可以使用 polyfill 来支持新的全局变量,如 Promise 或 新的原生方法。@babel/polyfill 在 @babel 7.4 已经废弃了,可以通过使用 @babel/preset-env 来引入 polyfill,即在 .babelrc 中,配置 userbuiltIns: 'usage'。具体可以参考 polyfilluseBuiltIns
  • "corejs": "3": useBuiltIns 使用 usage 的时候,需要声明 corejs 的版本
  • @babel/plugin-transform-runtime:以沙箱垫片的方式防止污染全局,并抽离公共的 helper function,以节省代码的冗余。

2.3.4 指定 browserslist

browserslist.jpg

通过 browserslist 指定了项目的目标浏览器的范围。这个值会被 @babel/preset-envAutoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀。

browserslist 广泛应用于多个库中:

  • Babel
  • postcss-preset-env
  • Autoprefixer
  • ESLint
  • StyleLint

package.json 中配置 browserslist

{
  "browserslist": ["> 0.25%", "not dead", "last 2 versions", "not ie <= 8"]
}

2.4 测试

调试

我们在 index.js 中添加 ES6+ 语法,然后进行 debug 或者 打包,查看 ES6+ 的语法是否被转译成了 ES5 语法 :

const bar = {
  a: {
    b: 123,
    c: {
      d: 'hello',
      e() {
        console.info(123)
      }
    }
  }
}

const bb = {
  ...bar,
  app: [1, 2, 3, 4],
  bpp: 'hello world'.includes('ll')
}

document.body.innerText = `这个是 ${JSON.stringify(bb)}`

测试

打开 http://0.0.0.0:8080/ 页面显示:

这个是 {"a":{"b":123,"c":{"d":"hello"}},"app":[1,2,3,4],"bpp":true}

运行 npm run build 查看打包后的文件:

// app.41ff3a5a6bab98ef169d.js
// ...

function u(e) {
  for (var r = 1; r < arguments.length; r++) {
    var t = null != arguments[r] ? arguments[r] : {}
    r % 2
      ? c(Object(t), !0).forEach(function (r) {
          o()(e, r, t[r])
        })
      : Object.getOwnPropertyDescriptors
      ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t))
      : c(Object(t)).forEach(function (r) {
          Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r))
        })
  }
  return e
}

var i = u(
  u(
    {},
    {
      a: {
        b: 123,
        c: {
          d: 'hello',
          e: function () {
            console.info(123)
          }
        }
      }
    }
  ),
  {},
  {
    app: [1, 2, 3, 4],
    bpp: 'hello world'.includes('ll')
  }
)
document.body.innerText = '这个是 '.concat(i)

// ...

通过打包后的文件,可以分析出,ES6 语法已经被转义了。其中 String.prototype.includes 被定义在 vendor~app.js 中。

2.5 示例工程

示例工程:

|-- examples
    |-- .babelrc // babel 配置文件
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

2.6 总结

在项目中添加 babel-loader 的步骤:

  • 安装依赖
  • 添加 babel-loader 的配置
  • 添加 .babelrc 配置文件
  • 指定 browserslist

【step-by-step】3. 使用 .editorconfig 设置 IDE 的配置

我们期待能有一套自动化工具,帮助我们自动调整代码风格,自动审查代码语法。使我们能够把更多的精力投放到业务开发中,而不是千奇百怪的代码风格上。

因此,我们在工程中添加几个工具:

  • .editorconfig : 让IDE遵循同样的编写规则。
  • prettier : 代码格式化工具。
  • eslint : 审查 js 语法。
  • stylelint : 审查 css 语法。
  • commitlint : 审查 git commit 信息格式。

TL;DR

.editorconfig 文件是为了在不同的开发环境上遵循 .editorconfig 的配置,以达到拥有同样的代码风格表现。

在前端工程中,即使已经eslintprettier 这样的代码格式化工具,也建议使用 .editorconfig.editorconfig 可以对其他文件格式的代码风格进行控制,例如 .py.md

3.1 添加 .editorconfig 文件

在工程的根目录下添加 .editorconfig 文件

# editorconfig.org
# 表示是最顶层的配置文件,IDE发现root设为true时,才会停止查找.editorconfig文件
root = true

[*]
# 设置缩进风格(tab是硬缩进,space为软缩进)
indent_style = space
# 用一个整数定义的列数来设置缩进的宽度,如果indent_style为tab,则此属性默认为tab_width
indent_size = 2
# 设置换行符,值为lf、cr和crlf
end_of_line = lf
# 设置编码,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用utf-8-bom
charset = utf-8
# 设为true表示会去除换行行首的任意空白字符。
trim_trailing_whitespace = true
# 设为true表示使文件以一个空白行结尾
insert_final_newline = true

3.2 配置 IDE

webstorm配置如下:

webstorm-editorconfig.png

VS Code 配置

参考这篇文章:VS Code 配置文档

推荐文章

【step-by-step】4. 使用 prettier 格式化代码

我们期待能有一套自动化工具,帮助我们自动调整代码风格,自动审查代码语法。使我们能够把更多的精力投放到业务开发中,而不是千奇百怪的代码风格上。

因此,我们在工程中添加几个工具:

  • .editorconfig : 让IDE遵循同样的编写规则。
  • prettier : 代码格式化工具。
  • eslint : 审查 js 语法。
  • stylelint : 审查 css 语法。
  • commitlint : 审查 git commit 信息格式。

TL;DR

Building and enforcing a style guide

Prettier 是一个代码格式工具,支持主流的前端语言(js, ts, ES6, ES7, markdown 等等)。 Prettier 会根据书写的代码,重新解析和构建代码的显示格式,确保团队使用统一的代码风格。

prettier.png

4.1 安装步骤

根据 prettier.io 中的示例说明,我们在项目中添加 prettier 的步骤如下:

  • 安装依赖
  • 创建 .prettierrc.js 文件
  • 添加 IDE 插件

4.2 具体流程

4.2.1 安装依赖

yarn add prettier --dev --exact

4.2.2 添加配置文件

在工程的根目录下,创建 .prettierrc.js 文件。

module.exports = {
  printWidth: 120, // 每行代码最大长度 默认为80
  tabWidth: 2, //一个tab代表几个空格数
  useTabs: false, //是否使用tab进行缩进
  semi: false, // 声明后带分号
  singleQuote: true, // 使用单引号
  trailingComma: 'none',
  endOfLine: 'auto'
}

由于在windows与mac系统中的换行符不同,建议在 .prettierrc.js 文件中添加 endOfLine 属性。

4.2.3 添加脚本命令(可选)

package.json 中,添加 prettier 命令:

{
  "script": {
    "prettier": "prettier --write ./src/**.{js,css}"
  }
}

运行 npm run prettier 命令,prettier 可以按照配置文件调整 src 文件夹中的代码。

4.3 测试

为了测试 prettier 是否安装成功,我们弄乱代码中的格式,然后使用 prettier 进行修复。

调整文件

调整 index.js 的文件格式

// 两个问题:1. 多行; 2. 段位分号;
import './assets/style.css';



function foo() {
  document.body.innerText = 'hello world';
}



foo();

以及 src/assets/style.css 文件的格式

body {
  background-color: red;
}




.example {
  background-color: red;
}

运行 prettier

运行命令 npx prettier --write ./src/** 后,查看 src文件夹 中的 js 文件和 css 文件是否格式化了。

body {
  background-color: red;
}





.example {
  background-color: red;
}

done!

4.4 在 IDE 中使用插件

webstorm

在 webpack 中使用 prettier插件,在保存(ctrl + S)的时候,自动调整文件的格式。

22.png

vscode

vscode 可参考:

  1. 安装方法是点击“Extension”图标,然后搜索 “prettier”,找到官方插件并安装:

image.png

  1. 代码的vscode窗口中,使用快捷键“CTRL + Shift + P”打开vscode命令框,在框中输入“format”关键字,可以看到有2个选项:

    • Format Document (快捷键 Shift+Alt+F)对整个文档做格式化

    • Format Selection (快捷键Ctrl+K, Ctrl+F)对选择代码做格式化

4.5 示例工程

示例工程:

|-- examples
    |-- .babelrc // babel配置
    |-- .editorconfig // editorconfig 的配置
    |-- .prettierrc.js // prettier 的配置
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

推荐阅读

【step-by-step】5. 使用 ESLint 审查 JS 代码

我们期待能有一套自动化工具,帮助我们自动调整代码风格,自动审查代码语法。使我们能够把更多的精力投放到业务开发中,而不是千奇百怪的代码风格上。

因此,我们在工程中添加几个工具:

  • .editorconfig : 让IDE遵循同样的编写规则。
  • prettier : 代码格式化工具。
  • eslint : 审查 js 语法。
  • stylelint : 审查 css 语法。
  • commitlint : 审查 git commit 信息格式。

.editorconfigprettier 可以自动调整代码风格,却无法约束语法。因此,还是需要 ESLint 这样专业的代码语法检查工具。

TL;DR

eslint

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性避免错误。在 webpack 项目中,使用 eslint-loader 自动检查 JS 代码。

在安装 eslint-loader 之前,我们需要先安装 ESLint 及其配置文件。

5.1 步骤

根据 ESLint 文档中的示例说明,我们在项目中添加 ESLint的步骤如下:

  • 安装依赖
  • 生成 .eslintrc 配置文件
  • 添加 eslint-plugin-prettier 插件
  • 调整 .eslintrc 文件

5.2 具体流程

5.2.1 安装 ESLint

yarn add eslint -D

5.2.2 生成 .eslintrc 文件

创建 .eslintrc.js 文件有两种方法:

  • 创建 .eslintrc.js 文件
  • 执行脚本命令自动生成
方法一:创建 .eslintrc.js 配置文件

安装依赖

yarn add eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node -D

在工程的根目录下创建 .eslintrc.js 文件

module.exports = {
  env: {
    browser: true,
    es2020: true
  },
  extends: ['standard'],
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  },
  rules: {}
}
方法二:运行脚本

安装完 eslint 后,运行 npx eslint --init 可以自动生成 .eslintrc 配置文件。

npx 的用法:阮一峰-npx 使用教程

在运行 npx eslint --init后,出现下面三个选项,选择第三项:

npx eslint --init
? How would you like to use ESLint? …
  To check syntax only
  To check syntax and find problems
❯ To check syntax, find problems, and enforce code style

module 的类型中,选择 JavaScript modules (import/export)

npx eslint --init
✔ How would you like to use ESLint? · style
? What type of modules does your project use? …
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

framework 中选择 None of these

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
? Which framework does your project use? …
  React
  Vue.js
❯ None of these

然后选择在 Browser 上运行代码。

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node

代码风格选择第一项

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
? How would you like to define a style for your project? …
❯ Use a popular style guide
  Answer questions about your style
  Inspect your JavaScript file(s)

代码规范选择 Standard 标准的格式

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
? Which style guide do you want to follow? …
  Airbnb: https://github.com/airbnb/javascript
❯ Standard: https://github.com/standard/standard
  Google: https://github.com/google/eslint-config-google

配置文件的类型中,选择 JavaScript

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · standard
? What format do you want your config file to be in? …
❯ JavaScript
  YAML
  JSON

自此,在项目的根目录下,生成了 .eslintrc.js 文件

module.exports = {
  'env': {
    'browser': true,
    'es2020': true
  },
  'extends': [
    'standard'
  ],
  'parserOptions': {
    'ecmaVersion': 12,
    'sourceType': 'module'
  },
  'rules': {}
}

动态例子

eslint-init.gif

5.2.3 添加 eslint-plugin-prettier 与 eslint-config-prettier 插件

为了让 prettier 能够更好的配合 eslint 检查代码,我们安装以下插件:

  • eslint-config-prettier: Prettier 与 Linter 工具配合的时候,插件间的配置会彼此冲突。为了解决这个问题,我们使用 eslint-config-prettier 插件,关闭部分 ESLint配置,让 Prettier 的配置覆盖 ESLint 的配置。
  • eslint-plugin-prettier:ESLint 会使用 prettier 的规则,对工程进行检查。

运行以下的命令,安装 plugin配置

yarn add eslint-plugin-prettier eslint-config-prettier eslint-config-recommended -D

5.2.4 调整 .eslintrc.js 文件

我们可以使用 prettier 提供的配置 plugin:prettier/recommended,它会做三件事:

  • 开启 eslint-plugin-prettier
  • 设置 prettier/prettier rule 为 "error"
  • 继承 eslint-config-prettier 的配置

因此,我们在工程中继承 ESLint(eslint:recommended) 与 Prettier(plugin:prettier/recommended) 的配置,即在 extends 列表中,添加 eslint:recommendedplugin:prettier/recommended

.eslintrc.js

module.exports = {
  env: {
    browser: true,
    es2020: true
  },
  extends: ['eslint:recommended', 'plugin:prettier/recommended'],
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  }
}

5.3 测试

我们调整 index.js 文件,故意搞错代码后。执行 ESLint 的脚本,查看 ESLint 是否能够检查出相关的错误。

调整 src/index.js

src/index.js

const bar = {
  a: {
    b: 123,
    c: {
      d: 'hello',
      e () {
        // XXXXXX e后括号的格式不对
        console.info(123)
      }
    }
  }
}

// XXXXXX规则中,不能以分号结尾。
const bb = {
  ...bar,
  app: [1, 2, 3, 4],
  bpp: 'hello world'.includes('ll')
};

document.body.innerText = `这个是 ${JSON.stringify(bb)}`

运行命令

运行 npx eslint src/**/*.js 命令来检查工程中的 js 语法,来验证 eslint 是否配置成功。

文件的匹配规则可以参考 glob 库中的介绍 。

查看结果

npx eslint src/**/*.js

/Users/CodingNutsZac/Documents/founder/git/test/test-webpack-tutorial/src/index.js
   7:8  error  Delete `·`  prettier/prettier
  19:2  error  Delete `;`  prettier/prettier

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

到目前为止,eslint配置正确。

5.4 总结

添加 eslint 的步骤

  • 安装依赖
  • 生成 .eslintrc 文件
  • 添加 eslint-plugin-prettier 插件
  • 调整 .eslintrc 文件

5.5 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc.js // eslint 的配置
    |-- .prettierrc.js
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

推荐阅读

【step-by-step】6. 使用 eslint-loader 自动审查代码

eslint-loader.png

使用 ESLint 审查 JS 代码 文档中,我们添加了 eslint 和 prettier。我们通过在项目中添加 eslint-loader ,让webpack 自动检查 JS 代码中的问题,自动提醒、自动报错。

6.1 步骤

6.2 具体流程

6.2.1 安装 eslint-loader

yarn add eslint-loader -D

6.2.2 调整 webpack.base.js 中的配置

module.rules 中添加 babel-loader 的配置:

build/webpack.base.js

module.exports = {
  // ...
  module: {
    rules: [
      // 在此添加 eslint-loader 的配置
      {
        enforce: 'pre',
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader',
        options: {
          cache: true,
          fix: false // 自动修复,对 ide 不够友好。如果,webstorm也开启了自动修复,会与eslint-loader互相冲突。
          // failOnError: true, // 如果有格式错误,不进行编译
          // failOnWarning: true
        }
      }
      // END 在此添加 eslint-loader 的配置
    ]
  }
  // ...
}

6.3 测试 eslint 是否生效

在开发模式下,我们故意编写错误代码,以验证 webpack 是否能够自动审查代码,并提示我们报错信息。

运行 yarn run dev 启动工程后,改写 index.js

src/index.js

const bar = {
  a: {
    b: 123,
    c: {
      d: 'hello',
      e () {
        // XXXXXX e后括号的格式不对
        console.info(123)
      }
    }
  }
}

// XXXXXX规则中,不能以分号结尾。
const bb = {
  ...bar,
  app: [1, 2, 3, 4],
  bpp: 'hello world'.includes('ll')
};

document.body.innerText = `这个是 ${JSON.stringify(bb)}`

修改index.js文件后,webpack在控制台中提示了代码的格式错误

ERROR in ./src/index.js
Module Error (from /Users/CodingNutsZac/Documents/gitee/webpack-project-tutorial/node_modules/eslint-loader/dist/cjs.js):

/webpack-project-tutorial/examples/04-add-eslint/src/index.js
   6:8  error  Delete `·`  prettier/prettier
  17:2  error  Delete `;`  prettier/prettier

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

ℹ 「wdm」: Failed to compile.

如果控制台显示了上面的提示信息,说明我们已经成功地在webpack工程中,添加了 eslint-loader。工程中的 webpack 可以通过 eslint-loader 自动审查代码错误,并提示我们报错信息。

使用 webstorm 快捷键、或者 vscode 快捷键,或者手动修复错误后,项目恢复正常。

6.4 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc.js // 新添加的 eslint 配置
    |-- .prettierrc.js
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

6.5 总结

添加 eslint-loader 的步骤:

  • 安装依赖
  • 调整 webpack 的配置

推荐阅读

【step-by-step】7. 通过 postcss 使用新一代的 CSS 语法

本篇文档的目的是希望前端同学能够以 复制粘贴 的方式,快速在 webpack 工程 中添加插件。因此,一些说明性质的知识将以推荐阅读的方式推荐给大家。

TL;DR

postcss

postcss 是一个利用 JavaScript 的强大编程能力对 CSS 代码进行转换的工具。它负责把 CSS 代码解析成抽象语法树结构(Abstract Syntax Tree,AST),再交由插件来进行处理。插件基于 CSS 代码的 AST 所能进行的操作是多种多样的,比如可以支持变量和混入(mixin),增加浏览器相关的声明前缀,或是把使用将来的 CSS 规范的样式规则转译(transpile)成当前的 CSS 规范支持的格式。从这个角度来说,PostCSS 的强大之处在于其不断发展的插件体系。目前 PostCSS 已经有 200 多个功能各异的插件。开发人员也可以根据项目的需要,开发出自己的 PostCSS 插件。

  • postcss 能够自动为css 规则添加前缀 ( autoprefixer 插件 )。
  • postcss 能够将最新的 css 语法转换成大部分版本的浏览器都能理解的语法( postcss-preset-env 插件 )。
  • postcss 能够使用 stylelint 强化一致性约束避免样式表中的错误( stylelint )。

Autoprefixer

Autoprefixer 是一款自动管理浏览器前缀的插件,它可以解析CSS文件并且添加浏览器前缀到CSS内容里,使用 Can I Use 的数据来决定哪些前缀是需要的。在 如何处理CSS3属性前缀 文章中,作者详细介绍了为什么要用浏览器前缀。

把Autoprefixe添加到资源构建工具(例如Webpack)后,可以完全忘记有关CSS前缀的东西,只需按照最新的W3C规范来正常书写CSS即可。如果项目需要支持旧版浏览器,可修改browsers参数设置

Autoprefixer将使用基于当前浏览器支持的特性和属性数据去为你添加前缀。你可以尝试下Autoprefixer的demo: Autoprefixer CSS online

autoprefixer.png

7.1 步骤

根据 postcss-loader 文档中的示例,我们在项目中添加 postcss-loader 的步骤如下:

  • 安装 postcss-loader
  • 创建 .postcssrc 文件
  • 调整 webpack 的配置

7.2 具体流程

7.2.1 安装 postcss-loader

同样的,我们在项目中直接添加 postcss-loader 来使用postcss的功能。 具体配置可参考: postcss-loader

yarn add postcss-loader -D

安装 postcss 相关的 plugins

yarn add postcss-load-config postcss-preset-env postcss-import -D
  • postcss-load-config: 允许我们在工程中,使用更多种类的配置文件。
  • postcss-preset-env: 允许我们在工程中,使用 新一代的 css 语法 ,根据 browserslist 进行转译。
  • postcss-import:通过内联内容来转换@import规则。

7.2.2 创建 .postcssrc.js 文件

在工程的根目录下,创建 .postcssrc.js 文件

// .postcssrc.js
module.exports = {
  plugins: [
    require('postcss-import'), // 需要放到最上面
    // require('autoprefixer'), // 自动添加浏览器前缀(已经包含在 postcss-preset-env 中)
    require('postcss-preset-env')() // 使用下一代css语法
  ]
}

7.2.3 调整 webpack.base.js

根据 postcss-loader 文档中的示例说明,我们在 webpack.base.js 中,添加 postcss-loader

build/webpack.base.js 中, 我们把 postcss-loader 添加到 .css 文件的loader列表中。

// webpack.base.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          // 在此添加 postcss-loader 的配置
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1
            }
          },
          {
            loader: 'postcss-loader'
          }
          // END 在此添加 postcss-loader 的配置
        ]
      }
    ]
  }
}

7.2.4 调整 webpack.prod.js

由于 webpack.prod.js 中 css 的配置与 base 中的不一样,所以需要分别配置。

build/webpack.prod.js

const webpackConfig = merge(base, {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              esModule: true
            }
          },
          // 在此添加 postcss-loader 的配置
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1
            }
          },
          {
            loader: 'postcss-loader'
          }
          // END 在此添加 postcss-loader 的配置
        ]
      }
    ]
  }
})

7.3 测试

7.3.1 调整项目

我们在项目中添加 最新的 CSS 语法后,查看 postcss-loader 是否能够帮助我们自动转译这些新的 CSS 语法。

style.css 文件中添加最新的 CSS 语法,并在 index.js中引用这个 css 文件。

src/assets/style.css

::placeholder {
  color: gray;
}

body {
  background-color: red;
}

.example {
  display: flex;
  position: relative;
  transform: translate(10, 10);
}

src/index.js

import './assets/style.css'

document.body.innerHTML = `<div class="example">hello world</div>`

7.3.2 测试打包

运行打包命令

npm run build

查看 dist/app.css,原来的 css 文件已经被转译。

::-webkit-input-placeholder {
  color: gray;
}

::-moz-placeholder {
  color: gray;
}

:-ms-input-placeholder {
  color: gray;
}

::-ms-input-placeholder {
  color: gray;
}

::placeholder {
  color: gray;
}

body {
  background-color: red;
  color: white;
}

.example {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  position: relative;
  -webkit-transform: translate(10, 10);
  transform: translate(10, 10);
}

/*# sourceMappingURL=app.css.map*/

done!

7.4 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc.js
    |-- .postcssrc.js // postcss 配置文件
    |-- .prettierrc.js
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

7.5 总结

添加 postcss-loader 的步骤

  • 安装 postcss-loader
  • 创建 .postcssrc 文件
  • 调整 webpack 的配置

推荐阅读

【step-by-step】8. 添加 stylelint

我们期待能有一套自动化工具,帮助我们自动调整代码风格,自动审查代码语法。使我们能够把更多的精力投放到业务开发中,而不是千奇百怪的代码风格上。

因此,我们在工程中添加几个工具:

  • .editorconfig : 让IDE遵循同样的编写规则。
  • prettier : 代码格式化工具。
  • eslint : 审查 js 语法。
  • stylelint : 审查 css 语法。
  • commitlint : 审查 git commit 信息格式。

TL;DR

stylelint 是强大的 css 代码审查工具,由PostCSS提供技术支持。与 ESLint 类似,是通过定义一系列的编码风格规则帮助我们避免在样式中出现错误。目前,我们可以把stylelint作为postcssplugin来使用。

8.1 步骤

根据 stylelint 文档中的示例说明,我们在项目中,添加 stylelint 的步骤如下:

  • 安装依赖
  • 添加配置文件
  • 调整 postcss 的配置

8.2 具体流程

8.2.1 安装 stylelint

运行脚本命令安装 stylelint

yarn add stylelint stylelint-config-standard -D

8.2.2 添加配置文件

创建 .stylelintrc 文件

{
  "extends": "stylelint-config-standard"
}

8.2.3 调整 .postcssrc.js 文件

调整 .postcssrc.js 文件,添加 stylelint plugin

module.exports = {
  plugins: [
    require('stylelint'), // 添加 stylelint 插件,需要放在最上面。
    require('postcss-import'), //
    // require('autoprefixer'), // 自动添加浏览器前缀(已经包含在 postcss-preset-env 中了)
    require('postcss-preset-env')() // 使用下一代css语法
  ]
}

stylelint 插件需要放在plugin列表的最上面。

8.3 测试

我们故意破坏 style.css 的格式后,运行 stylelint 的命令查看审查结果。

::placeholder {
  color: gray;
}

body {
  background-color: #00;
}


.example {
  display: flex;
  position: relative;
  transform: translate(10, 10);
}

运行 stylelint 命令

文件的匹配规则可以参考 glob 库中的介绍 。

npx stylelint src/**/*.css

在控制台会出现以下提醒

npx stylelint src/**/*.css

src/assets/style.css
 6:21  ✖  Unexpected invalid hex color "#00"   color-no-invalid-hex
 9:1   ✖  Expected no more than 1 empty line   max-empty-lines

调整后,程序恢复正常

8.4 添加 stylelint-prettier

通过安装 stylelint-prettierstylelint 会使用 prettier 中的配置规则对工程进行检查。

安装依赖

yarn add stylelint-prettier stylelint-config-prettier -D

调整配置文件

调整配置文件 .stylelintrc , 把原来的配置替换成下面的这个

{
  "extends": ["stylelint-prettier/recommended"]
}

再次进行测试

8.5 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintignore
    |-- .eslintrc.js
    |-- .postcssrc.js
    |-- .prettierignore
    |-- .prettierrc.js
    |-- .stylelintrc // 新添加的 stylelint 配置文件
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
            |-- style.css

8.6 总结

添加 stylelint 的 步骤

  • 安装依赖
  • 添加配置文件
  • 调整 postcss 的配置
  • 安装 stylelint-prettier
  • 调整配置文件

【step-by-step】9. 使用 husky 和 lint-staged 来调用 Git Hook (Pre-commit)

TL;DR

为了避免 一千个程序员,就有一千种代码风格 !我们期待在提交代码时,Git Hook 自动调用 prettier 统一代码风格,然后调用 eslintstylelint 审查代码语法。同时,我们也期待 eslint 这类工具审查本地修改过的文件,而不是所有文件,以此来提升执行效率。

我们借助下面两个工具来达成目标:

  • huskyhusky 是一个Git Hook工具(对!就是二哈的英文名)。 Git 能在特定的重要动作发生时触发 Husky 中的自定义脚本。在本工程中,主要是 pre-commit,也就是在执行 git commit 之前,先执行一些自定义操作。可参考:Git 钩子
  • lint-stagedlint-staged 可以让我们只对 Git 缓冲区 中的文件执行操作。

husky.jpeg

9.1 安装步骤

根据 pretter.io 文档中的建议,以及 huskylint-staged 说明文档中的示例说明,我们在项目中添加 Git Hook 的步骤如下所示:

  • 安装依赖
  • package.json中添加 huskylint-staged 的配置
  • 添加 ignore 文件

9.2 具体流程

9.2.1 安装依赖

运行命令安装 huskylint-staged

yarn add husky lint-staged -D

安装完 husky 后,在 根目录 下查看 .git/hooks 文件夹下面的脚本:

例如 .git/hooks/pre-commit

#!/bin/sh
# husky

# Created by Husky v4.3.0 (https://github.com/typicode/husky#readme)
#   At: 9/14/2020, 3:31:42 PM
#   From: /Users/CodingNutsZac/Documents/founder/git/webpack-tutorial/node_modules/husky (https://github.com/typicode/husky#readme)

. "$(dirname "$0")/husky.sh"

从注释中可以看出,这个脚本是由 husky 生成的,由此证明 husky 安装成功。

9.2.2 在 package 中添加配置

package.json 中添加 huskylint-staged 的配置:

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.js": ["prettier --write", "eslint --cache --fix"],
    "src/**/*.css": ["prettier --write", "stylelint --cache --fix"]
  }
}
  • husky 在 pre-commit 阶段(也就是 commit 之前)执行 lint-staged 命令。
  • lint-staged 里面定义了需要对 Git 暂存区中的文件执行的任务。先优化暂存区中的 js 代码格式,再进行 eslint 检测。
  • 如果在 eslint 执行阶段抛错了,则表示报错代码不符合 eslint 规范,进而导致 pre-commit 钩子抛错,最终导致整个 commit 操作失败。

lint-staged 将把 git缓冲区 中文件的绝对路径作为参数传给命令行,这样我们就可以只操作本地修改的文件了。

9.2.3 添加 ignore 文件

至此,我们就定义完 Git Hook 中的指令了。为了使整个工程更易于扩展,建议在工程中添加下面的 ignore 文件:

  • .eslintignore : ESLint 的ignore文件。
  • .gitignore: Git 的ignore文件。
  • .prettierignore: prettier 的ignore文件。

9.3 验证

9.3.1 验证 prettier

我们故意弄乱代码风格后执行git commit,查看 huskylint-staged 是否会调用 prettier 对代码格式化。

修改文件

修改 src/index.js 以及 src/assets/style.css。如果在 git commit 时,prettier 修复了代码格式,证明 huskylint-staged 添加成功。

index.js中,有两处错误: 1. 两行代码之间有多个换行符;2. 段位的分号问题;

// 两行代码之间,添加多个换行。这是违背eslint规则的,理应报错。
import './assets/style.css';


document.body.innerHTML = `<div class="example">hello world, zac</div>`;

style.css中,有两处错误:1. 第8行的分号;2. 第9行到第12行的换行符。

::placeholder {
  color: #fff;
}

body {
  background-color: #000;
  color: white;
};




.example {
  display: flex;
  position: relative;
  transform: translate(10, 10);
}
提交代码

控制台输出如下

webstorm-lint-staged.png

如果,执行git commit 后,两个修改过的文件被格式化,证明 huskylint-staged 安装成功。

9.3.2 验证 eslint

我们故意在js代码当中添加错误的语法调用。如果在 git commit 的时候,eslint 提示报错信息,证明 huskylint-staged 安装成功。

修改文件
// 两行代码之间,添加 `return`,eslint应该会报错。
import './assets/style.css'

return

document.body.innerHTML = `<div class="example">hello world, zac</div>`
提交 git 进行验证

控制台展示如下:

webstorm-lint-staged-2.png

9.4 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintignore // eslint的ignore文件
    |-- .eslintrc.js
    |-- .gitignore // git的ignore文件
    |-- .postcssrc.js
    |-- .prettierignore // prettier的ignore文件
    |-- .prettierrc.js
    |-- .stylelintrc
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
        |   |-- style.css
        |-- js
            |-- utils.js

9.5 总结

添加 huskylint-stage 的步骤:

  • 安装依赖
  • package.json中添加 huskylint-staged 的配置
  • 添加 ignore 文件

推荐阅读

【step-by-step】10. 使用 commitizen 规范 Git 日志格式

本篇文档的目的是希望前端同学能够以 复制粘贴 的方式,快速在 webpack 工程 中添加插件。因此,一些说明性质的知识将以推荐阅读的方式推荐给大家。

TL;DR

在项目开发中,我们经常能看到:

  • 一连串 一模一样 的 commit 日志;
  • 无法分辨提交意图的 commit 信息;
  • commit 信息与更变代码之间毫无关联

这些习惯会导致代码回滚、issue 追溯变得十分困难,让日志信息变得毫无意义。理想的 commit 信息应该是能较好的解决如上问题的。

  • 发生问题时快速让 PM 识别问题代码并回滚。
  • commit 和 代码之间建立联系,并和相关的 issue 予以关联。

因此,我们期待的流程是:

  • commitizen : 生成 commit message 的约定模板。
  • commitlint: 检查 commit message 是否符合提交格式。
  • standard-version : 每次发版的时候,生成 changelog.md,方便查看发版信息以及工单与代码的对应关系

当前比较推荐的日志格式是 Angular Git Commit Guidelines ,我们后面所使用的插件也是基于 angular 日志格式的。

接下来将介绍如何在系统安装 commitizen,并使用它生成 Git 提交日志。

10.1 期待的日志格式

Augular 推荐的日志格式:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
  • type:本次 commit 的类型,诸如 feat、fix、docs 等。
  • scope:本次 commit 涉及的范围,例如组件、文件等。
  • subject:日志的标题。
  • body:详细说明本次 commit 的内容,动机等,如需换行使用 \n
  • footer:描述与之关联的 issue 或 change
    • 不兼容变动:如果当前代码与上一个版本不兼容,则 Footer 部分以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动理由和迁移方法。
    • 关闭 Issue:如果当前 commit 针对某个 issue,那么可以在 Footer 部分关闭这个 issue。例如: closes #123

例如:

feat(\$browser): onUrlChange event (popstate/hashchange/polling)

Added new event to \$browser:

- forward popstate event if available
- forward hashchange event if popstate not available
- do polling when neither popstate nor hashchange available

Breaks \$browser.onHashChange, which was removed (use onUrlChange instead)
fix(\$compile): couple of unit tests for IE9

Older IEs serialize html uppercased, but IE9 does not...
Would be better to expect case insensitive, unfortunately jasmine does
not allow to user regexps for throw expectations.

Closes #392
Breaks foo.bar api, foo.baz should be used instead
feat(directive): ng:disabled, ng:checked, ng:multiple, ng:readonly, ng:selected

New directives for proper binding these attributes in older browsers (IE).
Added coresponding description, live examples and e2e tests.

Closes #351

更多的例子:Git Commit Message Conventions

10.2 使用 commitizen 规范日志格式

commitzen 是格式化 commit message 的工具,它以问询的方式获取所需的信息。

10.3 添加步骤

根据 commitizen 文档 中的说明,我们在项目中添加 commitizen

  • 安装依赖
  • 调整 package.json 文件

10.3.1 安装依赖

安装格式化工具 commitizen:

yarn add commitizen -D

我们需要按照一定的标准来规范日志的格式,也就是需要遵照 约定格式适配器 ,这样 commitizen 才能按照固定格式进行交互式提问。

npx commitizen init cz-conventional-changelog --yarn --dev --exact
注意

运行上面的命令后,脚本会在 package.json 中添加 config.commitizen 属性。此时,我们需要调整 path 属性,避免在windows下运行 commitizen 脚本时报错!

"config": {
    "commitizen": {
-     "path": "./node_modules/cz-conventional-changelog"
+     "path": "cz-conventional-changelog"
    }
  }

10.3.2 调整 package.json 文件

在 script 中添加 ct 命令。

{
  "script": {
    "ct": "git add . && git-cz"
  }
}

10.4 测试

提交代码进行验证,由于 git add 命令已经添加到 ct 中了,因此直接在 terminal 中运行 npm run ct 来提交代码。

commitizen-gitlab-1.gif

在 gitlab 上看结果:

commitzen-gitlab.png

10.5 总结

添加 commitizen 的步骤:

  • 安装依赖
  • 调整 package.json 文件

10.6 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintignore
    |-- .eslintrc.js
    |-- .gitignore
    |-- .postcssrc.js
    |-- .prettierignore
    |-- .prettierrc.js
    |-- .stylelintrc
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
        |   |-- style.css
        |-- js
            |-- utils.js

推荐

【step-by-step】11. 使用 commitlint 审查 Git 日志规范

我们期待能有一套自动化工具,帮助我们自动调整代码风格,自动审查代码语法。使我们能够把更多的精力投放到业务开发中,而不是千奇百怪的代码风格上。

因此,我们在工程中添加几个工具:

  • .editorconfig : 让IDE遵循同样的编写规则。
  • prettier : 代码格式化工具。
  • eslint : 审查 js 语法。
  • stylelint : 审查 css 语法。
  • commitlint : 审查 git commit 信息格式。

TL;DR

对于 Git 日志,我们期待的流程是:

  • commitizen: 生成 commit message 的约定模板。
  • commitlint: 检查 commit message 是否符合提交格式。
  • standard-version: 每次发版的时候,生成 changelog.md,方便查看发版信息以及工单与代码的对应关系

当前比较推荐的日志格式是 Angular Git Commit Guidelines ,我们后面所使用的插件也是基于 angular 日志格式的。

接下来将介绍如何在系统安装 commitlint,并使用它审查日志格式。

11.1 添加的步骤

  • 安装依赖
  • 生成配置
  • 调整 package.json

11.2 具体流程

根据 github:commitlint 中的示例,我们在项目中通过以下的流程来安装 commitlint

  • 安装依赖
  • 配置文件
  • 调整 package.json

11.2.1 安装依赖

安装 commitlint cli 以及配置的 adapter。

yarn add @commitlint/config-angular @commitlint/cli -D

11.2.2 生成配置文件

用以下命令生成配置文件 commitlint.config.js

echo "module.exports = {extends: ['@commitlint/config-angular']}" > commitlint.config.js

注意:请务必检查一下已生成的 commitlint.config.js 文件。在windows下,生成的 commitlint.config.js 文件可能会存在格式错误,例如在内容两端有双引号 (")。

11.2.3 调整 package.json

package.json 中添加 husky 的 hook:

{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}

11.3 测试

运行命令来验证 commitlint 是否安装成功。

git add . && git commit -m "测试"

# 在 windows 下,请分开运行
git add .
git commit -m "测试"

在 terminal 中会出现以下错误,证明安装成功。

> git add . && git commit -m "测试"
warning ../../../../package.json: No license field
husky > pre-commit (node v10.21.0)
⚠ Some of your tasks use `git add` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.

ℹ No staged files match any configured task.
warning ../../../../package.json: No license field
husky > commit-msg (node v10.21.0)
⧗   input: 测试
✖   subject may not be empty [subject-empty]
✖   type may not be empty [type-empty]

✖   found 2 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

husky > commit-msg hook failed (add --no-verify to bypass)
error Command failed with exit code 1.

11.4 通过 commitizen 来提交 git

如下:

11.5 示例工程

示例工程:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintignore
    |-- .eslintrc.js
    |-- .gitignore
    |-- .postcssrc.js
    |-- .prettierignore
    |-- .prettierrc.js
    |-- .stylelintrc
    |-- commitlint.config.js // 新添加的 commitlint 的配置
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
        |   |-- style.css
        |-- js
            |-- utils.js

11.6 总结

添加 commitlint 的步骤

  • 安装依赖
  • 生成配置
  • 调整 package.json

推荐

【step-by-step】12. 使用 standard-version 生成 CHANGELOG.md

本篇文档的目的是希望前端同学能够以 复制粘贴 的方式,快速在 webpack 工程 中添加插件。因此,一些说明性质的知识将以 推荐阅读 的方式推荐给大家。

TL;DR

对于 Git 日志,我们期待的流程是:

  • commitizen : 生成 commit message 的约定模板。
  • commitlint: 检查 commit message 是否符合提交格式。
  • standard-version : 每次发版的时候,生成 changelog.md,方便查看发版信息以及工单与代码的对应关系

当前比较推荐的日志格式是 Angular Git Commit Guidelines ,我们后面所使用的插件也是基于 angular 日志格式的。

接下来将介绍如何在工程中安装 standard-version,并用它根据 Git 日志信息自动生成 changelog.md

12.1 步骤

根据 standard-version用工具思路来规范化 git commit message 中的示例,我们在项目中添加 standard-version的步骤:

  • 安装依赖
  • 调整 package.json 中的配置

12.2 具体流程

12.2.1 安装依赖

yarn add standard-version replace -D

12.2.2 添加命令

package.json 文件中,添加 script.release

{
  "script": {
    "release": "standard-version --no-verify --header '# Changelog'"
  }
}

12.2.3 添加 standard-version 的配置

package.json 中添加 standard-version 字段,并把 issue 的地址换掉( standard-version 默认将 git 地址作为 issue 地址。通常情况下,我们有自己的工单系统,需要把 issue 地址替换成工单系统地址)。

{
  "standard-version": {
    "scripts": {
      "postchangelog": "replace 'https://git.fzyun.io/frontend/templates/webpack-sample/issues/' 'https://kb.fzyun.io/issues/' CHANGELOG.md"
    },
    "skip": {
      "commit": true,
      "tag": true
    }
  }
}

12.2.4 私建 gitlab 仓库

git地址不完整

在私建 gitlab 仓库中,使用 standard-version 生成的 CHANGELOG.md 文件后,文件中的 git地址 显示不完整。

例如,在 CHANGELOG.md 文件中,我们可以看到 git的地址是

https://git.xx.xx///commit/7166e3cafa2757315a039c734f02dc347db1f7dd

而实际的地址应该是

https://git.xx.xx/frontend/templates/webpack-sample/-/commit/7166e3cafa2757315a039c734f02dc347db1f7dd
解决办法

根据 standard-version/issues/384 中的建议,我们在工程根目录下,创建 .versionrc 文件,并在文件中指定 commitUrlFormat 字段和 issueUrlFormat 字段。

.versionrc

{
  "commitUrlFormat": "https://git.xx.xxo/frontend/templates/webpack-sample/-/commit/{{hash}}",
  "issueUrlFormat": "https://git.xx.xx/issues/{{id}}"
}

同时,删掉 package.json 文件中的 standardVersion.scripts.postchangelog 属性:

{
  "standard-version": {
-   "scripts": {
-     "postchangelog": "replace 'https://git.xx.xx/frontend/templates/webpack-sample/issues/' 'https://kb.fzyun.io/issues/' CHANGELOG.md"
-   },
    "skip": {
      "commit": true,
      "tag": true
    }
  }
}

12.3 测试

运行下面的命令,插件将帮我们生成 CHANGELOG.md 文件。

npm run release

changelog-3.png

12.4 示例工程

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintignore
    |-- .eslintrc.js
    |-- .gitignore
    |-- .postcssrc.js
    |-- .prettierignore
    |-- .prettierrc.js
    |-- .stylelintrc
    |-- CHANGELOG.md // 新生成的 changelog 文件
    |-- commitlint.config.js
    |-- directoryList.md
    |-- index.html
    |-- package.json
    |-- build
    |   |-- webpack.base.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js
    |-- src
        |-- index.js
        |-- assets
        |   |-- style.css
        |-- js
            |-- utils.js

12.5 总结

在工程中添加 standard-version 的步骤:

  • 安装依赖
  • 调整 package.json 中的配置

推荐