现代前端工程化-彻底搞懂基于 Monorepo 的 lerna 模块(从原理到实战)

发表于 3年以前  | 总阅读数:991 次

本文你能学到什么?

看完本文后希望可以检查一下图中的内容是否都掌握了,文中的例子最好实际操作一下,下面开始正文。

本文是前端工程化系列中的一篇,回不断更新,下篇更新内容可看文末的下期预告宗旨:工程化的最终目的是让业务开发可以 100% 聚焦在业务逻辑上

lerna是什么?有什么优势?

lerna 基础概念

A tool for managing JavaScript projects with multiple packages. Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

翻译:Lerna是一个用来优化托管在 git\npm 上的多 package 代码库的工作流的一个管理工具,可以让你在主项目下管理多个子项目,从而解决了多个包互相依赖,且发布时需要手动维护多个包的问题。

关键词:多仓库管理,多包管理,自动管理包依赖

lerna 解决了哪些痛点

资源浪费

通常情况下,一个公司的业务项目只有一个主干,多 git repo 的方式,这样 node_module 会出现大量的冗余,比如它们可能都会安装 ReactReact-dom 等包,浪费了大量存储空间。

调试繁琐

很多公共的包通过 npm 安装,想要调试依赖的包时,需要通过 npm link 的方式进行调试。

资源包升级问题

一个项目依赖了多个 npm 包,当某一个子 npm 包代码修改升级时,都要对主干项目包进行升级修改。(这个问题感觉是最烦的,可能一个版本号就要去更新一下代码并发布)

lerna的核心原理

monorepo 和 multrepo 对比

monorepo:是将所有的模块统一的放在一个主干分支之中管理。multrepo:将项目分化为多个模块,并针对每一个模块单独的开辟一个 reporsitory来进行管理。

image.png

lerna 软链实现(如何动态创建软链)

未使用 lerna 之前,想要调试一个本地的 npm 模块包,需要使用 npm link 来进行调试,但是在 lerna 中可以直接进行模块的引入和调试,这种动态创建软链是如何实现的?

软链是什么?

Node.js 中如何实现软链

lerna 中也是通过这种方式来实现软链的

fs.symlinkSync(target,path,type)

fs.symlinkSync(target,path,type)
target <string> | <Buffer> | <URL>   // 目标文件
path <string> | <Buffer> | <URL>  // 创建软链对应的地址
type <string>

它会创建名为 path 的链接,该链接指向 targettype 参数仅在 Windows 上可用,在其他平台上则会被忽略。它可以被设置为 'dir''file''junction'。如果未设置 type 参数,则 Node.js 将会自动检测 target 的类型并使用 'file''dir'。如果 target 不存在,则将会使用 'file'Windows 上的连接点要求目标路径是绝对路径。当使用 'junction' 时, target 参数将会自动地标准化为绝对路径。

  • 基本使用
const res = fs.symlinkSync('./target/a.js','./b.js');

image.png这段代码的意思是为 创建一个软链接 b.js 指向了文件 ./targert/a.js,当 a.js 中的内容发生变化时,b.js 文件也会发生相同的改变。

Node.js 文档中,fs.symlinkSync()``lerna 的源码中动态链接也是通过 symlinkSync 来实现的。源码对应地址:软链实现源码地址参考1

function createSymbolicLink(src, dest, type) {
  log.silly("createSymbolicLink", [src, dest, type]);

  return fs
    .lstat(dest)
    .then(() => fs.unlink(dest))
    .catch(() => {
      /* nothing exists at destination */
    })
    .then(() => fs.symlink(src, dest, type));
}

更多关于软链的文章,我后面会单独写一篇文章介绍软硬链接,这里知道 lerna 链接部分 的实现就可以了。Node fs 官网 参考2

lerna 基本使用

lerna 环境配置

lerna 在使用之前需要全局安装 lerna 工具。

npm install lerna -g

初始化一个lerna 项目

mkdir lerna-demo,在当前目录下创建文件夹lerna-demo,然后使用命令 lerna init执行成功后,目录下将会生成这样的目录结构。,一个 hello world级别的 lerna 项目就完成了。

image.png

 - packages(目录)
 - lerna.json(配置文件)
 - package.json(工程描述文件)

lerna 常用命令

介绍一些 lerna 常用的命令,常用命令这部分可以简单过一遍,当作一个工具集收藏就行,需要的时候来找下,用着用着就熟练了,主要可以实操下下面的实战小练习,这个过程会遇到一些坑的。

  1. 初始化 lerna 项目
lerna init 

2 . 创建一个新的由 lerna 管理的包。

lerna create <name>

3 . 安装所有·依赖项并连接所有的交叉依赖

lerna bootstrap

4 . 增加模块包到最外层的公共 node_modules

lerna add axios

5 . 增加模块包到 packages 中指定项目 下面是将 ui-web 模块增加到 example-web 项目中

lerna add ui-web --scope=example-web

6 . 在 packages 中对应包下的执行任意命令 下面的命令,是对 packages 下的 example-web 项目执行 yarn start 命令 ,比较常用,可以把它配置到最外层的 package.json 中。

lerna exec --scope example-web -- yarn start

如果命令中不增加 --scope example-web直接使用下面的命令,这会在 packages 下所有包执行命令rm -rf ./node_modules

lerna exec -- rm -rf ./node_modules

7 . 显示所有的安装的包

lerna list // 等同于 lerna ls

这里再提一个命令也比较常用,可以通过json的方式查看 lerna 安装了哪些包,json 中还包括包的路径,有时候可以用于查找包是否生效。

lerna list --json

8 . 从所有包中删除 node_modules 目录

lerna clean

⚠️注意下 lerna clean 不会删除项目最外层的根 node_modules

9 . 在当前项目中发布包

lerna publish

这个命令可以结合 lerna.json 中的 "version": "independent" 配置一起使用,可以完成统一发布版本号和packages 下每个模版发布的效果,具体会在下面的实战讲解。

lerna publish 永远不会发布标记为 private 的包(package.json中的”private“: true

以上命令基本够日常开发使用了,如果需要更详细内命令内容,可以查看下面的详细文档 lerna 命令详细文档参考3

lerna 应用(适用场景)

从零搭建一个 平台基础组件库项目

lerna 比较适合的场景:基础框架,基础工具类,ui-component 中会存在 h5 组件库,web 组件库,mobile 组件库,以及对应的 doc 项目,三个项目通用的 common 代码。为了方便多个项目的联调,以及分别打包,这里采用了lerna 的管理方式。

接下来会讲解使用 leran 搭建 ui-component 基础组件库的过程。

1. 项目初始化

创建一个文件夹 ui-component ,

切换到目录 ui-component目录下。执行 lerna init

image.png

lerna 会自动创建一个 packages 目录夹,我们以后的项目都新建在这里面。同时还会在根目录新建一个 lerna.json配置文件

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0" // 共用的版本,由lerna管理
}

注意`lerna默认使用的是集中版本,所有的package共用一个version,如果需要packages下不同的模块 使用不同的版本号,需要配置Independent模式。命令行介绍时有提到这里 在json 中增加属性配置

  "version": "independent"

package.json 中有一点需要注意,他的 private 必须设置为 true ,因为 mono-repo 本身的这个 Git仓库并不是一个项目,他是多个项目,所以一般不进行直接发布,发布的应该是 packages/ 下面的各个子项目。

子项目创建

现在 package 目录下是空的,我们需要创建一下组件库内部相关内容。使用 leran create 命令创建子 package 项目。

lerna create ui-common

lerna create ui-common会在 packages 中创建 ui-common 项目,另外创建两个基于 TypeScriptreact 项目 ui-webexample-web, 在 package 目录下运行

npx create-react-app ui-web --typescript
npx create-react-app example-web --typescript

这里补充一个小插曲吧,初始化 typescript 项目后如何进行配置,可以直接用 typescript 编写组件? 安装 typescript需要的模块包

$ npm install --save typescript @types/node @types/react @types/react-dom @types/jest
$ # 或者
$ yarn add typescript @types/node @types/react @types/react-dom @types/jest

然后在项目根目录创建 tsconfig.jsonwebpack.config.js 文件:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["dom","es2015"],
    "jsx": "react",
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": true,
    "baseUrl": "src",
    "paths": {
      "@/*": ["./*"],
    },
    "esModuleInterop": true,
    "experimentalDecorators": true,
  },
  "include": [
    "./src/**/*"
  ]
}
  • jsx 选择 react
  • lib 开启 domes2015
  • include 选择我们创建的 src 目录
var fs = require('fs')
var path = require('path')
var webpack = require('webpack')
const { CheckerPlugin } = require('awesome-typescript-loader');
var ROOT = path.resolve(__dirname);

var entry = './src/index.tsx';
const MODE = process.env.MODE;
const plugins = [];
const config = {
  entry: entry,
  output: {
    path: ROOT + '/dist',
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.ts[x]?$/,
        loader: [
          'awesome-typescript-loader'
        ]
      },
      {
        enforce: 'pre',
        test: /\.ts[x]$/,
        loader: 'source-map-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json'],
    alias: {
      '@': ROOT + '/src'
    }
  },
}

if (MODE === 'production') {
  config.plugins = [
    new CheckerPlugin(),
    ...plugins
  ];
}

if (MODE === 'development') {
  config.devtool = 'inline-source-map';
  config.plugins = [
    new CheckerPlugin(),
    ...plugins
  ];
}
module.exports = config;

创建完两个项目后, ui-webexample-web 中同时出现 node_modules,二者会有很多重复部分,并且会占用大量的硬盘空间

lerna bootstrap

lerna 提供了可以将子项目的依赖包提升到最顶层的方式 ,我们可以执行 lerna clean先删除每个子项目的 node_modules , 然后执行命令 lerna bootstrop --hoist

lerna bootstrop --hoist 会将 packages 目录下的公共模块包抽离到最顶层,但是这种方式会有一个问题,不同版本号只会保留使用最多的版本,这种配置不太好,当项目中有些功能需要依赖老版本时,就会出现问题。

yarn workspaces

有没有更优雅的方式?再介绍一个命令 yarn workspaces ,可以解决前面说的当不同的项目依赖不同的版本号问题, yarn workspaces会检查每个子项目里面依赖及其版本,如果版本不一致都会保留到自己的 node_modules 中,只有依赖版本号一致的时候才会提升到顶层。注意:这种需要在 lerna.json 中增加配置。

 "npmClient": "yarn",  // 指定 npmClent 为 yarn
  "useWorkspaces": true // 将 useWorkspaces 设置为 true

并且在顶层package.json 中增加配置

// 顶层的 package.json
{
    "workspaces":[
        "packages/*"
    ]
}

增加了这个配置后 不再需要 lerna bootstrap 来安装依赖了,可以直接使用 yarn install 进行依赖的安装。注意:yarn install 无论在顶层运行还是在任意一个子项目运行效果都是可以。

启动子项目

配置完成后,我们启动 packages 目录下的子项目 example-web,原有情况下我们可能需要频繁切换到 example-web 文件夹,在这个目录执行 yarn start

使用了 lerna 进行项目管理之后,可以在顶层的 package.json 文件中进行配置,在 scripts 中增加配置。

  "scripts": {
        "web": "lerna exec --scope example-web -- yarn start",
  }

lerna exec --scope example-web 命令是在 example-web 包下执行 yarn start

并且在顶层 lerna.json 中增加配置

{
"npmClient": "true"
}

然后在顶层执行 yarn web 就可以运行 example-web 项目了。

配置完成后尝试一下,项目正常启动。

image.png

example-web 模块中 引用 ui-common 中的函数

我们在 ui-common中定义一个网络请求公共函数,在 ui-webexample-web 项目中都会用到。在项目 example-web 中增加 ui-common 模块依赖,执行命令

lerna add ui-common --scope=example-web

执行命令后,在 example-webpackage.josn中会出现

image.png

ui-common 已经成功被 example-web 中引用,然后在 example-web 项目中引用 request 函数并使用,例子中只是简单使用下 ui-common 中的函数。

import React from "react";
import request from "ui-common";

interface IProps {}
interface IState {
  conents: Array<string>;
}
class CommentList extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      conents: ["我是列表第一条"],
    };
  }
  componentDidMount() {
    request({
      url: "www.baidu.com",
      method: "get",
    });
  }
  render() {
    return (
      <>
        <ul>
          {this.state.conents.map((item, index) => {
            return <li key={index}> {item} </li>;
          })}
        </ul>
      </>
    );
  }
}
export default CommentList;

发布

项目结构已基本搭建完成,我们尝试发布一下 ,使用命令

lerna publish

由于之前我们在 lerna.json 中配置了

{
  "packages": [
    "packages/*"
  ],
  "version": "independent",// 不同模块不同版本
  "npmClient": "yarn", 
  "useWorkspaces": true 
}

执行命令后在会出现如下内容,针对 packages 中的每个模块单独选择版本进行发布。

如果想要发布的模块统一,使用相同的版本号,需要修改lerna.json ,将 "version": "independent", 改为固定版本号,修改后尝试重新使用 lerna publish进行发布,

注意⚠️:这里再次声明一下,如果使用了 independent 方式进行版本控制,在 packages 内部的包进行互相依赖时,每次发布之后记得修改下发布后的版本号,否则在本地调试时会出现刚发布的代码不生效问题(这个问题本人亲自遇到过,单独说下)

框架类项目

公司组件库项目

组件库项目类似上面实战的目录结构,但是会在 packages 包下添加很多其他的模块,比如 ui-h5 , example-h5

工具类项目

举例一些开源项目。

  • babel 使用的就是 lerna 进行管理
  • facebook/jest 使用的是 lerna 进行管理
  • alibaba/rax 使用的是 lerna 进行管理

lerna 弊端

和传统的 git submodules 多仓库方式对比,我觉得 lerna 优势很明显的,个人认为唯一不足的是: 由于源码在一起,仓库变更非常常见,存储空间也变得很大,甚至几GCI 测试运行时间也会变长,虽然如此也是可以接受的。

下期预告

本文主要讲解了 lerna 的基本使用,并且用它搭建了一个基础目录结构(我会补充一些基础的配置 eslintprettier 等,本文不多写之前有写过),这种搭建我们没有必要每次都配置一遍,尝试一遍就好了,工程化的最终目的是让业务开发可以 100% 聚焦在业务逻辑上,下一篇文章会讲解 轮子 create-mono-repo cli 脚手架的完整实现过程,如何快速创建 mono-repo 项目

参考文章

  • [1] https://github.com/lerna/lerna/tree/main/utils/create-symlink
  • [2] http://nodejs.cn/api/fs.html#fs_fs_unlink_path_callback
  • [3] http://www.febeacon.com/lerna-docs-zh-cn
  • [4] https://juejin.cn/post/6844903885312622606
  • [5] https://github.com/dkypooh/front-end-develop-demo/tree/master/base/lerna
  • [6] http://www.febeacon.com/lerna-docs-zh-cn/routes/commands/bootstrap.html
  • [7] https://github.com/lerna/lerna/tree/main/utils/create-symlink

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/bRbEhMKRVKO3oAyMaUfVYA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237176次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  7981次阅读
 目录