前端环境部署全套实践指南

前端环境部署实践指南

在现代前端开发中,建立一个高效的开发环境和部署流程对于团队的工作效率以及产品质量至关重要。本篇博客将深入介绍如何利用Webpack以及其他工具来优化前端开发环境部署流程,并将内容分为以下几个方面。

1. 项目创建与基本结构

1.1 创建项目结构

  • 介绍如何创建一个基本的项目结构

    在本项目中,我们将使用pnpm来进行包管理。pnpm相比于npmyarn有以下优势:
    1. 更快的安装和更新时间
    2. 更少的磁盘空间使用
    3. 更好地支持 monorepos
    4. 更好地支持对等依赖
    5. 更清晰的依赖树

    初始化 package.json 文件 pnpm init
    在根目录新建基本的项目结构:
    ├── build
    | ├── webpack.base.ts # 公共配置
    | ├── webpack.dev.ts # 开发环境配置
    | └── webpack.prod.ts # 打包环境配置
    ├── public
    │ └── index.html # html模板
    ├── src
    | ├── App.tsx
    | ├── App.css
    │ └── index.tsx # react应用入口页面
    └── package.json

  • 引入React和TypeScript确保代码质量

    1. 为了引入 React,需要安装依赖:pnpm i react react-dom

    2. 为了确保代码质量,我们将使用 TypeScript。TypeScript有如下优点:

      1. 更好的代码质量
      2. 更好的可读性和可维护性
      3. 更好的 IDE 支持
    3. 安装 TypeScript 相关依赖
      pnpm i @types/react @types/react-dom -D
      pnpm i babel-loader ts-node @babel/core @babel/preset-react @babel/presettypescript @babel/preset-env core-js -D

    4. 初始化 tsconfig.json :npx tsc --init

1.2 配置Webpack

  • 分别介绍基础配置、开发环境配置和生产环境配置

  • 着重讨论Webpack配置文件(webpack.base.ts、webpack.dev.ts、webpack.prod.ts)

    1. 安装依赖:pnpm i webpack webpack-cli -D

    2. 配置 webpack.base.ts 文件:

      • 先安装依赖:
        • pnpm i @types/node -D
        • pnpm i style-loader css-loader html-webpack-plugin -D
      • 因为 webpack.base.ts 文件承载了基本的配置,随着 webpack 做的事情越来越多,会逐渐变得很庞大,我们可以将其中的 babel-loader 相关的配置抽离出来进行管理。在根目录新建babel.config.js :
      • 然后在 webpack.base.ts 文件中,就可以将 babel-loader 配置简化成:
module.exports = {                    
    // 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
    presets: [
        [
            "@babel/preset-env",
            {
                // 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
                    // "targets": {
                        // "chrome": 35,
                        // "ie": 9
                    // },
                targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
                useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入
                polyfill按需添加
                corejs: 3, // 配置使用core-js使用的版本
                loose: true,
            },
        ],
        // 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
        // 否则可能会出现错误:Uncaught ReferenceError: React is not defined
        ["@babel/preset-react", { runtime: "automatic" }],
        "@babel/preset-typescript",
    ],
};
    • 然后在 webpack.base.ts 文件中,就可以将 babel-loader 配置简化成:
// ...
module: {
    rules: [
        {
            test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
            use: "babel-loader"
        },
        // ...
    ],
},
// ...
  1. 我们需要通过 webpack-dev-server 来启动我们的项目,所以需要安装相关的依赖:

    pnpm i webpack-dev-server webpack-merge -D

    配置开发环境配置: webpack.dev.ts
    
import path from "path";       
import { merge } from "webpack-merge";
import { Configuration as WebpackConfiguration } from "webpack"; 
import { Configuration as WebpackDevServerConfiguration } from "webpack-devserver";
import baseConfig from "./webpack.base";
interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}
const host = "127.0.0.1";
const port = "8082";
// 合并公共配置,并添加开发环境配置
const devConfig: Configuration = merge(baseConfig, {
  mode: "development", // 开发模式,打包更加快速,省了代码优化步骤
  devtool: "eval-cheap-module-source-map",
  devServer: {
    host,
    port,
    open: true, // 是否自动打开
    compress: false, // gzip压缩,开发环境不开启,提升热更新速度
    hot: true, // 开启热更新
    historyApiFallback: true, // 解决history路由404问题
    setupExitSignals: true, // 允许在 SIGINT 和 SIGTERM 信号时关闭开发服务器和退出进程。
    static: {
      directory: path.join(__dirname, "../public"), // 托管静态资源public文件夹
    },
    headers: { "Access-Control-Allow-Origin": "*" }, // HTTP响应头设置,允许任何来源进行跨域请求
  },
});
export default devConfig;

然后再 package.json 中添加启动脚本:
"scripts": {
	"dev": "webpack serve -c build/webpack.dev.ts"
},
需要在 tsconfig.json 中加入一行 "jsx": "react-jsx"
  1. 配置 webpack.prod.ts :
import { Configuration } from "webpack"; 
import { merge } from "webpack-merge";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
	mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
});
export default prodConfig;
在 package.json 中添加:
"scripts": {
	// ...
	"build": "webpack -c build/webpack.prod.ts"
},

1.3 文件别名

  1. 先在 webpack.base.ts 中配置:
resolve: {
	extensions   : [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
	// 别名需要配置两个地方,这里和 tsconfig.json
	alias: {
		"@": path.join(__dirname, "../src")
	},
},
  1. 然后还需要在 tsconfig.json 中配置:
{
	"compilerOptions": {
		// ...
		"baseUrl": ".",
		"paths": {
			"@/*": ["src/*"]
		},
	},
}

2. CSS预处理器和其他资源处理

2.1 引入Less、Sass(SCSS)、Stylus

  • 在React中使用CSS Modules预处理器(Less、Sass、Stylus)具有多种优点:

    • 避免全局样式冲突。
    • 更好的可维护性,因为样式与组件代码紧密相关。
    • 提高代码可重用性,可以轻松地将样式从一个组件复制到另一个组件。
    • 支持动态样式,能够根据组件状态或属性更改样式。
    • 更好的性能,使用模块化加载样式提高了页面加载速度和性能。
  • 基本用法

    • 需要安装相关的依赖:pnpm i less less-loader sass-loader sass stylus stylus-loader -D
    • 在webpack配置文件中添加相应的loader
const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
const lessRegex = /\.less$/;
const stylRegex = /\.styl$/;

const styleLoadersArray = [
  "style-loader",
  {
    loader: "css-loader",
    options: {
      modules: {
        localIdentName: "[path][name]__[local]--[hash:5]",
      },
    },
  },
];

const baseConfig: Configuration = {
  // ... other configurations

  module: {
    rules: [
      // ... other rules
      {
        test: cssRegex, // 匹配css文件
        use: styleLoadersArray,
      },
      {
        test: lessRegex,
        use: [
          ...styleLoadersArray,
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // 如果要在less中写js的语法,需要加这一配置
                javascriptEnabled: true
              }
            }
          }
        ]
      },
      {
        test: sassRegex,
        use: [
          ...styleLoadersArray,
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass') // 使用dart-sass代替node-sass
            }
          }
        ]
      },
      {
        test: stylRegex,
        use: [
          ...styleLoadersArray,
          'stylus-loader'
        ]
      }
    ],
  },
  // ... other configurations
};

export default baseConfig;

2.2 处理其他常用资源

  • 对于图片文件, webpack4 使用 file-loader 和 url-loader 来处理的,但 webpack5 不使用这两个loader 了,而是采用自带的 asset-module 来处理,修改 webpack.base.ts ,添加图片解析配置
{
  output: {
    // ... 这里自定义输出文件名的方式是,将某些资源发送到指定目录
    assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
        // ...
        {
          test: /\.(png|jpe?g|gif|svg)$/i, // 匹配图片文件
          type: "asset", // type选择asset
          parser: {
            dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb转base64
          }
          },
          generator:{
            filename:'static/images/[hash][ext][query]', // 文件输出目录和命名
          },
      },
    ]
  }
}
  • 由于我们希望通过 ES6 的新语法 ESModule 的方式导入资源,为了使 TypeScript 可以识别图片模块,需要在 src/typings/global.d.ts 中加入声明:
// ...
/* IMAGES */
declare module '*.svg' {
  const ref: string;
  export default ref;
}
declare module '*.bmp' {
  const ref: string;
  export default ref;
}
declare module '*.gif' {
  const ref: string;
  export default ref;
}
declare module '*.jpg' {
  const ref: string;
  export default ref;
}
 declare module '*.jpeg' {
  const ref: string;
  export default ref;
}
declare module '*.png' {
  const ref: string;
  export default ref;
}

3. Babel处理JS非标准语法

  • 现在 react 主流开发都是函数组件和 react-hooks ,但有时也会用类组件,可以用装饰器简化代码。新增 src/components/Cls.tsx 组件,在 App.tsx 中引入该组件使用
import React, { PureComponent } from "react";     
// 装饰器为,组件添加age属性
function addAge(Target: Function) {
	Target.prototype.age = 111
}
// 使用装饰器
@addAge
class Cls extends PureComponent {
	age?: number
	render() {
		return (
			<h2>我是类组件---{this.age}</h2>
		)
	}
}
export default Cls
  • 需要开启一下 ts 装饰器支持,修改 tsconfig.json 文件
// tsconfig.json
{
	"compilerOptions": {
		// ...
		// 开启装饰器使用
		"experimentalDecorators": true
	}
}
  • 安装依赖:pnpm i @babel/plugin-proposal-decorators -D
  • 在 babel.config.js 中添加插件:
const isDEV = process.env.NODE_ENV === "development"; // 是否是开发模式
module.exports = {                             
	// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
	presets: [
		[
			"@babel/preset-env",
			{
				// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件,babel-loader会自动寻找上面配置好的文件.browserlistrc
				// "targets": {
					// "chrome": 35,
					// "ie": 9
				// },
				targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
				useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
				corejs: 3, // 配置使用core-js使用的版本
				loose: true,
			},
		],
		// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
		// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
		["@babel/preset-react", { runtime: "automatic" }],
		"@babel/preset-typescript",
	],
	plugins: [
		["@babel/plugin-proposal-decorators", { legacy: true }],
	].filter(Boolean), // 过滤空值
};

4. Webpack构建速度优化

4.1 构建进度条、耗时

  • webpackbar下载pnpm i webpackbar -D
    • 在 webpack.base.ts 中引入
// ...
import WebpackBar from 'webpackbar';
// ...
const baseConfig: Configuration = {
	// ...
	// plugins 的配置
	plugins: [
		// ...
		new WebpackBar({
			color: "#85d", // 默认green,进度条颜色支持HEX
			basic: false, // 默认true,启用一个简单的日志报告器
			profile:false, // 默认false,启用探查器。
		})
	],
};
export default baseConfig;
  • 构建耗时
    • 安装依赖:pnpm i speed-measure-webpack-plugin -D
    • 新增 webpack 构建分析配置文件 build/webpack.analy.ts
import { Configuration } from 'webpack'    
import prodConfig from './webpack.prod' // 引入打包配置
import { merge } from 'webpack-merge' // 引入合并webpack配置方法
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin(); // 实例化分析插件
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {

}))
export default analyConfig
  • scripts 新增:
{
	// ...
	"scripts": {
		// ...
		"build:analy": "cross-env NODE_ENV=production BASE_ENV=production webpack -cbuild/webpack.analy.ts"  
	}
	// ...
}

4.2 持久化存储缓存、多线程loader

  • 持久化存储缓存
    • 持久化存储缓存可以通过webpack的cache选项进行配置。此选项可用于配置缓存类型,包括内存缓存和文件缓存。
// webpack.base.ts
// ...
module.exports = { 
	// ...
	cache: {
		type: 'filesystem', // 使用文件缓存
	},
}
  • 开启多线程 loader
    • 运行在 Node.js 之上的 webpack 是单线程模式的,也就是说, webpack 打包只能逐个文件处理,当webpack 需要打包大量文件时,打包时间就会比较漫长。

    • 多进程/多实例构建的方案比较知名的有以下三种:

      • thread-loader
      • parallel-webpack
      • HappyPack
    • 安装依赖:pnpm i thread-loader -D

    • 使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的worker 池中运行。修改 webpack.base.ts:

module: {
	rules: [
		{
			test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
			use: [
				// 开启多进程打包。
				// 进程启动大概为600ms,进程通信也有开销。
				// 只有工作消耗时间比较长,才需要多进程打包
			{
				loader: 'thread-loader',
				options: {
					wokers: 4 // 进程数
			}
			},
			'babel-loader']
		},
	]
}

4.3 缩小构建目标、devtools配置

  • 缩小构建目标
    • 一般第三库都是已经处理好的,不需要再次使用 loader 去解析,可以按照实际情况合理配置 loader 的作用范围,来减少不必要的 loader 解析,节省时间,通过使用 include 和 exclude 两个配置项,可以实现这个功能,常见的例如:
      • include :只解析该选项配置的模块
      • exclude :不解该选项配置的模块,优先级更高
    • 修改 webpack.base.ts
module: {
	rules: [
		{
			test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
			exclude  : /node_modules/,
			use: [
				// 开启多进程打包。
				// 进程启动大概为600ms,进程通信也有开销。
				// 只有工作消耗时间比较长,才需要多进程打包
			{
			loader: 'thread-loader',
			options: {
				wokers: 4 // 进程数
			}
			},
			'babel-loader']
		},
	]
}
  • devtools 配置
    • 开发过程中或者打包后的代码都是 webpack 处理后的代码,如果进行调试肯定希望看到源代码,而不是编译后的代码, source map 就是用来做源码映射的,不同的映射模式会明显影响到构建和重新构建的速度, devtool 选项就是 webpack 提供的选择源码映射方式的配置。
    • devtool 的命名规则为:
      •   ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$
        
    • 打包环境推荐:none(就是不配置devtool选项了,不是配置devtool: ‘none’)
    • 修改 webpack.dev.ts
// webpack.dev.ts
module.exports = {   
	// ...
	devtool: 'eval-cheap-module-source-map'
}

5. Webpack构建产物优化

5.1 Bundle 体积分析工具、样式提取、样式压缩

  • bundle 体积分析工具
    • webpack-bundle-analyzer 是分析 webpack 打包后文件的插件,使用交互式可缩放树形图可视化webpack 输出文件的大小。通过该插件可以对打包后的文件进行观察和分析,可以方便我们对不完美的地方针对性的优化,安装依赖:
      • pnpm i webpack-bundle-analyzer -D
    • 修改 webpack.analy.ts :
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import prodConfig from "./webpack.prod";
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
// 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin();
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
	plugins: [
		new BundleAnalyzerPlugin() // 配置分析打包结果插件
	]
}))
export default analyConfig;
  • 样式提取
    • 在开发环境我们希望 css 嵌入在 style 标签里面,方便样式热替换,但打包时我们希望把 css 单独抽离出来,方便配置缓存策略。而插件mini-css-extract-plugin就是来帮我们做这件事的,安装依赖:pnpm i mini-css-extract-plugin -D
    • 修改 webpack.prod.ts
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')             
const prodConfig: Configuration = merge(baseConfig, {
	// ...
	plugins: [
		// ...
		new MiniCssExtractPlugin({
			filename: 'static/css/[name].css' // 抽离css的输出目录和名称
		}),
	],
});
export default prodConfig;
    • 修改 webpack.base.ts
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')               
const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
const styleLoadersArray = [
	isDev ? "style-loader" : MiniCssExtractPlugin.loader, // 开发环境使用stylelooader,打包模式抽离css
	{
		loader: "css-loader",
		options: {
			modules: {
				localIdentName: "[path][name]__[local]--[hash:5]",
			},
		},
	},
	'postcss-loader'
];
  • 样式压缩
    • 可以借助 css-minimizer-webpack-plugin 来压缩css,安装依赖:pnpm i css-minimizer-webpack-plugin -D
    • 修改 webpack.prod.ts 文件
// ...
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'     
module.exports = {
	// ...
	optimization: {
		minimizer: [
			new CssMinimizerPlugin(), // 压缩css
		],
	},
}

5.2 JS压缩、文件指纹、代码分割

  • js 压缩
    • 设置mode为production时,webpack会使用内置插件terser-webpack-plugin压缩js文件,该插件默认支持多线程压缩,但是上面配置optimization.minimizer压缩css后,js压缩就失效了,需要手动再添加一下,webpack内部安装了该插件,由于pnpm解决了幽灵依赖问题,如果用的pnpm的话,需要手动再安装一下依赖
      • pnpm i terser-webpack-plugin compression-webpack-plugin -D
    • 修改 webpack.prod.ts 文件:
import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
import TerserPlugin from "terser-webpack-plugin";
import CompressionPlugin from "compression-webpack-plugin";
import baseConfig from "./webpack.base";
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const prodConfig: Configuration = merge(baseConfig, {
  mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
  /**
   * 打包环境推荐:none(就是不配置devtool选项了,不是配置devtool: 'none')
   * ● none话调试只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。
   * ● 只是不方便线上排查问题, 但一般都可以根据报错信息在本地环境很快找出问题所在。
   */
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"), // 复制public下文件
          to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
          filter: (source) => !source.includes("index.html"), // 忽略index.html
        },
      ],
    }),
    new MiniCssExtractPlugin({
      filename: "static/css/[name].css", // 抽离css的输出目录和名称
    }),
    // 打包时生成gzip文件
    new CompressionPlugin({
      test: /\.(js|css)$/, // 只生成css,js压缩文件
      filename: "[path][base].gz", // 文件命名
      algorithm: "gzip", // 压缩格式,默认是gzip
      threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
      minRatio: 0.8, // 压缩率,默认值是 0.8
    }),
  ],
  optimization: {
    // splitChunks: {
    // chunks: "all",
    // },
    runtimeChunk: { name: "mainifels" },
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(), // 压缩css
      new TerserPlugin({
        parallel: true, // 开启多线程压缩
        terserOptions: {
          compress: {
            pure_funcs: ["console.log"], // 删除console.log
          },
        },
      }),
    ],
  },
  performance: {
    // 配置与性能相关的选项的对象
    hints: false, // 设置为false将关闭性能提示。默认情况下,Webpack会显示有关入口点和资产大小的警告和错误消息。将hints设置为false可以禁用这些消息。
    maxAssetSize: 4000000, // 设置一个整数,表示以字节为单位的单个资源文件的最大允许大小。如果任何资源的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为4000000字节(约4MB)。
    maxEntrypointSize: 5000000, // 设置一个整数,表示以字节为单位的入口点文件的最大允许大小。入口点是Webpack构建产生的主要JS文件,通常是应用程序的主要代码。如果入口点的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为5000000字节(约5MB)。
  },
});
export default prodConfig;

  • 文件指纹
    • 项目维护的时候,一般只会修改一部分代码,可以合理配置文件缓存,来提升前端加载页面速度和减少服务器压力,而 hash 就是浏览器缓存策略很重要的一部分。 webpack 打包的 hash 分三种:
      • hash :跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的hash 值
      • chunkhash :不同的入口文件进行依赖文件解析、构建对应的 chunk ,生成对应的哈希值,文件本身修改或者依赖文件修改, chunkhash 值会变化
      • contenthash :每个文件自己单独的 hash 值,文件的改动只会影响自身的 hash 值
    • 修改 webpack.base.ts
// webpack.base.ts
// ...
const baseConfig: Configuration = {
  // 打包文件出口
  output: {
    filename: 'static/js/[name].[chunkhash:8].js', // 加上[chunkhash:8]  
    // ...
  },
  module: {
    rules: [
    // ...
    {
      test: mediaRegex, // 匹配媒体文件
      // ...
      generator: {
        filename: 'static/media/[name].[contenthash:8][ext]' // 文件输出目录和命名
      }
    },
    {test: fontRegex, // 匹配字体图标文件
      // ...
      generator: {
       filename: 'static/json/[name].[contenthash:8][ext]' // 文件输出目录和命名
      }
    },
    {
      test: imageRegex, // 匹配图片文件
      // ...
      generator:{
        filename:'static/images/[name].[contenthash:8][ext]' // 加上[contenthash:8]
      },
    },
    {
    // 匹配json文件
    test: jsonRegex,
    type: 'json', // 模块资源类型为json模块
    generator: {
     filename: 'static/json/[name].[hash][ext][query]', // 专门针对json文件的处理
    }
  }
  ]
  // ...
}
    • 再修改 webpack.prod.ts
// webpack.prod.ts
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')   
module.exports = merge(baseConfig, {
	mode: 'production',
	plugins: [
		// 抽离css插件
		new MiniCssExtractPlugin({
			filename: 'static/css/[name].[contenthash:8].css' // 加上[contenthash:8]
		}),
		// ...
	],
	// ...
})
  • 代码分割
    • 一般第三方包的代码变化频率比较小,可以单独把 node_modules 中的代码单独打包,当第三包代码没变化时,对应 chunkhash 值也不会变化,可以有效利用浏览器缓存,还有公共的模块也可以提取出来,避免重复打包加大代码整体体积, webpack 提供了代码分隔功能,需要我们手动在优化项optimization 中手动配置下代码分割 splitChunks 规则。
    • 修改 webpack.prod.ts :
module.exports = {                       
// ...
	optimization: {
	// ...
		splitChunks: { // 分隔代码
			cacheGroups: {
				vendors: { // 提取node_modules代码
					test: /node_modules/, // 只匹配node_modules里面的模块
					name: 'vendors', // 提取文件命名为vendors,js后缀和chunkhash会自动加
					minChunks: 1, // 只要使用一次就提取出来
					chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
					minSize: 0, // 提取代码体积大于0就提取出来
					priority: 1, // 提取优先级为1
				},
				commons: { // 提取页面公共代码
					name: 'commons', // 提取文件命名为commons
					minChunks: 2, // 只要使用两次就提取出来
					chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
					minSize: 0, // 提取代码体积大于0就提取出来
				}
			}
		}
	}
}

5.3 Tree-shaking清理未引用JS和CSS

  • Tree-shaking原理
    • Tree-shaking是指通过静态分析,识别和清除未使用的代码(通常是未引用的模块或变量),从而减少最终打包输出的体积。在JavaScript中,这意味着删除未使用的导入模块和导出变量,以优化生产环境的代码。
    • Tree-shaking依赖于ES6模块系统的静态特性。由于ES6模块是静态的,意味着导入模块的关系在编译时就已经确定,因此工具可以通过静态分析来确定哪些模块和变量实际上被使用,然后将未使用的部分排除在最终的输出之外。
  • 清理未引用的JS
    • 在webpack中,可以使用Tree-shaking通过以下方式清理未引用的JS:
      • 使用ES6模块语法:确保代码按照ES6模块语法组织,以便Tree-shaking可以正常工作。
      • 在webpack配置中,通过mode选项设置为production,以启用相关的优化策略,包括Tree-shaking。
      • 确保生产模式下使用了UglifyJsPlugin或者TerserWebpackPlugin进行代码压缩,在压缩过程中会自动进行Tree-shaking优化。
  • 清理未引用的CSS
    • 对于CSS,可以通过以下方式清理未引用的样式:
      • 使用工具如PurgeCSS,它可以检测你的HTML和JS文件,并从CSS文件中删除未使用的样式。
      • 针对特定的CSS预处理器,比如在Sass中可以使用uncss等插件来执行类似的操作,删除未使用的样式。

5.4 资源懒加载、资源预加载、Gzip压缩

  • 资源懒加载

    • 资源懒加载是指在需要时才加载相关资源,通常用于优化页面加载性能。在webpack中,可以使用动态import()语法或React Suspense(搭配React.lazy)来实现组件级别的懒加载。示例:
      -const LazyComponent = React.lazy(() => import('./LazyComponent'));
  • 资源预加载

    • 资源预加载是指在页面初始化时提前加载一些可能在后续导航中需要的资源,以加速之后的页面加载。可以使用webpack的prefetch或preload来实现资源预加载。示例:
import(/* webpackPrefetch: true */ './SomeComponent');       
import(/* webpackPreload: true */ './AnotherComponent');
  • Gzip压缩
    • Gzip压缩是一种在服务器端对文件进行压缩传输的技术,可以显著减少文件的大小,提高传输速度。在webpack中,可以使用compression-webpack-plugin等插件来实现Gzip压缩。示例:
const CompressionPlugin = require('compression-webpack-plugin'); 

const webpackConfig = {
  // ... other configurations
  plugins: [
    new CompressionPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      minRatio: 0.8,
    }),
  ],
  // ... other configurations
};

6. 代码规范、语法检测和提交规范

6.1 EditorConfig、Prettier、stylelint和 ESLint

  • EditorConfig
    • 在项目中引入 editorconfig 是为了在多人协作开发中保持代码的风格和一致性。不同的开发者使用不同的编辑器或IDE,可能会有不同的缩进(比如有的人喜欢4个空格,有的喜欢2个空格)、换行符、编码格式等。甚至相同的编辑器因为开发者自定义配置的不同也会导致不同风格的代码,这会导致代码的可读性降低,增加代码冲突的可能性,降低了代码的可维护性。
    • 安装 EditorConfig for VS Code
    • 然后在插件的介绍页上点击设置的齿轮,并且选择Add to Workspace Recommendations,就可以将其加入清单。也可以直接开启 .vscode/extensions.json 进行编辑:{ "recommendations": ["editorconfig.editorconfig"] }
    • 新建 .editorconfig
# https://editorconfig.org
root = true # 设置为true表示根目录,控制配置文件 .editorconfig 是否生效的字段	[*] # 匹配全部文件,匹配除了 `/` 路径分隔符之外的任意字符串
charset = utf-8 # 设置字符编码,取值为 latin1,utf-8,utf-8-bom,
utf-16be 和 utf-16le,当然 utf-8-bom 不推荐使用
end_of_line = lf # 设置使用的换行符,取值为 lf,cr 或者 crlf
indent_size = 2 # 设置缩进的大小,即缩进的列数,当 indexstyle 取值 tab时,indentsize 会使用 tab_width 的值
indent_style = space # 缩进风格,可选space|tab
insert_final_newline = true # 设为true表示使文件以一个空白行结尾
trim_trailing_whitespace = true # 删除一行中的前后空格
[*.md] # 匹配全部 .md 文件
trim_trailing_whitespace = false
  • prettier
    • 安装 VS Code 插件和 prettier:pnpm i prettier -D
    • 在根目录下新建 .prettierrc.js 和 .prettierignore 文件:
// .prettierrc.js
module.exports = {            
	tabWidth: 2, // 一个tab代表几个空格数,默认就是2
	useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false
	printWidth: 100, // 一行的字符数,如果超过会进行换行
	semi: false, // 行尾是否使用分号,默认为true
	singleQuote: true, // 字符串是否使用单引号
	trailingComma: 'all', // 对象或数组末尾是否添加逗号 none| es5| all
	jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办
	bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
	arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号
}

// .prettierignore
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky

    • 配置 .vscode/settings.json
{
	"search.exclude" : {     
		"/node_modules": true,
		"dist": true,
		"pnpm-lock.yaml": true
	},
	"files.autoSave": "onFocusChange",
	"editor.formatOnSave": true,
	"editor.formatOnType": true,
	"editor.defaultFormatter": "esbenp.prettier-vscode",
	"[javascript]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[javascriptreact]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[typescript]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[typescriptreact]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[json]": {
		"editor.defaultFormatter": "vscode.json-language-features" 
	},
	"[html]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[markdown]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"javascript.validate.enable": false,
}
  • stylelint
    • 安装 stylelint 插件和依赖
      • pnpm i stylelint stylelint-config-css-modules stylelint-config-prettier stylelint-config-standard stylelint-order -D
    • 新建 .stylelintrc.js 和 .stylelintignore
module.exports = { 
  extends: [
    'stylelint-config-standard',
    'stylelint-config-prettier'
    // "stylelint-config-css-modules",
    // "stylelint-config-recess-order" // 配置stylelint css属性书写顺序插件,
  ],
  plugins: [
    "stylelint-scss",
    "stylelint-order",
    "stylelint-declaration-strict-value",
    "stylelint-no-unsupported-browser-features"
  ],
  rules: {
    "string-quotes": "single", // 指定字符串引号为单引号
    "at-rule-name-case": "lower", // @规则名称使用小写
    "number-leading-zero": "always", // 小数前面加零
    "block-opening-brace-space-before": "always", // 块级元素大括号之前需要空格
    "indentation": 2, // 使用两个空格作为缩进
    "linebreaks": "unix", // 使用Unix风格的换行符
    "at-rule-name-newline-after": "always", // @规则后需要换行
    "at-rule-name-space-after": "always", // @规则后需要空格
    /**
     * indentation: null, // 指定缩进空格
      "no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的
较低优先级的选择器
      "function-url-quotes": "always", // 要求或禁止 URL 的引号 "always(必须加上引
号)"|"never(没有引号)"
      "string-quotes": "double", // 指定字符串使用单引号或双引号
      "unit-case": null, // 指定单位的大小写 "lower(全小写)"|"upper(全大写)"
      "color-hex-case": "lower", // 指定 16 进制颜色的大小写 "lower(全小写)"|"upper(全
大写)"
      "color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简
写)"|"long(16进制扩写)"
      "rule-empty-line-before": "never", // 要求或禁止在规则之前的空行 "always(规则之前
必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有
一个空行)"|"never-multi-line(多行规则之前绝不能有空行。)"
      "font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表
中缺少通用字体族关键字
      "block-opening-brace-space-before": "always", // 要求在块的开大括号之前必须有一
个空格或不能有空白符 "always(大括号前必须始终有一个空格)"|"never(左大括号之前绝不能有空
格)"|"always-single-line(在单行块中的左大括号之前必须始终有一个空格)"|"never-single-
line(在单行块中的左大括号之前绝不能有空格)"|"always-multi-line(在多行块中,左大括号之前必须
始终有一个空格)"|"never-multi-line(多行块中的左大括号之前绝不能有空格)"
      "property-no-unknown": null, // 禁止未知的属性(true 为不允许)
      "no-empty-source": null, // 禁止空源码
      "declaration-block-trailing-semicolon": null, // 要求或不允许在声明块中使用尾随
分号 string:"always(必须始终有一个尾随分号)"|"never(不得有尾随分号)"
      "selector-class-pattern": null, // 强制选择器类名的格式
      "value-no-vendor-prefix": null, // 关闭 vendor-prefix(为了解决多行省略 -
webkit-box)
      "at-rule-no-unknown": null,
      "selector-pseudo-class-no-unknown": [
        true,
        {
          ignorePseudoClasses: ["global", "v-deep", "deep"]
        }
      ]
    }
     */
    'selector-class-pattern': [
      // 命名规范 -
      '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
      {
        message: 'Expected class selector to be kebab-case'
      }
    ],
    'string-quotes': 'double', // 单引号
    'at-rule-empty-line-before': null,
    'at-rule-no-unknown': null,
    'at-rule-name-case': 'lower', // 指定@规则名的大小写
    'length-zero-no-unit': true, // 禁止零长度的单位(可自动修复)
    'shorthand-property-no-redundant-values': true, // 简写属性
    'number-leading-zero': 'always', // 小数不带0
    'declaration-block-no-duplicate-properties': true, // 禁止声明快重复属性
    'no-descending-specificity': true, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器。 'selector-max-id': null, // 限制一个选择器中 ID 选择器的数量
    'max-nesting-depth': 10,
    'declaration-block-single-line-max-declarations': 1,
    'block-opening-brace-space-before': 'always',
    // 'selector-max-type': [0, { ignore: ['child', 'descendant', 'compounded']}],
    indentation: [
      2,
      {
        // 指定缩进  warning 提醒
        severity: 'warning'
      }
    ],
    'order/order': ['custom-properties', 'dollar-variables', 'declarations',
      'rules', 'at-rules'
    ],
    'order/properties-order': [
      // 规则顺序
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'float',
      'width',
      'height',
      'max-width',
      'max-height',
      'min-width',
      'min-height',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'margin-collapse',
      'margin-top-collapse',
      'margin-right-collapse',
      'margin-bottom-collapse',
      'margin-left-collapse',
      'overflow',
      'overflow-x',
      'overflow-y',
      'clip',
      'clear',
      'font',
      'font-family',
      'font-size',
      'font-smoothing',
      'osx-font-smoothing',
      'font-style',
      'font-weight',
      'line-height',
      'letter-spacing',
      'word-spacing',
      'color',
      'text-align',
      'text-decoration',
      'text-indent',
      'text-overflow',
      'text-rendering',
      'text-size-adjust',
      'text-shadow',
      'text-transform',
      'word-break',
      'word-wrap',
      'white-space',
      'vertical-align',
      'list-style',
      'list-style-type',
      'list-style-position',
      'list-style-image',
      'pointer-events',
      'cursor',
      'background',
      'background-color',
      'border',
      'border-radius',
      'content',
      'outline',
      'outline-offset',
      'opacity',
      'filter',
      'visibility',
      'size',
      'transform'
    ]
  }
}

*.js
*.tsx
*.ts
*.json
*.png
*.eot
*.ttf
.woff
src/styles/antd-overrides.less
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/

.vscode
public
.github
.husky

    • 配置 .vscode/settings.json
{
// ...
	"editor.codeActionsOnSave": {
		"source.fixAll.stylelint"  : true
	},
	"stylelint.validate": [
		"css",
		"less",
		"sass",
		"stylus",
		"postcss"
	]
}
  • ESLint 概述
    • ESLint 是一个开源的 JavaScript linting 工具,用于识别和报告代码中的问题,并确保代码的一致性。它通过规则模式匹配对 JavaScript 代码进行分析,发现可能的错误、质量问题以及风格不符合规范的代码。
    • 主要功能
      • 代码质量问题检测: 可发现代码中存在的潜在错误,如使用未声明变量、未使用的变量、修改 const 变量等。
      • 代码风格问题检测: 可用来统一团队的代码风格,例如加不加分号、使用 tab 还是空格、字符串使用单引号等。
      • 代码格式化: 具有部分代码格式化的功能,能够根据规则修复代码格式。
    • 文件匹配规则
      • ESLint 默认会匹配以下类型的文件:

*.tsx
*.ts
*.json
*.png
*.eot
*.ttf
*.woff
src/styles/antd-overrides.less
node_modules
dist
env

    • 配置文件:ESLint 可以使用 .eslintrc 或 package.json 中的 eslintConfig 字段来配置规则。
    • 安装 eslint :
      • pnpm i eslint eslint-config-airbnb eslint-config-standard eslint-friendlyformatter eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslintplugin-promise eslint-plugin-react-hooks eslint-plugin-react @typescripteslint/eslint-plugin @typescript-eslint/parser eslint-plugin-prettier eslintconfig-prettier -D
    • 新建 .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'airbnb-base',
    'eslint:recommended',
    'plugin:import/recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    // 最大警告数设置为 5
    // 'max-warnings': ['error', 5],
    // eslint (http://eslint.cn/docs/rules)
    'react/jsx-filename-extension': [
      'error',
      {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    ],
    'class-methods-use-this': 'off',
    'no-param-reassign': 'off',
    'no-unused-expressions': 'off',
    'no-plusplus': 0,
    'no-restricted-syntax': 0,
    'consistent-return': 0,
    '@typescript-eslint/ban-types': 'off',
    // "import/no-extraneous-dependencies": "off",
    '@typescript-eslint/no-non-null-assertion': 'off',
    'import/no-unresolved': 'off',
    'import/prefer-default-export': 'off', // 关闭默认使用 export default 方式导出
    'import/no-extraneous-dependencies': [
      'error',
      {
        devDependencies: true,
      },
    ],
    '@typescript-eslint/no-use-before-define': 0,
    'no-use-before-define': 0,
    '@typescript-eslint/no-var-requires': 0,
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
    'no-shadow': 'off',
    // "@typescript-eslint/no-var-requires": "off"
    'import/extensions': [
      'error',
      'ignorePackages',
      {
        '': 'never',
        js: 'never',
        jsx: 'never',
        ts: 'never',
        tsx: 'never',
      },
    ],
    // "no-var": "error", // 要求使用 let 或 const 而不是 var
    // "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
    // "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
    // "prefer-const": "off", // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
    // "no-irregular-whitespace": "off", // 禁止不规则的空白
    // // typeScript (https://typescript-eslint.io/rules)
    // "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
    // "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长
    // "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。
    // "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
    // "@typescript-eslint/ban-ts-ignore": "off", // 禁止使用 @ts-ignore
    // "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
    // "@typescript-eslint/explicit-function-return-type": "off", // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明
    // "@typescript-eslint/no-var-requires": "off", // 不允许在 import 语句中使用 require 语句
    // "@typescript-eslint/no-empty-function": "off", // 禁止空函数
    // "@typescript-eslint/no-use-before-define": "off", // 禁止在变量定义之前使用它们
    // "@typescript-eslint/ban-ts-comment": "off", // 禁止 @ts- 使用注释或要求在指令后进行描述
    // "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!)
    // "@typescript-eslint/explicit-module-boundary-types": "off", // 要求导出函数和类的公共类方法的显式返回和参数类型
    // // react (https://github.com/jsx-eslint/eslint-plugin-react)
    // "react-hooks/rules-of-hooks": "error",
    // "react-hooks/exhaustive-deps": "off"
  },
  settings: {
    react: {
      version: 'detect',
    },
    'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
    'import/parsers': {
      '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
      node: {
        paths: ['src'],
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
        moduleDirectory: ['node_modules', 'src/'],
      },
    },
  },
}

    • 新建 .eslintignore

node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky

    • 添加eslint语法检测脚本
      • package.json 的 scripts 中新增:
        • "lint:eslint": "eslint --fix --ext .js,.ts,.tsx ./src",
  • eslint 与 prettier 冲突
    • 安装两个依赖:pnpm i eslint-config-prettier eslint-plugin-prettier -D
    • 配置一下 .vscode/settings.json :
{
	// ...
	"eslint.enable": true,
	"editor.codeActionsOnSave": {
		"source.fixAll.eslint": true,
	},
}
 - .eslintrc.js 的 extends 中加入
module.exports = {       
	// ...
		extends: [
			// ...	
			'plugin:prettier/recommended', // <==== 增加一行 
		],
	// ...
}

6.2 Husky + lint-staged

  • 安装 Husky:

    pnpm i husky -D
    npx husky install
    
  • 配置 Husky 的 pre-commit 钩子:

    1. 生成 .husky 配置文件夹(如果项目中没有初始化 git ,需要先执行 git init
      npx husky add .husky/pre-commit "pnpm run pre-check"   
      
    2. 上述命令将在项目根目录生成 .husky 文件夹,并创建 pre-commit 文件,其中定义了执行 pnpm run pre-check 命令。这里的 pre-check 是自定义的用于代码检查的脚本。
  • 测试提交代码:
    提交代码进行测试,你会发现 pre-commit 钩子执行了 pnpm run pre-check,使用 ESLint 检测了 git 暂存区的文件,并且如果发现问题,则会阻止代码提交到本地仓库。

6.3 Commitlint和Commitizen

  • 安装 Commitlint
    首先安装 Commitlint:

    pnpm i @commitlint/cli -D
    
  • 使用 Commitlint
    安装完成后,配置 commitlint.config.js:

    module.exports = { 
      rules: {
        'header-min-length': [2, 'always', 10],
      }
    };
    

    然后执行 commitlint:

    echo 'foo' | npx commitlint
    

    当 message 信息为 “foo” 时,由于长度只有 3,Commitlint 会视为违规并输出错误提示。

  • 配置规则包
    为了节省配置规则的时间,可以使用预先配置的规则包来设定多项规则。安装并在配置中指定使用规则包,如 @commitlint/config-conventional。

  • 使用 Husky 为 Commitlint 注册 Git Hooks
    使用 husky add 将指令加入 Git hooks,并且重新注册 Git hooks,这样当执行 git commit 时,Commitlint 将会检查 commit message。

  • Commitizen
    为了避免写出不符合规范的 commit message 而提交失败,可以使用 Commitizen 来启动设定的 adapter,通过问答的方式编写符合规范的 commit message。

  • cz-git
    指定提交文字规范,一款工程性更强、高度自定义、标准输出格式的 commitizen 适配器。

  • 配置 package.json
    在 package.json 中配置 commitizen 的 path,指定使用的 adapter路径。

    "config": {
      "commitizen": {
        "path": "node_modules/cz-git"
      }
    }
    
  • 配置 commitlint.config.js 文件:

// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
module.exports = {
  ignores: [commit => commit.includes('init')],
  extends: ['@commitlint/config-conventional'],
  rules: {
    // @see: https://commitlint.js.org/#/reference-rules
    'body-leading-blank': [2, 'always'],
    'footer-leading-blank': [1, 'always'],
    'header-max-length': [2, 'always', 108],
    'subject-empty': [2, 'never'],
    'type-empty': [2, 'never'],
    'subject-case': [0],
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'build',
        'ci',
        'chore',
        'revert',
        'wip',
        'workflow',
        'types',
        'release',
      ],
    ],
  },
  prompt: {
    messages: {
      type: "Select the type of change that you're committing:",
      scope: 'Denote the SCOPE of this change (optional):',
      customScope: 'Denote the SCOPE of this change:',
      subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
      body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
      breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
      footerPrefixsSelect: 'Select the ISSUES type of changeList by this change (optional):',
      customFooterPrefixs: 'Input ISSUES prefix:',
      footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
      confirmCommit: 'Are you sure you want to proceed with the commit above?',
      // 中文版
      // type: '选择你要提交的类型 :',
      // scope: '选择一个提交范围(可选):',
      // customScope: '请输入自定义的提交范围 :',
      // subject: '填写简短精炼的变更描述 :\n',
      // body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
      // breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
      // footerPrefixsSelect: '选择关联issue前缀(可选):',
      // customFooterPrefixs: '输入自定义issue前缀 :',
      // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
      // confirmCommit: '是否提交或修改commit ?',
    },
    types: [
      {
        value: 'feat',
        name: 'feat:       A new feature',
        emoji: '',
      },
      {
        value: 'fix',
        name: 'fix:        A bug fix',
        emoji: '',
      },
      {
        value: 'docs',
        name: 'docs:       Documentation only changes',
        emoji: '',
      },
      {
        value: 'style',
        name: 'style:      Changes that do not affect the meaning of the code',
        emoji: '',
      },
      {
        value: 'refactor',
        name: 'refactor: ♻    A code change that neither fixes a bug nor adds a feature',
        emoji: '♻ ',
      },
      {
        value: 'perf',
        name: 'perf:     ⚡   A code change that improves performance',
        emoji: '⚡ ',
      },
      {
        value: 'test',
        name: 'test:     ✅  Adding missing tests or correcting existing tests',
        emoji: '✅',
      },
      {
        value: 'build',
        name: 'build:        Changes that affect the build system or external dependencies',
        emoji: ' ',
      },
      {
        value: 'ci',
        name: 'ci:         Changes to our CI configuration files and scripts',
        emoji: '',
      },
      {
        value: 'chore',
        name: "chore:      Other changes that don't modify src or test files",
        emoji: '',
      },
      {
        value: 'revert',
        name: 'revert:   ⏪   Reverts a previous commit',
        emoji: '⏪ ',
      },
      // 中文版
      // { value: '特性', name: '特性:     新增功能', emoji: '' },
      // { value: '修复', name: '修复:     修复缺陷', emoji: '' },
      // { value: '文档', name: '文档:     文档变更', emoji: '' },
      // {
      //   value: '格式',
      //   name: '格式:     代码格式(不影响功能,例如空格、分号等格式修正)',
      //   emoji: '',
      // },
      // { value: '重构', name: '重构:   ♻   代码重构(不包括 bug 修复、功能新增)', emoji: '♻ ' },
      // { value: '性能', name: '性能:   ⚡   性能优化', emoji: '⚡ ' },
      // { value: '测试', name: '测试:   ✅  添加疏漏测试或已有测试改动', emoji: '✅' },
      // {
      //   value: '构建',
      //   name: '构建:      构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
      //   emoji: ' ',
      // },
      // { value: '集成', name: '集成:     修改 CI 配置、脚本', emoji: '' },
      // { value: '回退', name: '回退:   ⏪   回滚 commit', emoji: '⏪ ' },
      // {
      //   value: '其他',
      //   name: '其他:     对构建过程或辅助工具和库的更改(不影响源文件、测试用例)',
      //   emoji: '',
      // },
    ],
    useEmoji: true,
    themeColorCode: '',
    scopes: [],
    allowCustomScopes: true,
    allowEmptyScopes: true,
    customScopesAlign: 'bottom',
    customScopesAlias: 'custom',
    emptyScopesAlias: 'empty',
    upperCaseSubject: false,
    allowBreakingChanges: ['feat', 'fix'],
    breaklineNumber: 100,
    breaklineChar: '|',
    skipQuestions: [],
    issuePrefixs: [
      {
        value: 'closed',
        name: 'closed:   ISSUES has been processed',
      },
    ],
    customIssuePrefixsAlign: 'top',
    emptyIssuePrefixsAlias: 'skip',
    customIssuePrefixsAlias: 'custom',
    allowCustomIssuePrefixs: true,
    allowEmptyIssuePrefixs: true,
    confirmColorize: true,
    maxHeaderLength: Infinity,
    maxSubjectLength: Infinity,
    minSubjectLength: 0,
    scopeOverrides: undefined,
    defaultBody: '',
    defaultIssues: '',
    defaultScope: '',
    defaultSubject: '',
  },
}

  • 一键提交
    • 我们还可以通过一个script来集成之前所有的这些步骤:
"scripts": {
	// ...
	"commit": "git pull && git add -A && git-cz && git push",
}

总结

通过学习Webpack配置,我掌握了创建基本项目结构并成功引入React和TypeScript的方法。在Webpack配置方面,我深入了解了webpack.base.ts、webpack.dev.ts和webpack.prod.ts这些配置文件的编写方式,以及如何处理静态资源、配置环境变量和设置文件别名。此外,我还学会了引入Less、Sass(Scss)和Stylus,并处理CSS3前缀兼容以及其他常用资源的方法。在JavaScript方面,我了解了Babel如何处理非标准语法,以及如何优化Webpack的构建速度和产物。另外,在代码规范工具方面,我学习并掌握了使用EditorConfig、Prettier、stylelint和eslint来统一代码格式、规范提交行为,并检测代码质量。通过Husky和lint-staged,我成功优化了eslint检测速度,同时充分利用Commitlint和Commitizen规范提交信息,提高团队协作效率。这些收获使我更加熟练地应用Webpack进行项目构建和优化,同时能够确保项目代码的一致性和高质量,为团队协作和项目持续健康发展提供了重要支持。

你可能感兴趣的:(前端,javascript,webpack,typescript,node.js)