webpack5.x+React18搭建移动端项目
项目仓库地址 https://github.com/MarvenGong/webpack5-react18-ts-basic
在项目开发中,每开始一个新项目我都是使用现有的脚手架,这非常便于快速地启动一个新项目,而且通用的脚手架通常考虑地更加全面,也有利于项目的稳定开发;不过对于一个小项目,根据需求自己搭建可能会更好,一方面小项目不需要脚手架那么丰富的功能,另一方面可以提高对项目的掌控度以方便后期的扩展
本项目基于公司内一个移动端项目搭建,旨在搭建一个快速,高效,灵活的前端开发环境。
项目中使用了最新版本的webpack构建工具,最新版本的react前段框架和react-router6.x特性搭建,通过本项目,可以可以熟悉和巩固以下基础技能
- webpack5构建原理和基本配置,以及打包性能的提升和使用方法
- react18的feature,如,根组件的创建方式等
const root = createRoot(document.getElementById('root') as Element);
root.render(<RouterProvider router={router} />);
- react-router6 的新特性和单文件路由的使用方式
- wecpack5 中配置移动端响应式方案 rem布局的方法
- 其他webpack插件和loader的使用
项目仓库地址 https://github.com/MarvenGong/webpack5-react18-ts-basic
接下来,详细说明项目中的各个配置
环境
webpack5的一个重要特性,会根据配置的mode属性是develop还是production来判断是否需要开启构建优化,这个优化过程是指对构建产品的优化,如js uglify,压缩,tree-shaking 等等,所以,我们的开发环境,是可以不用用到那些优化操作的,那么就需要把mode指定成 develop 这样可以在我们保存代码后,获得更快的热更新速度,我这里为了简单,直接在npm script中制定env,在webpack.js中使用
package.json 中的scripts
"scripts": {
"serve": "cross-env NODE_ENV=development webpack serve --config webpack.config.js",
"dev": "npm run serve",
},
PS: 切忌在NODE_ENV=development顺手写上&&
写上了就设置不上了
webpack.config.js 中使用环境变量
const isDev = process.env.NODE_ENV === 'development';
mode: isDev ? 'development' : 'production',
基础配置
1、entry入口,配置webpack构建的入口文件
我这里使用了package.json 中的name属性的值作为主文件的名称
entry: {
[packageJson?.name]: path.resolve(__dirname, './src/app.tsx'),
},
2、output 制定输出文件路径和文件名
output: {
// 开发环境不使用hash文件名
filename: isDev ? '[name].js' : '[name].[hash].js',
path: path.resolve(__dirname, './dist'),
publicPath: '/',
clean: true, // 输出文件前会先清空目标目录的文件
},
3、stats 指定webpack在控制台的输出内容
stats: {
all: false,
assets: true,
errors: true,
assetsSort: '!size', // 按照文件大小倒序
entrypoints: true,
modules: false,
assetsSpace: 1000,
preset: 'minimal',
},
4、devServer 配置本地开发服务器,如端口,代理等,此处不做详细说明,
5、devtool 制定sourcemap文件生成方式
devtool: isDev ? 'inline-source-map' : 'source-map', // 开发环境直接打包到js文件中,现网环境打包到独立文件,我的处理方式是在自动化部署工具中将sourcemap文件保存到特定地方,线上排查的时候使用
6、resolve 配置
6-1 extensions 指定webpack需要处理的文件扩展名类型
6-2 alias 路径别名,注意要去 tsconfig 中相应配置,否则代码中会报ts错误
项目使用的loader处理module说明
- ts-loader 处理typescript 语法以及es6特性(包含了babel-loader)
- style-loader 处理样式资源到html中,
开发环境使用
- MiniCssExtractPlugin.loader 处理样式资源到单独的样式文件中,
生产环境使用
- css-loader 让我们可以在js(x)或者ts(x)中使用import语句导入样式文件 如
import ‘./style.less’
- postcss-loader 对css进行编译处理,本项目主要用来处理 px2rem 将我们的样式中的px转为rem(只能针对import导入的样式文件,行内样式不生效)
- less-loader less编译为css
- url-loader 处理图片和字体等资源,小于limit则编码,大于则处理路径,包含了 file-loader(file-loader 主要作用是使我们可以在js(x)或者ts(x)中使用import语句导入静态资源文件
import ‘./xxx.png’
)
使用到的plugins做打包后处理说明
- copy-webpack-plugin 拷贝 public下的静态资源到 dist 目录
- clean-webpack-plugin 清理打包目标目录,防止重复文件
- WebpackBar 美化打包进度显示的插件
- HtmlWebpackPlugin 处理我们的构建产物到指定的html中,重中之重
- MiniCssExtractPlugin 压缩我们的css文件,只在生产环境使用
好了,有了上面的介绍,就可以直接看我们的webpack配置文件了
主webpack.config.js
// @ts-nocheck
const path = require('path');
const tsImportPluginFactory = require('ts-import-plugin');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const smp = new SpeedMeasurePlugin();
const fs = require('fs');
const packageJson = require('./package.json');
const optimization = require('./webpack-configs/optimization');
const config = require('./webpack-configs/config');
const plugins = require('./webpack-configs/plugins');
const isDev = process.env.NODE_ENV === 'development';
const SMP_SWICTH = false;
console.log('-------当前环境-------', process.env.NODE_ENV);
const wrapConfig = (isDev && SMP_SWICTH) ? smp.wrap : (config) => config;
module.exports = wrapConfig({
target: 'web',
mode: isDev ? 'development' : 'production',
entry: {
[packageJson?.name]: path.resolve(__dirname, './src/app.tsx'),
},
output: {
filename: isDev ? '[name].js' : '[name].[hash].js',
path: path.resolve(__dirname, './dist'),
publicPath: '/',
clean: true,
},
// infrastructureLogging: {
// level: 'error'
// },
stats: {
all: false,
assets: true,
errors: true,
assetsSort: '!size',
entrypoints: true,
modules: false,
assetsSpace: 1000,
preset: 'minimal',
},
devServer: {
...config.dev,
historyApiFallback: true,
// static: {
// directory: path.join(__dirname, './public'),
// },
watchFiles: './src/**/*',
},
devtool: isDev ? 'inline-source-map' : 'source-map',
resolve: {
alias: {
'@src': path.resolve(__dirname, './src'),
'@tea/app': path.resolve(__dirname, './app'),
},
extensions: ['.ts', '.tsx', '.js', 'less'],
},
module: {
rules: [
{
test: /\.(jsx|tsx|js|ts)$/,
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: () => ({
before: [
tsImportPluginFactory([
{
style: false,
libraryName: 'lodash',
libraryDirectory: null,
camel2DashComponentName: false,
},
{ style: true },
]),
],
}),
compilerOptions: {
module: 'esnext',
},
},
exclude: /node_modules/,
},
{
test: /\.(le|c)ss$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader, // 现网环境才提取css到一个文件中
{
loader: 'css-loader', // 使用import语句导入样式
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
config: './postcss.config.js',
},
sourceMap: true,
}
},
{
loader: 'less-loader', // 转less为css
options: {
lessOptions: {
modifyVars: {
'@body-background': '#f5f5f5',
},
javascriptEnabled: true,
},
},
},
// {
// loader: 'style-resources-loader',
// options: {
// patterns: path.resolve(__dirname, './src/styles/common.less'), // 全局引入公共的scss 文件
// },
// },
],
},
{
test: /\.(png|jp(e)?g|gif|woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'url-loader', // 处理图片和字体等资源,小于limit则编码,大于则处理路径,包含了 file-loader
options: {
limit: 8192,
},
},
],
},
],
},
watchOptions: {
// 设置不监听的⽂件或⽂件夹,默认为空
ignored: /node_modules/,
// ⽂件改变不会⽴即执⾏,⽽是会等待300ms之后再去执⾏
aggregateTimeout: 300,
// 原理是轮询系统⽂件有⽆变化再去更新的,默认1秒钟轮询1000次
poll: 1000,
},
plugins,
optimization: !isDev ? optimization : {},
});
在根目录下的wenpack-configs 目录中的下面三个拆出来的js
webpack 插件配置 plugins.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const WebpackBar = require('webpackbar');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const chalk = require('chalk');
const webpack = require('webpack');
const isDev = process.env.NODE_ENV === 'development';
const config = require('./config');
module.exports = [
new CopyWebpackPlugin({
patterns: [
{ from: path.resolve(__dirname, '../public/px2rem-hike.js') }
],
}),
new CleanWebpackPlugin({
dry: false, // 是否打印删除的内容
}),
// 热更新替换
new webpack.HotModuleReplacementPlugin(),
// @ts-ignore
new WebpackBar({
name: '能碳工场移动端h5',
color: '#0049b0', // 默认green,进度条颜色支持HEX
basic: true, // 默认true,启用一个简单的日志报告器
reporter: {
change() {
console.log(chalk.blue.bold('文件修改,重新编译...'));
},
afterAllDone(context) {
console.log(chalk.bgBlue.white(' 能碳工场移动端 ') + chalk.green(' 编译完成'));
isDev && console.log(chalk.bgBlue.white(' 能碳工场移动端 ')
+ chalk.green(' 开发预览地址:http://127.0.0.1:' + config.dev.port))
},
},
}),
new HtmlWebpackPlugin({
minify: false,
chunks: 'all',
template: path.resolve(__dirname, '../public/index.html'),
filename: 'index.html'
}),
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|ja|ko/),
].concat(isDev ? [] : [new MiniCssExtractPlugin({
filename: '[name].[hash].css',
})])
webpack优化配置 optimization.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
minimize: true,
minimizer: [
new CssMinimizerPlugin(), '...' // 压缩css 和 js
],
splitChunks: {
chunks: 'async',
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
};
其他常用需要修改配置 config.js
module.exports = {
dev: {
host: '0.0.0.0',
port: 10010,
hot: true,
open: false,
client: {
overlay: {
errors: true,
warnings: false,
},
},
proxy: {
'/api': {
// logLevel: 'debug',
changeOrigin: true,
pathRewrite: { '^/api': '' },
target: 'http://30.168.123.79',
},
},
}
};
怎么收藏这篇文章?
目前站点还没有收藏功能,您这边可以注册,然后我发内容给您,您自己存到自己的文章里
想想你的文章写的特别好www.jiwenlaw.com
兄弟写的非常好 https://www.cscnn.com/