Webpack-前端工程化的基石

在以前,为了减少 HTTP 请求,通常地,我们都会把所有的代码都打包成一个单独的 JS 文件。但是,如果这个 JS 文件体积很大的话,那就得不偿失了。

就拿咱们产品来说,都是 SPA 单页面应用,我们不可能在首屏加载所有的 JS 和 CSS 代码。

这时,我们不妨把所有代码分成一块一块,需要某块代码的时候再去加载它;还可以利用浏览器的缓存,下次用到它的话,直接从缓存中读取。很显然,这种做法可以加快我们网页的加载速度,webpack 刚好可以帮助我们。

1、什么是 webpack

webpack
  • WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript 等),并将其打包为合适的格式以供浏览器使用。

2、为什么要使用 webpack

  • 现在的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的 JavaScript 代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法

    • 模块化,让我们可以把复杂的程序细化为小的文件

    • 类似于 TypeScript 这种在 JavaScript 基础上拓展的开发语言:使我们能够实现目前版本的 JavaScript 不能直接使用的特性,并且之后还能转换为 JavaScript 文件使浏览器可以识别

    • scss,less等CSS预处理器

  • 这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别, 而手动处理又是非常繁琐的,这就为 WebPack 类的工具的出现提供了需求

3、webpack 入门

  • 创建项目目录如下:
 webpack-demo
 |- package.json
+ |- index.html
+ |- /src
+   |- index.js
  • 安装 webpack npm install webpack webpack-cli --save-dev

  • 执行 npx webpack

4、配置 webpack.config.js

  • 基础配置 mode、entry、output
const path = require('path')
​
module.exports = {
 mode: 'development',
 entry: path.resolve(__dirname, 'src/index'),
 output: {
 filename: 'main.js',
 path: path.resolve(__dirname, 'bundle')
 }
}

5、plugins

  • Plugin (插件) 是 webpack 生态的的一个关键部分。它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程

  • 在特定的时刻,做特定的事情

  • 在 webpack 运行的生命周期中会广播出许多的事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出的结果

  • clean-webpack-plugin、html-webpack-plugin 实例

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
​
const HtmlPlugin = new HtmlWebpackPlugin({
 template: path.resolve(__dirname, 'index.html')
})
​
module.exports = {
 mode: 'development',
 entry: path.resolve(__dirname, 'src/index'),
 output: {
 filename: '[name].[chunkHash:8].js',
 path: path.resolve(__dirname, 'dist')
 },
 plugins: [
 new CleanWebpackPlugin(),
 HtmlPlugin
 ]
}

6、loader

  • loader 就是一个打包的方案。对于特定的非 js 模块告诉 webpack 该如何打包。

  • webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。

  • loader 执行顺序:从上到下,从右到左

  • style-loader、css-loader、less-loader 实例

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
​
const HtmlPlugin = new HtmlWebpackPlugin({
 template: path.resolve(__dirname, 'index.html')
})
​
module.exports = {
 mode: 'development',
 entry: path.resolve(__dirname, 'src/index'),
 output: {
 filename: '[name].[chunkHash:8].js',
 path: path.resolve(__dirname, 'dist')
 },
 module:{
 rules: [{
 test: /\.js$/,
 use: ['babel-loader']
 }, {
 test: /\.css$/,
 use: ['style-loader', 'css-loader']
 }, {
 test: /\.less$/,
 use: ['style-loader', 'css-loader', 'less-loader']
 }]
 },
 plugins: [
 new CleanWebpackPlugin(),
 HtmlPlugin
 ]
}

7、webpack-dev-server

  • --inline 刷新页面 --hot 热更新

  • --progress 显示进度条

  • --color 颜色

  • --open 打开浏览器

  • --port 8001 端口

// package.json "start": "webpack-dev-server --inline --progress --color --open --port 8001"

8、 webpack 构建流程

  • 初始化参数,从配置文件和 shell 语句中读到的参数合并,得到最后的参数

  • 开始编译:用合并得到的参数初始化 complier 对象,加载所有配置的插件,执行 run 方法开始编译

  • 确定入口,通过 entry 找到入口文件

  • 编译模块,从入口文件出发,调用所有配置的 loader 对模块进行解析翻译,递归找到该模块依赖的模块进行处理

  • 完成模块编译,得到每个模块被翻译之后的最终的内容和依赖关系

  • 输出资源,根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,在把每个 chunk 转换成一个单独的文件加载到输出列表(这一步是修改输出内容的最后机会)

  • 输出完成,根据配置中输出的路径和文件名,把内容写到文件系统中

  • 在以上过程中,webpack会在特定的时间点广播出特定的事件,插件在监听事件后会执行特定的逻辑,改变 webpack 的运行结果

9、优化 Webpack 的构建

  • 使用高版本的 Webpack 和 Node.js, 多进程/多实例构建:HappyPack(不维护了)、thread-loader

    • 压缩代码

    • webpack-parallel-uglify-plugin

    • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)

    • terser-webpack-plugin 开启 parallel 参数,多进程并行压缩

    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。

    • 使用基于 Node 库的 imagemin (很多定制选项、可以处理多种图片格式)

    • 配置 image-webpack-loader 图片压缩

  • 缩小打包作用域:

    • exclude/include (确定 loader 规则范围)

    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)

    • resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)

    • resolve.extensions 尽可能减少后缀尝试的可能性

    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)

    • IgnorePlugin (完全排除模块)忽略本地化内容之打包核心模块

    • 合理使用 alias

  • 提取页面公共资源:

    • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中

    • 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件

  • 基础包分离:

    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。

    • HashedModuleIdsPlugin 可以解决模块数字 id 问题

  • 充分利用缓存提升二次构建速度:

    • babel-loader 开启缓存

    • terser-webpack-plugin 开启缓存

    • 使用 cache-loader 或者 hard-source-webpack-plugin

  • Tree shaking

    • 打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高 tree shaking 效率

    • purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)

    • 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking

    • 使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码

  • Scope Hoisting

    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

    • 必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法

    • webpack.optimize.ModuleConcatenationPlugin 开启 Scope Hoisting 作用域提升,提升代码在浏览器中的执行速度

10、vue-cli 脚手架简析

npm install @vue/cli
vue create myApp

你可能感兴趣的:(Webpack-前端工程化的基石)