将共享节点模块用于通用类


15

目标

所以我有一个具有这种结构的项目:

  • 离子应用
  • Firebase功能
  • 共享

目的是在shared模块中定义公共接口和类。

限制条件

我不想将代码上传到npm以便在本地使用它,也根本不打算上传代码。它应该100%脱机工作。

尽管开发过程应离线进行,但ionic-appfirebase-functions模块将部署到Firebase(托管和功能)。因此,shared模块的代码应该在那里可用。

到目前为止我尝试过的

  • 我尝试在打字稿中使用项目引用,但还没开始工作
  • 我尝试将其安装为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)

您是否有使用打字稿配置或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"
  }
}

当前状态

我在共享模块中添加了一个npm脚本,该脚本将所有文件(没有index.js)复制到其他模块。问题在于,我将重复的代码检入到SCM中,并且每次更改都需要运行该命令。而且,IDE只是将其视为不同的文件。

Answers:


4

前言:我不太熟悉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挂钩。这是firebase.json通过属性predeploypostdeploy所需的服务完成的。这些属性包含一系列顺序命令,分别在部署代码之前和之后运行。此外,这些命令是用环境变量RESOURCE_DIR./functions或的目录路径./ionic-app,以适用者为准)和PROJECT_DIR(包含的目录路径firebase.json)调用的。

使用内部predeploy数组,我们可以将共享库的代码复制到部署到Cloud Functions实例的文件夹中。通过这样做,你可以简单地包括共享代码,就好像它是位于子文件夹库,也可以使用它映射的名字打字稿的路径映射在一个名为模块(所以你可以使用)。functionsfirebase.jsontsconfig.jsonimport { hiThere } from 'shared';

predeploy挂钩定义(用途全球的安装shx为Windows兼容):

// 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",
    ...
  }
}

将复制的库的打字稿源链接到功能打字稿编译器配置:

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

将模块名称“ shared”与复制的库的包文件夹关联。

// 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
}

托管文件夹可以使用相同的方法。


希望这能激发对Typescript编译更熟悉的人提出使用这些钩子的更干净的解决方案。


3

您可能想要尝试Lerna,这是一个用于管理具有多个软件包的JavaScript(和TypeScript)项目的工具。

设定

假设您的项目具有以下目录结构:

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

确保在所有不想发布的模块中以及模块中的条目中指定正确的访问级别(privateconfig/access密钥):typingsshared

共享:

{
  "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"
  }
}

离子应用:

{
  "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,您可以在其中指定devDependencies希望所有项目模块都可以访问的任何级别,例如单元测试框架,tslint等。

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

您还可以使用此根级别package.json来定义npm脚本,这些脚本将调用项目模块中的相应脚本(通过lerna):

{
  "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在根目录中运行时,在根postinstall级别定义的脚本package.json将被调用lerna bootstrap

是什么lerna bootstrap做的是,这将符号链接你的shared模块ionic-app/node_modules/sharedfirebase-functions/node_modules/shared,因此来自这两个模块的点,shared看起来就像任何其他的NPM模块。

汇编

当然,对模块进行符号链接是不够的,因为您仍然需要将它们从TypeScript编译为JavaScript。

这就是根package.json compile脚本的作用。

当您npm run compile在项目根目录中运行时,npm将调用lerna run compile --stream,并lerna run compile --stream调用compile每个模块package.json文件中调用的脚本。

由于每个模块现在都有自己的compile脚本,因此tsonfig.json每个模块应该可以有一个文件。如果您不喜欢复制,则可以使用根级tsconfig或从根目录继承的根级tsconfig文件和模块级tsconfig文件的组合。

如果您想了解此设置在实际项目中的工作方式,请查看Serenity / JS,在此我已经广泛使用了它。

部署方式

使shared模块node_modulesfirebase-functionsionic-app和下以及在项目下的根目录devDepedencies下对称链接的好处node_modules是,如果您需要将使用者模块部署到任何地方(ionic-app例如,例如),您只需将其与其一起压缩在一起,node_modules而不必担心在部署之前必须删除开发人员依赖。

希望这可以帮助!

一月


有趣!我将最终检查出来,看看是否合适。
MauriceNino19年

2

如果您正在使用git来管理代码,则另一个可能的解决方案是使用git submodule。使用git submodule您可以将另一个git存储库包含到您的项目中。

应用于您的用例:

  1. 推送您的shared-git-repository的当前版本
  2. git submodule add <shared-git-repository-link>在您的主要项目中使用以链接共享存储库。

这是文档的链接:https : //git-scm.com/docs/git-submodule


这实际上不是一个坏主意,但是这种方法基本上不需要进行本地开发和测试。
MauriceNino19年

0

如果我正确地理解了您的问题,那么解决方案比单个答案要复杂得多,并且部分取决于您的偏好。

方法1:本地副本

您可以使用Gulp来使您已经描述过的工作解决方案自动化,但是IMO很难维护,并且如果其他开发人员进来的话,会大大增加复杂性。

方法2:Monorepo

您可以创建一个包含所有三个文件夹的单个存储库,并将它们连接起来,以便它们表现为单个项目。正如上面已经回答的那样,您可以使用Lerna。它需要一些配置,但是一旦完成,这些文件夹将表现为单个项目。

方法3:组件

将这些文件夹中的每一个视为独立组件。看看Bit。它将允许您将文件夹设置为较大项目的较小部分,并且可以创建一个私人帐户,该帐户将仅对您限制这些组件。初始设置后,它甚至允许您将更新应用于单独的文件夹,使用它们的父文件夹将自动获取更新。

方法4:包

您专门说过,您不想使用npm,但我想共享它,因为我目前正在按照如下所述进行设置,并且对我来说做得很完美:

  1. 使用npmyarn为每个文件夹创建一个程序包(您可以为两个文件夹都创建作用域的程序包,这样,如果您担心此代码,则仅对您可用)。
  2. 在父文件夹(使用所有这些文件夹)中,创建的包作为依赖关系连接。
  3. 我使用webpack捆绑所有代码,结合使用webpack路径别名和打字稿路径。

就像一个魅力一样,当将软件包符号链接以进行本地开发时,它完全可以脱机工作,根据我的经验-每个文件夹都可以分别扩展,并且非常易于维护。

注意

在我的情况下,“子”软件包已经过预编译,因为它们很大,并且我为每个软件包创建了单独的tsconfigs,但是美丽的是您可以轻松更改它。过去,我在模块和编译文件中都使用过打字稿,在原始js文件中也都用过,所以整个过程非常通用。

希望这可以帮助

*****更新****继续进行第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还是普通javascript编写的。

设置完成后,它将非常适合生产。然后,链接并离线工作:

  1. 导航至firebase-functions项目并编写npm link。它将在您的计算机本地创建一个符号链接,并将该链接映射为您在package.json中设置的名称。
  2. 导航到父项目并编写npm link firebase-functions,这将创建符号链接并将firebase-functions的依赖项映射到创建它的文件夹中。

我想你误会了。我从未说过我不想使用npm。实际上,所有这三个模块都是节点模块。我只是说过,我不想将模块上传到npm。您能否在第四部分进行详细说明-听起来很有趣?也许提供一个代码示例?
MauriceNino19年

我将添加另一个答案,因为它会很长且
难以理解

更新了我的初始答案,希望更清楚
Ivan Dzhurov

0

我不想将代码上传到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. 我们的构建服务器将创建npm软件包,bundled dependencies其中包含所有内部软件包node_modules,生产服务器从不需要访问NPM或私有NPM软件包,因为所有必需的软件包已捆绑在一起。

使用私有npm存储库具有多种优势,

  1. 无需自定义脚本
  2. 适合节点buid /发布管道
  3. 每个私有的npm软件包都将直接链接到您的私有git源代码控件,便于将来调试和调查错误
  4. 每个软件包都是只读快照,因此一旦发布就无法修改,并且在创建新功能时,具有较早版本的依赖软件包的现有代码库将不会受到影响。
  5. 您可以轻松地将某些软件包公开,并在将来移至其他存储库
  6. 如果您的私有npm提供程序软件发生了更改,例如,您决定将代码移至节点的私有npm软件包注册表云中,则无需对代码进行任何更改。

这可能是一个解决方案,但不幸的是,这对我来说不是。谢谢您的宝贵时间!
MauriceNino19年

还有一个本地npm存储库,它已安装为小型节点服务器verdaccio.org
Akash Kava,

-1

您正在寻找的工具是npm linknpm link提供到本地npm软件包的符号链接。这样,您可以链接包并在主项目中使用它,而无需将其发布到npm包库中。

应用于您的用例:

  1. npm linkshared包装内使用。这将为以后的安装设置符号链接目标。
  2. 导航到您的主要项目。在您的functions程序包内部,用于npm link shared链接共享程序包并将其添加到node_modules目录中。

这是文档的链接:https : //docs.npmjs.com/cli/link.html


据我所知,npm链接仅用于测试,如果您要部署结果代码(例如我的函数),则该链接不起作用。
MauriceNino19年

我知道,您可能应该将此问题添加到您的问题中。
Friedow

问题中已经提到了它,但我将对其进行澄清。
MauriceNino19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.