1. 为什么要将项目拆分成多个 package ?

  1. 当项目越来越大,拆分成多个 package 有利于降低软件的复杂度
  2. 作为工具类、或者组件类项目,有助于使用者按照需要引用合适的 package (按需加载,减少打包体积)

2. 多 package 的代码架构怎么维护? 多仓库还是单仓库?

首先,既然分了多个 package 简单的方式就是每个 package 由各自的 代码仓库管理,这种方式在 package 数量较少时比较方便

  1. 但是一旦 package 数量变多,每个 package 都有各自的 node_modules 安装时需要依次安装,不但耗时而且安装的内容容易重复且占用过多空间;
  2. 由于拆到了多个代码仓库,issue 会比较分散,但 package 之间又有关联关系,这样就会导致问题难以统一处理
  3. 公共模块 package 一旦更新,所有其他 package 都要手动更新一遍版本

单仓库,开发时多个 package 的代码放到一个文件夹下,通过 npm 将这些文件夹在的模块安装成 local module 来使用;这样 子 package 就会作为一个模块引入到了父项目中,开发时就如同在一个项目开发一样

  1. 这样做与 多仓库相比有一个缺陷:多仓库可以依据不同仓库进行不同 package 的开发及发布权限控制,但是单仓库的话只能整体做一个权限控制

Monorepo 是针对单仓库的流行解决方案,这套方案旨在兼顾模块化的优势前提下提供简单的源代码管理方法,monorepo 的主要两个实现 Yarn workspaces 和 lerna

3. NPM 模块中的 scope package

我们使用第三方包时经常能看到类似如下命名的包,这些包都会被下载到 node_modules下 名为 @babel 的目录下边,这种使用方式称为 scope package 它是一种把相关模块组织到一起的方法

"@babel/core": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@babel/preset-flow": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
 

scope package 的安装与普通的 package 安装一样,使用时需要带上 scope 即 import xxx from '@babel/core';

scope package 的发布也与普通模块类似,它们的主要区别是

  • 包的 package.json 中 name 字段为 scope 的格式 "name": "@mjz-scope/package-name"
  • 首次发布时追加 npm publish –access public (因为 scope package 默认时受限制的,如果没有私有的 npm 账户会发布不出去,所以这里将其设置为 public 即可)

scope package 与我们的 “源代码管理方案” (即:单仓库还是多仓库)并们有太大关系,因为无论怎样组织源代码,最后的发布的包还是通过 npm publish 发布到 registory 中,但是如果我们使用了 scope package 的方案,那么我们的“源代码” 与 “npm package” 在组织结构上就会更一致,使用相关性会更强一些。

哪些项目在用?

  • lerna : Babel 、material-ui、create-react-app
  • yarn workspace: React
  • 类似实现方案: Vue、Element-ui

Lerna 项目从 0-1 搭建

到底是使用 Yarn workspace 还是使用 lerna, 答案是两个一起使用,主要原因是两者各有优势

  1. Yarn workspace 在依赖管理上做的更好,可以智能的优化依赖关心的安装
  2. Lerna 在版控制方面更好

因此最终的方案是,lerna 只负责创建 package 以及 package 的版本控制,开发过程中涉及到 安装依赖或者删除依赖等都使用 yarn 的命令(尽量不要混用)

包管理结果:

  • 依赖版本的 lock 文件只存在于更目录的 yarn.lock 中,子项目中不会有
  • 所有依赖都优先安装到根目录的 node_modules 下,除非不同的子项目中需要安装不同的版本
  • 如果不同依赖版本各自有多个 package 使用,那么最多引用的会放到根目录 node_modules 下
  • 清空 所有 node_modules 后,重新 yarn install 时就会重新计算依赖的安装位置,达到最优解
  • lerna create [packageName] 创建一个 package
  • yarn workspace [packageName] [add|remove] lodash 给某个 package 中安装或删除依赖
  • yarn workspaces run [add|remove] xxx 给所有 package 中安装或删除依赖,会安装到每个子 package 的 package.json
  • yarn add -W -D typescript 给 root 安装依赖,会安装到 根目录 package.json
  • lerna clean && rm -rf ./node_modules 清除安装的依赖

最终的目录结构

├── README.md ├── lerna.json ├── node_modules │ ├── @mjz-test │ ├── lodash ├── package.json ├── packages │ ├── lerna-package-1 │ ├── lerna-package-2 │ ├── lerna-package-3 │ └── lerna-package-4 └── yarn.lock

0. 确保项目链接到 Git

因为 lerna 会根据 git 动态发布,所以先要确保项目中有 git

git init lerna-test && cd lerna-test
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:mjzhang1993/lerna-test.git
git push -u origin main

1. 初始化项目,转变项目为一个 Lerna 仓库

# 全局安装 lerna
npm install lerna -g
# 全局安装 yarn 
brew install yarn # 如果使用 nvm 等 node 版本管理工具则需要追加 --without-node 
# 初始化项目,将仓库转变为 lerna 仓库
yarn init
lerna init
 

可以看到,项目中新增了 lerna.json package.json 以及 packages 空文件夹;
更改 package.json 追加 private: true 这一项,这表示当前这个根目录所在的区域是不应该被 npm 发布的; 追加 workspaces: [] 开启 yarn 的 workspaces 模式

"name": "lerna-test", "version": "1.0.0", "private": true, "workspaces": [ // 同时这里也要设置 workspaces (yarn) "packages/*" "devDependencies": { "lerna": "^3.22.1"

更改 lerna.json

"packages": [ "packages/*" "version": "0.0.0", "npmClient": "yarn", "useWorkspaces": true,

2. packages 目录下创建三个 package

为了测试及演示,创建以下三个 package

在创建之前我们要先确定,创建的三个 package 要发布到那个 registry(镜像仓库) 中

# 如果目标的 registry 就是 npm 的官方镜像则不需要设置
yarn config set registry https://registry.npm.xxx.com
# 登录到 镜像仓库
yarn login # 或者执行 npm adduser --registry=https://registry.npm.xxx.com
1. 创建 lerna-package-1
# 创建一个带 scope 的 npm package
lerna create @mjz-test/lerna-package-1
 

命令执行后可以在 packages 目录下看到一个 名为 lerna-package-1 的 目录就创建好了,这个目录对应的 package.name 则为 @mjz-test/lerna-package-1,接下来我们给这个模块添加依赖

yarn workspace @mjz-test/lerna-package-1 add lodash
 

执行后可以看到,lodash 被安装到了 根目录的 /node_modules 下边,但是 lodash 的安装记录被记录到了 /packages/lerna-package-1/package.json

2. 同样的方式创建 lerna-package-2
lerna create @mjz-test/lerna-package-2
 

然后我们给 lerna-package-2 添加 lodash 依赖,

yarn workspace @mjz-test/lerna-package-2 add lodash
 

结果不会有新的 lodash 被安装,但是 /packages/lerna-package-2/package.json 中同样会被记录 lodash 的安装,也就是说,多个package 使用同一个 依赖不会被重复下载,但是会被各自package 记录

下面我们给 lerna-package-2 添加 lerna-package-1 作为依赖

# 注意:被安装的内部依赖一定要加版本号,否则,yarn 会到 registry 获取对应的包
yarn workspace @mjz-test/lerna-package-2 add @mjz-test/lerna-package-1@0.0.0
 

以上操作后 /packages/lerna-package-2/package.json 中增加了对 @mjz-test/lerna-package-1 的依赖,而在 root 层的 node_modules 中 @mjz-test/lerna-package-1 是通过软连接的方式连接到了 packages/ 下对应的包中的

2. 同样的方式创建 lerna-package-3
lerna create @mjz-test/lerna-package-3
 

然后我们给 lerna-package-2 添加 lodash 另外一个版本的依赖

yarn workspace @mjz-test/lerna-package-3 add lodash@3.10.1
 

执行后 lodash@3.10.1 由于只有 @mjz-test/lerna-package-3 使用,其他两个 package 使用的都是 lodash@4 ,因此它被安装到了 /packages/lerna-package-2/node_modules/ 下边

3. 项目构建

由于当前三个 package 之间有引用关系,例如 @mjz-test/lerna-package-2 引用 @mjz-test/lerna-package-1, 这个时候如果先构建了 @mjz-test/lerna-package-2 就会出错,lerna 提供了一个可以按照依赖顺序打包的能力,我们可以尝试在 @mjz-test/lerna-package-1 中把 @mjz-test/lerna-package-3 作为依赖(为了更清楚的展示)

yarn workspace @mjz-test/lerna-package-1 add @mjz-test/lerna-package-1@0.0.0
 

然后给每个 package 增减一个 build 的 script, 每个脚本都只要返回一个字符串即可

"scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1",
    "build": "node ./lib/lerna-package-3.js"
 

最后执行顺序构建的命令

lerna run --stream --sort build
# 以下是构建结果,可以看到顺序依次是 package-3 -> package-1 -> package-2 与依赖的顺序一致
@mjz-test/lerna-package-3: yarn run v1.15.2
@mjz-test/lerna-package-3: $ node ./lib/lerna-package-3.js #
@mjz-test/lerna-package-3: this is running in package 3
@mjz-test/lerna-package-3: Done in 0.12s.
@mjz-test/lerna-package-1: yarn run v1.15.2
@mjz-test/lerna-package-1: $ node ./lib/lerna-package-1.js
@mjz-test/lerna-package-1: this is running in package 1
@mjz-test/lerna-package-1: Done in 0.14s.
@mjz-test/lerna-package-2: yarn run v1.15.2
@mjz-test/lerna-package-2: $ node ./lib/lerna-package-2.js
@mjz-test/lerna-package-2: this is running in package 2
@mjz-test/lerna-package-2: Done in 0.14s.

4. 版本发布

1. 首先,修改当前的配置
  1. 首先更改 lerna.json 配置文件,配置要发布到那个地址
"packages": [ "packages/*" "version": "0.0.1", "npmClient": "yarn", "useWorkspaces": true, "command": { "publish": { "ignoreChanges": ["*.md"], "registry": "https://registry.npmjs.org/"
  1. 如果我们使用了类似 @mjz-test/lerna-package-3 这种 scope package 还需要手动给, package 所在目录的 package.json 设置如下信息,(原本的 lerna create [packageName ] --access=public 并没有效果,所以要手动设置一次)
    "access": "public"
 
  1. 如果全局的 registry 与我们要发布的 registry 不是一个,那么我们还需要给每一个 package 设置 registry:
    "access": "public"
    "registry": "https://registry.npmjs.org/" 
 

关于 registry 如果我们项目内设置了 .npmrc 则会优先使用.npmrc 内的 registry 作为默认创建,如果没有则会使用全局的 npm config get registry 作为默认,如果这两个都不是我们想要的发布地址,那就只能手动设置了

2. 执行 lerna publish
git commit -am "second commit"
lerna publish
 

结果报错, 原因是我们用了 scope package 与 普通 package 不同的是,scope package 需要到 npm 官网创建 scope 命名空间,(实际就是进入npm orgization 来创建一个组织,例如:我们设计的包名为 @mjz-test/lerna-package-3 , 那创建的这个组织就叫做 mjz-test), 创建好后再次执行,就不会出现下面的报错了

lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
lerna http fetch PUT 404 https://registry.npmjs.org/@mjz-test%2flerna-package-3 282ms
lerna ERR! E404 Scope not found
3. 假设 更改 | 上一次 publish | 更改 lerna-package-2 后再 publish
  1. 如果当前最大版本是修订版本(0.0.x)那么即使只有一个 package 改动也会导致所有的 packages 都升一级版本即
  • 上一次 publish 后版本
    • @mjz-test/lerna-package-1@0.0.1
    • @mjz-test/lerna-package-2@0.0.1
    • @mjz-test/lerna-package-3@0.0.1
  • 更新 lerna-package-2 后版本
    • @mjz-test/lerna-package-1@0.0.2
    • @mjz-test/lerna-package-2@0.0.2
    • @mjz-test/lerna-package-3@0.0.2
  1. 如果当前最大版本是小版本或者大版本,那么只有改动的这个 package 会升级
  • 上一次 publish 后版本
    • @mjz-test/lerna-package-1@0.1.0
    • @mjz-test/lerna-package-2@0.1.0
    • @mjz-test/lerna-package-3@0.1.0
  • 更新 lerna-package-2 后版本
    • @mjz-test/lerna-package-1@0.1.0
    • @mjz-test/lerna-package-2@0.1.1
    • @mjz-test/lerna-package-3@0.1.0
  1. 另外,不需要担心被内部引用 package 的版本如何处理,实际上 lerna publish 会在执行时会将依赖更新过的 package 的 包中的依赖版本号更新
4. 集中版本号与独立版本号

集中版本号(fixed)是默认的模式,其表现是 所有 package 的版本变更都是依据当前 lerna.json 中的 version 来改动的,即:依照上边的例子,第三次在对 lerna-package-1 进行改动,publish 后 lerna-package-1 的版本号会直接从 v0.1.0 升到 v0.1.2 ,这种版本号升级方式有点不利于语义化

独立版本号(independent)可以解决语义化的问题,即每次升级都会逐个询问需要升级的版本号,其基准为自身的 package.json, 配置独立版本号只需更改 lerna.json 的 version: independent 即可

Lerna 项目搭建后的日常使用

0. 安装lerna 到项目内

为了项目可以多人协同,lerna 安在全局会比较麻烦,所以首先会将 lerna 安装到项目根目录,并将常用的工作流命令存在 scripts 中

# 根目录下安装 lerna
yarn add -D -W lerna
# /package.json 中增加如下 脚本
"scripts": {
    "lerna:create": "lerna create",
    "lerna:build": "lerna run --stream --sort build",
    "lerna:publish": "lerna publish",
    "lerna": "lerna",
    "clean": "lerna clean && rm -rf ./node_modules"

1. 日常使用命令

# clone 下代码后执行安装依赖
yarn install --use-workspaces
# 可能会创建新的 package
yarn lerna:create @mjz-test/lerna-package-4
# 创建后的 package 需要设置一下 package.json 的 publicConfig
# publishConfig": {
#   "access": "public"
#   "registry": "https://registry.npmjs.org/"
# 可能会需要给某个 package 安装依赖
yarn workspace @mjz-test/lerna-package-1 add lodash
yarn workspace @mjz-test/lerna-package-1 remove lodash
# 可能需要给所有 package 安装依赖
yarn workspaces add lodash
yarn workspaces remove lodash
# 可能需要将内部的package 作为依赖安装(注意要加版本号)
yarn workspace @mjz-test/lerna-package-1 add @mjz-test/lerna-package-3@0.0.0
yarn workspace @mjz-test/lerna-package-1 remove @mjz-test/lerna-package-3@0.0.0
# 可能需要将依赖安装到根目录
yarn add -D -W lodash
# 可能会需要单独执行某个 package 中的 script 命令
yarn lerna run dev --scope @mjz-test/lerna-package-1
# 可能需要对 node_modules 做清理
yarn run clean
# 开发完成后执行编译
yarn lerna:build
# commit 更改
git commit -am "xxx commit"
# 提交到代码库与 npm
yarn lerna:publish
# 关于提交到版本库的版本,我们假设当前版本为 0.3.3
yarn lerna:publish major # 0.3.3 => 1.0.0
yarn lerna:publish minor # 0.3.3 => 0.4.0
yarn lerna:publish patch # 0.3.3 => 0.3.4
yarn lerna:publish premajor # 0.3.3 => 1.0.0-alpha.0
yarn lerna:publish preminor # 0.3.3 => 0.4.0-alpha.0
yarn lerna:publish prepatch # 0.3.3 => 0.3.4-alpha.0
yarn lerna:publish 1.2.3 # 0.3.3 => 1.2.3 # 也可以是具体的版本号
yarn lerna:publish # 如果什么都不传,则在没有开启 conventionalCommits 的时候弹出命令行提示选择要升级的版本,如果开启了 conventionalCommits 则会根据 commit 信息的 type 做简单的判断输出一个合适的版本
                    Lerna 多 package 项目管理工具说明Lerna是一个用于管理包含多个软件包(package) 的 JavaScript 源代码管理方案参考Lerna 中文官网Yarn wrokspacelerna+yarn workspace+monorepo项目的最佳实践Lerna 配置详解1. 为什么要将项目拆分成多个 package ?当项目越来越大,拆分成多个 package 有利于降低软件的复杂度作为工具类、或者组件类项目,有助于使用者按照需要引用合适的 packag
将大型代码库分成独的独立版本化的软件包对于代码共享非常有用。 但是,跨许多存储库进行更改很麻烦且难以跟踪,并且跨存储库的测试变得非常复杂。
 为了解决这些(以及许多其他)问题,一些项目会将其代码库组织到多包存储库中(有时称为 )。 像 ,  ,  ,  ,  , ,以及许多其他项目,都在一个存储库中开发了所有软件包。
 Lerna是一种工具,用于优化使用git和npm管理多包存储库的工作流程。
 Lerna还可以减少开发和构建环境中大量软件包副本的时间和空间要求,这通常是将项目分成许多独的NPM软件包的缺点。 有关详细信息
添加独依赖
假设moduleA 自己依赖 jquery,moduleB 自己依赖 zepto
lerna add jquery --scope=@fengyinchao/modulea
lerna add zepto --scope=@fengyinchao/moduleb
注意 scope 的值对应的是 package.json 中的 name 字段
重要:添加packages里其它模块作为自己的依赖
假设moduleA 依赖 moduleB
lerna add @fengyinchao/moduleb --scope=@fengyinchao/modulea
lerna link // 不执行会报错
lerna是GitHub上面开源的一款js代码库管理软件, 用来对一系列相互耦合比较大、又相互独立的js git库进行管理。解决各个库之间修改混乱、难以跟踪的问题。lerna可以优化这种情形下的工作流。
对于一些功能比较全的库,我们往往会把各个小功能拆分成独立的npm库,他们直接有比较强的依赖关系。比如:Babel、React等开源代码都是按照这种方式进行处理的。
代码库...
Lerna是啥,干嘛用的,有什么好处,这里我引用官方的一段说明:
将大型代码库拆分成独立版本的包对于代码共享非常有用。然而,跨多个存储库进行更改是混乱的,很难跟踪,跨存储库的测试变得非常复杂。
为了解决这些(以及许多其他)问题,一些项目将把它们的代码库组织到多包存储库(有时称为monorepos)中。像Babel、React、Angular、Ember、Meteor、Jest等项目,以及许...
对于维护过多个package的同学来说,都会遇到一个选择:这些package是放在一个仓库里维护还是放在多个仓库独维护,数量较少的时候,多个仓库维护不会有太大问题,但是当package数量逐渐增多时,一些问题逐渐暴露出来:
package之间相互依赖,开发人员需要在本地手动执行npm link,维护版本号的更替;
issue难以统一追踪,管理,因为其分散在独立的repo里;
每一个pa...
				
在开发若干个有相互依赖关系的库的时候,通常都会采用 symlink 的方式互相引用,比较典型的一种场景就是使用 lerna 开发多个 packagelerna 简介 lerna 是用于管理拥有多个 package 的 JavaScript 项目,其典型目录结构为 lerna-repo/ packages/ package...
最近在做一些工程化相关的内容,有使用到fs.symlink(target,path), 查看Node.js文档发现讲的一般!所以这里详细整理下,并且记得之前在Linux下直接使用命令创建软链通过ls -s source_file target_file在想这两个参数位置有些怎么不一致呢? 本文是我上一篇工程化系列文章中的一个小插曲,上篇文章现代前端工程化-彻底搞懂基于 Monorepo 的 lerna 模块(从原理到实战) 有提到软链接,那篇没有对软硬链接... 将大型代码库分成独的独立版本化的软件包对于代码共享非常有用。但是,跨许多存储库进行更改很麻烦且难以跟踪,并且跨存储库的测试变得非常复杂。 为了解决这些(以及许多其他)问题,一些项目会将其代码库组织到多包存储库中(有时称为monorepos)。像Babel,React,Angular, Ember,Meteor,Jest等项目,以及许多其他项目,都在一个存储库中开发了所有软件包。 Lerna是一种工具,可以优化使用git和npm管理多包存储库的工作流程。 Lerna还可以减少开发和构建环境中大量软件包
组件库的文档需要清晰地展示每个组件的功能、用法和参数等信息,让使用者能够快速了解和掌握每个组件的使用方法。 在编写组件库文档时,可以使用一些第三方库来辅助完成,比如使用 React Styleguidist 来生成组件文档页面。React Styleguidist 可以自动生成组件的示例代码和文档说明,方便用户理解和使用组件。 此外,还可以使用 Lerna管理组件库的代码仓库Lerna 是一个工具,可以用来管理具有多个包的 JavaScript 项目。Lerna 可以帮助我们在同一个仓库管理多个组件,方便维护和发布组件。 关于 Lerna 的官方文档地址,可以在 GitHub 上找到:https://github.com/lerna/lerna#readme。
我另一种使用起来更直观的方式,也许可以为你提供一种其其他构建组件的的思路: import { Button, Menu } from "antd"; import { observer } from "mobx-react"; import React, { useEffect, useState } from "react"; import useStore from "../../hooks"; import TestStore from "../../store/mobx/testStore"; const LeftHeader: React.FC<any> = (props) => { const store:TestStore = useStore("testStore"); const {total,addTotal}=store const setTotal=()=>{ addTotal(1) useEffect(() => { console.log("重新渲染了") return ( {total} <Button type="primary" onClick={setTotal}>Click1</Button> export default observer(LeftHeader);
使用 vite 代替 webpack 搭建 react 前端开发环境 mjzhang1993: 默认就是加了 hash 的,如果想改,需要配置 [code=javascript] {rollupOptions: { output: { // 重点在这里哦 // entryFileNames: `assets/[name].${timestamp}.js`, // chunkFileNames: `assets/[name].${timestamp}.js`, // assetFileNames: `assets/[name].${timestamp}.[ext]` entryFileNames: `assets/[name].js`, chunkFileNames: `assets/[name].js`, assetFileNames: `assets/[name].[ext]`, [/code] 使用 vite 代替 webpack 搭建 react 前端开发环境 尼古拉·瑞: 请问如何在打包后的js的后缀上加上时间戳呢,解决浏览器缓存问题 使用 vite 代替 webpack 搭建 react 前端开发环境 weixin_43952075: 官网前言说打包为什么用的rollup不用esbuild,但是 build.target 和 build.minify 都是用的 esbuild。读他官网我很懵 使用 vite 代替 webpack 搭建 react 前端开发环境 mjzhang1993: 这俩没啥关系,@vitejs/plugin-react 这个插件在解析阶段,用来帮助 vite 处理 react 的 jsx 语法,可以理解为 将 react 代码解析成 vite 可以以处理的普通 js ,vite 的 build.target 用来定义打包后的结果是 ESModule 还是其他; 总结:@vitejs/plugin-react 用来解析 react ,build.target 定义代码打包后结果格式(可以看 build.target 文档 https://cn.vitejs.dev/config/build-options.html#build-target)