React小册 | Webpack | Vite | Snowpack

3,401 阅读8分钟

本篇记录一下手动搭建 React 过程中遇到的问题 项目分别借助当下热门 🔥 的 Webpack | Vite | Snowpack 来搭建

项目地址 github.com/rick-chou/o…

需要的 vscode 插件 eslint | prettier

时光机 👾

Webpack

typescript

安装 typescriptts-loader

后者用作 webpack 的 loader 用于将 ts --> js 的转换

安装完后使用 npx tsc --init 生成 tsconfig.json 文件

我这里开启了部分的配置 具体的每一项配置 可参考官方文档的说明

aka.ms/tsconfig.js…

{
  "include": ["src", "types"] /*  */,
  "exclude": ["node_modules"],
  "compilerOptions": {
    "target": "ESNEXT" /* 直接输出为最新的 ES 标准 */,
    "module": "ESNEXT" /* 面向未来的 ESM 模块化 */,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false /* 不允许导入js文件 */,
    "jsx": "react-jsx" /* react17后 选择这个就好了 */,
    "outDir": "./build" /* 打包路径 */,
    "strict": true /* 开启严格模式 */,
    "noImplicitAny": true /* 不能用any类型 */,
    "moduleResolution": "node" /* 模块解析策略 这里我们使用node */,
    "baseUrl": "./" /* 配置根路径 比如下面的typeRoots和最上面的include exclude都会加上这个路径 */,
    "typeRoots": [
      "node_modules/@types",
      "types/*.d.ts"
    ] /* 默认会引入@types下的类型类型声明文件 可以配置这项加入一些自己定义的类型 */,
    "allowSyntheticDefaultImports": true /* 即使一个模块没有默认导入 也可以使用默认导入 */,
    "esModuleInterop": true /* https://zhuanlan.zhihu.com/p/148081795 */,
    "experimentalDecorators": true /* 开启装饰器语法 */,
    "skipLibCheck": false /* 跳过所有.d.ts文件的类型检查 */,
    "forceConsistentCasingInFileNames": true
  }
}

我们新建一个 types 目录 可以定义一些我们想定义的类型 例如

/* Use this file to declare any custom file extensions for importing */
/* Use this folder to also add/extend a package d.ts file, if needed. */

/* CSS MODULES */
declare module '*.module.css' {
  const classes: { [key: string]: string };
  export default classes;
}
declare module '*.module.scss' {
  const classes: { [key: string]: string };
  export default classes;
}
declare module '*.module.sass' {
  const classes: { [key: string]: string };
  export default classes;
}
declare module '*.module.less' {
  const classes: { [key: string]: string };
  export default classes;
}
declare module '*.module.styl' {
  const classes: { [key: string]: string };
  export default classes;
}

/* CSS */
declare module '*.css';
declare module '*.scss';
declare module '*.sass';
declare module '*.less';
declare module '*.styl';

/* IMAGES */
declare module '*.svg' {
  const ref: string;
  export default ref;
}
declare module '*.bmp' {
  const ref: string;
  export default ref;
}
declare module '*.gif' {
  const ref: string;
  export default ref;
}
declare module '*.jpg' {
  const ref: string;
  export default ref;
}
declare module '*.jpeg' {
  const ref: string;
  export default ref;
}
declare module '*.png' {
  const ref: string;
  export default ref;
}

/* CUSTOM: ADD YOUR OWN HERE */

webpack

首先安装 webpack webpack-cli webpack-dev-server

  • webpack webpack 核心功能

  • webpack-cli webpack 命令行

  • webpack-dev-server 开发环境使用 方便调试

然后新建一个 config 文件夹 来编写 webpack 的配置文件

我们为开发环境和生产环境创建各自的配置文件

分别为 webpack.config.dev.jswebpack.config.prod.js

因为项目中 css 我使用了 scss 所以提前安装下 sass sass-loader css-loader style-loader

还有构建需要的 html-webpack-pluginclean-webpack-plugin

webpack.config.dev.js 的配置如下

const path = require('path');
// 生成html文件 自动引入打包后的js文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: path.resolve(__dirname, '../public/index.html'),
});

module.exports = {
  mode: 'development',
  entry: path.resolve(__dirname, '../src/index.tsx'),
  output: {
    path: path.resolve(__dirname, '../build'),
    filename: 'js/[name].[hash].bundle.js',
  },
  module: {
    // 由于我们是ts项目 所以直接借助ts-loader将文件编译成js 无需再使用babel转译
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      // 配置scss环境 ⚠️注意 loader使用顺序为从右到左
      // 所以使用顺序为 sass-loader --> css-loader --> style-loader
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
    ],
  },

  plugins: [htmlWebpackPlugin],

  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
};

webpack.config.prod.js 配置如下

const path = require('path');
// 生成html文件 自动引入打包后的js文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 打包前 清理打包目录文件夹 因为打包后的文件带hash值 不会重名 所以不会覆盖 多次打包就会有很多bundle.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: path.resolve(__dirname, '../public/index.html'),
});

module.exports = {
  mode: 'production',
  entry: path.resolve(__dirname, '../src/index.tsx'),
  output: {
    path: path.resolve(__dirname, '../build'),
    filename: 'js/[name].[hash].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
    ],
  },

  plugins: [htmlWebpackPlugin, new CleanWebpackPlugin()],

  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
};

因为没有涉及到太多的场景 我们的 prod 只比 dev 的多了一个 clean-webpack-plugin 插件的配置

随后新增script命令

"start": "webpack-dev-server --config  ./config/webpack.config.dev.js --open",
"build": "webpack --config ./config/webpack.config.prod.js",

你也可以使用 webpack-merge 这个库去抽取一些通用的配置

github.com/survivejs/w…

eslint

eslint 可以校验我们的代码 生成统一的风格

首先安装 eslint 然后在使用 npx eslint --init 生成 eslint 的配置

下面为作者的一些选择

? How would you like to use ESLint? …
  To check syntax only
  To check syntax and find problems
❯ To check syntax, find problems, and enforce code style

? What type of modules does your project use? …
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

? Which framework does your project use? …
❯ React
  Vue.js
  None of these

? Does your project use TypeScript?
❯ Yes
  No

? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯ Browser
  Node

? How would you like to define a style for your project? …
❯ Use a popular style guide
  Answer questions about your style
  Inspect your JavaScript file(s)

? Which style guide do you want to follow? …
❯ Airbnb: https://github.com/airbnb/javascript
  Standard: https://github.com/standard/standard
  Google: https://github.com/google/eslint-config-google
  XO: https://github.com/xojs/eslint-config-xo

? What format do you want your config file to be in? …
❯ JavaScript
  YAML
  JSON

Would you like to install them now with npm?
❯ Yes
  No

做完这些后 项目的根目录下就会生成一个 .eslintrc.js 文件 内容如下

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ['plugin:react/recommended', 'airbnb'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {},
};

同时 附上上述操作后新增的依赖

"@typescript-eslint/eslint-plugin": "^4.31.2",
"@typescript-eslint/parser": "^4.31.2",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.26.0",
"eslint-plugin-react-hooks": "^4.2.0"

prettier

安装 prettier 新建一个 prettier.config.js 文件 下面是我的一些配置

module.exports = {
  printWidth: 100, // 单行长度
  tabWidth: 2, // 缩进长度
  useTabs: false, // 使用空格代替tab缩进
  semi: true, // 句末使用分号
  singleQuote: true, // 使用单引号
  quoteProps: 'as-needed', // 仅在必需时为对象的key添加引号
  jsxSingleQuote: true, // jsx中使用单引号
  trailingComma: 'all', // 多行时尽可能打印尾随逗号
  bracketSpacing: true, // 在对象前后添加空格-eg: { foo: bar }
  arrowParens: 'always', // 单参数箭头函数参数周围使用圆括号-eg: (x) => x
  requirePragma: false, // 无需顶部注释即可格式化
  insertPragma: false, // 在已被prettier格式化的文件顶部加上标注
  htmlWhitespaceSensitivity: 'ignore', // 对HTML全局空白不敏感
  endOfLine: 'lf', // 结束行形式
  embeddedLanguageFormatting: 'auto', // 对引用代码进行格式化
};

随后 写入一个 script 命令

"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\""

执行这个命令 就可以格式化所有的代码 按照上述的规则

到此 我们的基本配置结束 随后安装必须的 react react-dom

然后创建如下的项目目录

webpack
├─ .eslintrc.js
├─ .gitignore
├─ .prettierrc.config.js
├─ config
│  ├─ webpack.config.dev.js
│  └─ webpack.config.prod.js
├─ package-lock.json
├─ package.json
├─ public
│  ├─ index.html
│  └─ pic.jpg
├─ src
│  ├─ app.module.scss
│  ├─ app.tsx
│  └─ index.tsx
├─ tsconfig.json
├─ types
│  └─ static.d.ts
└─ yarn.lock

FAQ

然而 当你还没启动项目的时候 离谱的事情就发生了

Cannot find module './app.module.scss' or its corresponding type declarations.

原因很简单 ts 不能把 app.module.scss 当成一个模块

而且我们也没有为它编写一个类型声明文件

解决的方法有两种

第一种是为每一个 scss 文件编写.d.ts 声明文件

例如我们的 app.module.scss 有如下内容

.container {
  color: skyblue;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 66px;
  height: 90vh;
}

我们就可以编写一个 app.module.scss.d.ts 文件 为每一个选择器声明类型

export const container: string;

这样就消除这个报错了 而且在引入的时候 会有友善的代码提示

第二种 是为 scss 文件编写全局的声明文件 我们在上面 ts 那一部分创建了 types 文件

里面就有我们对 scss 这些类型文件的定义

我们只需要在 tsconfig.json 中配置 typeRoots 把我们的 types 加进去就好了

"typeRoots": ["node_modules/@types", "types/*.d.ts"]

eslint

eslint 也会有很多不兼容的地方

例如 我们的项目是 React17 版本 不必再引入 React 但是 eslint 会报错

然后一些等等的问题 我在下面罗列了

在.eslintrc.js 的 rules 中加入如下配置

{
// 解决 JSX not allowed in files with extension '.tsx' 为tsx文件开启jsx语法
'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
// 解决 'React' must be in scope when using JSX 但是17后不必再引入 所以关闭这条规则
'react/react-in-jsx-scope': 'off',
// 解决 'React' was used before it was defined
'@typescript-eslint/no-use-before-define': ['error'],
'no-use-before-define': 'off',
// 解决 Missing file extension "tsx" for 'xxx'
'import/extensions': [
  'error',
  'ignorePackages',
  {
    ts: 'never',
    tsx: 'never',
  },
],
// 关闭默认导出 .d.ts中会报错
'import/prefer-default-export': 'off',
},

yarn start 🎉 🎉 🎉 🎉 🎉

Vite

typescript

ts 安装 typescript 就好了 不需要 loader 了 配置同上

scss

安装 sass 也不需要其他的 loader 了

eslint | prettier 同上

vite

安装 vite @vitejs/plugin-react

在根目录下新建 vite.config.ts

内容如下

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
});

package.json 中写入 script

"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview",

FAQ 同上

yarn dev 🎉 🎉 🎉 🎉 🎉

Snowpack

snowpack

其他的基础配置都类似 这里不再赘述

snowpack 有自己的生态 首先安装以下的包

  • snowpack

  • @snowpack/plugin-sass

  • @snowpack/plugin-react-refresh

  • @snowpack/plugin-typescript

通过名称 大致就可以知道它们各自的功能

snowpack 有自己的配置文件 snowpack.config.mjs

详细的配置参考官方

www.snowpack.dev/reference/c…

/** @type {import("snowpack").SnowpackUserConfig } */
export default {
  mount: {
    // 静态资源路径
    public: { url: '/', static: true },
    // 打包路径
    src: { url: '/dist' },
  },
  // 配置一些插件
  plugins: [
    '@snowpack/plugin-sass',
    '@snowpack/plugin-react-refresh',
    '@snowpack/plugin-typescript',
  ],
};

yarn start 🎉 🎉 🎉 🎉 🎉

package.json 添加 script

"start": "snowpack dev",
"build": "snowpack build",

Git

这里再补充一下对 git 提交信息的管控

我们需要引入 husky lint-stagedcommit-lint

yarn add -D husky lint-staged @commitlint/config-conventional @commitlint/cli cz-conventional-changelog

接下来我们要添加 .lintstagedrc.json

{
  "*.{js,jsx,ts,tsx}": ["eslint --cache"]
}

接下来添加 commitlint.config.js

module.exports = { extends: ['@commitlint/config-conventional'] };

最后执行如下命令

npm set-script prepare "husky install"
npm run prepare

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

执行完毕后将.husky/commit-msg 内容修改如下

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

npx --no-install commitlint --edit "$1"

package.json 中添加

"scripts": {
  "commit": "./node_modules/.bin/git-cz"
},

至此 我们可以使用 yarn commit 的方式 提交 commit

也可以使用 git commit -m xxx 的形式

一但你的 commit 不规范 就会抛出异常

🎉 🎉 🎉