从头开始搭建一个 react + typescript 脚手架项目

前言

本文舍弃官方推荐的 npx create-react-app my-app 脚手架安装方式,自行搭建环境,让你对自己的项目有绝对的控制权,全面的了解项目的配置文件。

舍弃官方脚手架,手动搭建环境

创建基础配置文件

创建 package.json 文件
  npm init -y
复制代码
  • 部分字段介绍
    • script:运行脚本命令的 npm 命令行缩写
    • description:对项目的描述
创建 .gitignore
  • 该文件决定了项目进行 git 提交时所需要忽略掉的文件或文件夹
  • 编辑器如 vscode 也会监听 .gitignore 之外的所有文件
  • 如果没有进行忽略的文件有所变动时,在进行 git 提交时就会被识别为需要提交的文件
创建 .npmrc
  • 创建文件

      # powershell
      New-Item .npmrc
      # linux
      touch .npmrc
    复制代码
  • 加入配置代码使用淘宝镜像加快速度

      registry=https://registry.npm.taobao.org/
    复制代码
创建 README.md

规范代码

规范代码之 prettier
  1. 规范代码非常重要,放在第一位,一个好的项目,必须保证代码质量,统一代码风格

  2. 使用 prettier 进行代码格式化

  • 在 vscode 安装 Prettier - Code formatter 插件

  • 安装命令

  npm install prettier -D
复制代码
  • 创建 .prettierrc.js 文件,根据公司规则定义
  arrowParens: 'avoid', // 箭头函数只有一个参数的时候可以忽略括号
  bracketSpacing: true, // 括号内部不要出现空格
  endOfLine: 'lf', // 行结束符使用 Unix 格式
  jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
  jsxSingleQuote: false, // jsx 属性使用双引号
  printWidth: 140, // 行宽
  proseWrap: 'preserve', // 换行方式
  semi: true, // 句尾添加分号
  singleQuote: true, // 使用单引号
  tabWidth: 2, // 缩进
  useTabs: false, // 使用空格缩进
  trailingComma: 'es5', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
  bracketSpacing: true, // 在对象,数组括号与文字之间加空格
  semicolons: true, // 在语句末尾打印分号
复制代码
  • 新建 .vscode 文件夹下新建 settings.json 文件,其中的配置优先级高于编辑器的全局配置

  • 使用 .prettierignore 忽略某些文件夹

      /node_modules
      /build
      /dist
    复制代码
  • 关于 npm 的注释

    • npm install XXX -S
      • 这样安装是局部安装的,会写进 package.json 文件中的 dependencie 里
      • dependencies: 表示生产环境下的依赖管理
    • npm install XXX -D
      • 这样安装是局部安装的,会写进 package.json 文件中的 devDependencies 里
      • devDependencies :表示开发环境下的依赖管理
      • 如果你安装的库是用来打包的、解析代码的,比如 webpack、babel,就可以用 -D 来安装,项目上线了,这些库就没用了
    • npm install XXX -g
      • 表示全局安装,安装一次过后,可以在其他地方直接用
    • npm install XXX
      • npm 5 开始通过 npm install 什么都不加 和 npm install --save 一样,都是局部安装并会把模块自动写入 package.json 中的 dependencies 里
代码规范之 eslint
  1. eslint 主要为了解决代码质量的问题,可以进行修复,进行红线提示

  2. 使用 eslint

  • 安装命令

      npm install eslint -D
      npx eslint --init
    复制代码
    • 执行第二条命令之后,会进行选择,选择自己需要的即可
    • 这边为了避免报错提前安装一下 typescript npm install typescript -D
    • 进行配置
        具体可以根据自己的需要自行设置
      复制代码
  • 如果需要开启 eslint 自动修复功能,除了在 vscode 中配置外,还可以在 .vscode 文件夹下的 setting.json 中加入如下代码,效果是一样的

      "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
      "typescript.tsdk": "./node_modules/typescript/lib", // 代替 vscode 的 ts 语法智能提示
      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true,
      },
    复制代码
  • 使用 .eslintignore 忽略某些文件夹

      /node_modules
      /build
      /dist
    复制代码
prettier 和 eslint 冲突问题
  • 安装插件 eslint-config-prettier,禁用和 prettier 起冲突的规则

      npm install eslint-config-prettier -D
    复制代码
  • 在 .eslintrc.js 的 extends 中加入以下代码

      'prettier'
    复制代码
代码提交前对提交的代码进行 eslint 校验
  1. 利用 lint-staged 和 husky,对 git 缓存区最新改动过的文件进行格式化和 eslint 校验
  • 安装命令
      npm install husky lint-staged -D
    复制代码
  • 在 package.json 中加入如下配置
  "husky": {
   "hooks": {
     "pre-commit": "lint-staged"
   }
 },
 "lint-staged": {
   "*.{ts,tsx,js}": [
     "eslint --config .eslintrc.js" // --config 指定配置文件,对暂存区的 .ts .tsx .js 进行校验,这边不加 --fix,防止自动修复未知代码
   ],
   "*.{ts,tsx,js,json,html,yml,css,less,scss,md}": [
     "prettier --write" // --write 不会更改 语法层面的代码,可以加去自动 format
   ]
 }
复制代码
对提交的 commit message 进行校验检查
  1. 安装 commitlint
  • 安装命令
      npm install @commitlint/cli @commitlint/config-conventional -D
    复制代码
  • 根目录新建文件 .commitlintrc.js,输入以下配置
      module.exports = {
        extends: ['@commitlint/config-conventional'],
        rules: {
          'type-enum': [
            2,
            'always',
            ['build', 'ci', 'chore', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', 'anno'],
          ],
        },
      }
    复制代码
  • 在 package.json 的 husky 配置以下代码
      {
        "husky": {
          "hooks": {
            "pre-commit": "lint-staged",
            "commit-msg": "commitlint --config .commitlintrc.js -E HUSKY_GIT_PARAMS"
          }
        },
      }
    复制代码

webpack 配置

安装 webpack 和 webpack-cli 命令
  npm install webpack webpack-cli -D
复制代码
创建 config 文件夹
  • 在文件夹下创建 constant.js 定义公用变量

      const path = require('path');
    
      const PROJECT_PATH = path.resolve(__dirname, '../'); // 当前项目根目录
      const PROJECT_NAME = path.parse(PROJECT_PATH).name;
    
      module.exports = {
        PROJECT_PATH,
        PROJECT_NAME,
      };
    复制代码
  • 在文件夹下创建 webpack.common.js 文件,新建如下代码

  const path = require('path');
  const { PROJECT_PATH } = require('./constant');

  module.exports = {
    entry: {
      index: path.resolve(PROJECT_PATH, './src/index.tsx'),
    },
    output: {
      filename: 'js/[name].[hash:8].js',
      path: path.resolve(PROJECT_PATH, './dist'),
    },
  };
复制代码
  • __dirname 电脑上的绝对路径,指代当前文件夹
在 package.json 的 script 中加入命令,在终端执行 npm run bulid,就会生成 dist 文件对应的文件
  "build": "webpack --config ./scripts/config/webpack.common.js"
复制代码
区分开发、生产环境
  • 安装 webpack-merge 命令

      npm install webpack-merge -D
    复制代码
    • 在 config 下新建 webpack.dev.js 文件,配置开发环境

        const { merge } = require('webpack-merge');
        const common = require('./webpack.common');
      
        module.exports = merge(common, {
          mode: 'development',
        });
      复制代码
    • 在 config 下新建 webpack.prod.js 文件,配置开发环境

        const { merge } = require('webpack-merge');
        const common = require('./webpack.common');
      
        module.exports = merge(common, {
          mode: 'production',
        });
      复制代码
  • 使用 cross-env 在公共配置文件中进行区分开发和生产环境

    • 公共配置中也会存在某个配置的某个选项在开发环境和生产环境中采用不同的配置,为了避免重复写代码,设置环境变量
    • cross-env 可跨平台设置和使用环境变量,无需在考虑操作系统带来的差异性
    • 安装命令
      npm install cross-env -D
    复制代码
    • 在 package.json 中的 script 中修改代码
      "start": "cross-env NODE_ENV=development webpack --config ./scripts/config/webpack.dev.js",
      "build": "cross-env NODE_ENV=production webpack --config ./scripts/config/webpack.prod.js"
    复制代码
    • 在 constants.js 中加入变量
      const isDev = process.env.NODE_ENV !== 'production';
    复制代码
    • 在 webpack.common.js 中修改 output,hash 值生产环境缓存的时候需要用到,开发环境不需要
      filename: `js/[name]${isDev ? ' ' : '.[hash:8]'}.js`,
    复制代码
    • devtool 增加报错信息

      • 在 webpack.dev.js 加入代码
        devtool: 'cheap-module-source-map',
      复制代码
      • 生产环境不需要报错信息,在 webpack.pord.js 设置为 none
        devtool: false,
      复制代码
npm run start 出现实时页面
  • html-webpack-plugin 将打包完的 js 文件自动引入 html 文件
  • webpack-dev-server 本地起一个 http 服务,通过简单的配置还可指定其端口、热更新的开启等
  • 安装命令
      npm install webpack-dev-server html-webpack-plugin -D
    复制代码
  • 根目录新建 public 文件夹下的 index.html 文件,在 webpck.common.js 中增加 plugin,引入 index.html 文件
  plugins: [
    new HtmlWebpackPlugin({
      template: resolve(PROJECT_PATH, './public/index.html'),
      filename: 'index.html',
      cache: false,
      minify: isDev
        ? false
        : {
            removeAttributeQuotes: true,
            collapseWhitespace: true,
            removeComments: true,
            collapseBooleanAttributes: true,
            collapseInlineTagWhitespace: true,
            removeRedundantAttributes: true,
            removeScriptTypeAttributes: true,
            removeStyleLinkTypeAttributes: true,
            minifyCSS: true,
            minifyJS: true,
            minifyURLs: true,
            useShortDoctype: true,
          },
    }),
  ],
复制代码
  • 在 constants.js 中定义变量,在 webpack.dev.js 引入变量并中配置 devServer
  devServer: {
    host: SERVER_HOST, // 指定 host,不设置的话默认是 localhost
    port: SERVER_PORT, // 指定端口,默认是8080
    compress: true, // 是否启用 gzip 压缩
    open: true, // 打开默认浏览器
    hot: true, // 热更新
  },
复制代码
  • 如果前面都没出错的话,这个时候 npm run start 就可以启动页面了,恭喜你!
  • 利用 clean-webpack-plugin 插件,每次 npm run build 打包编译清除 dist 文件夹
    • 安装命令
      npm install clean-webpack-plugin -D
    复制代码
    • 在 webpack.pord.js 中加入以下代码,可以自动找到 output 中的 path 然后进行清除
       plugins: [
        new CleanWebpackPlugin(),
      ],
    复制代码
loader 文件处理
  • 安装 style-loader 和 css-loader,进行 css 样式处理

    • 安装命令
      npm install style-loader css-loader -D
    复制代码
    • 在 webpack.common.js 加入如下代码
       module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: false, // 默认就是 false
                  sourceMap: isDev, // 开启后与 devtool 设置一致, 开发环境开启,生产环境关闭
                  importLoaders: 0, // 指定在 CSS loader 处理前使用的 laoder 数量
                },
              },
            ],
          },
        ],
      },
    复制代码
  • 安装 less 和 less-loader,处理 less 文件

    • 安装命令
      npm install less less-loader -D
    复制代码
    • webpack.common.js 的 rules 加入代码
     {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: false,
              sourceMap: isDev,
              importLoaders: 1, // 需要先被 less-loader 处理,所以这里设置为 1
            },
          },
          {
            loader: 'less-loader',
            options: {
              sourceMap: isDev,
            },
          },
        ],
      },
    复制代码
  • 安装 node-sass 和 sass-loader 处理 scss 文件

    • 安装命令
        npm install node-sass sass-loader -D
      复制代码
    • webpack.common.js 的 rules 中加入以下代码
         {
          test: /\.scss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: false,
                  sourceMap: isDev,
                  importLoaders: 1, // 需要先被 sass-loader 处理,所以这里设置为 1
                },
              },
              {
                loader: 'sass-loader',
                options: {
                  sourceMap: isDev,
                },
              },
            ],
          },
      复制代码
  • file-loader 或者 url-loader 处理本地资源文件

    • 安装命令
      npm install file-loader url-loader -D
    复制代码
    • rules 中添加代码
      {
         test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
         use: [
           {
             loader: 'url-loader',
             options: {
               limit: 10 * 1024,
               name: '[name].[contenthash:8].[ext]',
               outputPath: 'assets/images',
             },
           },
         ],
       },
       {
         test: /\.(ttf|woff|woff2|eot|otf)$/,
         use: [
           {
             loader: 'url-loader',
             options: {
               name: '[name].[contenthash:8].[ext]',
              outputPath: 'assets/fonts',
            },
          },
        ],
      },
    复制代码

支持 react 和 typescript

react 安装命令
  npm install react react-dom -S
复制代码
安装 babel-loader 识别语法,不然报错
  • 安装命令
      npm install babel-loader @babel/core @babel/preset-react -D
    复制代码
  • 支持 ts 的安装命令
  npm install @babel/preset-typescript -D
复制代码
  • 根目录新建 .babelrc 文件

      {
        "presets": ["@babel/preset-react", "@babel/preset-typescript"]
      }
    复制代码
  • 在 webpack.common.js 的 rules 中加入以下代码

  {
    test: /\.(tsx?|js)$/,
    loader: 'babel-loader',
    options: { cacheDirectory: true },
    exclude: /node_modules/,
  },
复制代码
  • 在 webpack.common.js 中增加 resolve 属性,webpack 会先尝试加上 .tsx 后缀,看找得到文件不,如果找不到就依次尝试进行查找,所以我们在配置时尽量把最常用到的后缀放到最前面,可以缩短查找时间
  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.json'],
  },
复制代码
  • react 的类型声明,安装命令
  npm install @types/react @types/react-dom -D
复制代码
  • 注意,此时这边可能会有 eslint 报错,只要在 eslint 配置文件修改对应的值即可,接下来,可以在 tsx 文件中尝试代码,开始 npm run start 了
根目录创建 tsconfig.json 文件
  • 生成文件命令,也可自己新建
      npx tsc --init
    复制代码
  • 删除其中无用的代码,加入如下代码
      具体配置见 githup
    复制代码
    • 讲一下 baseUrl 和 paths,配置路径,防止深层次嵌套
      "baseUrl": "./", // 根路径
      "paths": { // 路径映射,与 baseUrl 关联
        "Src/*": ["src/*"],
        "Components/*": ["src/components/*"],
        "Utils/*": ["src/utils/*"]
      },
    复制代码
    • 在 webpack.common.js 中加入代码
      resolve: {
        extensions: ['.tsx', '.ts', '.js', '.json'],
        alias: {
          'Src': resolve(PROJECT_PATH, './src'),
          'Components': resolve(PROJECT_PATH, './src/components'),
          'Utils': resolve(PROJECT_PATH, './src/utils'),
        }
      },
    复制代码
    • 有了以上的配置,就可以直接 Src、Components 引入了,不必再 ../../ 了
babel 配置
  • 之前已经配置过部分 babel,这边进一步进行配置

  • @babel/preset-env 据设置的目标浏览器环境找出所需的插件去转译 ES6+ 语法

    • 安装命令
      npm install @babel/preset-env -D
    复制代码
  • @babel/plugin-transform-runtime 解决一些新特性,比如 includes

    • 安装命令

        npm install @babel/plugin-transform-runtime -D
        npm install @babel/runtime-corejs3 -S
      复制代码
    • 其中 @babel/plugin-transform-runtime 的作用是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs3 里面的模块。所以前者运行在编译时,后者运行在运行时。类似 polyfill,后者需要被打包到最终产物里在浏览器中运行

  • 修改 .babelre 中的代码

  {
    "presets": [
      [
        "@babel/preset-env",
        {
          // 防止babel将任何模块类型都转译成CommonJS类型,导致tree-shaking失效问题
          "modules": false
        }
      ],
      "@babel/preset-react",
      "@babel/preset-typescript"
    ],
    "plungins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": {
            "version": 3,
            "proposals": true
          },
          "useESModules": true
        }
      ]
    ]
  }
复制代码

webpack 优化

注意到官方脚手架 public 中有 favicon.ico 文件,我们可以同样在 index.html 引入
  • 要想 npm run build 的时候 dist 文件夹中生成图片,安装 copy-webpack-plugin 插件
    • 安装命令
        npm install copy-webpack-plugin -D
      复制代码
    • 在 webpack.common.js 加入引入并加入 plugin 代码
        new CopyWebpackPlugin({
          patterns: [
            {
              context: resolve(PROJECT_PATH, './public'),
              from: '*',
              to: resolve(PROJECT_PATH, './dist'),
              toType: 'dir',
              globOptions: {
                dot: true,
                gitignore: true,
                ignore: ['**/index.html'],
              },
            },
          ],
        }),
      复制代码
    • 这个插件就是处理静态资源的,其他静态资源也可
start 或者 build 的时候显示编译进度
  • 安装 webpackbar 命令
      npm install webpackbar -D
    复制代码
  • webpack.common.js 加入以下代码,颜色根据自己喜欢的来
      new WebpackBar({
        name: isDev ? 'run' : 'build',
        color: isDev ? '#00b2a9' : '#ee6139',
      }),
    复制代码
编译时进行 typescript 类型检测
  • fork-ts-checker-webpack-plugin 防止出现不必要的 bug

    • 安装命令

        npm install fork-ts-checker-webpack-plugin -D
      复制代码
    • 在 webpack.common.js 加入以下代码

        new ForkTsCheckerWebpackPlugin({
          typescript: {
            configFile: resolve(PROJECT_PATH, './tsconfig.json'),
          },
        }),
      复制代码
把一些引用的第三方包也打成单独的 chunk
  • 在 webpack.prod.js 加入以下代码
      output: {},
      optimization: {
        splitChunks: {
          chunks: 'all',
          name: false,
          minSize: 0,
        },
      },
    复制代码
代码热更新
  • 修改完代码,不会整个页面刷新,只会局部的修改页面

  • 在 webpack.dev.js 中修改代码

    • devServer 中的 hot: true
    • 新加 plugins
        const webpack = require('webpack');
        plugins: [new webpack.HotModuleReplacementPlugin()],
      复制代码
    • 安装 @types/webpack-env
        npm install @types/webpack-env -D
      复制代码
    • 在定义的路口文件加入以下代码
        if (module && module.hot) {
          module.hot.accept()
        }
      复制代码
抽离 css 单独打包并且去除无用 css 代码
  • mini-css-extract-plugin 进行 css 样式拆分,单独打包

    • 安装命令

        npm install mini-css-extract-plugin -D
      复制代码
    • 在 webpack.common.js 修改以下代码

        将 'style-loader' 修改为 isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
      复制代码
    • 在 webpack.prod.js 中加入以下代码

        const MiniCssExtractPlugin = require('mini-css-extract-plugin');
        在 plugins 中添加
        new MiniCssExtractPlugin({
          filename: 'css/[name].[contenthash:8].css',
          chunkFilename: 'css/[name].[contenthash:8].css',
          ignoreOrder: false,
        }),
      复制代码
  • purgecss-webpack-plugin 去除无用样式

    • 安装命令(注释:glob 是用来查找文件路径的)
        npm install purgecss-webpack-plugin glob -D
      复制代码
    • 在 webpack.prod.js 中加入以下代码
        const PurgeCSSPlugin = require('purgecss-webpack-plugin');
        new PurgeCSSPlugin({
          paths: glob.sync(`${resolve(PROJECT_PATH, './src')}/**/*.{tsx,scss,less,css}`, { nodir: true }),
          whitelist: ['html', 'body'],
        }),
      复制代码
压缩代码
  • 压缩 js 代码

    • terser-webpack-plugin 安装命令

        npm install terser-webpack-plugin -D
      复制代码
    • 在 webpack.prod.js 的 optimization 中加入以下代码

      ```
        const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
        const TerserPlugin = require('terser-webpack-plugin');
      
        minimize: true,
        minimizer: [
          new TerserPlugin({
            extractComments: false, // 去掉所有的注释,除了有特殊标记的注释
            terserOptions: {
              compress: { pure_funcs: ['console.log'] }, // 将代码的 console.log 去掉
            },
          }),
          new CssMinimizerPlugin(), // 下面压缩 css 的代码
        ],
      ```
      复制代码
  • 压缩 css 代码

    • css-minimizer-webpack-plugin 安装命令
        npm install css-minimizer-webpack-plugin -D
      复制代码
    • 代码在上面已经加过

分割线

  • 文章进行到这边,也花了挺多时间的,文章肯定存在很多不足,欢迎大家指正
  • 接下来也会在此文章基础上继续封装、优化
  • githup 地址:https://github.com/BreezyZhang/react-scaffold

猜你喜欢

转载自juejin.im/post/7037316769195196429