1. 引入类
1.1 webpack安装
npm install --save-dev webpack
npm install --save-dev webpack-cli
npm install --save-dev webpack-dev-server
entry: {
main: './src/index.ts'
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[contentHash],js',
chunkFilename: "[id].[contentHash],js"
},
// package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.common.js",
"build": "webpack --env.production --config webpack.common.js"
},
1.2. loader
将不同类型的文件进行不同形式的处理
1.2.1 CSS相关
mini-css-extract-plugin
将样式文件单独打包成一个css
文件
style-loader
将样式通过<style>
注入页面
css-loader
解析打包css
文件
sass-loader
解析打包sass
文件
autoprefixer
是postcss-loader
的插件,为样式添加前缀兼容浏览器
// 二选一
npm install --save-dev mini-css-extract-plugin // 生产环境
npm install --save-dev style-loader // 开发环境
npm install --save-dev css-loader
npm install --save-dev sass-loader
npm install --save-dev postcss-loader
npm install --save-dev autoprefixer
module: {
rules: [{
test: /\.scss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader', options: { importLoaders: 2 }},
'sass-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')
]
}
}
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contentHash].css', // 修改直接引入的CSS文件名
chunkFilename: '[id].[contentHash].css', // 间接(css中@import)引入的文件名
})
]
兼容浏览器需要在package.json
中添加兼容的版本号
"browserslist": [
"iOS >= 6",
"Android >= 4",
"IE >= 9"
]
1.2.2 JS相关
typescript ts-loader
使用ts
并解析ts
文件
注:ts配置文件需要同时配置include和exclude,如果只配exclude,ts还是会检查exclude文件夹。如使用html-webpack-plugin插件且webpack入口文件是ts文件时,打包会报错,报错显示html-webpack-plugin插件内有不满足ts的语法
@babel/core @babel/cli
安装babel
@babel/preset-env
进行JS
语法转换
@babel/polyfill
进行JS
代码垫片注入
npm install --save-dev typescript ts-loader
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
配置.babelrc
文件
// .babelrc
{
"presets": [["@babel/preset-env", {
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage" // 会自动注入polyfill无需手动注入
}]]
}
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
"babel-loader",
'ts-loader'
],
},
1.2.3 文件相关
都用于打包文件,区别在于url-loader
可以将大小低于限定值的文件转成base64
npm install --save-dev url-loader
npm install --save-dev file-loader
{
test: /\.(jpg|png|gif)$/,
use: {
loader: "url-loader",
options: {
name: '[path][name].[ext]?[hash]',
outputPath: 'images/',
limit: 204800
}
}
}
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
1.3 插件
1.3.1 dist目录
html-webpack-plugin
在打包完成后为dist
目录生成index.html
文件
clean-webpack-plugin
在打包前删除dist
目录中的文件
两个插件会自动匹配webpack的output配置
npm install --save-dev html-webpack-plugin
npm install --save-dev clean-webpack-plugin
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'src/index.html')
})
],
1.3.2 打包分析
webpack-bundle-analyzer
一种打包分析插件
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins: [new BundleAnalyzerPlugin()]
1.3.3 打包界面优化
FriendlyErrorsWebpackPlugin
优化打包界面,使控制台看起来舒服
npm install --save-dev friendly-errors-webpack-plugin
引入同上
1.3.4 打包性能优化
原理:将第三方模块提前打包,在项目中引入,以后打包便不再分析打包第三方模块,从而提高打包性能。
add-asset-html-webpack-plugin
向html-webpack-plugin
插件导出的index.html
文件中引入其他的js
文件
webpack.DllPlugin
通过暴露的全局变量,生成manifest
映射文件
webpack.DllReferencePlugin
将已打包好的代码与第三方包进行映射。在项目中使用第三方模块的方式不变,webpack
会自动将包映射到已打包好的代码中获取资源
npm install --save-dev add-asset-html-webpack-plugin
- 配置
webpack.dll.js
和package.json
// webpack.dll.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: "production",
entry: {
lodash: ['lodash'],
moment: ['moment']
},
output: {
filename: "[name].dll.js",
path: path.resolve(__dirname, '../dll'),
library: '[name]' // 通过全局变量形式暴露代码
},
plugins: [
new webpack.DllPlugin({
name: '[name]', // 对暴露的全局变量进行分析,生成manifest映射文件
path: path.resolve(__dirname, '../dll/[name].manifest.json')
})
]
}
// package.json
"scripts": {
"build:dll": "webpack --config ./build/webpack.dll.js"
},
- 引入插件并配置
// webpack.common.js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
files.forEach((file) => { // 循环将插件导入
if (/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/', file)
}))
}
if (/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll/', file)
}))
}
})
2. 集成类
2.1 sourceMap
将打包文件映射到源代码中,便于调试
devtool: "cheap-module-eval-source-map", // 开发环境使用
devtool: "cheap-module-source-map", // 生产环境使用
devtool: "none", // 关闭sourceMap
2.2 热更新
修改代码后实时更新页面
devServer: {
contentBase: './dist', // 运行目录
open: true, // 在运行打包命令时自动打开浏览器并显示页面
port: 8081, // 端口配置
}
2.3 tree shaking
不打包代码中未使用的代码
// package.json
"sideEffects": ["*.scss"] // 配置不使用的文件
ts
配置了生产环境也无法适用,第三方包是否支持也无法控制
mode: 'production' // 生产环境自动使用
2.4 代码分割
将代码打包成多个文件便于并行加载
代码更新后只需更新业务代码
同步代码分割:
SplitChunksPlugin
已内置,无需安装
splitChunks: {
chunks: "all",
cacheGroups: { // 用于cache
vendor: {
test: /[\\/]node_modules[\\/]/, // 在node_modules文件夹下的文件
name: 'vendors', // 打包后的文件名
chunks: 'all', // 所有的chunk打包成一个文件
}
}
}
异步代码分割:
无需配置,当异步引入时webpack
会自动分割
使用场景:懒加载
注:如无需代码转译,可略过如下配置
babel-plugin-dynamic-import-webpack
用于代码异步加载后使用babel
转译
npm install --save-dev babel-plugin-dynamic-import-webpack
// .babelrc
"plugins": ["babel-plugin-dynamic-import-webpack"]
2.5 懒加载
在需要使用时再加载代码,配合prefetch
使用更好
webpackPrefetch
浏览器空闲后下载
webpackPreload
和其他资源一起下载
document.addEventListener('click', () => {
import( /* webpackPrefetch: true */ './script/compute.js').then(({ default: fn }) => {
console.log(fn(1, 2))
})
})
上面代码没卵用,不知道为什么,还是只有点击后才会加载,网上找遍了没找到解决方法
2.6 Shimming
全局导入某个模块
const webpack = require('webpack')
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
})
],
2.7 路径
简化项目中路径的使用
resolve: {
extensions: ['.tsx', '.ts', '.js'], // 从左往右匹配后缀
alias: {
'@': path.resolve(__dirname,'src') // 规定符号路径代表的根路径
}
}
3. 环境类
3.1 环境变量
在1.1 webpack安装中
,命令--env.production
会将evn.production
设置为true
,即可在环境构建中使用
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js"
},
3.2 环境构建
webpack-merge
用于将webpack
配置项合并
npm install --save-dev webpack-merge
创建三个js
文件:
webpack.common.js
:公用部分
entry
配置resolve
配置- 除
css
以外的loader
配置 dist
目录配置Shimming
配置- 代码分割配置
- 导出配置
module.exports = (env) => {
if (env && env.production) {
return merge(commonConfig, proConfig)
} else {
return merge(commonConfig, devConfig)
}
}
webpack.dev.js
:开发环境
mode
配置devtool(sourceMap)
配置css
相关loader
配置(style-loader
)devServer
配置(热更新、请求转发)
webpack.prod.js
:生产环境
mode
配置devtool(sourceMap)
配置css
相关loader
配置(MiniCssExtractPlugin
)- 打包分析配置
output
配置
3.3 请求转发
请求接口时进行转发
devServer: {
historyApiFallback: true, // 用于解决非锚点路由页面加载
proxy: {
'/api': { // 接口前缀
target: 'http://www.baidu.com', // 目标服务器
changeOrigin: true, // 修改请求源
secure: false, // 忽略https
pathRewrite: {
'api': '' // 重写路径
}
}
}
}
4. 其他类
4.1 Library
用于打包库代码,方便使用者通过多种方式引用
externals: 'jquery', // 库中使用了jquery,在打包时忽略jquery的打包
output: {
library: 'library', // 通过<script>引入,将方法挂载到library变量上
libraryTarget: 'umd' // 通过import 或 require 引入
}
4.2 ESLint
用于规范代码
npx eslint --init
npm install --save-dev eslint-loader
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"]
}
5. 打包原理
- 使用
Node
读取入口文件,并用babel
将代码解析成AST
- 解析
AST
读取依赖,并将文件代码通过babel
转译成浏览器可执行代码 - 遍历依赖树,提取所有依赖文件的依赖关系与执行代码,形成依赖关系图(就是一个对象)
- 生成执行代码并写入文件
const path = require('path')
const fs = require('fs')
const babelParser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
function moduleAnalyzer(module) { // 生成模块对象
const moduleContent = fs.readFileSync(module, 'utf-8')
const ast = babelParser.parse(moduleContent, { // 生成ast
sourceType: 'module'
})
let dependencies = {}
traverse(ast, { // 变量ast,提取该模块所有依赖
ImportDeclaration({ node }) {
const dirname = path.dirname(module)
const filePath = './' + path.join(dirname, node.source.value)
dependencies[node.source.value] = filePath
}
})
const { code } = babel.transformFromAst(ast, null, { // 转译模块代码,生成执行代码
presets: ['@babel/preset-env']
})
return { // 导出模块对象
module,
dependencies,
code
}
}
function dependenciesGraphGenerator (entry) { // 生成依赖关系图
const entryModule = moduleAnalyzer(entry)
const graph = [entryModule]
for (let i = 0; i < graph.length; i++) { // 广度优先遍历,提取所有模块对象,形成依赖图对象
for (let j in graph[i].dependencies) {
graph.push(moduleAnalyzer(graph[i].dependencies[j]))
}
}
let dependenciesGraph = {}
graph.forEach(item => { // 对象结构转换,便于后续使用
dependenciesGraph[item.module] = {
dependencies: item.dependencies,
code: item.code
}
})
return dependenciesGraph
}
function codeGenerator (entry) { // 生成执行代码
let graph = dependenciesGraphGenerator(entry)
return `
(function(graph){
function require(module){
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath])
}
const exports = {}
;(function(require, exports, code){
eval(code) // code中会包含exports和require,浏览器无法识别,需要polyfill
})(localRequire, exports, graph[module].code)
return exports
}
require('${entry}')
})(${JSON.stringify(graph)})
`
}
function fileWrite(entry) { // 执行代码写入
const code = codeGenerator(entry)
fs.writeFile(path.resolve(__dirname, 'main.js'), code, 'utf-8', (err) => {
if (err) {
return console.log(err)
}
console.log('success')
})
}
fileWrite('./src/index.js')