快速开始

定义

基于node.js来开发的静态资源打包工具,最基本的功能递归解析依赖的文件并打包,顺便把模块化代码转化成可以直接在浏览器上运行的代码

下载

1
npm i webpack webpack-cli -D //下载webpack和调用webpack的命令

不下载到全局的原因:

  • 不同项目可能需要使用不同版本的webpack

  • 下载到全局无法被项目中的package.json文件记录,分享项目给其他人使用的时候需要额外下载webpack

webpack打包命令

  • 如果全局安装:

    1
    webpack
  • 如果安装到本地

    1
    npx webpack #npx会自动在node_modules/.bins目录下查找可执行文件

    或者

    1
    npx webpack --watch #实时监测文件的变化,变化后保存文件自动打包

webpack.config.js

webpack配置文件,在此自定义webpack配置,就不用每次都在命令行中指定配置参数。

这个文件的执行环境是node.js,必须使用cjs语法,使用module.exports导出配置对象

常见属性

entry

  • 指定一个入口文件:

    1
    entry:'./src/main.js'
  • 指定多个入口文件:

    1
    2
    3
    4
    5
    6
    entry:{
    app:'./src/main.js',
    app2:'./src/main2.js'
    }
    //或者
    //entry: ['./src/index.js', './src/another-entry.js']
  • 更详细的配置:

    1
    2
    3
    4
    5
    6
    7
    entry:{
    app:{
    import:'./src/main.js',//指定入口文件
    dependOn:'lodash',//指定依赖的模块
    filename:'page/[name].bundle.js',//指定打包后的js文件名称
    } //app是模块名
    }

output

属性值为一个对象

1
2
3
4
5
6
{
filename:打包后输出的js文件名
path:打包后所有文件的存放位置,必须是绝对路径,所以使用path.resolve()来拼接路径,如果最终拼接的不是绝对路径,还会和当前工作目录拼接,确保结果是一个绝对路径。
clean:布尔值,为true表示每次打包清除之前的打包文件
publicPath:指定所有文件的公共路径
}

mode

定义打包模式(必填)

1
mode:developmemt||production

开发模式和打包模式的区别

开发环境:

  • 不需要使用文件缓存,所以不需要给文件名额外添加[contenthash]
  • 保留devServer
  • 删除压缩css,js文件配置

生产环境:

  • 需要使用缓存,保留文件额外名[contenthash]
  • 删除devServer
  • 保留压缩css,js文件配置
  • 使用tree-shaking

devtool

devServer

webpack-dev-server配置的地方

webpack-dev-server是一个由webpack团队维护的,webpack高度支持的独立的工具,用于在开发过程中提供一个开发服务器, 使用不需要导入,但是需要额外下载。

安装:

1
npm i webpack-dev-server -D

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
devServer:{
static:'./dist',//提供开发环境中的一个静态资源目录,静态资源是指在开发中我们不希望被打包的一些资源
port:8080,//配置服务器端口号
compress: true, //保证传输的是压缩文件从而提高我们的传输效率
//headers:配置响应头
proxy:[
{
path:'/api',//使用的是前缀匹配
changeOrigin:true,
target:'http://localhost:8081',
pathRewrite: { '^/api': '' }
}
]//配置代理,解决跨域问题,因为搭建的本地服务器部署的网页也可能发请求获取其他源下的资源。
}
}

开启服务器:

1
npx webpack-dev-server

运行这个命令不仅会启动Webpack的打包过程(打包到内存,不输出实际文件),还会启动一个开发服务器,部署的是打包到内存中的文件。至于是生产打包还是开发打包,就看当前的webpack配置文件了,但是一般开发环境才会用。

这个服务器会监听源文件(src目录下的js文件)的变化,源文件修改并保存会自动重新编译(因为只编译发生变化的部分)和刷新浏览器。

模块

资源模块

用来加载图片或者字体

asset/resource

导出资源打包后的路径,打包后导出源文件

1
2
3
4
5
6
7
8
9
module:{
rules:[{
test:/\.jpg$/,
type:'asset/resource',
generator:{
filename:'images/[contenthash][ext]'#打包后的图片名称
}
}]
}

asset/inline

导出源的data:url,打包后不会导出源文件

asset/source:

导出资源的源码,如果是文本文件就是内容,不会导出源文件

asset

webpack将按照默认条件,自动地在resource和 inline 之间进行选择:小于8kb的文件,将会视为inline模块类型,否则会被视为resource模块类型。也可以修改这个配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module:{
rules:[{
test:/\.jpg$/,
type:'asset/resource',
generator:{
filename:'images/[contenthash][ext]'#打包后的图片名称
}
parser: {
dataUrlCondition:{
maxSize: 4 * 1024 *1024
}//4Mb
}
}]
}

loader

扩展webpack的功能,帮助webpack解析其他类型(非js类型)的文件,并把这些文件转换成有效的模块,实现代码转换;只要有对应的loader,万物皆是模块。

loader在webpack.config.js文件中使用的时候通常不需要导入,直接使用即可, Webpack 会根据配置中的名称自动查找并使用相应的加载器

解析css文件

我们需要下载css-loader,以及style-loader或者mini-css-extract-plugin

style-loader: 把解析后的css样式放到打包后的html文件的style标签中

mini-css-extract-plugin:把css提取为单独的文件,多个css文件会合并为一个单独的css文件,在html-webpack-plugin插件的作用下还会自动在html文件中引入(在head标签中)。

1
2
3
4
5
6
7
8
9
10
module:{
rules:[
{
test:/\.css$/,//匹配css文件
use:[MiniCssExtractPlugin.loader,'css-loader']
//或者
// use:['style-loader','css-loader']
},
]
}

loader是支持链式调用的,顺序从右往左,就拿上面的例子来说,先对css文件使用css-loader,再使用style-loader。

注意:单纯打包css文件不会修改原来的css代码,也不会压缩代码,只是把原来的css代码放到一个文件中。压缩,修改css代码需要借助其他插件。

babel-loader

webpack本身只能对js代码打包,压缩,不能进行ES6到ES5的转换,需要借助babel-loader将新的语法转换成低版本的语法,实现语法降级,比如对esm语法的转化。

安装:

1
npm i babel-loader @babel/core @babel/preset-env --save-dev

babel-loader 是一个 Webpack 加载器,用于在 Webpack 构建过程中使用 Babel 转译 JavaScript 代码。

@babel/core 是 Babel 的核心库,负责执行实际的代码转译工作

@babel/preset-env 是一个智能预设,可以根据目标环境自动选择需要的 Babel 插件,以生成兼容的代码。

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}

代码分离

我们可能在不同的文件引入相同的模块,这样就可能出现重复打包的问题。

将公共的代码抽离出去,减小啊打包后文件的大小,从而提高首屏的加载效率。

分离方法

模块抽离

在得知哪个模块被重复引用的前提下,抽离出该模块。

1
2
3
4
5
6
7
8
9
10
11
entry:{
index:{
import:'./src/main.js',
dependOn:'shared'//表示依赖哪个模块
},
another:{
import:'./src/another.js',
dependOn:'shared'
},
shared:'lodash'
} //index,another,shared是模块名

动态导入

1
import('./math.js')//返回一个promise对象

如果引入的模块是命名导出,传入回调函数的参数格式形如

1
2
3
4
{
add: f(x,y),
sub: f(x,y)
}

调用then时可以使用解构赋值来模仿按需导入

1
2
3
4
5
export const add = (x,y)=>{
console.log(x+y)
}//lazy.js代码

import('./lazy.js').then( ({add})=>{add(1,1)} )

如果是默认导出,传入回调函数的参数格式形如,就不能直接解构了。

1
2
3
4
5
6
{
default:{
add: f(x,y),
sub: f(x,y)
}
}

动态导入的文件打包的时候会被自动抽离为一个单独的文件,即便没有被多次使用。

编译的时候动态导入的文件会被打包好,使用的时候再导入

魔法注释

1
2
import(/* webpackChunkName:'math' */'./lazy.js') //指定动态导入的文件打包后的模块名(也许不是最后文件名)
import(/* webpackPrefetch:true */'./lazy.js') //首页内容都加载完毕,等待网络空闲的时候再加载这个文件

split-chunks-plugin

使用插件split-chunks-plugin

插件

html-webpack-plugin

生成一个自动引用打包后的文件的html文件

安装:

1
npm i html-webpack-plugin -D

在webpack.config.js中配置:

1
2
3
4
5
6
7
8
9
10
11
12
const HtmlWebpackPlugin = require(' html-webpack-plugin') //引入
module.exports = {
plugins:[
new HtmlWebpackPlugin({
template:'html模板路径',
filename:'输出的html文件名',
title:'打包后html文件的title',
inject:'指定打包后的js文件的的注入位置',
chunks:[]//指定引入哪些打包后的js文件(入口文件),默认全引入
})
]
}//注册

构建多页面应用:在plugins里new多次HtmlWebpackPlugin

自定义打包后的html的标题:

  • 在配置对象中添加titile属性并赋值
  • html模板中的title标签内容替换为<%= htmlWebpackPlugin.options.title %>

mini-css-extract-plugin

把css提取到一个单独的css文件里,代替style-loader

安装:

1
npm i mini-css-extract-plugin -D

配置:

1
2
3
4
5
6
7
8
9
10
11
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins:[
new MiniCssExtractPlugin({
filename:'styles/main.css' //打包后css文件的名称
})
]
modules:{
rules:[ {test:/\.css$/,use:[MiniCssExtractPlugin.loader,'css-loader']} ]
}
}//注册

css-minimizer-webpack-plugin

压缩打包后的css文件

安装:

1
npm i css-minimizer-webpack-plugin -D

配置:

1
2
3
4
5
6
7
const CssMinimizeWebpackPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
mode: 'production'
optimization: {
minimizer:[new CssMinimizerPlugin()]
}
}//注册

split-chunks-plugin

自动抽离重复引用的模块,无需下载,webpack内置, 但自动抽离代码在大型项目中使用,构建过程较费时

配置:

1
2
3
4
5
6
module.exports = {
mode: 'production'
optimization: {
splitChunks:{chunks:'all'}
}
}//注册

webpack-bundle-analyzer

可以帮助开发者可视化和分析 Webpack 打包后的文件大小和内容,它生成一个交互式的报告,显示每个模块的大小及其在最终打包文件中的占比,从而帮助识别和优化代码。

安装:

1
npm i webpack-bundle-analyzer -D

配置:

1
2
3
4
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports = {
plugins:[new BundleAnalyzerPlugin()]
}//注册

terse-webpack-plugin

用来压缩js代码,但是Webpack 5 内置了对 Terser 的支持,在生产模式下会自动使用 Terser 进行js代码压缩,这一功能已经是webpack核心功能的一部分。但是如果需要自定义 Terser 的选项,仍然需要安装 terser-webpack-plugin 并进行相应的配置。这时,terser-webpack-plugin 是作为一个第三方插件来使用的。

tree-shaking

基于esm语法(importexport),只有生产模式才会开启tree-shaking,开启了生产模式就会启用tree-shaking

通过配置:

1
module.exports = { optimization:{usedExports:true} }

能让webpack更积极的使用tree-shaking,那些未在外部模块使用的导出也会被移除,就拿下面的例子来说,未开启上述配置,subtract不会被删除,开启后就会被删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//src/utils.js
export function add(a, b) {
return a + b;
}

export function subtract(a, b) {
console.log('This is subtract function');
return a - b;
}

// 内部调用 subtract
function internalCall() {
subtract(10, 5);
}

internalCall();
1
2
3
4
//src/index.js
import { add } from './utils';

console.log(add(1, 2));

sideEffects

配置在package.json中,可以将其设置为 false 或提供一个数组,列出有副作用的文件,告诉webpack哪些文件是有副作用的。因为有的文件就是没有导出的,比如css文件,不能因为这些文件没有导出就在打包的时候把他们删除

1
"sideEffects": ["./src/polyfills.js"]

开发模式只要被引入就会被打包,生产环境有副作用的只要引入就会被打包,否则被使用才会被打包

搭建vue脚手架

npm包下载

webpack开发必备:

1
npm i webpack webpack-cli html-webpack-plugin webpack-dev-server -D

vue相关:

1
npm i vue-template-compiler vue-loader -D

vue-template-compiler用来解析vue模板

vue-loader用来解析.vue文件

配置vue-loader:

1
2
3
4
5
6
7
8
9
const {VueLoaderPlugin} = require('vue-loader') //vue-loader还包含一个plugin
module.exports = {
plugins:[
new VueLoaderPlugin() //作用是让其他文件的(js,scss文件)的解析规则复用到解析vue文件
],
module:{
rules:[ {test:/\.vue$/,use:'vue-loader'} ]
}
}

css相关:

1
npm i css-loader mini-css-extract-plugin -D

sass相关:

1
npm i sass sass-loader -D

babel相关:

1
npm i babel-loader @babel/core @babel/preset-env -D

生产环境相关

1
npm i vue axios vue-router

注意vue下载的是vue2

模拟vue-cli打包

1
2
3
4
5
6
{
"scripts": {
"serve": "webpack-dev-server --mode development",
"build": "webpack --mode production"
}
}

完整配置文件

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
44
45
46
47
48
49
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const {VueLoaderPlugin} = require('vue-loader')
module.exports = {
entry:'./src/main.js',
output:{
filename:'main.js',
path:path.resolve(__dirname,'dist'),
clean:true
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
inject:'body'
}),
new MiniCssExtractPlugin({
filename:'styles/main.css'
}),
new VueLoaderPlugin()//作用是让其他文件的(js,scss文件)的解析规则复用到解析vue文件
],
module:{
rules:[
{
test:/\.s[ca]ss$/,
use:[ MiniCssExtractPlugin.loader,'css-loader','sass-loader'] //先使用sass-loader再使用css-loader
},
{
test:/\.vue$/,
use:'vue-loader'
},
{
test:/\.js$/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
}
]
},
mode:'development',
devServer:{
static:'./dist'
}
}

webpack打包流程

  • 合并webapck配置文件和命令行中的参数,创建compiler对象

  • 加载所有配置的插件,执行compiler对象的run方法,开始编译

  • 从入口文件开始,使用配置的loader递归解析依赖的文件,把他们转化成可用的模块,并构建模块依赖图

  • 把入口文件和它依赖的模块打包成一个单独的文件。

loader和plugin的区别

loader主要用来编译,转换文件,扩展了模块化的范围,只作用在打包文件之前。

webpack在运行过程中会产生许多广播事件,plugin通过监听webpack的广播事件,调用webpack提供的api来改变webpack的打包结果;插件可以扩展 Webpack 的功能,比如生成html文件,压缩css代码,可以作用在打包任何时期