小程序 Taro 使用 pnpm 的 monorepo 方案小总结

背景

最近一段时间参与一个小程序项目开发,分为 A、B 两个,开始搞的时候团队暂时没有小程序开发的基础,走一步看一步。

两个小程序有很多公用的组件、函数,刚开始搞就看谁先做,后做的小程序复制一份代码过去复用。从 0 到 1 的过程比较简单,当设计师介入走查修复问题比较麻烦,一个问题两个项目同时存在,其中一个修复复制到另一个,也不能确定另一个是否也做了更新。

此时我们需要把公共组件、函数比较优雅的方式复用起来。

方案一,公共组件、函数提取成 npm 包,这种形式最常见,更新、调试不是很方便,版本更新可能造成破坏性改动,无法和项目代码联合在一起 lint。

方案二,monorepo,及时调试,不用发版本,也可以和项目代码一起 lint,公共库需要本地打包一下。

两个方案多多少少都有点不顺畅,我们的目的是把代码复用起来,就像使用本项目目录下的代码一样,同时复用的代码也能单独 lint 检测代码。最后我们使用 monorepo 混合别名一起使用。

目录结构

bash 复制代码
|-- apps
|-- -- app1
|-- -- app2
|-- packages
|-- -- components
|-- -- hooks
|-- -- utils
|-- package.json
|-- pnpm-workspace.yaml
|-- tsconfig.base.json
|-- tsconfig.json

从目录结构上看,把业务相关的代码都放到 apps 文件夹下,复用代码都放到 packages 文件夹下。

yaml 复制代码
# pnpm-workspace.yaml
packages:
  - 'packages/**'
  - 'apps/**'

现在,每个文件夹都是一个 pnpm 的本地包,互相独立,可以满足各自 lint,甚至独立发版。

tsconfig.base.json 文件内容如下。

json 复制代码
{
    // 其他配置自行补充
    "compilerOptions": {
        "baseUrl": ".",
        "rootDir": "./",
        "paths": {
          "~/*": ["packages/*"]
        }
    }
}

公共代码

在此之前或许你应该去学习一点 pnpm monorepo 相关的内容。

我们在每个 packages 的文件夹里创建 package.json 文件,添加一个 scripts 名为 lint:ts,内容为 tsc --noEmit && eslint --ext .ts,.tsx。

把需要的第三方库添加为 devDependencies、peerDependencies,例如 @tarojs/components、@tarojs/taro、@types/react、react,版本号使用 * 代替,不做任何限制。

我们在每个 packages 的文件夹里创建 tsconfig.json 文件。

json 复制代码
{
  "extends": "../../tsconfig.base",
}

现在编辑器、lint 在 packages 目录下可以识别 '~/utils' 这样的别名路径,满足像项目代码一样使用,不需要打包,如果以后想独立发版只需要把别名路径改为包名就好。

代码复用

在 apps 文件夹里初始化各个小程序,添加一个 scripts 名为 lint:ts,内容为 tsc --noEmit && eslint --ext .ts,.tsx。

tsconfig.json里面补充 paths,添加 ~/* 字段内容为 ["../../packages/*"],在 apps/app1/config/index.js 文件中找到 alias 字段添加构建工具自定义别名,添加 ~ 字段内容为 path.resolve(__dirname, '../../../packages')。项目代码就可以直接 import { xxx } from '~/utils',类似于 @/xxx 这样的别名,编辑器可以快速打开文件,构建工具也能找到文件。

本身项目使用 scss 管理样式文件,在 config.js 文件中的 sass 的 importer 字段添加别名配置。

js 复制代码
const config = {
  sass: {
    importer: (url) => {
      // ~ 开头的路径补全路径
      const reg = /^~\/(.*)/;
      return {
        file: reg.test(url)
          ? path.resolve(__dirname, "../../../packages", url.match(reg)[1])
          : url,
      };
    },
  },
  alias: {
    "@": path.resolve(__dirname, "../src"),
    "~": path.resolve(__dirname, "../../../packages"),
  },
};

一个简单的示例

tsx 复制代码
import type { ViewProps } from '@tarojs/components'
import { View, Text } from '@tarojs/components'
import { navigateTo } from '@tarojs/taro'

import { MansionOutline } from '@fruits-chain/icons-taro'
import { memo } from 'react'
import { gray7 } from '~/nut-extend/theme/color'
import { joinClassNames, getFirstImageUrl } from '~/utils'
import type { ExcludeUndefined } from '~/utils/types'

import Image from '@/components/image'
import Price from '@/components/price'
import type { CommodityOnlinePageQuery } from '@/graphql/operations/goods/__generated__/list.generated'

import './index.scss'

export type ListItem = ExcludeUndefined<
  ExcludeUndefined<CommodityOnlinePageQuery['commodityOnlinePage']>['records']
>[0]

export interface CardGoodsProps extends ViewProps {
  /**
   * 图片与文案的方向
   * @default 'vertical'
   */
  direction?: 'vertical' | 'horizontal'

  /**
   * 横向布局图片大小
   * @default 'm'
   */
  coverSize?: 's' | 'm'

  /**
   * 图片圆角
   * @default false
   */
  coverRound?: boolean

  data: ListItem
}

const CardGoods: React.FC<CardGoodsProps> = ({
  direction,
  coverSize = 'm',
  coverRound = false,
  data,
}) => {
  return (
    <View />
  )
}

export default memo(CardGoods)

其他

在根目录 package.json 添加 scripts,字段名为 lint:all 内容为 pnpm -r lint:ts,根目录运行 npm run lint:all 即可实现全代码 lint。

多个项目的 Taro 版本一定要统一,如果出现多个版本 Taro 可能出现控制台报错说某个模板不存在(不同版本的编译工具不通用)。在 package.json 添加 scripts,字段名为 clean 内容为 npx rimraf {packages,apps}/**/node_modules && npx rimraf ./node_modules,根目录运行 npm run clean,手动删除 pnpm-lock.yaml,重新使用 pnpm 安装依赖。

Taro 版本检测范围包含 apps、packages 目录下所有有关 @tarojs 的包,packages 使用 * 作为版本,apps 里锁定某个版本,这样整个目录只会存在一个版本。

相关推荐
计算机毕设定制辅导-无忧学长6 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
拖孩1 天前
流量主门槛从 500 降到 100,我的小程序上线 18 小时够到了线
前端·后端·微信小程序
云-缩影1 天前
做微信小程序,自建服务器还是用云托管:技术选型对比
服务器·微信小程序·小程序
极限挑战哥2 天前
共享电单车联营平台分账架构实践:二清合规与自动分账零人工方案
java·大数据·人工智能·微信小程序·架构·微信公众平台
云-缩影2 天前
拆解微信小程序后端架构:ECS、RDS、SSL 和 VPC 怎么配合
微信小程序·架构·ssl
拖孩2 天前
AI 帮我写的小程序上线 44 天:36 个人进了收银台,8 个人付钱
前端·后端·微信小程序
sinat_267611913 天前
用 AI 开发微信小程序(八):一个极简收纳工具的开发复盘
微信小程序·小程序
李纲明3 天前
微信小程序真机预览白屏或 request 失败:先核 request 合法域名与 TLS,再查体验版与本地缓存
缓存·微信小程序·小程序·小程序开发·devdiy·真机预览·合法域名
计算机毕设定制辅导-无忧学长4 天前
《基于物品协同过滤的闲置书换购微信小程序设计开发》
java·vue.js·spring boot·微信小程序·uniapp·闲置书换购微信小程序·物品协同过滤算法