10分钟带你从0到1搭建monorepo 工程化项目(一)

简介: 前言大家好,我是Fly哥, 之前写博客的仓库,还是用的原生的html 和js 也没有引入 ts , 和一些工程化的东西, 所以自己重新搭建了一套前端项目架构 基于 lerna + yarn 的 monrepo的仓库, 主要是后面会学习输出的一些东西, 整个架子先搭建起来。2d 和 3d 公共 util 的封装个人 npm 包的发布 (rollup)2d react 项目 搭建(vite)3d react 项目 搭建 (webpack)搭建一套基于webpack 5 的cli每个项目都有一些特定的依赖, 但是也会有一些相同的依赖。比如eslint、 babel 的一些基础配置,

前言



大家好,我是Fly哥, 之前写博客的仓库,还是用的原生的html 和js 也没有引入 ts , 和一些工程化的东西, 所以自己重新搭建了一套前端项目架构 基于 lerna +  yarn 的 monrepo的仓库, 主要是后面会学习输出的一些东西, 整个架子先搭建起来。


  1. 2d 和 3d 公共 util 的封装


  1. 个人 npm 包的发布 (rollup)


  1. 2d react 项目 搭建(vite)


  1. 3d react 项目 搭建 (webpack)


  1. 搭建一套基于webpack 5 的cli


每个项目都有一些特定的依赖, 但是也会有一些相同的依赖。比如eslint、 babel 的一些基础配置,或者一些通用的脚本文件。读完本篇文章你可以学到 从0 到 1 搭建


monorepo 前端工程化项目  如下图所示:


image.gif项目架构图


为什么使用monorepo


monorepo 是一种将多个项目代码存储在一个仓库里的软件开发策略("mono" 来源于希腊语 μόνος 意味单个的,而 "repo",显而易见地,是 repository 的缩写)。将不同的项目的代码放在同一个代码仓库中,这种「把鸡蛋放在同一个篮子里」的做法可能乍看之下有些奇怪,但实际上,这种代码管理方式有很多好处,无论是世界一流的互联网企业 Google,Facebook,还是社区知名的开源项目团队 Babe、都使用了 monorepo 策略管理他们的代码。这是Taro 的官方源码库:


image.giftaro


至于他的优点如下:


  1. 代码重用将变得非常容易:由于所有的项目代码都集中于一个代码仓库,我们将很容易抽离出各个项目共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;


  1. 赖管理将变得非常简单, 可以轻松的做到版本依赖管理 和版本号自动升级


  1. 发布npm 包 也很特别简单, 提取公共方法 直接公共包,可以快速发布到npm 上


  1. 还有一个 最大的 优点  就是 避免重复安装包, 减少的磁盘空间, 降低了构建时间

这两项好处全部都可以由一个成熟的包管理工具来完成,对前端开发而言,即是 yarn(1.0 以上)或 npm(7.0 以上)通过名为 workspaces 的特性实现的(⚠️ 注意:支持 workspaces 特性的 npm 目前依旧不是 LTS 版本)。


yarn



这里的话 我们全局安装 yarn


npm install yarn -g


然后新建一个文件夹 进入到目录中执行


yarn init


会在项目的根目录生成 package.json


这时候我们在项目根目录


新建packages 目录


在package.json 新增下面字段 workspace


{
  "name": "yarn-test",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "main": "index.js",
  "license": "MIT"
}


表示工作区是packages 下的所有子目录,


private: true 表示项目的根目录 不会被发布出去


假设项目中有foo和bar两个package:


mono-demo/
|--package.json
|--packages/
|  |--foo/
|  |  |--package.json
|  |--bar/
|  |  |--package.json


yarn workspace <workspace_name>


在指定的package中运行指定的命令。


# 在foo中添加react,react-dom作为devDependencies
yarn workspace foo add react react-dom --dev
# 移除bar中的lodash依赖
yarn workspace bar remove lodash
# 运行bar中package.json的 scripts.test 命令
yarn workspace bar run test


yarn workspaces run


在所有package中运行指定的命令,若某个package中没有对应的命令则会报错。


# 运行所有package(foo、bar)中package.json的 scripts.build 命令
yarn workspaces run build


yarn workspaces info [--json]


查看项目中的workspace依赖树。


例如我的bar依赖了foo,如下:


// bar/package.json
{
  "name": "bar",
  "version": "1.0.0",
  "dependencies": {
    "foo": "^1.0.0"
  }
}


在项目中的依赖结构是这样的(假设foo/package.json的版本匹配bar的依赖版本,否则会另外安装一个匹配的foo):


/package.json
/yarn.lock
/node_modules
/node_modules/foo -> /packages/foo
/packages/foo/package.json
/packages/bar/package.json


那么运行yarn workspaces info会得到如下输出:


yarn workspaces 
{
  "bar": {
    "location": "packages/bar",
    "workspaceDependencies": [
      "foo"
    ],
    "mismatchedWorkspaceDependencies": []
  },
  "foo": {
    "location": "packages/foo",
    "workspaceDependencies": [],
    "mismatchedWorkspaceDependencies": []
  }
}


比如我的一些依赖是所有package 通用的 比如 eslint、babel... 我们就使用下面的这个命令  加一个 -W 就可以了


yarn <add|remove>-W


  • -W: --ignore-workspace-root-check ,允许依赖被安装在workspace的根目录

管理根目录的依赖。


# 安装eslint作为根目录的devDependencies
yarn add eslint -D -W


lerna


**Lerna**是社区主流的monorepo管理工具之一,集成了依赖管理、版本发布管理等功能。


使用Learn管理的项目的目录结构和yarn workspace类似。


我们根目录安装


yarn add lerna -D -W


然后执行


npx lerna init


然后项目中就会生成lerna.json


我们进行下面配置


{
  "packages": ["packages/*"],
  "command": {
    "run": {
      "npmClient": "yarn"
    },
    "publish": {
      "ignoreChanges": ["ignored-file", "*.md"],
      "message": "chore(release): publish",
      "registry": "https://npm.pkg.github.com"
    }
  },
  "version": "independent",
  "useWorkspaces": true,
  "npmClient": "yarn"
}


这里 同样使用 workspace, 指定项目 使用yarn 进行包管理


这里有一个很重要的字段  "version": "independent",


这是表示使用 独立模式 Lerna 项目允许维护人员彼此独立地增加包版本。每次发布时,您都会收到有关已更改的每个包的提示,以指定它是补丁、次要、主要还是自定义更改。独立模式允许您更具体地更新每个包的版本,并且对一组组件有意义。这里搭配   semantic-release 这个npm包 感兴趣的可以去了解下。


下面我介绍一些lerna 的一些命令: 大家可以去github lerna 看的更多


lerna bootstrap:等同于 lerna link + yarn install,用于创建符合链接并安装依赖包;


lerna run:会像执行一个 for 循环一样,在所有子项目中执行 npm script 脚本,并且,它会非常智能的识别依赖关系,并从根依赖开始执行命令;


lerna exec:像 lerna run 一样,会按照依赖顺序执行命令,不同的是,它可以执行任何命令,例如 shell 脚本;


lerna publish:发布代码有变动的 package,因此首先您需要在使用 Lerna 前使用 git commit 命令提交代码,好让 Lerna 有一个 baseline;


lerna add:将本地或远程的包作为依赖添加至当前的 monorepo 仓库中,该命令让 Lerna 可以识别并追踪包之间的依赖关系,因此非常重要


tsconfig




作为一个ts 项目, 在项目根目录安装 ts


yarn add typesript -D -W


首先在项目中生成 tsconfig.json


npx tsc --init


然后在项目根目录生成tsconfig.json  这里 划重点 我们把基础的 tsconfig.json 放在这里 ,然后 新建一个项目 生成tsconfig.json  都是继承根目录的 tsconfig.json 类似于这样


{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "target": "es2018",
    "module": "ESNext",
    "outDir": "./dist"
  },
  "include": ["./src/**/*.ts"] // * 匹配0或者多个字符 (不包括目录分割符) **/递归匹配任意子目录
}


这是一个子项目的tsconfig.json


至于tsconfig.json 文件 详细配置,你可以自己百度。


Babel



Babel 配置文件合并的方式与 TypeScript 如出一辙,甚至更加简单,我们只需在子项目中的 .babelrc 文件中这样声明即可:


{
  "extends": "../.babelrc"
}


scripts



我们在全局新建一个scripts 文件夹 可能 是 shell 文件 也有可能是ts 文件。我们都知道 ts 文件 是不能直接执行, 都是先编译成js 然后再执行, 这也太麻烦了吧。好在社区已经提供的 ts-node 可以允许你直接运行ts 文件  这东西实现的原理 大概就是

我们可以使用 ts-node + 某个 ts 文件,来直接执行这个 ts 文件,它的原理就是修改了 require hook,也就是 Module._extensions['.ts'] 来实现的。


在 require hook 里面做 ts 的编译,然后后面直接执行编译后的 js,这样就能达到直接执行 ts 文件的效果。


所以我们重写 Module._extensions['.ts'] 方法,在里面读取文件内容,然后调用 ts.transpileModule 来把 ts 转成 js,之后调用 Module._compile 来处理编译后的 js。


yarn add ts-node -D -W


新建一个 test.ts 文件


const foo = {
  baz: {
    a: 1,
  },
}
console.log(foo)


然后 在package.json


编写如下脚本:


"test": "ts-node ./scripts/test.ts "


然后执行 yarn test

image.png

res


其实这个ts-node 有一个坑 就是 文件引用问题 当你的 ts 脚本文件 引用了当前项目的其他包  可能就会出现 执行报错


我们在package 新建一个util  然后 新建了一个 index.ts 文件


const add = (a: number, b: number) => a + b
export default add


然后我在根目录的 tsconfig.json 进行别名配置


"baseUrl": "./packages", // 根路径 路径映射,
  "paths": {
    "@fly/util": ["./util/index.ts"]
  }


我们在 脚本 引入 这个加法 函数


import add from '@fly/util'
console.error(add(2, 3))


然后继续执行


会报下面这个错误

image.png

image.giferror

大家注意看 我画框的地方,大体就是 由于 我们 ts-node 在执行的过程中, 由于tsconfig.json 的   "module": "CommonJS", 会将

import add from '@fly/util'  编译成


const add = require("@fly/util")


由于我们这个是ts 别名配置 当然找不到 这个模块, 如果你是webpack 项目的话 ,可以 配置别名 解决, 会进行路径替换,


但是我们在写脚手架的时候,不可能用到webpack 就是 node 环境, 这里我怎么去解决呢


社区也提供了解决方案

image.png

image.gifts-config

大概意思就是:使用它来加载位置在 tsconfig.json 的路径部分中指定的模块。支持在运行时加载和通过 API 加载。


Typescript 默认模仿模块的 Node.js 运行时解析策略。但它也允许使用路径映射,允许指定任意模块路径(不以“/”或“.”开头)并将其映射到文件系统中的物理路径。


typescript 编译器可以从 tsconfig 解析这些路径,因此它可以编译。但是,如果您尝试使用 node(或 ts-node)执行编译后的文件,它只会在 node_modules 文件夹中一直查找到文件系统的根目录,因此不会找到 tsconfig 中路径指定的模块 这句话 很重要, 所以我们刚才的报错,就是这个原因 而这个库 就是帮我们解决的。


yarn add tsconfig-paths -D -W


如何使用呢


ts-node --project customLocation/tsconfig.json -r tsconfig-paths/register "test/**/*.ts"


这里的话最好ts config.json 的 common js 因为我们是在node 环境


所以我在项目新建一个tsconfigs 用来 存放不同的 ts配置 同样继承 根目录


{
  "extends": "../tsconfig",
  "compilerOptions": {
    "module": "CommonJS"
  }
}


执行命令


ts-node --project ./tsconfigs/cmj.json -r tsconfig-paths/register  ./scripts/test.ts


image.png

image.gifres

执行成功 很舒服哇。看到这里觉得有帮助的话 可以帮忙点个赞吧

但是这里还会有个问题 如图:

image.png

image.gifeslint


这其实是eslint import 的配置 ,如果你配置了的话  安装下面这个npm


yarn  add eslint-import-resolver-typescript -D -W


光从名字就可以看出和这个问题极为相关。从项目 README 可以发现,这个 lib 可以在 TypeScript 项目使 eslint-plugin-import 找到正确的 .ts 和 .tsx 文件,也能识别 tsconfig.json 的 path 配置(路径别名 2),甚至 monorepo 这类一个 git 仓库多个项目的工程也支持。


用法也很简单在 eslint 的"import/resolver":指向当前配置了 path 的 tsconfig 的路径即可,eslint 就会自动识别就不会报错了。


{
  "plugins": ["import"],
  "rules": {
    "import/no-unresolved": "error"
  },
  "settings": {
    "import/parsers": {
      // 使用 TypeScript parser
      "@typescript-eslint/parser": [".ts", ".tsx"]
    },
    "import/resolver": {
      // 默认使用根目录 tsconfig.json
      "typescript": {
        // 从 <roo/>@types 读取类型定义
        "alwaysTryTypes": true,
      },
      // 使用指定路径 tsconfig.json, <root>/path/to/folder/tsconfig.json
      "typescript": {
        "directory": "./path/to/folder"
      },
      // monorepos 这类多 tsconfig.json
      // 可以用 glob 这类匹配模式
      "typescript": {
        "directory": "./packages/*/tsconfig.json"
      },
      // 或者数组
      "typescript": {
        "directory": [
          "./packages/module-a/tsconfig.json",
          "./packages/module-b/tsconfig.json"
        ]
      },
      // 也可以混合使用
      "typescript": {
        "directory": [
          "./packages/*/tsconfig.json",
          "./other-packages/*/tsconfig.json"
        ]
      }
    }
  }
}


上面就是官方的用法, 下面我们就开始 eslint 详细用法吧



相关文章
|
7月前
|
资源调度 前端开发 测试技术
前端工程化实践:从零搭建现代化项目构建流程
【4月更文挑战第6天】本文介绍了前端工程化的概念和重要性,包括模块化、自动化、规范化和CI/CD。接着,讨论了选择合适的工具链,如包管理器、构建工具和测试框架。然后,详细阐述了如何从零开始搭建一个基于React的现代化项目构建流程,涉及初始化、代码规范、测试、CSS处理、代码分割和CI/CD配置。最后,提到了持续优化与迭代的方向,如性能优化、类型检查和微前端。通过这样的实践,开发者可以提升开发效率和代码质量,为项目长远发展奠定基础。
313 0
|
7月前
|
缓存 前端开发 JavaScript
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
|
JavaScript 前端开发 关系型数据库
和chatgpt学架构01-搭建项目脚手架
和chatgpt学架构01-搭建项目脚手架
|
7月前
|
自然语言处理 前端开发 测试技术
前端工程化最佳实践:项目结构、代码规范和文档管理
前端工程化最佳实践:项目结构、代码规范和文档管理
|
2月前
|
Rust 前端开发 iOS开发
Tauri 开发实践— Tauri 工程搭建
本文首发于微信公众号“前端徐徐”,介绍了在 macOS 环境下使用 Rust 和 Tauri 构建跨平台桌面应用的过程。首先需安装 Rust 及系统依赖,参考链接:[Rust 入门](https://www.rust-lang.org/zh-CN/learn/get-started) 和 [Tauri 前置条件](https://tauri.app/zh-cn/v1/guides/getting-started/prerequisites)。
40 0
Tauri 开发实践— Tauri 工程搭建
|
6月前
|
资源调度 前端开发 JavaScript
前端工程化实践:Monorepo与Lerna管理
**前端工程化中,Monorepo和Lerna用于大型项目管理。Monorepo集纳所有项目,便于代码共享、版本控制和CI/CD。Lerna是Monorepo工具,管理多npm包,支持独立或共享版本。安装Lerna用`npm install --save-dev lerna`,初始化后可创建、管理包,通过`lerna bootstrap`、`lerna add`、`lerna publish`等命令协同工作。Lerna配置可在`lerna.json`,与CI/CD工具集成实现自动化。
75 0
|
前端开发 JavaScript API
前端工程化和构建工具的选择
前端工程化和构建工具的选择
143 1
|
存储 缓存 资源调度
Monorepo,大型前端项目管理模式实践
阅读本文您将了解到:什么是 monorepo、为什么要 monorepo、如何实践 monorepo。
6911 50
Monorepo,大型前端项目管理模式实践
|
存储 资源调度 JavaScript
基于 Yeoman 脚手架技术构建前端项目的实践
基于 Yeoman 脚手架技术构建前端项目的实践
177 0
|
前端开发 JavaScript 开发者
前端工程化构建工具之Gulp
在前端工程化构建工具中,Gulp是一个非常流行的开源工具。
86 0