复习--webpack4.0基础使用

###webpack简述
webpack是目前最流行的模块管理器,是前端构建体系中不可或缺的一环。

如果用一句话概括的话,webpack本身只做了一件事件:运行一个入口.js文件,分析它的依赖,最后把文件导出。如果你需要在这个过程中做点其他什么事情,请使用webpack的强大插件机制。

基本使用

第一步,初始化文件夹

1
yarn init -y

第二步,添加 webpackwebpack-cli

1
yarn add webpack webpack-cli -D

第三步, 添加 webpack.config.js文件,这是webpack的配置文件,可以指定其他名字,这是默认名字。虽然webpack4号称零配置,但一些重要的配置项和基本信息还是要了解一下的。

1
2
3
4
5
6
7
8
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
},
plugins: [],
module: {}
}

第四步, 执行webpack进行打包

1
npx webpack

入口文件

webpack干的事情就是执行一个 .js文件(不一定是.js文件,node支持运行就可以),然后分析收集它依赖了(引用、import、require)了哪些模块,然后对这些代码进行处理,最后导出。

这个.js文件就是所谓的入口文件,可以在webpack.config.js文件中这么配置

1
2
3
module.exports = {
entry: "./src/index.js",
}

出口

文件导出的目录与文件夹

1
2
3
4
5
6
7
module.exports = {
output: {
filename: "bundle.[hash:8].js", //文件名
path: path.join(__dirname, "dist"), //文件夹
publicPath: '' // 路径前缀,比如 https://static.aliyun.com
},
}

输出管理

设置html输出

使用html-webpack-plugin为入口文件关联并解析html模版。

1
yarn add html-webpack-plugin -D

配置插件

1
2
3
4
5
6
7
8
9
10
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html"
})
]
}

清理dist

clean-webpack-plugin在每次构建之前清理文件夹。

1
npm install --save-dev clean-webpack-plugin

1
2
3
4
5
6
7
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
plugins: [
new CleanWebpackPlugin('./dist')
]
}

开发模式与发布模式

现实中对于模块的处理,在开发时和发布时是两种完全不同的方式。比如开发时并不需要压缩代码、发布时也不需要起一个简易http服务器。
webpack4.0中可以通过 mode配置项来配置开发模式还是发布模式,通过配置它可以得到官方默认的开发或者发布模式

1
2
3
module.exports = {
mode: "development", //production
}

使用本地开发服务器

webpack-dev-server提供了一个简单的Web服务器和使用实时重新加载的能力。

1
2
3
4
5
6
7
8
module.exports = {
devServer: {
port: 8080,
contentBase: "./dist", //告诉webpack-dev-server文件路径在哪
progress: true
},

}

1
yarn add webpack-dev-server -D

loader

默认情况下,webpack只支持.js文件的加载,比如想加载css文件就需要使用css加载器。
你需要在 module配置项下为每种文件配置 rules。以css文件为例,使用test正则匹配文件,
然后在use数组中依次写上你要使用的loader, 它们会按数组从下到上的顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
"css-loader",
"postcss-loader"
]

]
},
}

加载图片

file-loader插件会将文件直接复制到出口目录下,并以[hash][ext]的方式重新命名

1
2
3
4
5
6
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}

加载字体
1
2
3
4
5
6
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
加载本地数据(xml、json、csv)

webpack默认支持.json格式的文件,但是对于 .xml、.csv等文件还是需要安装额外的包

1
yarn add  csv-loader xml-loader -D

1
2
3
4
5
6
7
8
9
10
11
12
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
1
2
3
4
// src/index.js
import xmlData from './data.xml'

console.log(Data)

插件

webpack本身的功能是非常有限的,如果要使用一些其他能力,就需要使用插件。loader的能力是为webpack扩张可以处理的文件类型,而 plugins则是为webpack扩张生命周期中的某一部分能力。比如我们想使用html模版就需要html-webpack-html插件,使用前请先安装插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
minify: {
collapseWhitespace: true
}
}),

]
}

Tree shaking(树摇曳)

树抖动是JavaScript上下文中常用于消除死代码的术语。举个例子:

1
2
3
4
5
6
7
8
// math.js
export function square(x) {
return x * x;
}

export function cube(x) {
return x * x * x;
}

1
2
3
4
// index.js
import { cube } from './math.js';

console.log(cube(3))

默认情况下,webpack会使用树摇曳优化,打包时不会包含square函数的代码,因为它没有被使用。

tree-shaking 仅在 mode: 'production'模式下有效

sourceMap

webpack支持四种方式的souceMap

1
2
3
4
5
6
7
8
9
10
module.exports = {
// 普通 source-map, 产生 .map文件,与代码文件产生关联,报错提示详细行和列
devtool: 'source-map',
// 和 source-map的区别是不会产生.map文件,集成增加到打包后的文件中,会增加打包文件的大小。
// devtool: 'eval-source-map',
// 简单source-map,会产生.map文件,但不会与代码关联,报错仅有简单提示,可保护源码。
// devtool: 'cheap-module-source-map',
// 简单的 eval-source-map

}

代码分割

使用 splitChunks 之后,webpack会自动将公共代码以代码块的方式分离出来,以多个文件的方式输出到dist目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
optimization: {
splitChunks: {
// 代码分割。
cacheGroups: { //缓存组
common: { //公共模块
chunks: 'initial',
minSize: 0, //大于该值的代码块抽离到公共模块
minChunks: 2, // 调用次数大于该值的抽离到公共模块
}
}
}
}
}

第三方模块分离

第三方模块大部分情况下我们不会去更新它,可以把它单独打成一个包,以高效利用缓存。

如果设置了vendor.chunks: 'all',会将静态、动态导入的包都压缩在一起,这将导致动态导入不可用,代码报错。

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
module.exports = {
optimization: {
splitChunks: {
// 代码分割。
cacheGroups: {
//缓存组
common: {
//公共模块
chunks: "initial",
minSize: 0, //大于该值的代码块抽离到公共模块
minChunks: 2 // 调用次数大于该值的抽离到公共模块
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "initial"
},
//如果你想把所有动态包打一起。
dynamic: {
test: /[\\/]node_modules[\\/]/,
name: "dynamic",
chunks: "async"
}
}
}

}
文件hash名的问题。

假设我们有src/index.jsnode_modules/vuenode_modules/lodash。默认情况下,我们修改了src/index.js文件, 导出verdordynamic的文件hash名也会更新,这经常是我们不愿意看到的,因为这会让缓存失效。
解决方案,使用HashedModuleIdsPlugin

1
2
3
4
5
6
7
8
9
10

const webpack = require("webpack")

module.epoxrts = {
output: {
filename: "bundle.[contenthash].js",
path: path.join(__dirname, "dist")
},
plugins: [new webpack.HashedModuleIdsPlugin()]
}

延时加载

使用延时加载的模块会被单独分割成一个.js文件。并且在代码运行时以JSONP(动态拼接script返回.js)的形式动态加载。
首先安装

1
yarn add @babel/plugin-syntax-dynamic-import -D

配置webpack.config.js.js加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
test: /\.js$/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
'@babel/plugin-syntax-dynamic-import'
]
}
}
]
}

编写代码, import()返回一个cmd模块,所以需要module.default

1
2
3
4
5
6
7
setTimeout(() => {
import(/* webpackChunkName: "Vue" */'vue').then((module) => {
console.log(module.default)
}).catch((err)=> {
console.log('module vue load fail', err)
})
}, 1000)

preload/prefetch
  1. prefetch:将来某些导航可能需要资源
  2. preload:当前导航期间可能需要资源

  3. preload的块开始与父块并行加载。父块完成加载后,将启动prefetch的块

  4. preload的块具有中等优先级并立即下载。浏览器空闲时会下载prefetch的块。
  5. 父组块应立即请求preload的块。未来的任何时候都可以使用prefetch的块
1
2
3
4
5
6
7
import(/* webpackPrefetch: true */ 'LoginModal')
// <link rel="prefetch" href="login-modal-chunk.js">
// document.head.appendChild(link)

import(/* webpackPreload: true */ 'ChartingLibrary');
// <script src='xxx'></script>
// document.head.appendChild(script)

优化

webpack打包结果分析

webpack支持打包结果的导出,运行以下命令会在当前目录下生成stats.json文件,该文件存储着各个模块的打包信息。

1
npx webpack --mode production --profile --json > stats.json

如果觉得json文件看着太累,你可以把文件上传到这些webpack-chartwebpack-visualizer网站上,它们提供了一个可视化图表来分析你的打包结果。
或者使用webpack-bundle-analyzer。它可以在本地生成一个html服务,在你运行 npx webpack --profile --json > stats.json命令之后。

webpack打包加速

happypack

默认情况下webpack是单线程打包,在项目比较大的情况下,打包速度会很慢。happypack是一个多性程任务,它可以用多线性的方式去执行其他任务。比如处理css、js。

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
38
39
40
41
42
43
module.exports = {

plugins: [
new Happypack({
id: 'js',
use: [
{
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
],
}
}
]
}),
new Happypack({
id: 'css',
use: [
"css-loader",
"postcss-loader"
]
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude:/node_modules/,
include: path.resolve('src'),
use: [
'Happypack/loader?id=js'
]
},
{
test: /\.css$/,
use: [
'Happypack/loader?id=css',
]
}
]
}
}
noParse

noParse配置项可以跳过匹配的模块,不作处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module: {
noParse: /jquery/,
rules: [
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
"css-loader",
"postcss-loader"
]

]
},
}

exclude 和 include

loader匹配模块的时候可以通过指定 exclude来跳过一些文件夹,从而提高匹配速度。
loader匹配模块的时候可以通过指定 include来指定文件夹,从而提高匹配速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
module: {
noParse: /jquery/,
exclude:/node_modules/,
include: path.resolve('src'),
rules: [
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top'
}
},
"css-loader",
"postcss-loader"
]

]
},
}