项目
配置react项目
开发模式配置
使用npm进行包管理
npm init -y
安装
npm i webpack webpack-cli -D
webpack
webpack-cli
webpack-dev-server
初始化文件
新建/config/webpack.dev.js:
config
└─ webpack.dev.js
初始化入口,出口,模式:
mode: "development",
entry: "./src/main.js",
output: {
path: undefined,
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
对css处理
npm i css-loader style-loader less less-loader postcss-loader postcss-preset-env -D
css-loader css提取到js中
style-loader 创建style标签将js中的css插入标签中
less 预编译器
less-loader 将less转换为css
postcss-loader 兼容处理
postcss-preset-env 兼容所需预设
webpack.dev.js:
const getStyleLoader = (pre) => {
return [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
}
module.rules:
{
test: /.css$/,
use: getStyleLoader(),
},
{
test: /.less$/,
use: getStyleLoader("less-loader"),
},
package.json:
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
],
对图片和其他资源处理
webpack.dev.js:
module.rules:
{
test: /.(png|jpe?g|svg|git|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
},
对html处理
npm i html-webpack-plugin -D
html-webpack-plugin 自动将打包的js插入html文件中
webpack.dev.js:
const HtmlWebpackPlugin = require("html-webpack-plugin");
plugins:
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
eslint配置
npm i eslint-webpack-plugin eslint-config-react-app eslint-config-react-app -D
eslint-webpack-plugin
eslint-config-react-app 使用react的预设
webpack.dev.js:
const ESLintPlugin = require("eslint-webpack-plugin");
plugins:
new ESLintPlugin({
context: path.join(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
}),
.eslintrc.js:
module.exports = {
extends: ["react-app"],
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
babel配置
react的预设中已经包含corejs和runtime的配置
npm i babel-loader @babel/core babel-preset-react-app -D
babel-loader
@babel/core
babel-preset-react-app 使用react预设
webpack.dev.js:
{
test: /.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
}
.babelrc.js:
presets: ["react-app"],
安装 react
npm i react react-dom react-router-dom -S
react
react-dom
react-router-dom 路由
样例:
import { Link, Routes, Route } from "react-router-dom";
import { lazy, Suspense } from "react"; // 分开打包
lazy(() => import(/*webpackChunkName: 'about'*/ "./pages/about")); 配合魔法命名
main.js:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import APP from "./App";
const root = ReactDOM.createRoot(document.querySelector("#app"));
root.render(
<BrowserRouter>
<APP />
</BrowserRouter>
);
自动补全文件后缀
配置文件的resolve选项: 通过import引入模块的时候会加载的选项:
webpack.dev.js:
// webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
resolve: {
// 自动补全文件扩展名
extensions: [".jsx", ".js", "json"],
},
热更新
npm i webpack-dev-server @pmmmwh/react-refresh-webpack-plugin react-refresh -D
react的热更新: @pmmmwh/react-refresh-webpack-plugin
webpack.dev.js:
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
babel配置: options.plugins: ["react-refresh/babel"],
plugins:[new ReactRefreshWebpackPlugin()]
devServer: {
host: "127.0.0.1",
port: 8080,
hot: true,
open: true,
},
刷新404问题
路由刷新页面返回404问题,一旦返回404,devServer会返回index.html,顺着index.html自动就会回到地址栏对应的路由
webpack.dev.js:
devServer: {
historyApiFallback: true,
}
sourcemap:
devtool: "cheap-module-source-map",
文件引用映射和代码分割:
webpack.dev.js:
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
配置环境变量
cross-env: 专门定义环境变量的一个库
npm i cross-env -D
在启动的时候定义一个cross-env key=value就可以了
配置package.json命令
"scripts": {
"dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.dev.js"
},
完整文件
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const getStyleLoader = (pre) => {
return [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
};
module.exports = {
mode: "development",
entry: "./src/main.js",
output: {
path: undefined,
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
module: {
rules: [
{
test: /.css$/,
use: getStyleLoader(),
},
{
test: /.less$/,
use: getStyleLoader("less-loader"),
},
{
test: /.(png|jpe?g|svg|git|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
},
{
test: /.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ["react-refresh/babel"],
},
},
],
},
plugins: [
new ESLintPlugin({
context: path.join(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
new ReactRefreshWebpackPlugin(),
],
devtool: "cheap-module-source-map",
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
// webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
resolve: {
// 自动补全文件扩展名
extensions: [".jsx", ".js", "json"],
},
devServer: {
host: "127.0.0.1",
port: 8080,
hot: true,
open: true,
historyApiFallback: true,
},
};
生产模式配置
复制开发模式的配置文件在此基础上修改
移除devserver
删除devServer配置
移除hmr相关
删除ReactRefreshWebpackPlugin插件和babel对应的预设"react-refresh/babel"
output修改
mode: "production",
output: {
path: path.resolve(__dirname, "../dist"), // 输出文件路径
filename: "static/js/[name].[contenthash:10].js", // 加上hash方便做缓存
chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
clean: true, // 清空上一次打包文件
},
样式
提取样式为文件: 查看基础=>将css打包为文件
压缩样式代码: 查看基础=>css代码压缩
js压缩
配置了css压缩,要重新配置一下webpack自带的Terser
const TerserWebpackPlugin = require("terser-webpack-plugin");
plugins: new TerserWebpackPlugin(),
sourcemap
devtool: "source-map",
图片压缩
查看高级=>减少打包体积 => 压缩图片
public文件夹处理
public放置的都是不要处理的资源,如网站图标等,配置自动复制到dist下即可,保证打包后可以找到:
npm i -D copy-webpack-plugin
const CopyPlugin = require("copy-webpack-plugin");
plugins:
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
ignore: ["**/index.html"], // 忽略index.html
},
},
],
}),
node_modules分包
splitChunks中chunks: 'all'的配置会将动态导入单独打包,也会将node_modules下的文件单独打一个包,随着项目越来越大,node_modules对应的打包文件也会越来越大,每次请求都会很耗时,因此我们需要把他分开打包,根据不同的库配置单独打包
主要考虑两点:
- 单个文件体积过大时单独打包
- 分包过多同时也会导致请求压力过大
splitChunks: {
chunks: "all",
cacheGroups: {
// 拆分node_modules
// 将react相关包打在一起
react: {
test: /[\/]node_modules[\/]react(.*)?/,
priority: 30,
name: "chunk-react.js",
},
libs: {
test: /[\/]node_modules[\/]/,
priority: 20,
name: "chunk-libs.js",
},
},
},
两个模式合并
两个文件存在大量相同代码,可以通过判断当前模式,将两个配置进行合并,步骤如下:
-
通过
process.env.NODE_ENV获取到cross-env配置的环境变量 -
根据环境变量的值判断某个配置是否使用
-
修改原来的打包命令,使用合并后的配置文件:
"dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.config.js", "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js" -
关闭打包分析
performance:false,这样打包时就不会自动分析包的大小了,从而节省一些打包时间
合并后的配置文件webpack.config.js如下:
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const isProduction = process.env.NODE_ENV === "production"; // 获取cross-env定义的全局变量
const getStyleLoader = (pre) => {
return [
isProduction ? MiniCssExtractPlugin.loader : "style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
};
module.exports = {
mode: process.env.NODE_ENV,
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
filename: isProduction
? "static/js/[name].[contenthash:10].js"
: "static/js/[name].js",
chunkFilename: isProduction
? "static/js/[name].[contenthash:10].chunk.js"
: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
{
test: /.css$/,
use: getStyleLoader(),
},
{
test: /.less$/,
use: getStyleLoader("less-loader"),
},
{
test: /.(png|jpe?g|svg|git|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
},
{
test: /.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [!isProduction && "react-refresh/babel"].filter(Boolean),
},
},
],
},
plugins: [
new ESLintPlugin({
context: path.join(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
!isProduction && new ReactRefreshWebpackPlugin(),
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
isProduction &&
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
ignore: ["**/index.html"],
},
},
],
}),
].filter(Boolean),
devtool: isProduction ? "source-map" : "cheap-module-source-map",
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
// 拆分node_modules
// 将react相关包打在一起
react: {
test: /[\/]node_modules[\/]react(.*)?/,
priority: 30,
name: "chunk-react.js",
},
libs: {
test: /[\/]node_modules[\/]/,
priority: 20,
name: "chunk-libs.js",
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimize: isProduction,
minimizer: [
new TerserWebpackPlugin(),
new CssMinimizerPlugin(), // css压缩
// 压缩本地图片
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
],
},
// webpack 解析模块选项,配置后引入下述文件的时候可以不添加扩展名
resolve: {
// 自动补全文件扩展名
extensions: [".jsx", ".js", "json"],
},
devServer: {
host: "127.0.0.1",
port: 8080,
hot: true,
open: true,
historyApiFallback: true,
},
performance:false
};