鉴于ES2015,依赖注入和库抽象,2016年我的理想模块应该是什么样的?

时间:2016-04-30 22:28:10

标签: javascript dependency-injection module ecmascript-6

如果没有,首先,我会全力以赴编写我的所有模块,如

import A from './a.js';

var B = function(){
  //use A
};

export default B;

然后使用编译器将其构建为某种浏览器或服务器格式。

我上面的一个问题是./a.jsimport的明确说明。

我理解为什么规范以这种方式 1 ,以支持static analysis。但是有两个非常实际的原因可以解释为什么模块的文件名路径都很麻烦。

  1. 作为already raised here,当频繁地从项目回收模块时,您很可能无法在项目树中为该资源维护一致的路径。将import myModule from './../../vendor/lib/dist/mod.js'之类的导入调用绑定到模块的代码中并不能完全让我感到前瞻性。
  2. 除路径本身外,指定文件名也会让您失望。像这样的东西似乎是无辜的:

    import $ from 'vendor/jquery.js'

    但是,当我想使用Zepto代替jQuery的那一天呢?我发现抽象,尤其是供应商库的抽象,在处理大型代码库,自以为是的开发人员和不断变化的JavaScript生态系统时非常有用。我今天可能想要导入React作为我的组件库,但明天呢?此外,如果我将在客户端和服务器上使用相同的模块,但我需要不同版本的依赖库,该怎么办?

  3. 我在团队中坚持强大(但清晰且一致)的抽象。通常,抽象采取某种命名空间的形式。我幻想一下这个:

    //BAD: Bakes React into my component modules
    import ComponentLib from './React.js';
    
    //GOOD: Leaves me free to use any React-like library
    import ComponentLib from 'vendor.lib.component';
    

    vendor.lib.component以类似Java的方式在某处注册过。

    请注意unlike in this question,我的目标不是动态控制我的导入。我不想要运行时灵活性,我希望构建时灵活性。我应该能够为另一个,或者模拟,或者在某个特定环境中工作的东西提出一个依赖框架,而不必担心我的模块正在调用哪些依赖项,或者尝试复制一些疯狂的目录我追求的每一个构建产品的树。

    类似的问题导致了一个利用System specification的库的建议,如SystemJS。然后,您可以使用类似jspm的内容来引入模块映射以获取抽象。但是,在我这样做的那一刻,我正在以不同的方式编写所有模块:

    System.import('a', function(A){
      //use 'A'
    });
    

    这突然间是未来吗?如果是这样,为什么我不继续使用AMD?如果我要回到使用异步外观的加载器API,为什么还要烦扰ES2015模块并运行转换器呢?

    更加引人注目,我在ES2017 spec中没有看到太多或任何提及解决模块加载器API标准的问题。

    编辑:修订问题以符合非意见回答的标准

    考虑到上述所有情况,我问社区 - 如何编写一个(i)遵守ES2015标准的JavaScript模块,(ii)不通过其文件名或路径引用依赖模块,以及(iii)不依赖于广泛的中间工具/配置,这将使得与多个团队共享该模块的程度过高。

    -

    注1 正如评论中提到的@zeroflagL一样,规范没有明确声明应该将模块指定为路径,只是一个字符串(参见 ModuleSpecifier - http://www.ecma-international.org/ecma-262/6.0/#table-41)。但是,还有一个明确的指令来说明循环引用,这意味着某种静态分析(http://www.ecma-international.org/ecma-262/6.0/#sec-imports),文件路径似乎是这一点的首选参考上下文。因此,我们不能责怪这里的僵化,反之亦然。然后,我们可能有责任开发更强大的import / ModuleSpecifier 实现,从而形成次要标准。

3 个答案:

答案 0 :(得分:1)

对我而言,这似乎是JS社区最大的未解决问题之一。围绕依赖关系管理和依赖注入没有最佳实践(至少据我所知)。

这个帖子有一个很长的讨论:Do I need dependency injection in NodeJS, or how to deal with ...?,但大多数解决方案似乎只适用于特定情况,并且需要改变你以某种方式编写模块的方式。最令人震惊的是,很多答案都认为你甚至不需要DI。

我自己的问题解决方案是minimal DI framework,它允许您定义模块一次,它将为您提供适当的依赖关系。

答案 1 :(得分:0)

我知道您要求的解决方案可以在JS编译器的情况下专门使用,但由于您要求最佳实践,我觉得我们必须考虑到JavaScript依赖的完整游戏领域管理发生,包括浏览器和Node.js等不同的主机环境,Webpack,Browserify和jspm等前端打包系统,以及HTML和HTTP / HTTP2等相邻技术。

ES模块的历史

有一个原因是你不会在任何ECMA规范中找到加载器API:当ECMA2015规范的截止日期到来时,依赖性解析没有最终确定,并且决定ECMA规范只描述模块语法,并且主机环境(例如browser,node.js,JS编译器/转换程序)将负责通过名为HostResolveImportedModule的钩子通过其说明符解析模块,您可以在ECMA2015中找到它。 }和ECMA2017规格。

ES2015模块模式背后的语义规范现在掌握在WHATWG手中。两项值得注意的发展是他们努力的成果:

  1. 他们have determined HTML中的模块可以用<script type="module">表示。
  2. 有一个draft for a loader specification,它将考虑不同的主机环境和前端打包系统。这听起来像最终有助于回答你的问题的规范,但不幸的是它还远没有完成,并且已经有一段时间没有更新了。
  3. 当前的实施

    浏览器

    通过添加<script type="module">标记,在浏览器中简化了ES6模块的实现似乎已经到位了。它现在的工作方式是不考虑性能(请参阅thisthis评论)。此外,它不支持任何前端包装系统。很明显,模块的概念必须扩展才能在生产网站中使用,因此浏览器供应商并没有急于在浏览器中实现它。

    Node.js的

    Node.js是一个完全不同的故事,因为它从一开始就实现了CommonJS样式模块。目前,不存在对ES2015模块的支持。已经提出了两个单独的建议,将Node.js中的ES6模块与CommonJS模块结合在一起。 This blog post非常详细地讨论了这些建议。

    编译器和包装系统

    <强>结论

    因此,当您要求最佳方式编写模块时,您会发现这非常困难。提出一个可与所有可能的主机环境兼容的解决方案是可取的,因为:

    • 您可能希望在不同环境之间共享代码。
    • 某些类开发人员(例如前端开发人员)可能会接受ES2015模块,而其他人(例如Node.js开发人员)会坚持使用其他解决方案(例如CommonJS模块)。这将进一步降低跨环境代码的可能性。

    但是你会从上面看到,不同的环境有不同的需求。从这个意义上说,对你的问题的最佳答案可能是:目前没有编写模块的最佳方式来涵盖你的抽象问题。

    <强>解决方案

    但是,如果我现在正在编写编译为JS的ES2015模块的任务,我将远离相对路径并始终使用项目根目录中的绝对路径作为模块的标识符,我不会这样做。看来有问题。 Java实际上以完全相同的方式镜像名称空间及其目录结构。我会使用Webpack或Babel将我的源代码编译为可在当今JS环境中运行的代码。

    至于你的另一个问题,如果我希望能够替换供应商库,我可能会创建一个模块,将供应商库别名化为我将在内部使用的名称。有点像:

    //  /lib/libs.js
    import jQuery from 'vendor/jquery.js'
    export const $ = jQuery;
    

    然后所有其他模块将从lib / libs.js导入$,您可以通过在一个地方更改引用来切换供应商库。

答案 2 :(得分:-1)

如果您想遵循最佳做法,请按照AirBnb JavaScript样式指南进行操作。在我看来,最好和最完整的JavaScript样式指南

https://github.com/airbnb/javascript#classes--constructors

导入

这对于重用模块看起来很糟糕:import myModule from './../../vendor/lib/dist/mod.js'

在NPM上发布您的模块(也可以是私有或自托管的NPM)并像import myModule from 'my-module';那样导入

最终将NODE_PATH设置为根文件夹,并从根目录中引用模块。

在package.json

'start': 'NODE_PATH=. node index.js'

// in Windows
'start': 'NODE_PATH=. && node index.js'

现在导入:

import myModule from 'vendor/lib/dist/mod.js'

<强>变量

var不属于ES6。使用:

  • constant - 当变量的值不会改变时,也会对象和导入。即使对象的参数发生了变化,它仍然是一个常量。

  • let - 当变量的值发生变化时,即for(let = i; i < 100; i++)

  • 根据我自己的经验,始终将const设为默认值,如果let抱怨,则只更改为ESLint(顺便说一句。使用ESLint http://eslint.org/

<强>类

现在有一种在JavaScript中定义类的正确方法

class B {
  constructor() {
  }
  doSomething() {
  }
}

您的示例已更新:

import A from './a';

Class B {
  constructor() {
  }
  doSomething() {
  }
};

export default B;

如果你想扩展A:

import A from './a';

Class B extends A{
  constructor(argumentA, argumentB) {
    super(argumentA, argumentB);
    this.paramA = argumentA;
  }
};

export default B;

提示

  • 将Webpack与NPM一起用作构建工具。不要使用Gulp或Grunt
  • 使用Babel来转换代码(JSX加载器可能还不够)
  • 学习根本不使用jQuery,而是选择适合您需要从NPM完成的工作的polyfill和工具
  • github上有大量精心编写的样板回购,所以从最好的窃取。以下是我正在使用的一些React。

我的答案实质上是:

您要求的模式/库是AirBnb JavaScript样式指南而忘记了jQuery