【问题标题】:Application modularity with Vue.js and local NPM packages使用 Vue.js 和本地 NPM 包的应用程序模块化
【发布时间】:2020-11-15 02:10:51
【问题描述】:

我正在尝试通过vue-cli-service 在 Vue 中构建一个模块化应用程序。主应用程序和模块是位于不同文件夹中的独立项目,结构如下:

-- app/package.json
      /src/**
-- module1/package.json
          /src**
-- module2/package.json
          /src**

这个想法是让 Vue 应用程序完全不知道运行时可能存在的应用程序模块,这些模块本身使用 vue-cli-service build --target lib 在本地 moduleX/dist 文件夹中编译,并使用 package.json “main”和“文件”节点。

我的第一个想法(现在只是为了提高开发速度)是将模块作为本地 NPM 包添加到应用程序中,使用 watcher 构建它们并使用 watcher 本身为应用程序提供服务,这样对依赖模块的任何更改都会(我认为)自动分发到主应用程序。

所以应用程序的package.json 包含如下依赖项:

...
"module1": "file:../module1",
"module2": "file:../module2",
...

这些依赖项意味着可以随时删除,或者通常根据我们的需要进行组合,应用程序只需重新编译,一切都应该正常工作。

我现在正试图了解如何在应用程序中动态加载和激活模块,因为我不能像这样使用动态导入:

import(/* webpackMode: "eager" */ `module1`).then(src => {
    src.default.boot();
    resolve();
});

因为基本上我不知道'module1','module2'等......

在 OOP 世界中,我只会使用依赖注入检索实现特定接口的类,但在 JS/TS 中我不确定它是否可行。

有办法做到这一点吗?

【问题讨论】:

  • 在构建时而不是在运行时限制模块集的原因是什么?我的意思是您可以轻松地让 webpack 将每个模块构建到自己的块中,然后在运行时决定加载哪个模块(基于一些 json 配置)...
  • @MichalLevý 这正是我不知道该怎么做。尝试使用动态导入,正如您在问题上看到的那样,我仍然有问题对“module1”、“module2”等字符串进行硬编码......我想要这样的东西:“导入所有暴露这个的模块界面”。或者 JS 中的等价物。

标签: javascript vue.js npm vue-cli-3 modularity


【解决方案1】:

package.json 混在一起对我来说听起来不是一个好主意 - 无法扩展。我会做什么:

  1. 将所有可用的“模块”保留在package.json
  2. 使用所有可用配置创建单独的js 文件(或package.json 中的自己的prop)(例如,针对不同的客户端)
module.exports = {
   'default': ['module1', 'module2', 'module3'],
   'clientA': ['module1', 'module2', 'module4'],
   'clientB': ['module2', 'module3', 'module4']
}
  1. 进入 VueCLI 构建过程 - 我发现的最佳示例是 here 并创建 js 文件,该文件将在每次构建(或“服务”)之前运行并使用简单模板(例如 lodash)生成新的 js 文件,该文件将根据某些 ENV 变量的值引导配置的模块。请参阅以下(伪)代码(请记住,它在构建期间在节点内运行):
const fs = require('fs')
const _ = require('lodash')
const modulesConfig = require(`your module config js`)

const configurationName = process.env.MY_APP_CONFIGURATION ?? 'default'
const modules = modulesConfig[configurationName]

const template = fs.loadFileSync('name of template file')
const templateCompiled = _.template(template)
const generatedJS = templateCompiled({ `modules`: modules })
fs.writeFileSync('bootModules.js', generatedJS)
  1. bootModules.js 编写模板。最简单的是:
<% _.forEach(modules , function(module) { %>import '<%= module %>' as <%= module %><% }); %>;
  1. bootModules.js 导入到您的应用中
  2. 使用 MY_APP_CONFIGURATION ENV 变量来切换所需的模块配置 - 不仅在开发期间有效,而且您还可以设置不同的 CI 流程,针对相同的 repo 使用不同的 MY_APP_CONFIGURATION

这样您就可以将所有配置集中在一个地方,您无需在每次构建之前更改package.json,您可以通过简单的机制在不同的模块配置之间切换,并且每个构建(捆绑包)仅包含所需的模块。. ..

【讨论】:

    【解决方案2】:

    在 OOP 世界中,我只会使用依赖注入检索实现特定接口的类,但在 JS/TS 中我不确定它是否可行。

    为什么不呢?

    不仅如此,使用 JS/TS,您不仅可以使用实现特定接口的类:您只需定义模块的接口(即module.exports)并在库条目中尊重它(vue build库)。

    编辑:阅读 cmets 可能我理解了这个请求。

    每个模块都应遵循以下接口(在作为 vue 库入口的文件中)

    export function isMyAppModule() {
      return true;
    }
    
    export function myAppInit() {
      return { /* what you need to export */ };
    }
    

    比在您的应用中:

    require("./package.json").dependencies.forEach(name => {
      const module = require(name);
    
      if(! module.isMyAppModule || module.isMyAppModule() !== true) return;
    
      const { /* the refs you need */ } = module.myAppInit();
    
      // use your refs as you need
    });
    

    【讨论】:

    • 我仍然需要对字符串进行硬编码以识别模块。我的问题是我想找到一种方法,根据应用程序的 package.json 引用的内容,使这部分自动化。
    • 不用等待 @tanathos ,如果模块列表在 package.json 中,则意味着您在构建时就知道了。我在想您的应用程序开始时会读取(示例)modules 目录以加载运行时模块,然后每个模块很容易将文件名计算为require。如果模块列表在package.json 文件中,那么您正在考虑哪种安装方法(显然与标准构建过程不同,否则您不会问这个问题)?你能澄清一下你的情况吗?
    • 阅读各种 cmets @tanathos 可能我理解了这个要求;你能看看我更新的答案吗?
    • 当然。需要为不同的客户端安装该应用程序,每个客户端都需要不同的模块。所以我的想法是只更改 package.json(现在模块是本地 npm 包,但将来它们将分发到内部 npm 存储库),使应用程序的代码保持不变。模块将始终公开一个 boot() 方法,我将在模块本身加载完成后调用该方法。
    • 我会尽快试一试@Daniele Ricci
    猜你喜欢
    • 1970-01-01
    • 2019-01-26
    • 1970-01-01
    • 2019-09-11
    • 2011-12-26
    • 1970-01-01
    • 1970-01-01
    • 2019-06-01
    • 2020-02-16
    相关资源
    最近更新 更多