官方文档:
https://www.webpackjs.com/concepts/
why webpack
1.现代web开发‘问题’
- 采用模块化开发
- 使用新特性提高效率保证安全性
- 实时监听开发过程使用热更新
- 项目结果打包压缩优化
使用webpack实现项目工程化
2. webpack上手
为现代javascript应用提供静态模块打包的工具
- 打包:将不同类型资源按模块处理进行打包
- 静态:打包后最终产出静态资源
- 模块:webpack支持不同规范的模块化开发。
全局安装:npm install --global webpack
查看版本:webpack --version
webpack 5.51.1
使用全局的webpack并不合适,因为每个人本地安装的版本并不相同,因此需要使用局部的webpack打包。
- 默认会找src/index.js开始打包。
- 打包:npx webpack
- 自定义打包入口 npx webpack --entry ./src/main.js
- 希望打包的文件放在指定的目录中 npx webpack --entry ./src/main.js --output-path ./build
- 命令简写: 在package.json中配置:
"scripts": {
"build": " npx webpack --entry ./src/main.js --output-path ./build"
},
书写配置文件
- webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
}
}
4. webpack 依赖图
1.新添加的js文件,如果也想打包,则需要加入到主入口文件中,构成依赖
2. 自定义配置文件名:lg.webpack.js
在packaje.json中配置:
"scripts": {
"build": "webpack --config lg.webpack.js"
},
lg.webpack.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
5. CSS-Loader
在使用webpack的过程中,发现并不是可以处理所有类型的文件,因此需要使用loader对它进行转换。
loader要想使用,首先需要安装,然后具体的loader需要去查找。然后在配置文件中进行配置。
- 为什么需要使用loader?
loader是必须的。loader起到一个转换的功能 - loader是什么?
是一个模块,里面导出一个函数,这个函数的功能起到一个转换的作用,webpack默认只识别js语法,其他的语法不能识别。因此需要转换成webpack可以识别的语法。 - css-loader案例
npm i css-loader --save-dev
1.行内loader的使用
// 行内loader(一般不使用)
// css-loadder只是将当前css语法处理为js可以识别的语法,但是并不能把样式放在页面上进行使用。
// 使用方式1
// import 'css-loader!../css/login.css'
// 使用方式2 并且需要在webpack.config.js 中进行配置
import '../css/login.css'
function login() {
const oH2 = document.createElement('h2')
oH2.innerHTML = '拉勾教育前端'
oH2.className = 'title'
return oH2
}
document.body.appendChild(login())
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
// 3中书写方式
// 适合多个loader一起使用
// {
// test: /\.css$/, // 一般就是一个正则表达式,用于匹配我们需要处理的文件类型
// use: [
// {
// loader: 'css-loader'
// }
// ]
// },
// 简写1
// {
// test: /\.css$/,
// loader: 'css-loader'
// },
// 简写2
{
test: /\.css$/,
use: ['css-loader']
}
]
}
}
6.style-loader 使用
- 让我们用css-loader处理的css,最终可以在页面上显示出效果。
安装:npm i style-loader --save-dev
使用:webpack.config.js
module: {
rules: [
{
test: /\.css$/,
// 处理的顺序,如果是一行:从右向左,如果换行:从下向上。
use: ['style-loader', 'css-loader']
}
]
}
7. less-loader
在项目中使用less
./longin.less
@bgColor: seagreen;
@fontSize: 100px;
.title {
background-color: @bgColor;
font-size: @fontSize;
}
在项目中引入:
import '../css/login.less'
1.安装:npm install less less-loader -D
2.使用:
webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
// 先使用less-loader把less转换成css,css还是浏览器不能识别的,还需要使用css-loader,style-loader
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
8. browserslitrc 工作流程
- 浏览器市场占有率表格: https://caniuse.com/usage-table
浏览器的兼容性处理
1.工程化
- 兼容性:新特性/新语法的支持( css, js)
- 如何实现兼容操作?
- 到底要兼容哪些平台?
caniuse.com .可以查到浏览器的市场占用率
.browserslitrc
> 1%
的含义:市场占有率>1%的浏览器都能被兼容到- last 2 version :某一个浏览器的最新2个版本。
- default: 市场占有率>0.5%,最新的2个版本。
- dead:废弃的平台。(24个月没有维护的平台)
2种配置方法:
- 中在package.js
"browserslistrc": [
">1%",
"last 4 version",
"not dead"
]
执行 npx browserslist
.browserslistrc
> 1%
last 2 version
not dead
9. postcss 工作流程
- 查看样式在浏览器中的兼容性:http://autoprefixer.github.io/
- postcss是什么:通过javascript来转换样式的工具。
安装:npm i postcss -D - 在终端中使用postcss需要安装postcss-cli:npm i postcss-cli -D
- 安装:npm i autoprefixer -D。进行具体的样式转换。
// -o :output
// -o ret.css :输出目录
// ./src/css/test.js 需要处理的路径
执行命令查看效果:npx postcss --use autoprefixer -o ret.css ./src/css/test.js
postcss: 整体的解析器
autoprefixer:指的是具体的功能,可以对样式做一个前缀的添加
postcss-cli:想在命令行中使用命令执行.
10.postcss-loader 处理兼容
- 如果有很多文件要处理,不可能还使用命令行来操作,因此要借助:postcss-loader来处理。
- postcss-loader和.browserslitrc是搭配使用的。
- 如果想要兼容的更多,可以调整.browserslitrc文件中的配置项,例如>1%调整为>0.1%,打包出来的css是不一样的。
- npm i postcss-loader -D
- npm i autoprefixer -D
postcss-loader在css-loader之前进行工作,需要在代码之前加上前缀,处理之后交给css-loadre。
配置postcss-loader:
webpack.config.js
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'),
require('postcss-preset-env'),
]
}
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'),
require('postcss-preset-env')
]
}
}
},
'less-loader'
]
}
]
- 例如:css颜色的设置:color:#12345678,后面的两位是透明度,postcss-loader是不能解析的,新出的写法。因此出现了postcss-preset-env。处理css的集合。
- 集合了很多现代css转换需要做的集合:npm i postcss-preset-env -D
预设
插件集合
- autoprefixer已经被包含在了postcss-preset-env中了,因此可以不需要引入:autoprefixer
- 可以发现代码同样的配置写了2遍,可以很方便的进行添加和管理。因此可以用配置文件的写法:
- postcss.config.js(文件的名称不能随意修改,默认的就会找这个文件)
module.exports = {
plugins: [
require('postcss-preset-env')
// require('autoprefixer')
]
}
webpack.config.js
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
npm run build查看样式。
11. importLoaders 属性
importLoaders:
解析postcss-loader解析后,一直执行,但是有的文件不会被postcss-loader处理问题
1: 表示调用几次。
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
}
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
12.file-loader 处理图片
- 2种图片的引入方式
- image src
- background-image url
- npm i file-laoder -D
/**
* 打包图片:
* - img src
* + 使用 require 导入图片,此时如果不配置 esModule: false ,则需.default 导出
* + 也可以在配置当中设置 esModule: false
* + 采用 import xxx from 图片资源,此时可以直接使用 xxxx
* - background url
*/
// esModule: false。为了解决:使用background引入图片时,引入的背景图不能显示问题。
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:6].[ext]',
// outputPath: 'img'
}
}
]
}
]
/**
* [ext]: 扩展名
* [name]: 文件名
* [hash]: 文件内容
* [contentHash]:
* [hash:<length>]
* [path]:
*/
14.url-loader 处理图片
npm i url-loader -D
- 两者的不同:
file-loader: 将图片资源直接拷贝到要打包的目录。
url-loader: 把我们当前要打包出来的图片资源以base64uri的方式去加载到代码中去。打包出来的文件,发现找不到图片。好处是:减少请求的次数,不好:如果文件过大,会出现请求的时间过长,在首屏影响性能和体验。
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: 'url-loader',
// 设置图片名称与输出 tscproj
options: {
name: 'img/[name].[hash:6].[ext]',
limit: 25 * 1024
}
}
]
}
]
/**2者区别:
* 01 url-loader base64 uri 文件当中,减少请求次数
* 02 file-loader 将资源拷贝至指定的目录,分开请求
* 03 2者之间的联系:url-loader 内部其实也可以调用 file-loader。
* 04 limit:决定了:以哪种方式加载。如果大于这个值,就做做拷贝,如果没有超过这个值,就是base64 uri的方式加载
*
*/
15. asset 处理图片
- 卸载url-loader, file-loader
- 使用asset。asset是webpack内置的功能,因此不需要安装。
- asset:4种配置。
- type: ‘asset /resource’ 功能和file-loader是一样的
- type: ‘asset/inline’ 功能和uri-loader一样。
- type: ‘asset/source’ 功能和 raw-loader一样。
- asset
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// 重定向输出方式2.一般呢不会这样配置。因为是:全局的配置。字体也会走这样的打包路线。
// assetModuleFilename: "img/[name].[hash:4][ext]"
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
// type: 'asset/resource',
// type: 'asset/inline',
generator: {
// 重定向输出方式1
filename: "img/[name].[hash:4][ext]"
},
parser: {
// 如果图片的大小超过maxSize的值,就走asset/resource
// 如果图片的大小小于maxSize的值,就走asset/inline
dataUrlCondition: {
maxSize: 30 * 1024
}
}
}
]
}
}
16. asset 处理图标字体
rules: [
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
17. webpack插件使用
- loader: 转换,特定类型
- plugin:做更多的事情。可以贯穿整个webapck生命周期。
每次打包时清除上次打包的文件。
- npm install clean-webpack-plugn -D
- 在webpack.config.js中使用
webpakc.confg.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
18.html-webapck-plugin 使用
- 自定义html模版
- npm install html-webpack-plugin - D
- 在webpack.config.js中使用
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { DefinePlugin } = require('webpack')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// title
title: 'html-webpack-plugin',
// 定义自己打包的html 模版
template: './public/index.html'
}),
// 内置的插件 。定义iocn。
// 注意:BASE_URL的赋值。
new DefinePlugin({
BASE_URL: '"./"'
})
]
}
- 创建模版文件
./public/index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- icon -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
19.copy-webpack-plugin
- npm install copy-webpack-plugin -D
- 在webpack.config.js中使用
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
new CopyWebpackPlugin({
// 拷贝规则
patterns: [
{
// 期望从哪里开始拷贝
from: 'public',
// 拷贝到哪里 to可以不写。默认是:上面的output.path的值
// to: 'dist',
// 可以指定规则拷贝的时候,不需要拷贝的地方
globOptions: {
// '**/':表示从from设置的目录开始
ignore: ['**/index.html']
}
}
]
})
]
20. babel 工作的过程和使用
- 为什么需要 Babel。 – 处理js兼容
我们在做react/vue开发时,用到的jsx语法,ts语法,es6+ 语法,不能直接在浏览器平台使用,如果想在浏览器平台直接使用,那么中间就需要一个转换的过程,需要一个东西来处理js,类似于postcss兼容css。
babel是一个工具,本身不具备任何功能。 - @babel/core:核心功能。
- npm install @babel/core -D 作用:非es5的代码专程浏览器可以识别的。
- npm install @babel/cli -D 。 作用:命令行工具。在命令行中可以使用。可以直接在命令行中执行:npx babel
- npx babel src --out-dir build.
- src:开始转换的入口。 --out-dir build: 输出的目录。
- npm install --save-dev @babel/plugin-transform-arrow-functions
- 执行命令:npx babel src --out-dir build --plugins=@@babel/plugin-transform-arrow-functions. 查看代码可以发现箭头函数被转换成了普通函数。
使用:
- npm install @babel/core -D
- npm install @babel/cli -D
- npm install @babel/plugin-transform-arrow-functions -D. // 箭头函数转换成普通函数
- npm install @babel/plugin-transform-block-scoping -D。 // const. - > 转换成var
- 书写伪代码:
const title = 'wode'
const foo = () => {
console.log(title)
}
foo()
- 执行打包命令: npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
- 查看打包出来的文件
const title = 'wode';
const foo = function () {
console.log(title);
};
foo();
直接使用:npm install @babel/plugin-preset-env -D。可以完成js代码的转换。
- 执行命令: npx babel src --out-dir build --plugins=@babel/preset-env。发现可以完成以上箭头函数,变量…等转换。
21. babel-loader 使用
-
npm install babel-loader -D 拷贝一份js代码,什么也不做。打包之后的代码:箭头函数仍然书箭头函数,const仍然是const,并没有做转换。
-
npm install @babel/plugin-preset-env -D。 @babel/plugin-preset-env对代码进行转换
-
bebl-loader 相关的配置文件有2中方式书写:
1. 方式1: 直接在webpack.config.js中配置
2. 在webpack.config.js + 创建babel.config.js文件 (一般采用的方式)
方式1 :在webpack.config.js中进行配置:
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{ targets: 'chrome91' } // 兼容的浏览器版本设置
]
}
}
]
}
]
- 方式2: webpack.config.js + 新建babel.config.js文件
webpack.config.js:
rules: [
{
test: /\.js$/,
use: ['babel-loader']
}
]
新建babel.config.js文件
module.exports = {
presets: ['@babel/preset-env']
}
注意:兼容性处理:需要配合.browserslistrc这个文件一块设置使用。
22. polyfill 配置
更加方便的处理js的兼容性操作
webpack4默认自己加进去了,打包出来的代码包非常的大。webpack5默认情况下被移除掉了,用到了里面的东西,需要自己安装和配置。
- polyfill到底是什么?
可以转换一些es6的新语法,例如:promise,aysnc… - npm install @babel/polyfill --save. 开发依赖。 按需配置。
- 官方文档: https://babeljs.io/docs/en/babel-polyfill
- .browserslistrc 指出要兼容哪些浏览器。
- babel.config.js. 怎么兼容之处的浏览器,具体的做一些配置。
babel.config.js配置:
module.exports = {
presets: [
[
'@babel/preset-env',
{
// false: 不对当前的JS处理做 polyfill 的填充
// usage: 依据用户源代码当中所使用到的新语法进行填充
// entry: 依据我们当前筛选出来的浏览器决定填充什么
useBuiltIns: 'entry',
corejs: 3 // 版本3
}
]
]
}
23.webpack-dev-server配置
开启本地服务器:
- 配置:webpack-dev-server
- npm install --save-dev webpack-dev-server
- 修改package.json
- 会在开始一个8080端口的监听
package.json
"scripts": {
"serve": "webpack serve --config lg.webpack.js"
},
原理:把数据都写在内存里面,内存的操作肯定比本地的硬盘操作快。
24.webpack-dev-middleware 使用
文档介绍:https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-middleware
- 定制自己的server,开启服务。
npm install --save-dev express webpack-dev-middleware
25. HMR 模块热替换功能使用
官网配置:
https://www.webpackjs.com/guides/hot-module-replacement/#%E5%90%AF%E7%94%A8-hmr
- 没有使用watch模式,也没有使用webpack-dev-middleware的前提下:
- 浏览器只需要改变当前局部发生改变的组件进行更新展现,不影响其他的模块。
- 如何配置:
- 在webpack。config.js中进行配置:
module.exports = {
target:'web', // 屏蔽.browserslistrc的设置
devServer: {
hot: true
},
}
index.js
+ if (module.hot) {
+ module.hot.accept('./index.js', function() {
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ }
- 重启服务
26.React 组件支持热更新
思路:
- 需要babel-loader解析jsx语法
- hmr
- npm install @babel/preset-react - D
- npm install @pmmmwh/react-refresh-webpack-plugin react-refresh -D
- npm install @babel/core @babel/preset-env @babel-loader- D
- npm install react-dom react -D
配置:
babel.config.js
module.exports = {
presets: [
['@babel/preset-env'],
['@babel/preset-react'],
],
plugins: [
['react-refresh/babel']
]
}
webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
target:'web', // 屏蔽.browserslistrc的设置
devServer: {
hot: true
},
},
module: {
rules: [
{
test: /\.jsx?$/, // js也会走这条规则
use: ['babel-loader']
}
],
plugins: [
new ReactRefreshWebpackPlugin()
]
}
index.js
import './title'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'
if (module.hot) {
module.hot.accept(['./title.js'], () => {
console.log('title.js模块更新')
})
}
ReactDOM.render(<App />, document.getElementById('app'))
package.json
"scripts": {
"serve": "webpack serve"
},
27. Vue 组件支持热更新
npm install vue-template-compiler -D
npm install vue-loader -D //
index.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#root')
webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
target:'web', // 屏蔽.browserslistrc的设置
devServer: {
hot: true
},
},
module: {
rules: [
{
test: /\.vue$/, // js也会走这条规则
use: ['vue-loader']
}
],
},
plugins: [
new VueLoaderPlugin()
]
}
28. output 中的 path
https://www.webpackjs.com/configuration/output/#output-publicpath
output: {
filename: 'js/main.js',
path: path.resolve(__dirname, 'dist'),// 打包资源的输出路径
//
publicPath: '/'
},
target: 'web',
devServer: {
hot: true,
//
publicPath: '/',
contentBase: path.resolve(__dirname, 'public'),
watchContentBase: true
},
/**
* output
- publicPath:index.html内部的引用路径
- 域名+ publicPath + filename
devServer
- publicPath: 指定本地服务所在的目录
- contentBase: 我们打包之后的资源如果说依赖其它的资源,此时就告知去哪找。
- watchContentBase: true
*/
30.devServer 常用配置
https://www.webpackjs.com/configuration/dev-server/#devserver
webpack.config.js
devServer: {
hot: true, // 启用 webpack 的模块热替换特性:
hotOnly: true, // 启用热模块替换(参见devServer.hot),在构建失败时不刷新页面作为回退。
port: 4000,
open: false,
compress: true, // 一切服务都启用gzip 压缩
historyApiFallback: true
},
31proxy 代理设置
- 为什么会出现跨域?
在开发阶段,浏览器中访问的是localhost:3000,服务器的访问地址是api.com:4000,就会出现跨域,
前端在一个服务上,后端在一个服务上。服务器和服务器之间的通信是不会跨域的。
/api:表示要唤起这个代理。
我们接口请求的地址是:localhost:3000/api/user。但是我们设置了proxy的target:‘https://api/github.com’。因此我们接口请求的地址就被代理成了’https://api/github.com/api/user`/ 。 target表示问哪个服务端要数据。
32. resolve 模块解析规则
- resolve: 配置模块如何被解析。
- 在webpack中解析的路径分为3类?
然后每一类按照自己的解析规则去处理。
- 相对路径。 例如在项目:import ‘’./title’ 。 根据相对目录来查找文件
- 绝对路径。找不到,不做特殊处理。
- 模块名称。例如:import React from ‘react’ 根据模块的名称到node_modules中查找。
- 首先处理好路径,当路径确定了之后确定最后是文件,还是文件夹,如果有后缀,直接根据后缀名称找到对应的文件。
- 如果是文件用resolve.extensions 处理。
- 如果是文件夹,用resolve.mainFiles处理文件。
module.exports = {
mode: 'development',
devtool: false,
entry: './src/index.js',
resolve: {
extensions: [".js", ".json", '.ts', '.jsx', '.vue'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
}
}
在项目中引入组件;import About from ‘./components/Home’
可以修改为:import About from ‘@/components/Home’
33.source-map 作用
mode:告知 webpack 使用相应模式的内置优化。 https://www.webpackjs.com/concepts/mode/#%E7%94%A8%E6%B3%95
devtool: https://www.webpackjs.com/configuration/devtool/#devtool
- source-map: 是一种技术,一种映射的技术。这个映射指的就是可以去依据我们转换之后的代码,然后再去返还成我们没有转换之前的源代码,之后我们在调试的过程中就可以直接去定位错误在源代码中的信息。
- 好处: 在调试时可以定位到源代码中的信息。
34.devtool 详细说明
35. ts-loader 编译 TS
npm install --save-dev ts-loader
webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.ts$/, use: 'ts-loader' }
]
}
};
36. babel-loader 编译 TS
- ts-loader:把我们写的ts语法转换成js语法,但是并不能把polyfill进行填充。
- 如果使用了新的语法特性ts-loader就能打包,但是不能进行填充和解析,因此就要用到lalbel-loader。例如:promise,async
- 如何用babel-loader在ts文件中对我们的ts文件进行编译和打包的操作。
babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}],
['@babel/preset-typescript']
]
}
38.区分打包环境
- webpack-merge 2个对象的合并操作
目录结构
webpack.common.js
const resolveApp = require('./paths')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { merge } = require('webpack-merge')
// 导入其它的配置
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')
// 定义对象保存 base 配置信息
const commonConfig = {
entry: './src/index.js', // 反而没有报错( 相对路径 )
resolve: {
extensions: [".js", ".json", '.ts', '.jsx', '.vue'],
alias: {
'@': resolveApp('./src')
}
},
output: {
filename: 'js/main.js',
path: resolveApp('./dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: "img/[name].[hash:4][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
},
{
test: /\.jsx?$/,
use: ['babel-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'copyWebpackPlugin',
template: './public/index.html'
})
]
}
module.exports = (env) => {
const isProduction = env.production
process.env.NODE_ENV = isProduction ? 'production' : 'development'
// 依据当前的打包模式来合并配置
const config = isProduction ? prodConfig : devConfig
const mergeConfig = merge(commonConfig, config)
return mergeConfig
}
webpack.prod.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
globOptions: {
ignore: ['**/index.html']
}
}
]
})
]
}
webpack.dev.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
target: 'web',
devServer: {
hot: true,
hotOnly: true,
port: 4000,
open: false,
compress: true,
historyApiFallback: true,
proxy: {
'/api': {
target: 'https://api.github.com',
pathRewrite: { "^/api": "" },
changeOrigin: true
}
}
},
plugins: [
new ReactRefreshWebpackPlugin()
]
}
paths.js
const path = require('path')
// 当前应用的根路径
const appDir = process.cwd()
// relativePath: 相对路径
const resolveApp = (relativePath) => {
return path.resolve(appDir, relativePath)
}
module.exports = resolveApp
package.json
{
"name": "02_webpack_config_start",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"serve": "webpack serve",
"build2": "webpack --config ./config/webpack.common.js --env production",
"serve2": "webpack serve --config ./config/webpack.common.js --env development"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.14.8",
"@babel/core": "^7.15.0",
"@babel/plugin-transform-arrow-functions": "^7.14.5",
"@babel/plugin-transform-block-scoping": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"autoprefixer": "^10.3.1",
"axios": "^0.21.1",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"postcss": "^8.3.6",
"postcss-cli": "^8.3.1",
"postcss-loader": "^6.1.1",
"postcss-preset-env": "^6.7.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh": "^0.10.0",
"react-router-dom": "^5.2.0",
"style-loader": "^3.2.1",
"webpack": "^5.47.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"core-js": "^3.16.0",
"express": "^4.17.1",
"regenerator-runtime": "^0.13.9",
"webpack-dev-middleware": "^5.0.0"
}
}
babel.config.js
const presets = [
['@babel/preset-env'],
['@babel/preset-react'],
]
const plugins = []
console.log(process.env.NODE_ENV, '<------')
// 依据当前的打包模式来决定plugins 的值
const isProduction = process.env.NODE_ENV === 'production'
if (!isProduction) {
plugins.push(['react-refresh/babel'])
}
module.exports = {
presets,
plugins
}
postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')
]
}
.browserslistrc
> 1%
last 2 version
not dead
42.代码拆分方式
代码分离:https://webpack.docschina.org/guides/code-splitting/
- 为什么在webpack.进行打包的时候如何对代码块进行打包输出的操作。会分2个步骤进行描述,
- 为什么由一个入口找到所有依赖再去打包到一个bundle中不合适?
- 在webpack中具体有哪几种方式可以帮助我们实现拆分打包的需求?
在首屏加载中,
代码的分离打包3种方式:
1. 第一种多入口的打包方式
修改webpack.common.js文件
entry: {
main1: './src/main1.js',
main2: './src/main2.js'
},
output: {
filename: 'js/[name].build.js',
path: resolveApp('./dist')
},
- 入口添加依赖 TerserWebpackPlugin
webpack.common.js
https://webpack.docschina.org/configuration/optimization/#optimizationminimizer
const TerserPlugin = require("terser-webpack-plugin");
const commonConfig = {
entry: {
// dependOn: 依赖的包
main1: { import: './src/main1.js', dependOn: 'shared' },
main2: { import: './src/main2.js', dependOn: 'shared' },
shared: ['lodash', 'jquery']
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
},
}
- splitChunks
官方讲解splitChunks配置参数:https://webpack.docschina.org/configuration/optimization/#optimizationsplitchunks
webpack.common.js
const commonConfig = {
entry: {
index: './src/index.js'
},
optimization: {
splitChunks: {
chunks: 'all' // async :处理异步导入。 all:不论是异步或者同步都可以处理。 默认:异步处理方式。initial:同步导入
}
},
}
- 以上最常见的方式是第3种
42splitChunks配置
splitChunks打包出来的文件命名方式有4种
- 1.xxx.bundle.js
- 2.xxx.venderos.js
- 3.xxxchunk.js
- 4.runtime.js
+ splitChunks: {
chunks: 'initial', // async initial all
minSize: 20000, // 如果包的大小达不到20000(20kb默认值),就不会进行分包处理
maxSize: 20000, // 默认值 0 。体积大于maxSize的值的包进行拆分,拆分的规则:是拆分出来的包的大小不能小于minSize值的大小。如果用默认值会有警告,一般设置和minSize一样。
minChunks: 1, // 我们的包如果要被拆分,至少被引用一次。minChunks的值如果大于1,:minSize和maxSize最好不要再设置了。会影响打包的结果。
cacheGroups: { // 对拆分的过程进行分组
syVendors: {
test: /[\\/]node_modules[\\/]/,
filename: 'js/[id]_vendor.js',
priority: -10, // 优先级的属性
},
default: {
minChunks: 1,
filename: 'js/syy_[id].js',
priority: -20,
}
}
}
43.import 动态导入配置
如何用动态导入的方式对我们的代码进行拆包的操作。
chunkIds:
output: {
filename: 'js/[name].bundle.js',
path: resolveApp('./dist'),
+ chunkFilename: 'js/chunk_[name].js'
},
optimization: {
// 当前文件的名称是按自然数进行编号排序,如果某个文件当前次不再被依赖那么重新打包时序号都会变
// 文件打包出来的命名规则。
+ chunkIds: 'deterministic', // 'natural','named' // 开发阶段设置为:named。生产阶段:deterministic
minimizer: [
new TerserPlugin({
extractComments: false,
}),
]
},
魔法注释语法:
// import ('./title') 改成下面的引入方法
+ import(/*webpackChunkName: "title"*/'./title')
44. runtimeChunk 优化配置
runtimeChunk:官方文档:https://webpack.docschina.org/configuration/optimization/#optimizationsplitchunks
output: {
- filename: 'js/[name].bundle.js',
+ filename: 'js/[name].[contenthash:8].bundle.js',
path: resolveApp('./dist'),
},
optimization: {
+ runtimeChunk: true, // false, single:a文件用到了c,b文件也用到了c,如果设置为true,那么都会打包出来一个c,如果设置了single,那就会只打包出来一个c
minimizer: [
new TerserPlugin({
extractComments: false,
}),
]
},
45.代码懒加载
- 基于动态导入的方式实现懒加载
- 按需加载:场景案例:首评页面只有点击某个按钮,才会加载这个js文件,如果没有点击不需要加载这个js文件。
// 这样在文件中导入,用不用都会加载。
// import ('./utils')
const oBtn = document.createElement('button')
oBtn.innerHTML = '点击加载元素'
document.body.appendChild(oBtn)
// 按需加载
oBtn.addEventListener('click', () => {
// 只有点击这个按钮才会加载这个文件。
+ import('./utils').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
46. prefetch 与 preload
官方描述:https://webpack.docschina.org/guides/code-splitting/#prefetchingpreloading-modules
在用户没有感知到情况下对将来可能要用到的文件进行加载。
用法:
const oBtn = document.createElement('button')
oBtn.innerHTML = '点击加载元素'
document.body.appendChild(oBtn)
// 按需加载
oBtn.addEventListener('click', () => {
import(
/*webpackChunkName:'utils' */
+ /*webpackPreLoad:true */
'./utils').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
- preload与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
47.第三方扩展设置 CDN
-
开发过程中为什么使用到cdn?cdn是什么?
cdn是一个内容分发网络,是一个整个的体系。有了这个cdn网络,可以让用户在请求资源的时候,首先以最快的速度找到离他最近的资源服务器,然后取回他想要的内容。这样的话我们就可以把用户想要的东西更快的发给他。体验高性能,扩展性强。
用户1 查找资源,服务器会从离他最近的地方开始查找,如果没有就去旁边的边缘节点查找,如果边缘节点也没有,就去父节点,父节点没有就去源节点,源节点如果有就拷贝一份到父节点,父节点-- >拷贝一份到边缘节点,最后返回给用户1。 -
在实际的开发中怎么去用cdn?
结合webpack用。如果我们自己有cdn服务器,那么我们完全可以把打包完的代码直接部署在cdn服务器上。cdn服务器要买,价格不一。开发阶段用免费别人放在服务器上的资源的就好了。
例如代码中用到了lodash库。
代码配置:
// 抽离第3方包依赖,不进行打包。
externals: {
lodash: '_' // _:表示对全局变量暴漏的包名,不能乱写。
},
在模版中进行引入cdn地址:
index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
+ <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<!-- built files will be auto injected -->
</body>
</html>
对cdn进行一些设置,从而对我们的项目做一些优化:
48.打包 Dll 库
-
如何使用 Dll 库对我们构建过程进行优化。
官方描述:https://webpack.docschina.org/plugins/dll-plugin/#dllplugin -
DllPlugin 和 DllReferencePlugin 用某种方法实现了拆分 bundles,同时还大幅度提升了构建的速度。“DLL” 一词代表微软最初引入的动态链接库。
-
案例:在react开发过程中:不想每次react库和raect-dom库都跟着入口文件一块打包,提前做成一个dll库,用到的时候直接找dll动态库就可以了。和cdn的思想类似。会生成2个文件分别是:dll_react.js,react.manifest.json。将来用的时候用.json文件,.json文件会找到.js的源文件。
-
提高打包的构建速度。
dll/webpack.config.js
const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: "production",
entry: {
// 把这两个包单独的打包出来。
react: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: 'dll_[name].js',
library: 'dll_[name]'
},
optimization: {
minimizer: [
// 去掉描述文件
new TerserPlugin({
extractComments: false
}),
],
},
plugins: [
new webpack.DllPlugin({
// 生成的dll包名
name: 'dll_[name]',
// 记录路径信息:manifest.json 文件的 绝对路径(输出文件)
path: path.resolve(__dirname, './dll/[name].manifest.json')
})
]
}
package.json
{
"name": "dll",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"dll": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"webpack": "^5.52.0",
"webpack-dev": "^1.1.1"
}
}
- 执行命令npm run dll
- 生成如下文件:
49.使用 Dll 库
webpack.common.js
const webpack = require('webpack')
// 只使用DllReferencePlugin,发现不能对模版文件进行填充。
// 对模版文件进行动态填充
+ const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
plugins: [
+ new webpack.DllReferencePlugin({
// context: 查找动态乱接库 .js文件
+ context: resolveApp('./'),
// manifest: 去哪个地方找文件的名称
+ manifest: resolveApp('./dll/dll/react.manifest.json')
+ }),
+ new AddAssetHtmlPlugin({
// 在打包出来的文件index.html中引用js的文件。如果设置为auto,着则不需要手动修改,如果使用js,则需要手动修改index.html引入的路径
+ outputPath: 'auto', // 'auto', 'js'
+ filepath: resolveApp('./dll/dll/dll_react.js')
+ })
]
打包出来的件index.html
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>
copyWebpackPlugin
</title>
+
+ <script defer="defer" src="auto/dll_react.js">
</script>
<script defer="defer" src="js/runtime~index.bundle.js">
</script>
<script defer="defer" src="js/index.bundle.js">
</script>
</head>
<body>
<noscript>
<strong>
We're sorry but copyWebpackPlugin doesn't work properly without JavaScript
enabled. Please enable it to continue.
</strong>
</noscript>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js">
</script>
</body>
</html>
- 如果每次都把react,react-dom都打包进去,那么对于时间消耗是非常大的。因此,我们想着把这些单独的抽离出来,做成一个独立的库文件,之后在其他的地方用到,我们就可以找这个文件进行使用。类似于cdn。我们在打包的时候,不需要进行单独的打包了。这就是为什么使用dll库。
- 现在react,vue已经移除了对dll的使用。
50 . CSS 抽离和压缩
抽离css为一个单独的文件,而不是在当前的文件的<style></style>
里面
-
css文件抽离为一个单独的文件: MiniCssExtractPlugin: webpack官方描述:https://webpack.docschina.org/plugins/mini-css-extract-plugin/#getting-started
-
抽离出来的css文件进行压缩:CssMinimizerWebpackPlugin: webpack官方描述: https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/#getting-started
配置:
webpack.config.js
// css文件抽离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+ const commonConfig = (isProduction) => {
+return {
rules: [
{
test: /\.css$/,
use: [
+ isProduction ? MiniCssExtractPlugin.loader :'style-loader' ,
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
]
}
}
module.exports = (env) => {
const isProduction = env.production
process.env.NODE_ENV = isProduction ? 'production' : 'development'
// 依据当前的打包模式来合并配置
const config = isProduction ? prodConfig : devConfig
+ const mergeConfig = merge(commonConfig(isProduction), config)
return mergeConfig
}
webpack.prod.js
// css抽离出来的文件压缩
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin(),
]
},
51. TerserPlugin 压缩 JS
压缩js文件:TerserWebpackPlugin: 官方描述: https://webpack.docschina.org/plugins/terser-webpack-plugin/
效果:
源码:
使用TerserPlugin
底层就是使用terser这个库。
如果是webpack5不需要安装,如果是webpack4需要安装。
52. scope hoisting(作用域的提升)
- 提升打包的性能
- 已经内置在prod模式
- ModuleConcatenationPlugin:官方描述:https://webpack.docschina.org/plugins/module-concatenation-plugin/
- 最好使用esmodule
53. usedExports 配置
性能提升。tree shaking(树摇:去除死代码)
- 1.tree shaking是什么?帮助我们解决了什么问题?
- 在一个文件中如果没有使用这个函数,那么不希望被打包进去,因此可以减少打包的体格和速度。极少成多,如果代码中很多这样的场景,那么整个性能也就得到了提升。
- 树摇:摇掉死(不用的)代码。
树摇
: 核心原理:基于esmodule的静态语法分析,就像是可以通过一个人对代码进行扫描,标记出未被使用的代码,在后期打包的时候去掉,不参与打包。只能对esmodule格式的代码起作用。- 2.在webpack中这针对tree shaking有什么样的实现方式?在webpack中有2种方式实现。1.usedExports。2.sideEffects
- 3.不同的实现方式具体的配置是怎么样的?
- usedExports 只是标记不被使用的函数,如果把标记的没用的代码去掉还要结合TerserPlugin一块使用。
- TerserPlugin:根据标记,摇掉没使用的代码。
- webpack.prod.js
usedExports配合TerserPlugin一块使用
module.exports = {
mode: 'development',
devtool: false,
optimization: {
// 只是标记不被使用的函数
+ usedExports: true,
+ minimize: true, // 必须开启,否则没办法摇掉。
+ minimizer: [
+ new TerserPlugin({
extractComments: false,
}),
]
},
}
- sideEffects (副作用)配置
- sideEffects:https://webpack.docschina.org/configuration/optimization/#optimizationsideeffects
配置:
package.json
{
"name": "awesome npm module",
"version": "1.0.0",
"sideEffects": true, // true:开启,false:不开启。[./src]:具体的文件
}
在某些导入的函数中可能是不被使用的,那么可以通过usedExports进行标记,通过TerserPlugin进行清除 但是这种情况并不能满足所有树摇的场景,例如:我们在react中编写代码的时候写了一些带有副作用的代码, 但是在打包的时候想清除掉,那么怎么办呢?我们可以通过sideEffects进行配置,在package.json中进行 添加就行了,最简单粗暴的就是:"sideEffects": false,所有模块的副作用都不要,但是有些文件的副作用是需要 保留的,那么怎么办呢?例如css文件,那么我们可以给sideEffects一个数组,通过数组指定不跳过哪些文件。针对 css我们给了一个单独的处理,直接在rules里面添加"sideEffects": true。
55.Css-TreeShaking
对css代码进行TreeShaking(删除不用的css )
- purgecss-webpack-plugin: https://www.npmjs.com/package/purgecss-webpack-plugin
- 剔除没有使用的css
webpack.prod.js
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const glob = require('glob')
plugins:[
new PurgeCSSPlugin({
// paths:路径
paths: glob.sync(`${resolveApp('./src')}/**/*`, { nodir: true }),
// safelist:安全列表。不会把'body', 'html', 'ef'剔除
safelist: function () {
return {
standard: ['body', 'html', 'ef']
}
}
})
]
56.资源压缩 - gzip
生产环境压缩:
http压缩:网络消耗带宽变小。从而节省性能。
CompressionWebpackPlugin: https://webpack.docschina.org/plugins/compression-webpack-plugin/
- http的压缩流程分几步?
webpack.prod.js
const CompressionPlugin = require("compression-webpack-plugin");
plugins: [
new CompressionPlugin({
test: /\.(css|js)$/, // 只压缩js/css文件,
minRatio: 0.8, // 最小压缩比例
threshold: 0,
algorithm: 'gzip' // 压缩的方式
})
],
- 经过这个压缩的可以部署在服务端。
57. inlineChunkHtmlPlugin 使用
webpack.prod.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var InlineChunkHtmlPlugin = require('inline-chunk-html-plugin');
plugins: [
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/])
]
58. webpack 打包 Library
59. 打包时间和内容分析
- 打包时间分析:SpeedMeasurePlugin: https://www.npmjs.com/package/speed-measure-webpack-plugin
webpack.common.js
// 时间分析
+ const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
+ const smp = new SpeedMeasurePlugin()
module.exports = (env) => {
+ return smp.wrap(mergeConfig)
}
package.json
"mini-css-extract-plugin": "^1.3.6",
打包时间:
- BundleAnalyzerPlugin: 官方描述: https://webpack.docschina.org/guides/code-splitting/#bundle-analysis
- webpack-bundle-analyzer:
webpack.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins:[
new BundleAnalyzerPlugin()
]
直接npm run build之后,会在默认的端口开启一个服务,自己打开一个浏览器窗口。
- 帮助我们定位性能消耗在了哪?知道了消耗在哪之后,具体的采用方式来解决。