记录一次搭建 react 项目遇到的坑

背景

电商项目需要搭建一个 h5 项目。这里简单记录一下搭建过程遇到的坑,作为总结。

ui 框架:为了统一 ui 框架,因此选择使用 antd-mobile。
打包:parceljs 在此前的项目中已经使用过很多次了,打包速度较 cra 快,且配置简单,因此拟用 parcel。
ts:多人项目还是上 ts,没什么好说的。
很快就搭建好 demo,接着就开始遇到一些问题。

antd-mobile 按需加载

antd-mobile 按需加载推荐使用 babel-plugin-import

// .babelrc or babel-loader option
{
  "plugins": [
    ["import", { "libraryName": "antd-mobile", "style": "css" }] // `style: true` 会加载 less 文件
  ]
}

此前 parcel 实际上内置支持了 ts,直接转成 es5 了,因此此前没有额外再配置 babel。
🤔 babel-plugin-import 只支持 es6 module 写法的代码,看来需要先把 ts 转成 es6 然后在通过 babel 转成 es5, 没问题,安排~ 于是:

  1. 新增 .babelrc

  2. 修改 tsconfig 的 module 为 es6

    {
      "compilerOptions": {
        "module": "ES6"
      }
    }

    项目启动,没问题。但是 vscode 开始报错:

import React from 'react'
// Module '"/node_modules/@types/react/index"' can only be default-imported using the 'allowSyntheticDefaultImports' flag

根本原因是由于 react 导出如下:

module.exports = require('react.development.js')

而非

module.exports.default = require('react.development.js')

Babel 会在 commonjs 格式添加 default 导出,那我们要做的就是让 ts 不要提示这种类型的报错:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true
    // ...
  }
}

🤔 不过为什么一开始 parcel 不会报错呢?

接下来路由动态引入组件开始报错:

import React, { Suspense, lazy } from 'react'

const List = lazy(() => import('./modules/List'))
// Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.

看来 es6 是不支持 import() 语法, 那好办,将 module 改成 esnext

{
	"compilerOptions": {
    "module": "ESNext",
  }
}

接下来引入 anti-mobile 组件

import { List } from 'antd-mobile'
// Cannot find module 'antd-mobile' or its corresponding type declarations.

此时提示找不到对应的模块声明, 此时我的内心是崩溃的,看起来应该是模块解析路径不对,因此在 tsconfig 中显式配置 moduleResolution:

{
  "compilerOptions": {
    "moduleResolution": "Node"
    // ...
  }
}

不再报错了,看看 ts 文档的解释吧,ts 共有两种可用的模块解析策略:NodeClassic

module === "AMD" or "System" or "ES6" ? "Classic" : "Node"

🤔 按理说 module 为 esnext 的话岂不是应该按 node 策略解析?但是目前看来似乎不是这样的?

配置 antd-mobile 主题

接下来需要配置主题,anti-mobile 推荐使用 modifyVars 来配置主题。
将 .babelrc 改成做一下调整:

{
    ...
    "plugins": [
        ["import", {"libraryName": "antd-mobile", "style": true}],
        ...
    ]
}

按照 webpack 的经验,此时需要开启 javascriptEnabled: true问题来了 parcel 内置支持 less ,那我们要在哪里去配置 less 呢?根据 issue 提到可以用 .lessrc 文件来配置,但是我经过尝试发现其实是无效的。。
至此基本放弃 parcel。parcel 在快速搭建实验性项目时确实有一定的优势,不过生态确实不如 webpack,由此转向使用 create-react-app

create-react-app

使用 cra 很快也建好 demo,解决如何让 cra 创建的项目支持 less ,这个简单:
通过 customize-cra 提供的 addLessLoader 方法即可,同时也提供了 fixBabelImports 可以很方便的添加 babel-plugin-import 配置:

const { override, fixBabelImports, addLessLoader } = require('customize-cra')

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd-mobile',
    style: true,
  }),
  addLessLoader({
    lessOptions: {
      javascriptEnabled: true,
      modifyVars: {
        '@brand-primary': '#ff74b9',
        'brand-primary-tap': '#ff9cca',
      },
    },
  })
)
别名

接着是配置路径别名遇到的问题,通过 customize-cra 提供的 addWebpackAlias 可以配置别名,但是 typescript 一直无法解析对应别名,那么应该只需要配置好 ts 的 paths 就行了吧:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
    // ...
  }
}

vscode 不再报错了,接下来就是见证奇迹的时刻了npm run start
控制台显示 - compilerOptions.paths must not be set (aliased imports are not supported) 并且 cra 会自动把 tscongfig 中的 paths 移除掉 😂
根据 issue,使用将 baseUrl 和 paths 的配置移动到单独的文件后,在 tsconfig 里通过 extends 的方式继承,虽然控制台依然提示错误,但是别名可以生效。

mini-css-extract-plugin

在 build 时,mini-css-extract-plugin 提示 Conflicting order, 错误位置在 antd-mobile 的样式文件。根据 issue 以及 issue 看来这个问题主要是由于懒加载加上 antd 组件导入顺序不一致导致的,目前看来无需解决。