1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        現(xiàn)代前端工程化-徹底搞懂基于 Monorepo 的 lerna 模塊(從原理到實戰(zhàn))

        共 13033字,需瀏覽 27分鐘

         ·

        2021-04-23 11:42

        本文你能學到什么?

        看完本文后希望可以檢查一下圖中的內(nèi)容是否都掌握了,文中的例子最好實際操作一下,下面開始正文。

        本文是前端工程化系列中的一篇,回不斷更新,下篇更新內(nèi)容可看文末的下期預告!宗旨:工程化的最終目的是讓業(yè)務開發(fā)可以 100% 聚焦在業(yè)務邏輯上

        lerna是什么?有什么優(yōu)勢?

        lerna 基礎(chǔ)概念

        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是一個用來優(yōu)化托管在 git\npm 上的多 package 代碼庫的工作流的一個管理工具,可以讓你在主項目下管理多個子項目,從而解決了多個包互相依賴,且發(fā)布時需要手動維護多個包的問題。

        關(guān)鍵詞:多倉庫管理,多包管理,自動管理包依賴

        lerna 解決了哪些痛點

        資源浪費

        通常情況下,一個公司的業(yè)務項目只有一個主干,多 git repo 的方式,這樣 node_module 會出現(xiàn)大量的冗余,比如它們可能都會安裝 React、React-dom 等包,浪費了大量存儲空間。

        調(diào)試繁瑣

        很多公共的包通過 npm 安裝,想要調(diào)試依賴的包時,需要通過 npm link 的方式進行調(diào)試。

        資源包升級問題

        一個項目依賴了多個 npm 包,當某一個子 npm 包代碼修改升級時,都要對主干項目包進行升級修改。(這個問題感覺是最煩的,可能一個版本號就要去更新一下代碼并發(fā)布)

        lerna的核心原理

        monorepo 和 multrepo 對比

        monorepo:是將所有的模塊統(tǒng)一的放在一個主干分支之中管理。multrepo:將項目分化為多個模塊,并針對每一個模塊單獨的開辟一個 reporsitory來進行管理。

        image.png

        lerna 軟鏈實現(xiàn)(如何動態(tài)創(chuàng)建軟鏈)

        未使用 lerna 之前,想要調(diào)試一個本地的 npm 模塊包,需要使用 npm link 來進行調(diào)試,但是在 lerna 中可以直接進行模塊的引入和調(diào)試,這種動態(tài)創(chuàng)建軟鏈是如何實現(xiàn)的?

        軟鏈是什么?

        Node.js 中如何實現(xiàn)軟鏈

        lerna  中也是通過這種方式來實現(xiàn)軟鏈的

        fs.symlinkSync(target,path,type)

        fs.symlinkSync(target,path,type)
        target <string> | <Buffer> | <URL>   // 目標文件
        path <string> | <Buffer> | <URL>  // 創(chuàng)建軟鏈對應的地址
        type <string>

        它會創(chuàng)建名為 path 的鏈接,該鏈接指向 target。type 參數(shù)僅在 Windows 上可用,在其他平臺上則會被忽略。它可以被設(shè)置為 'dir'、 'file''junction'。如果未設(shè)置 type 參數(shù),則 Node.js 將會自動檢測 target 的類型并使用 'file''dir'。如果 target 不存在,則將會使用 'file'。Windows 上的連接點要求目標路徑是絕對路徑。當使用 'junction' 時, target 參數(shù)將會自動地標準化為絕對路徑。

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

        這段代碼的意思是為  創(chuàng)建一個軟鏈接 b.js 指向了文件 ./targert/a.js,當 a.js 中的內(nèi)容發(fā)生變化時,b.js 文件也會發(fā)生相同的改變。

        Node.js 文檔中,fs.symlinkSync()lerna 的源碼中動態(tài)鏈接也是通過 symlinkSync 來實現(xiàn)的。源碼對應地址:軟鏈實現(xiàn)源碼地址參考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));
        }

        更多關(guān)于軟鏈的文章,我后面會單獨寫一篇文章介紹軟硬鏈接,這里知道 lerna 鏈接部分 的實現(xiàn)就可以了。Node fs 官網(wǎng) 參考2

        lerna 基本使用

        lerna 環(huán)境配置

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

        npm install lerna -g

        初始化一個lerna 項目

        mkdir lerna-demo,在當前目錄下創(chuàng)建文件夾lerna-demo,然后使用命令 lerna init執(zhí)行成功后,目錄下將會生成這樣的目錄結(jié)構(gòu)。,一個 hello world級別的 lerna 項目就完成了。

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

        lerna 常用命令

        介紹一些 lerna 常用的命令,常用命令這部分可以簡單過一遍,當作一個工具集收藏就行,需要的時候來找下,用著用著就熟練了,主要可以實操下下面的實戰(zhàn)小練習,這個過程會遇到一些坑的。

        1. 初始化 lerna 項目
        lerna init 
        1. 創(chuàng)建一個新的由 lerna 管理的包。
        lerna create <name>
        1. 安裝所有·依賴項并連接所有的交叉依賴
        lerna bootstrap
        1. 增加模塊包到最外層的公共 node_modules
        lerna add axios
        1. 增加模塊包到 packages 中指定項目 下面是將 ui-web 模塊增加到 example-web 項目中
        lerna add ui-web --scope=example-web
        1. packages 中對應包下的執(zhí)行任意命令 下面的命令,是對 packages 下的 example-web 項目執(zhí)行 yarn start 命令 ,比較常用,可以把它配置到最外層的 package.json 中。
        lerna exec --scope example-web -- yarn start

        如果命令中不增加 --scope example-web直接使用下面的命令,這會在 packages 下所有包執(zhí)行命令rm -rf ./node_modules

        lerna exec -- rm -rf ./node_modules
        1. 顯示所有的安裝的包
        lerna list // 等同于 lerna ls

        這里再提一個命令也比較常用,可以通過json的方式查看 lerna 安裝了哪些包,json 中還包括包的路徑,有時候可以用于查找包是否生效。

        lerna list --json
        1. 從所有包中刪除 node_modules 目錄
        lerna clean

        ??注意下 lerna clean 不會刪除項目最外層的根 node_modules

        1. 在當前項目中發(fā)布包
        lerna publish

        這個命令可以結(jié)合 lerna.json 中的  "version": "independent" 配置一起使用,可以完成統(tǒng)一發(fā)布版本號和packages 下每個模版發(fā)布的效果,具體會在下面的實戰(zhàn)講解。

        lerna publish 永遠不會發(fā)布標記為 private 的包(package.json中的”private“: true

        以上命令基本夠日常開發(fā)使用了,如果需要更詳細內(nèi)命令內(nèi)容,可以查看下面的詳細文檔 lerna 命令詳細文檔參考3

        lerna 應用(適用場景)

        從零搭建一個 平臺基礎(chǔ)組件庫項目

        lerna 比較適合的場景:基礎(chǔ)框架,基礎(chǔ)工具類,ui-component 中會存在 h5 組件庫,web 組件庫,mobile 組件庫,以及對應的 doc 項目,三個項目通用的 common 代碼。為了方便多個項目的聯(lián)調(diào),以及分別打包,這里采用了lerna 的管理方式。

        接下來會講解使用 leran 搭建 ui-component 基礎(chǔ)組件庫的過程。

        1. 項目初始化

        創(chuàng)建一個文件夾 ui-component ,

        切換到目錄 ui-component目錄下。執(zhí)行 lerna init

        image.png

        lerna 會自動創(chuàng)建一個 packages 目錄夾,我們以后的項目都新建在這里面。同時還會在根目錄新建一個 lerna.json配置文件

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

        注意``lerna默認使用的是集中版本,所有的package共用一個version,如果需要packages下不同的模塊 使用不同的版本號,需要配置Independent模式。命令行介紹時有提到這里 在json` 中增加屬性配置

          "version""independent"

        package.json 中有一點需要注意,他的 private 必須設(shè)置為 true ,因為 mono-repo 本身的這個 Git倉庫并不是一個項目,他是多個項目,所以一般不進行直接發(fā)布,發(fā)布的應該是 packages/ 下面的各個子項目。

        子項目創(chuàng)建

        現(xiàn)在 package 目錄下是空的,我們需要創(chuàng)建一下組件庫內(nèi)部相關(guān)內(nèi)容。使用 leran create 命令創(chuàng)建子 package 項目。

        lerna create ui-common

        lerna create ui-common會在 packages 中創(chuàng)建 ui-common 項目,另外創(chuàng)建兩個基于 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

        然后在項目根目錄創(chuàng)建 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 選擇我們創(chuàng)建的 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;

        創(chuàng)建完兩個項目后, ui-webexample-web 中同時出現(xiàn) node_modules,二者會有很多重復部分,并且會占用大量的硬盤空間

        lerna bootstrap

        lerna 提供了可以將子項目的依賴包提升到最頂層的方式 ,我們可以執(zhí)行 lerna clean先刪除每個子項目的 node_modules , 然后執(zhí)行命令  lerna bootstrop --hoist。

        lerna bootstrop --hoist 會將 packages 目錄下的公共模塊包抽離到最頂層,但是這種方式會有一個問題,不同版本號只會保留使用最多的版本,這種配置不太好,當項目中有些功能需要依賴老版本時,就會出現(xiàn)問題。

        yarn workspaces

        有沒有更優(yōu)雅的方式?再介紹一個命令 yarn workspaces ,可以解決前面說的當不同的項目依賴不同的版本號問題, yarn workspaces會檢查每個子項目里面依賴及其版本,如果版本不一致都會保留到自己的 node_modules 中,只有依賴版本號一致的時候才會提升到頂層。注意:這種需要在 lerna.json 中增加配置。

          "npmClient": "yarn",  // 指定 npmClent 為 yarn
        "useWorkspaces": true // 將 useWorkspaces 設(shè)置為 true

        并且在頂層package.json 中增加配置

        // 頂層的 package.json
        {
            "workspaces":[
                "packages/*"
            ]
        }

        增加了這個配置后 不再需要 lerna bootstrap 來安裝依賴了,可以直接使用 yarn install 進行依賴的安裝。注意:yarn install 無論在頂層運行還是在任意一個子項目運行效果都是可以。

        啟動子項目

        配置完成后,我們啟動 packages 目錄下的子項目 example-web,原有情況下我們可能需要頻繁切換到 example-web 文件夾,在這個目錄執(zhí)行 yarn start

        使用了 lerna 進行項目管理之后,可以在頂層的 package.json 文件中進行配置,在 scripts 中增加配置。

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

        lerna exec --scope example-web 命令是在 example-web 包下執(zhí)行 yarn start。

        并且在頂層 lerna.json 中增加配置

        {
        "npmClient""true"
        }

        然后在頂層執(zhí)行 yarn web 就可以運行 example-web 項目了。

        配置完成后嘗試一下,項目正常啟動。

        image.png

        example-web 模塊中 引用 ui-common 中的函數(shù)

        我們在 ui-common中定義一個網(wǎng)絡(luò)請求公共函數(shù),在 ui-webexample-web 項目中都會用到。在項目 example-web 中增加 ui-common 模塊依賴,執(zhí)行命令

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

        執(zhí)行命令后,在 example-webpackage.josn中會出現(xiàn)

        image.png

        ui-common 已經(jīng)成功被 example-web 中引用,然后在 example-web 項目中引用 request 函數(shù)并使用,例子中只是簡單使用下 ui-common 中的函數(shù)。

        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>;
                  })}
                </u
        l>
              </>
            );
          }
        }
        export default CommentList;

        發(fā)布

        項目結(jié)構(gòu)已基本搭建完成,我們嘗試發(fā)布一下 ,使用命令

        lerna publish

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

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

        執(zhí)行命令后在會出現(xiàn)如下內(nèi)容,針對 packages 中的每個模塊單獨選擇版本進行發(fā)布。

        如果想要發(fā)布的模塊統(tǒng)一,使用相同的版本號,需要修改lerna.json ,將 "version": "independent", 改為固定版本號,修改后嘗試重新使用 lerna publish進行發(fā)布,

        注意??:這里再次聲明一下,如果使用了 independent 方式進行版本控制,在 packages 內(nèi)部的包進行互相依賴時,每次發(fā)布之后記得修改下發(fā)布后的版本號,否則在本地調(diào)試時會出現(xiàn)剛發(fā)布的代碼不生效問題(這個問題本人親自遇到過,單獨說下)

        框架類項目

        公司組件庫項目

        組件庫項目類似上面實戰(zhàn)的目錄結(jié)構(gòu),但是會在 packages 包下添加很多其他的模塊,比如 ui-h5 , example-h5

        工具類項目

        舉例一些開源項目。

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

        lerna 弊端

        和傳統(tǒng)的 git submodules 多倉庫方式對比,我覺得 lerna 優(yōu)勢很明顯的,個人認為唯一不足的是: 由于源碼在一起,倉庫變更非常常見,存儲空間也變得很大,甚至幾G,CI 測試運行時間也會變長,雖然如此也是可以接受的。

        下期預告

        本文主要講解了 lerna 的基本使用,并且用它搭建了一個基礎(chǔ)目錄結(jié)構(gòu)(我會補充一些基礎(chǔ)的配置 eslint,prettier 等,本文不多寫之前有寫過),這種搭建我們沒有必要每次都配置一遍,嘗試一遍就好了,工程化的最終目的是讓業(yè)務開發(fā)可以 100% 聚焦在業(yè)務邏輯上,下一篇文章會講解 輪子 create-mono-repo cli 腳手架的完整實現(xiàn)過程,如何快速創(chuàng)建 mono-repo 項目

        導圖插入后不是很清晰,有需要的公眾號回復 lerna 可獲取原圖。

        參考文章

        • [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
        瀏覽 67
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久久久久91香蕉国产 | 韩国精品一区二区 | 啊轻点灬太粗嗯太深了糙汉文 | 中国黄片一级片日逼的用普通话讲的 | 久免费视频 | 天堂俺去俺来也WWW | 美女露出让男人桶爽 | 草草久久久亚洲黑人AV成人片 | 久久婷婷秘 精品日产538 | 奇米二区 |