保姆级构建React+TypeScript+Webpack5项目

构建项目

初始化项目

mkdir react-templeate
cd react-templeate 
npm init

安装Webpack

npm i webpack webpack-cli --save-dev
  • webpack-cli@4.9.1
  • webpack@5.64.1

配置Webpack

新建webpack相关文件

    |-- react-templeate
    |   |-- package-lock.json
    |   |-- package.json
    |   |-- tsconfig.json
    |   |-- webpack.common.js
    |   |-- webpack.dev.js
    |   |-- webpack.prod.js

安装loader等来预处理文件

npm i css-loader style-loader -D
npm i postcss-loader autoprefixer -D
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm i ts-loader -D
  • css-loader@6.5.1:会对@import和url()进行处理
  • style-loader@3.3.1:将CSS注入到JavaScript中,通过DOM操作控制css
  • autoprefixer@10.4.0:增加厂商前缀(css增加浏览器内核前缀)
  • postcss-loader@6.2.0:处理css的loader
  • babel-loader@8.2.3:webpack的babel插件,在webpack中运行babel
  • @babel/core@7.16.0:babel核心库
  • @babel/preset-env@7.16.4:将ES6转换为向后兼容的JavaScript
  • @babel/plugin-transform-runtime@7.16.4:处理async,await、import()等语法关键字的帮助函数
  • ts-loader@9.2.6

在webpack5中,内置了资源模块(asset module),代替了file-loader和url-loader

安装plugin插件

npm i html-webpack-plugin
npm i clean-webpack-plugin
npm i progress-bar-webpack-plugin
npm i chalk
npm i speed-measure-webpack-plugin
npm i webpack-bundle-analyzer
  • html-webpack-plugin@5.5.0:生成一个HTML5文件,在body中使用script标签引入webpack生成的bundle
  • clean-webpack-plugin@4.0.0:再次打包的时候,先把本地已有的打包后的资源清空,来减少它们对磁盘空间的占用
  • progress-bar-webpack-plugin@2.1.0:增加编译进度条
  • chalk@4.1.2
  • speed-measure-webpack-plugin@1.5.0:[非必备]构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化
  • webpack-bundle-analyzer@4.5.0:查看打包后生成的 bundle 体积分析

基础webpack.common.js配置

// webpack.common.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");

// 公用基础配置
module.exports = {
  // 入口
  entry: path.resolve(__dirname, "./src/index.js"),
  // 输出文件名
  output: {
    path: path.resolve(__dirname, "dist"), // 目标输出目录 path 的绝对路径
    filename: "[name].[contenthash:8].js", // 用于输出文件的文件名
  },
  // 路径别名
  resolve: {
    alias: {
      src: path.resolve(__dirname, "src"),
      utils: path.resolve(__dirname, "src/common/utils/"),
      components: path.resolve(__dirname, "src/components"),
    },
    extensions: [".js", ".tsx"],
  },
  // 打包环境,默认开发
  mode: "development",
  // 模块
  module: {
    rules: [
      {
        test: /\.s?css$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg|mp3|mp4|mov|wav|wma|avi|flv)$/i,
        type: "asset/inline",
        parser: {
          dataUrlCondition: {
            // 转换成data-uri的条件
            maxSize: 10 * 1024, // 10kb
          },
        },
        generator: {
          filename: "images/[hash][ext][query]", // 指定生成目录名称
        },
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[hash][ext][query]",
        },
      },
      {
        test: /\.(js|ts|jsx|tsx)$/,
        exclude: /node_modules/,
        use: [
          "babel-loader", // 代码换成ES5 的代码来做浏览器兼容
          "ts-loader",
        ],
      },
    ],
  },
  //插件
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "src/index.html"),
    }),
    new CleanWebpackPlugin(),
    new ProgressBarPlugin({
      format: `  :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
    }), // 进度条
  ],
};

直接在根目录下新建文件postcss.config.js配置

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer'),
  ],
}
易错点

1、TypeError: CleanWebpackPlugin is not a constructor

// 错误写法
const CleanWebpackPlugin = require("clean-webpack-plugin");
...
plugin: [
  new CleanWebpackPlugin(["dist"], {
    root: path.resolve(__dirname, "../"), //根目录
    verbose: true, //开启在控制台输出信息
  }),
],
//或者
plugin: [
  new CleanWebpackPlugin(["dist"]),
],
...

// 正确写法
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
...
plugins: [
  new CleanWebpackPlugin(),
],
...

2、 Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.- configuration has an unknown property ‘plugin’.

// 错误写法
module: {
  rules: [
     {
      test: /\.(js|tsx)$/,
      exclude: /node_modules/,
      use: ["babel-loader", "ts-loader"],// 代码换成ES5 的代码来做浏览器兼容
      options: {
        presets: ["@babel/preset-env"],
        cacheDirectory: true, // 开启缓存
      },
    },
  ],
},

// 正确写法
module: {
  rules: [
    {
      test: /\.(js|tsx)$/,
      exclude: /node_modules/,
      use: [
        {
          loader: "babel-loader",// 代码换成ES5 的代码来做浏览器兼容
          options: {
            presets: ["@babel/preset-env"],
            cacheDirectory: true, // 开启缓存
          },
        },
        "ts-loader",
      ],
    },
  ],
},
// 或者
module: {
  rules: [
   {
      test: /\.js$/,
      loader: "babel-loader", // 代码换成ES5 的代码来做浏览器兼容
      options: {
        presets: ["@babel/preset-env"],
        cacheDirectory: true, // 开启缓存
      },
    },
    {
      test: /\.tsx?$/,
      exclude: /node_modules/,
      use: ["babel-loader", "ts-loader"],
    },
  ],
},

3、No inputs were found in config file ‘tsconfig.json‘. Specified ‘include‘ paths were

原因:我们使用的是tsconfig默认是打包ts的却要打包js, 只需要在tsconfig配置"allowJs": true就可以了

4、ERROR in Conflict: Multiple assets emit different content to the same filename index.html

原因:在webpack.common.jswebpack.dev.js文件中都配置了HtmlWebpackPlugin,并且template的地址不一致,保留一个就可以

 plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "src/index.html"),
    }),
  ],

5、ERROR in ./node_modules/html-entities/lib/index.js 15:28-60
ERROR in ./node_modules/html-entities/lib/index.js 14:25-54

image.png
大致原因就是extensions的引号之类的符号写错了,这个问题看了我好久😭

 // 错误写法
resolve: {
    extensions: [".js,'.tsx"],
},
// 正确写法
resolve: {
    extensions: [".js",".tsx"],
},

在package.json中配置dev命令

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.common.js",
    "start": "webpack serve --config webpack.dev.js",
    "release": "webpack serve --config webpack.prod.js"
},

运行npm run build

npm run build

打包成功,发现dist文件夹中多了bundle.306f67760e9c5ad3b005.jsindex.html两个文件

image.png

配置开发环境和生产环境

npm i webpack-merge -D
npm i mini-css-extract-plugin
npm i css-minimizer-webpack-plugin
  • webpack-merge@5.8.0:合并通用配置和特定环境配置
  • mini-css-extract-plugin@2.4.5
  • css-minimizer-webpack-plugin@3.1.4:优化、压缩 CSS

webpack.dev.js

// webpack.dev.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// 开发环境
module.exports = smp.wrap(merge(common, {
  mode: 'development',
  entry: path.resolve(__dirname, "src"),
  devtool: "eval-cheap-module-source-map",
  cache: {
    type: 'filesystem', // 使用文件缓存
  },
  devServer: {
    hot: true, //热更新
    open: false, // 是否打开默认浏览器
    historyApiFallback: true, // 当使用 [HTML5 History API] 时,任意的 `404` 响应被替代为 `index.html`
    port: 9000, // 启动的端口
    compress: true, // 是否开启代码压缩
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: path.resolve(__dirname, "src/index.html"),
    }),
  ],
}));

webpack.prod.js

// webpack.prod.js
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin"); // webpack5自带,无需安装
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //优化、压缩 CSS
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = merge(common, {
  mode: "production",
  entry: path.resolve(__dirname, "./src/index.js"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name]-[chunkhash:8].js",
    clean: true, // 编译前清除目录
  },
  module: {
    rules: [
      {
        test: /\.s?css$/,
        include: [path.resolve(__dirname, "src/common/styles"), /node_modules/],
        use: [MiniCssExtractPlugin.loader],
      },
    ],
  },
  plugins: [
    new BundleAnalyzerPlugin(),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
    }),
  ],
  optimization: {
    // js压缩
    minimizer: [
      new TerserPlugin({
        parallel: 4, // 使用多进程并发运行压缩以提高构建速度。
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
      new CssMinimizerPlugin({
        parallel: 4,
      }),
    ],
  },
});

规范化配置:eslint和prettier

npm i prettier
npm i eslint
npm i eslint-config-prettier
  • prettier@2.4.1:代码格式的校验(并格式化代码),不会对代码质量进行校验
  • eslint@8.2.0:代码格式的校验,代码质量的校验,JS规范
  • eslint-config-prettier@8.3.0:覆盖eslint部分规则,解决冲突

prettier

新建.prettierrc.js文件

// .prettierrc.js
module.exports = {
  printWidth: 124, // 代码宽度建议不超过124字符
  tabWidth: 2, // tab缩进2个空格
  semi: false, // 末尾分号
  singleQuote: true, // 单引号
  jsxSingleQuote: true, // jsx中使用单引号
  arrowParens: 'avoid', // 箭头函数仅在必要时使用()
  htmlWhitespaceSensitivity: 'css', // html空格敏感度
  useTabs: false,
  trailingComma: "none",// 尾随逗号
  bracketSpacing: true,
  jsxBracketSameLine: false,
  endOfLine: "lf",
}

新建.prettierignore文件

//.prettierignore 
**/*.min.js
**/*.min.css

.idea/
node_modules/
dist/
build/

eslint

初始化eslint

npx eslint --init

修改.eslintrc.js部分配置

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    browser: true,
    commonjs: true,
    amd: true
  },
  extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    ecmaVersion: 13,
    sourceType: 'module'
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    '@typescript-eslint/no-var-requires': 0,
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/ban-ts-ignore': 'off'
  }
}

新建.eslintignore文件

.eslintrc.js
node_modules
public

代码提交校验:husky+lint-staged+commitlint

npm i husky
npm i lint-staged
  • husky@7.0.4
  • lint-staged@11.2.6

packpackage.json文件中添加prepare脚本语句

"scripts": {
    "prepare": "husky install",
},

运行命令npm run prepare,创建.husky/目录并指定该目录为git hooks所在的目录,可以看到根目录下新增了.husky的文件夹

npm run prepare

运行命令创建git hooks,pre-commit的shell脚本

npx husky add .husky/pre-commit "npm run lint"

运行完该命令后我们会看到.husky/目录下新增了一个名为pre-commit的shell脚本,脚本内容为:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run lint

该脚本就是执行npm run lint这个命令,在执行git commit命令时会先执行pre-commit的shell脚本内容

运行命令创建commit-msg脚本

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

运行完该命令后我们会看到.husky/目录下新增了一个名为commit-msg的shell脚本,脚本内容为:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"

配置lint-staged

package.json中配置运行命令:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.common.js",
    "start": "webpack serve --config webpack.dev.js",
    "release": "webpack --config webpack.prod.js",
    "prepare": "husky install",
    "lint": "lint-staged"
},
"lint-staged": {
    "./src/*.{js,jsx,ts,tsx,json}": [
      "prettier --write",
      "eslint",
      "git add"
    ]
},

新建lint-staged.config.js文件

// lint-staged.config.js
'use strict'
module.exports = {
  linters: {
    './src/*.ts': ['prettier --write', 'eslint --fix', 'git add'],
    './src/*.js': ['prettier --write', 'eslint --cache --fix', 'git add'],
    './src/*.vue': ['prettier --write', 'eslint --cache --fix', 'git add'],
    './src/*.{json,md,yml,css}': ['prettier --write', 'git add']
  }
}

配置提交规范

安装依赖

npm install --save-dev @commitlint/config-conventional @commitlint/cli
  • @commitlint/config-conventional@15.0.0
  • @commitlint/cli@15.0.0
    生成配置文件
echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js

配置提交代码规则

//commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'bug', // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况
        'feature', // 新功能(feature)
        'fix', // 修补bug
        'docs', // 文档(documentation)
        'style', // 格式(不影响代码运行的变动)
        'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动)
        'test', // 增加测试
        'chore', // 构建过程或辅助工具的变动
        'revert', // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit)
        'merge' // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
      ]
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72]
  }
}

如果提交命令不正确,则会报错
image.png

配置react

安装基础相关库

npm i react react-dom react-redux react-router-dom typescript --save
  • react-redux@7.2.6
  • react-dom@17.0.2
  • react@17.0.2
  • react-router-dom@6.0.2
  • typescript@4.4.4

安装@types声明库

npm i @types/react @types/react-dom @types/react-redux --save-dev
  • @types/react-dom@17.0.11
  • @types/react-redux@7.1.20
  • @types/react@17.0.35

创建 tsconfig.json

更详细的配置可以看tsconfig.json

{
    "compilerOptions": {
      "module": "esnext",
      "target": "es5",
      "allowJs": true,
      "lib": [
        "es5", "dom", "es2015.promise", "es2015.core", "es2015.collection", "es2016.array.include"
      ],   
      "sourceMap": false,
      "declaration": true,
      "removeComments": false, // 删除所有的注释
      "experimentalDecorators": true,  //启用实验性的ES装饰器。
      "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入
      "importHelpers": true, // 从 tslib 导入辅助工具函数
      "noUnusedLocals": true, // 若有未使用的局部变量则抛错。
      "noUnusedParameters": true, // 若有未使用的参数则抛错。
      "noImplicitThis": true, // 当 this表达式的值为 any类型的时候,生成一个错误
      "noFallthroughCasesInSwitch": true, // 不允许switch的case语句贯穿
      "noImplicitReturns": true, // 不是函数的所有返回路径都有返回值时报错
      "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
      "strict": true,
      "strictNullChecks": true,// 在严格的 null检查模式下, null和 undefined值不包含在任何类型里,只允许用它们自己和 any来赋值
      "strictPropertyInitialization": true, // 确保类的非undefined属性已经在构造函数里初始化
      "rootDir": ".",
      "baseUrl": "src",
      "outDir": "dist", 
      "jsx": "react", // 在 .tsx 文件里支持 jsx
      "moduleResolution": "node",
    },
    "include": [
      "src",
    ]
}

修改webpack.common.js文件中的配置让其支持reactTypeScript

npm install --save-dev @babel/preset-typescript @babel/preset-react
  • @babel/preset-typescript@7.16.0
  • @babel/preset-react@7.16.0

修改之前.babelrc配置文件,让babel支持转译的ts文件

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react",
        "@babel/preset-typescript"
    ],
    "plugins": ["@babel/plugin-transform-runtime"]
}

配置index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta content="webkit" name="renderer">
    <meta name="format-detection" content="telephone=no">
    <meta content="email=no" name="format-detection">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <meta content="width=device-width,initial-scale=1,user-scalable=no,shrink-to-fit=no" name="viewport">
    <title>react模版</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

配置index.tsx

import React from 'react';
import ReactDOM from 'react-dom';

// import 'styles/index.scss'

const App = () => {
  return <div>hello world</div>;
};

ReactDOM.render(<App />, document.getElementById('app'));

运行命令npm run start成功

image.png

代码

https://github.com/Jessie-jzn/react-template


✨谢谢观看

✨谢谢指出不足

✨今天的你也辛苦了~

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值