如何使用Web组件缓解JavaScript库膨胀

How is JavaScript library bloat mitigated with Web Components?

本文关键字:JavaScript 膨胀 何使用 Web 组件      更新时间:2024-01-29

作为一个多年来一直试图通过创建(HTML)组件来帮助内容作者开发和维护大型网站的人,我真的很高兴看到web组件在w3c、谷歌和mozilla获得跟踪。但在我看来,规范中并没有针对javascript库膨胀的措施。

假设我开发了与underscore.js有依赖关系的组件A,并希望使用与lodash.js版本1.*有依赖关系等组件BC
我看不出有任何方法可以标记依赖项和库版本。当我们谈论拥有多个团队和利益相关者的网站时,这可能会导致巨大的图书馆膨胀。

目前的解决方案是在全球范围内对整个网站的批发客户端框架进行标准化。当您在不同的服务器端框架(如LifeRay(java)、EpiServer(.net)、Django(python)等)中投入大量资源时,这是很困难的。每个框架都有首选的客户端库。

我认为web组件是将服务器端框架与客户端代码解耦的一种手段,但客户端依赖关系处理的遗漏令人担忧。

它是否在规范中,而我错过了它,或者是否有我不知道的缓解这个问题的策略?

【提到的库只是示例。这个问题与框架、库和服务器端语言无关】

更新感谢大家的回答。我很惊讶没有人提到Mozilla X-Tag或谷歌聚合物,这是最近的大肆宣传。我完全赞同影子DOM、作用域样式、自定义元素等概念,但我看不到任何关于如何处理JavaScript依赖关系的内容。正如@Daniel Baulig正确地编写HTML一样,Imports根本没有提到JavaScript。我承认这个问题几乎不可能回答。然而,当Daniel Bailig提到ES6模块时,我认为他是最接近的。我个人认为,我们将在ES6模块和require.js之间找到一个可持续的解决方案。

这也是一个困扰我一段时间的问题,尤其是在面临维护许多开发人员接触过的代码。你经常遇到中包含的多个JS库(其中一些库基本上做相同的事情)一种解决方案,更不用说使用的同一框架的不同版本了在一个解决方案中。

或者更确切地说,我正在寻找的"一个"潜在解决方案是创建一种调解人框架。

其基本思想是"针对"中介进行编码(从不直接访问/使用js库,而是通过中介使用它),从而从本质上使代码不可知(与"父"库解耦),并在下面包含中介实现。

这不会解决我/我们的直接问题或膨胀,但无论我写什么web组件将能够跨框架运行。

以下是概念的一点证明:Tagger Mediator POC

例如,包括以下中介:

JQuery(1.9.1)

Mootools(1.4.5)

原型(1.7.1.0)

YUI(3.10.3)

Dojo(1.9.1)

Ext(3.4.0)

Zepto(1.0)

但没有什么能阻止任何人创建自己的调解框架,该框架"抽象"了其他介质,嗯,所以可能也会导致膨胀(使事情变得更糟而不是更好)。

我想你应该制定自己的标准;)

在当前的W3C规范中,似乎没有特定的方法来定义依赖项,甚至对其进行版本化。组件不应使用任何库/依赖项,或与它们紧密耦合。

这意味着每个主要库可能都会带来自己的一组组件,这些组件期望这些库已经加载。

也许ES6模块在这方面提供了帮助,但话说回来,它们目前也没有提供任何版本控制机制。

所有这些都表明,规范还处于相当早期的阶段,可能会发生变化。向规范作者提出依赖关系问题可能会将该主题提上桌面,甚至可能在规范固化之前解决。最终,无论平台和语言如何,在单个代码库中使用不同的库来执行相同的任务一直是软件开发中的一个问题,并将继续是一个问题。您只需要就在代码库中使用哪些框架/库达成一致,即使这意味着将您锁定在其他框架/库之外。

此外,如果您现在已经对为web开发独立组件感兴趣,您可能需要查看React库

我自己也对web组件有过类似的疑虑,但后来我开始使用第三方React组件。他们也有同样的问题,因为他们带来了自己所有的依赖关系。但更糟糕的是,由于React不喜欢与其他版本的React共存,React组件必须将React列为对等依赖项。这意味着您必须使用与第三方组件相同版本的React。

因此,我的结论是,这实际上是web组件的功能!每个自定义元素都可以有自己的依赖项,完全独立于应用程序的其他部分。我认为第三方组件是Web组件的主要用例;其中消费者希望能够使用具有尽可能小的摩擦的部件。当然会有一些重复的代码,但组件消费者不需要知道或关心。我认为组件作者将通过在组件中使用较小的模块来处理这一问题。例如,他们可以使用Snabdom之类的东西来代替React。

如果您正在用您控制的web组件构建整个应用程序,您仍然可以使用Browserify或WebPack等捆绑工具来管理您的依赖关系。这将允许您覆盖某些模块,以便您甚至可以强制第三方组件使用与应用程序其他部分相同的版本(请参阅:https://www.npmjs.com/package/aliasify)。这可能会破坏一些模块,但这就是生活。

IMHO您所描述的问题始终存在于前端,与web组件没有直接关系,但web组件确实使情况变得更糟

假设你依赖于组件A和B,它们都依赖于lodash,除非A和B都将lodash设置为对等依赖关系(这使它们看起来像大多数jquery插件那样的插件),否则你可以包含一个lodash副本,否则,A和B对你来说就像黑盒。尽管bundler可能会进行一些构建后处理,如树抖动/死代码消除,以帮助缓解这种情况,但这永远不可能100%奏效,例如A和B都依赖于两个不同版本的lodash。

为什么web组件会让情况变得更糟?WC的目标是提供一种与框架无关的方式来重用UI组件,比如说,如果我使用vue和一堆vue组件,我的捆绑js最终会包含这些组件,而单个vue运行时cuz-vue是这些组件中的对等dep。现在,如果我使用WC和一堆WC组件,我很可能会得到一个更大的捆绑js。例如,可以用一个简单的按钮创建一个简单发光元素WC组件,然后构建项目,看看bundle js是如何大得多的。有些框架可能会采取不同的方法,例如,vue仍然要求您添加vue作为外部框架来运行基于vue的WC,如果您将vue切换到WC,则不会节省一个字节。此外,共享样式是WC中减少束大小的另一个问题。

我的公司使用bootstrap和react,我们的应用程序捆绑了1个bootstrap、1个react和许多外部react组件,这些组件最终已经是3.5mb js/css,我怀疑我们能否在3.5mb中的WC中做同样的事情。

我从未听说过标准化javascript框架。然而,有了HTML5,早期版本的HTML中曾经需要javascript框架的一些部分现在已经作为标准功能添加到HTML5中(例如标签<canvas>、圆角边框、阴影效果等),这是解决您所提到的问题的第一步。

老实说,我不认为这会发生,或者至少在不久的将来不会发生。所有这些框架都有自己的目的、优点和缺点。除非你能构建一个巨大的javascript库,以某种方式将它们结合在一起,否则整个网络上总是会使用不同的库。

另一个重要点是不同库之间的竞争,这使得javascript市场不断增长,并提出了新的创新。如果你想制作一个所有人都能使用的标准javascript库,你也可以消除不同框架之间的竞争,从而保持创新和进步。

使用web组件并不一定能缓解库的膨胀,事实上,您可以通过使用自定义构建来精简库(链接适用于Lo Dash,但其他流行的库也有构建步骤)。这里的某种自动化可能非常有用,即一个可以扫描源代码并根据您使用的函数生成自定义构建的工具。

我认为随着npm的兴起,这样的图书馆变得越来越不重要了。Lo Dash就是一个很好的例子,因为它的功能是作为独立模块在npm上发布的,但你也有类似Sizzle的东西,jQuery使用的CSS选择器引擎。如果你仔细观察,你会发现很多插件都是在没有jQuery作为依赖项的情况下编写的,或者它在项目的路线图中删除了依赖项,或者有人已经将项目分叉以删除对另一个库的依赖项。例如,Exoskeley,一个完全下划线&jQuery免费版的Backbone。

我不认为我们会看到另一个流行的实用程序库,如jQuery或下划线;使用npm,我们可以简单地选择我们想要的模块,以及依赖于这些大型库的fork项目,只使用他们需要的模块或完全无依赖的版本来重写它们。

有了AMD和requirejs,这已经成为现实。您可以定义一些源代码的依赖关系;在代码中,您可以声明该组件只需要例如microjax,而不需要整个jQuery库:,而不是链接到单片库

define(['microajax'], function(microAjax) {
    microAjax("/resource/url", function (res) {
      alert (res);
    });
});

查看microjs网站,了解更多像这样的帮助程序库。

就web组件而言,我希望这些组件的作者以这样的方式编写组件,这样他们就不需要像jQuery这样可以做任何事情的大型库。如果他们这样做了,我也希望我能用叉子把它们叉起来,自己把所有不必要的部分都剪掉。:-)

编辑:这篇24ways文章很好地介绍了原生JS功能的兴起,这些功能最终将取代jQuery。值得一提的是,jQuery是在JavaScript实现大不相同的时候编写的;但随着标准化程度的提高和API的一致性的提高,对原生功能包装器的需求有所减少。例如,现在我们有了querySelectorAll:

// jQuery
$('.counter').each(function (index) {
  $(this).text(index + 1);
});
// Vanilla JavaScript
var counters = document.querySelectorAll('.counter');
[].slice.call(counters).forEach(function (elem, index) {
  elem.textContent = index + 1;
});

我们只是尝试将我们的web组件javascript保持在最低限度,使其占用空间非常小。因此,我们不需要使用下划线,而只需要使用本机的ES6等价物(考虑到现有的use web组件,这并不是一个很大的扩展)。

然后,我们用一个html文件、一个css文件和一个js文件封装我们的web组件。它们实际上可以由多个文件组成,但我们的构建过程会处理好这一点。我们的javascript文件与Browserify捆绑在一起,允许我们使用npm模块,同时将它们很好地打包在我们的web应用程序中。

这提供了非常好的微小但黑框的web组件,它们可以共享公共模块而不会发生任何冲突,也不必担心AMD的开销,只需简单的commonjs即可实现所有功能。

如果我们确实需要在多个组件之间捆绑一个沉重的库,我们要么将该库设置为外部库,然后将其传入,将其包含在应用程序中(这就是使用backbone.js的方法,因为backbone.jsp使用instanceof而不是duck类型,使得多个backbone..js实例无法相互通信),或者让web组件使用像wzrd.in这样的浏览cdn服务器将其捆绑在一起——然而,对于父web应用程序来说,处理大型dep要好得多,就好像web组件使用不同的cdn一样,那么你就有问题了。

假设我开发了对underscore.js有依赖关系的组件A,并希望使用对lodash.js 1.*版本有依赖性的组件B和C,等等。我看不出有任何方法可以标记依赖项和库版本。

1999年有一个旧的ECMA规范(ECMA-290),它指定了一个组件机制,其中包括依赖项和库版本的元素和属性:

<component
name="com.mycompany.selectnav"
displayname="SelectNav"
src="selectnav.js"
env="client"
hint="Navigational Select List"
version="1.0"
needsform="yes">
</component>

对于依赖项,请使用customizer元素。对于版本控制,请使用meta元素。例如:

<customizer type="ecmascript" url="http://com.com/foo">
  <meta name="version" value="1.1b"/>
</customizer>

用于现代实现的JSON编码如下所示:

{
"component":
 {
 "name":"com.mycompany.selectnav",
 "displayname":"SelectNav",
 "src":"selectnav.js",
 "env":"client",
 "hint":"Navigational Select List",
 "version":"1.0",
 "needsform":"yes",
 "customizer":
   {
   "type":"ecmascript",
   "url":"http://com.com/foo",
   "meta":
     {
     "name":"version",
     "value":"1.1b"
     }
   }
 }
}

与CDN API、API检查器和按需脚本加载器集成的生命周期回调将是另一个选项:

createdCallback => check API URL => createElement for dependent script tags => onload event for dependent script tags => appendChild for dependent script tags

以下项目中的HTML子集是一个已经尝试过多次的解决方案:

  • GitHub-ampproject/amphtml:amphtml源代码、示例和文档。有关详细信息,请参阅下文。

  • XHTML™基本1.1-第二版

  • X标记★Web组件

  • 覆盖范围ext

  • 如何用摇树清理您的JavaScript构建

  • 消除未使用的CSS

  • 使用webpack和源地图浏览器分析并最小化客户端捆绑包的大小

  • polyfill.io

参考

  • ECMA-290:ECMAScript组件规范(PDF)

  • 基于浏览器的分布式进化计算:性能和伸缩行为(ACM)

  • 自定义元素回调队列

  • Web组件自定义元素规范:自定义元素类型

  • CommonJS模块的懒惰评估

  • 小型模块的成本

  • Yepnope从带有回退的CDN加载(示例)

  • 为CDN回退提供本地JS和CSS资源·Edd Mann

  • 插件优点和缺点

  • Caja游乐场