webpack的原理
一、webpack的结构配置
二、webpack的原理
1、核心概念
2、webpack构建流程(原理)
三、业务场景和对应解决方案
1、单页应用
2、一个项目管理多个单页面
四、自定义webpack扩展
1、编写webpack loader
2、编写webpack plugin
五、一些问题
1、webpack与grunt、gulp的不同?
2、 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?
3、有哪些常见的Loader?他们是解决什么问题的?
4、有哪些常见的Plugin?他们是解决什么问题的?
5、loader和plugin的不同
6、webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全
7、是否写过Loader和Plugin?描述一下编写loader或plugin的思路?
8、webpack的热更新是如何做到的?说明其原理?
9、如何利用webpack来优化前端性能?(提高性能和体验)
10、如何提高webpack的构建速度?
11、怎么配置单页应用?怎么配置多页应用?
12、npm打包时需要注意哪些?如何利用webpack来更好的构建?
13、如何在react项目中实现按需加载?
方式1.精确加载组件
方式2.暴露配置,配合babel-plugin-import插件实现按需加载
方式3.通过babel-plugin-import+react-app-rewired实现按需加载
14、webpack优化
1、构建时间优化
2、thread-loader 多进程打包,提高构建的速度,使用方式是将 thread-loader 放在比较费时间的loader之前,
3、cache-loader。缓存资源,提高二次构建的速度,使用方法是将 cache-loader 放在比较费时间的loader之前,比如 babel-loader
4、开启热更新,只刷新修改的模块,提高重构时间
5、exclude & include 合理使用include来指定编译文件夹、exclude排除指定文件夹 可以提高构建速度
6、构建区分环境,不同环境使用不同的配置
7、提升webpack版本,webpack版本越新,打包的效果肯定更好
8、打包体积优化,主要是打包后项目整体体积的优化,有利于项目上线后的页面加载速度提升
9、css代码压缩、去重(耗时多,用在打包项目时,所以只需要在 webpack.prod.js 中配置)
10、JS代码压缩(耗时多,用在打包项目时,所以只需要在 webpack.prod.js 中配置)
11、tree-shaking,只打包用到的代码,没用到的代码不打包,而 webpack5 默认开启 tree-shaking,当打包的 mode 为 production 时,自动开启 tree-shaking 进行优化
12、source-map类型,方便你报错的时候能定位到错误代码的位置
13、打包体积分析,审查打包后的体积分布,进而进行相应的体积优化
14、优化模块懒加载提升用户体验,使用 模块懒加载 之后,大js文件会分成多个小js文件,网页加载时会按需加载,大大提升首屏加载速度
15、Gzip,提高用户的页面加载速度,因为gzip的体积比原文件小很多,当然需要后端的配合
16、小图片转base64,减少用户的http网络请求次数,提高用户的体验
17、合理配置hash,更新改动后文件的hash值,保证上线后可以命中缓存,达到性能优化
1、webpack的结构配置:
配置包含有打包后的输出位置和文件名、插件配置、自动打包配置、扩展文件如css、less、图片的打包配置等;
1、插件下载:
插件 - html-webpack-plugin
html-webpack-plugin插件可以在每次打包时都创建一个用于测试用的html文件
npm i --save-dev html-webpack-plugin
下载 style-loader、css-loader配置打包css
npm i style-loader css-loader --save-dev
下载 npm i --save-dev webpack-dev-server 实现自动打包和热更新
2、创建webpack配置文件 webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 引入插件
const friendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
mode: 'development',// webpack的打包模式,development 开发模式、production 生产模式
// 配置打包入口文件
entry: {
app: './src/index.js'
},
// 配置打包输出位置,及文件名
output: {
path: path.resolve(__dirname, './dist'),
// 将入口entry对象的属性名,替换[name]
filename: '[name].bundle.js',
},
// 在plugins中传入插件、注册插件配置参数
plugins: [ //
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
filename: 'index.html',
}),
new CleanWebpackPlugin(), // 传入的插件
new friendlyErrorsWebpackPlugin(), // 传入的插件
],
// 扩展文件加载模块 - css模块、less模块、图片、
module: {
// 由于可以加载多种文件,每种文件对应一种loader,所以是数组
rules: [// js文件、css、图片等打包都需要loader
{ //打包css
test: /\.css$/,// 正则判断文件类型
use: ['style-loader', 'css-loader'], // 这种类型文件使用以下loader
},
{
// 判断less文件
test: /\.less$/i,
// less使用到的loader,
use: ["style-loader", "css-loader", "less-loader"]
},
{
// 加载图片资料模板
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
use: [
{
loader: 'url-loader', //背景图片的引用
options: {
limit: 8192
},
},
],
},
{
// 加载字体文件
test: /\.(eot|ttf|otf|woff2)$/,
type: 'asset'
},
],
},
// webpack-dev-server自动打包配置
devServer: {
// 配置站点根目录,默认为输出位置
static: path.resolve(__dirname, 'dist'),
// 设置端口号
port: 8080,
// 自动打开浏览器,访问index.html
open: true,
// 热替换配置,true启用,false禁用,默认为true
hot: true
},
}
3、配置webpack执行命令
webpack需要npm来调用才可以执行,在package.json中的script中进行配置
"script":{
"dev":"webpack --config webpack.config.js"
}
经过以上配置,在控制台运行 npm run dev 命令执行 webpack对 js 文件进行打包
https://blog.csdn.net/wsx1212123/article/details/123493958
二、webpack的原理
webpack是一个打包模块化js的工具,可以通过loader转换文件,通过plugin扩展功能。
1、核心概念
(1)entry:一个可执行模块或者库的入口。
(2)chunk:多个文件组成一个代码块。可以将可执行的模块和他所依赖的模块组合成一个chunk,这是打包。
(3)loader:文件转换器。例如把es6转为es5,scss转为css等
(4)plugin:扩展webpack功能的插件。在webpack构建的生命周期节点上加入扩展hook,添加功能。
2、webpack构建流程(原理)
从启动构建到输出结果一系列过程:
(1)初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
(2)开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。
(3)确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
(4)编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
(5)完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry配置生成代码块chunk。
(6)输出完成:输出所有的chunk到文件系统。
注意:在构建生命周期中有一系列插件在做合适的时机做合适事情,比如UglifyPlugin会在loader转换递归完对结果使用UglifyJs压缩覆盖之前的结果。
三、业务场景和对应解决方案
1、单页应用
一个单页应用需要配置一个entry指明执行入口,web-webpack-plugin里的WebPlugin可以自动的完成这些工作:webpack会为entry生成一个包含这个入口的所有依赖文件的chunk,但是还需要一个html来加载chunk生成的js,如果还提取出css需要HTML文件中引入提取的css。
一个简单的webpack配置文件例子
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './src/doc/index.js',
home: './src/doc/home.js'
},
plugins: [
// 一个WebPlugin对应生成一个html文件
new WebPlugin({
//输出的html文件名称
filename: 'index.html',
//这个html依赖的`entry`
requires: ['app','home'],
}),
],
};
说明:require: [‘app’, ‘home’]指明这个html依赖哪些entry,entry生成的js和css会自动注入到html中。
还支持配置这些资源注入方式,支持如下属性:
(1)_dist只有在生产环境中才引入的资源;
(2)_dev只有在开发环境中才引入的资源;
(3)_inline把资源的内容潜入到html中;
(4)_ie只有IE浏览器才需要引入的资源。
这些属性可以通过在js里配置,看个简单例子:
new WebPlugin({
filename: 'index.html',
requires: {
app:{
_dist:true,
_inline:false,
}
},
}),
这些属性还可以在模板中设置,使用模板好处就是可以灵活的控制资源的注入点。
new WebPlugin({
filename: 'index.html',
template: './template.html',
}),
//template模板
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<link rel="stylesheet" href="app?_inline">
<script src="ie-polyfill?_ie"></script>
</head>
<body>
<div id="react-body"></div>
<script src="app"></script>
</body>
</html>
WebPlugin插件借鉴了fis3的思想,补足了webpack缺失的以HTML为入口的功能。想了解WebPlugin的更多功能,见文档。
2、一个项目管理多个单页面
一个项目中会包含多个单页应用,虽然多个单页面应用可以合成一个,但是这样做会导致用户没有访问的部分也加载了,如果项目中有很多的单页应用。为每一个单页应用配置一个entry和WebPlugin?如果又新增,又要新增webpack配置,这样做麻烦,这时候有一个插件web-webpack-plugin里的AutoWebPlugin方法可以解决这些问题。
module.exports = {
plugins: [
// 所有页面的入口目录
new AutoWebPlugin('./src/'),
]
};
分析:
1、AutoWebPlugin会把./src/目录下所有每个文件夹作为一个单页页面的入口,自动为所有的页面入口配置一个WebPlugin输出对应的html。
2、要新增一个页面就在./src/下新建一个文件夹包含这个单页应用所依赖的代码,AutoWebPlugin自动生成一个名叫文件夹名称的html文件。
3、代码分隔优化
一个好的代码分割对浏览器首屏效果提升很大。
最常见的react体系:
(1)先抽出基础库react react-dom redux react-redux到一个单独的文件而不是和其它文件放在一起打包为一个文件,这样做的好处是只要你不升级他们的版本这个文件永远不会被刷新。如果你把这些基础库和业务代码打包在一个文件里每次改动业务代码都会导致文件hash值变化从而导致缓存失效浏览器重复下载这些包含基础库的代码。所以把基础库打包成一个文件。
// vender.js 文件抽离基础库到单独的一个文件里防止跟随业务代码被刷新
// 所有页面都依赖的第三方库
// react基础
import 'react';
import 'react-dom';
import 'react-redux';
// redux基础
import 'redux';
import 'redux-thunk';
// webpack配置
{
entry: {
vendor: './path/to/vendor.js',
},
}
2)通过CommonsChunkPlugin可以提取出多个代码块都依赖的代码形成一个单独的chunk。在应用有多个页面的场景下提取出所有页面公共的代码减少单个页面的代码,在不同页面之间切换时所有页面公共的代码之前被加载过而不必重新加载。所以通过CommonsChunkPlugin可以提取出多个代码块都依赖的代码形成一个单独的chunk。
4、构建服务端渲染
服务端渲染的代码要运行在nodejs环境,和浏览器不同的是,服务端渲染代码需要采用commonjs规范同时不应该包含除js之外的文件比如css。
webpack配置如下:
module.exports = {
target: 'node',
entry: {
'server_render': './src/server_render',
},
output: {
filename: './dist/server/[name].js',
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.(scss|css|pdf)$/,
loader: 'ignore-loader',
},
]
},
};
分析一下:
(1)target: 'node’指明构建出代码要运行在node环境中。
(2)libraryTarget: ‘commonjs2’ 指明输出的代码要是commonjs规范。
(3){test: /.(scss|css|pdf)$/,loader: ‘ignore-loader’} 是为了防止不能在node里执行服务端渲染也用不上的文件被打包进去。
5、fis3迁移到webpack
fis3和webpack有很多相似地方也有不同的地方,相似地方:都采用commonjs规范,不同地方:导入css这些非js资源的方式。
fis3通过@require ‘./index.scss’,而webpack是通过require(’./index.scss’)。
如果想把fis3平滑迁移到webpack,可以使用comment-require-loader。
比如:你想在webpack构建是使用采用了fis3方式的imui模块
loaders:[{
test: /\.js$/,
loaders: ['comment-require-loader'],
include: [path.resolve(__dirname, 'node_modules/imui'),]
}]
四、自定义webpack扩展
如果你在社区找不到你的应用场景的解决方案,那就需要自己动手了写loader或者plugin了。
在你编写自定义webpack扩展前你需要想明白到底是要做一个loader还是plugin呢?可以这样判断:如果你的扩展是想对一个个单独的文件进行转换那么就编写loader剩下的都是plugin。
其中对文件进行转换可以是像:
1、babel-loader把es6转为es5;
2、file-loader把文件替换成对应的url;
3、raw-loader注入文本文件内容到代码中。
1、编写webpack loader
编写loader非常简单,以comment-require-loader为例:
module.exports = function (content) {
return replace(content);
};
loader的入口需要导出一个函数,这个函数要干的事情就是转换一个文件的内容。
函数接收的参数content是一个文件在转换前的字符串形式内容,需要返回一个新的字符串形式内容作为转换后的结果,所有通过模块化倒入的文件都会经过loader。从这里可以看出loader只能处理一个个单独的文件而不能处理代码块。可以参考官方文档
2、编写webpack plugin
plugin应用场景广泛,所以稍微复杂点。以end-webpack-plugin为例:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
// 监听webpack生命周期里的事件,做相应的处理
compiler.plugin('done', (stats) => {
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
this.failCallback(err);
});
}
}
module.exports = EndWebpackPlugin;
loader的入口需要导出一个class,在new EndWebpackPlugin()的时候通过构造函数传入这个插件需要的参数,在webpack启动的时候会先实例化plugin,再调用plugin的apply方法,插件在apply函数里监听webpack生命周期里的事件,做相应的处理。
webpack plugin的两个核心概念:
(1)compiler:从webpack启动到退出只存在一个Compiler,compiler存放着webpack的配置。
(2)compilation:由于webpack的监听文件变化自动编译机制,compilation代表一次编译。
Compiler 和 Compilation 都会广播一系列事件。webpack生命周期里有非常多的事件
以上只是一个最简单的demo,更复杂的可以查看 how to write a plugin或参考web-webpack-plugin。
五、一些问题
1、webpack与grunt、gulp、vite、rollup的不同?
这几者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
webpack:
适用于大型的项目,开始就需要通过入口文件开始打包,一次性加载所有的资源,webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。侧重的是模块化前端开发流程
vite:
实时的打包,开始不会进行打包,快速的冷启动,即时的模块热更新,真正的按需编译,所以启动非常快,直接请求所需模块并实时编译;
利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去 webpack 冗长的打包时间.
利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。
rollup:
适用于基础裤的打包,基于ES6打包,打包文件小且干净,执行效率更高,vue、react都是基于他打包的
grunt和gulp:
基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程,旨在强调自动化前端构造流程。
2、 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?
同样是基于入口的打包工具还有以下几个主流的:webpack,rollup,parcel。
从应用场景上来看:
(1)webpack适合大型复杂的前端站点构建;
(2)rollup适合基础库的打包,比如vue,react;
(3)parcel适用于简单的实验室项目,但是打包出错很难调试。
3、有哪些常见的Loader?他们是解决什么问题的?
(1)babel-loader:把es6转成es5;
(2)css-loader:加载css,支持模块化,压缩,文件导入等特性;
(3)style-loader:把css代码注入到js中,通过dom操作去加载css;
(4)eslint-loader:通过Eslint检查js代码;
(5)image-loader:加载并且压缩图片晚间;
(6)file-loader:文件输出到一个文件夹中,在代码中通过相对url去引用输出的文件;
(7)url-loader:和file-loader类似,文件很小的时候可以base64方式吧文件内容注入到代码中。
(8)source-map-loader:加载额外的source map文件,方便调试。
4、有哪些常见的Plugin?他们是解决什么问题的?
(1)uglifyjs-webpack-plugin:通过UglifyJS去压缩js代码;
(2)commons-chunk-plugin:提取公共代码;
(3)define-plugin:定义环境变量。
5、loader和plugin的不同
作用不同:(1)loader让webpack有加载和解析非js的能力;(2)plugin可以扩展webpack功能,在webpack运行周期中会广播很多事件,Plugin可以监听一些事件,通过webpack的api改变结果。
用法不同:(1)loader在module.rule中配置。类型为数组,每一项都是Object;(2)plugin是单独配置的,类型为数组,每一项都是plugin实例,参数通过构造函数传入。
6、webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
(1)初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
(2)开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
(3)确定入口:根据配置中的 entry 找出所有的入口文件;
(4)编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
(5)完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
(6)输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
(7)输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
7、是否写过Loader和Plugin?描述一下编写loader或plugin的思路?
编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。 每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过 this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。
Plugin的编写就灵活了许多。 webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
8、webpack的热更新是如何做到的?说明其原理?
webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
原理:
分析:
(1)第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
(2)第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
(3)第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
(4)第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
(5)webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
(6)HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
(7)而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
(8)最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。
9、如何利用webpack来优化前端性能?(提高性能和体验)
用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。
(1)压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css。使用webpack4,打包项目使用production模式,会自动开启代码压缩。
(2)利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
(3)删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数–optimize-minimize来实现或者使用es6模块开启删除死代码。
(4)优化图片,对于小图可以使用 base64 的方式写入文件中
(5)按照路由拆分代码,实现按需加载,提取公共代码。
(6)给打包出来的文件名添加哈希,实现浏览器缓存文件
10、如何提高webpack的构建速度?
(1)多入口的情况下,使用commonsChunkPlugin来提取公共代码;
(2)通过externals配置来提取常用库;
(3)使用happypack实现多线程加速编译;
(4)使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。原理上webpack-uglify-parallel采用多核并行压缩来提升压缩速度;
(5)使用tree-shaking和scope hoisting来剔除多余代码。
11、怎么配置单页应用?怎么配置多页应用?
单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可。
多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。
12、npm打包时需要注意哪些?如何利用webpack来更好的构建?
NPM模块需要注意以下问题:
(1)要支持CommonJS模块化规范,所以要求打包后的最后结果也遵守该规则
(2)Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。
(3)Npm包大小应该是尽量小(有些仓库会限制包大小)
(4)发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。
(5)UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。
基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
(1)CommonJS模块化规范的解决方案: **设置output.libraryTarget=‘commonjs2’**使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用;
(2)输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: 'source-map’输出SourceMap以发布调试。
(3)Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
(4)不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
(5)对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现
13、如何在react项目中实现按需加载?
有三种方式实现:
方式1.精确加载组件
import Button from 'antd/lib/button'
import 'antd/lib/button/style'
方式2.暴露配置,配合babel-plugin-import插件实现按需加载
babel-plugin-import是一个用于按需加载组件和样式的babel插件
暴露配置:npm run eject
安装插件:npm install babel-plugin-import -S
修改package.json
"babel": {
"presets": [
"react-app"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style":"css"
}
]
]
}
配置完之后直接引入:import {Button} from ‘antd’
方式3.通过babel-plugin-import+react-app-rewired实现按需加载
react-app-rewired在不用暴露的配置的情况下对webpack配置进行扩展
//安装插件:
npm install babel-plugin-import -S
//修改(添加)config-overrides.js文件
//引入react-app-rewired添加babel插件的函数
const {injetBabelPlugin}=require('react-app-rewired')
module.exports=function override(config,env){
config=injetBabelPlugin([
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style":"css"
}
]
],config);
return config
}:
配置完之后直接引入:import {Button} from ‘antd’
14、webpack优化
1、构建时间优化
2、thread-loader 多进程打包,提高构建的速度
使用方法是将 thread-loader 放在比较费时间的loader之前,比如 babel-loader
由于启动项目和打包项目都需要加速,所以配置在 webpack.base.js
npm i thread-loader -D
// webpack.base.js { test: /\.js$/, use: [ 'thread-loader', 'babel-loader' ], } }
3、cache-loader。缓存资源,提高二次构建的速度
使用方法是将 cache-loader 放在比较费时间的loader之前,比如 babel-loader
由于启动项目和打包项目都需要加速,所以配置在 webpack.base.js
npm i cache-loader -D
// webpack.base.js { test: /\.js$/, use: [ 'cache-loader', 'thread-loader', 'babel-loader' ], },
4、开启热更新
比如你修改了项目中某一个文件,会导致整个项目刷新,这非常耗时间。如果只刷新修改的这个模块,其他保持原状,那将大大提高修改代码的重新构建时间
只用于开发中,所以配置在 webpack.dev.js
// webpack.dev.js //引入webpack const webpack = require('webpack'); //使用webpack提供的热更新插件 plugins: [ new webpack.HotModuleReplacementPlugin() ], //最后需要在我们的devserver中配置 devServer: { hot: true },
5、exclude & include 合理设置可以提高构建速度
在 webpack.base.js 中配置
// webpack.base.js { test: /\.js$/, //使用include来指定编译文件夹 include: path.resolve(__dirname, '../src'), //使用exclude排除指定文件夹 exclude: /node_modules/, use: [ 'babel-loader' ] },
6、构建区分环境
区分环境去构建是非常重要的,我们要明确知道,开发环境时我们需要哪些配置,不需要哪些配置;而最终打包生产环境时又需要哪些配置,不需要哪些配置
7、提升webpack版本
webpack版本越新,打包的效果肯定更好
8、打包体积优化
主要是打包后项目整体体积的优化,有利于项目上线后的页面加载速度提升
9、css代码压缩
CSS代码压缩使用 css-minimizer-webpack-plugin ,效果包括压缩、去重
代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在 webpack.prod.js 中配置
npm i css-minimizer-webpack-plugin -D
// webpack.prod.js const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') optimization: { minimizer: [ new CssMinimizerPlugin(), // 去重压缩css ], }
10、JS代码压缩
JS代码压缩使用 terser-webpack-plugin ,实现打包后JS代码的压缩
代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在 webpack.prod.js 中配置
npm i terser-webpack-plugin -D
// webpack.prod.js const TerserPlugin = require('terser-webpack-plugin') optimization: { minimizer: [ new CssMinimizerPlugin(), // 去重压缩css new TerserPlugin({ // 压缩JS代码 terserOptions: { compress: { drop_console: true, // 去除console }, }, }), // 压缩JavaScript ], }
11、tree-shaking
tree-shaking 简单说作用就是:只打包用到的代码,没用到的代码不打包,而 webpack5 默认开启 tree-shaking ,当打包的 mode 为 production 时,自动开启 tree-shaking 进行优化
module.exports = { mode: 'production' }
12、source-map类型
source-map 的作用是:方便你报错的时候能定位到错误代码的位置。它的体积不容小觑,所以对于不同环境设置不同的类型是很有必要的。
开发环境
开发环境的时候我们需要能精准定位错误代码的位置
// webpack.dev.js module.exports = { mode: 'development', devtool: 'eval-cheap-module-source-map' }
生产环境
生产环境,我们想开启 source-map ,但是又不想体积太大,那么可以换一种类型
// webpack.prod.js module.exports = { mode: 'production', devtool: 'nosources-source-map' }
13、打包体积分析
使用 webpack-bundle-analyzer 可以审查打包后的体积分布,进而进行相应的体积优化
只需要打包时看体积,所以只需在 webpack.prod.js 中配置
npm i webpack-bundle-analyzer -D
// webpack.prod.js const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') plugins: [ new BundleAnalyzerPlugin(), ]
14、优化模块懒加载提升用户体验
如果不进行 模块懒加载 的话,最后整个项目代码都会被打包到一个js文件里,单个js文件体积非常大,那么当用户网页请求的时候,首屏加载时间会比较长,使用 模块懒加载 之后,大js文件会分成多个小js文件,网页加载时会按需加载,大大提升首屏加载速度
// src/router/index.js const routes = [ { path: '/login', name: 'login', component: login }, { path: '/home', name: 'home', // 懒加载 component: () => import('../views/home/home.vue'), }, ]
15、Gzip
开启Gzip后,大大提高用户的页面加载速度,因为gzip的体积比原文件小很多,当然需要后端的配合,使用 compression-webpack-plugin
只需要打包时优化体积,所以只需在 webpack.prod.js 中配置
npm i compression-webpack-plugin -D
// webpack.prod.js const CompressionPlugin = require('compression-webpack-plugin') plugins: [ // 之前的代码... // gzip new CompressionPlugin({ algorithm: 'gzip', threshold: 10240, minRatio: 0.8 }) ]
16、小图片转base64
对于一些小图片,可以转base64,这样可以减少用户的http网络请求次数,提高用户的体验。webpack5 中 url-loader 已被废弃,改用 asset-module
在 webpack.base.js 中配置
// webpack.base.js { test: /\.(png|jpe?g|gif|svg|webp)$/, type: 'asset', parser: { // 转base64的条件 dataUrlCondition: { maxSize: 25 * 1024, // 25kb } }, generator: { // 打包到 image 文件下 filename: 'images/[contenthash][ext][query]', }, },
17、合理配置hash
我们要保证,改过的文件需要更新hash值,而没改过的文件依然保持原本的hash值,这样才能保证在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的
在 webpack.base.js 中配置
// webpack.base.js output: { path: path.resolve(__dirname, '../dist'), // 给js文件加上 contenthash filename: 'js/chunk-[contenthash].js', clean: true, },