web-dev-qa-db-ja.com

一般的なクラスの共有ノードモジュールの使用

目標

だから私はこの構造を持つプロジェクトを持っています:

  • ionic-app
  • firebase-functions
  • 共有した

目標は、sharedモジュールで共通のインターフェースとクラスを定義することです。

制限

ローカルで使用するためにコードをnpmにアップロードしたくないので、コードをアップロードする予定はありません。 100%オフラインで動作するはずです。

開発プロセスはオフラインで動作するはずですが、ionic-appおよびfirebase-functionsモジュールがfirebase(ホスティングおよび関数)にデプロイされます。したがって、sharedモジュールのコードがそこにあるはずです。

これまでに試したこと

  • TypeScriptで Project References を使用してみましたが、動作に近づいていません
  • この質問 の2番目の回答のように、npmモジュールとしてインストールして試してみました。
    • 最初は問題なく動作しているようですが、ビルド中にfirebase deployを実行すると、次のようなエラーが発生します。
Function failed on loading user code. Error message: Code in file lib/index.js can't be loaded.
Did you list all required modules in the package.json dependencies?
Detailed stack trace: Error: Cannot find module 'shared'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/srv/lib/index.js:5:18)

質問

Typescripts configまたはNPMを使用して共有モジュールを作成するためのソリューションはありますか?

これを重複としてマークしないでください→StackOverflowで見つけた解決策を試しました。

追加情報

共有の構成:

// package.json
{
  "name": "shared",
  "version": "1.0.0",
  "description": "",
  "main": "dist/src/index.js",
  "types": "dist/src/index.d.ts",
  "files": [
    "dist/src/**/*"
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "publishConfig": {
    "access": "private"
  }
}

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "rootDir": ".",
    "sourceRoot": "src",
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "target": "es2017"
  }
}

関数の構成:

// package.json
{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "Shell": "npm run build && firebase functions:Shell",
    "start": "npm run Shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.0.0",
    "firebase-functions": "^3.1.0",
    "shared": "file:../../shared"
  },
  "devDependencies": {
    "@types/braintree": "^2.20.0",
    "tslint": "^5.12.0",
    "TypeScript": "^3.2.2"
  },
  "private": true
}


// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "rootDir": "src",
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  }
}

現在のsoution

共有モジュールにnpmスクリプトを追加しました。これにより、すべてのファイル(index.jsなし)が他のモジュールにコピーされます。これには問題があり、重複したコードをSCMにチェックインし、変更のたびにそのコマンドを実行する必要があります。また、IDEは、それを別のファイルとして扱います。

15
MauriceNino

序文:TypeScriptコンパイルがどのように機能するか、およびそのようなモジュールでpackage.jsonをどのように定義する必要があるかについて、あまり詳しくありません。このソリューションは機能しますが、目前のタスクを達成するためのハッキーな方法と考えることができます。

次のディレクトリ構造を想定します。

project/
  ionic-app/
    package.json
  functions/
    src/
      index.ts
    lib/
      index.js
    package.json
  shared/
    src/
      shared.ts
    lib/
      shared.js
    package.json

Firebaseサービスをデプロイするときに、コマンドを predeployおよびpostdeployフック にアタッチできます。これは、目的のサービスのプロパティpredeployおよびpostdeployを介してfirebase.jsonで行われます。これらのプロパティには、コードをデプロイする前と後にそれぞれ実行する一連のコマンドの配列が含まれています。さらに、これらのコマンドは、環境変数RESOURCE_DIR./functionsまたは./ionic-appのいずれか該当する方)およびPROJECT_DIRfirebase.jsonを含むディレクトリパス)で呼び出されます。

firebase.json内のpredeployfunctions配列を使用して、共有ライブラリのコードをCloud Functionsインスタンスにデプロイされているフォルダーにコピーできます。これを行うことで、サブフォルダーにあるライブラリのように共有コードを単純に含めることができます。または、tsconfig.jsonTypeScriptのパスマッピング を使用して名前付きモジュールに名前をマップできます(したがって、import { hiThere } from 'shared';を使用できます) )。

predeployフック定義(Windowsとの互換性のために shx のグローバルインストールを使用):

// firebase.json
{
  "functions": {
    "predeploy": [
      "shx rm -rf \"$RESOURCE_DIR/src/shared\"", // delete existing files
      "shx cp -R \"$PROJECT_DIR/shared/.\" \"$RESOURCE_DIR/src/shared\"", // copy latest version
      "npm --prefix \"$RESOURCE_DIR\" run lint", // lint & compile
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "ionic-app",
    ...
  }
}

コピーされたライブラリのTypeScriptソースを関数TypeScriptコンパイラ構成にリンクします。

// functions/tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": "./src",
    "paths": {
      "shared": ["shared/src"]
    }
  },
  "include": [
    "src"
  ],
  ...
}

コピーしたライブラリのパッケージフォルダーにモジュール名「共有」を関連付けます。

// functions/package.json
{
  "name": "functions",
  "scripts": {
    ...
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
    "shared": "file:./src/shared",
    ...
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "TypeScript": "^3.2.2",
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

同じ方法をホスティングフォルダーでも使用できます。


4
samthecodingman

複数のパッケージを持つJavaScript(およびTypeScript)プロジェクトを管理するためのツールである Lerna を試してみるとよいでしょう。

セットアップ

プロジェクトが次のディレクトリ構造を持っていると仮定します。

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json

公開したくないすべてのモジュールと、privateモジュールのtypingsエントリで、正しいアクセスレベル(sharedおよびconfig/accessキー)を指定してください。

共有:

{
  "name": "shared",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "typings": "lib/index.d.ts",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  }
}

Ionic-app:

{
  "name": "ionic-app",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  },
  "dependencies": {
    "shared": "1.0.0"
  }
}

上記の変更を行うと、ルートレベルpackage.jsonを作成できます。ここで、ユニットテストフレームワーク、tslintなど、すべてのプロジェクトモジュールにアクセスさせたいdevDependenciesを指定できます。

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json         // root-level, same as the `packages` dir

このルートレベルのpackage.jsonを使用して、プロジェクトのモジュール内の対応するスクリプトを(lerna経由で)呼び出すnpmスクリプトを定義することもできます。

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "compile": "lerna run compile --stream",
    "postinstall": "lerna bootstrap",
  },
  "devDependencies": {
    "lerna": "^3.18.4",
    "tslint": "^5.20.1",
    "TypeScript": "^3.7.2"
  },
}

これを配置したら、lerna構成ファイルをルートディレクトリに追加します。

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json
lerna.json

次の内容で:

{
  "lerna": "3.18.4",
  "loglevel": "info",
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0"
}

ここで、ルートディレクトリでnpm installを実行すると、ルートレベルpackage.jsonで定義されているpostinstallスクリプトがlerna bootstrapを呼び出します。

lerna bootstrapが行うことは、sharedモジュールをionic-app/node_modules/sharedおよびfirebase-functions/node_modules/sharedにシンボリックリンクすることです。したがって、これらの2つのモジュールから見ると、sharedは他のnpmモジュールと同じように見えます。

コンパイル

もちろん、モジュールをシンボリックリンクするだけでは十分ではありません。モジュールをTypeScriptからJavaScriptにコンパイルする必要があるからです。

ここで、ルートレベルのpackage.jsoncompileスクリプトが役立ちます。

プロジェクトルートでnpm run compileを実行すると、npmはlerna run compile --streamを呼び出し、lerna run compile --streamはモジュールの各package.jsonファイルでcompileというスクリプトを呼び出します。

モジュールごとに独自のcompileスクリプトがあるため、モジュールごとにtsonfig.jsonファイルを作成できます。複製が気に入らない場合は、ルートレベルのtsconfig、またはルートレベルのtsconfigファイルとモジュールレベルのtsconfigファイルの組み合わせをルートから継承することで回避できます。

この設定が実際のプロジェクトでどのように機能するかを確認したい場合は、- Serenity/JS を参照してください。

配備

sharedモジュールをnode_modulesの下のfirebase-functionsionic-appの下でシンボリックリンクし、プロジェクトルートの下のdevDepedenciesnode_modulesの下にシンボリックリンクすることの良い点は、コンシューマモジュールをデプロイする必要がある場合です。どこでも(たとえばionic-appなど)、node_modulesと一緒にすべてをZipするだけで、展開前に開発の依存関係を削除する必要がないか心配する必要はありません。

お役に立てれば!

ヤン

3
Jan Molak

Gitを使用してコードを管理している場合のもう1つの解決策は、git submoduleを使用することです。 git submoduleを使用すると、プロジェクトに別のgitリポジトリを含めることができます。

ユースケースに適用:

  1. Shared-git-repositoryの現在のバージョンをプッシュする
  2. メインプロジェクト内でgit submodule add <shared-git-repository-link>を使用して、共有リポジトリをリンクします。

ドキュメントへのリンクは次のとおりです。 https://git-scm.com/docs/git-submodule

2
friedow

私があなたの問題を正しく理解している場合、解決策は単一の回答よりも複雑であり、それはあなたの好みに一部依存します。

アプローチ1:ローカルコピー

Gulp を使用して、すでに説明した作業ソリューションを自動化できますが、IMOは維持が非常に簡単ではなく、ある時点で別の開発者が参加すると、複雑さが大幅に増大します。

アプローチ2:Monorepo

3つのフォルダーすべてを含む単一のリポジトリーを作成し、それらを接続して、それらが単一のプロジェクトとして動作するようにすることができます。上記ですでに回答したように、 Lerna を使用できます。少し設定が必要ですが、一度完了すると、これらのフォルダーは単一のプロジェクトとして動作します。

アプローチ3:コンポーネント

これらのフォルダーのそれぞれをスタンドアロンコンポーネントとして扱います。 Bit を見てください。これにより、大きなプロジェクトの小さな部分としてフォルダーを設定でき、これらのコンポーネントのスコープを自分だけに限定するプライベートアカウントを作成できます。最初に設定すると、個別のフォルダーに更新を適用することもでき、それらを使用する親フォルダーは自動的に更新を取得します。

アプローチ4:パッケージ

Npmを使用したくないと具体的に言ったが、私はそれを共有したい。なぜなら、私は現在、以下で説明するセットアップで作業しており、私にとって完璧な仕事をしているからです。

  1. npmまたはyarnを使用して、各フォルダーのパッケージを作成します(これらの両方のスコープパッケージを作成して、コードが利用できる場合にのみ、コードを使用できます)。
  2. 親フォルダー(これらのフォルダーをすべて使用する)では、作成されたパッケージが依存関係として接続されます。
  3. Typepackパスと組み合わせてwebpackパスエイリアスを使用して、すべてのコードをバンドルするためにwebpackを使用します。

チャームのように動作し、パッケージがローカル開発用にシンボリックリンクされている場合、それは完全にオフラインで動作し、私の経験では-各フォルダーは個別にスケーラブルであり、メンテナンスが非常に簡単です。

「子」パッケージはかなり大きいため、すでにプリコンパイルされています。パッケージごとに個別のtsconfigを作成しましたが、簡単に変更できるというのがすばらしい点です。過去には、モジュールとコンパイルされたファイルでTypeScriptを使用しており、未加工のjsファイルも使用していたため、全体が非常に用途が広いです。

お役に立てれば

***** UPDATE ****ポイント4に進むために、申し訳ありません。私の知る限り、アップロードされていないモジュールはシンボリックリンクできないため、間違っているのかもしれません。それにもかかわらず、ここにあります:

  1. 別のnpmモジュールがあるので、firebase-functionsを使用してみましょう。好みに応じて、コンパイルするか、生のtsを使用します。
  2. 親プロジェクトで、依存関係としてfirebase-functionsを追加します。
  3. tsconfig.json"paths": {"firebase-functions: ['node_modules/firebase-functions']"}を追加
  4. Webpack内-resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

このように、firebase-functionsを使用するだけで、import { Something } from 'firebase-functions'モジュールからエクスポートされたすべての関数を参照できます。 WebpackとTypeScriptは、それをノードモジュールフォルダーにリンクします。この構成では、親プロジェクトは、firebase-functionsモジュールがTypeScriptまたはVanilla JavaScriptで記述されているかどうかを気にしません。

設定すると、本番環境で完全に機能します。次に、リンクしてオフラインで作業するには:

  1. firebase-functionsプロジェクトに移動し、npm linkと書き込みます。マシンにローカルなシンボリックリンクを作成し、package.jsonで設定した名前にリンクをマッピングします。
  2. 親プロジェクトに移動してnpm link firebase-functionsを書き込みます。これにより、シンボリックリンクが作成され、firebase-functionsの依存関係が、作成したフォルダーにマッピングされます。
0
Ivan Dzhurov

ローカルで使用するためにコードをnpmにアップロードしたくないので、コードをアップロードする予定はありません。 100%オフラインで動作するはずです。

すべてのnpmモジュールはローカルにインストールされ、常にオフラインで動作しますが、パッケージを公開して他の人に見られたくない場合は、プライベートnpmレジストリをインストールできます。

ProGetは、Windows用のNuGet/Npmプライベートリポジトリサーバーであり、プライベート開発/本番環境で使用して、プライベートパッケージのホスト、アクセス、公開を行うことができます。それはWindows上ですが、Linux上で利用可能なさまざまな選択肢があると確信しています。

  1. Gitサブモジュールは悪い考えです。パッケージのようにバージョン管理されていないコードを共有するための古い方法であり、サブモジュールの変更とコミットは非常に困難です。
  2. ソースインポートフォルダーも悪い考えです。ここでもバージョン管理が問題です。誰かが依存リポジトリの依存フォルダーを変更した場合、再び追跡するのは悪夢だからです。
  3. Npmはパッケージを適切に管理するためのさまざまなツールをすでに提供しているため、パッケージの分離をエミュレートするサードパーティのスクリプトツールは時間の無駄です。

これが私たちのビルド/展開シナリオです。

  1. すべてのプライベートパッケージには、.npmrcを含むregistry=https://private-npm-repositoryがあります。
  2. すべてのプライベートパッケージを、プライベートにホストされているProGetリポジトリに公開します。
  3. すべてのプライベートパッケージには、ProGetの依存プライベートパッケージが含まれています。
  4. 私たちのビルドサーバーは、私たちが設定したnpm認証を介してProGetにアクセスします。私たちのネットワーク外の誰もこのリポジトリにアクセスできません。
  5. ビルドサーバーは、bundled dependencies内のすべてのパッケージを含むnode_modulesを使用してnpmパッケージを作成します。本番サーバーは、必要なすべてのパッケージがすでにバンドルされているため、NPMまたはプライベートNPMパッケージにアクセスする必要はありません。

プライベートnpmリポジトリを使用すると、さまざまな利点があります

  1. カスタムスクリプトは不要
  2. ノードのbuid/publishパイプラインに適合
  3. すべてのプライベートnpmパッケージには、プライベートgitソース管理への直接リンクが含まれ、将来のエラーのデバッグと調査が容易になります
  4. すべてのパッケージは読み取り専用のスナップショットであるため、一度公開すると変更できません。新しい機能を作成している間、古いバージョンの依存パッケージを含む既存のコードベースには影響しません。
  5. いくつかのパッケージを簡単に公開し、将来的に他のリポジトリに移動することができます
  6. プライベートnpmプロバイダーソフトウェアが変更された場合、たとえば、コードをノードのプライベートnpmパッケージレジストリクラウドに移動する場合、コードを変更する必要はありません。
0
Akash Kava