フルスタックのJavaScriptのmonorepoの例


あなたは大きなJavaScriptプロジェクトに取り組んでいます.それは機能でいっぱいで、日によって成長しています.
あなたはフロントエンドやサービスのバックエンド側の任意の種類のWebとモバイルクライアントを持っている.
あなたのアプリケーションのすべての部分はどういうわけか、他の部分と連携して、開発段階でプロジェクトを開始することもできます.
それがそうであるならば、私がMonoreposについて言わなければならないものを聞きます.
( source code )

モノレールは何ですか。


ASWikipedia says :

A monorepo is a software development strategy
where code for many projects is stored in the same
repository.


シンプルで簡単.
JavaScriptの典型的な構造は以下の通りです:
repo-root/
  package.json
  projects/
    project-1/
      package.json
    project-2/
      package.json

工具


JavaScriptが付属しているとき、私たちは一つのリポジトリ内のプロジェクトを管理するために少なくとも2つのツールを持っています.


Yarn は、よく知られている依存関係管理ツール(NPMに相当)です.また、マルチパッケージ管理のプリミティブを提供することにより、プロジェクト管理ツールworkspaces :

Workspaces are a new way to set up your package architecture that’s
available by default starting from Yarn 1.0. It allows you to setup
multiple packages in such a way that you only need to run yarn
install
once to install all of them in a single pass.


基本的にこれらの機能を使用して、我々は単一の糸があります.ロックとルートレベルでの単一のNodeThangモジュールフォルダは、すべてのプロジェクト依存関係が一緒にインストールされることを意味します.
さらに、内部のパッケージ間の依存関係を定義することができます.

ラーナ


A tool for managing JavaScript projects with multiple packages.


Lerna サブプロジェクトの特定のセットとリポジトリで変更されたものに基づいてバージョン管理とパッケージパブリッシングのための統合された解決にカスタムスクリプトを実行する能力などのユーティリティを提供します.
完全性のために、それは糸のワークスペースだけでなく、それらを統合する可能性によってネイティブに実装されたすべての機能を提供しています:我々は第2のオプションを選択します.
糸、ラーナとmonoreposのより徹底的な議論のために、私はこの素晴らしいことを勧めますarticle .

サンプルプロジェクト


私たちのサンプルプロジェクトは、バックエンドからいくつかの本をフェッチし、Webインターフェイスを介して表示されるおもちゃのアプリケーションです.
しかし、それを実装するには、以下のようなアーキテクチャを選びました.
  • これは、マイクロサービスアーキテクチャ、特にフロントエンドとバックエンドは2つの別々のアプリケーションです.
  • また、複数のアプリケーションで共有できるパッケージを作成する可能性もあります.
  • 簡単に少なくとも1つの現実世界のユースケースをカバーするために強化することができますStorybook Design System Architecture )
  • フォルダ構造


    アプリケーションとパッケージの2つの別個のフォルダにプロジェクトを分割します.
    アプリケーションフォルダには、実行時にアプリケーションを構成するすべてのコンポーネントが含まれます.
    パッケージフォルダには、我々のアプリケーションによって共有されるモジュールが含まれます.

    最終的なフォルダ構造は次のようになります.
    repo-root/
      package.json
      packages/
        design-system/
          package.json
      applications/
        client/
          package.json
        api/
          package.json
    

    糸/ Lernaセットアップ


    まず、Monorepoの管理ツールを設定する必要があります.
    ルートの内部:
    yarn init
    
    注意:糸のワークスペースはルートパッケージを必要とします.JSONはプライベートなので、糸の初期化過程では、プライベートフラグをtrueに設定してください.
    それから、Lernaをインストールしなければなりません.
    yarn add lerna -D
    yarn lerna init
    
    私は常に依存関係のこの種の依存関係をインストールすることを好む.
    次に我々のプロジェクト構造に従って糸のワークスペースを定義します.
    // package.json
    
    {  
      
      "private": true,
      "workspaces": [
        "applications/*",
        "packages/*"
      ],
        
    }
    
    次に、我々はLearnaに糸のワークスペースとの統合を指示する.
    // lerna.json
    
    {
      ...
      "packages": [
        "applications/*",
        "packages/*"
      ],
      "npmClient": "yarn",
      "useWorkspaces": true,
      ...
    }
    
    最後に、開発中にアプリケーションを起動するためのカスタムスクリプトを追加します.
    // package.json
    
    {  
      
      "scripts": {
        "start": "yarn lerna run development:start --parallel"
      },
        
    }
    

    APIアプリケーションのコーディング


    バックエンドのために、私はGraphSQLを選びました.特に、私たちはgetting started tutorial of the official apollo website ( JavaScript ES 6の構文を利用するバベルを追加しました).
    まず最初に新しいディレクトリとcdを作ります.
    mkdir -p applications/api
    cd applications/api
    
    プロジェクトの依存関係を初期化しなければなりません
    yarn init -y
    yarn workspace applications/api add @babel/core @babel/cli @babel/node @babel/preset-env nodemon -D
    yarn add apollo-server graphql
    yarn install
    
    ファイルとフォルダ
    mkdir src
    touch src/index.js
    touch .babelrc
    
    次にいくつかの設定を加えなければなりません.
    ここでは、GraphSQLアプリケーションを起動するスクリプトを定義します.
    // applications/api/package.json
    
    {
      ...
      "scripts": {
        ...
        "development:start": "yarn nodemon --exec babel-node src/index.js ",
        ...
      },
      ...
    }
    
    
    ここでは、Babelコンパイラのプリセットを定義します.
    // applications/api/.babelrc
    
    {
      "presets": ["@babel/preset-env"]
    }
    
    最後にコードを追加できます:
    // applications/api/src/index.js
    
    import { ApolloServer, gql } from "apollo-server";
    
    const typeDefs = gql`
      type Book {
        title: String
        author: String
      }
    
      type Query {
        books: [Book]
      }
    `;
    
    const books = [
      {
        title: "Harry Potter and the Chamber of Secrets",
        author: "J.K. Rowling"
      },
      {
        title: "Jurassic Park",
        author: "Michael Crichton"
      }
    ];
    
    const resolvers = {
      Query: {
        books: () => books
      }
    };
    
    const server = new ApolloServer({ typeDefs, resolvers });
    
    server.listen().then(({ url }) => {
      console.log(`🚀 Server ready at ${url}`);
    });
    
    これで次のように動作します.
    yarn development:start
    
    or
    cd ../..
    yarn start
    

    クライアントアプリケーションのコーディング


    クライアント側のために、我々はGraphSQLバックエンドで働くためにアポロクライアントで反応ウェブアプリケーションを構築するつもりです.
    まず最初に、新しいCRAプロジェクトをブートストラップします.
    npx create-react-app applications/client
    
    我々は1つだけ糸を覚えている.ロックとそれがルートレベルに置かれなければならないので、craが糸を作成しないことを確認してください.ロック.そうでない場合:
    rm applications/client/yarn.lock
    
    次に依存関係をインストールします.
    cd applications/client
    yarn add @apollo/client graphql
    
    次にいくつかの設定を追加します.
    // applications/client/package.json
    
    {
      ...
      "scripts": {
        "development:start": "CI=true yarn react-scripts start",
        ...
      }
      ...
    }
    
    最後に、コードを追加します.
    // applications/client/src/App.js
    
    import React from "react";  
    import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";  
    import Books from "./components/Books";
    
    const client = new ApolloClient({  
      uri: "http://localhost:4000",  
      cache: new InMemoryCache()  
    });
    
    function App() {  
      return (  
        <ApolloProvider client={client}>  
          <Books />  
        </ApolloProvider>  
      );  
    }
    
    export default App;
    
    ここで我々のアプリのコンテンツを作成している:
    mkdir src/components
    touch src/components/Books.js
    
    // applications/client/src/components/Books.js
    
    import React from "react";
    import { useQuery, gql } from "@apollo/client";
    
    const ALL_BOOKS = gql`
      query GetAllBooks {
        books {
          title
          author
        }
      }
    `;
    
    function Books() {
      const { loading, error, data } = useQuery(ALL_BOOKS);
    
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;
    
      return data.books.map(({ title, author }) => (
        <div key={title}>
          <p>
            {title} by {author}
          </p>
        </div>
      ));
    }
    
    export default Books;
    
    実行してテストします.
    cd ../..
    yarn start
    
    APIアプリケーションも起動する方法に注意してください.

    デザインシステムパッケージのコーディング


    ここではいくつかの反応コンポーネントをパッケージ化します.
    まず最初に新しいディレクトリとcdを作ります.
    mkdir -p packages/design-system
    cd packages/design-system
    
    それから我々はプロジェクトとその構造をinitしなければならない.
    yarn init -y
    yarn add react@^16.0.0 -P  
    yarn add microbundle-crl -D
    
    mkdir src
    touch src/index.js
    mkdir src/components
    touch src/components/List.js
    touch src/components/ListItem.js
    
    次にいくつかの設定を追加します.
    // packages/design-system/package.json
    
    {
      ...
      "main": "dist/index.js",
      "module": "dist/index.modern.js",
      "source": "src/index.js",
      "scripts": {
        ...
        "development:start": "yarn microbundle-crl watch --no-compress --format modern,cjs"
        ...
      },
      ...
    }
    
    最後に、コードを追加します.
    // packages/design-system/src/index.js
    
    import List from "./components/List";
    
    export { List };
    
    // packages/design-system/src/components/ListItem.js
    
    import React from "react";
    import PropTypes from "prop-types";
    
    // I'm not using css files because they will not work when exported!
    // Consider to use styled components for your project...
    function ListItem(props) {
      return (
        <div
          style={{
            margin: "10px",
            padding: "10px",
            border: "1px solid #bbb",
            backgroundColor: "#eee"
          }}
        >
          <span
            style={{
              fontSize: "1.2em",
              textDecoration: "none",
              color: "#333"
            }}
          >
            {props.text}
          </span>
        </div>
      );
    }
    
    ListItem.propTypes = {
      text: PropTypes.string.isRequired
    };
    
    export default ListItem;
    
    // packages/design-system/src/components/List.js
    
    import React from "react";  
    import PropTypes from "prop-types";  
    import ListItem from "./ListItem";
    
    function List(props) {  
      return (  
        <div>  
          {props.items.map((content, index) => (  
            <ListItem key={index} text={content || ""} />  
          ))}  
        </div>  
      );  
    }
    
    List.propTypes = {  
      items: PropTypes.arrayOf(PropTypes.string).isRequired  
    };
    
    export default List;
    
    最後のステップとしてクライアントアプリケーションを更新する必要があります.
    // applications/client/src/components/Books.js
    
    import React from "react";
    import { useQuery, gql } from "@apollo/client";
    import { List } from "design-system";
    
    const ALL_BOOKS = gql`
      query GetAllBooks {
        books {
          title
          author
        }
     }
    `;
    
    function Books() {
      const { loading, error, data } = useQuery(ALL_BOOKS);
      if (loading) return <p>Loading</p>;
      if (error) return <p>Error :(</p>;
      return (
        <List
          items={data.books.map(({ title, author }) => `${title} by ${author}`)}
        />
      );
    }
    
    export default Books;
    
    と依存関係:
    yarn add design-system@^1.0.0
    
    これで、最終的なアプリケーションをテストすることができます
    cd ../..
    yarn start
    
    注:現在のところ、反応の開発サーバーのバグがあるようです.最初の開始後、ページを更新する必要があります.

    改善の余地


    我々のアプリは、そのような複雑なアーキテクチャが完全に不当に見えるかもしれないほど簡単です.
    しかし、この方法を考えてください.あなたは世界で最高のオンライン書店になるこの本のリストアプリをしたい!
    クライアント側では、少なくともあなたの顧客のためのストアアプリケーションとあなたのためのダッシュボードを供給する必要があります.
    サーバー側でデータモデルの下に爆発します.あなたのユーザー、トラックの注文などを管理する必要があります.つまり、サードパーティのシステムには、おそらくコードのビジネスロジックラインのトンを書き込む必要があります.あなたのコードを通して低結合と高い結合の原則を維持するために、あなたは多くのアプリケーションとモジュールの向こう側にこれらの論理を分割する必要があります.
    あなたのアプリはおそらく次のようになります.

    提案されたMonorepo構造によれば、あなたのコードを管理可能にしている間、プロジェクトをスケールアップするのは簡単です.適切なフォルダの下に必要なすべての新しいパッケージおよび/またはアプリケーションを作成するだけです.

    結論


    Web開発の分野におけるJavaScriptの破壊的な上昇は、単一のプログラミング言語で非常に複雑なアプリケーションを開発することが可能な技術の現状に達している.
    この状況は、ここで部分的に記述されたプロジェクト管理の集中化の可能性のようないくつかの利点を提供します.
    私はこの問題に関する私の考えがあなたの現在または次のプロジェクトへの助けとなることを心から願っています.
    フィードバックの任意の並べ替えは非常に有り難いです!