1.webpack的介绍
webpack所解决的问题时:如何在前端项目中更高效地管理和维护项目中的每一个资源。
更为理想的方式是在页面中引入一个js入口文件,其余用到的模块可以通过代码控制,按需加载。
2.如何使用webpack
npm init -y
npm i webpack webpack-cli --save--dev
2.webpack的配置
devServer
import axios from 'axios'; //13.9k(gzipped:4.9k)
axios.get('/api/Yixiantong/getHomeDatas')
.then(({data}) => {
console.log('data',data);
})
//webpack.dev.js
const devConfig = {
mode: 'development',
devServer: {
port:8080, //服务器启动的端口 8080
contentBase: './dist', //服务器静态资源文件夹
progress: true, //打包时显示进度条
open:true, //启动服务器后,自动打开浏览器
compress: true, //开启gzip压缩
//虽然源代码写了路径,但是做真实请求的时候,
//我们会去掉/api
proxy: {
//以'/api'请求开头的路径做请求转发
'/api':{
//代理的域名
target:'http://study.jsplusplus.com',
//将'/api'路径置为空
pathRewrite:{
'^/api':''
},
changeOrigin:true
}
}
}
3.webpack的优化
1)构建时间的优化
(1)thread-loader
多进程打包,可以大大提高构建的速度,使用方法是将thread-loader放在比较费时间的loader之前,比如说babel-loader。
npm i thread-loader -D
//由于开发环境和生产环境都需要加速
//所以配置在webpack.common.js文件中
module.exports = {
test:/\.js$/,
use:[
'thread-loader',
'babel-loader'
]
}
(2)cache-loader
缓存资源,提高构建的速度,使用方法将cache-loader
放在比较费时的loader
之前,比如babel-loader
。
npm i cache-loader -D
//由于开发环境和生产环境都需要加速
//所以配置在webpack.common.js文件中
module.exports = {
test:/\.js$/,
use:[
'cache-loader',
'thread-loader',
'babel-loader'
]
}
(3)开启热模块更新
比如你修改了项目中某一个文件,会导致整个项目刷新,这非常耗时间。如果只刷新修改的这个模块,其他保持状态,那将大大提高修改代码的重新构建时间。
//webpack.dev.js
//引入webpack
const webpack = require('webpack')
module.exports = {
//使用webpack提供的热更新插件
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot:true
}
}
(4)exclude&&include
exclude
:不需要处理的文件include
:需要处理的文件
合理设置这两个属性,可以大大提高构建速度
// webpack.common.js
module.exports = {
test:/\.js$/,
//使用include来指定编译文件夹
include:path.resoleve(__dirname,'.../src'),
//使用exclude排除指定文件夹
exclude:/node_modules/,
use: [
'babel-loader'
]
}
开发环境
:去除
代码压缩,gzip,体积分析等优化配置,大大提高构建速度。生产环境
:需要
代码压缩,gzip,体积分析等优化配置,大大降低最终项目打包体积。
2)打包体积的优化
主要是打包后项目整体体积的优化
,有利于项目上线后的页面加载速度提升。
(1)css-minimizer-webpack-plugin
css代码压缩,去重
。
//代码的压缩比较消耗时间
//所以只需要在webpack.prod.js中配置
npm i css-minimizer-webpack-plugin -D
//webpack.prod.js
module.exports = {
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
optimization: {
minimizer: [
new CssMinimizerPlugin(), //去重压缩css
],
}
}
(2)terser-webpack-plugin
实现打包好的js代码压缩
。
压缩代码的原理主要是1.长变量变短变量
,2.去除空格和换行符
npm i terser-webpack-plugin -D
//webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization:{
minimizer:[
new CssMinimizerPlugin(), //去重压缩css
new TerserPlugin({ //压缩JS代码
terserOptions:{
compress:{
drop_console: true, //去除console
},
},
})
],
}
}
(3)tree-shaking
tree-shaking
简单说作用就是:只打包用到的代码,没有用到的代码不打包,而webpack5
默认开启tree-shaking
,当打包的mode
为production
时,自动开启tree-shaking
进行优化。
module.exports = {
mode:'produciton'
}
(4)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'
}
3)打包体积分析
webpack-bundle-analyzer
使用webpack-bundle-analyzer
可以审查打包后的体积分布,进而进行相应的体积优化,只需要看打包时的体积,所以只需要在webpack.prod.js
中配置。
npm i webpack-bundle-analyzer -D
// webpack.prod.js
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports = {
plugins: [
new BundleAnalyzerPlugin(),
]
}
4)用户体验优化
(1)模块懒加载
如果不进行模块懒加载
的话,最后整个项目代码都会被打包到一个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')
}
]
(2)Gzip
开启Gzip后,大大提高用户的页面加载速度,因为gzip的体积比源文件小很多,当然需要后端的配合,使用compression-webpack-plugin
。
只需要打包时优化体积,所以只需在webpack.prod.js
中配置
npm i compression-webpack-plugin -D
//webpack.prod.js
const CompressPlugin = require('compression-webpack-plugin')
module.exports = {
plugins: [
//之前的代码...
//gzip
const CompressPlugin({
algorithm:'gzip',
threshold:10240,
minRatio:0.8
})
}
(3)小图片转base64
对于一些小图片,可以转base64
,这样可以减少用户的http网络请求次数,提高用户的体验。webpack5
中url-loader已被废弃,改用asset-module
。
// webpack.common.js
module.exports = {
test:/\.(png|jpe?g|gif|svg|webp)$/,
type:'asset',
parser:{
//转base64的条件
dataUrlCondition:{
maxSize:25*1024, //25kb
}
},
generator:{
//打包image文件下
filename:'images/[contenthash][ext][query]',
},
}
(4)合理配置hash
我们要保证,改过的文件需要更新hash
值,而没有改过的文件依然保持原来的hash
值,这样才能保证在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的。
是在webpack.common.js中配置呢?还是在webpack.prod.js中配置呢?
// webpack.common.js
moudle.exports = {
output: {
path: path.resolve(__dirname,'../dist'),
//给js文件上加上contenthash
filename:'js/chunk-[contenthash].js',
clean: true,
}
}
4.项目实战
配置项目时,比较不同配置的webpack的编译速度和打包体积的大小
config.js
const isDevelopment = process.env.NODE_ENV === "development"
const isProduction = process.env.NODE_ENV === "production"
const SERVER_HOST = "0.0.0.0"
const SERVER_PORT = 8001
module.exports = {
isDevelopment,
isProduction,
SERVER_HOST,
SERVER_PORT,
}
webpack.common.js
//引入,html-webpack-plugin包,打包HTML资源
const HtmlWebpackPlugin = require("html-webpack-plugin")
//引入path模块
const path = require("path")
//引入webpack
const webpack = require("webpack")
//打包时可以看到优雅的进度条
const WebpackBar = require("webpackbar")
//从./config中解构赋值出,isDevelopment和isProduction
const { isDevelopment, isProduction } = require("./config")
//提取css成为单独文件(css资源合并)
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
//fork-ts-checker-webpack-plugin可以加速ts类型检查和eslint linting
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin")
//fork-ts-checker-webpack-plugin用于eslint查找和修复js代码中的问题
const ESLintPlugin = require("eslint-webpack-plugin")
//antd-dayjs-webpack-plugin可以使用Day.js替换Moment.js
const AntdDayjsWebpackPlugin = require("antd-dayjs-webpack-plugin")
//从path的文件中解构出一些路径
const {
moduleFileExtensions,
appSrc,
appIndex,
appDirectory,
appTsConfig,
appHtml,
appPublic,
} = require("./paths")
//将获取cssloader写成一个函数
const getCssLoaders = () => {
const cssLoaders = [
isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[local]",
},
sourceMap: isDevelopment,
},
},
]
isProduction &&
cssLoaders.push({
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
isProduction && [
"postcss-preset-env",
{
autoprefixer: {
grid: true,
},
},
],
],
},
},
})
return cssLoaders
}
//下面写webpack的公共配置
const config = {
// 文件的入口 5大核心组件之一
entry: {
app: appIndex,
},
context: appDirectory,
resolve: {
extensions: moduleFileExtensions,
alias: {
"@": appSrc,
},
},
//loader加载器 5大核心组件之一
//loader让webpack能够去处理哪些非Javascript文件
//webpack自身只能理解js
module: {
rules: [
// 处理tsx和jsx文件
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
cacheDirectory: true,
include: appSrc,
plugins: [
isDevelopment && require.resolve("react-refresh/babel"),
].filter(Boolean),
},
},
//处理less
{
test: /\.less$/,
// exclude: /node_modules/,
use: [
...getCssLoaders(),
{
loader: "less-loader",
options: {
sourceMap: isDevelopment,
lessOptions: {
path: [
appSrc,
// path.resolve(PROJECT_PATH, 'node_modules/antd'),
],
javascriptEnabled: true,
},
},
},
],
},
//处理css
{
test: /\.css$/,
exclude: /node_modules/,
use: [...getCssLoaders()],
},
//处理scss
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
...getCssLoaders(),
{
loader: "sass-loader",
options: {
sourceMap: isDevelopment,
},
},
],
},
//处理图片资源
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
exclude: /node_modules/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 4 * 1024,
},
},
},
//处理字体格式
{
test: /\.(eot|svg|ttf|woff|woff2?)$/,
exclude: /node_modules/,
type: "asset/resource",
//解析
// parser: {
// //转base64的条件
// dataUrlCondition: {
// maxSize: 25 * 1024, // 25kb
// }
// },
// generator: {
// //与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径
// filename: 'img/[name].[hash:6][ext]',
// //打包后对资源的引入,文件命名已经有/img了
// publicPath: './'
// },
},
],
},
//插件 五大核心组件之一
//插件都需要require引进,而加载器却不需要
plugins: [
//DefinePlugin这个插件是用来定义全局变量的,在webpack打包的时候会对这些变量做替换
// 介绍了这么多,在实际使用中, DefinePlugin 最为常用的用途就是用来处理我们开发环境和生产环境的不同。
// 比如一些 debug 的功能在生产环境中需要关闭、开发环境中和生产环境中 api 地址的不同。
new webpack.DefinePlugin({
"process.env.RUN_ENV": JSON.stringify(process.env.RUN_ENV),
}),
//打包HTML资源
new HtmlWebpackPlugin({
template: appHtml,
templateParameters: {
title: "IOT后台管理系统",
},
favicon: path.resolve(appPublic, "favicon.png"),
scriptLoading: "blocking", // 'blocking'|'defer'
}),
//打包时的进度条
new WebpackBar({
name: "构建进度",
color: "#52c41a",
}),
//fork-ts-checker-webpack-plugin可以加速ts类型检查和eslint linting
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: appTsConfig,
},
}),
new ESLintPlugin({
extensions: moduleFileExtensions,
}),
new AntdDayjsWebpackPlugin(),
].filter(Boolean),
}
module.exports = config
webpack.dev.js
const common = require("./webpack.common")
const { merge } = require("webpack-merge")
const { appBuild, appPublic } = require("./paths")
const { SERVER_HOST, SERVER_PORT } = require("./config")
const path = require("path")
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin")
const webpack = require("webpack")
//merge合并两个对象
module.exports = merge(common, {
//模式 5大核心模块之一
mode: "development",
//代码报错,调试
devtool: "eval-cheap-module-source-map",
//DevServer是webpack开发服务器
devServer: {
historyApiFallback: true,
quiet: true,
// host: SERVER_HOST,
port: SERVER_PORT,
watchContentBase: true,
// stats: 'errors-only',
clientLogLevel: "none",
compress: true,
// open: true,
//webpack优化配置1:开启热更新 HMR
hot: true,
inline: true,
noInfo: true,
// contentBase: `http://${SERVER_HOST}:${SERVER_PORT}`,
// publicPath: appPublic,
contentBase: appBuild,
contentBasePublicPath: "/",
//服务器配置
proxy: {
"/api": {
//target为服务器的地址和端口号,本题的端口号为4093
target: "http://localhost:4093",
// changeOrigin: false,
// secure: false,
}
},
//自动打开浏览器
open:true
},
target: "web",
cache: {
type: "memory",
},
optimization: {
moduleIds: "named",
chunkIds: "named",
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
].filter(Boolean),
})
webpack.prod.js
const webpack = require("webpack")
const { appDll } = require("./constant")
const path = require("path")
module.exports = {
entry: {
vendor: ["react", "react-dom", "react-router-dom", "axios"],
},
output: {
path: appDll,
filename: "[name].dll.js",
libraryTarget: "var",
library: "[name]_dll_[hash]",
// clean: true
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(appDll, "[name]-manifest.json"),
name: "[name]_dll_[hash]",
context: __dirname,
}),
],
}
webpack.dll.js
const webpack = require("webpack")
const { appDll } = require("./constant")
const path = require("path")
module.exports = {
entry: {
vendor: ["react", "react-dom", "react-router-dom", "axios"],
},
output: {
path: appDll,
filename: "[name].dll.js",
libraryTarget: "var",
library: "[name]_dll_[hash]",
// clean: true
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(appDll, "[name]-manifest.json"),
name: "[name]_dll_[hash]",
context: __dirname,
}),
],
}
path.js
const path = require("path")
const fs = require("fs")
//process是node的一个全局变量,即global对象的属性。
//cwd()返回当前进程的工作目录
const appDirectory = fs.realpathSync(process.cwd())
function resolveApp(relativePath) {
//path.resolve()返回的是当前的文件的绝对路径
//path.resolve不带/开头的参数,返回的是当前绝对路径拼接现在的参数
//path.resolve('a') ==> /Users/xxx/a
return path.resolve(appDirectory, relativePath)
}
//文件后缀
const moduleFileExtensions = [".tsx", ".ts", ".jsx", ".js", ".json"]
/**
* Resolve module path
* @param {function} resolveFn resolve function
* @param {string} filePath file path
*/
function resolveModule(resolveFn, filePath) {
const extension = moduleFileExtensions.find((ex) =>
fs.existsSync(resolveFn(`${filePath}.${ex}`)),
)
if (extension) {
return resolveFn(`${filePath}${extension}`)
}
return resolveFn(`${filePath}.tsx`) // default is .ts
}
module.exports = {
appBuild: resolveApp("dist"),
appPublic: resolveApp("public"),
appIndex: resolveModule(resolveApp, "src/index"), // Package entry path
appHtml: resolveApp("src/index.ejs"),
appNodeModules: resolveApp("node_modules"), // node_modules path
appSrc: resolveApp("src"),
appDll: resolveApp("dll"),
appSrcComponents: resolveApp("src/components"),
appSrcUtils: resolveApp("src/utils"),
appProxySetup: resolveModule(resolveApp, "src/setProxy"),
appPackageJson: resolveApp("package.json"),
appTsConfig: resolveApp("tsconfig.json"),
appDirectory,
moduleFileExtensions,
}