架构:合并多个JavaScript项目(模块化)

8
我想为我的JavaScript项目创建一个清晰的架构。该项目由一个Node.js服务器和两个不同用途的Angular.js前端组成。我使用自定义grunt构建来构建前端。构建结果是每个项目一个单一的HTML文件和两个压缩/混淆的CSS和JavaScript文件。然后,每个前端都在一个独立的最小化版本的Node服务器上运行(仅提供静态文件服务)。
到目前为止,一切都很清楚。现在的目标是使每个核心项目都能添加插件模块。一个模块应该扩展任何一个项目的JavaScript。这意味着例如在一个前端中添加一个附加的angular模块到angular配置。我已经知道在核心应用程序中添加angular模块代码的位置和方法。
问题现在是:如何在多个项目之间创建合理的构建过程,同时还依赖于插件模块?我想出了两个解决方案。
1. 我可以将插件作为NPM依赖项添加到其中一个核心项目中。然而,这种方法的缺点是,每次更改模块都需要推送到NPM,并且以这种方式开发插件模块并不方便。插件无法独立运行。
2. 我可以在一个核心项目的Gruntfile中列出插件列表。该列表将包含到模块的本地文件路径。在核心模块的构建中,将执行插件的构建。这将包括监视插件的构建文件的更改。这是一个不太干净的解决方案。
3. 我可以有另一个项目,其中包含对核心项目和所有插件的依赖关系,并将它们全部构建在一起。如何添加依赖关系仍然是个问题(情况1或2)。
你会怎样解决这个问题?

3
建议考虑使用Webpack而不是Grunt。 - charlietfl
@charlietfl,您能否解释一下如何使用webpack在多个项目中实现架构和良好的开发环境? - ssc-hrep3
我很抱歉,我并不熟练使用它,因为它并不适用于我所做的大部分工作。我只是提出来,因为我相信它会有所帮助,因为它可以从节点模块中提取数据。 - charlietfl
1
对于解决方案#1,我在使用Composer拉取模块时遇到了类似的问题。我的解决方法是,在开发过程中使用符号链接将这些模块链接起来。因此,我可以实时更新依赖项,但它们的位置就像通过软件包管理器拉取的一样。 - PeterM
@charlietfl,相比webpack,Grunt有哪些优势? - jacobherrington
如果我没记错的话,你也可以使用 "some-local-package": "file:./to/the/pkg/dir"。这样你的本地更改就可以被包含进来了,但是对于生产/CI构建,你需要将这些更改发布到你正在使用的任何注册表中。 - Jhecht
4个回答

3

在我看来,这与任务管理器的工作(如gulp、grunt甚至webpack)并不是非常相关。社区正在走向一个你拥有许多(相对)小的节点模块,每个模块只做一件事情并且做得很好,因此这与你的第一个建议密切相关。

版本控制仓库的结构可能如下:

my-repo/
  package-1/
    package.json
  package-2/
    package.json
  package-3/
    package.json
  ...

然后你可以使用npm link在模块内部创建符号链接,这样就不必发布模块以获取更新。

有一个相当新的包叫做lerna,它自动执行npm link操作(检测本地模块中的依赖关系图并在它们之间建立链接),你只需要遵循它们的结构即可。此外,它还能智能地发布,你可以在所有包中运行命令和一堆其他相关的事情。

BabelReact是这种模块分离关注点的绝佳例子。Babel与lerna一起工作,自动化包之间的链接,这里是他们拥有单一代码库的原因


谢谢你的回答!我看了一下Lerna,它似乎非常有前途。你能详细说明一下如何使用吗?官方文档很糟糕。据我所知:你有几个项目,然后将它们复制/链接到“./packages/”文件夹中。Lerna如何识别你本地仓库的副本应该链接而不是从NPM获取呢?我也不太明白模块分离和单体库之间的联系,因为它听起来像相反的概念? - ssc-hrep3
1
Lerna会映射您当前存储库中的模块并相应地进行链接。假设您有模块ab,且a依赖于b,它知道在b中运行npm link,然后在a中运行npm link b。关于这两个术语,它们将其模块聚合到一个存储库中,存储库只是一种组织它们的方式,模块本身仍然是分离的。顺便说一下,这是关于lerna的另一个资源(刚刚发布):https://blog.cloudflare.com/cf-ui/#improvingthedevelopmentexperiencewithlernahttpslernajsio - Roy Miloh

1

几个月前我遇到了类似的问题。如果您熟悉像Grunt或Gulp这样的流式构建系统,安装动态依赖项就可以很容易地解决。

  1. 您可以在package.json中分配一个属性,例如dependencies或devdependencies,其中包含您需要的所有其他信息,bower.json或自定义JSON文件也可以。我更喜欢NPM打包清单或Bower清单,因为它们为每个模块提供有效的入口点。
  2. 使用grunt-auto-install,您可以动态安装任何构建过程所需的其他模块依赖项,这些依赖项可以从项目的主要package.json文件中解析出来。
  3. 使用每个附加package.json中的main属性将为您提供一个文件列表,以初始化uglify或连接任务。

谢谢您的回答!那么,您是否建议像这样链接到NPM模块:"project_a": "file:../project_a",还是建议不使用NPM来检索依赖项,而是使用Grunt?您如何在每个package.json中读取main属性?在Gruntfile中吗?您将如何监视插件模块中的更改? - ssc-hrep3
你好,我建议使用NPM或Bower来获取依赖项。使用Grunt,您可以安装它们,版本号将跟踪您的更改。之后,一个新的Grunt任务将循环每个额外的package.json或bower.json从已安装的属性中。使用每个主要属性将为您提供JavaScript文件的实际入口点。 有关main属性的更多信息,请参阅NPM文档:https://docs.npmjs.com/files/package.json#main - vorillaz
但是,您如何将NPM软件包链接到本地存储库,例如在开发过程中实时重新加载本地文件?如果您将项目链接到git {"dependencies": { "project_a": "http://github.com/...." } },则只能在推送另一个版本时更新该项目。这在开发过程中很麻烦。 - ssc-hrep3
使用Bower本地依赖项:https://dev59.com/GGcs5IYBdhLWcg3wHwbU#14571729或者使用NPM本地依赖项:https://dev59.com/BGYq5IYBdhLWcg3wkRYo#14387210您可以保留一个单独的文件来进行版本控制,并轻松扩展您的项目。 - vorillaz

1
我通常会按照您在第1点中描述的方式进行操作。但是我使用本地依赖项,这些依赖项将由我的构建链连接起来。
npm install ~/project_root/sub_project --save-dev

这样你就不需要一直推它了。你只需删除node_module/sub_project文件夹,然后在构建链中再次运行npm install即可。
使用browserify,您可以将依赖项添加到angular项目中。

插件本身无法运行。

我通常将我的服务或指令开发为常规JavaScript类,并用angular进行包装。
var Service = require("./path/to/service.js")
module.exports = [function() {
    return {
        restrict: 'AC',
        link: function($scope, $element) {
            var someService = new Service();
            $scope.$watch("whatever",function(value){
                service.doSomething();
            })
        }
    };
}];

这样您就可以独立运行和测试它们,而不依赖于Angular。当与WebWorkers一起使用时,这也具有优势。因为它们不能完全与Angular 1.X一起使用。

谢谢你的回答。我已经在NPM中找到了使用本地路径的解决方案,但是手动删除节点模块并重新运行npm install并不令人满意,因为我通常使用实时重载构建过程.. 你知道在开发插件时如何仍然使用自动重载吗? - ssc-hrep3
如果您使用gulp-watch,您只能扫描最终构建以进行自动重新加载。Gulp watch会将此功能转发到您的重新加载器。 - Richard Burkhardt
构建链的问题/优点在于它们是为测试驱动开发而设计的。我知道这并没有满足你的问题,但值得思考。 - Richard Burkhardt

0
你正在给你的MEAN项目增加不必要的复杂性。
为什么要通过grunt连接两个html部分?
你应该使用AngularJS的SPA架构,他们采用这种架构是有原因的,其中之一就是避免出现这样的问题。
不要将文件连接起来,只需配置你的SPA状态以从html文件中调用所需的部分模板。就这样!
在后端使用Web服务(我认为你已经在做了),这样你就可以独立于数据来源,因为你消耗的URI不受它来自哪个服务器的影响。
关于依赖项,你可以使用npm轻松地在前端或后端使用--save标志将它们添加到package.json文件中,然后将该文件复制到相应的应用程序目录并运行npm install。
但我真的不知道你想做什么,如果你能解释一下功能需求,那会有助于理解它的意义。

问题与构建过程有关,而不是有关在Angular中运行它的问题。 运行Angular单页应用程序并非难点。 此外,我不将HTML文件连接成一个HTML文件。 一个微小的index.html结果,主要的HTML组件会使用Grunt(grunt-html2js和grunt-contrib-htmlmin)一起构建成精简后的JavaScript字符串。这样,HTML组件始终在初始页面加载后可用,并且应用程序以最快的方式响应。 - ssc-hrep3
我猜你没有理解主要问题。为了简化问题,有一个主要的GitHub项目,它依赖于多个“插件”GitHub项目。这些项目不是必需的,但可以添加到主项目中,并将作为常规angular模块添加。但首先,几个项目的代码必须合并(连接或复制到核心项目中,以便angular可以拾取该模块)。这可以使用NPM完成,就像问题中描述的那样,但它具有一些缺点,我正在寻找更好的方法来做到这一点。 - ssc-hrep3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接