带你撸个属于自己的react项目|webpack+babel+typescript+eslint

7,420 阅读8分钟

新的知识仓库 前端从入门到入土 求关注、star及提建议,不定时更新~

你们都不看的总集篇: 从零开始的大前端筑基之旅(深入浅出,持续更新~)
觉得不错就顺手点个赞吧~

写前端一年多了,用的都是大佬建好的架子,还没自己从头建个项目,现在开始踩坑。

项目大体包括以下几部分

  • 目录规范
  • react
  • webpack
  • less
  • typescript
  • eslint
  • axios封装

初始化项目

首先,新建个文件夹basic-react-app。名字不是重点,你高兴就好。 然后,使用 npm init 初始化项目,依据提示,一路回车下去

$basic-react-app npm init

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.

// 从这里开始,使用默认的话回车即可
package name: (basic-react-app)
version: (1.0.0)
description: basic react demo
entry point: (index.js)
test command:
git repository:
keywords:
author: suil
license: (ISC)
About to write to /Users/zhangpengcheng15/Documents/code/temp/basic-react-app/package.json:

{
  "name": "basic-react-app",
  "version": "1.0.0",
  "description": "basic react demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "suil",
  "license": "ISC"
}


Is this OK? (yes)

现在,你得到了一个package.json文件

然后,使用git init 初始化仓库,可以考虑先在远端初始化仓库,再拉去到本地。

$basic-react-app git init

Initialized empty Git repository in /Users/zhangpengcheng15/Documents/code/temp/basic-react-app/.git/

$basic-react-app git:(master) ✗

如果你使用了特殊的命令窗或zsh,应该就可以看到目录后面显示git:(master),代表本地仓库已经建好了。

别着急下一步,不要忘记配置gitignore,否则电脑配置不好会卡顿一阵子。
在根目录下新建.gitignore文件,使用编辑器打开,输入

node_modules
dist

并保存。好了,现在仓库初始化结束。

目录规范

最后,打开你喜欢的编辑器,比如 vscode,来创建如下文件结构。

.
+ |- /src
+   |- /assets
+     |- /less
+     |- /icons
+   |- /components
+   |- /constants
+   |- /static
+     |- /imgs
+   |- /utils 

使用 vscode的同学,一定要下 vscode-icons插件呀

其中,

  • assets 放置资源文件,包括各种类型的icon、全局使用的样式表等
  • components 抽取的组件,下属每个文件夹都是一个系列的组件
  • constants 常量资源,比如抽取出的配置常量,公共的接口定义、统一放置请求映射等
  • static 静态资源,包括多语言资源、图片资源、字体资源、第三方lib资源等
  • utils 工具类包,项目中抽取出的共用方法之类的

好了,文件结构建完成。下面开始安装 react

React

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

  • React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。

  • React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

使用命令行安装 reactreact-dom

npm install react react-dom
或者 yarn add react react-dom

在开始下一步之前,我们先在目录中添加一些文件。

在src目录下,新建index.tsxindex.less,先不用纠结能否被识别或者运行的问题,下一步我们再来解决它。

// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.less';  // 一定要加 './' 表示当前目录下

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);
h1{
	color: blue;
}

webpack

webpack 是什么

webpack 是一个现代 JavaScript 应用程序的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle

webpack 的核心概念

  • entry: 入口
  • output: 输出
  • loader: 模块转换器,用于把模块原内容按照需求转换成新内容
  • 插件(plugins): 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情

在本地安装 webpack,接着安装 webpack-cli(此工具用于在命令行中运行 webpack):

npm install webpack webpack-cli --save-dev

或者 yarn add webpack webpack-cli --dev

当前webpack基于最新的版本

"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"

在 webpack 4 中,可以无须任何配置使用,然而大多数项目会需要很复杂的设置,这就是为什么 webpack 仍然要支持 配置文件。

本文目的在于配置一个可用的react项目,因此有些配置会一步到位,如需更多webpack知识,请移步 webpack中文网

在根目录创建一个 webpack.config.js,写入如下内容

const path = require('path');

module.exports = {
  entry: './src/index.tsx',
  mode: "development",
  devtool: 'cheap-module-eval-source-map',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

如果 webpack.config.js 存在,则 webpack 命令将默认选择使用它。使用 --config 选项可以传递任何名称的配置文件。

对于多入口文件,可以使用如下配置

entry: {
  index: './src/index.js',
  index2: './src/index2.js',
},

output: {
  path: path.resolve(__dirname,'dist'),      //此处若非绝对路径,可能报错
  filename: '[name].bundle.js',
},

在上面的配置文件中,我们使用了入口 entry和出口 output两个概念,相信你也理解这两个配置的含义。后面我们会用到另外两个概念。

mode

mode 配置项,告知 webpack 使用相应模式的内置优化。

mode 支持以下两个配置:

  • development:将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamedModulesPlugin

  • production:将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin

简单来说,*开发环境(development)生产环境(production)*的构建目标差异很大。将 mode设置为development 会启用一些webpack默认的优化。这里我们先设置为 mode: "development",

devtool

devtool 中的一些设置,可以帮助我们将编译后的代码映射回原始源代码。不同的值会明显影响到构建和重新构建的速度。

对我们而言,能够定位到源码的行即可,因此,综合构建速度,在开发模式下,devtool 的值设置为cheap-module-eval-source-map

其他的配置参数可参考devtool

Babel

Babel 是一个 JavaScript 编译器

ES2015 中的 import 和 export 语句已经被标准化。虽然大多数浏览器还无法支持它们,但是 webpack 却能够提供开箱即用般的支持。

事实上,webpack 在幕后会将代码“转译”,以便旧版本浏览器可以执行。但是注意的是,webpack 不会更改代码中除 import 和 export 语句以外的部分。如果我们需要使用其它 ES2015 特性,需要在 webpack 的 loader 系统中使用了一个像是 Babel 或 Bublé 的转译器。

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
  • 源码转换 (codemods)

执行下面的指令安装 babel系列

yarn add @babel/core @babel/cli @babel/preset-env @babel/preset-react babel-loader --dev

其中,

  • @babel/preset-env 是一个智能预置,允许我们使用最新的JavaScript,而不需要管理在目标环境中哪些语法需要转换(以及可选的浏览器填充)。这既使我们的工作更轻松,也使JavaScript包更小!
  • @babel/preset-react 能够转换 JSX 语法

webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件。也就是说,以上列出的那些 JavaScript 的优点(例如显式依赖),同样可以用来构建网站或 web 应用程序中的所有非 JavaScript 内容。

现在,在 webpack 配置对象中,添加 babel-loader 到 module 的 loaders 列表中,,当前配置文件如下

module.exports = {
  entry: './src/index.tsx',
  mode: "development",
  devtool: 'cheap-module-eval-source-map',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
      },
    ],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

我下载了那么多包,你就用了这一个?你专门解释的那两个包呢?

不着急,其他的包我们通过设置babel配置文件来使用。在根目录下新建 .babelrc文件,输入如下内容

{
  "presets": [
    "@babel/env",
    "@babel/preset-react"
  ]
}

既然说到这里,我们先还个债,还记得上面新建的 index.tsx文件么?webpack不认识tsx文件,浏览器也不认识 tsx 文件,那谁来认呢?

当然是babel了,在7之前的版本中,我们需要专门的 ts-loader来转义 tstsx类型的文件,但是,现在只需要有babel就够了

无论代码是否具有 ES2015 特性,JSX,TypeScript,还是其他疯狂的自定义————编译器都知道要做什么。向 ts-loader、ts-jest、ts-karma、create-react-app-typescript 等等说再见就好啦,使用 Babel 代替它们。

yarn add @babel/preset-typescript --dev

.babelrc文件中补充

{
  "presets": [
    "@babel/env",
    "@babel/preset-react",
+   "@babel/preset-typescript"
  ],
}

好了,到此,ts | tsx 文件可以使用babel-loader编译了,记得修改webpack.config.js文件,补充 ts | tsx使用babel-loader编译

 rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
      },
    ],

Loader

对于loader的用法,

test 字段是匹配规则,针对符合规则的文件进行处理。

use 字段有几种写法

  • 可以是一个字符串,例如上面的 use: 'babel-loader'
  • 可以是一个数组,例如下面处理CSS文件时,use: ['style-loader', 'css-loader']
  • use 数组的每一项既可以是字符串也可以是一个对象,当我们需要在webpack 的配置文件中对 loader 进行配置,就需要将其编写为一个对象,并且在此对象的 options 字段中进行配置,例如上面 babel-loader 可以使用另一种配置形式
rules: [
    {
        test: /\.jsx?$/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ["@babel/preset-env"]
            }
        },
        exclude: /node_modules/
    }
]

css

现在,去还另一个债,less文件。要让webpack识别 less文件,自然是 less-loader了。
当然,我们顺手也补充 css文件的识别。

有关 less 及 sass 的选择,可移步 Sass.vs.Less | 简介与比较

为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:

yarn add style-loader css-loader less-loader --dev

webpack.config.js

 rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
      },
+     {
+       test: /\.css$/,
+       use: [
+         'style-loader',
+         'css-loader'
+       ]
+     },
+     {
+     test: /\.less$/,
+       use: [
+         'style-loader',
+         'css-loader',
+         'less-loader'
+       ]
+     }
  ],

小提示:vscode中,选中 +号,然后按 win + D,就可以连续选中加号,最后按删除就可以删除所有的+

postcss-loader

PostCss是一个样式处理工具,它通过自定义的插件和工具生态体系来重新定义css。它鼓励开发者使用规范的css原生语法编写代码,然后配置编译器转换需要兼容的浏览器版本,最后通过编译将源码转换为目标浏览器可用的css代码。

yarn add postcss-loader autoprefixer --dev

webpack.config.js

 rules: [
     {
       test: /\.css$/,
       use: [
         'style-loader',
         'css-loader',
+        'postcss-loader',
       ]
     },
     {
     test: /\.less$/,
       use: [
         'style-loader',
         'css-loader',
+        'postcss-loader',
         'less-loader'
       ]
     }
  ],

postcss-loader 是专门用来加浏览器前缀的,但是它自己也就只能加个前缀而已,因为它自己不知道哪个该加,哪个不该加。所以我们需要 autoprefixer 来告诉它,哪个加,哪个不加。

在根目录新增postcss.config.js

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

package.json中补充下列字段,来确定我们要支持到哪一步

  "browserslist": [
    "defaults",
    "not ie < 11",
    "last 2 versions",
    "> 1%"
  ]

未雨绸缪,我们在此安装另外两个会用到的loader

file-loader 和 url-loader

  • 使用 file-loader,我们可以轻松地将图片、字体等内容混合到 打包文件中。
  • url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
yarn add url-loader file-loader --dev

webpack.config.js中补充rules规则

 {
   test: /\.(png|svg|jpg|gif)$/,
     use: [
       {
         loader: 'url-loader',
         options: {
           limit: 2048,
         }
       },
       'file-loader',
     ]
 },
{
  test: /\.(woff|woff2|eot|ttf|otf)$/,
  use: [
  	'file-loader'
  ]
},

resolve

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。

resolve.alias

创建 import 或 require 的别名,来确保模块引入变得更简单。例如,一些位于 src/ 文件夹下的常用模块:

resolve: {
   alias: {
        '@': path.resolve(__dirname, "src/")
      }
}

如此,引用自己封装的组件或函数就可以以@作为起始,例如

import BaseButton from "./components/Button/BaseButton";

这样,避免了无止尽的../../../,而且万一某个组件切换了目录,组件里的外部引用也不需要重新调整,因为它们总是相对于src路径开始寻找。

请注意,此处只是允许webpack打包时识别,要开启路径提示,需要配置tsconfig.json中的path,文末有附配置代码

Plugin

clean-webpack-plugin

每次文件修改后,重新打包,导致 dist 目录下的文件越来越多怎么办?
快使用 clean-webpack-plugin,每次构建自动为你清理 /dist/目录,留你想留,清你想清,一键配置,呵护懒癌晚期的你

yarn add clean-webpack-plugin --dev

webpack.config.js

+ const CleanWebpackPlugin = require('clean-webpack-plugin');

 module.exports = {
   
    plugins: [
+     new CleanWebpackPlugin({
+           cleanOnceBeforeBuildPatterns:['**/*', '!store', '!store/**'] // 不删除store目录下的文件
+      }),

    ],
  };

注意:本文没有拆分不同环境的配置文件,在本地调试时,请注释掉这个插件

更多配置移步clean-webpack-plugin

HtmlWebpackPlugin

你可能需要一个HtmlWebpackPlugin,否则,你可能会想,react组件渲染了挂在哪里呢?找不到的话它会自己造一个Html模版么?

并不是,我只是因为懒没有创建模版文件,

当没有这个插件的时候,每次webpack打包生成的[name].bundle.js需要手动配置,现在,它解放了你的双手,你可以快乐的干点别的事了。

yarn add html-webpack-plugin --dev

不论你之前有没有创建,HtmlWebpackPlugin 会默认在输出目录生成 index.html 文件,所有的 bundle 会自动添加到 html 中。

+  const HtmlWebpackPlugin = require('html-webpack-plugin');
   
   
   plugins: [
     new CleanWebpackPlugin({
         cleanOnceBeforeBuildPatterns:['**/*', '!store', '!store/**'] // 不删除store目录下的文件
     }),
+     new HtmlWebpackPlugin({
+       title: 'Basic react app'
+     })
    ],

当然,一般来说,我们有时需要在public目录下新建一个index.html 的模版文件,里面定义了一些我们定制的内容,但我还是不想占用我的双手在里面引入打包生成的bundle.js文件。

 new HtmlWebpackPlugin({
    template: './public/index.html',
    filename: 'index.html', //打包后的文件名
    config: config.template
 })

更多功能移步html-webpack-plugin

webpack-dev-server

说了这么多,终于到了遇见Hello world的时候了。否则我担心你会看不下去。

webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。让我们运行以下命令:

yarn add webpack-dev-server --dev

修改配置文件

+   devServer: {
+		  port: '3000', //默认是8080
+		  publicPath: "http://localhost:3000/dist/",
+     hotOnly: true,
+     contentBase: './dist'
+   },

在package.json中补充脚本

  "scripts": {
      "watch": "webpack --watch",
      "start": "webpack-dev-server --open",
      "build": "webpack"
    },

在命令行先执行yarn build,再执行yarn start,页面自动打开 -> 遇见Hello World

Typescript

eslint

虽然我们使用babel来编译 ts|tsx文件,但是,有时候我们还会怀念ts的类型错误检查,比如

“不!我不会编译这玩意儿的!你的代码在42个不同的文件中出现异常!”

如何去检查类型错误呢?添加一段 lint 脚本来唤起 TypeScript 编译器。例如,将 npm test 命令调整为先检查类型,然后再继续运行单元测试。

由于性能问题,TypeScript 官方决定全面采用 ESLint,甚至把仓库(Repository)作为测试平台,而 ESLint 的 TypeScript 解析器也成为独立项目,专注解决双方兼容性问题。

yarn add eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev

同时,我们是一个react app 项目,所以,你懂的

yarn add eslint-plugin-react eslint-plugin-react-hooks --dev

在根目录下新建.eslintrc.js文件,填入如下内容

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    "ecmaFeatures": {
        "jsx": true
    },
    "ecmaVersion": 11,
    "sourceType": "module",
    project: './tsconfig.json',
},
  plugins: [
    "react",
    "react-hooks",
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
};

总有些文件是不需要检查的,做人么,可以为难别人,但不能为难自己,所以,在根目录创建一个.eslintignore文件

config/
scripts/
node_modules/
\.eslintrc.js // 干掉它自己。。。
webpack.config.js

经过考虑,补充 airbnb规则

yarn add eslint-config-airbnb-typescript eslint-plugin-jsx-a11y eslint-plugin-import --dev

eslintrc.js文件替换为

 extends: [
    'airbnb-typescript',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],

不要忘了在vscode中下载eslint插件

虽然上述规则经过了检验,但总有些对于我们自己来说是多余或者缺失的,所以在.eslintrc.js文件补充 rules字段,用于开启或关闭某些规则

 "rules": {
   // 禁止使用 var
   'no-var': "error",
   // 优先使用 interface 而不是 type
   '@typescript-eslint/consistent-type-definitions': [
   "error",
   "interface"
   ],
   '@typescript-eslint/no-explicit-any': 'off',
   '@typescript-eslint/explicit-module-boundary-types': 'off',
   "react-hooks/rules-of-hooks": "error",
   "react-hooks/exhaustive-deps": "warn",
   "react/prop-types": "off",
 }

修改package.json中script脚本

 "scripts": {
    "lint": "eslint --ext .ts --ext .tsx src/",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open",
    "build": "webpack"
  },

执行yarn lint,然后愉快的扇自己吧。。。。

我在src/index.tsx中留了三个错误,大概? 你可以试着改一下
受不了检查记得禁止一些规则

custom.d.ts

突然发现我没写怎么生成tsconfig.json,两个方法,

  1. 找个现成的复制到根目录,当然,不要乱复制
  2. 如果全局安装typescript,执行tsc --init

  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "outDir": "./dist/",
    "forceConsistentCasingInFileNames": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    // "isolatedModules": true,
    // "noEmit": true,
    "jsx": "react",
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"]
    }
  },
  "include": [
    "src","test",
  ]
}

配置项不是本文重点,主要是为了下面的内容。
在typscript中是无法识别非代码资源的,所以如果你试图import一个svg|png之类的文件就会提示你 cannot find module '.png'

custom.d.ts

declare module "*.svg" {
  const content: any;
  export default content;
}
declare module "*.png" {
  const content: any;
  export default content;
}

在tsconfig.json中有一个include字段,里面的内容表示ts需要转义的内容,将custom.d.ts文件放到include字段包含的目录中,就可以解决类型不识别的问题。

最后,这里附上

本篇教程到此结束,这只是一篇简明的指导式教程,如果有什么不对的地方,欢迎在评论中指出,我会及时修改

后续会针对webpack、eslint语法规范专门整理一期,工作较忙,时间就随缘了~

如果你收获了新知识,请点个赞吧~

本文收纳于: 从零开始的大前端筑基之旅(深入浅出,持续更新~)

推荐阅读:

参考文档

  1. webpack中文网
  2. babel
  3. eslint
  4. typescript