💡前言
最近学习了 Webpack 5 之后,想自己搭建个项目练练手,于是就搭建了一个基于 Webpack 5 的 React 的脚手架。
脚手架配置了相关模块,集成了常用功能,便于自己以后 React 新项目的搭建,开箱即用!
仓库地址:「Github」
🔌模块/功能
- 框架
React - 路由
react-router-dom Typescript - 状态管理库
redux - 样式预处理
less 、sass - 代码检测
eslint git commit 前规范检测commitlint - 时间库
dayjs - UI库
antd ,配置了样式按需引入、自定义主题 react hooks 库ahooks
💾目录结构
项目的整体目录结构如下所示,其中为了测试可用性,添加了一些简单的组件和页面,可自行更改。
│ .babelrc // Babel配置
│ .commitlintrc.js // commitlint配置
│ .eslintrc.js // eslint配置
│ .gitignore // git忽略文件列表
│ package.json
│ README.md
│ tsconfig.json // typescript配置
│ yarn.lock
│
├─public // 全局文件
│ │ index.html // 模板
│ │
│ └─assets // 不需要动态导入的资源
│ index.css
│ index.jpg
│ index.js
│
├─scripts // 脚本
│ │ antd-theme.js // antd自定义主题配置
│ │ constant.js // webpack相关的常量
│ │ env.js // 环境变量
│ │
│ └─config
│ webpack.common.js // 开发环境+生产环境的公共配置
│ webpack.dev.js // 开发环境webpack配置
│ webpack.prod.js // 生产环境webpack配置
│
└─src
│ App.scss
│ App.tsx
│ index.tsx // 入口文件
│
├─components // 组件
│ └─ErrorBoundary // 错误边界
│ index.tsx
│
├─pages // 页面(写了一些页面测试)
│ ├─Admin
│ │ index.tsx
│ │
│ └─Home
│ index.tsx
│
├─redux // redux相关
│ │ actions.ts
│ │ constant.ts
│ │ interface.ts
│ │ store.ts
│ │
│ └─reducers
│ count.ts
│ index.ts
│
└─types // 模块声明
asset.d.ts
style.d.ts
??主要配置文件
package.json
主要看scripts 下的内容。还配置了git husky ,用于在提交commit 前自动检测commit 规范性。
{
"name": "my-react",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./scripts/config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config ./scripts/config/webpack.prod.js"
},
"dependencies": {
},
"browserslist": [
">0.2%",
"not dead",
"ie >= 9",
"not op_mini all"
],
"husky": {
"hooks": {
"commit-msg": "commitlint --config .commitlintrc.js -e"
}
}
}
env.js
导出环境变量。
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
isDevelopment,
isProduction,
};
constant.js
导出根路径、HOST、POST。
const path = require('path');
const ROOT_PATH = path.resolve(__dirname, '../');
const SERVER_HOST = 'localhost';
const SERVER_PORT = 8080;
module.exports = {
ROOT_PATH,
SERVER_HOST,
SERVER_PORT,
};
webpack.common.js
const path = require('path');
const WebpackBar = require('webpackbar');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
const { ROOT_PATH } = require('../constant');
const { isDevelopment, isProduction } = require('../env');
const { myAntd } = require('../antd-theme');
const getCssLoaders = () => {
const cssLoaders = [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]--[hash:base64:5]',
},
sourceMap: isDevelopment,
},
},
];
const postcssLoader = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
isProduction && [
'postcss-preset-env',
{
autoprefixer: {
grid: true,
},
},
],
],
},
},
};
isProduction && cssLoaders.push(postcssLoader);
return cssLoaders;
};
const getAntdLessLoaders = () => [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: isDevelopment,
},
},
{
loader: 'less-loader',
options: {
sourceMap: isDevelopment,
lessOptions: {
modifyVars: myAntd,
javascriptEnabled: true,
},
},
},
];
module.exports = {
entry: {
index: path.resolve(ROOT_PATH, './src/index'),
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(ROOT_PATH, './public/index.html'),
filename: 'index.html',
inject: 'body',
}),
new WebpackBar(),
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: path.resolve(ROOT_PATH, './tsconfig.json'),
},
}),
new CopyWebpackPlugin({
patterns: [
{
context: 'public',
from: 'assets/*',
to: path.resolve(ROOT_PATH, './build'),
toType: 'dir',
globOptions: {
dot: true,
gitignore: true,
ignore: ['**/index.html'],
},
},
],
}),
new CleanWebpackPlugin(),
new AntdDayjsWebpackPlugin(),
],
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: getCssLoaders(),
},
{
test: /\.less$/,
exclude: /node_modules/,
use: [
...getCssLoaders(),
{
loader: 'less-loader',
options: {
sourceMap: isDevelopment,
},
},
],
},
{
test: /\.less$/,
exclude: /src/,
use: getAntdLessLoaders(),
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
...getCssLoaders(),
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment,
},
},
],
},
{
test: /\.(tsx?|js)$/,
loader: 'babel-loader',
options: { cacheDirectory: true },
exclude: /node_modules/,
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024,
},
},
},
{
test: /\.(eot|svg|ttf|woff|woff2?)$/,
type: 'asset/resource',
},
],
},
resolve: {
alias: {
'@': path.resolve(ROOT_PATH, './src'),
},
extensions: ['.tsx', '.ts', '.js', '.json'],
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
};
webpack.dev.js
const path = require('path');
const { merge } = require('webpack-merge');
const webpack = require('webpack');
const common = require('./webpack.common');
const { ROOT_PATH, SERVER_HOST, SERVER_PORT } = require('../constant');
module.exports = merge(common, {
target: 'web',
mode: 'development',
devtool: 'eval-cheap-module-source-map',
output: {
path: path.resolve(ROOT_PATH, './build'),
filename: 'js/[name].js',
},
devServer: {
host: SERVER_HOST,
port: SERVER_PORT,
compress: true,
open: true,
hot: true,
client: {
logging: 'warn',
overlay: true,
},
historyApiFallback: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
optimization: {
minimize: false,
minimizer: [],
splitChunks: {
chunks: 'all',
minSize: 0,
},
},
});
webpack.prod.js
const path = require('path');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const common = require('./webpack.common');
const { ROOT_PATH } = require('../constant');
module.exports = merge(common, {
target: 'browserslist',
mode: 'production',
devtool: false,
output: {
path: path.resolve(ROOT_PATH, './build'),
filename: 'js/[name].[contenthash:8].js',
assetModuleFilename: 'assets/[name].[contenthash:8].[ext]',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
new BundleAnalyzerPlugin(),
],
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: { pure_funcs: ['console.log'] },
},
}),
],
splitChunks: {
chunks: 'all',
minSize: 0,
},
},
});
📝遇到的问题
BrowserRouter开发环境404问题
在webpack.dev.js 添加一项,任何请求都会返回index.html 文件,解决单页面应用的路由跳转问题。
devServer: {
historyApiFallback: true,
}
安装 node-sass 失败
先全局安装node-gyp :
npm install -g node-gyp
再到项目根目录下,yarn 继续安装即可。
antd 样式按需加载
安装babel-plugin-import ,在.babelrc 文件的plugins 下,添加一项:
{
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]
]
}
正常使用即可,无需再引入样式:
import React from 'react';
import { Button } from 'antd';
import { useTitle } from 'ahooks';
const Admin: React.FC = () => {
useTitle('Admin');
return <Button type='primary'>按钮</Button>;
};
export default Admin;
css-module 与 antd 样式冲突
当css-loader 配置了模块化引入时,如下所示:
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]--[hash:base64:5]',
},
sourceMap: isDevelopment,
},
}
发现 antd 的样式不显示了。原因是模块化也应用于node_modules 中的文件,把 antd 中引入的样式也作了模块化,但是引入的组件还是正常的类名,所以显示不出。
解决办法是,将自己写的业务代码与第三方库的代码配置分开,因为之前 antd 按需加载配置时,配置了"style": true ,加载less ,所以要单独配置下less ,只在业务代码中开启module :
module.exports = {
module: {
rules: [
{
test: /\.less$/,
exclude: /node_modules/,
use: [
...getCssLoaders(),
{
loader: 'less-loader',
options: {
sourceMap: isDevelopment,
},
},
],
},
{
test: /\.less$/,
exclude: /src/,
use: getAntdLessLoaders(),
},
],
},
};
antd 自定义主题
处理less 。注意排除业务代码,不开启module :
const myAntd = {
'primary-color': '#1DA57A',
'link-color': '#1DA57A',
'border-radius-base': '8px',
};
module.exports = {
myAntd,
};
const { myAntd } = require('../antd-theme');
const getAntdLessLoaders = () => [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: isDevelopment,
},
},
{
loader: 'less-loader',
options: {
sourceMap: isDevelopment,
lessOptions: {
modifyVars: myAntd,
javascriptEnabled: true,
},
},
},
];
{
test: /\.less$/,
exclude: /src/,
use: getAntdLessLoaders(),
}
本文记录自己所学,若有不妥,欢迎批评指出~
|