有没有做cad单的网站/百度站点
💡前言
最近学习了 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.tsstyle.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 = [// 开发模式使用style-loader,生产模式MiniCssExtractPlugin.loaderisDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,{loader: 'css-loader',options: {modules: {// 模块化类名,防止重复localIdentName: '[local]--[hash:base64:5]',},sourceMap: isDevelopment,},},];// 加css前缀的loader配置const postcssLoader = {loader: 'postcss-loader',options: {postcssOptions: {plugins: [isProduction && ['postcss-preset-env',{autoprefixer: {grid: true,},},],],},},};// 生产模式时,才需要加css前缀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: {// antd 自定义主题modifyVars: myAntd,javascriptEnabled: true,},},},
];module.exports = {entry: {index: path.resolve(ROOT_PATH, './src/index'),},plugins: [// html模板new HtmlWebpackPlugin({template: path.resolve(ROOT_PATH, './public/index.html'),filename: 'index.html',inject: 'body',}),// 打包显示进度条new WebpackBar(),// webpack打包不会有类型检查,强制ts类型检查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(),// 将antd中的moment.js替换为day.jsnew 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)$/, // ts\tsx\jsloader: 'babel-loader',options: { cacheDirectory: true }, // 缓存公共文件exclude: /node_modules/,},{test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],// 自动选择导出为单独文件还是url形式type: 'asset',parser: {dataUrlCondition: {maxSize: 4 * 1024,},},},{test: /\.(eot|svg|ttf|woff|woff2?)$/,// 分割为单独文件,并导出urltype: '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, // gzip压缩open: true, // 自动打开默认浏览器hot: true, // 启用服务热替换配置client: {logging: 'warn', // warn以上的信息,才会打印overlay: true, // 当出现编译错误或警告时,在浏览器中显示全屏覆盖},// 解决路由跳转404问题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: [// 生产模式使用了MiniCssExtractPlugin.loader,则需要使用MiniCssExtractPluginnew MiniCssExtractPlugin({filename: 'css/[name].[contenthash:8].css',chunkFilename: 'css/[name].[contenthash:8].chunk.css',}),// 查看打包体积大小,启用一个本地服务器new BundleAnalyzerPlugin(),],// 专门存放优化打包的配置optimization: {minimize: true,minimizer: [new CssMinimizerPlugin(),// JS压缩new TerserPlugin({extractComments: false, // 去除所有注释terserOptions: {compress: { pure_funcs: ['console.log'] }, // 去除所有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 // `style: true` 会加载 less 文件}]]
}
正常使用即可,无需再引入样式:
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(), // 不开启module},// ...],},// ...
};
antd 自定义主题
处理less
。注意排除业务代码,不开启module
:
// antd自定义主题配置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: {// antd 自定义主题modifyVars: myAntd,javascriptEnabled: true,},},},
];
//...{test: /\.less$/,exclude: /src/,use: getAntdLessLoaders(),
}//...
本文记录自己所学,若有不妥,欢迎批评指出~