<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 13033字,需瀏覽 27分鐘

           ·

          2021-04-06 21:13

          本文你能學(xué)到什么?

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

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

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

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

          關(guān)鍵詞:多倉(cāng)庫(kù)管理,多包管理,自動(dòng)管理包依賴

          lerna 解決了哪些痛點(diǎn)

          資源浪費(fèi)

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

          調(diào)試繁瑣

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

          資源包升級(jí)問(wèn)題

          一個(gè)項(xiàng)目依賴了多個(gè) npm 包,當(dāng)某一個(gè)子 npm 包代碼修改升級(jí)時(shí),都要對(duì)主干項(xiàng)目包進(jìn)行升級(jí)修改。(這個(gè)問(wèn)題感覺是最煩的,可能一個(gè)版本號(hào)就要去更新一下代碼并發(fā)布)

          lerna的核心原理

          monorepo 和 multrepo 對(duì)比

          monorepo:是將所有的模塊統(tǒng)一的放在一個(gè)主干分支之中管理。multrepo:將項(xiàng)目分化為多個(gè)模塊,并針對(duì)每一個(gè)模塊單獨(dú)的開辟一個(gè) reporsitory來(lái)進(jìn)行管理。

          image.png

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

          未使用 lerna 之前,想要調(diào)試一個(gè)本地的 npm 模塊包,需要使用 npm link 來(lái)進(jìn)行調(diào)試,但是在 lerna 中可以直接進(jìn)行模塊的引入和調(diào)試,這種動(dòng)態(tài)創(chuàng)建軟鏈?zhǔn)侨绾螌?shí)現(xiàn)的?

          軟鏈?zhǔn)鞘裁矗?span style="display: none;">

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

          lerna  中也是通過(guò)這種方式來(lái)實(shí)現(xiàn)軟鏈的

          fs.symlinkSync(target,path,type)

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

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

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

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

          Node.js 文檔中,fs.symlinkSync()lerna 的源碼中動(dòng)態(tài)鏈接也是通過(guò) symlinkSync 來(lái)實(shí)現(xiàn)的。源碼對(duì)應(yīng)地址:軟鏈實(shí)現(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)于軟鏈的文章,我后面會(huì)單獨(dú)寫一篇文章介紹軟硬鏈接,這里知道 lerna 鏈接部分 的實(shí)現(xiàn)就可以了。Node fs 官網(wǎng) 參考2

          lerna 基本使用

          lerna 環(huán)境配置

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

          npm install lerna -g

          初始化一個(gè)lerna 項(xiàng)目

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

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

          lerna 常用命令

          介紹一些 lerna 常用的命令,常用命令這部分可以簡(jiǎn)單過(guò)一遍,當(dāng)作一個(gè)工具集收藏就行,需要的時(shí)候來(lái)找下,用著用著就熟練了,主要可以實(shí)操下下面的實(shí)戰(zhàn)小練習(xí),這個(gè)過(guò)程會(huì)遇到一些坑的。

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

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

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

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

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

          ??注意下 lerna clean 不會(huì)刪除項(xiàng)目最外層的根 node_modules

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

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

          lerna publish 永遠(yuǎn)不會(huì)發(fā)布標(biāo)記為 private 的包(package.json中的”private“: true

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

          lerna 應(yīng)用(適用場(chǎng)景)

          從零搭建一個(gè) 平臺(tái)基礎(chǔ)組件庫(kù)項(xiàng)目

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

          接下來(lái)會(huì)講解使用 leran 搭建 ui-component 基礎(chǔ)組件庫(kù)的過(guò)程。

          1. 項(xiàng)目初始化

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

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

          image.png

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

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

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

            "version""independent"

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

          子項(xiàng)目創(chuàng)建

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

          lerna create ui-common

          lerna create ui-common會(huì)在 packages 中創(chuàng)建 ui-common 項(xiàng)目,另外創(chuàng)建兩個(gè)基于 TypeScriptreact 項(xiàng)目 ui-webexample-web, 在 package 目錄下運(yùn)行

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

          這里補(bǔ)充一個(gè)小插曲吧,初始化 typescript 項(xiàng)目后如何進(jìn)行配置,可以直接用 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

          然后在項(xiàng)目根目錄創(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)建完兩個(gè)項(xiàng)目后, ui-webexample-web 中同時(shí)出現(xiàn) node_modules,二者會(huì)有很多重復(fù)部分,并且會(huì)占用大量的硬盤空間

          lerna bootstrap

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

          lerna bootstrop --hoist 會(huì)將 packages 目錄下的公共模塊包抽離到最頂層,但是這種方式會(huì)有一個(gè)問(wèn)題,不同版本號(hào)只會(huì)保留使用最多的版本,這種配置不太好,當(dāng)項(xiàng)目中有些功能需要依賴?yán)习姹緯r(shí),就會(huì)出現(xiàn)問(wèn)題。

          yarn workspaces

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

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

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

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

          增加了這個(gè)配置后 不再需要 lerna bootstrap 來(lái)安裝依賴了,可以直接使用 yarn install 進(jìn)行依賴的安裝。注意:yarn install 無(wú)論在頂層運(yùn)行還是在任意一個(gè)子項(xiàng)目運(yùn)行效果都是可以。

          啟動(dòng)子項(xiàng)目

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

          使用了 lerna 進(jìn)行項(xiàng)目管理之后,可以在頂層的 package.json 文件中進(jìn)行配置,在 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 就可以運(yùn)行 example-web 項(xiàng)目了。

          配置完成后嘗試一下,項(xiàng)目正常啟動(dòng)。

          image.png

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

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

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

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

          image.png

          ui-common 已經(jīng)成功被 example-web 中引用,然后在 example-web 項(xiàng)目中引用 request 函數(shù)并使用,例子中只是簡(jiǎn)單使用下 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ā)布

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

          lerna publish

          由于之前我們?cè)?lerna.json  中配置了

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

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

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

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

          框架類項(xiàng)目

          公司組件庫(kù)項(xiàng)目

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

          工具類項(xiàng)目

          舉例一些開源項(xiàng)目。

          • babel 使用的就是 lerna 進(jìn)行管理
          • facebook/jest 使用的是 lerna 進(jìn)行管理
          • alibaba/rax 使用的是 lerna 進(jìn)行管理

          lerna 弊端

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

          下期預(yù)告

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

          導(dǎo)圖插入后不是很清晰,有需要的公眾號(hào)回復(fù) 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


          瀏覽 152
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青春草视频在线免费观看 | 青青草手机免费视频 | 国产免费九九视频 | 国产精品a久久久久久 | 国产资源在线播放 |