webpack5从零搭建完整的react18+ts项目模板

一、初始化项目

先手动初始化一个react + ts 项目。新建文件夹template_react_ts,在项目中翻开终端。履行:

npm init -y

指令履行之后能够看到文件夹的根目录多了一个package.json文件,初始化好package.json后,在项目下新增以下所示目录结构和文件

├── build
| ├── webpack.base.js # 公共装备 
| ├── webpack.dev.js # 开发环境装备 
| └── webpack.prod.js # 打包环境装备 
├── public 
│ └── index.html # html模板 
├── src 
| ├── App.tsx 
│ └── index.tsx # react运用进口页面 
├── tsconfig.json # ts装备 
└── package.json

1、装置webpack依靠

npm i webpack webpack-cli -D
or
yarn add webpack webpack-cli -D

2、装置react依靠

npm i react react-dom -S
or
yarn add react react-dom -S

3、装置react类型依靠

npm i @types/react @types/react-dom -D
or
yarn add @types/react @types/react-dom -D

4、添加public/index.html内容

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack5-react-ts</title>
</head>
<body>
  <!-- 容器节点 -->
  <div id="root"></div>
</body>
</html>

5、添加tsconfig.json内容

{
"compilerOptions": {  
"target": "es5",  
// 指定要包含在编译中的 library  
"lib": [  
"dom",  
"dom.iterable",  
"esnext"  
],  
// 答应 ts 编译器编译 js 文件  
"allowJs": true,  
// 越过类型声明文件的类型查看  
"skipLibCheck": true,  
// es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异  
"esModuleInterop": true,  
// 答应经过 import x from 'y' 即便模块没有显式指定 default 导出  
"allowSyntheticDefaultImports": true,  
// 敞开严厉方法  
"strict": true,  
// 对文件称号强制区分大小写  
"forceConsistentCasingInFileNames": true,  
// 为 switch 句子启用过错陈述  
"noFallthroughCasesInSwitch": true,  
// 生成代码的模块化标准  
"module": "esnext",  
// 模块解析(查找)策略  
"moduleResolution": "node",  
// 答应导入扩展名为.json的模块  
"resolveJsonModule": true,  
// 是否将没有 import/export 的文件视为旧(大局而非模块化)脚本文件  
"isolatedModules": true,  
// 编译时不生成任何JS文件(只进行类型查看)  
"noEmit": true,  
// 指定将 JSX 编译成什么方法  
"jsx": "react-jsx"  
},  
// 指定答应ts处理的文件目录  
"include": [  
"src"  
]  
}

6、添加src/App.tsx内容

import React from 'react'
function App() {  
return (  
<div>  
<h2>template_react_ts</h2>  
</div>  
)  
}  
export default App

7、添加 src/index.tsx 内容

import React from "react";
import ReactDOM from "react-dom/client";  
import App from "./App";  
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);  
root.render(  
<React.StrictMode>  
<App />,  
</React.StrictMode>,  
);

到此项目事务代码现已添加好了,接下来就要开始装备webpack的代码

二、装备根底版React+ts环境

1、webpack公共装备

修正webpack.base.js

装备进口文件

// webpack.base.js 
const path = require('path') 
module.exports = {  
entry: path.join(__dirname, '../src/index.tsx'), // 进口文件  
}

装备出口文件

// webpack.base.js 
const path = require("path")  
module.exports = {  
......
// 打包文件出口  
output: {  
filename: 'static/js/[name].js', // 每个输出js的称号  
path: path.join(__dirname, '../dist'), // 打包成果输出途径  
clean: true, // webpack4需求装备clean-webpack-plugin来删去dist文件,webpack5内置了  
publicPath: '/' // 打包后文件的公共前缀途径  
},  
}

装备loader解析ts和jsx

webpack默许只能辨认js文件,不能辨认jsxts语法,需求装备loader的预设@babel/preset-typescript来先将TS语法转化成JS语法,再经过@babel/preset-react来辨认jsx语法。

装置babel中心模块和preset预设:

npm i babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D
or
yarn add babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D

webpack.base.js中添加module.rules装备:

// webpack.base.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
        use: {
          loader: 'babel-loader',
          options: {
            // 预设履行次序由右往左,所以先处理ts,再处理jsx
            presets: [
              '@babel/preset-react',
              '@babel/preset-typescript'
            ]
          }
        }
      }
    ]
  }
}

装备extensions

extebsions是webpack的resolve解析装备下的选项,在==引进模块时不带入文件后缀==的时分,会在该装备数组中依次添加后缀查找文件。因为ts不支撑引进以.ts.tsx为后缀的文件,所以要在extensions中要装备,在许多第三方库中里边许多引进js文件且没有带后缀,所以也要装备下js。

修正webpack.base.js,注意要把高频呈现的文件后缀放在前面:

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

这儿只装备了js、tsx、ts,其他引进的文件都要求带后缀,这样能够提高构建的速度

添加 html-webpack-plugin 插件

webpack需求把终究构建好的静态资源都引进到HTML文件中,这样才能在浏览器中运转,html-webpack-plugin插件便是用来做这个的,首要装置依靠:

npm i html-webpack-plugin -D
or
yarn add html-webpack-plugin -D

因为html-webpack-plugin在开发和构建打包方法都会用到,所以仍是在公共装备文件webpack.base.js里边装备

// 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, // 主动注入静态资源
    })
  ]
}

到这儿一个最根底的react根本公共装备就现已装备好了,可是项目开发是需求区分隔发环境和打包装备,因而需求在此根底上分别装备开发环境和打包环境。

2、webpack开发环境下的装备

装置webpack-dev-server

发环境装备代码在webpack.dev.js中,需求凭借webpack-dev-server在开发环境发动服务器来辅佐开发,还需求依靠webpack-merge来兼并根本装备,装置依靠:

npm i webpack-dev-server webpack-merge -D
or
yarn add webpack-dev-server webpack-merge -D

修正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', // 开发方法,打包更加快速,省了代码优化过程
  devtool: 'eval-cheap-module-source-map', // 源码调试方法,后边会讲
  devServer: {
    port: 3000, // 服务端口号
    compress: false, // gzip紧缩,开发环境不敞开,提高热更新速度
    hot: true, // 敞开热更新,后边会讲react模块热替换详细装备
    historyApiFallback: true, // 处理history路由404问题
    static: {
      directory: path.join(__dirname, "../public"), //保管静态资源public文件夹
    },
  }
})

package.json添加dev脚本

package.jsonscripts中添加

// package.json
"scripts": {
  "start": "webpack-dev-server -c build/webpack.dev.js"
},

履行npm run start,就能看到项目现已发动起来了,拜访http://localhost:3000/,就能够看到项目界面,详细完善的react模块热替换鄙人面会讲到。

3、webpack打包环境装备

修正webpack.prod.js代码

// webpack.prod.js
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
module.exports = merge(baseConfig, {
  mode: 'production', // 出产方法,会敞开tree-shaking和紧缩代码,以及其他优化
})

package.json添加build打包指令脚本

// package.json
"scripts": {
  "build": "webpack -c build/webpack.prod.js"
},

履行npm run build,终究打包在dist文件中, 打包成果:

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

打包之后的dist能够在本地凭借node服务器serve翻开,先大局装置serve依靠

nom i serve -g
or
yarn add serve -g

然后在项目根目录指令行履行serve -s dist,就能够发动打包后的项目了。

到现在一个根底的支撑reacttswebpack5根底项目就现已装备好了,但实践项目开发过程中这些装备是远远不够的,因而咱们还需求装置更多装备;

三、根底功用装备

1、装备环境变量

环境变量按作用来分分两种

  1. 区分是开发方法仍是打包构建方法
  2. 区分项目事务环境,开发/测验/预测/正式环境

区分隔发方法仍是打包构建方法能够用process.env.NODE_ENV,因为许多第三方包里边判别都是选用的这个环境变量。

区分项目接口环境能够自界说一个环境变量process.env.BASE_ENV,设置环境变量能够凭借cross-env和webpack.DefinePlugin来设置。

  • cross-env:兼容各体系的设置环境变量的包
  • webpack.DefinePluginwebpack内置的插件,能够为事务代码注入环境变量

装置cross-env依靠

npm i cross-env -D
or
yarn add cross-env -D

修正package.jsonscripts脚本字段,删去原先的start build,改为

"scripts": {
"start:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack-dev-server -c build/webpack.dev.js",  
"start:pre": "cross-env NODE_ENV=development BASE_ENV=preRelease webpack-dev-server -c build/webpack.dev.js",  
"start:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack-dev-server -c build/webpack.dev.js",  
"build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack -c build/webpack.prod.js",  
"build:pre": "cross-env NODE_ENV=production BASE_ENV=preRelease webpack -c build/webpack.prod.js",  
"build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack -c build/webpack.prod.js",  
"test": "echo \"Error: no test specified\" && exit 1"  
},

process.env.NODE_ENV环境变量webpack会主动依据设置的mode字段来给事务代码注入对应的developmentprodction,这儿在指令中再次设置环境变量NODE_ENV是为了在webpackbabel的装备文件中拜访到。

能够在webpack.base.js里边打印一下NODE_ENVBASE_ENV两个变量

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

履行yarn start:dev之后能够看到打印如下:

// NODE_ENV development 
// BASE_ENV development

当时是开发方法,事务环境是开发环境,这儿需求把process.env.BASE_ENV注入到事务代码里边,就能够经过该环境变量设置对应环境的接口地址和其他数据,要凭借webpack.DefinePlugin插件。

修正webpack.base.js

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

装备后会把值注入到事务代码里边去,webpack解析代码匹配到process.env.BASE_ENV,就会设置到对应的值。测验一下,在src/index.tsx打印一下两个环境变量

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

2、处理css和less文件

前端开发项意图时分别的一个重要的装备便是cssless文件处理,webpack默许只知道js,是不辨认css文件的,所以需求装置依靠来处理cssless文件,不然当进口文件index.jsx引进css文件时,运转yarn start:dev操控台就会报错,因而需求运用loader来解析css

3、装置依靠

npm i style-loader css-loader -D
or
yarn add style-loader css-loader -D
  • style-loader: 把解析后的css代码从js中抽离,放到头部的style标签中(在运转时做的)
  • css-loader: 解析css文件代码

因为解析css的装备开发和打包环境都会用到,所以在公共装备webpack.base.js中添加装备

// webpack.base.js
// ...
module.exports = {
 // ...
 module: { 
   rules: [
     // ...
     {
       test: /.css$/, //匹配 css 文件
       use: ['style-loader','css-loader']
     }
   ]
 },
 // ...
}

loader履行次序是从右往左,从下往上的,匹配到css文件后先用css-loader解析css, 终究凭借style-loadercss刺进到头部style标签中。

装备完结后再yarn start:dev打包发动后在浏览器查看,这时能够看到操控台不报错了,而且款式收效了。

4、支撑less或scss

项目开发中,为了更好的提高开发体验,一般会运用css超集less或许scss,关于这些超集也需求对应的loader来辨认解析,咱们以less为例,首要装置解析less的相关依靠:

npm i less-loader less -D
or
yarn add less-loader less -D

假设是运用scss的话装置以下依靠

npm i node-sass sass-loader -D
or
yarn add node-sass sass-loader -D
  • less-loader: 解析less文件代码,把less编译为css
  • less:less中心依靠

完结支撑less也很简单,只需求在rules中添加less文件解析,遇到less文件,运用less-loader解析为css,再进行css解析流程,修正webpack.base.js(ps: 假设是scss的话运用sass-loader):

// webpack.base.js
module.exports = {
  // ...
  module: {
    // ...
    rules: [
      // ...
      {
        test: /.(css|less)$/, //匹配 css和less 文件
        use: ['style-loader','css-loader', 'less-loader']
      }
    ]
  },
  // ...
}

测验一下,新增src/app.less

#root {
  h2 {
    font-size: 20px;
  }
}

App.tsx中引进app.less,履行yarn start:dev发动之后,能够看到less文件编写的款式编译css后也刺进到style标签了。

5、处理css3前缀兼容

尽管css3现在浏览器支撑率现已很高了, 但有时分需求兼容一些低版别浏览器,需求给css3加前缀,能够凭借插件来主动加前缀,postcss-loader便是来给css3加浏览器前缀的,装置依靠:

npm i postcss-loader autoprefixer -D
or
yarn add postcss-loader autoprefixer -D
  • postcss-loader:处理css时主动加前缀
  • autoprefixer:决议添加哪些浏览器前缀到css

修正webpack.base.js, 在解析cssless的规矩中添加装备:

module.exports = {
  // ...
  module: { 
    rules: [
      // ...
      {
        test: /.(css|less)$/, //匹配 css和less 文件
        use: [
          'style-loader',
          'css-loader',
          // 新增
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: ['autoprefixer']
              }
            }
          },
          'less-loader'
        ]
      }
    ]
  },
  // ...
}

装备完结后,需求有一份要兼容浏览器的清单,让postcss-loader知道要加哪些浏览器的前缀,在根目录创立 .browserslistrc文件,在文件内添加以下内容:

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

以兼容到ie9chrome35版别为例,装备好后,履行yarn start:dev发动之后,能够看到打包后的css文件现已加上了ie谷歌内核的前缀;

上面能够看到解析cssless有许多重复装备,能够进行提取postcss-loader装备优化一下

postcss.config.jspostcss-loader的装备文件,会主动读取装备,根目录新建postcss.config.js

module.exports = {
  plugins: ['autoprefixer']
}

修正webpack.base.js, 撤销postcss-loaderoptions装备

// webpack.base.js
// ...
module.exports = {
  // ...
  module: { 
    rules: [
      // ...
      {
        test: /.(css|less)$/, //匹配 css和less 文件
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
    ]
  },
  // ...
}

6、babel预设处理js兼容

现在js不断新增许多便利好用的标准语法来便利开发,甚至还有非标准语法比方装修器,都极大的提高了代码可读性和开发功率。但前者标准语法许多低版别浏览器不支撑,后者非标准语法一切的浏览器都不支撑。需求把最新的标准语法转化为低版别语法,把非标准语法转化为标准语法才能让浏览器辨认解析,而babel便是来做这件事的,这儿只讲装备,更详细的能够看Babel 那些事儿。

装置依靠

npm i babel-loader @babel/core @babel/preset-env core-js -D
or
yarn add babel-loader @babel/core @babel/preset-env core-js -D
  • babel-loader: 运用 babel 加载最新js代码并将其转化为 ES5(上面现已装置过)

  • @babel/corer: babel 编译的中心包

  • @babel/preset-env: babel 编译的预设,能够转化现在最新的js标准语法

  • core-js: 运用低版别js语法模拟高版别的库,也便是垫片

修正webpack.base.js

// webpack.base.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/,
        use: {
          loader: 'babel-loader',
          options: {
            // 履行次序由右往左,所以先处理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'
            ]
          }
        }
      }
    ]
  }
}

此刻再打包就会把语法转化为对应浏览器兼容的语法了,为了防止webpack装备文件过于庞大,能够把babel-loader的装备抽离出来, 在项意图根目录新建babel.config.js文件,运用js作为装备文件,是因为能够拜访到process.env.NODE_ENV环境变量来区分是开发仍是打包方法。

// babel.config.js
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"
  ]
}

移除webpack.base.jsbabel-loaderoptions装备,修正后内容如下:

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

7、babel处理js非标准语法

现在react干流开发都是函数组件和react-hooks,但有时也会用类组件,能够用装修器简化代码。

新增src/components/Class.tsx组件, 在App.tsx中引进该组件运用

import React, { PureComponent } from "react";
// 装修器为,组件添加age特点
function addAge(Target: Function) {
  Target.prototype.age = 111
}
// 运用装修圈
@addAge
class Class extends PureComponent {
  age?: number
  render() {
    return (
      <h2>我是类组件---{this.age}</h2>
    )
  }
}
export default Class

需求敞开一下ts装修器支撑,修正tsconfig.json文件

// tsconfig.json
{
  "compilerOptions": {
    // ...
    // 敞开装修器运用
    "experimentalDecorators": true
  }
}

上面Class组件代码中运用了装修器,现在js标准语法是不支撑的,现在运转或许打包会报错,不辨认装修器语法,需求凭借babel-loader插件,装置以下依靠:

npm i @babel/plugin-proposal-decorators -D
or
yarn add @babel/plugin-proposal-decorators -D

babel.config.js中添加插件

module.exports = {
  // ...
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}

8、仿制public文件夹内容

一般public文件夹都会放一些静态资源,能够直接依据绝对途径引进,比方图片,css,js文件等,不需求webpack进行解析,只需求打包的时分把public下内容仿制到构建出口文件夹中,能够凭借copy-webpack-plugin插件,装置依靠:

npm i copy-webpack-plugin -D
or
yarn add 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');
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文件下,所以不需求再额外装备仿制该文件了。

html-webpack-plugin装备好之后,能够在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 http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>template_react_ts</title>
</head>
<body>
  <!-- 容器节点 -->
  <div id="root"></div>
</body>
</html>

再履行yarn build:dev打包,就能够看到public下的favicon.ico图标文件被仿制到dist文件中了。

9、图片文件处理

关于图片文件,webpack4运用file-loaderurl-loader来处理的,但webpack5不运用这两个loader了,而是选用自带的asset-module来处理;

修正webpack.base.js,添加图片解析装备

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

测验一下,预备一张小于10kb的图片和大于10kb的图片,放在src/assets/imgs目录下, 修正App.tsx:

import React from 'react'
import smallImg from './assets/imgs/5kb.png'
import bigImg from './assets/imgs/20kb.png'
import './app.css'
import './app.less'
function App() {
  return (
    <>
      <img src={smallImg} alt="小于10kb的图片" />
      <img src={bigImg} alt="大于于10kb的图片" />
    </>
  )
}
export default App

这个时分在引进图片的当地会报:找不到模块“./assets/imgs/20kb.png”或其相应的类型声明,这是因为在ts项目内引进图片是需求添加一个静态文件的声明文件来着

新增src/index.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'

添加图片声明文件后,就能够正常引进图片了, 然后履行yarn build:dev打包,凭借serve -s dist查看作用,能够看到能够正常解析图片了,而且小于10kb的图片被转成了base64位格局的。

10、处理字体和媒体文件

字体文件和媒体文件这两种资源处理方法和处理图片是相同的,只需求把匹配的途径和打包后放置的途径修正一下就能够了。修正webpack.base.js文件:

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

四、装备react模块热更新

热更新上面现已在devServer中装备hottrue, 在webpack4中,还需求在插件中添加了HotModuleReplacementPlugin,在webpack5中,只需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
or
yarn add @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-loader装备react-refesh改写插件,修正babel.config.js文件

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

新增或许删去页面hooks时,热更新时组件状况不会保存。

五、优化构建速度

1、构建耗时剖析

当进行优化的时分,必定要先知道时刻都花费在哪些过程上了,而speed-measure-webpack-plugin插件能够帮咱们做到,装置依靠:

npm i speed-measure-webpack-plugin -D
or
yarn add speed-measure-webpack-plugin -D

运用的时分为了不影响到正常的开发/打包方法,咱们挑选新建一个装备文件,新增webpack构建剖析装备文件build/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文件里边的script里边添加发动webpack打包剖析脚本指令

2、敞开耐久化存储缓存

webpack5之前做缓存是运用babel-loader缓存处理js的解析成果,cache-loader缓存css等资源的解析成果,还有模块缓存插件hard-source-webpack-plugin,装备好缓存后第二次打包,经过对文件做哈希对比来验证文件前后是否共同,假设共同则选用上一次的缓存,能够极大地节省时刻。

webpack5 较于 webpack4,新增了耐久化缓存、改善缓存算法等优化,经过装备 webpack 耐久化缓存,来缓存生成的 webpack 模块和 chunk,改善下一次打包的构建速度,可提速 90% 左右,装备也简单,修正webpack.base.js内容如下:

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

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

3、敞开多线程loader

webpackloader默许在单线程履行,现代电脑一般都有多核cpu,能够凭借多核cpu敞开多线程loader解析,能够极大地提高loader解析的速度,thread-loader便是用来敞开多进程解析loader的,装置依靠:

npm i thread-loader -D
or
yarn add 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']
      }
    ]
  }
}

因为thread-loader不支撑抽离css插件MiniCssExtractPlugin.loader(下面会讲),所以这儿只装备了多进程解析js,敞开多线程也是需求发动时刻,大约600ms左右,所以适合规模比较大的项目。

4、装备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文件中都能够用。

5、缩小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里边。

6、精确运用loader

loaderwebpack构建过程中运用的位置是在webpack构建模块依靠联系引进新文件时,会依据文件后缀来倒序遍历rules数组,假设文件后缀和test正则匹配到了,就会运用该rule中装备的loader依次对文件源代码进行处理,终究拿到处理后的sourceCode成果,能够经过防止运用无用的loader解析来提高构建速度,比方运用less-loader解析css文件。

能够拆分上面装备的lesscss, 防止让less-loader再去解析css文件

// webpack.base.js
// ...
module.exports = {
  module: {
    // ...
    rules: [
      // ...
      { // css文件处理  
      test: /.(css|less)$/, //匹配 css 文件  
      use: [  
      'style-loader',  
      'css-loader',  
      'postcss-loader',  
      'less-loader'  
      ]  
      },
    ]
  }
}

webpack.base.js以上内容修正为如下内容:

// webpack.base.js
// ...
module.exports = {
  module: {
    // ...
    rules: [
      // ...
      {
        test: /.css$/, //匹配一切的 css 文件
        include: [path.resolve(__dirname, '../src')],
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /.less$/, //匹配一切的 less 文件
        include: [path.resolve(__dirname, '../src')],
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
    ]
  }
}

7、缩小模块搜索规模

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: {
     // ...
     // 假设用的是pnpm 就暂时不要装备这个,会有鬼魂依靠的问题,拜访不到许多模块。
     modules: [path.resolve(__dirname, '../node_modules')], // 查找第三方模块只在本项意图node_modules中查找
  },
}

8、devtool 装备

开发过程中或许打包后的代码都是webpack处理后的代码,假设进行调试必定期望看到源代码,而不是编译后的代码, source map便是用来做源码映射的,不同的映射方法会显着影响到构建和重新构建的速度, devtool选项便是webpack供给的挑选源码映射方法的装备。

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

关键字 描绘
inline 代码内经过 dataUrl 方法引进 SourceMap
hidden 生成 SourceMap 文件,但不运用
eval eval(...) 方法履行代码,经过 dataUrl 方法引进 SourceMap
nosources 不生成 SourceMap
cheap 只需求定位到行信息,不需求列信息
module 展现源代码中的过错位置

开发环境推荐:eval-cheap-module-source-map

  • 本地开发首次打包慢点没联系,因为 eval 缓存的原因, 热更新会很快
  • 开发中,咱们每行代码不会写的太长,只需求定位到行就行,所以加上 cheap
  • 咱们期望能够找到源代码的过错,而不是打包后的,所以需求加上 module

修正webpack.dev.js

// webpack.dev.js
module.exports = {
  // ...
  devtool: 'eval-cheap-module-source-map'
}

9、其他优化装备

除了上面的装备外,webpack还供给了其他的一些优化方法,本次搭建没有运用到,所以只简单罗列下:

  • externals: 外包拓宽,打包时会疏忽装备的依靠,会从上下文中寻觅对应变量;
  • module.noParse: 匹配到设置的模块,将不进行依靠解析,适合jquery,boostrap这类不依靠外部模块的包;
  • ignorePlugin: 能够运用正则疏忽一部分文件,常在运用多语言的包时能够把非中文语言包过滤掉;

六、优化构建成果文件

1、webpack包剖析工具

webpack-bundle-analyzer是剖析webpack打包后文件的插件,运用交互式可缩放树形图可视化webpack输出文件的大小。经过该插件能够对打包后的文件进行观察和剖析,能够便利咱们对不完美的当地针对性的优化,装置依靠:

npm install webpack-bundle-analyzer -D
or
yarn add 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() // 装备剖析打包成果插件
  ]
}))

装备好后,履行yarn build:analy指令,打包完结后浏览器会主动翻开窗口,能够看到打包文件的剖析成果页面,能够看到各个文件所占的资源大小。

2、抽取css款式文件

在开发环境咱们期望css嵌入在style标签里边,便利款式热替换,但打包时咱们期望把css独自抽离出来,便利装备缓存策略。而插件mini-css-extract-plugin便是来帮咱们做这件事的,装置依靠:

npm i mini-css-extract-plugin -D
or
yarn add 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文件。

3、紧缩css文件

上面装备了打包时把css抽离为独自css文件的装备,翻开打包后的文件查看,能够看到默许css是没有紧缩的,需求手动装备一下紧缩css的插件。

能够凭借css-minimizer-webpack-plugin来紧缩css,装置依靠:

npm i css-minimizer-webpack-plugin -D
or
yarn add css-minimizer-webpack-plugin -D

修正webpack.prod.js文件, 需求在优化项optimization下的minimizer特点中装备

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

这时分再履行打包指令之后能够看见css文件现已被紧缩

4、紧缩js文件

设置modeproduction时,webpack会运用内置插件terser-webpack-plugin紧缩js文件,该插件默许支撑多线程紧缩,可是上面装备optimization.minimizer紧缩css后,js紧缩就失效了,需求手动再添加一下,webpack内部装置了该插件,因为pnpm处理了鬼魂依靠问题,假设用的pnpm的话,需求手动再装置一下依靠。

npm i terser-webpack-plugin -D
or
yarn add terser-webpack-plugin -D

修正webpack.prod.js文件

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

装备完结后再打包,cssjs就都能够被紧缩了。

5、合理装备打包文件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

6、代码分割第三方包和公共模块

一般第三方包的代码改变频率比较小,能够独自把node_modules中的代码独自打包, 当第三包代码没改变时,对应chunkhash值也不会改变,能够有用利用浏览器缓存,还有公共的模块也能够提取出来,防止重复打包加大代码整体体积, webpack供给了代码分隔功用, 需求咱们手动在优化项optimization中手动装备下代码分隔splitChunks规矩。

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

7、tree-shaking清理未运用css

js中会有未运用到的代码,css中也会有未被页面运用到的款式,能够经过purgecss-webpack-plugin插件打包的时分移除未运用到的css款式,这个插件是和mini-css-extract-plugin插件合作运用的,在上面现已装置过,还需求glob-all来挑选要检测哪些文件里边的类名和id还有标签称号, 装置依靠:

npm i purgecss-webpack-plugin@4 glob-all -D
or
yarn add purgecss-webpack-plugin@4 glob-all -D

本文版别是4版别最新的5版别导入方法需求改为 const { PurgeCSSPlugin } = require(‘purgecss-webpack-plugin’)

修正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')
      ]),
    }),
  ]
}

插件自身也供给了一些白名单safelist特点,符合装备规矩挑选器都不会被删去掉,比方运用了组件库antd, purgecss-webpack-plugin插件检测src文件下tsx文件中运用的类名和id时,是检测不到在src中运用antd组件的类名的,打包的时分就会把antd的类名都给过滤掉,能够装备一下安全挑选列表,防止删去antd组件库的前缀ant

new PurgeCSSPlugin({
  // ...
  safelist: {
    standard: [/^ant-/], // 过滤以ant-开头的类名,哪怕没用到也不删去
  }
})

8、资源懒加载

react,vue等单页运用打包默许会打包到一个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)  
// 点击事件中动态引进css, 设置show为true  
const onClick = () => {  
import("./app.css")  
setShow(true)  
}  
return (  
<>  
<h2 onClick={onClick}>展现</h2>  
{/* show为true时加载LazyDemo组件 */}  
{ show && <Suspense fallback={<span>加载中</span>}><LazyDemo /></Suspense> }  
</>  
)  
}  
export default App

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

9、资源预加载

上面装备了资源懒加载后,尽管提高了首屏渲染速度,可是加载到资源的时分会有一个去恳求资源的延时,假设资源比较大会呈现延迟卡顿现象,能够凭借link标签的rel特点prefetchpreload,link标签除了加载css之外也能够加载js资源,设置rel特点能够规矩link提前加载资源,可是加载资源后不履行,等用到了再履行。

rel的特点值

  • preload是告知浏览器页面必定需求的资源,浏览器一定会加载这些资源。
  • prefetch是告知浏览器页面或许需求的资源,浏览器不一定会加载这些资源,会在空闲时加载。

关于当时页面很有必要的资源运用preload,关于或许在将来的页面中运用的资源运用prefetch

webpack v4.6.0+ 添加了对预获取和预加载的支撑,运用方法也比较简单,在import引进动态资源时运用webpack的魔法注释;

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

测验一下,在src/components目录下新建PreloadDemo.tsx,PreFetchDemo.tsx

// src/components/PreloadDemo.tsx
import React from "react";
function PreloadDemo() {
  return <h3>我是PreloadDemo组件</h3>
}
export default PreloadDemo
// src/components/PreFetchDemo.tsx
import React from "react";
function PreFetchDemo() {
  return <h3>我是PreFetchDemo组件</h3>
}
export default PreFetchDemo

修正App.tsx

import React, { lazy, Suspense, useState } from 'react'
// prefetch
const PreFetchDemo = lazy(() => import(
  /* webpackChunkName: "PreFetchDemo" */
  /*webpackPrefetch: true*/
  '@/components/PreFetchDemo'
))
// preload
const PreloadDemo = lazy(() => import(
  /* webpackChunkName: "PreloadDemo" */
  /*webpackPreload: true*/
  '@/components/PreloadDemo'
 ))
function App() {
  const [ show, setShow ] = useState(false)
  const onClick = () => {
    setShow(true)
  }
  return (
    <>
      <h2 onClick={onClick}>展现</h2>
      {/* show为true时加载组件 */}
      { show && (
        <>
          <Suspense fallback={null}><PreloadDemo /></Suspense>
          <Suspense fallback={null}><PreFetchDemo /></Suspense>
        </>
      ) }
    </>
  )
}
export default App

然后打包后查看作用,页面初始化时预加载了PreFetchDemo.js组件资源,可是不履行里边的代码,等点击展现按钮后从预加载的资源中直接取出来履行,不用再从服务器恳求,节省了许多时刻。

10、打包时生成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
or
yarn add compression-webpack-plugin -D

添加装备,修正webpack.prod.js

const glob = require('glob')
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结束的文件

七、项目标准

1、装置eslint

详情eslint+prettier首要经过指令创立.eslintrc.js文件。首要装置eslint依靠:

npm i eslint -D
or
yarn add eslint -D

然后运用eslint指令装置相关依靠

npx eslint --init

会主动装置下方依靠

eslint-plugin-import@2.25.4
eslint-plugin-jsx-a11y@6.5.1
eslint-config-airbnb@19.0.4
eslint-plugin-react@7.28.0
eslint@8.8.0
eslint-plugin-react-hooks@4.3.0
<@typescript-eslint/parser@5.11.0>
@typescript-eslint/eslint-plugin@5.11.0

npx 是用于履行项目下装置的模块指令。如:咱们在项目下装置了 webpack 模块,可是咱们并不能直接在指令行履行 webpack 指令。只能运用以下方法履行,或许添加到package.json脚本模块中去履行 node_modules/.bin/webpacknpx 的原理很简单,便是运转的时分,会到node_modules/.bin途径和环境变量$PATH里边,查看指令是否存在。因为 npx 会查看环境变量$PATH,所以体系指令也能够调用; 而且 npx 随用随删,关于不存在的模块指令、会先去下载履行完后会删去模块;

How would you like to use ESLint?  style
✔ What type of modules does your project use?  esm
✔ Which framework does your project use?  react
✔ Does your project use TypeScript?  No / YesWhere does your code run?  browser
✔ How would you like to define a style for your project?  guide
✔ Which style guide do you want to follow?  airbnb
✔ What format do you want your config file to be in?  JavaScript
eslint-plugin-react@^7.28.0 @typescript-eslint/eslint-plugin@latest eslint-config-airbnb@latest eslint@^7.32.0 || ^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react-hooks@^4.3.0 @typescript-eslint/parser@latest
✔ Would you like to install them now with npm?  No / Yes

装置完结之后项目根目录就会主动生成一个 eslint 装备文件.eslintrc.js, .eslintrc.js文件内容如下:

module.exports = {
env: {  
browser: true,  
es2021: true  
},  
extends: [  
'plugin:react/recommended',  
],  
overrides: [  
],  
parserOptions: {  
ecmaVersion: 'latest',  
sourceType: 'module'  
},  
plugins: [  
'react'  
],  
rules: {  
}  
}

装置typescript相关依靠

因为项目运用的是Typescript,所以得改下 eslint 解释器,参阅typescript-eslint,依靠装置指令如下:

npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
or
yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

修正.eslintrc.js文件内容如下:

module.exports = {
env: {  
browser: true,  
es2021: true,  
node: true  
},  
extends: [  
'plugin:react/recommended',  
'plugin:import/recommended',  
'plugin:@typescript-eslint/recommended',  
],  
parser: '@typescript-eslint/parser',  
overrides: [  
],  
parserOptions: {  
ecmaVersion: 'latest', // 指定ECMAScript 语法为最新  
sourceType: 'module', // 指定代码为 ECMAScript 模块  
ecmaFeatures: {  
jsx: true, // 启用jsx   
}  
},  
plugins: [  
'react',  
'@typescript-eslint',  
],  
rules: {  
}  
}

运用airbnd代码风格

至此eslint的根底装备现已搭建完结,可是实践项目开发过程中,咱们需求或许运用到一些第三方的eslint标准,当时咱们项目运用airbnd, airbnd也是当时githubstar 最多的, 详细能够参阅airbnb,假设不需求就省掉这一步,首要需求装置airbnd相关装备,装置依靠之前咱们需求先简单了解一下相关依靠

  • eslint-config-airbnbAirbnb JavaScript风格的 eslint 同享装备库,检测规矩包括ES6+React,它依靠于eslint-plugin-importeslint-plugin-reacteslint-plugin-react-hookseslint-plugin-jsx-a11y包。

  • eslint-config-airbnb-base,假设咱们不需求React,能够装置这个包代替eslint-config-airbnb

  • eslint-config-airbnb-typescript,支撑 typescript,依靠于eslint-config-airbnb

因为咱们现在的项目是React+Ts,所以要装置eslint-config-airbnbeslint-config-airbnb-typescript这两个包。

咱们先履行npm info "eslint-config-airbnb@latest" peerDependencies,了解eslint-config-airbnb的依靠包版别;

warning " > autoprefixer@10.4.14" has unmet peer dependency "postcss@^8.1.0".
  eslint: '^7.32.0 || ^8.2.0',
  'eslint-plugin-import': '^2.25.3',
  'eslint-plugin-jsx-a11y': '^6.5.1',
  'eslint-plugin-react': '^7.28.0',
  'eslint-plugin-react-hooks': '^4.3.0'
}

直达依靠包版别之后,装置依靠相应版别(ps: 这一步在咱们初始化eslint的时分其实就现已主动装置了相关依靠,假设不是运用eslint --init初始化eslint就按下面的依靠装置一遍)

npm i eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react@^7.28.0 eslint-plugin-react-hooks@^4.3.0 -D
or
yarn add eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react@^7.28.0 eslint-plugin-react-hooks@^4.3.0 -D

依照eslint-config-airbnb-typescript 装备过程, 修正.eslintrc.js文件

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'airbnb',
    'airbnb-typescript',
    'airbnb/hooks',
    // 'plugin:react/recommended',
    // 'plugin:import/recommended',
    'plugin:@typescript-eslint/recommended',
  ],
  parser: '@typescript-eslint/parser',
  overrides: [
  ],
  parserOptions: {
    ecmaVersion: 'latest', // 指定ECMAScript 语法为最新
    sourceType: 'module', // 指定代码为 ECMAScript 模块
    ecmaFeatures: {
      jsx: true, // 启用jsx
    }
  },
  plugins: [
    'react',
    '@typescript-eslint',
  ],
  rules: {
    // 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-<directive> 运用注释或要求在指令后进行描绘
    // "@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: {
    '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/']
      }
    }
  }
}

至此,airbnd校验规矩就装备完结了,咱们现在能够在package.json添加eslint指令来检验咱们的代码质量:

{
  "script": {
    "lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./src"
  }
}

有时分,咱们需求在代码中疏忽esLint的某些规矩查看,此刻咱们能够经过参加代码注释的方法处理:能够指定整个文件、某一行、某一区块敞开/封闭 某些或悉数规矩查看;

在项目目录添加.eslintignore,将需求疏忽的文件内容添加到文件:

.eslintrc.js
node_modules  
dist  
.idea  
README.md  
.gitignore

2、装置prettier

每个人写代码的风格习气不相同,比方代码换行,结束是否带分号,单双引号,缩进等,而且不能只靠口头标准来约束,项目紧迫的时分或许会不太注意代码格局,这时分需求有工具来帮咱们主动格局化代码,而prettier便是帮咱们做这件事的,因而prettier也是咱们一般项目开发中必不可少的。

依靠装置

依靠装置指令如下:

npm i prettier -D
or
yarn add prettier -D

新建 .prettier.js

依靠装置完结之后,在项意图根目录新增.prettier.js 文件,添加咱们常用的代码风格

// .prettierrc.js
module.exports = {
  tabWidth: 2, // 一个tab代表几个空格数,默许便是2
  useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false
  printWidth: 100, // 一行的字符数,假设超过会进行换行
  semi: false, // 行尾是否运用分号,默许为true
  singleQuote: true, // 字符串是否运用单引号
  trailingComma: 'none', // 对象或数组结尾是否添加逗号 none| es5| all
  jsxSingleQuote: true, // 在jsx里是否运用单引号,你看着办
  bracketSpacing: true, // 对象大括号直接是否有空格,默许为true,作用:{ foo: bar }
  arrowParens: 'avoid' // 箭头函数假设只要一个参数则省掉括号
}

新建 .prettierignore

项目目录添加.prettierignore,疏忽一些不需求 prettier 格局化的文件

package.json
dist  
node_modules  
.idea  
README.md  
.gitignore

装备履行指令

至此,咱们能够在package.json添加指令

{
  "script": {
    "lint:prettier": "prettier -c --write \"src/**/*\""
  }
}

履行yarn lint:prettier就能够格局化咱们项意图代码了

处理eslintprettier 抵触

防止 eslint 和 prettier 抵触,咱们需求再装置两个包eslint-config-prettiereslint-plugin-prettier

eslint-config-prettier的作用是封闭 eslint 中一切不必要的或或许与 prettier 抵触的规矩,让 eslint 检测代码时不会对这些规矩报错或告警。比方 eslint 规矩是双引号,而咱们用 prettier 格局化代码时是用单引号,会存在抵触。咱们在eslint-config-prettier 代码能够看到,例如缩进、引号等格局规矩都被封闭了。封闭后,咱们能够彻底自界说 prettier 来格局化咱们的代码,而不受 eslint 影响。

eslint-plugin-prettier 是一个 ESLint 插件。上面咱们说封闭了一些 eslint 的代码格局规矩。假定咱们约好 prettier 规矩运用双引号,但是敲代码写成单引号,我仍是期望能够按 prettier 的规矩给我一些代码不标准的报错或警告提示。那么eslint-config-prettier是封闭了 eslint 中与 prettier 抵触的规矩,eslint-plugin-prettier便是敞开了以 prettier 为准的规矩,并将陈述过错给 eslint。

装置依靠

npm i eslint-config-prettier eslint-plugin-prettier -D
yarn add eslint-config-prettier eslint-plugin-prettier -D

装置后咱们只需求在.eslintrc.js文件添加一行即可

{
  extends: [
    // ...
    'plugin:prettier/recommended'
  ]
}

3、装置 stylelint

检测 css 款式代码质量,其实许多项目都是不检测的,假设不做这步能够疏忽。

装置依靠

依照官网 docs,咱们首要装置依靠:

npm i stylelint stylelint-config-standard -D
or
yarn add stylelint stylelint-config-standard -D

新建.stylelintrc.js

在项目根目录创立.stylelintrc.js文件,添加如下内容:

module.exports = {
  extends: ['stylelint-config-standard'],
};

至此,咱们能够在package.json添加指令

{
  "script": {
    "lint:style": "stylelint \"**/*.css\""
  }
}

装置 stylelint-prettier

相同的,咱们统一用 prettier 来格局化 css 代码。 需求装置stylelint插件来防止与prettier抵触。

  • stylelint-config-prettier,和eslint-config-prettier相似,作用是封闭 stylelint 一切不必要的或或许与 prettier 抵触的规矩。可是在 Stylelint v15 版别之后,Stylelint 默许封闭了一切与 prettier 相抵触的风格规矩,所以不需求装置stylelint-config-prettier了。
  • stylelint-prettier,和eslint-plugin-prettier相似,敞开了以 prettier 为准的规矩,并将陈述过错给 stylelint。

上面了解后,咱们只需求装置stylelint-prettier

npm i stylelint-prettier -D
or
yarn add stylelint-prettier -D

修正.stylelintrc.js文件内容如下:

module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
};

新建.stylelintignore

有时分有些目录或许文件咱们不想进行检测这时分咱们就能够在项意图根目录新建.stylelintignore文件,在文件内添加咱们想要stylelint疏忽的内容,例如:

dist
public
env
build
.vscode
.DS_Store
README.md
node_modules
.idea

装备 .vscode/settings.json

终究记住在.vscode/settings.json中参加:

{
    // ...
    "editor.codeActionsOnSave": { 
         "source.fixAll.stylelint": true 
     },
     "stylelint.validate": [
        "css",
        "less",
        "sass",
        "stylus",
        "postcss"
      ]
}

4、editorconfig

在项目中引进 editorconfig 是为了在多人协作开发中坚持代码的风格和共同性。不同的开发者运用不同的编辑器或IDE,或许会有不同的缩进(比方有的人喜爱4个空格,有的喜爱2个空格)、换行符、编码格局等。甚至相同的编辑器因为开发者自界说装备的不同也会导致不同风格的代码,这会导致代码的可读性降低,添加代码抵触的或许性,降低了代码的可维护性。

EditorConfig 使不同编辑器能够坚持相同的装备。因而,咱们得以无需在每次编写新代码时,再依靠 Prettier 来依照团队约好格局化一遍(呈现保存时格局化突然改变的状况) 。当然这需求在你的 IDE 上装置了必要的 EditorConfig 插件或扩展。

EditorConfig for VS Code

现在干流的编辑器或许 IDE 根本上都有对应的EditorConfig插件,有的是内置支撑的(比方,WebStorm不需求独立装置EditorConfig的插件),有的需求独立装置。

然后在插件的介绍页上点击设置的齿轮,而且挑选Add to Workspace Recommendations,就能够将其参加清单。也能够直接敞开.vscode/extensions.json进行编辑:

{
    "recommendations": ["editorconfig.editorconfig"] 
}

webpack5从零搭建完整的react18+ts项目模板

为什么要做这个操作? 假设哪天项目新来一个协同开发的同学,当他拉取取项目,用vscode翻开项意图时分,编辑器就会主动提醒他装置这个插件,并将相关的装备做设定。上面的eslintprettier插件也是相似。

新建 .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

上面的装备能够标准本项目中文件的缩进风格,和缩进空格数等,会掩盖vscode的装备,来到达不同编辑器中代码默许行为共同的作用。