webpack基础概念
webpack 是一个模块打包器,将根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源。
当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
entry
打包的入口文件
单入口:
entry:'./src/index.js'
多入口:
entry:{
home:'./src/home.js',
other:'./src/other/js'
}
mode
- production:生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码
- development:开发模式,打包更加快速,省了代码优化步骤
output
输出文件
output:{
filename:'[name].[hash:8].js', // 输出文件名
path:path.join(__dirname,'dist') // 输出文件地址
}
loaders
webpack开箱只支持js和json两种文件类型,通过loaders去支持其他文件类型并且把他们转化成有效的模块,并且可以添加到依赖图中
本身是一个函数,接收源文件作为参数,返回转化的结果
常见loader
- babel-loader 转换ES6 ES7等JS新特性语法
npm install babel-loader @babel/core @babel/preset-env -D
babel-loader 使用 Babel 加载 ES2015+ 代码并将其转换为 ES5
@babel/core Babel 编译的核心包
@babel/preset-env Babel 编译的预设,可以理解为 Babel 插件的超集
- css-loader 解析css
- style-loader 让样式正确显示在页面上
- less-loader 将less转成css
- postcss-loader 通常用来进行浏览器css兼容性处理
- ts-loader 将ts转成js
- file-loader 进行图片字体的打包
- raw-loader 将文件以字符串的形式导入
style-loader的核心逻辑相当于:
const content = `${样式内容}`
const style = document.createElement('style');
style.innerHTML = content;
document.head.appendChild(style);
loader加载顺序是从右向左
loader的本质:loader就是一个导出为函数的js模块,函数接收源文件内容或者是被其他loader处理过的内容,然后进行自己的一些处理操作,在将结果暴露出去
plugin
html-webpack-plugin
该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle
当使用 webpack打包时,创建一个 html 文件,并把 webpack 打包后的静态文件自动插入到这个 html 文件当中
const HtmlWebpackPlugin = require('html-webpack-plugin')
...
其他配置
...
plugins:[
// 将JS css文件自动引入到html中
new HtmlWebpackPlugin({
template:'./src/index.html'
})
]
如果是多入口,则采用下面的配置
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'),
filename:'index.html',
chunks:['main'] // 与入口文件对应的模块名
}),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/header.html'),
filename:'header.html',
chunks:['header'] // 与入口文件对应的模块名
}),
]
clean-webpack-plugin
帮助我们每次打包前清理dist
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins:[
new CleanWebpackPlugin()
],
mini-css-extract-plugin
css默认是打包在html中的style标签内,如果向拆分出来就需要mini-css-extract-plugin
注意:这里还需要修改loader
{
test:/\.less$/,
use:[
// 'style-loader',
MiniCssExtractPlugin.loader,
'cache-loader', // 获取前面 loader 转换的结果 cache-loader : 缓存一些性能开销比较大的 loader 的处理结果
'css-loader',
'postcss-loader',
'less-loader'
]
},
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins:[
new MiniCssExtractPlugin({
filename:'[name].[hash:8].css',
chunkFilename: "[id].css"
})
],
手写一个插件
插件基本结构就是一个有apply方法的构造函数,里面执行一些插件的钩子,apply方法是用来获取compiler的
// jszip是一个用于创建、读取和编辑.zip文件的JavaScript库
const JSZip = require('jszip');
const path = require('path');
const RawSource = require('webpack-sources').RawSource;
const zip = new JSZip();
module.exports = class ZipPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
// emit是compiler的一个钩子 emit: 输出 asset到output 目录之前执行。这个钩子不会被复制到子编译器
// 异步钩子用tapAsync执行,同步用tap执行
compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
// 添加一个文件夹
const folder = zip.folder(this.options.filename);
// 遍历打包的资源
for (let filename in compilation.assets) {
// 获取每个文件的source
const source = compilation.assets[filename].source();
// 将资源文件添加到文件夹里
folder.file(filename, source);
}
// 生成zip文件
zip.generateAsync({
type: 'nodebuffer'
}).then((content) => {
const outputPath = path.join(
compilation.options.output.path,
this.options.filename + '.zip'
);
// 确定文件输出路径
const outputRelativePath = path.relative(
compilation.options.output.path,
outputPath
);
// 通过 RawSource 向 compilation.assets写入zip包
compilation.assets[outputRelativePath] = new RawSource(content);
// 继续执行后面的操作
callback();
});
});
}
}
devServer
npm intall webpack-dev-server -D
devServer:{
contentBase:path.resolve(__dirname,'public'),
compress:true,// 是否启动gzip压缩
port:8080, // 端口号
open:true // 是否自动打开浏览器
}
为什么要配置contentBase?
因为 webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 contentBase 之后,就直接到对应的静态目录下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。
多页面打包思路
- 每个页面对应一个entry,同时对应一个html-webpack-plugin
缺点:每次新增或删除页面都需要改webpack配置
- 动态获取entry和设置html-webpack-plugin
利用glob.sync(同步读取文件)
const glob = require('glob');
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
Object.keys(entryFiles)
.map((index) => {
const entryFile = entryFiles[index];
// '/Users/cpselvis/my-project/src/index/index.js'
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
});
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins } = setMPA();
module.exports = {
entry:entry,
plugins: [
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(),
new FriendlyErrorsWebpackPlugin()
].concat(htmlWebpackPlugins),
}
sourceMap
sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中debug问题会带来非常糟糕的体验,sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率
开发环境开启,线上环境关闭
sourceMap关键字
| 关键字 | 描述 |
|---|---|
| inline | 代码内通过 dataUrl 形式引入 SourceMap 不单独生成.map文件 |
| hidden | 生成 SourceMap 文件,但不使用 |
| eval | eval(...) 包裹模块代码 |
| nosources | 不生成 SourceMap |
| cheap | 只需要定位到行信息,不需要列信息 |
| module | 展示源代码中的错误位置 包含loader的sourceMap |
- inline:内联,一个chunk生成一个总的source-map
- eval:内联,每一个文件生成一个source-map
- cheap:外部,报错位置只能精确到行。
- cheap-module:显示第三方库的source-map
| devtool | build | rebuild | 显示代码 | SourceMap 文件 | 描述 |
|---|---|---|---|---|---|
| (none) | 很快 | 很快 | 无 | 无 | 无法定位错误 |
| eval | 快 | 很快(cache) | 编译后 | 无 | 定位到文件 |
| source-map | 很慢 | 很慢 | 源代码 | 有 | 定位到行列 |
| eval-source-map | 很慢 | 一般(cache) | 编译后 | 有(dataUrl) | 定位到行列 |
| eval-cheap-source-map | 一般 | 快(cache) | 编译后 | 有(dataUrl) | 定位到行 |
| eval-cheap-module-source-map | 慢 | 快(cache) | 源代码 | 有(dataUrl) | 定位到行 |
| inline-source-map | 很慢 | 很慢 | 源代码 | 有(dataUrl) | 定位到行列 |
| hidden-source-map | 很慢 | 很慢 | 源代码 | 有 | 无法定位错误 |
| nosource-source-map | 很慢 | 很慢 | 源代码 | 无 | 定位到文件 |
css压缩
npm install -D optimize-css-assets-webpack-plugin
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const config = {
// ...
optimization: {
minimize: true, // 开启最小化
minimizer: [
// ...
new OptimizeCssAssetsPlugin({})
]
},
// ...
}
SplitChunksPlugin 分包配置
optimization:{
splitChunks:{
// chunks:'all', // 有效值为 `all`(所有引入的库都进行分离)`async`(异步引入的库分离) 和 `initial`(同步引入库分离)
// minSize:20000, // 生成chunk的最小体积
// minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
// minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。 最小引用数
// maxAsyncRequests: 30, // 最大的按需(异步)加载次数
// maxInitialRequests: 30, // 打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件)
cacheGroups: { // 配置提取模块的方案
default: false,
styles: {
name: 'styles',
test: /\.(s?css|less|sass)$/,
chunks: 'all',
enforce: true,
priority: 10,
},
common: {
name: 'chunk-common',
chunks: 'all',
minChunks: 2, // 做少引用次数
maxInitialRequests: 5,
minSize: 0,
priority: 1,
enforce: true,
reuseExistingChunk: true,
},
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
priority: 2,
enforce: true,
reuseExistingChunk: true,
},
// ... 根据不同项目再细化拆分内容
}
}
}
tree shaking 摇树优化
Tree-shaking 作用是剔除没有使用的代码,以降低包的体积
webpack 默认支持,需要在 .bablerc 里面设置 model:false即可,生产环境下默认开启
要求:必须是ES6的语法方式引入 commonjs不支持
tree shaking原理
Tree-shaking的原理主要是依赖于ES6的模块特性
ES6模块的特点:
- 只能作为模块顶层的语句出现 (import)
- import的模块名只能是字符串常量
- import binding是immutable的 (import之后不能修改)
ES6模块依赖关系是确定的,和运行时的状态无关。可以进行可靠的静态分析,这就是tree-shaking的基础
本质是编译是对代码进行静态分析,在uglify阶段删除无效代码
Scope Hoisting(作用域提升)
不开启Scope Hoisting的编译结果会存在大量的闭包代码,导致体积增加
运行代码时创建的函数作用域变多,内存开销变大
模块转换分析:
- 被wabpack转换后的模块会带上一层包裹
- import会被转换成__webpack_require
Scope Hoisting 即作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销
- webpack 默认支持,在生产环境下默认开启
- 只支持 es6 代码
speed-measure-webpack-plugin(速度分析)
npm i -D speed-measure-webpack-plugin
...
// 费时分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
...
const config = {...}
module.exports = (env, argv) => {
// 这里可以通过不同的模式修改 config 配置
return smp.wrap(config);
}
webpack-bundle-analyzer(体积分析)
npm i -D webpack-bundle-analyzer
// 引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const config = {
// ...
plugins:[
// ...
// 配置插件
new BundleAnalyzerPlugin({
// analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
// generateStatsFile: true, // 是否生成stats.json文件
})
],
};
修改启动项
"scripts": {
"analyzer": "cross-env NODE_ENV=prod webpack --progress --mode production"
},
多进程/多实例构建
- thread-loader(官方推荐)
- paraller-webpack
- HappyPack
利用缓存提升二次构建速度
- babel-loader 开启缓存
use:[{
loader:'babel-loader',
options:{
cacheDirectory:true
}
}]
- terser-webpack-plugin 开启缓存
- cache-loader 或者 hard-source-webpack-plugin
hard-source-webpack-plugin 利用缓存时,配置文件不能发生更改,如果配置文件的配置项每次都更改,则该功能不生效
webpack热更新原理
一般的刷新分为两种:
- 页面刷新,不保留页面状态,就是简单粗暴的window.location.reload()
- 基于WDS的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留页面的状态
当我们开启热更新时,获取更新文件,浏览器会请求两个文件,一个是以hot-update.json 结尾的文件,一个是以hot-update.js结尾的文件
具体内容如下:
- JSON文件
{
"h": "0c256052432b51ed32c8",
"c": {
"201": true
}
}
h代表本次新生成的Hash值,本次输出的hash值会被做为下次热更新的标识,c表示当前需要热更新的文件对应的是哪个模块
- JS文件
是本次修改的代码,重新编译打包后的,webpackHotUpdate 方法就是用来更新模块的,201 对应的是哪个模块(我们称它为模块标识),其他的就是要更新的模块的内容了
webpackHotUpdate(201, {
"./src/views/moveTransfer/list/index.vue?vue&type=script&lang=js&": function (
module,
exports,
__webpack_require__
) {
"use strict";
var _Object$defineProperty = __webpack_require__(
/*! @babel/runtime-corejs3/core-js-stable/object/define-property */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js"
);
_Object$defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _default = {
data: function data() {
return {};
},
computed: {},
methods: {
clickMe: function clickMe() {
},
},
};
exports.default = _default;
},
});
热更新使用了websocket进行浏览器和webpack服务器的通信
热更新过程
几个重要的概念
-
Webpack-complier:webpack的编译器,将JavaScript编译成bundle(就是最终的输出文件) -
HMR Server:将热更新的文件输出给HMR Runtime -
Bunble Server:提供文件在浏览器的访问,也就是我们平时能够正常通过localhost访问我们本地网站的原因 -
HMR Runtime:开启了热更新的话,在打包阶段会被注入到浏览器中的bundle.js,这样bundle.js就可以跟服务器建立连接,通常是使用websocket,当收到服务器的更新指令的时候,就去更新文件的变化 -
bundle.js:构建输出的文件
启动阶段
文件经过 Webpack-complier 编译好后传输给 Bundle Server,Bundle Server 可以让浏览器访问到我们打包出来的文件
热更新阶段
文件经过 Webpack-complier 编译好后传输给 HMR Server,HMR Server 知道哪个资源(模块)发生了改变,并通知 HMR Runtime 有哪些变化(也就是上面我们看到的两个请求),HMR Runtime 就会更新我们的代码,这样我们浏览器就会更新并且不需要刷新
本地修改了文件,浏览器是怎么知道要更新的呢
本地实际上启动了一个 HMR Server 服务,而且在启动 Bundle Server 的时候已经往我们的 bundle.js 中注入了 HMR Runtime(主要用来启动 Websocket,接受 HMR Server 发来的变更)
所以我们聚焦以下几点:
Webpack如何启动了 HMR Server- HMR Server 如何跟 HMR Runtime 进行通信的
HMR Runtime接受到变更之后,如何生效的
启动HMR Server
该工作主要是在webpack-dev-server中完成,查看源码,在webpack-dev-server/lib/Server.js中 setupApp方法,该方法启动了一个express服务器,实际上对应的是 Bundle Server
setupApp() {
// Init express server
// eslint-disable-next-line new-cap
// 初始化 express 服务
// 使用 express 框架启动本地 server,让浏览器可以请求本地的静态资源。
this.app = new express();
}
listeningApp监听到启动成功,就会通过createSocketServer创建一个websocket服务
listen(port, hostname, fn) {
this.hostname = hostname;
return this.listeningApp.listen(port, hostname, (err) => {
// 启动 express 服务之后,启动 websocket 服务
this.createSocketServer();
if (this.options.bonjour) {
runBonjour(this.options);
}
this.showStatus();
if (fn) {
fn.call(this.listeningApp, err);
}
if (typeof this.options.onListening === 'function') {
this.options.onListening(this);
}
});
}
createSocketServer() {
this.socketServer = new this.SocketServerImplementation(this);
this.socketServer.onConnection((connection, headers) => {
});
}
HMR Server和HMR Runtime通信
首先我们要确认的是,通信的时机,什么时候去通知客户端我的文件更新。通过 webpack 创建的 compiler 实例(监听本地文件的变化、文件改变自动编译、编译输出),可以往 compiler.hooks.done 钩子(代表 webpack 编译完之后触发)注册事件, 当监听到一次 webpack 编译结束,就会调用 sendStats 方法
// lib/Server.js
// 绑定监听事件
setupHooks() {
// ...
const addHooks = (compiler) => {
// 监听 webpack 的 done 钩子,tapable 提供的监听方法
// done 标识编译结束
const { compile, invalid, done } = compiler.hooks;
compile.tap('webpack-dev-server', invalidPlugin);
invalid.tap('webpack-dev-server', invalidPlugin);
done.tap('webpack-dev-server', (stats) => {
// 当监听到一次webpack编译结束,就会调用 sendStats 方法
this.sendStats(this.sockets, this.getStats(stats));
this.stats = stats;
});
};
}
当监听到一次 webpack 编译结束,就会调用 sendStats 方法,里面会向客户端发送 hash 和 ok 事件
_sendStats(sockets, stats, force) {
const shouldEmit =
!force &&
stats &&
(!stats.errors || stats.errors.length === 0) &&
stats.assets &&
stats.assets.every((asset) => !asset.emitted);
if (shouldEmit) {
return this.sockWrite(sockets, 'still-ok');
}
this.sockWrite(sockets, 'hash', stats.hash);
if (stats.errors.length > 0) {
this.sockWrite(sockets, 'errors', stats.errors);
} else if (stats.warnings.length > 0) {
this.sockWrite(sockets, 'warnings', stats.warnings);
} else {
this.sockWrite(sockets, 'ok');
}
}
在client/index.js中,会去更新hash,并且在ok的时候去进行更新检查reloadApp
// client-src/default/index.js
const onSocketMessage = {
// 更新 current Hash
hash(hash) {
status.currentHash = hash;
},
'progress-update': function progressUpdate(data) {
if (options.useProgress) {
log.info(`${data.percent}% - ${data.msg}.`);
}
sendMessage('Progress', data);
},
ok() {
sendMessage('Ok');
if (options.useWarningOverlay || options.useErrorOverlay) {
overlay.clear();
}
if (options.initial) {
return (options.initial = false);
}
// 进行更新检查等操作
reloadApp(options, status);
}
};
接下来我们看看 client-src/default/utils/reloadApp.js 中的 reloadApp。这里又利用 node.js 的 EventEmitter,发出webpackHotUpdate 消息。这里又将更新的事情给回了 webpack(为了更好的维护代码,以及职责划分的更明确
function reloadApp(
{ hotReload, hot, liveReload },
{ isUnloading, currentHash }
) {
// ...
if (hot) {
log.info('App hot update...');
// hotEmitter 其实就是 EventEmitter 的实例
const hotEmitter = require('webpack/hot/emitter');
// 又利用 node.js 的 EventEmitter,发出 webpackHotUpdate 消息。
// websocket 仅仅用于客户端(浏览器)和服务端进行通信。而真正做事情的活还是交回给了 webpack。
hotEmitter.emit('webpackHotUpdate', currentHash);
if (typeof self !== 'undefined' && self.window) {
// broadcast update to window
self.postMessage(`webpackHotUpdate${currentHash}`, '*');
}
}
// ...
}
module.exports = reloadApp;
在 webpack 的 hot/dev-server.js 中,监听 webpackHotUpdate 事件,并执行 check 方法。并在 check 方法中调用 module.hot.check 方法进行热更新
// hot/dev-server.js
// 监听webpackHotUpdate事件
hotEmitter.on("webpackHotUpdate", function (currentHash) {
lastHash = currentHash;
if (!upToDate() && module.hot.status() === "idle") {
log("info", "[HMR] Checking for updates on the server...");
check();
}
var check = function check() {
// moudle.hot.check 开始热更新
// 之后的源码都是HotModuleReplacementPlugin塞入到bundle.js中的哦,我就不写文件路径了
module.hot
.check(true)
.then(function (updatedModules) {
// ...
})
.catch(function (err) {
// ...
});
};
至于 module.hot.check ,实际上通过 HotModuleReplacementPlugin 已经注入到我们 chunk 中了(也就是我们上面所说的 HMR Runtime),所以后面就是它是如何更新 bundle.js 的呢
webpack 4.x 对比3.x版本进行了哪些优化,为什么提高了构建速度?
-
webpack4增加了mode配置项,可以对不同的环境开启不同的配置
-
不再支持CommonsChunkPlugin,改用optimization.splitChunks
-
不在支持UglifyJsPlugin,通过optimization.minimize = true进行设置
-
移除了loaders,必须使用rules去配置loader
速度的提升包括以下几方面:
- V8 带来的 优化( for of 代替了 forEach,Map 和 Set 代替了 Object,includes 代替了 indexOf )。
- 默认使用更快的 md4 hash 算法。
- webpack AST 可以直接从loader传递给 AST,减少解析时间。
- 使用字符串方法代替正则 表达式
webpack5 有哪些新特性
- 内置静态资源的构建能力
在webpack5之前,我们一般都会使用file-loader、url-loader、raw-loader来处理静态资源,而webpack提供了内置的静态资源构建能力,我们不需要安装额外的 loader,仅需要简单的配置就能实现静态资源的打包和分目录存放。
// webpack.config.js
module.exports = {
...,
module: {
rules: [
{
test: /.(png|jpg|svg|gif)$/,
type: 'asset/resource',
generator: {
// [ext]前面自带"."
filename: 'assets/[hash:8].[name][ext]',
},
},
],
},
}
- 内置文件系统缓存
Webpack5 之前,我们会使用 cache-loader 缓存一些性能开销较大的 loader ,或者是使用 hard-source-webpack-plugin 为模块提供一些中间缓存。在 Webpack5 之后,默认就为我们集成了一种自带的缓存能力(对 module 和 chunks 进行缓存)
// webpack.config.js
module.exports = {
...,
cache: {
type: 'filesystem',
// 可选配置
buildDependencies: {
config: [__filename], // 当构建依赖的config文件(通过 require 依赖)内容发生变化时,缓存失效
},
name: '', // 配置以name为隔离,创建不同的缓存文件,如生成PC或mobile不同的配置缓存
...,
},
}
- 内置worker的构建能力
以前使用webpack,如果在项目中使用了webworker,需要手动安装work-loader并配置,还需要针对worker配置特别的work.js的文件名。
在 Webpack5 中,我们不需要添加 loader 的处理方式,并且不需要针对 worker 配置特定的 .worker.js 之类的文件名,借助于 new URL, 便能实现 worker 的创建
// master.js
const worker = new Worker(new URL('./calc.js', import.meta.url), {
name: "calc"
/* webpackEntryOptions: { filename: "workers/[name].js" } */
});
worker.onmessage = e => {
console.log(e.data.value);
};
- 不在为nodeJS模块提供自动引用Polyfills
在早期,webpack 的目的是为了让大多数的 Node.js 模块运行在浏览器中,但如今模块的格局已经发生了变化,现在许多模块主要是为前端而编写。Webpack <= 4 的版本中提供了许多 Node.js 核心模块的 polyfills,一旦某个模块引用了任何一个核心模块(如 cypto 模块),webpack 就会自动引用这些 polyfills。
尽管这会使得使用为 Node.js 编写模块变得容易,但它在构建时给 bundle 附加了庞大的 polyfills。在大部分情况下,这些 polyfills 并非必须
- 支持命名代码块ID
比如以前在vue路由懒加载中会写
import(/* webpackChunkName: "name" */ "module")
现在可以在生产环境中使用 chunkIds: "named"命名
- 对Tree-shaking进行了优化
Webpack5 能够跟踪对导出的嵌套属性的访问,所以支持嵌套模块的Tree-shaking分析,也能够处理commonJS的Tree-shaking
-
新增了长期缓存算法,生产环境下默认启用
-
新增了模块联邦功能,即允许多个webpack构建一起工作
webpack和gulp的区别
gulp的核心是task,通过配置一系列task,并且定义task要处理的事务(比如js压缩,css压缩,less编译),之后让gulp来依次执行这些task,让整个流程自动化。本质是一个工具链,所以gulp是一种前端自动化任务管理工具。
webpack:是静态文件打包工具,可以把项目的各种js文件、css文件等打包合并成一个或多个文件,主要用于模块化方案,预编译模块的方案
如果工程模块依赖非常简单,甚至没有用到模块化的概念。只需要进行简单的合并、压缩,就使用grunt/gulp即可。但是如果整个项目使用了模块化管理,而且相互以来非常强,我们就可以使用更加强大的webpack了
Vite和webpack的区别
webpack core 是一个纯打包工具(对标 Rollup),而 Vite 其实是一个更上层的工具链方案,对标的是 (webpack + 针对 web 的常用配置 + webpack-dev-server)
webpack优点:灵活,不局限于web打包,几乎所有的环节都设置成了可配置的
webpack缺点:配置项极度复杂,插件机制和内部逻辑晦涩难懂,针对常见的 web 也需要大量的配置
vite:实际上是通过预设了场景去降低复杂度,比如将常见的 web 构建需求都可以直接做成默认内置
vite的特点:
-
index.html中入口文件的导入指定了type=module,这样main.js中就可以使用ES6 Module方式组织代码,浏览器会自动加载这些导入
-
支持直接导入css文件
-
安装对应的样式预处理器就可以直接使用,而不必再去配置loader之类的操作
-
默认支持TS
-
vite按需编译当前页面需要的组件,而不需要打包整个APP的组件