webpack详解

引言

webpack中文官网:webpack.docschina.org/concepts/

webpack是一个javascript常用的静态模块打包工具,通过配置webpack,可以实现包括压缩文件,解析不同文件格式等强大的功能,作者发现很多同学对webpack的认知都停留在入口出口以及简单的loader和plugin配置上,对webpack的核心原理都一知半解。本文期望通过更深层的解读,让读者能更彻底地理解这个打包工具的来龙去脉。

为什么要用webpack

在webpack等打包工具出世之前,我们普通的H5项目是怎么处理错综复杂的脚本呢? 第一种方式:引用不同的脚本去使用不同的功能,但脚本太多的时候会导致网络瓶颈 第二种方式:使用一个大型js文件去引入所有代码,但这样会严重影响可读性,可维护性,作用域。

举个栗子: 由于浏览器不能直接解析less文件,我们可通过引入转换的插件(file watcher)把less实时转换为css并引入,但项目里面会多出一个map跟css文件,造成项目文件的臃肿。

官方文档的说法:

node.js诞生可以让Javasrcipt在浏览器环境之外使用,而webpack运行在node.js中。CommonJS的require机制允许在文件中引用某个模块,如此一来就可以解决作用域的问题。

const HtmlWebpackPlugin = require('html-webpack-plugin')

webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载 chunk 和预取,以便为你的项目和用户提供最佳体验

核心概念

webpack有7个核心概念:

  1. 入口(entry)
  2. 输出(output)
  3. loader
  4. 插件(plugin)
  5. 模式(mode)
  6. 浏览器兼容性(brower compatibility)
  7. 环境(environment)

新建一个build文件夹,里面新建一个webpack.config.js

入口entry

这是打包的入口文件,所有的脚本将从这个入口文件开始

单入口

const path = require('path') module.exports = { entry: path.resolve(__dirname, '../src/main.js') }

多入口

使用对象语法配置,更好扩展,例如一个项目有前台网页跟后台管理两个项目可用多入口管理。

entry: { app: path.resolve(__dirname, '../src/main.js'), admin: path.resolve(__dirname, '../src/admin.js'), },

输出output

打包后输出的文件,[name]跟[hash:8]表示文件名跟入口的保持一致但后面加上了hash的后缀让每次生成的文件名是唯一的。

单入口

module.exports = { output: { filename: '[name].[hash:8].js', path: path.resolve(__dirname, '../dist'), }, }

多入口

module.exports = { entry: { app: './src/app.js', admin: './src/admin.js', }, output: { filename: '\[name].js', path: \_\_dirname + '/dist', }, }; // 写入到硬盘:./dist/app.js, ./dist/admin.js

loader转化器

用于模块的源码的转换,也是同学们在配置webpack的时候频繁接触的一个配置项。

举个例子,加载typescript跟css文件需要用到ts-loader跟css-loader、style-loader, 如果没有对应的loader,打包会直接error掉。

[图片上传失败...(image-ea5f3b-1698754139181)]

我们可以这么配置:先 npm i css-loader style-loader

module: { rules: [ { test: /\.css\$/, use: ['style-loader','css-loader'] }, { test: /\.ts\$/, use: 'ts-loader' }

必须留意的是,loader的执行是从右到左,就是css-loader执行完,再交给style-loader执行,

plugin插件

这是webpack配置的核心,有一些loader无法实现的功能,就通过plugin去扩展,建立一个规范的插件系统,能让你每次搭建项目的时候省去很多成本。

举个例子,我们会使用HtmlWebpackPlugin这个插件去生成一个html,其中会引入入口文件main.js。 假设不用这个插件,会发生什么?

当然是不会生成这个html,因此HtmlWebpackPlugin插件也是webpack的必备配置之一

`const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html') // 同时引入main.js的hash名字
}),
],`

模式mode

mode一共有production,development,node三种,如果没有设置,会默认为production

不同的mode对于默认优化的选项有所不同,环境变量也不同,具体需要了解每个插件的具体使用

选项 描述
development 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development
production 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginTerserPlugin
none 没优化选项

module.exports = { mode: 'production' }

source-map 的解读

Sourcemap 本质上是一个信息文件,里面储存着代码转换前后的对应位置信息。它记录了转换压缩后的代码所对应的转换前的源代码位置,是源代码和生产代码的映射。 Sourcemap 解决了在打包过程中,代码经过压缩,去空格以及 babel 编译转化后,由于代码之间差异性过大,造成无法debug的问题

当mode为development时,devtool默认为‘eval’,当mode为production时,devtool默认为false。

sourceMap的分类

  • source-map:外部。可以查看错误代码准确信息和源代码的错误位置。
  • inline-source-map:内联。只生成一个内联 Source Map,可以查看错误代码准确信息和源代码的错误位置
  • hidden-source-map:外部用于生产环境。可以查看错误代码准确信息,但不能追踪源代码错误,只能提示到构建后代码的错误位置。
  • eval-source-map:内联。每一个文件都生成对应的 Source Map,都在 eval 中,可以查看错误代码准确信息 和 源代码的错误位置。
  • nosources-source-map:外部。可以查看错误代码错误原因,但不能查看错误代码准确信息,并且没有任何源代码信息。
  • cheap-source-map:外部。可以查看错误代码准确信息和源代码的错误位置,只能把错误精确到整行,忽略列。
  • cheap-module-source-map:外部用于生产环境。可以错误代码准确信息和源代码的错误位置,module 会加入 loader 的 Source Map。
  • eval-cheap-module-source-map: 内联,用于开发环境,构建跟热更新比较快

内联和外部的区别: 外部生成了文件(.map),内联没有。内联构建速度更快。

笔者用的两种配置分为是

// webpack.dev.js devtool: 'eval-cheap-module-source-map',

// webpack.prod.js devtool: 'cheap-module-source-map'

浏览器兼容性 brower compatibility

Webpack 支持所有符合 ES5 标准 的浏览器(不支持 IE8 及以下版本)。webpack 的 import()require.ensure() 需要 Promise。如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要提前加载 polyfill

环境 enviroment

本文使用的是webpack5 ,要求Node.js V在10.13.0+

Loader的汇总

笔者汇总了一部分常用的Loader以及其配置事项

  1. 浏览器兼容性:babel-loader
  2. css相关: css/style/less/postcss-loader
  3. vue: vue-loader

在配置loader前,先了解一下基本的配置

  • test: 匹配的文件,多用正则匹配
  • use: 使用loader,多用数组
  • exclude: 调整Loader解析的范围,包括某个路径下的文件,不如node_modules
  • include: 调整Loader解析的范围,包括某个路径下的文件

解决浏览器兼容性:babel

转义语法的babel-loader

譬如把const转为浏览器认识的var,虽然现在大部分主流浏览器都认识ES5之后的语法。

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

在rules配置:

{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } }, exclude: /node_modules/ }

转义ES API的babel/polyfill

如果只有babel-loader,浏览器并不能识别出新的API(promise,proxy,includes),如图:

[图片上传失败...(image-bd7be9-1698754139180)]

因此还需要配置一个babel/polyfill,在入口里面:

`// npm i @babel/polyfill

entry: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')],`

解析vue的vue-loader

vue-loader: 解析vue vue-template-compiler: 编译vue模板

npm i vue-loader vue-template-compiler vue-style-loader npm i vue

在rules跟plugins配置:

`const { VueLoaderPlugin } = require('vue-loader') // vue3的引入跟vue2路径不同

rules:{
{
test: /.vue$/,
use: ['vue-loader']
}
},
plugins:[
...
new VueLoaderPlugin()
...
]`

配置完成后,vue文件就可以正常解析了

[图片上传失败...(image-a0d42f-1698754139180)]

解析CSS文件

需要引入的Loader不止一个

  • 引入的基本Loader: style-loader,css-loader,如有less还需要less-loader
  • postcss-loader 添加不同浏览器的css前缀: 解决部分css语法在不同浏览器的写法不同的弊端

modules.exports = { modules: { rules: [{ test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] // 解析css文件必须的style-loader,css-loader }, { test: /\.less$/, use: [ 'style-loader', 'css-loader', { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ [ 'postcss-preset-env' // 解决css不同浏览器兼容性 ], ], }, } }, 'less-loader' ] }, } }

拆分css

mini-css-extract-plugin: 把css拆分出来用外链的形式引入css文件,然后会在dist生成css文件,为每一个包含了 CSS 的 JS 文件创建一个 CSS 文件, ps: 使用该插件不能重复使用style-loader

··· plugins: [ ... new MiniCssExtractPlugin({ filename: devMode ? "[name].css" : "[name].[hash].css", chunkFilename: devMode ? "[id].css" : "[id].[hash].css", }), ... ], module:{ rules: [{ test: /\.css$/, use: [ MiniCssExtractPlugin.loader, // 'style-loader', 'css-loader', 'postcss-loader' ] // 解析css文件必须的style-loader,css-loader }] }

打包图片,字体,媒体等文件

file-loader: 就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中

url-loader 一般与file-loader搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中

{ test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件 use: [{ loader: 'url-loader', options: { limit: 10240, fallback: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' } } } }], include: [resolve('src/assets/image')], exclude: /node_modules/ }, { test: /\.(jpg|png|gif)$/i, use: [{ loader: 'url-loader', options: { limit: 10240, // KB fallback: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' } } } }], include: [resolve('src/assets/image')], exclude: /node_modules/ }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 use: [{ loader: 'url-loader', options: { limit: 10240, fallback: { loader: 'file-loader', options: { name: 'fonts/[name].[hash:8].[ext]' } } } }], include: [resolve('src/assets/icon')], exclude: /node_modules/ },

使用多线程提升构建速度

js是单线程的工程,在构建工程的过程中,要消耗大量的时间在Loader的转换过程中,为了提升构建速度,这里使用了thread-loader将任务拆分为多个线程去处理。 其原理是把任务分配到各个worker线程中,之前多数人会使用happyPack,但webpack官方使用了thread-loader取代happypack。

... { test: /\.js$/, use: [{ loader: 'babel-loader', options: { presets: ['@babel/preset-env', ], cacheDirectory: true, } }, { loader: 'thread-loader', // 多线程解析模块 options: { workers: 3, // 开启几个 worker 进程来处理打包,默认是 os.cpus().length - 1 } } ], exclude: /node_modules/ } ...

必须使用的插件Plugins

配置plugins必须注意的是,由于我们的模式(mode)区分为development跟production,因此plugins也需要按照实际需要,在config(公用),dev,prod三个配置文件分开加入。

首先先配置公用部分的plugins

公用plugins

清除打包残留文件

每次执行npm run build 会发现dist文件夹里会残留上次打包的文件,在打包输出前清空文件夹

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

用外链的形式引入css

当一个html文件里面的css太多,全部把css添加到html中会显得很臃肿,那我们可以用mini-css-extract-plugin 把css拆分成外链引入,为每一个包含了 CSS 的 JS 文件创建一个 CSS 文件。

需要留意的是不能跟style-loader同时使用,下面用了hash

`const MiniCssExtractPlugin = require("mini-css-extract-plugin");
....
plugins: [
new MiniCssExtractPlugin({
filename: devMode ? "[name].css" : "[name].[hash].css",
chunkFilename: devMode ? "[id].css" : "[id].[hash].css",
}, {
filename: devMode ? "[name].css" : "[name].[hash].less",
chunkFilename: devMode ? "[id].css" : "[id].[hash].less",
}),
]
....

module:{
rules:[{
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
// 'style-loader',
'css-loader',
'postcss-loader'
]
},
}]
}
....`

生产打包后的html

wepback必备的插件之一,上述举例也有提到。 主要是生产打包后的html, 同时由于main.js文件会随机生成新的hash名字,html在引入main.js文件时频繁改名字会很浪费时间,此插件会自动同步改文件名

`const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html') // 同时引入main.js的hash名字
}),
],`

开发环境Dev

热更新: webpack-dev-server

当我们修改文件的内容时,要重新build一次才能看到变化,这样对开发的效率不友好。

需要留意的是,webpack-dev-server只是在开发环境搭建一个服务帮助开发人员提高开发效率,实现了实时更新的功能,在生产环境并不会用到这一个插件.

同时注意在plugins中加入webpack自带的HotModuleReplacementPlugin。

webpack-dev-server这个插件功能十分强大,官方文档有详细的记录 (webpack.docschina.org/configurati…)

npm i webpack-dev-server --save-dev

module.exports = { devServer: { // 基本目录 static: { directory: path.join(__dirname, 'dist'), }, // 自动压缩代码 compress: true, port: 9000, // 自动打开浏览器 open: true, // 热加载,默认是true hot: true, }, plugins: [ new Webpack.HotModuleReplacementPlugin() ] }

生产环境Prod

由于生产环境对性能的要求跟开发不同,需要引入的插件比较丰富,也更需要对项目构建有更高的熟悉程度

压缩Js文件

webpack mode设置production的时候会自动压缩js代码。原则上不需要引入terser-webpack-plugin进行重复工作。但是optimize-css-assets-webpack-plugin压缩css的同时会破坏原有的js压缩,所以这里我们引入terser-webpack-plugin进行压缩

option很多,使用了dropconsole去除打印的内容

`const TerserPlugin = require("terser-webpack-plugin");

...

optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress:{
drop_console: true
}
}
}),
],
},
...`

压缩CSS

前面有使用mini-css-extract-plugin的插件去拆分css,但这个插件并不能压缩CSS体积, 使用css-minimizer-webpack-plugin 可以压缩css的体积,但不同的是它是被加入到optimization的minimizer中,跟上述的js压缩插件共同作用

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); ... optimization: { minimizer: [ new CssMinimizerPlugin(), new TerserPlugin({ terserOptions: { compress:{ drop_console: true } } }), ], }, ...

抽离第三方模块

使用DllReferencePlugin把不需要经常变更的静态文件抽离出来,譬如element-ui组件,这样每次打包的时候就不会再去重新打包选中的静态文件了。

如此一来,当我们修改代码后,webpack只需要打包项目的代码而不需要重复去编译没有发生改变的第三方库。这样当我们没有升级第三方库时,webpack就不会再对这些库进行打包,从而提升项目构建的速度。

首先我们在同级目录下新建文件webpack.dll.config.js,在entry的vendor里面配置了vue跟element-ui。

`// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
// 每次执行npm run build 会发现dist文件夹里会残留上次打包的文件,在打包输出前清空文件夹
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')

module.exports = {
mode: 'production',
// 你想要打包的模块的数组
entry: {
vendor: ['vue','element-plus']
},
output: {
path: path.resolve(__dirname, '../public/vendor'), // 打包后文件输出的位置,要在静态资源里面避免被打包转义
filename: '[name].dll.js',
library: 'vendor_library'
// 这里需要和webpack.DllPlugin中的name: '[name]_library',保持一致。
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
path: path.resolve(__dirname, '[name]-manifest.json'),
name: 'vendor_library',
context: __dirname
})
]
};`

同时在packake.json里面配置dll的命令

"scripts":{ "dll": "webpack --config build/webpack.dll.config.js", }

最后在webpack.prod.js 加入配置项

··· plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./vendor-manifest.json') }), ···

随后执行命令npm run dll 在public/vendor会出现一个vendor.dll.js文件,我们需要在html文件引入这个文件.

`<body>

<script src="./vendor/vendor.dll.js"></script>

<div id="app"></div>
</body>`

配置完毕,这样我们在不需要更新第三方包的时候可以不用执行npm run dll,然后直接执行npm run build/dev的时候就会发现构建速度有所提高。

分析打包后的文件

使用webpack-bundle-analyzer,启动项目后会打开一个展示各个包的大小。从图中可以看出来,es6.promise.js这个包

  • stat size: webpack 从入口文件打包递归到的所有模块体积
  • parsed size: 解析与代码压缩后输出到dist目录的体积
  • gzipped size: 开启Gzip之后的体积

[图片上传失败...(image-b703a5-1698754139179)]

总结

webpack身为前端必备的一项技能,各位在学会基础的配置之后,千万别忘了因地制宜,看看哪些插件更适合自己的项目哦

转载:原文链接:https://juejin.cn/post/7277490138518159379

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

推荐阅读更多精彩内容

  • 一、小白眼中的前端开发 vs 实际的前端开发 1.1. 小白眼中的前端开发 会写 HTML+ CSS +JavaS...
    扶得一人醉如苏沐晨阅读 475评论 0 0
  • webpack 介绍 webpack 是什么 为什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert阅读 6,464评论 2 71
  • 视频资源来自:b站coderwhy王红元老师——最全最新Vue、Vuejs教程,从入门到精通[https://ww...
    小910888阅读 921评论 0 0
  • 第 1 章:webpack 简介 1.1 webpack 是什么 webpack 是一种前端资源构建工具,一个静态...
    阿_军阅读 762评论 0 1
  • webpack是一个静态模块打包工具 grunt/gulp的核心是Task我们可以配置一系列的task,并且定义t...
    緋想天長女阅读 497评论 0 0