こんにちは、こんにちは! 少し前に、数種類のフロントエンド、多くのバックエンドサービス、コマンドラインインターフェイス、デーモンなどを含む、または計画している包括的なプロジェクトの開発を開始しました。 これにはすべてシャッフルされたコードが含まれており、既存のブリックから簡単で理解可能な方法で完全に新しいアプリケーションを組み立てることができるはずです。
用語に飽きていない場合は、プラットフォームを作成します。 DIYエレクトロニクス向けの視覚プログラミング用プラットフォーム。
プロジェクトが初期段階にあるという事実にもかかわらず、コードベースはすでにスラリーになると脅迫しています。 これをキャッチするために、プロジェクトをいわゆるmonorepoアプローチに移行しました。 Habréでは、このテーマに関する資料はありませんでした。そのため、ギャップを埋めようとします。
始まりは何でしたか
すべては非常に伝統的に始まった。 リポジトリは次のようになりました。
dist/ node_modules/ src/ assets/ components/ containers/ reducer/ actions.js actionTypes.js constants.js test/ package.json
React + Reduxスタックを扱う人は、テンプレートを即座に認識します。 フロントエンドのソースコードはsrc/
にあり、コマンドによってWebpackによってdist/
収集され、そこからフロントエンドを単純な静的として提供できます。
拡大
このような構造は、アプリケーションがそれほど大きくない場合にうまく機能します。 しかし、かなりすぐに多くのReact-componentsおよび-containers、Redux-reducers、および-actionを取得し、それらのディレクトリに群がり始めました。
グループへのセマンティック分割が提案されました:ユーザープログラムのグラフのレンダリングを担当するもの、このグラフの編集ツールを担当するもの、ファイルやタブをナビゲートするもの、情報メッセージを表示するものなど
共有する時間です。 その時点で、一般的に受け入れられている2つの分離アプローチの選択が生じました。
Railsスタイル:
src/ components/ project/ projectBrowser/ editor/ messages/ containers/ project/ projectBrowser/ editor/ messages/ reducers/ project/ projectBrowser/ editor/ messages/ actions/ project.js projectBrowser.js editor.js messages.js ...
またはポッドスタイル:
src/ project/ components/ containers/ reducers/ actions.js actionTypes.js constants.js projectBrowser/ components/ containers/ reducers/ actions.js actionTypes.js constants.js editor/ components/ containers/ reducers/ actions.js actionTypes.js constants.js messages/ components/ containers/ reducers/ actions.js actionTypes.js constants.js
Railsのアプローチは、レイヤーが明確に定義されているため優れています。 「パッケージ」の構造は規制されており、発明を誘発するものではありません。
しかし、そこには問題があります。 CLIインターフェイスが必要です。 Reactはコマンドラインユーティリティにはあまり意味がありません。コンポーネントとコンテナのレイヤーは必要ありません。 ただし、端末への美しい出力、引数の解析などのために、どこかにモジュールを配置する必要があります。 これにはレイヤーがありません。CLIにのみ追加する必要があります。
それから私達は何か他のものを思いつき、再度それが構造に合わないことを見る。 再び膨らまなければならない。 必然的に、 utils
、 helpers
、 tools
、 shared
またはそれらが通常マスクする名前のゴミ箱があります。 悪いオプション。
さて、そして最も重要なことは、コードベースから何らかの「パッケージ」を引き裂く簡単な方法はありません。今では独立したものであり、フロッピーディスクに投げてメールで送信することです。
したがって、私たちはポッドのコンセプトに決めました。
ウェイアップ
あるパッケージが別のパッケージを利用したい場合、相対的な方法でインポートする必要があります。
// src/editor/containers/Editor.jsx import { validateProject } from '../../core/project/selectors'
これについては議論の余地がある。 パッケージはディレクトリに配布されますが、それらの配置についての厳密な仮定は残ります。 「ポイント」の数は、インポートするモジュールのネストによって異なります。 とりわけ、リファクタリングが困難になります。
ライブラリのようにしたい:ライブラリの名前でインポートを開始すると、誰かがこのライブラリをどこで入手できるかがわかります:
// src/xod-editor/containers/Editor.jsx import { validateProject } from 'xod-core/project/selectors'
コアは、単純な名前を使用する場合にサードパーティのライブラリと競合する可能性を排除するためにxod-coreに変更しました。 XODは、私たちが行っているプロジェクトの名前です。
それでどうやってこれに来ますか? NPMとnode_modules
を介して実行される実際のJSパッケージを作成することは、ライブラリで発生するのとまったく同じです。
従来のアプローチは、独自のpackage.json
、バージョニングなどを使用して、各JSパッケージのリポジトリを作成することです。
ただし、動的な開発では、npm install、build、publish、npm link、git pull、git pushを使用して数十のリポジトリをジャグリングすることは地獄のようにも感じられます。 どういうわけか、すべてを1つのリポジトリに残す必要があります。
当分の間、構造をリファクタリングし、パッケージを明示的に強調表示します。
node_modules/ xod-cli/ bin/ src/ test/ xod-client/ dist/ src/ test/ xod-client-browser/ ... xod-client-electron/ xod-core/ xod-espruino/ xod-fs/ xod-server/ package.json
リンク
理論的には、このようなシナリオにはnpmリンクがありますが、プロジェクト構造を再現するためにすべてのリンクを新しいマシンに正しく配置することは簡単です。npmリンクはステートレスではありません。 そして、すべては単純化のために行われます。 結構です。
Nodeがモジュールを検索する方法に基づいたトリックがあります。 つまり、ノードはnode_modules/
を検索してファイルツリーを実行し、インポートモジュールが存在するディレクトリから開始します。
したがって、必要なシンボリックリンクをsrc/
内に手で作成できます。 一方では、インポート用に独自のパッケージを表示し、 package.json
通常の依存関係と競合しません。
node_modules/ react/ redux/ webpack/ xod-client/ dist/ src/ node_modules/ xod-core -> ../../../xod-core/src this-package-js-files.js xod-core/ src/ project/ selectors.js package.json
Hooray、インポートモジュールの位置に関係なく、次のことができます。
import { validateProject } from 'xod-core/project/selectors'
SimlinkはGitリポジトリに安全に保存できます。 Windowsは開発者の間ではありませんが、すべてがうまく機能します。コードは、クローン作成後すぐにアセンブリの準備ができています。
パッケージへのロールオーバー
起こったことは、まだ本格的なJSパッケージではありません。 パッケージをNPMにアップロードし、サードパーティのプロジェクトのすべての権限で平等に使用できるようにするには、各パッケージに独自のpackage.json
を提供し、その唯一の依存関係を登録する必要があります。 これで、メガパッケージの説明がルートにあります。 すべてのパッケージのすべての依存関係がそこにダンプされます。 修正します:
xod-client/ dist/ node_modules/ babel/ ramda/ react/ redux/ webpack/ src/ node_modules/ package.json xod-core/ node_modules/ babel/ ramda/ webpack/ src/ package.json package.json Makefile ←
シンボリックリンクを使用したストーリーは機能し続けますが、各パッケージは自分だけが必要とする独自のメタ情報と依存関係を受け取りました。 構造がより管理しやすくなりました。
10個のパッケージがあり、同じnpm install
を実行するには、各ディレクトリに移動して各パッケージのスクリプトを実行する必要があります。 クールじゃない。
ビルド、テスト、リント、または1つのコマンドで実行する機能を返すために、 Makefile
を追加しました。 次のようなコンテンツ:
install: npm install cd xod-cli && npm install cd xod-client && npm install cd xod-client-browser && npm install cd xod-client-electron && npm install cd xod-core && npm install cd xod-espruino && npm install
そして、すべてのアクションに対して。 少し厄介ですが、動作します。
愚かなビルド
このような構造の問題は、かなり迅速に表面化しました。 各パッケージがアカデミックの観点から独自の依存関係を持つようになったという事実は良好ですが、実際的な観点からは、同じ依存関係が数回インストールされ始めました。 make install
にはわずか10分かかりました。
時間の大部分は、Webpack、Babel、およびその友人の設定に費やされました。
さらに、ビルドでは、同じソースファイルが数回転置/パックされました:組み立てられるパッケージに対して1回。 生産的ではありません。
解決策:各パッケージがdist/
onceでビルドされ、依存パッケージが既製のアーティファクトを使用するようにします。 ビルドツール自体は、ルートnode_modules/
一度インストールできます。
このアプローチでは、パッケージ間のシンボリックリンクをsrc/
からdist/
転送し、Webpackの設定を少し調整して、「エイリアン」ソースを処理しないようにします。
また、ビルド順序に違反していないことも確認する必要があります。依存するパッケージの前に、依存するパッケージをビルドする必要があります。
開発者依存関係のすべてのツールがルートに移動しました:Webpack、Babel、Mocha、ESLint。
この測定のペアは、3分でCIサーバー上で完全なビルドと検証を返しました。 したがって、ローカルホストでは、事態はさらに活発になりました。
レルナ
パッケージディレクトリを前後に移動しているときに、 Lernaに出会いました。 これはかつてBabelから分離されたツールであり、1つのリポジトリに多くのパッケージを保持するのに役立ちます。 もちろん、 これはバベル自身で行われます。
Lernaを使用すると、各パッケージ内でnpmコマンドを実行し、各パッケージのバージョンをバンプできます。そして最も重要なこととして、いわゆるブートストラップを実行できます。
ブートストラップはローカルパッケージへのシンボリックリンクの作成であり、これは自動的に(パッケージのpackage.json
基づいて)、 src/
ではなく通常のnode_modules/
でのみ行われます。 ブートストラップの最後のステップは、各パッケージに3番目の依存関係をインストールすることです。 そして、これはすべてクロスプラットフォームです。
すべてがうまくいくでしょう、Lernaだけが2つの点で現在の構造と互換性がありません:
- パッケージは
packages/
サブディレクトリにある必要がありpackages/
- Simlinkはパッケージディレクトリに直接作成され、
dist/
やsrc/
などのサブディレクトリには作成されません
最初の問題は簡単に解決されます。 二番目はもっと難しいです。
実際には、次のように書くことはできません。
import { validateProject } from 'xod-core/project/selectors'
どこにでも書かなければなりません:
import { validateProject } from 'xod-core/dist/project/selectors'
これには不自然なものがあります。 しかし、いくつかのパッケージがいくつかのタイプのターゲット用にビルドしたい場合、そのサブディレクトリがdist/
表示されるとしたらどうでしょうか? すべてのインポートパスを完全に書き換える必要があります。 悪い、悪い。
package.json
ファイルを使用すると、たとえばdist/index.js
などのいわゆるメインファイルを指定できますが、「メインディレクトリ」を指定することはできません。 私が読んだものに基づいて、これはノードの公式の位置であり、変更されません。 ふけるために。
になる方法 息を吐き、他の人の経験を見てください。 そして、経験上、パスのあるインポートはほとんど見当たりません。 つまり foo
ライブラリがある場合は、 import { blabla } from 'foo'
直接インポートします: import { blabla } from 'foo'
。 import {blabla } from 'foo/bla/bla'
ありません。
そして、いまいましい、と思った。 このパッケージは、わかりやすく明確なフレームワークを取ります。特定の数の関数、定数、隣人が使用できるクラスのAPIを持っています。 このAPIは、独自のREADME.md
記述され、このリポジトリから切り取られ、別のリポジトリに配置され、独自に公開されます。
内部では、条件付きで、草が成長していなくても、あなたが親切であるかどうかがわかります。それは、美しく美しいAPIです。
その結果、多くの種類のインポートのすべて:
import { validateProject } from 'xod-core/project/selectors'
エレガントになりました:
import { validateProject } from 'xod-core' validateProject(...) // import core from 'xod-core' core.validateProject(...)
パッケージ自体は、ルートindex.js
検出に必要な文字を再エクスポートするだけです。
おわりに
その結果、快適に作業できる非常に優れた構造が得られ、1000を超えるコミットに耐えられるように感じました。
node_modules/ webpack/ babel/ mocha/ eslint/ packages/ xod-cli/ xod-client/ dist/ node_modules/ src/ api/ editor/ messages/ processes/ projectBrowser/ user/ index.js test/ package.json weback.config.js xod-client-browser/ xod-client-electron/ xod-core/ xod-espruino/ xod-fs/ .babelrc dist/ node_modules/ src/ backup.js index.js load.js save.js package.json xod-server/ package.json lerna.json
最も注意深い人は、フロントエンドコンポーネントを爆発させることからサンプルを開始し、いくつかのより大きなパッケージを続けたことに気付くでしょう。 そうです。 私たちの前線全体が1つのxod-client
内にありxod-client
。 そこでは、ポッドのスタイルで整理されています。 彼がそれを押さない間、それは判明しました。 そして、それが刈り取られ始めたら、私たちは何をすべきかを知っています:それを別のパッケージでトップレベルに持っていきます。
TODO:
- LernaはまだYarnと友達ではありません。 開発者が同意し、
npm install
がモノリポジトリのロケットになるまで待ちます。 - Lernaはすべてのパッケージでnpmスクリプトを実行できますが、相互依存関係があるため、これを実行できません。 そのため、ルート
package.json
ビルド順序を手動で登録する必要があります。 Gulpを使用してこれを簡素化する価値があります。
提示されたアプローチが「正しい」というふりをしません。 それは私たちにも起こり、プロジェクトが経た進化的変化に基づいて発展しました。 述べられた考えが誰かにとって有用であると判明した場合、私は嬉しいです;)
PSプロジェクトでフルスタックのJS開発者を探しています。 あなたがこの空室のために誰かを推薦できるならば、私は非常に感謝します。