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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容