webpack5.x+React18搭建移动端项目

webpack5.x+React18搭建移动端项目

admin
2022-11-23 / 0 评论 / 438 阅读 / 正在检测是否收录...

webpack5.x+React18搭建移动端项目

项目仓库地址 https://github.com/MarvenGong/webpack5-react18-ts-basic

webpack

在项目开发中,每开始一个新项目我都是使用现有的脚手架,这非常便于快速地启动一个新项目,而且通用的脚手架通常考虑地更加全面,也有利于项目的稳定开发;不过对于一个小项目,根据需求自己搭建可能会更好,一方面小项目不需要脚手架那么丰富的功能,另一方面可以提高对项目的掌控度以方便后期的扩展

本项目基于公司内一个移动端项目搭建,旨在搭建一个快速,高效,灵活的前端开发环境。
项目中使用了最新版本的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',
      },
    },
  }
};
1

评论 (0)

取消