快速开始

定义

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

下载

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

不下载到全局的原因:

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

  • 下载到全局无法被项目中的package.json文件记录,分享项目给其他人使用的时候需要额外下载webpack,对于没有前端基础的人可能甚至不知道需要下载webpack。

webpack打包命令

  • 如果全局安装:

    1
    webpack
  • 如果安装到本地

    1
    npx webpack #npx会自动在node_modules/.bins目录下查找可执行文件,效果和运行package.json中的脚本一样

    或者

    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是模块名,用来替代[name]的位置
    }

output

属性值为一个对象

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

mode

定义打包模式(必填)

1
mode:developmemt||production

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

开发环境:

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

生产环境:

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

contenthash

在 Vue 项目中,特别是在使用 Vue CLI 构建项目时,可以通过配置生成带有哈希值的文件名来优化缓存。哈希值通常用于静态资源(如 JavaScript、CSS 和图片)的文件名中,以确保当文件内容发生变化时,浏览器能够识别并下载最新版本的资源,而不是使用旧的缓存。

Vue CLI 基于 Webpack 实现了这一功能,因此可以利用 Webpack 的相关配置,来控制哈希值的生成方式。以下是几种常见的哈希计算方式:

**[hash]**:基于某次构建过程中的编译结果,生成唯一一个哈希值,一次打包后的所有文件共用同一个hash值。这意味着如果构建过程中有任何文件发生了变化,所有输出文件的哈希值都会改变。

1
2
3
output: {
filename: '[name].[hash].js',
}

这会导致每次构建时,所有文件都具有相同的哈希值,即使只有其中一个文件被修改。

[chunkhash]根据每个入口文件的内容生成哈希值,不同的入口点可能会有不同的 chunkhash,和这个入口文件有关的文件,它们的chunkhash值是一样的。这对于多页应用特别有用。

1
2
3
output: {
filename: '[name].[chunkhash].js',
}

当某个入口点下的文件发生更改时,仅该入口点相关的文件哈希值会更新,其他入口点的文件哈希值保持不变。

**[contenthash]**:这是 Vue CLI 推荐的方式,它根据文件内容生成哈希值。对于 CSS 文件,Vue CLI 使用 extract-text-webpack-pluginmini-css-extract-plugin 插件提取样式到单独的文件,并为这些文件生成基于内容的哈希值。

1
2
3
output: {
filename: '[name].[contenthash].js',
}

这种方法更加精细,只会在文件的实际内容发生变化时才更新其哈希值,从而最大限度地利用浏览器缓存。

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': '' }
}
]//配置代理,解决跨域问题,因为搭建的本地服务器部署的网页也可能发请求获取其他源下的资源。
}
}

static属性

提供开发环境中的一个静态资源(比如图片,字体,视频)目录,这意味着你可以通过浏览器访问这些目录下的文件。

在vue和react中,这个静态资源目录都是public目录,public目录下的文件在构建过程中,会被直接复制到输出目录。

proxy

开启代理,关于proxy的介绍,参考《前端面试-vue》一文中跨域的解决办法。

开启服务器

1
npx webpack-dev-server

运行这个命令不仅会启动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]'#打包后的图片名称,打包路径由output决定
}
}]
}

[contenthash]代替文件名,这意味着会根据文件的内容来确定文件名,如果文件内容改变,这个值也会改变

[ext]来代替后缀,表示源文件是什么类型的后缀,打包后的文件也是什么类型的后缀。

图片可以通过import或者src的方式被引入

1
2
3
4
5
6
7
//通常会得到该图片的一个路径或 Base64 编码字符串(取决于你的构建配置)
import logoImage from './logo.png'
function App() {
return (
<img src={logoImage} alt="Logo" />
);
}

asset/inline

返回源文件的data:url,也就是base64格式字符串,打包后的结果就不会包括源文件了

1
2
3
4
5
6
7
//通常会得到该图片的一个路径或 Base64 编码字符串(取决于你的构建配置)
import logoImage from './logo.png'
function App() {
return (
<img src={logoImage} alt="Logo" />
);
}

设你有一张 PNG 格式的图片,通过 data URL 和 Base64 编码的方式内联到 HTML 文件中,它可能看起来像这样:

1
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." alt="Embedded Image">

data:image/png表示数据的MIME类型,base64表示数据是否经过了 Base64 编码,如果数据未进行 Base64 编码,则应省略此部分。

这里的 "iVBORw0KGgoAAAANSUhEUgAAAAUA..." 就是该图片经过 Base64 编码后的字符串

asset/source

导出资源的源码,这种方式非常适合需要将小文件的内容,直接包含到代码中的场景,比如模板、小型脚本或样式表等。

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
rules: [
{
test: /\.(txt|xml|json)$/, // 匹配你想要作为 source 导入的文件类型
type: 'asset/source',
},
],
},
};

假设有一个名为 example.txt 的文本文件,其内容为 "Hello, Webpack!",你可以通过以下方式将其导入到 JavaScript 文件中:

1
2
import content from './example.txt';
console.log(content); // 输出: Hello, Webpack!

在这种情况下,content 变量将会包含 example.txt 文件的所有文本内容。

注意事项

  • 仅限文本文件:asset/source 最适合用于文本文件。如果你尝试用它来处理二进制文件(如图片、字体),可能会导致不可预料的结果,因为这些文件的二进制数据会被当作字符串处理。
  • 文件大小:虽然可以将任何大小的文件作为源代码导入,但通常建议只对较小的文件这样做,以避免增加最终打包文件的体积。

asset

webpack将按照默认条件,自动地在resourceinline 之间进行选择,小于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代码需要借助其他插件。也就是说,单纯借助上述的3个工具,只能做到提取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对象

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

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文件,包括mini-css-extract-plugin生成的css文件,会自动注入打包后的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代码,提取到一个单独的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

前端面试—webpack | 三叶的博客中已介绍,不赘述。

搭建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'
}
}