前端工程化系列 | (一) 配置自己的React项目模板

650 阅读14分钟

通常,一个中高级前端工程师,除了要完成业务功能开发目标外,还要对所开发项目的效率、性能、质量等工程化维度去制定和实施技术优化目标,其中以提升效率为目标的优化技术和工具就属于效率工程化的范畴。 对于公司而言,团队效率可以直接带来人工投入产出比的提升,因此效率提升通常会被作为技术层面的一个重点优化方向。而在面试中,对效率工程化的理解程度和实践中的优化产出情况,也是衡量前端工程师能力高低的常见标准。

当然上面的话都是官方话语,实际就是最近一段时间在搞个人的前端基建生态,包括一些项目模板,项目规范,组件库,工具函数库,以及脚手架等,所以就想用一系列文章来记录一下。好记性不如烂笔头,好好做笔记才是对将来的自己负责,我是深有感受的。

1.初始化项目

1.1.初始化package.json

在开始webpack配置之前,先手动初始化一个基本的React+TypeScript项目,新建项目文件夹 xyl-react-template , 在项目根目录下使用 npm init -y 生成package.json文件。

npm init -y

可以看到在项目根目录下,生成了一个package.json文件;接下来就需要确定项目的目录结构。

1.2.初始项目目录结构

├── config
|   ├── webpack.base.js # 公共配置
|   ├── webpack.dev.js  # 开发环境配置
|   └── webpack.prod.js # 打包环境配置
├── public
│   └── index.html # html模板
├── src
|   ├── App.tsx 
│   └── index.tsx # react应用入口页面
├── tsconfig.json  # ts配置
└── package.json
  • config:存放webpack编译配置文件
  • src:源码目录
  • public:静态文件托管目录

1.3.初始react配置

安装reactreact-dom依赖

npm i react react-dom -S

安装 @types/react@types/react-dom 依赖

npm i @types/react @types/react-dom -D

添加public/index.html内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>xyl-react-template</title>
  </head>
  <body>
   
    <div id="root"></div>
  </body>
</html>

添加tsconfig.json内容

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": false,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react", 
  },
   "include": [
    "src/**/*"
  ],
  "exclude": [
      "node_modules/**"
  ]
}

添加src/App.tsx内容

import React from 'react'

function App() {
  return <h2>xyl-react-template</h2>
}
export default App

添加src/index.tsx内容

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const root = document.getElementById('root');
if(root) {
  createRoot(root).render(<App />)
}

接下来,我们需要使用 webpack 将我们的源码打包。

2.webpack配置

2.1.webpack基础配置

首先安装依赖 webpackwebpack-cli

npm install webpack webpack-cli -D

项目入口出口配置

webpack.base.js文件中添加项目的入口出口等基本配置

// webpack.base.js
const path = require("path");

module.exports = {
  mode: "production",
  entry: path.resolve(__dirname, "../src/index.tsx"),
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "static/js/[name].[chunkhash:8].js",
    publicPath: "/", // 打包后文件的公共前缀路径
  },
};

js相关解析配置

由于webpack默认只能识别js文件,不能识别jsx语法,需要配置loader的预设,借助预设 @babel/preset-react 来识别jsx语法;但是由于我们还是用了ts语法,所以我们要先借助 @babel/preset-typescript 来将ts语法转换为 js 语法。

安装babel核心模块和babel预设

npm i babel-loader @babel/preset-react @babel/core @babel/preset-react @babel/preset-typescript core-js -D

webpack.base.js添加module.rules配置

// webpack.base.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
        use: ["babel-loader"]
      }
    ]
  }
}

在项目根目录下创建文件babel.config.js,将babel解析相关配置添加到文件中,如下:

module.exports = {
  // 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
  presets: [
    [
      '@babel/preset-env',
      {
        // 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
        // "targets": {
        //  "chrome": 35,
        //  "ie": 9
        // },
        useBuiltIns: 'usage', // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
        corejs: 3 // 配置使用core-js使用的版本
      }
    ],
    '@babel/preset-react',
    '@babel/preset-typescript'
  ],
};
  • 配置extensions(解析文件扩展名)

extensionswebpackresolve解析配置下的选项,在引入模块时不带文件后缀时,会来该配置数组里面依次添加后缀查找文件,因为ts不支持引入以 .ts, .tsx为后缀的文件,所以要在extensions中配置,而第三方库里面很多引入js文件没有带后缀,所以也要配置下js。

修改webpack.base.js文件配置

// webpack.base.js
module.exports = {
  // ...
  resolve: {
    extensions: ['.tsx', '.ts','.js', '.jsx']
  }
}

html模板文件管理配置

通过html-webpack-plugin可以简化 html 文件的创建,以便为webpack提供服务。

安装依赖:

npm i html-webpack-plugin -D

这个插件会生成一个html文件,在body中使用script标签自动引入你所有webpack生成的bundle,只需要添加这个插件到webpack的plugins中,如下:

// webpack.base.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'), // 模板取定义root节点的模板
      inject: true, // 自动注入静态资源
    })
  ]
}

修改package.json文件配置:

 "scripts": {
    "build": "webpack -c config/webpack.base.js"
  },

执行npm run build,会发现此时 dist 目录中就多了index.html文件,并且还自动帮我们引入了打包后的文件模块,打包后的dist文件可以在本地借助node服务器serve打开,全局安装serve

npm i serve -g

然后在项目根目录命令行执行serve -s dist,就可以启动打包后的项目了。

image.png

样式文件解析配置

对于样式文件的解析,webpack本身也是不支持的,但是提供了loader让我们去配置使用解析能力。

安装依赖:

npm install style-loader css-loader less-loader postcss-loader postcss autoprefixer --save-dev
  • less-loader:将less编译为css
  • css-loader:对@import和url()进行处理,就像js解析import/require()一样。
  • style-loader:将css插入到DOM中
  • postcss-loader,postcss和autoprefixer一起处理css浏览器兼容问题。

配置如下:

 rules: [
 	 //...
      {
        test: /.css$/,
        include: [path.resolve(__dirname, "../src")],
        use: ["style-loader", "css-loader","postcss-loader"],
      },
      {
        test: /.less$/,
        include: [path.resolve(__dirname, "../src")],
        use: ["style-loader", "css-loader","postcss-loader", "less-loader"],
      },
    ],

在项目根目录下,创建文件postcss.config.js,添加如下配置:

module.exports = {
  plugins: [require("autoprefixer")],
};

在项目根目录下执行npx browserslist,这个,命令是查看默认条件下筛选出来的一些浏览器(默认条件就是:市场占有率大于百分之五,并且没有死掉,如果这个浏览器版本24个月没有更新,那么就相当于死掉)

在项目根目录下,创建配置文件.browserslistrc文件,这个文件是用来筛选需要兼容的浏览器的,我们自定义我们自己的配置

IE 9 # 兼容IE 9
chrome 35 # 兼容chrome 35

重新执行npx browserslist,可以看到跟默认的列表不一样了,这就表明我们的配置生效了。

现在在src目录下添加App.less文件,并添加以下代码:

#root {
  h2 {
    font-size: 20px;
    transform: translateY(100px);
  }
}

在App.tsx文件中引入样式文件,重新编译,查看浏览器。可以看到对于css3的新特性都自动加上了浏览器兼容前缀。

image.png

静态资源解析配置

静态资源解析配置的基本策略就是,当文件小于最大体积的时候,就采用bse64的方式将静态文件转成base64打包到代码中,否则就会直接被生成到输出的构建产物目录中。

webpack5自身已经支持静态资源解析,不需要额外的loader,配置如下:

  • 处理图片文件

修改webpack.base.js,添加图片解析配置;

module.exports = {
  module: {
    rules: [
      // ...
      {
        test:/.(png|jpg|jpeg|gif|svg)$/, // 匹配图片文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 20  * 1024, // 小于20kb转base64位
          }
        },
        generator:{ 
          filename:'static/images/[name][ext]', // 文件输出目录和命名
        },
      },
    ]
  }
}

测试:准备一张小于20kb和大于20kb的图片,放在src/assets/imgs目录下, 修改App.tsx

import React from "react";
import "./App.less";
import sml from './assets/imgs/sml.jpeg'
import big from './assets/imgs/big.png'

function App() {
  return (
    <>
      <h2>xyl-react-template</h2>
      <img src={sml} alt="小于20kb的图片" />
      <img src={big} alt="大于于20kb的图片" />
    </>
  );
}
export default App;

这个时候在引入图片的地方会报错

image.png

这是因为ts语法不支持这些图片类型,我们需要添加一个图片的声明文件。

新增 src/images.d.ts 文件,添加内容;

declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module '*.less'
declare module '*.css'

添加图片声明文件后,就可以正常引入图片了, 然后执行npm run build打包,借助serve -s dist查看效果,可以看到可以正常解析图片了,并且小于 20kb 的图片被转成了 base64 位格式的。如下图:

image.png

  • 处理字体和媒体文件

webpack.base.js文件:

// webpack.base.js
module.exports = {
  module: {
    rules: [
      // ...
      {
        test:/.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 20 * 1024, // 小于20kb转base64位
          }
        },
        generator:{ 
          filename:'static/fonts/[name][ext]', // 文件输出目录和命名
        },
      },
      {
        test:/.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 20 * 1024, // 小于20kb转base64位
          }
        },
        generator:{ 
          filename:'static/media/[name][ext]', // 文件输出目录和命名
        },
      },
    ]
  }
}

配置环境变量

区分开发环境还是生产环境,可以用 process.env.NODE_ENV ,设置环境变量可以借助cross-envwebpack.DefinePlugin来设置。

  • cross-env:兼容各系统的设置环境变量的包
  • webpack.DefinePlugin:webpack 内置的插件,可以为业务代码注入环境变量

安装cross-env

npm i cross-env -D

修改package.jsonscripts脚本字段,改为如下配置:

"scripts": {
       "build": "cross-env NODE_ENV=production webpack -c config/webpack.base.js"
    },

webpack 会自动根据设置的 mode字段来给process.env.NODE_ENV 环境变量注入对应的 developmentproduction ,这里在命令中再次设置环境变量 NODE_ENV 是为了在 webpackbabel 的配置文件中访问到。

webpack.base.js中打印一下设置的环境变量

// webpack.base.js
// ...
console.log('NODE_ENV', process.env.NODE_ENV)

执行 npm run build ,可以看到打印的信息

这里需要把 process.env.NODE_ENV 注入到业务代码里面,就可以通过该环境变量设置对应环境的接口地址和其他数据,要借助webpack.DefinePlugin插件

修改 webpack.base.js

// webpack.base.js
// ...
const webpack = require('webpack')
module.export = {
  // ...
  plugins: [
    // ...
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
  ]
}

配置后会把值注入到业务代码里面去

src/App.tsx 打印一下环境变量

// src/App.tsx
// ...
console.log('NODE_ENV', process.env.NODE_ENV)

2.2.webpack开发环境配置

webpack-dev-server配置

开发环境配置代码在webpack.dev.js中,通过webpack-dev-server的这些配置提供一个本地服务,还需要依赖webpack-merge来合并公共配置。

npm i webpack-dev-server webpack-merge -D

修改webpack.base.js,删除mode属性。

修改webpack.dev.js代码, 合并公共配置,并添加开发环境配置。

// webpack.dev.js
const path = require('path')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')


module.exports = merge(baseConfig, {
  mode: 'development',
  devServer: {
    open: true,//告诉 dev-server 在服务器已经启动后打开浏览器。设置其为 `true` 以打开你的默认浏览器
    port: 3000, // 服务端口号
    compress: false, // gzip压缩,开发环境不开启,提升热更新速度
    hot: true, // 开启热更新,后面会讲react模块热替换具体配置
    proxy: {
      '/api': 'http://localhost:8080', //服务代理配置
    },
    static: {
      directory: path.join(__dirname, "../public"), //托管静态资源public文件夹
      publicPath: '/', //告诉服务器在哪个 URL 上提供static.directory的内容
    }
  }
})

修改package.json文件配置:

"scripts": {
    "build": "cross-env NODE_ENV=production webpack -c config/webpack.base.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server -c config/webpack.dev.js"
  },

执行npm run dev,就能看到项目已经启动起来了,访问 http://localhost:3000/

模块热替换

热更新上面在 devServer 中配置hottrue就已经开启了。

现在开发模式下修改cssless文件,页面样式可以在不刷新浏览器的情况实时生效,因为此时样式都在style标签里面,因为style-loader做了替换样式的热替换功能。但是修改App.tsx,浏览器会自动刷新后再显示修改后的内容,但我们想要的不是刷新浏览器,而是在不需要刷新浏览器的前提下模块热更新,并且能够保留react组件的状态。

可以借助@pmmmwh/react-refresh-webpack-plugin插件来实现,该插件又依赖于react-refresh, 安装依赖:

npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D

配置react热更新插件,修改webpack.dev.js

// webpack.dev.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = merge(baseConfig, {
  // ...
  plugins: [
    new ReactRefreshWebpackPlugin(), // 添加热更新插件
  ]
})

babel配置react-refesh刷新插件,修改babel.config.js文件

const isDEV = process.env.NODE_ENV === 'development' // 是否是开发模式
module.exports = {
  // ...
  "plugins": [
    isDEV && require.resolve('react-refresh/babel'), // 如果是开发模式,就启动react热更新插件
    // ...
  ].filter(Boolean) // 过滤空值
}

source-map配置

Devtool 选项可以控制是否生成,以及如何生成 source map。

  • 对于开发环境

以下选项非常适合开发环境:

eval - 每个模块都使用 eval() 执行,并且都有 //# sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。

eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

eval-cheap-source-map - 类似 eval-source-map,每个模块使用 eval() 执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。

eval-cheap-module-source-map - 类似 eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。

我们通常将开发环境的devtool取值设为 eval-cheap-module-source-map,该配置能保留预处理前的原始代码信息,并且打包速度也不慢,是最佳选择。

  • 对于生产环境

这些选项通常用于生产环境中,生产环境通常不使用source-map,因为source-map会有泄漏原始代码的风险。

(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

devtool的命名规则为 ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$

关键字描述
inline代码内通过 dataUrl 形式引入 SourceMap
hidden生成 SourceMap 文件,但不使用
evaleval(...) 形式执行代码,通过 dataUrl 形式引入 SourceMap
nosources不生成 SourceMap
cheap只需要定位到行信息,不需要列信息
module展示源代码中的错误位置

修改webpack.dev.js,添加devtool为 'eval-cheap-module-source-map'。

const path = require("path");
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.js");
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = merge(baseConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map"
 ///...
});

2.3.webpack生产环境配置

修改package.json文件配置:

"scripts": {
    "build": "cross-env NODE_ENV=production webpack -c config/webpack.prod.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server -c config/webpack.dev.js"
  },

修改webpack.prod.js代码

// webpack.prod.js

const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.js");

module.exports = merge(baseConfig, {
  mode: 'production', // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
})

执行npm run build,最终打包在dist文件中, 打包结果:

dist                    
├── static
|   ├── js
|     ├── main.js
├── index.html

清除打包文件

由于遗留了之前的构建产物,我们的 /dist 文件夹显得相当杂乱。webpack 将生成文件并放置在 /dist 文件夹中,但是它不会追踪哪些文件是实际在项目中用到的。

通常比较推荐的做法是,在每次构建前清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项实现这个需求。

// webpack.base.js
output: {
   ///...
    clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
    
  },

复制public文件夹

一般public文件夹都会放一些静态资源,可以直接根据绝对路径引入,不需要webpack进行解析,只需要打包的时候把public下内容复制到构建目录中,可以借助copy-webpack-plugin插件。

安装依赖

npm i copy-webpack-plugin -D

开发环境已经在 devServer 中配置了 static 托管了 public 文件夹,在开发环境使用绝对路径可以访问到 public 下的文件,但打包构建时不做处理会访问不到,所以现在需要在打包配置文件webpack.prod.js中新增 copy 插件配置。

// webpack.prod.js
// ..
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin');
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.js");

module.exports = merge(baseConfig, {
  mode: 'production',
  plugins: [
    // 复制文件插件
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../public'), // 复制public下文件
          to: path.resolve(__dirname, '../dist'), // 复制到dist目录中
          filter: source => {
            return !source.includes('index.html') // 忽略index.html
          }
        },
      ],
    }),
  ]
})

在上面的配置中,忽略了index.html,因为html-webpack-plugin会以 public 下的index.html为模板生成一个index.htmldist 文件下,所以不需要再复制该文件了。

测试一下,在 public 中新增一个favicon.ico图标文件,在index.html中引入

<!DOCTYPE html>
<html lang="en">
<head>

  <meta charset="UTF-8">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
  <title>xyl-react-template</title>
</head>
<body>
  <!-- 容器节点 -->
  <div id="root"></div>
</body>
</html>

再执行npm run build打包,就可以看到 public 下的 favicon.ico 图标文件被复制到 dist 文件中了。

2.4.优化构建速度

构建耗时分析

当进行优化的时候,肯定要先知道时间都花费在哪些步骤上了,而 speed-measure-webpack-plugin 插件可以帮我们做到,安装依赖:

npm i speed-measure-webpack-plugin -D

使用的时候为了不影响到正常的开发/打包模式,我们选择新建一个配置文件,新增 webpack 构建分析配置文件config/webpack.analy.js

const prodConfig = require('./webpack.prod.js') // 引入打包配置
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin(); // 实例化分析插件
const { merge } = require('webpack-merge') // 引入合并webpack配置方法

// 使用smp.wrap方法,把生产环境配置传进去
module.exports = smp.wrap(merge(prodConfig, {

}))

修改package.json添加启动webpack打包分析脚本命令,在scripts新增:

{
  // ...
  "scripts": {
    // ...
    "analy": "cross-env NODE_ENV=production webpack -c config/webpack.analy.js"
  }
  // ...
}

执行npm run analy命令,可以看到各pluginloader的耗时时间,现在因为项目内容比较少,所以耗时都比较少,在真正的项目中可以通过这个来分析打包时间花费在什么地方,然后来针对性的优化。

image.png

开启持久化缓存

webpack5 较于 webpack4,新增了持久化缓存、改进缓存算法等优化,通过配置 webpack 持久化缓存,来缓存生成的 webpack 模块和 chunk,改善下一次打包的构建速度,可提速 90% 左右,配置也简单。

修改webpack.base.js

// webpack.base.js
// ...
module.exports = {
  // ...
  cache: {
    type: 'filesystem', // 使用文件缓存
  },
}

当前文章代码的测试结果

模式第一次耗时第二次耗时
启动开发模式2869毫秒687毫秒
启动打包模式5455毫秒552毫秒

通过开启webpack5持久化存储缓存,再次打包的时间提升了90%

缓存的存储位置在node_modules/.cache/webpack,里面又区分了developmentproduction缓存

image.png

开启多线程loader

webpackloader默认在单线程执行,现代电脑一般都有多核cpu,可以借助多核cpu开启多线程loader解析,可以极大地提升loader解析的速度,thread-loader就是用来开启多进程解析loader的,安装依赖

npm i thread-loader -D

使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。

修改webpack.base.js

// webpack.base.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/,
        use: ['thread-loader', 'babel-loader']
      }
    ]
  }
}

开启多线程也是需要启动时间,大约600ms左右,所以适合规模比较大的项目。

缩小loader作用范围

一般第三库都是已经处理好的,不需要再次使用loader去解析,可以按照实际情况合理配置loader的作用范围,来减少不必要的loader解析,节省时间,通过使用 includeexclude 两个配置项,可以实现这个功能,常见的例如:

  • include:只解析该选项配置的模块
  • exclude:不解该选项配置的模块,优先级更高

修改webpack.base.js

// webpack.base.js
const path = require('path')
module.exports = {
  // ...
  module: {
    rules: [
      {
        include: [path.resolve(__dirname, '../src')], 只对项目src文件的ts,tsx进行loader解析
        test: /.(ts|tsx)$/,
        use: ['thread-loader', 'babel-loader']
      }
    ]
  }
}

其他loader也是相同的配置方式,如果除src文件外也还有需要解析的,就把对应的目录地址加上就可以了,比如需要引入antdcss,可以把antd的文件目录路径添加解析css规则到include里面。

缩小模块搜索范围

node里面模块有三种

  • node核心模块
  • node_modules模块
  • 自定义文件模块

使用requireimport引入模块时如果有准确的相对或者绝对路径,就会去按路径查询,如果引入的模块没有路径,会优先查询node核心模块,如果没有找到会去当前目录下node_modules中寻找,如果没有找到会查从父级文件夹查找node_modules,一直查到系统node全局模块。

这样会有两个问题,一个是当前项目没有安装某个依赖,但是上一级目录下node_modules或者全局模块有安装,就也会引入成功,但是部署到服务器时可能就会找不到造成报错,另一个问题就是一级一级查询比较消耗时间。可以告诉webpack搜索目录范围,来规避这两个问题。

修改webpack.base.js

// webpack.base.js
const path = require('path')
module.exports = {
  // ...
  resolve: {
     // ...
     modules: [path.resolve(__dirname, '../node_modules')], // 查找第三方模块只在本项目的node_modules中查找
  },
}

配置文件alias别名

webpack支持设置别名alias,设置别名可以让后续引用的地方减少路径的复杂度。

修改webpack.base.js

module.export = {
  // ...
   resolve: {
    // ...
    alias: {
      '@': path.join(__dirname, '../src')
    }
  }
}

修改tsconfig.json,添加baseUrlpaths

{
  "compilerOptions": {
    // ...
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  }
}

配置修改完成后,在项目中使用 @/xxx.xx,就会指向项目中src/xxx.xx, js/ts文件和css文件中都可以用。

src/App.tsx可以修改为

import React from "react";
import "./App.less";
import sml from '@/assets/imgs/sml.jpeg'
import big from '@/assets/imgs/big.png'

console.log("ssss1",process.env.NODE_ENV)
function App() {
  return (
    <>
      <h2>xyl-react-template</h2>
      <img src={sml} alt="小于20kb的图片" />
      <img src={big} alt="大于于20kb的图片" />
    </>
  );
}
export default App;

2.5.优化构建结果文件

webpack包分析工具

webpack-bundle-analyzer是分析webpack打包后文件的插件,使用交互式可缩放树形图可视化 webpack 输出文件的大小。通过该插件可以对打包后的文件进行观察和分析,可以方便我们对不完美的地方针对性的优化,安装依赖:

npm install webpack-bundle-analyzer -D

修改 webpack.analy.js

// webpack.analy.js
const prodConfig = require('./webpack.prod.js')
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
const { merge } = require('webpack-merge')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') // 引入分析打包结果插件
module.exports = smp.wrap(merge(prodConfig, {
  plugins: [
    new BundleAnalyzerPlugin() // 配置分析打包结果插件
  ]
}))

配置好后,执行npm run analy命令,打包完成后浏览器会自动打开窗口,可以看到打包文件的分析结果页面,可以看到各个文件所占的资源大小。

image.png

抽取css样式文件

在开发环境我们希望css嵌入在style标签里面,方便样式热替换,但生产环境时我们希望把css单独抽离出来,方便配置缓存策略。而插件mini-css-extract-plugin就是来帮我们做这件事的,安装依赖:

npm i mini-css-extract-plugin -D

修改webpack.base.js, 根据环境变量设置开发环境使用style-looader,打包模式抽离css

// webpack.base.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
module.exports = {
  // ...
  module: { 
    rules: [
      // ...
      {
        test: /.css$/, //匹配所有的 css 文件
        include: [path.resolve(__dirname, '../src')],
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader, // 开发环境使用style-looader,打包模式抽离css
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /.less$/, //匹配所有的 less 文件
        include: [path.resolve(__dirname, '../src')],
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader, // 开发环境使用style-looader,打包模式抽离css
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
    ]
  },
  // ...
}

再修改webpack.prod.js, 打包时添加抽离css插件

// webpack.prod.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(baseConfig, {
  mode: 'production',
  plugins: [
    // ...
    // 抽离css插件
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].css' // 抽离css的输出目录和名称
    }),
  ]
})

配置完成后,在开发模式css会嵌入到style标签里面,方便样式热替换,打包时会把css抽离成单独的css文件。

资源压缩

资源压缩主要是对js和css文件进行压缩,常用的方式有把整个文件或大段代码压缩成一行,把较长的变量名替换成较短的变量名,移除空格与空行等。

资源压缩的主要目的是减小文件体积,提升页面加载速度和降低带宽消耗等。资源压缩通常发生在生产环境打包的最后一个环节,本地开发环境不需要进行压缩处理。

压缩css文件

上面配置了打包时把css抽离为单独css文件的配置,打开打包后的文件查看,可以看到默认css是没有压缩的,需要手动配置一下压缩css的插件。

可以借助css-minimizer-webpack-plugin来压缩css,安装依赖

npm i css-minimizer-webpack-plugin -D

修改webpack.prod.js文件, 需要在优化项optimization下的minimizer属性中配置

// webpack.prod.js
// ...
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
  // ...
  optimization: {
    minimize:true,
    minimizer: [
      new CssMinimizerPlugin(), // 压缩css
    ],
  },
}

再次执行打包就可以看到css已经被压缩了

压缩js文件

设置modeproduction时,webpack会使用内置插件terser-webpack-plugin压缩js文件,该插件默认支持多线程压缩,但是上面配置optimization.minimizer压缩css后,js压缩就失效了,需要手动再添加一下,webpack内部安装了该插件。

修改webpack.prod.js文件

// ...
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
  // ...
  optimization: {
    minimiz:true,
    minimizer: [
      // ...
      new TerserPlugin({ // 压缩js
        parallel: true, // 开启多线程压缩
        terserOptions: {
          compress: {
            pure_funcs: ["console.log"] // 删除console.log
          }
        }
      }),
    ],
  },
}

注意:只有当optimization.minimize的值为true时,webpack才会使用optimization.minimizer里配置的压缩器进行压缩。

配置完成后再打包,cssjs就都可以被压缩了

gzip压缩

前端代码在浏览器运行,需要从服务器把html,css,js资源下载执行,下载的资源体积越小,页面加载速度就会越快。一般会采用gzip压缩,现在大部分浏览器和服务器都支持gzip,可以有效减少静态资源文件大小,压缩率在 70% 左右。

nginx可以配置gzip: on来开启压缩,但是只在nginx层面开启,会在每次请求资源时都对资源进行压缩,压缩文件会需要时间和占用服务器cpu资源,更好的方式是前端在打包的时候直接生成gzip资源,服务器接收到请求,可以直接把对应压缩好的gzip文件返回给浏览器,节省时间和cpu

webpack可以借助compression-webpack-plugin插件在打包时生成 gzip 文章,安装依赖

npm i compression-webpack-plugin -D

添加配置,修改webpack.prod.js

const CompressionPlugin  = require('compression-webpack-plugin')
module.exports = {
  // ...
  plugins: [
     // ...
     new CompressionPlugin({
      test: /.(js|css)$/, // 只生成css,js压缩文件
      filename: '[path][base].gz', // 文件命名
      algorithm: 'gzip', // 压缩格式,默认是gzip
      test: /.(js|css)$/, // 只生成css,js压缩文件
      threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
      minRatio: 0.8 // 压缩率,默认值是 0.8
    })
  ]
}

配置完成后再打包,可以看到打包后js的目录下多了一个 .gz 结尾的文件。

代码分割

代码分割是webpack优化中非常重要的一部分,webpack中主要有三种方法进行代码分割。

  1. 入口entry:配置entry入口文件,从而手动分割 代码。
  2. 动态加载:通过import等方法进行按需加载。
  3. 抽取公共代码:使用splitChunks等技术抽取公共代码。

我们一般使用的比较多的就是splitChunks抽取公共代码。

optimization.splitChunks

代码分割非常重要的一项技术就是optimization.splitChunks,splitChunks指的是webpack插件SplitChunksPlugin,在webpack的配置项optimization.splitChunks里直接配置即可,无须单独安装。

在webpack4之前,webpack是通过CommonsChunkPlugin插件来抽取公共代码,webpack4之后使用的是SplitChunksPlugin插件,在webpack5又对其进行了优化。

splitChunks的宗旨是通过一定的规则实现模块的自动提取。

一般第三方包的代码变化频率比较小,可以单独把node_modules中的代码单独打包, 当第三包代码没变化时,对应chunkhash值也不会变化,可以有效利用浏览器缓存,还有公共的模块也可以提取出来,避免重复打包加大代码整体体积,配置如下:

修改webpack.prod.js

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就提取出来
        }
      }
    }
  }
}

配置完成后执行打包,可以看到生成了3个js文件,node_modules 里面的模块被抽离到verdors.xxx.js中,其他公共代码被抽离到commons.xxx.js中,业务代码在main.xxx.js中。

摇树优化 Tree Shaking

Tree Shaking可以帮我们检测模块中没有用到的代码块,并且在webpack打包时将没有用到的代码块移除,减小打包后的资源体积。

当我们没有配置摇树机制时,对于一个文件中引入的模块但是没有使用到的,webpack依然会将其引入,这时候我们就需要配置Tree Shaking来移除这部分没有使用的代码

使用Tree Shaking需要两个步骤:

  1. 标注未使用的代码
  2. 对未使用的代码进行删除

对于开发环境我们只需要配置:

const path = require('path');
//...
const TerserPlugin = require('terser-webpack-plugin');
​
//...
 optimization: {
    usedExports: true,
    minimize: true,
    minimizer: [new TerserPlugin()]
  }
​
  • usedExports:用来标记未使用的代码。
  • TerserPlugin:webpack5自带插件,用来清除未使用的代码。

通常我们在开发环境是不会使用Tree Shaking的,因为他会降低构建速度并且对于开发环境也没有太大的意义;而在生产环境,只要配置参数mode为production就会自动开启Tree Shaking。

开启了Tree Shaking后,webpack会在打包时候删除大部分没有使用到的代码,但是有一些代码没有被其他模块导入使用,如polyfill.js,它主要用来扩展全局变量,这类代码是有副作用的代码,我们需要告诉webpack在Tree Shaking时不能删除它们。

要告诉webpack在Tree Shaking时不能删除某些文件,可以在package.json文件里使用sideEffects配置,如:

{
    "sideEffects":[
        "./polyfill.js"
    ] 
}

如果sideEffects的值为false就表示所有的代码都没有副作用可以安全的删除。

PurgeCSS清理未使用css

js中会有未使用到的代码,css中也会有未被页面使用到的样式。PurgeCSS 是一个用来删除未使用的 CSS 代码的工具。PurgeCSS 通过分析你的内容和 CSS 文件,首先它将 CSS 文件中使用的选择器与内容文件中的选择器进行匹配,然后它会从 CSS 中删除未使用的选择器,从而生成更小的 CSS 文件。

这个插件是和mini-css-extract-plugin插件配合使用的,此外还需要glob-all来选择要检测哪些文件里面的类名和 id 还有标签名称,mini-css-extract-plugin已经安装过了,不用重复安装了。

安装依赖:

npm i purgecss-webpack-plugin glob-all -D

修改webpack.prod.js

/ webpack.prod.js
// ...
const globAll = require('glob-all')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  // ...
  plugins: [
    // 抽离css插件
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].[contenthash:8].css'
    }),
    // 清理无用css
    new PurgeCSSPlugin({
      // 检测src下所有tsx文件和public下index.html中使用的类名和id和标签名称
      // 只打包这些文件中用到的样式
      paths: globAll.sync([
        `${path.join(__dirname, '../src')}/**/*.tsx`,
        path.join(__dirname, '../public/index.html')
      ]),
    }),
  ]
}

资源懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

react等单页应用打包默认会打包到一个js文件中,虽然使用代码分割可以把node_modules模块和公共模块分离,但页面初始加载还是会把整个项目的代码下载下来,其实只需要公共资源和当前页面的资源就可以了,其他页面资源可以等使用到的时候再加载,(比如与用户交互的时候才需要加载的某些资源)可以有效提升首屏加载速度。

webpack默认支持资源懒加载,只需要引入资源使用import语法来引入资源,webpack打包的时候就会自动打包为单独的资源文件,等使用到的时候动态加载。

以懒加载组件和css为例,新建懒加载组件src/components/LazyDemo.tsx

import React from "react";

function LazyDemo() {
  return <h3>我是懒加载组件组件</h3>
}

export default LazyDemo

修改App.tsx

import React, { lazy, Suspense, useState } from 'react'
const LazyDemo = lazy(() => import('@/components/LazyDemo')) // 使用import语法配合react的Lazy动态引入资源

function App() {
  const [ show, setShow ] = useState(false)
  
  // 点击事件中动态引入less, 设置show为true
  const onClick = () => {
    import('./App.less')
    setShow(true)
  }
  return (
    <>
      <h2 onClick={onClick}>展示</h2>
      {/* show为true时加载LazyDemo组件 */}
      { show && <Suspense fallback={null}><LazyDemo /></Suspense> }
    </>
  )
}
export default App

点击展示文字时,才会动态加载app.cssLazyDemo组件的资源。

资源预加载

上面配置了资源懒加载后,虽然提升了首屏渲染速度,但是加载到资源的时候会有一个去请求资源的延时,如果资源比较大会出现延迟卡顿现象,可以借助link标签的rel属性prefetchpreloadlink标签除了加载css之外也可以加载js资源,设置rel属性可以规定link提前加载资源,但是加载资源后不执行,等用到了再执行。

rel的属性值

  • preload是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。
  • prefetch是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源,会在空闲时加载。

与 prefetch 指令相比,preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。

对于当前页面很有必要的资源使用 preload ,对于可能在将来的页面中使用的资源使用 prefetch

webpack v4.6.0+ 增加了对预获取和预加载的支持,使用方式也比较简单,在import引入动态资源时使用webpack的魔法注释

// 单个目标
import(
  /* webpackChunkName: "my-chunk-name" */ // 资源打包后的文件chunkname
  /* webpackPrefetch: true */ // 开启prefetch预获取
  /* webpackPreload: true */ // 开启preload预获取
  './module'
);

懒加载、预加载、正常加载的区别

  • 懒加载:当文件需要用到的时候才会去加载,不用的时候不加载。
  • 正常加载:正常的时候是并发去加载,但是一般受到6个个数的限制。
  • 预加载:等其他资源加载完毕后,浏览器空闲了,再加载资源,但是不会运行这个文件。

选择文件hash类型

项目维护的时候,一般只会修改一部分代码,可以合理配置文件缓存,来提升前端加载页面速度和减少服务器压力,而hash就是浏览器缓存策略很重要的一部分。webpack打包的hash分三种:

  • hash:跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash
  • chunkhash:不同的入口文件进行依赖文件解析、构建对应的chunk,生成对应的哈希值,文件本身修改或者依赖文件修改,chunkhash值会变化
  • contenthash:每个文件自己单独的 hash 值,文件的改动只会影响自身的 hash

hash是在输出文件时配置的,格式是filename: "[name].[chunkhash:8][ext]" , [xx] 格式是webpack提供的占位符, :8是生成hash的长度。

占位符解释
ext文件后缀名
name文件名
path文件相对路径
folder文件所在文件夹
hash每次构建生成的唯一 hash 值
chunkhash根据 chunk 生成 hash 值
contenthash根据文件内容生成hash 值

因为js我们在生产环境里会把一些公共库和程序入口文件区分开,单独打包构建,采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响,可以继续使用浏览器缓存,所以js适合使用chunkhash

css和图片资源媒体资源一般都是单独存在的,可以采用contenthash,只有文件本身变化后会生成新hash值。

修改webpack.base.js,把js输出的文件名称格式加上chunkhash,把css和图片媒体资源输出格式加上contenthash

// webpack.base.js
// ...
module.exports = {
  // 打包文件出口
  output: {
    filename: 'static/js/[name].[chunkhash:8].js', // // 加上[chunkhash:8]
    // ...
  },
  module: {
    rules: [
      {
        test:/.(png|jpg|jpeg|gif|svg)$/, // 匹配图片文件
        // ...
        generator:{ 
          filename:'static/images/[name].[contenthash:8][ext]' // 加上[contenthash:8]
        },
      },
      {
        test:/.(woff2?|eot|ttf|otf)$/, // 匹配字体文件
        // ...
        generator:{ 
          filename:'static/fonts/[name].[contenthash:8][ext]', // 加上[contenthash:8]
        },
      },
      {
        test:/.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
        // ...
        generator:{ 
          filename:'static/media/[name].[contenthash:8][ext]', // 加上[contenthash:8]
        },
      },
    ]
  },
  // ...
}

再修改webpack.prod.js,修改抽离css文件名称格式

// webpack.prod.js
// ...
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]
    }),
    // ...
  ],
  // ...
})

再次打包就可以看到文件后面的hash了。

3.项目规范配置

3.1.配置eslint和prettier

第三方依赖

  • eslint
  • prettier
  • eslint-config-prettier
  • eslint-plugin-prettier
  • eslint-plugin-react
  • @typescript-eslint/eslint-plugin
  • @typescript-eslint/parser

在vscode插件市场下载两个插件,ESLint和Prettier,下载安装好。

在项目根目录下执行npx eslint --init或者npm init @eslint/config,然后根据提示选择配置

装好依赖后,eslint就生效了,由于我们还需要使用prettier对代码格式进行美化,所以我们还需要安装prettier相关依赖。

npm install prettier eslint-config-prettier eslint-plugin-prettier

eslint-config-prettier 和eslint-plugin-prettier这两个依赖是用于解决prettier规则和eslint规则冲突的,缺一不可。

  • eslint-config-prettier 的作用是关闭eslint中与prettier相互冲突的规则。
  • eslint-plugin-prettier 的作用是赋予eslint用prettier格式化代码的能力

在项目根目录创建.prettierrc文件,配置如下(这个配置根据自己的团队实际需要配置):

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 100,
  "singleQuote": true,
  "trailingComma": "none",
  "bracketSpacing": true,
  "semi": true
}

在.eslintrc.js文件中修改配置,继承prettier的格式规则

//...
extends: [
   	//...
    "plugin:prettier/recommended",
  ]
//...

在package.json中配置script脚本命令,

"script":{
	"eslint":"eslint src/**/*.{js,jsx,ts,tsx} --fix",
	//...
}

在终端执行npm run eslint 可以发现有问题的格式,有的已经被自动修复,需要手动修复的文件也已经提示出来了。我们按照文件逐个修复就OK,但是eslint只能够检测出语法的问题,是无法检测到ts的类型错误的,所以我们还需要通过另一个工具校验ts类型错误的。

3.2.配置typescript类型校验

在package.json文件的script脚本中配置:

"scripts": {
    "ts-check": "tsc",
	}

在终端执行脚本npm run ts-check,如果存在ts类型错误就会被抛出来

虽然目前可以校验语法错误,但是实际上我们还是可以把错误的语法格式提交到远程仓库,所以我们还需要在提交代码的时候去校验语法是否符合通过,如果不通过则不允许提交。

3.3.配置husky和lint-staged

安装lint-staged

npm i lint-staged -D

修改package.json中eslint的配置

 "eslint": "tsc&&eslint"

修package.json中添加lint-staged配置

 "lint-staged": {
    "src/**/*.{ts,tsx,js,jsx}": [
      "npm run eslint"
    ]
  },

因为要检测git暂存区代码,所以需要执行git init初始化一下git, git init

然后将有语法问题的代码提交到暂存区,如App.tsx,然后执行npx lint-staged

然后安装husky,在git钩子函数触发时校验语法

npm install husky -D

配置pre-commit钩子

生成 .husky配置文件夹

npx husky install

会在项目根目录生成 .husky文件夹,生成文件成功后,需要让husky支持监听pre-commit钩子,监听到后执行上面定义的npm run eslint语法检测。

npx husky add .husky/pre-commit 'npm run eslint'

然后提交代码进行测试

然后发现被拦截了

修正语法,重新提交,成功commit

使用husky和commitlint规范commit备注信息, commitlint文档

npm install --save-dev @commitlint/config-conventional @commitlint/cli

在根目录创建commitlint.config.js文件,继承@commitlint/config-conventional的规则,并且自定义comit备注规则,配置如下:

module.exports = {
  // 继承的规则
  extends: ['@commitlint/config-conventional'],
  // 定义规则类型
  rules: {
    // type 类型定义,表示 git 提交的 type 必须在以下类型范围内
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新功能 feature
        'fix', // 修复 bug
        'docs', // 文档注释
        'style', // 代码格式(不影响代码运行的变动)
        'refactor', // 重构(既不增加新功能,也不是修复bug)
        'perf', // 性能优化
        'test', // 增加测试
        'chore', // 构建过程或辅助工具的变动
        'revert', // 回退
        'build' // 打包
      ]
    ],
    // subject 大小写不做校验
    'subject-case': [0]
  }
}

添加hook

npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

在.husky目录下查看,新生成了commit-msg文件

测试提交备注信息

git commit -m "eeee"

根据commitlint规则,修正提交信息,重新commit,commit成功。到这里基本的规范就完成了,还有stylelint,没有使用,如果需要可以参考stylelint官网自行配置。

项目模板源码可参考xyl-react-template

如果有笔误,或者对于某些知识理解不到位的地方,也希望大家能不吝赐教!