用TypeScript和ESLint与Webpack 5创建一个React应用

337 阅读8分钟

这篇文章将介绍如何使用webpack 5来捆绑一个React和TypeScript的应用。我们的设置将包括TypeScript的类型检查和Webpack过程中ESLint的提示,这将有助于代码质量。我们将对Webpack进行配置,以便通过热重载和优化的生产捆绑为我们提供良好的开发体验。

React, Type and Webpack

创建一个基本项目

我们将首先在我们选择的根文件夹中创建以下文件夹:

  • build:构建输出的所有工件都会在这里。
  • src:这将存放我们的源代码。

注意,当我们开始安装项目的依赖关系时,也将创建一个node_modules 文件夹。

在项目的根目录下,添加以下package.json 文件:

{
  "name": "my-app",
  "version": "0.0.1"
}

当我们在本篇文章中安装项目的依赖项时,这个文件将自动更新。

让我们在src 文件夹中添加以下index.html 文件:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My app</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

这个HTML文件是一个模板,Webpack将在捆绑过程中使用。我们最终会告诉Webpack将React应用注入到root div 元素中,并引用捆绑的JavaScript和CSS。

添加React和TypeScript

在终端中添加以下命令来安装React、TypeScript和React类型:

npm install react react-dom
npm install --save-dev typescript
npm install --save-dev @types/react @types/react-dom

TypeScript的配置是一个名为tsconfig.json 的文件。让我们在我们项目的根目录下创建这个文件,内容如下:

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react"
  },
  "include": ["src"]
}

我们只打算在项目中使用TypeScript来进行类型检查。我们最终将使用Babel来进行代码转译。因此,我们的tsconfig.json 中的编译器选项主要集中在类型检查,而不是代码转译。

下面是对我们所使用的设置的解释:

  • lib:在类型检查过程中要包括的标准类型。在我们的例子中,我们选择使用浏览器的DOM和最新版本的ECMAScript的类型。
  • allowJs:是否允许JavaScript文件被编译。
  • allowSyntheticDefaultImports:这允许在类型检查过程中从没有默认导出的模块中默认导入。
  • skipLibCheck:是否跳过所有类型声明文件(*.d.ts)的类型检查。
  • esModuleInterop:这使得与Babel兼容。
  • strict:这将类型检查的级别设置为非常高。当这一点是true ,该项目被称为在严格模式下运行。
  • forceConsistentCasingInFileNames:确保在类型检查过程中,引用的文件名的大小写是一致的。
  • moduleResolution:如何解决模块的依赖性,这对我们的项目来说是节点。
  • resolveJsonModule:这使得模块可以在.json ,这对配置文件很有用。
  • noEmit:是否在编译过程中抑制TypeScript生成的代码。这在我们的项目中是true ,因为Babel将生成JavaScript代码。
  • jsx:是否支持JSX在.tsx 文件。
  • include:这些是TypeScript要检查的文件和文件夹。在我们的项目中,我们已经指定了src 文件夹中的所有文件。

添加一个根React组件

让我们在src 文件夹中的index.tsx 文件中创建一个简单的React组件。这最终将显示在index.html

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

const App = () => (
  <h1>My React and TypeScript App!</h1>
);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

我们在严格模式下创建了React应用,并将其注入到一个div ,该元素的id"root"

添加Babel

我们的项目将使用Babel将我们的React和TypeScript代码转换为JavaScript。让我们用必要的插件来安装Babel:

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-transform-runtime @babel/runtime

下面是对我们刚刚安装的软件包的解释:

  • @babel/core:顾名思义,这就是Babel的核心库。
  • @babel/preset-env:这是一个插件集合,允许我们使用最新的JavaScript特性,但仍然针对不支持这些特性的浏览器。
  • @babel/preset-react:这是一个插件集合,使Babel能够将React代码转化为JavaScript。
  • @babel/preset-typescript:这是一个插件,使Babel能够将TypeScript代码转化为JavaScript。
  • @babel/plugin-transform-runtime 和 : 这些是使我们能够使用 和 JavaScript 功能的插件。@babel/runtime async await

配置Babel

Babel是在一个名为.babelrc 的文件中配置的。让我们在我们项目的根目录下创建这个文件,内容如下:

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

这个配置告诉Babel使用我们已经安装的插件。

添加提示

我们将在我们的项目中使用 ESLint在我们的项目中。ESLint可以帮助我们找到有问题的编码模式或不遵守特定风格准则的代码。

那么,让我们把ESLint和我们要用到的插件一起安装:

npm install --save-dev eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin

下面是对我们刚刚安装的软件包的解释:

  • eslint:这是ESLint的核心库。
  • eslint-plugin-react:这包含一些React代码的标准提示规则。
  • eslint-plugin-react-hooks:这包括一些React钩子代码的提示规则。
  • @typescript-eslint/parser:这允许TypeScript代码被刷新。
  • @typescript-eslint/eslint-plugin:这包含了一些TypeScript代码的标准提示规则。

ESLint可以在项目根部的.eslintrc.json 文件中进行配置。

让我们创建包含以下内容的配置文件:

{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint",
    "react-hooks"
  ],
  "extends": [
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "react/prop-types": "off"
  },
  "settings": {
    "react": {
      "pragma": "React",
      "version": "detect"
    }
  }
}

我们已经将ESLint配置为使用TypeScript解析器,并将标准的React和TypeScript规则作为基础规则集。我们明确地添加了两个React钩子规则,并抑制了react/prop-types 规则,因为道具类型在React与TypeScript项目中不相关。我们还告诉ESLint检测我们所使用的React的版本。

添加Webpack

Webpack是一个流行的工具,我们可以用它来创建包含我们应用程序的JavaScript代码的高性能包。它可以在我们的index.html 中引用这些bundles。

让我们来安装核心的Webpack库以及它的命令行接口:

npm install --save-dev webpack webpack-cli

TypeScript类型包含在webpack 包中,所以我们不需要单独安装它们。

Webpack有一个网络服务器,我们将在开发过程中使用。让我们来安装这个:

npm install --save-dev webpack-dev-server @types/webpack-dev-server

TypeScript类型不包括在webpack-dev-server 包中,这就是我们安装@types/webpack-dev-server 包的原因。

我们需要一个Webpack插件,babel-loader ,以便让Babel将React和TypeScript的代码转译成JavaScript。让我们来安装这个:

npm install --save-dev babel-loader

我们还需要一个Webpack插件,html-webpack-plugin ,它将生成HTML。让我们安装这个:

npm install --save-dev html-webpack-plugin

配置开发

Webpack的配置文件是基于JavaScript的标准配置。然而,如果我们安装一个名为ts-node 的软件包,我们可以使用TypeScript。让我们安装这个:

npm install --save-dev ts-node

我们要为Webpack添加两个配置文件,一个用于开发,一个用于生产。

让我们先添加一个开发配置文件。将该文件命名为webpack.dev.config.ts ,并在项目的根目录下创建它,内容如下:

import path from "path";
import { Configuration as WebpackConfiguration, HotModuleReplacementPlugin } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
import HtmlWebpackPlugin from "html-webpack-plugin";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const config: Configuration = {
  mode: "development",
  output: {
    publicPath: "/",
  },
  entry: "./src/index.tsx",
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/i,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript",
            ],
          },
        },
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    new HotModuleReplacementPlugin(),
  ],
  devtool: "inline-source-map",
  devServer: {
    static: path.join(__dirname, "build"),
    historyApiFallback: true,
    port: 4000,
    open: true,
    hot: true
  },
};

export default config;

以下是这个配置文件中的关键部分:

  • mode 字段告诉Webpack,该应用是需要捆绑在一起用于生产还是开发。我们为开发配置Webpack,所以我们将其设置为"development" 。Webpack会自动将process.env.NODE_ENV 设置为"development" ,这意味着我们会得到捆绑中包含的React开发工具。
  • output.public 字段告诉Webpack应用程序中的根路径是什么。这对开发服务器中的深度链接正常工作很重要。
  • entry 字段告诉Webpack从哪里开始寻找要捆绑的模块。在我们的项目中,这就是index.tsx
  • module 字段告诉Webpack将如何处理不同的模块。我们的项目告诉Webpack使用babel-loader 插件来处理扩展名为.js.ts.tsx 的文件。
  • resolve.extensions 字段告诉Webpack在模块解析过程中要以何种顺序寻找哪些文件类型。我们需要告诉它寻找TypeScript文件和JavaScript文件。
  • HtmlWebpackPlugin 创建HTML文件。我们已经告诉它使用我们在src 文件夹中的index.html 作为模板。
  • HotModuleReplacementPlugindevServer.hot 允许在应用程序运行时更新模块,而无需完全重新加载。
  • devtool 字段告诉Webpack使用全内联源码地图。这允许我们在转译前对原始代码进行调试。
  • devServer 字段配置了Webpack开发服务器。我们告诉Webpack,Webserver的根目录是build ,并在端口4000 上提供文件。historyApiFallback 是在多页面应用程序中进行深层链接所必需的。我们还告诉Webpack在服务器启动后打开浏览器。

添加一个npm脚本以在开发模式下运行应用程序

我们将利用npm脚本,在开发模式下启动我们的应用程序。让我们用下面的脚本在package.json ,添加一个scripts 部分:

  ...,
  "scripts": {
    "start": "webpack serve --config webpack.dev.config.ts",
  },
  ...

该脚本启动Webpack开发服务器。我们使用了config 选项来引用我们刚刚创建的开发配置文件。

让我们在终端运行以下命令,在开发模式下启动应用程序:

npm start

几秒钟后,Webpack开发服务器将启动,我们的应用程序将在我们的默认浏览器中打开。

Start

注意,Webpack在构建文件夹中没有捆绑任何文件。这是因为这些文件都在Webpack开发服务器的内存中。

让我们改变App 组件中的h1 元素的内容。注意当文件被保存时,浏览器会自动刷新以显示更新后的应用程序。

Live reload

很好!😊

在webpack过程中添加类型检查

Webpack过程目前不会做任何类型检查。我们可以使用一个名为 fork-ts-checker-webpack-plugin的包来使Webpack进程对代码进行类型检查。这意味着Webpack会通知我们任何类型错误。让我们安装这个包:

npm install --save-dev fork-ts-checker-webpack-plugin @types/fork-ts-checker-webpack-plugin

让我们把这个加入到webpack.dev.config.ts

...
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const config: Configuration = {
  ...,
  plugins: [
    ...,
    new ForkTsCheckerWebpackPlugin({
      async: false
    }),
  ],
};

我们使用了async 标志来告诉Webpack在发出任何代码之前等待类型检查过程的完成。

我们需要停止并重启应用程序,使这个额外的配置生效。

让我们对呈现在index.tsx 中的标题做一个改变。让我们在标题中引用一个叫做today 的变量:

...
const App = () => <h1>My React and TypeScript App!! {today}</h1>;
...

当然,这是一个错误,因为我们没有在任何地方声明和初始化today 。Webpack会在终端引发这个类型的错误:

Type error

现在让我们通过改变渲染的标题来解决这个问题,引用有效的东西:

const App = () => (
  <h1>
    My React and TypeScript App!!{" "}
    {new Date().toLocaleDateString()}
  </h1>
);

类型错误将消失,正在运行的应用程序将被更新以包括今天的日期:

Type error fix 在webpack过程中添加提示功能

Webpack进程目前不会做任何提示。我们可以使用一个名为 ESLintPlugin的包,使Webpack进程能够使用ESLint对代码进行提示。这意味着Webpack会通知我们任何提示性的错误。让我们来安装这个包:

npm install --save-dev eslint-webpack-plugin

让我们把这个添加到webpack.dev.config.ts

...
import ESLintPlugin from "eslint-webpack-plugin";

const config: Configuration = {
  ...,
  plugins: [
    ...,
    new ESLintPlugin({
      extensions: ["js", "jsx", "ts", "tsx"],
    }),
  ],
};

我们已经使用extensions 设置来告诉插件对TypeScript文件以及JavaScript文件进行提示。

我们需要停止并重启应用程序,使这个额外的配置生效。

index.tsx ,添加一个未使用的变量:

const unused = "something";

Webpack会通知我们关于linting的警告:

Linting error 如果我们删除这个未使用的行,该警告将消失。

这样我们的开发配置就完成了。

配置生产

用于生产的Webpack配置有点不同--我们希望文件被捆绑在为生产而优化的文件系统中,没有任何开发的东西。

让我们在项目根目录下创建一个名为webpack.prod.config.ts 的文件,内容如下:

import path from "path";
import { Configuration } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import ESLintPlugin from "eslint-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";

const config: Configuration = {
  mode: "production",
  entry: "./src/index.tsx",
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "[name].[contenthash].js",
    publicPath: "",
  },
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/i,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript",
            ],
          },
        },
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    new ForkTsCheckerWebpackPlugin({
      async: false,
    }),
    new ESLintPlugin({
      extensions: ["js", "jsx", "ts", "tsx"],
    }),
    new CleanWebpackPlugin(),
  ],
};

export default config;

这与开发配置类似,但有以下区别:

  • 我们已经指定mode 为生产用。Webpack会自动将process.env.NODE_ENV 设置为"production" ,这意味着我们不会得到捆绑中的React开发工具。
  • output 字段告诉Webpack将我们的代码捆绑在哪里。在我们的项目中,这就是build 文件夹。我们使用了[name] 令牌,以允许Webpack在我们的应用程序被代码分割时命名文件。我们使用了[contenthash] 令牌,这样,当捆绑文件的内容发生变化时,捆绑文件的名称也会发生变化,这将破坏浏览器的缓存。
  • CleanWebpackPlugin插件将在捆绑过程开始时清除构建文件夹。

我们需要使用以下命令来安装CleanWebpackPlugin

npm install --save-dev clean-webpack-plugin

添加一个npm脚本来构建生产用的应用程序

让我们添加一个npm脚本来构建生产用的应用程序:

  ...,
  "scripts": {
    ...,
    "build": "webpack --config webpack.prod.config.ts",
  },
  ...

该脚本启动Webpack捆绑过程。我们使用了config 选项来引用我们刚刚创建的生产配置文件。

让我们在终端运行以下命令,在开发模式下启动应用程序:

npm run build

几秒钟后,Webpack将把捆绑的文件放在build 文件夹中。

如果我们看一下JavaScript文件,我们会发现它已经被最小化了。Webpack在生产模式下使用其TerserWebpackPlugin开箱即用,以最小化代码。这个JavaScript捆绑包包含了我们应用程序的所有代码,以及来自reactreact-dom 包的代码。

如果我们看一下html文件,我们会看到所有的空格都被删除了。如果我们仔细观察,我们会看到一个脚本元素引用了我们的JavaScript文件,这是HtmlWebpackPlugin为我们做的。

Production files 很好!😊

就这样了!我们的项目现在已经设置好了,可以让我们有效地开发React和TypeScript应用。build 命令允许我们轻松地将其整合到我们的CI/CD流程中。

这段代码可以在GitHub上找到:github.com/carlrip/rea…