从 Github 安装时自动构建 NPM 模块

Posted

技术标签:

【中文标题】从 Github 安装时自动构建 NPM 模块【英文标题】:Automatically Build NPM Module On Install From Github 【发布时间】:2018-06-25 13:07:45 【问题描述】:

鉴于项目的lib/ 目录不应检入 Git,因为它包含的文件是派生文件(来自构建过程)。从项目的 github 安装包时(例如在开发过程中)lib/ 目录将不存在,因此如果包的package.jsonmain 字段指向(例如)lib/index.js,则该包不能导入时编译,因为这些文件在存储库中不存在,因此在安装到 node_modules 的包中。这意味着需要构建包(就像发布之前一样),只是这次在本地构建,以便将lib 目录(或在构建过程中生成的任何其他文件)添加到模块的目录中。

假设在package.json 文件的scripts 字段中有一个build 脚本,是否可以将包配置为在仅从github 安装的情况下自动运行它?如果没有,从 github 安装时确保构建它的最佳方法是什么?

现在有prepublishprepublishOnlyprepare 生命周期钩子,但没有一个可以解决这个问题,因为它们不允许任何方式来区分安装源。简而言之,是的,它们允许您在安装时构建,但它们不允许您仅从 github 安装构建。当人们从 npm 安装时,不仅没有理由强制构建,而且更重要的是,不会安装开发依赖项(例如 babel,这对构建至关重要)。

我知道解决这个问题的一种策略:

分叉/分支回购 在本地构建 从.gitignore 中删除lib/ 目录并签入。 从你的 fork/branch 安装模块 当您准备好 PR / rebase 时,将 lib/ 目录添加到 .gitignore 并从 git 中删除目录。

但这远非理想。我想这可以通过 gitook 自动化。所以每次你推送到 master 项目也会构建并推送到一个单独的分支。

在 NPM 的 Github 上有一个 closed issue 没有解决方案 - 只是很多人想要一个解决方案。从这个问题很明显,使用prepare 不是答案。

我的用例是我正在开发一个用于许多其他项目的模块。我想使用最新版本的模块,而不必在更新代码库时向 NPM 推出版本 - 我宁愿在准备好后推出更少的版本,但我仍然需要使用最新版本的 lib在 Github 上。

注意:我还就此问题联系了 NPM 的支持,如果我收到回复,我会添加他们的回复。

【问题讨论】:

我正在探索同样的问题。就我而言,我有一个经过修补的 react/react-sketchapp 分支。唯一的解决方案似乎是:“cd node_modules/react-sketchapp && npm install && npm run prepublishOnly”。这可以作为安装后步骤添加到我自己的 package.json 中,但它会在安装包时运行,不仅在需要时(安装/更新 react-sketchapp 之后) 有一个名为“npm-git-install”的 npm 包提出了一个解决方案,但我试过了,它在 /var/... 临时目录中安装了依赖库并创建了一个符号链接似乎没有工作。然后它阻止我卸载,所以我不得不对 package-lock.json 进行一些手动编辑和还原来解决。 可能重复:***.com/questions/40528053/… @DaveMeehan 谢谢。我认为使用prepublishOnly 可能确实存在问题,并且可能最好打折作为解决方案。我试过npm-git-install,发现它不可靠。这太奇怪了,没有解决办法。这一定是一个普遍的问题。 对于 git 和 npm 的不同构建步骤的假设对我来说似乎是错误的。从 git 安装后运行构建的整个想法是专门生成在 npm-publish 期间已经构建的相同资产。在 git-clone 之后没有简单的方法来做某事的原因是因为您不应该这样做。 TL;DR:prepare 是正确的脚本。它将在发布期间和作为 git dep 安装时为包 tarball 构建相同的资产。 【参考方案1】:

编辑:检测是否从 git repo 安装包

我没有正确理解这个问题。 以下是我写的东西,但有点离题。 现在,如果您想在从 repo 安装时运行 build.js:

repo 中的文件:

 .gitignore
 .npmignore
 ThisIsNpmPackage
 build.js
 package.json

.gitginore:

ThisIsNpmPackage

.npmignore

!ThisIsNpmPackage

在 package.json 中:

"scripts": 
    "install": "( [ ! -f ./ThisIsNpmPackage ] && [ ! -f ./AlreadyInstalled ] && echo \"\" > ./AlreadyInstalled && npm install . && node ./build.js ) || echo \"SKIP: NON GIT SOURCE\""

我们的想法是让文件 ThisIsNpmPackage 在 repo 中可用,但不在 npm 包中。 安装钩子它只是一个检查ThisIsNpmPackage 是否存在的笨拙脚本。如果是,那么我们执行npm install .(这将确保我们有devDependencies。生成文件AlreadyInstalled以防止无限循环 (npm install 会递归调用 install hook)

发布时我会使用 git pushnpm publish请注意,npm 发布可以通过 CI 工具 - githooks 自动化

这个带有文件ThisIsNpmPackage 的小技巧使源检测可用。

调用npm install dumb-package的结果:

“跳过:非 GIT 源”

并执行npm install https://github.com/styczynski/dumb-package

文件将被构建

问题

我们在这里面临的主要问题如下:

每次都必须做npm publish ... 有时修复一个小错误太痛苦了,然后推送到 repo 并忘记在 npm 上发布。当我在处理一个基于微服务的项目时,该项目有大约 5 个独立的子项目,分为模块,我发现了一个问题,修复了它,却忘记在我必须的每个地方发布,这真的很烦人。

不想将lib 推送到 repo 中,因为它来自源代码

变基和合并更烦人。

不要乱用.gitgnore

哎呀,当您有麻烦的文件必须包含在 repo 中但从不修改它们或有时删除时,我知道这个问题?那只是病态。

编辑:npm 钩子

正如@Roy Tinker 提到的,安装包时存在执行命令的能力。 可以通过 npm hooks 实现。

"install": "npm run build"

然后我们执行:

npm install https://github.com/<user>/<package>

编辑: 来自 cmets 的 OP 问题:

但这将为从 npm 下载模块的每个人运行安装,对吗?鉴于不会为从 npm 下载模块的任何人安装开发依赖项,因此这是一个很大的问题。不会安装用于构建应用程序的库 - babel 等。

注意:但是如果你想要一个特定版本的包(生产/开发),无论是否有开发依赖,你都可以通过以下方式安装它:

npm install --only=dev

--only=prod[uction]|dev[elopment] 参数将导致仅安装 devDependencies 或仅安装非 devDependencies,而不管 NODE_ENV。

在我看来,更好的解决方案是使用:

npm install <git remote url>

然后在 package.json 中指定:

"scripts": 
    "prepare": "npm run build"

如果正在安装的包包含一个准备脚本,它的依赖项和devDependencies将被安装,准备脚本将在包被打包和安装之前运行。

例子:

npm install git+https://isaacs@github.com/npm/npm.git

阅读 npm 文档:npm install

编辑:代理模块(高级技术)

这是一种不好的做法,但很高兴知道。

有时(如 Electron 框架,您需要根据各种情况安装其他外部包或资源/模块)。

在这些情况下使用代理思想:

您制作了一个行为类似于安装程序的模块,并根据您的需要安装所有内容

在您的情况下,准备脚本就足够了,但我保留此选项,因为它有时可能会有所帮助。

这个想法是你编写一个模块并为它编写一个安装kook

"scripts": 
    "install": "<do the install>"

在这种情况下你可以放在那里:

npm install . && npm run build

无论如何都会安装所有 devDependencies(如前面提到的准备案例),但这有点黑客攻击。

如果你想在那里进行真正的黑客攻击

 "scripts": 
    "install": "curl -L -J -O \"<some_url>\""
 

使用 -nix 命令手动下载文件curl

应该避免这种情况,但如果模块的每个平台都有大量二进制文件并且您不想全部安装它们,这是一个选项。

就像 Electron 你已经编译了二进制文件(每个用于单独的平台)

所以你希望人们制作 install package 而不是 install package-linuxpackage-window 等。

所以您在package.json 中提供自定义install 脚本


  ...
  "scripts": 
     "install": "node ./install_platform_dep.js"
  

然后在安装module 时会执行install_platform_dep.js 脚本。里面install_platform_dep.js你放:

// For Windows...
if(process.platform === 'win32') 
    // Trigger Windows module installation
    exec('npm install fancy-module-windows', (err, stdout, stderr) => 
         // Some error handling...
    
 else if ... // Process other OS'es

而这以纯手动方式安装一切。

注意:再一次,这种方法可用于依赖于平台的模块,如果你使用它,它可能是你的代码的设计问题。

基于 CI 构建

我想到的是我真正使用了很长时间的解决方案(使用 CI 服务自动构建)。

大多数 CI 服务的主要目的是在推送到分支或对 repo 执行其他操作时测试/构建/发布您的代码。

这个想法是您提供设置文件(如 travis.yml.gitlab-ci.yml),其余的由工具来处理。

如果您真的不想将 lib 包含到项目中,请相信 CI 可以完成所有工作:

Githook 将在提交时触发构建(在分支或任何其他位置 - 这只是配置问题) CI 将构建您的文件,然后将它们传递到测试阶段并发布

现在我正在 Gitlab 在我自己的项目上工作(作为爱好的一部分)一些网页。构建项目的 Gitlab 配置如下所示:

image: tetraweb/php

cache:
  untracked: true
  paths:
    - public_html/
    - node_modules/

before_script:
  - apt-get update

stages:
  - build
  - test
  - deploy
  
build_product:
  stage: build
  script:
    - npm run test

build_product:
  stage: build
  script:
    - npm run build
  
deploy_product:
  stage: deploy
  script:
    - npm run deploy

当我合并到主分支时,会发生以下事件:

CI 运行 build 阶段 如果构建成功,则启动 test 阶段 如果test 阶段正常,那么最终会触发deploy 阶段

脚本是要执行的unix命令列表。

您可以在配置中指定任何 Docker 映像,因此实际上可以使用您想要的任何 Unix 版本以及一些(或不)预安装的工具。

有一个包 deploy-to-git 将人工制品部署到所需的 repo 分支。

或者在这里(对于 Travis CI)将工件发布到 repo 的配置片段:

travis-publish-to-git

(我自己用过)

那么,当然可以让 CI 运行:

npm publish .

因为 CI 执行 Unix 命令,所以它可以(至少有一堆 CI 提供者):

发布标签(可能是发布标签?) 触发脚本以更新所有自述文件和任何地方的项目版本 如果所有阶段均成功,则向您发送通知

那么我该怎么做: 我承诺、推动并让工具做我想做的一切。 在此期间,我进行了其他更改,并在 1 到 10 分钟后通过邮件收到更新报告。

那里有很多 CI 提供者:

Travis CI Circle CI Gitlab CI for Gitlab Projects

这里附上我另一个项目的另一个例子(.travis.yml):

language: generic
install:
    - npm install
script:
    - chmod u+x ./utest.sh 
    - chmod u+x ./self_test/autodetection_cli/someprogram.sh
    - cd self_test && bash ../utest.sh --ttools stime --tno-spinner

如果你设置 CI 来推送和发布你的包,你总是可以确保使用最新的尖端版本的代码,而不必担心 我现在也必须运行这个命令...... 问题。

我建议您选择其中一家 CI 提供商。 最好的为您提供数百种能力!

当您习惯自动执行发布、测试和构建阶段时,您会发现它对享受生活有何帮助! 然后用自动脚本启动另一个项目,只需复制配置!

总结

在我看来 npm prepare script 是一种选择。 您也可以尝试其他人。 所描述的每种方法都有其缺点,可以根据您想要实现的目标来使用。 我只是想提供一些替代方案,希望其中一些适合您的问题!

【讨论】:

感谢您的补充说明。我可以看到这如何解决安装开发依赖项的问题,但除非我遗漏了什么,否则这仍然涉及为每个下载包的人运行build,无论是从 npm 还是从 github。我正在寻找仅在从 Github 安装时发生的构建,而 prepare 没有实现这一点。 npm install example 或 npm install githubURL 都将导致 prepare 并因此运行 build。我已在我的问题中添加了此说明。 哦,我明白了。所以也许这是一个选项:将文件(内容无关紧要)ThisIsNpmPackage 添加到您的存储库中?添加ThisIsNpmPackage.gitignore!ThisIsNpmPackage.npmignore 然后"install": "node ./build.js" 在构建脚本中检测文件ThisIsNpmPackage 是否存在如果它存在那么你从npm 安装包然后在非npm 包的情况下做@ 987654385@ 并继续... 我提供了带有设置的示例 repo(在那里描述^)请查看新的编辑 - 编辑:检测是否正在从 git repo 安装包 @Undistraction 我希望它会有所帮助:) 虽然我不相信这是最好的解决方案(至少我希望有一个完美的解决方案),但你绝对值得为你的努力付出代价。谢谢。【参考方案2】:

prepare 是正确的方法,但可能看起来坏了

如果您有一个包含源文件的存储库,但需要“构建”步骤才能使用它,prepare 在所有情况下都能满足您的需求(截至 npm 4) .

prepare:在打包和发布包之前,在本地 npm install 上运行,不带任何参数,在安装 git 依赖项时运行。

您甚至可以将构建依赖项放入devDependencies,它们将在prepare 执行之前安装。

这是我的一个包的an example,它使用了这种方法。


.gitignore - prepare 的问题似乎坏了

这个选项有一个问题会吸引很多人。 在准备依赖项时,Npm 和 Yarn 将保留package.jsonfiles 部分中列出的文件。

人们可能会看到files defaults to all files being included 并认为他们已经完成了。 很容易错过的是:

.npmignore大部分会覆盖files指令, 如果.npmignore 不存在,则使用.gitignore 代替。

所以,如果您的构建文件列在 .gitignore 中,就像一个理智的人,并且不做任何其他事情,prepare似乎损坏 .

如果您将files 修复为仅包含已构建的文件或添加一个空的.npmignore,则一切就绪。

我的建议是设置files(或者,通过反转,.npmignore),以便实际发布的文件只有已发布包的用户需要的文件。恕我直言,没有必要在已发布的包中包含未编译的源代码。

【讨论】:

除了它运行构建 2 次 - github.com/npm/cli/blob/… @KamilTomšík 我认为这不是一个准确的说法。您引用的代码看起来只构建一次。我也试过直接测试,没有看到构建运行两次的证据。 我知道它会在 prepareGitDep 阶段执行 npm install (静默),但是如果您仅在准备阶段执行此操作,则可能不会再次构建包,但不幸的是,您可能是对的我的情况是,它确实是因为我同时进行了打字稿编译和本机模块编译,这必须在安装后完成,因此它完成了两次(第一次在 npm 安装期间的 prepareGitDep 中,第二次在安装打包结果时) - 尝试安装这个@ 987654324@(需要很长时间)【参考方案3】:

假设 package.json 文件的 scripts 字段中有一个 build 脚本,是否可以将包配置为在这种情况下自动运行?

是的。您需要做两件事:

    确保您的系统使用 npmyarn 从 GitHub 安装包。如果此包是另一个包的依赖项,您可以使用 GitHub URL 代替 package.json 中的版本号。否则,以下命令将起作用:

    npm install https://github.com/YourUser/your-package
    

    如果您在特定标签或分支之后,您可以将/tags/v1.0.0 或其他任何内容添加到 URL 的末尾。

    将以下内容添加到您模块的package.json 中的scripts

    "install": "npm run build"
    

install 是包管理器在安装模块后执行的钩子。 (preinstallpostinstall 也 - 参见文档)。

文档:https://docs.npmjs.com/misc/scripts

【讨论】:

@Undistraction,是的,但此时你已经进入了矩阵。其他问题可以单独解决——也许只需将所需的依赖项从devDependencies 移动到dependencies。或者你的构建脚本可以运行它自己的npm install——它将安装开发依赖项。 prepare 脚本是正确的方法。此解决方案将重建已构建的版本。【参考方案4】:

编辑 2

这是一个很好的问题。没有公认的可靠解决方案太糟糕了,但以下似乎可行。

创建一个.buildme 标记文件,并提交到 git。

package.json:

  "files": ["lib"],
  "scripts": 
    "build": "echo DO WHAT YOU NEED TO BUILD",
    "prepack": "[ ! -f .buildme ] || npm run build",
    "preinstall": "[ ! -f .buildme ] || npm run build"
  ,

以下是需要注意的事项。

    应使用 "files" 键或通过 .npmignore 从 npm 包中排除特殊的 .buildme 标记文件。

    prepack 钩子在您发布时运行(prepublishOnly 也可以工作,但很高兴使用prepacknpm pack 将生成正确的压缩包)。

    当从 npm 安装时,preinstall 运行,但由于缺少 .buildme(感谢 [ ! -f .buildme ] 子句)而没有执行任何操作。

    从 github 安装时,.buildme 确实存在。在 npm6 上,prepack 钩子运行构建(并生成一个没有.buildme 的包),而preinstall 什么也不做。在 yarn 1.12 上,preinstall 进行构建。

    如果您从 github 安装更新版本,preinstall 将再次运行,并再次构建。

注意:从 github 安装时,由安装人员决定是否已经安装了足够的软件包 devDependencies 以使构建工作。 (此解决方案不会尝试自动安装 devDependencies。)

就是这样。它似乎适用于 npm 6 和 yarn 1.12 的各种组合。

【讨论】:

这是一个聪明的解决方案。不幸的是,它依赖于bash,这在 Windows 系统上通常不可用。使用prepare 脚本有一种官方方法。

以上是关于从 Github 安装时自动构建 NPM 模块的主要内容,如果未能解决你的问题,请参考以下文章

如何从需要构建步骤的 github 安装 npm 包,例如分叉图书馆时?

我应该在部署之前构建一个打字稿 npm 模块吗?

Vue项目模板--和--webpack自动化构建工具的---项目打包压缩使用

npm安装package.json中的模块依赖

实战:向GitHub提交代码时触发Jenkins自动构建

使用npm直接从github repo安装加载模块