web 1.0 : static website
web 2.0 : AJAX
web 3.0 : modern front-end | module | framework | sass | es 6 .....
问题: 模块开发支持问题 | 实时监听 热更新 | 打包 压缩 | 新特新使用 |=>webpack
上手
webpack 5.45.1 |"webpack": "^5.52.1",
webpack cli 4.7.2 |"webpack-cli": "^4.8.0"
live server |vscode extension
添加 type="module" 字段 => 浏览器(if support )可以识别 ESM 代码, 但 此时CJS => 无效
<script src="./src/index.js" type="module"></script>
使用webpack 打包后则 CJS 和 ESM 都可以正常执行=》被转换,
此时 type="module" 则不需要
默认入口: src/index.js
默认出口: dist/main.js
yarn webpack
<script src="./dist/main.js"></script>
配置 package.json
指定入口: yarn webpack --entry ./src/main.js
指定出口: yarn webpack --output-path ./build
//package.json
"scripts":{
"build": "yarn webpack --entry ./src/main.js --output-path ./build"
}
// node
yarn build
配置 webpack.config.js
⚠️ path: path.join(__dirname,'dist') // webpack 4 写法
entry 指定入口文件,相对路径
path { filename:指定输出文件名,path:绝对路径<require> ( require('path') ) }
// 默认: webpack.config.js
const path = require('path')
module.exports = {
entry:"./src/index.js", // 可以是相对路径
output:{
filename:"build.js", // 指定文件名
path: path.resolve(__dirname,'dist') // 必须是绝对路径
// path: path.join(__dirname,'dist') // webpack 4 写法
}
}
// node
yarn webpack
// package.json
"scripts":{
"build":"webpack --config lg.config.js" // 修改webpack配置文件路径
}
无法从入口出发找到的文件不会被webpack打包
Loader
1. what is loader: 模块|转换
2. loader 的使用
webpack4 中 loader 有3中使用方式, webpack5 中使用 行内 和配置文件方式
1. 行内loader
import "css-loader! ../css/login.css" //每一次导入都要写
2. 配置文件中 loader (推荐)
module.exports = { entry:"", output: { }, module:{ rules:[ { //方式1:较全方式 test:/\.css$/, use:[ { loader:"css-loader", // options:"", // 添加配置 // query:{} // 在webpack5 中 被合并到了options 里 } ] }, { //方式2 : 简写 test:/\.css$/, loader:"css-loader" }, { //方式3: 简写 test:/\.css$/, use:["css-loader"] } ] } }
3. 命令行中的loader (webpack5 中 CLI 方式被废弃)
css-loader + style-loader | 执行顺序问题
yarn add css-loader --dev |转换css文件|"css-loader": "^6.2.0",
yarn add style-loader --dev|展示css样式|"style-loader": "^3.2.1",
module.exports = { entry:"", output: { }, module:{ rules:[ { test:/\.css$/, use:["style-loader","css-loader"] //从后往前执行 } ] } }
less-loader
首先用 less-loader 将 less 文件转化为css文件
然后用 css-loader 将 css 文件转化为js代码
最后用 style-loader 将 js 代码 展示到浏览器
"css-loader": "^6.2.0",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"style-loader": "^3.2.1",// index.less @bgColor:seagreen; .title{ background-color:@bgColor; } //index.js import "./css/index.less"
// yarn add less --dev module.exports = { ... module:{ rules:[ { test:/\.less$/, use: ["style-loader","css-loader","less-loader"] } ] } ... }
配置 Browserslistrc
工程化:前端开发基于工程化 => 使用了CSS JS新特性
兼容性:浏览器 支持 CSS JS 新特性 问题
哪些浏览器平台需要兼容:项目指明的平台 | 市面主流平台 caniuse.com
常见配置: >1% | default |last 2 versions|not dead|
配置位置: 1. package.json | 2. .browserslistrc
由此处更新:
postcss 的工作流程
基于browserlistrc 的配置 做兼容
1. postcss : 利用JavaScript转换 样式 的工具
2. less(less-loader) -> css ->css-loader
yarn add postcss --dev | "postcss": "^8.3.6", | 工具
yarn add postcss-cli --dev | "postcss-cli": "^8.3.1", |命令行工具
yarn add autoprefixer --dev |"autoprefixer": "^10.3.1",|添加前缀包
yarn postcss --use autoprefixer -o ret.css ./src/css/test.css
--use <postcss 使用的包>|-o < 输出文件 + 输入文件地址 >
Autoprefixer CSS online | 检查浏览器 CSS 前缀
postcss-loader
postcss.config.js | webpack.config.js 可以在这两个文件中配置
postcss-loader 调用 postcss ==》postcss 调用工具包 (postcss-preset-env)
yarn add postcss-loader --dev | "postcss-loader": "^6.1.1",
yarn add postcss-preset-env --dev | "postcss-preset-env": "^6.7.0", | 插件集合module.exports = { ... modules: [ { test: /\.less$/, use: ['style-loader', 'css-loader', // 'postcss-loader', // 添加配置文件后的写法 { loader:'postcss-loader', postcssOptions: { plugins:['postcss-preset-env'] // 写法1 // plugins:[ require('autoprefixer') ] // 写法2 } }, 'less-loader' ] } ] ... }
// postcss.config.js // 不可改名 module.exports = { plugins:[ require('postcss-preset-env') ] }
importLoaders 属性
向后推 loaders 处理顺序
问题实例: @import test.css 中有需要 postcss 处理的 代码。
思路流程:当postcss-loader 运行结束后交给css-loader,而css-loader 可以识别@import |media |url 等语句, css-loader 运行后会继续向前。 导致 test.css 里文件并没有被处理。
解决办法:在css-loader中添加 importLoaders 属性 处理未解析的css 文件
test:/\.css$/ use:[ 'style-loader', { loader:'css-loader', options:[ importLoaders: 1 ] }, 'postcss-loader' ]
file-loader
yarn add file-loader --dev | "file-loader": "^6.2.0",
处理图片| 将图片当作模块对待
img src|background url|
// 前置准备 function packImg(){ const ele = document.createElement('div') // 创建一个容器 const img = document.createElement('img') // 创建一个图片 img.src = require('../img/icon.png').default // 设置图片src ole.appendChild(img) // 将img 普鹿梗 进 ele return ele // 返回容器 } document.body.appendChild(packImg()) // 将 ele 普鹿梗 进 body // 关于image 导入 再webpack5 中的问题 //********** 方式1 // img.src = require('../img/icon.png') // 将图片当作模块使用 img.src = require('../img/icon.png').default // webpack5中返回的是一个ESModule对象 // webpack4 中不需要添加 //********** 方式2 // 在 file-loader 里 添加 esModule: false 属性 options:{ esModule: false } //********** 方式3 import image from '../img/icon.png' // 使用ESModule的方式导入
module.exports = { ... module:{ rules:[ test: /\(png|svg|gif|jpe?g)$/, use: [ 'file-loader'] ] } ... }
关于 background url 的问题:
在css 处理css 文件是遇到 url ('../img/icon.png') 时会转化为 require('../img/icon.png') 语法
而 require 语法 返回的 是一个 ESModule 对象,
解决办法是:css-loader 中 添加 esModule: false
修改产出图片名
占位符:|ext|name|hash|contentHash|hash:8|
module.exports = { ... module:{ rules:[ test: /\(png|svg|gif|jpe?g)$/, use: [ { loader: 'file-loader', options: { name: 'img/[name].[hash:6].[ext]', // output-path: 'img' } } ] ] } ... }
url-loader
"url-loader": "^4.1.1",
url-lloader:减少请求次数|适合小文件 | 以base64 uri 方式 加入文件
file-lloader:直接拷贝文件
url-loader 内部可以直接调用 file-loader
通过limit 选择 url-loader 还是 file-loader
module.exports = { ... module:{ rules:[ { test: /\(png|svg|gif|jpe?g)$/, use: [ { loader: 'url-loader', options: { name: 'img/[name].[hash:6].[ext]', // output-path: 'img' limit: 25 * 1024 } } ] } ] } ... }
asset 处理图片 - 资源类型模块(webpack 内置 | webpack 5 特有)
配置选项:
asset/resource = file-loader | 拷贝资源 / 配置文件名 和 输出路径
asset/inline = url-loader
asset/source = raw-loader
asset (parser dataurlconditon maxsize) = 设置 limit
module.exports = { output:{ assetModuleFilename: "img/[name].[hash:6][ext]" // 方式1:全局指定导出 }, modules:{ rules:[ // asset/resource | asset/inline { test:/\.(png|svg|gif|gpe?g)$/, type:'asset/resource', // type: 'asset/inline', // 不需要导出文件 generator:{ filename:"img/[name].[hash:6][ext]" //方式2:只将图片导出到img里 } }, // asset { test:/\.(png|svg|gif|gpe?g)$/, type:'asset', generator:{ filename:"img/[name].[hash:6][ext]" }, parser:{ dataUrlCondition:{ maxSize: 30 * 1024 } } } ] } ... }
asset 处理图标字体
asset/resource
@font-face { font-family: "iconfont"; /* Project id 2250626 */ src: url('iconfont.woff2?t=1628066777598') format('woff2'), url('iconfont.woff?t=1628066777598') format('woff'), url('iconfont.ttf?t=1628066777598') format('truetype'); } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-linggan:before { content: "\e602"; }
//前置工作 import '../font/iconfont.css' import '../css/index.css' function packFont() { const oEle = document.createElement('div') const oSpan = document.createElement('span') oSpan.className = 'iconfont icon-linggan lg-icon' oEle.appendChild(oSpan) return oEle } document.body.appendChild(packFont())
module.exports = { ... module: { rules:[ { test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1, esModule: false } }, 'postcss-loader' ] }, { test: /\.(ttf|woff2?)$/, type: 'asset/resource', // 直接拷贝 generator: { filename: 'font/[name].[hash:3][ext]' } } ] } ... }
loaders VS plugins
loader:对模块 转换 读取资源时
plugin:更多功能 | 更多工作时间
clean-webpack-plugin
版本: "clean-webpack-plugin": "^4.0.0-alpha.0" | 解构
const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { ... plugins:[ new CleanWebpackPlugin() ] ... }
html-webpack-plugin
版本: "html-webpack-plugin": "^5.3.2",
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { ... plugins: [ new HtmlWebpackPlugin({ title: 'html-webpack-plugin' template:'./public/index.html' }) ] ... } // 无参数: 建立基础html文件, default title is: webpack app, 通过参数title修改 // 注入 生成的js 文件,添加defer 属性 // 模版参数: 自定义模版html,通过 EJS <%= htmlWebpackPlugin.options.title %> 添加数据 // 添加常量数据: 接下一段 DefinePlugin (webpack 自带的插件)
copy-webpack-plugin
arguments - patterns
版本: "copy-webpack-plugin": "^9.0.1",const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { ... plugins: [ new CopyWebpackPlugin({ patterns: [ { from:'public', to:'./dist', // 可省略 ignore:[ '**/index.html'] //注意public和dist下的重复文件 } ] }) ] ... } //**** ignore:['**/index.html'] // webpack 5 需要添加,如果 public下有index.html // html-webpack-plugins已经在dist下生成了index.html // **/ 意味着从public下找
define-webpack-plugin
webpack 内置 plugin
const DefinePlugin = require('webpack') module.exports = { ... plugins:[ new DefinePlugin({ BASE_URL: '"./"' // 注意这里两次引号的使用 }) ] ... }
Babel
Babel 是 一个工具| 本身不具备任何功能
"@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", |插件集合npx babel src --out-dir build --plugins=@babel/plugin-transfrom-arrow-functions,@babel/plugin-transfrom-block-scoping npx babel 输入 --out-dir 输出 --plugins=插件,插件
Babel-loader
"babel-loader": "^8.2.2", | 类似于babel/core:不转换
"@babel/plugin-transform-arrow-functions": "^7.14.5", |箭头函数转换
"@babel/plugin-transform-block-scoping": "^7.14.5", |作用域转换
"@babel/preset-env": "^7.15.0", |插件集合{ test:/\.js$/, use:[ { loader:'babel-loader', options:{ //plugins:[ //'@babel/plugin-transform-arrow-function', //'@babel/plugin-transform-block-scoping', //] presets: ['@babel/preset-env'] } } ] }
查看 .browserslistrc | 针对需要转换的浏览器支持进行转换 (次)(推荐)(其他也可用)
或者添加配置 来设置转换目标支持的浏览器(主)(仅仅对babel 起效)
presets: [ ['@babel/preset-env',{targets:"chrome 91"}] ]
babel-loader 配置文件
第一种:babel.config.js(json cjs mjs)|多包管理 (推荐)(示例)
第二种:.babelrc.json(js)|babel 7 之前|babel每个功能对应一个仓库|
// babel.config.js module.exports = { presets: ['@babel/preset-env'] }
//webpack.config.js { test:/\.js$/, use:['babel-loader'] }
polyfill
ployfill 是什么: 对于preset-env中不支持的转换做填充,例如:promise
在 webpack 4 中 默认添加,产出较大|webpack5 中 移除 > 优化打包速度, 按需添加
yarn add @babel/polyfill@7.12.1 --dev(开发依赖) | --save(生产依赖)(推荐)
core-js 3.16.0
regenerator-runtime 0.13.9
useBuildIns:false | usage
corejs:3
| entry +
2核心包 index.js
(import "core/js/stable")
(import "regenerator-runtime/runtime")
// babel.config.js module.exports = { presets: [ ['@babel/preset-env', { // useBuildIns:false // 默认 // useBuildIns:entry // 依据.browserlistrc中的表填充(浏览器需要的都填充,即使代码中 没有) useBuildIns:usage, // 对于preset-env 不能转换 但 源代码中使用的新语法 做填充 corejs:3 // 默认使用的是corejs 2 // 安装几版本就是几 } ] ] }
// webpack.config.js { test:/\.js$/, exclude:/node_modules/, // 不转换第三方的包 use:["babel-loader"] }
webpack-dev-server
方式1: 观察模式 + live server |所有源代码重新编译|磁盘交互|
观察模式:方式1: yarn webpack --watch
方式2: module.exports = { watch: true } |默认false
方式2: webpack-dev-server
版本:"webpack-dev-server": "^3.11.2"
yarn webpack serve | webpack 5 // yarn webpack-dev-server --open | webpack 4 yarn webpack serve --config lg.webpack.js
webpack-dev-middleware
express | 开启服务器
webpack-dev-middleware |wrapper|拿取webpack打包结果|传递给服务器
yarn add express webpack-dev-middleware --dev
// server.js const express = require("express") const webpackDevMiddleware = require("webpack-dev-middleware") const webpack = require("webpack") const app = express() // 开启一个服务器 // webpack 打包 : 获取配置文件 const config = require("webpack.config.js") const compiler = webpack(config) app.use(webpackDevMiddleware(compiler)) // 开启端口 app.listen(3000, ()=>{ console.log("服务器开启运行在3000") })
node .\server.js
HMR
webpack.config.js
// webpack.config.js module.exports = { mode: "development", | 在开发阶段 与.browserslistrc 冲突 target:"web", | 解决冲突办法|屏蔽.browserlistrc ... devServer:{ |热更新功能开启 hot: true |热更新功能开启 } ... }
index.js
在入口文件制定需要HMR的模块
import "./title.js" if(module.hot){ // 源码 module.hot.accept( // 源码 ["./title.js"], // 指定热更新模块 ()=>{ console.log("title.js is updated") } // 可以添加回调函数 ) }
HMR - React
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3","react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh": "^0.10.0",//react import React, {Component} from 'react' class App extends Component{ constructor(props){ super(props) this.state = { title: '前端 33' } } render(){ return ( <div> <h2>{this.state.title}</h2> </div> ) } } export default App
import './title' import React from 'react' import ReactDOM from 'react-dom' import App from './App.jsx' // webpack.config.js 配置 if (module.hot) { module.hot.accept(['./title.js'], () => { console.log('title.js模块更新') }) } ReactDOM.render(<App />, document.getElementById('app'))
//babel.config.js module.exports = { presets:[ ['@babel/preset-env'], // 识别常见es6语法 ['@babel/preset-react'] // 识别jsx代码 ], plugins:[ ['react-refresh/babel'] // 搭配ReactRefreshWebpackPlugin使用 ] }
const ReactRefreshWebpackPlugin = require(@pmmmwh/react-refresh-webpack-plugin) target: web, devServer:{ hot: true }, ... { test:/\.jsx?$/, use:['babel-loader'] }
HMR - VUE
使用vue-loader
"vue-loader": "^15.9.8", // vue 3 使用 vue-loader@16, vue2 使用vue-loader@15/14
vue@2 + vue-loader@14
// index.js |入口文件 import Vue from 'vue' import App from './App.vue' //if(module.hot){ //... // js HMR //} new Vue({ render: h => h(app) }).$mount('#root')
const VueLoaderPlugin = require('vue-loader/lib/plugin') ... { test:/\.vue$/, use:['vue-loader'] // vue 2 用 vue-loader@14 } ... plugins:[ new VueLoaderPlugin() ] // vue-loader@15 以后 需要手动添加VueLoaderPlugin
<template> <div class="example">{{ msg }}</div> </template> <script> export default { data() { return { msg: 'Hello world! 222', } }, } </script> <style> .example { color: orange; } </style>
output - path
output:{ filename:'js/main.js' path: path.resolve(__dirname,'dist'), // path:打包输出路径,本地静态资源访问 // publicPath:开启本地服务时访问本地文件 publicPath:'./' |1. 默认 '' |2. 绝对路径 '/' // 本地浏览无法访问,devServer可以 |3. 相对路径 './' // 本地可以访问, devServer 不可以 } http://域名 + publicPath + filename 形式访问 如果 publicPath为空 则: 默认添加 / :http://localhost8080/js.main.js
devServer - publicPath | path | contentBase | watchContentBase
devServer - publicPath 和 output - publicPath 保持一致
devServer - publicPath:如:告知express, 资源存在哪里
contentbase: 打包后的资源 依赖了 未被打包的文件, 因此publicPath 无法找到(绝对路径)
contentBase: path.resolve(__dirname,'public')
index.html 中 <script src="/utils.js"></script>
watchContentBase: true // 为contentbase 开启HMR
devServer - hotonly:true
修改报错后, 不刷新页面,只做热更新
devServer - histroyApiFallback:true
当请求404时,则找index.html.single page web application 只有index.html 返回,
因此刷新后的可能出现其他页面404. 所以使用 histroyApiFallback:true
devServer - proxy
本地开启服务 https://localhost:8080/index.html 中 需要其他数据, 而这些数据在其他端口上, 如:api.com/users. 此时可能会出现 跨域问题 CORS. ==> 通过代理proxy 解决
devServer:{ proxy:{ // /api/users |简写了 http://localhost:4000|浏览器会自动补充 | index.html // http://localhost:4000/api/usrs |通过localhost 发送请求 // 设置 'api' 后,结果为:https://api.github.com/api/users '/api':{ target:'https://api.github.com', pathRewrite:{ // "^/api": "" // 若:请求接口为 https://api.github.com/users "^/api": "info" // 若:请求接口为 https://api.github.com/info/users }, changeOrigin:true // 这样对于GitHub 来说, 属于同源请求。 } } }
configration - resolve
resolve.extensions |默认文件名
resolve: { extensions: ['.js', '.json', '.wasm'], },
当 导入文件没有文件名时,从该配置中找。 如:
import Home from "./components/Home" // 此时 home 在文件结构中为一个文件,但是并没有文件名后缀。 则从提供的extension中找,并添加,如果重名,以第一个为准
resolve.mainFiles
若导入文件夹后为写明文件, 则默认index,然后再从 extensions里找后缀补全。如:
import About from "./components/"
resolve: { mainFiles: ['index'], }, // 此时找的是 compoents下的index.js
resolve.alias | 修改别名
resolve: { alias: { '@': path.resolve(__dirname, 'src') },
import About from "./components/" import About from "@/components/"
devtool.source-map
为什么需要sourcemap, 在开发中遇到报错时,能够定位源码中的错误。
mode:"development" 模式下 默认是 devtool: "eval" 模式, short for evaluate
devtool: "source-map"
最佳实践
开发模式下: cheap-module-eval-source-map
发布前打包: none : source map 会暴露源代码
ts-loader vs babel-loader[preset-typescript]
ts-loader@9.2.5
npm i typescript -D
{ test:/\.ts$/, use:['ts-loader'] }
ts-loader 在编译阶段 可以 校验出 语法错误|不能做polyfill 填充
babel-loader[preset-typescript] 在编译阶段 有时候不可以 校验出 语法错误,运行阶段方可
搭配: yarn tsc --noEmit 检测语法错误
"build":"npm run check && webpack" "check":"tsc --noEmit"
// babel.config.js module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }], ['@babel/preset-typescript'] ] }
加载 vue 文件
打包环境 区分 合并
"build":"webpack --config ./conifg/webpack.common.js --env production"
"serve":"webpack serve --config ./conifg/webpack.common.js --env development"
// 配置信息可以通过参数获取
module.exports = (env)=>{
const isProduction = env.production
return {
// module.exports里的对象全部替换在此
}
}
1. 命令行 - 环境配置
2. 分离配置文件: 运行环境通过module.exports = (env) =>{} 参数获取
3. 文件路径问题: 运行 webpack.common.js 时,__dirname 获取的时conifg文件夹,
path: path.resolve(__dirname, '../dist') // __dirname: config目录,拼接: 退后一层= 根, 接dist
运行 "build":"webpack --config ./conifg/webpack.common.js --env production" 时工作环境时config所在的目录 = 项目目录
yarn add webpack-merge
const { merge } = require("webpack-merge")
webpack-merge
return merge( commonConfig,config) 合并的是对象
根据 模式来判断 对 babel.config.js 文件的处理, 关键是:如何获取 当前模式
在 webpack 文件中 设置process.env.NODE_ENV = .... 来设置环境配置,
在 babel.config.js 中 获取环境配置的值,从而进行不同的操作
代码拆分打包(依赖)- 3 methods - 主流:optimization.splitChunks/chunks
const TerserPlugin = require('terser-webpack-plugin') // 不需要安装 直接导入
...
entry:{
// 方式1: 多入口
main1: "./src/main1.js", // main1. main2 在自定义输出文件名时可以用到
main2: "./src/main2.js"
// 方式2: 多入口 + 依赖 |共同依赖
main1: {import:"./src/main1.js", dependOn:"lodash"},
main2: {import:"./src/main2.js", dependOn:"lodash"},
lodash:"lodash"
shared:["lodash, jquery"]
// 方式3: 常见/单入口 + optimization - splitChunks:{chunks: "all"}
main3: './src/index.js'
}
output: {
filename:'js/[name].js' // 自定义文件名
}
optimization:{
minimizer:[
new TerserPlugin({
extractComments:false, // 不导出注释文件txt
})
],
splitChunks:{
chunks: "all" // 拆分依赖, 在index.js中导入的jquery会被单独打包出来
// default: async
}
}
optimization.splitChunks 配置(基于同步模式拆分的前提 )
SplitChunksPlugin | webpack 中文文档
bundles vs chunks: |bundle=> 交给 html 使用| chunks => 依赖的包|
动态导入文件默认单独打包
output:{
chunkFilename:'js/chunk_[name].js'
}
// [name] 可以通过动态导入时的魔法注释获取。
// 如: import(/*webpackChunkName:"title"*/ './title')
// 导出结果为: js/chunk_title.js
optimization:{
chunkIds: 'natural', // 问题: 当有一个包不打包了, 所有id都变了, 影响缓存
...
}
natural | 按使用顺序的数字 id。 |
named | 对调试更友好的可读的 id。 |
deterministic | 被哈希转化成的小位数值模块名。生产模式下 建议使用 |
chunks: async (默认)|initial | all minSize: 20000(默认|如果拆分后的包大小 小于20kb 则 不会单独拆开) minChunks:1 // 如果一个包需要被拆分 至少要被使用一次 cacheGroups:{ // 第三方包统一打包规则 syVendors:{ test: /[\\/]node_modules[\\/]/, filename:'js/[id]_vendor.js', priority:-10 } default:{ // 打包默认规则: chunks, minSize,minChunks 都可以在这里设置 priority:-20 // 当 syvendors 和 default 规则同时符合,优先级高的规则执行 } }
optimization.runtimeChunk
runtimeChunk: true(默认false),
处理模块加载规则的代码会被 单独提取=> 利于浏览器缓存
optimization: { runtimeChunk: true, }
代码懒加载
首次加载时不需要的文件, 在运行时才需要
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)
})
})
prefetch + preload(魔法注释+懒加载+子文件中)
prefetch: 浏览器加载完 当前页面后, 空闲时:加载以后可能会遇到的代码。
preload:与当前chunk 并行下载
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)
})
})
第三方扩展设置CDN
1. 不参与webpack打包
module.exports = {
//...
externals: { // 不让lodash参与打包
lodash: '_' // 语法: 不希望被打包的包名:"包对外暴露的全局变量名"
},
};
2. 去官网找src link ,插入需要的html文件
<script src="https://cdn...../lodash.js"></script>
DLL库(动态链接库 dynamic link library)
好处: 不需要频繁打包的不重复打包,跨项目使用
第一部分: 打包
拆分bundles,将重复使用且不常变动的bundle 提取出来单独打包,配合一个json文件。
const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: "production",
entry: {
react: ['react', 'react-dom']
},
output: { // 生成.js 文件
path: path.resolve(__dirname, 'dll'),
filename: 'dll_[name].js',
library: 'dll_[name]'
},
optimization: { // 去除txt文件
minimizer: [
new TerserPlugin({
extractComments: false
}),
],
},
plugins: [
new webpack.DllPlugin({ // 生成配套json 文件用于查找源代码
name: 'dll_[name]',
path: path.resolve(__dirname, './dll/[name].manifest.json')
})
]
}
第二部分: 使用
new webpack.DllReferencePlugin({context:路径<dll folder>, manifest:路径<manifest.json>})
"add-asset-html-webpack-plugin": "^3.2.0",
// resolveApp 模块 => 绝对路径拼接
const path = require('path')
const appDir = process.cwd()
const resolveApp = (relativePath) => {
return path.resolve(appDir, relativePath)
}
module.exports = resolveApp
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
...
plugins: [
new webpack.DllReferencePlugin({
// 都是绝对路径
context: resolveApp('./'), // 相对于manifest如何查找到dll_react.js文件
manifest: resolveApp('./dll/react.manifest.json') // 查找 manifest.json 文件
}),
new AddAssetHtmlPlugin({ // 引入静态资源之中
outputPath: 'js', // 需要手动更改, html中也是(小问题)
filepath: resolveApp('./dll/dll_react.js')
})
]
<script src="js/dll_react.js"></script>
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'
ReactDOM.render(<App />, document.getElementById('app'))
CSS 抽离 与压缩
在production 模式下进行 css 抽离 和压缩, 开发模式下不需要。
CssMinimizerWebpackPlugin | "css-minimizer-webpack-plugin": "^3.0.2",
CssMinimizerWebpackPlugin.loader 搭配 css-loader 使用
MiniCssExtractPlugin | "mini-css-extract-plugin": "^2.2.0",
npm install --save-dev mini-css-extract-plugin
$ npm install css-minimizer-webpack-plugin --save-dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin') module.exports = { ... plugins:[ new MiniCssExtractPlugin({ filename:'css/[name].[hash:6].css' }) ] ... module:{ rules:[ { test:/\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] // 开发模式下用的style-loader, 如果分开模式打包, 可以通过函数来判断时开发还是生产模式, 选择相对的loader。 use:[ isProduction ? MiniCssExtractPlugin.loader : style-loader, 'css-loader' ] } ] }, optimization:{ minimizer:[ new CssMinimizerWebpackPlugin() ] } }
TerserPlugin 压缩 JS
GitHub - terser/terser: 🗜 JavaScript parser, mangler and compressor toolkit for ES6+
基于teser, teser 是一个工具,用于优化JavaScript。
webpack5 内置,不需要安装, webpack4 需要安装|"terser": "^5.7.1",
主要有两个功能 mangler/compressor
terser --compress --input.js // compress options
terser -- mangler --input.js // minify options
const TerserPlugin = require("terser-webpack-plugin")
module.exports = {
mode:"production",
optimization: {
minimize: true, // 需要为true, terser plugin 才生效
minimizer: [
new TerserPlugin({
extractComments: false
})
]
}
}
scope hoisting 作用域提升
ModuleConcatenationPlugin,基于 ESModules 规范
将合适的数据打包入同一个层级,方便查找
production 模式下该功能默认开启。
//const webpack = require('webpack')
module.exports = {
mode: 'production',
plugins: [
// new webpack.optimize.ModuleConcatenationPlugin()
]
}
tree shaking
tree shaking 解决了:去除无用代码
核心原理: 基于ESModules的静态语法分析
webpack 中两种常见的tree shaking 方式:usedExports |sideEffects
usedExports 配置
?production模式下默认产出的就是tree shaking 后的结果吗?
usedExports:true // 标记未使用的代码
terserPlugin // 去除未使用的代码, 而 terser plugin 要搭配 minimize:true 使用
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
mode: 'development',
devtool: false,
optimization: {
usedExports: true, // 标记未使用
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
]
}
}
sideEfeects 配置
// package.json
"sideEffects": [
"./src/title.js" // 单独保留副作用
],
"sideEffects": true | false // 全部保留 | 去除
rules: [
{
test: /\.css$/,
use: [
...
],
sideEffects: true, // 对于css 的副作用, 推荐在webpack配置文件中设置
},
]
Css-TreeShaking
purgecss-webpack-plugin | "purgecss-webpack-plugin": "^4.0.3",
glob | "glob": "^7.1.7",
const PurgeCSSPlugin = require('purgecss-webpack-plugin') const resolveApp = require('./paths') const glob = require('glob') module.exports = { mode: 'development', devtool: false, plugins: [ new PurgeCSSPlugin({ paths: glob.sync(`${resolveApp('./src')}/**/*`, { nodir: true }), //绝对路径|不包括文件夹 safelist: function () { return { standard: ['body', 'html', 'ef'] // 不会被摇掉的选择器 } } }) ] }
资源压缩
上线前的压缩输出
"compression-webpack-plugin": "^8.0.1",
const CompressionPlugin = require("compression-webpack-plugin") module.exports = { mode: 'production', plugins: [ new CompressionPlugin({ test: /\.(css|js)$/,// 压缩什么类型的文件 minRatio: 0.8, // 压缩比例超过20% 则输出 threshold: 0, // 文件大于多少 则 压缩 algorithm: 'gzip' // 指定算法 }) ] }
InlineChunkHtmlPlugin
配合htmlWebpackPlugin 使用
"inline-chunk-html-plugin": "^1.1.1",
"html-webpack-plugin": "^5.3.2",将通过<script></script>注入html的文件,通过inline方式注入,从而不需要进行一次请求。
const InlineChunkHtmlPlugin = require('inline-chunk-html-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: "production", plugins:[ new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/]) ] }