一、reactcli开发环境配置
效果:
1、目录结构
2、开始搭建
npm init -y生成package.json:
{
"name": "react-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
],
"devDependencies": {
"@babel/core": "^7.17.10",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"babel-loader": "^8.2.5",
"babel-preset-react-app": "^10.0.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"eslint": "^8.41.0",
"eslint-config-react-app": "^7.0.1",
"eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^10.2.0",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.5.0",
"react-refresh": "^0.13.0",
"sass": "^1.51.1",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
},
"dependencies": {
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0"
}
}
babel.config.js
module.exports = {
presets: ['react-app'] // 使用react官方预设,内部集成了babel-preset-react-app
};
.eslintrc.js
module.exports = {
extends: ["react-app"], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
About/index.jsx
import React from 'react';
const About = () => {
return (
<h2>
about<p className="p">段落2</p>
</h2>
);
};
export default About;
Home/index.jsx
import React from 'react';
import styles from './index.module.less'
const Home = () => {
return (
<h2 className={styles.title}>
home
<p className={styles.p}>段落23</p>
</h2>
);
};
export default Home;
Home/index.module.less
.title {
color: aqua;
}
.p {
color: yellow;
}
App.js
import React, { Suspense, lazy } from 'react';
import { Link, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import(/* webpackChunkName: 'Home' */ './pages/Home'));
const About = lazy(() =>
import(/* webpackChunkName: 'About' */ './pages/About')
);
const App = () => {
return (
<div>
<h1>App</h1>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</div>
);
};
export default App;
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>
);
public/index.html
<!DOCTYPE html>
<html lang="en">
<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>react 脚手架</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
webpack.dev.js
const path = require('path');
const EslintWebpackPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 返回loader数组
const getStyleLoaders = loaderName => {
const loaders = [
'style-loader',
'css-loader',
/*
处理css兼容性问题,需要在package.json中设置browserslist属性
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
*/
{
loader: 'postcss-loader',
options: { postcssOptions: { plugins: ['postcss-preset-env'] } }
},
loaderName // undefined/less-loader/sass-loader/...
];
return loaders.filter(Boolean);
};
module.exports = {
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: [
{
oneOf: [
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 10 * 1024 } } // 小于10kb的图片会被处理成base64
},
{ test: /\.(woff2?|ttf)$/, type: 'asset/resource' },
{
test: /\.jsx?$/,
include: path.resolve(__dirname, '../src'),
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: ['react-refresh/babel'] // 激活js的hmr
}
}
]
}
]
},
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
exclude: 'node_modules',
cache: true,
cacheLocation: path.resolve(
__dirname,
'../node_modules/.cache/.eslintcache'
)
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new ReactRefreshWebpackPlugin() // 激活js的hmr
],
mode: 'development',
devtool: 'cheap-module-source-map',
optimization: {
splitChunks: { chunks: 'all' }, // 代码分割
runtimeChunk: { name: entrypoint => `runtime~${entrypoint.name}.js` } // 提取runtime文件
},
resolve: { extensions: ['.jsx', '.js', '.json'] }, // 自动补全文件扩展名
devServer: {
host: 'localhost',
port: 3005,
// open: true,
// hot:true, // 默认已经开启
historyApiFallback: true // 解决react路由刷新404问题
}
};
二、reactcli生产环境配置
安装包(node版本:16.14.0)
cnpm i mini-css-extract-plugin css-minimizer-webpack-plugin -D // 提取css、css压缩
cnpm i image-minimizer-webpack-plugin imagemin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D // 图片无损压缩
cnpm i copy-webpack-plugin -D // 将plugin目录下的资源拷贝到dist目录中
添加指令
"prod": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
public
- 添加favicon.ico
- 引入ico:
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
webpack.prod.js
const path = require('path');
const EslintWebpackPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 提取css生成单独的css文件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // js压缩
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); // 图片压缩
const CopyPlugin = require('copy-webpack-plugin'); // 将plugin目录下的资源拷贝到dist目录中
const { loader: miniCssLoader } = MiniCssExtractPlugin;
// 返回loader数组
const getStyleLoaders = loaderName => {
const loaders = [
miniCssLoader,
'css-loader',
/*
处理css兼容性问题,需要在package.json中设置browserslist属性
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
*/
{
loader: 'postcss-loader',
options: { postcssOptions: { plugins: ['postcss-preset-env'] } }
},
loaderName // undefined/less-loader/sass-loader/...
];
return loaders.filter(Boolean);
};
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[hash:10][ext][query]',
clean: true // 清除上一次的dist
},
module: {
rules: [
{
oneOf: [
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 10 * 1024 } } // 小于10kb的图片会被处理成base64
},
{ test: /\.(woff2?|ttf)$/, type: 'asset/resource' },
{
test: /\.jsx?$/,
include: path.resolve(__dirname, '../src'),
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 关闭缓存文件压缩
}
}
]
}
]
},
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
exclude: 'node_modules',
cache: true,
cacheLocation: path.resolve(
__dirname,
'../node_modules/.cache/.eslintcache'
)
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: { ignore: ['**/index.html'] } // 忽略index.html文件
}
]
})
],
mode: 'production',
devtool: 'source-map', // 包含行/列映射,打包速度慢
optimization: {
splitChunks: { chunks: 'all' }, // 代码分割
runtimeChunk: { name: entrypoint => `runtime~${entrypoint.name}.js` }, // 提取runtime文件
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin(),
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: { xmlnsOrder: 'alphabetical' }
}
]
}
]
]
}
}
})
]
},
resolve: { extensions: ['.jsx', '.js', '.json'] } // 自动补全文件扩展名
};
执行npm run prod,输出dist目录,如果直接打开,此时还看不到ico,可以使用serve dist或者anywhere -d dist
三、合并配置
webpack.config.js
const path = require('path');
const EslintWebpackPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 提取css生成单独的css文件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin'); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // js压缩
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); // 图片压缩
const CopyPlugin = require('copy-webpack-plugin'); // 将plugin目录下的资源拷贝到dist目录中
const { loader: miniCssLoader } = MiniCssExtractPlugin;
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 通过cross-env获取当前环境变量
const isProduction = process.env.NODE_ENV === 'production';
// 返回loader数组
const getStyleLoaders = loaderName => {
const loaders = [
isProduction ? miniCssLoader : 'style-loader',
'css-loader',
/*
处理css兼容性问题,需要在package.json中设置browserslist属性
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
*/
{
loader: 'postcss-loader',
options: { postcssOptions: { plugins: ['postcss-preset-env'] } }
},
loaderName // undefined/less-loader/sass-loader/...
];
return loaders.filter(Boolean);
};
module.exports = {
entry: './src/main.js',
output: {
path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
filename: isProduction ? 'static/js/[name].[contenthash:8].js' : 'static/js/[name].js',
chunkFilename: isProduction ? 'static/js/[name].[contenthash:8].chunk.js' : 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[hash:10][ext][query]',
clean: true // 清除上一次的dist
},
module: {
rules: [
{
oneOf: [
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 10 * 1024 } } // 小于10kb的图片会被处理成base64
},
{ test: /\.(woff2?|ttf)$/, type: 'asset/resource' },
{
test: /\.jsx?$/,
include: path.resolve(__dirname, '../src'),
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: [!isProduction && 'react-refresh/babel'].filter(Boolean) // 开发环境需要激活js的hmr
}
}
]
}
]
},
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
exclude: 'node_modules',
cache: true,
cacheLocation: path.resolve(
__dirname,
'../node_modules/.cache/.eslintcache'
)
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
isProduction && new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
}),
isProduction && new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: { ignore: ['**/index.html'] } // 忽略index.html文件
}
]
}),
!isProduction && new ReactRefreshWebpackPlugin() // 开发环境需要激活js的hmr
].filter(Boolean),
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'cheap-module-source-map', // 包含行/列映射,打包速度慢
optimization: {
splitChunks: { chunks: 'all' }, // 代码分割
runtimeChunk: { name: entrypoint => `runtime~${entrypoint.name}.js` }, // 提取runtime文件
minimize: isProduction, // 控制minimizer的配置是否有效
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin(),
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: { xmlnsOrder: 'alphabetical' }
}
]
}
]
]
}
}
})
]
},
resolve: { extensions: ['.jsx', '.js', '.json'] }, // 自动补全文件扩展名
// 运行指令时需要添加serve才能用到devServer
devServer: {
host: 'localhost',
port: 3005,
// open: true,
// hot:true, // 默认已经开启
historyApiFallback: true // 解决react路由刷新404问题
}
};
package.json
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
"prod": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
四、优化配置
1、antd定制主题色
安装antd
cnpm i antd@4.20.3
main.js
import 'antd/dist/antd.css';
App.jsx
import { Button } from 'antd';
<Button type="primary">按钮</Button>
此时可以看到页面中一个蓝色的按钮
webpack.config.js判断是否为less-loader,如果是,加上less配置
loaderName && {
loader: loaderName,
options:
loaderName === 'less-loader'
? {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true
}
}
: {}
main.js需要将css改为less
import 'antd/dist/antd.less';
重启,便可以看到按钮变成绿色的了
2、打包优化
目前node_modules会打包成一个文件,造成某个文件的体积过大,会导致加载偏慢
我们给代码分割做一些配置:
webpack.config.js
splitChunks: {
chunks: 'all',
// 将node_modules中比较大的几个模块单独打包
cacheGroups: {
// layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
// 可以单独打包,从而复用
// 如果项目中没有,请删除
layouts: {
name: 'layouts',
test: path.resolve(__dirname, '../src/layouts'),
priority: 40
},
antd: {
test: /[\\/]node_modules[\\/]antd(.*)/,
name: 'chunk-antd',
priority: 30
},
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: 'chunk-react',
priority: 20,
chunks: 'initial'
},
libs: {
test: /[\\/]node_modules[\\/]/,
name: 'chunk-libs',
priority: 10, // 权重最小,优先级低
chunks: 'initial'
}
}
}
...
performance: false // 关闭性能分析,提示速度