webpack功能以及原理

核心思想

webpack 中一切皆为模块。由于 webpack 不支持 js 以外的文件,因此项目中的 css,字体等都会通过不同的 loaders 压缩打包成一个 js,公共的代码抽离,也可以指定部分单独打包。

#打包原理

webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。并不是什么commonjs或者amd之类的模块化规范。webpack就是识别你的 入口文件。识别你的模块依赖,来打包你的代码。至于你的代码使用的是commonjs还是amd或者es6的import。webpack都会对其进行分析。来获取代码的依赖。webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块,所以webpack.config.js是以commonjs形式书写的(node中的模块化是commonjs规范的)

webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。

功能

依赖管理

方便引用第三方模块、让模块更容易复用、避免全局注入导致的冲突、避免重复加载或加载不需要的模块。

例如在一个页面中,我引用了 a,b,c 三个 js 文件,他们互相之间有个字的依赖关系,所以引入的时候顺序出错就会导致问题。如果
使用 webpack 的话就会打成一个文件并自动处理依赖关系
WebPack 打包时, 会把有依赖关系的 js 模块(物理形式上,一般表现为一个.js 文件),打包成一个 bundle。而依赖关系是基于 require(‘…’),而且一般不要相互依赖。

合并代码

把各个分散的模块集中打包成大文件,减少 HTTP 的请求链接数,配合 UglifyJS 可以减少、优化代码的体积。

##使用插件
通过不同的 loaders 实现,例如 babel 把 ES6+转译成 ES5-,eslint 可以检查编译期的错误,编译 jsx,less/sass 转 css,自动处理 img 图片路径等等

构建过程

  1. 解析配置参数,合并从 shell 传入和 webpack.config.js 文件的配置信息,输出最终的配置信息
  2. 注册配置中的插件,好让插件监听 webpack 构建生命周期中的事件节点,做出对应的反应
  3. 解析配置文件中 entry 入口文件,并找出每个文件依赖的文件,递归下去
  4. 在递归每个文件的过程中,根据文件类型和配置文件中 loader 找出相对应的 loader 对文件进行转换
  5. 递归结束之后得到每个文件最终的结果,根据 entry 配置生成代码 chunk
  6. 输出所有 chunk 到文件系统

解析路径

webpack 中有一个很关键的模块 enhanced-resolve 就是处理依赖模块路径的解析的,这个模块可以说是 Node.js 那一套模块路径解析的增强版本,有很多可以自定义的解析配置。

  • 解析相对路径

    1. 查找相对当前模块的路径下是否有对应文件或文件夹
    2. 文件则直接加载
    3. 是文件夹则继续查找文件夹下的 package.json 文件
    4. 有 package.json 文件则按照文件中 main 字段的文件名来查找文件
    5. 无 package.json 或者无 main 字段则查找 index.js 文件
    
  • 解析模块名

    1. 查找当前文件目录下,父级目录及以上目录下的 node_modules 文件夹
    2. 看是否有对应名称的模块
    3. 解析绝对路径(不建议使用)
    4. 直接查找对应路径的文件…

resolve.alias

创建 import 或 require 的别名,来确保模块引入变得更简单。

假设我们有个 utils 模块极其常用,经常编写相对路径很麻烦,希望可以直接 import ‘utils’ 来引用,那么我们可以配置某个模块的别名,如:

1
2
3
4
5
alias: {
utils: path.resolve(**dirname, 'src/utils') // 这里使用 path.resolve 和 **dirname 来获取绝对路径
}
//文件内
import 'utils/query.js' // 等同于 import '[项目绝对路径]/src/utils/query.js'

resolve.extensions

这个配置可以定义在进行模块路径解析时,webpack 会尝试帮你补全那些后缀名来进行查找,

1
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.css'],

resolve.mainFields

有 package.json 文件则按照文件中 main 字段的文件名来查找文件

webpack 的 resolve.mainFields 配置可以进行调整。当引用的是一个模块或者一个目录时,会使用 package.json 文件的哪一个字段下指定的文件,默认的配置是这样的:

1
2
3
4
5
6
7
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],

// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
},

因为通常情况下,模块的 package 都不会声明 browser 或 module 字段,所以便是使用 main 了

resolve.mainFiles

无 package.json 或者无 main 字段则查找 index.js 文件

当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的,是的,使用 resolve.mainFiles 字段,默认配置是:

1
2
3
resolve: {
mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},

通常情况下我们也无须修改这个配置

Plugins

我们一般会把开发的所有源码和资源文件放在 src/ 目录下,构建的时候产出一个 build/ 目录,通常会直接拿 build 中的所有文件来发布。有些文件没经过 webpack 处理,但是我们希望它们也能出现在 build 目录下,这时就可以使用 CopyWebpackPlugin 来处理了。

webpack-dev-server

webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。让我们设置以下:

  • publicPath 字段用于指定构建好的静态文件在浏览器中用什么路径去访问

before 和 after 配置用于在 webpack-dev-server 定义额外的中间件,如

1
2
3
4
5
before(app){
app.get('/some/path', function(req, res) { // 当访问 /some/path 路径时,返回自定义的 json 数据
res.json({ custom: 'response' })
})
}

before 在 webpack-dev-server 静态资源中间件处理之前,可以用于拦截部分请求返回特定内容,或者实现简单的数据 mock。

after 在 webpack-dev-server 静态资源中间件处理之后,比较少用到,可以用于打印日志或者做一些额外处理。…

https://juejin.im
掘金 — 一个帮助开发者成长的社区

webpack-dev-middleware

用中间件提升 webpack-dev-server 能力

原理

HRM 原理

概念
compile: webpack 的核心。js 编译、拆包。
hmr-server: 建立连接并完成模块热更新的推送。
bundle-server: 静态服务器。
bundle.js: client 端。
hmr-runtime: 注入到 bundle.js 中的代码。

更新流程
热更新开启后,当 webpack 打包时,会向 client 端注入一段 HMR runtime 代码,同时 server 端(webpack-dev-server 或是 webpack-hot-middware)启动了一个 HMR 服务器,它通过 websocket 和注入的 runtime 进行通信。

当 webpack 检测到文件修改后,会重新构建,并通过 ws 向 client 端发送更新消息,浏览器通过 jsonp 拉取更新过的模块,回调触发模块热更新逻辑。

1.修改了一个或多个文件。 2.文件系统接收更改并通知 Webpack。
3.Webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行了更新。
4.HMR Server 使用 websockets 通知 HMR Runtime 需要更新。HMR 运行时通过 HTTP 请求这些更新(jsonp)。
5.HMR 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新(这是个大坑。。)。

tree shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。未使用的代码不会被打包

优化

雪碧图

如果你使用的 webpack 3.x 版本,需要 CSS Sprites 的话,可以使用 webpack-spritesmith 或者 sprite-webpack-plugin。

图片压缩

file-loader 来处理图片文件,在此基础上,我们再添加一个 image-webpack-loader 来压缩图片文件。简单的配置如下:

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
module.exports = {
// ...
module: {
rules: [
{
test: /.*\.(gif|png|jpe?g|svg|webp)$/i,
use: [
{
loader: 'file-loader',
options: {}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { // 压缩 jpeg 的配置
progressive: true,
quality: 65
},
optipng: { // 使用 imagemin-optipng 压缩 png,enable: false 为关闭
enabled: false,
},
pngquant: { // 使用 imagemin-pngquant 压缩 png
quality: '65-90',
speed: 4
},
gifsicle: { // 压缩 gif 的配置
interlaced: false,
},
webp: { // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
quality: 75
},
},
],
},
],
},
}

###代码压缩
webpack 4.x 版本运行时,mode 为 production 即会启动压缩 JS 代码的插件,
而对于 webpack 3.x,可以使用压缩 JS 代码插件 uglifyjs-webpack-plugin

对于 HTML 文件,之前介绍的 html-webpack-plugin 插件可以帮助我们生成需要的 HTML 并对其进行压缩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 配置输出文件名和路径
template: 'assets/index.html', // 配置文件模板
minify: {
// 压缩 HTML 的配置
minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
minifyJS: true // 压缩 HTML 中出现的 JS 代码
}
})
]
}

分离代码

webpack4 中我们使用

mini-css-extract-plugin

分离出一个单独的 css 文件,这样可以利用缓存更好的访问静态资源

使用 webpack 4.x 的 optimization。splitChunks
webpack 的作者推荐直接这样简单地配置:

1
2
3
4
5
6
7
8
9
module.exports = {
// ... webpack 配置

optimization: {
splitChunks: {
chunks: 'all' // 所有的 chunks 代码公共的部分分离出来成为一个单独的文件
}
}
}

loader

Loaders 就是对一个模块源码的转换。它们可以在引入或加载文件时对文件进行预处理。

loader 就是一个 node 模块,它输出了一个函数。当某种资源需要用这个 loader 转换时,这个函数会被调用。并且,这个函数可以通过提供给它的 this 上下文访问 Loader API。

1
2
3
4
5
6
7
8
9
10
11
module.exports = function(src) {
//可以通过 this 访问 Loader API
//this 是由 webpack 提供的,可以直接使用
}

module.exports = function(src) {
//src是原文件内容(abcde),下面对内容进行处理,这里是反转
var result = src.split('').reverse().join(''); //edcba
//返回JavaScript源码,必须是String或者Buffer
return `module.exports = '${result}'`;
}