Three.js: 继续支持 JS 库和 ES6 JSM 库

创建于 2020-10-05  ·  51评论  ·  资料来源: mrdoob/three.js

您的功能请求是否与问题有关?

我相信同时支持模块和经典静态文件导入是良好的库卫生。这使库可供更广泛的开发人员访问,并允许开发人员使用他们喜欢的样式。

就我个人而言,我真的尽量避免使用模块,我喜欢包含经典简单 JS 文件的静态文件项目。 也许我只是个怪人,但我真的很讨厌框架不必要地将您从事物中抽象出来,以及它们重新发明轮子或以其他方式黑盒您的程度。 我知道你可以在没有任何框架的情况下使用模块,但是模块包含不如传统的 JS 包含那么直观,当我尝试在静态文件设置中使用它们时,它们经常让我失望。

使用 ES6 模块并不适合每个部署,尽管它们肯定是一个受欢迎的补充。 我教了许多新程序员threejs,因为我喜欢这个库和IMO,这是进入编程的一种很棒且令人满意的方式。 教人们基本的普通 CSS/JS/HTML 会容易得多,而不必同时将整个 node/npm + 框架堆栈推到他们的喉咙里。 静态库更易于使用/理解,并保持较低的进入门槛。

从风格上讲,我也更喜欢用附加功能重载 THREE,而不是添加自由浮动的新命名函数。 虽然这显然是偏好。

描述您想要的解决方案

也许在获得更多关于为什么决定只转换到模块的信息后,我可以更好地回答这个问题,但我会尝试一下。

文档指出 ES6 模块可能无法在所有情况下工作,并且对于那些建议使用打包器(例如 browserify/rollup/webpack/parcel 等)的情况......

我的解决方案是让一个自动 ES6 捆绑器脚本通过/examples/jsm中的模块生成/examples/js非模块版本。 这样开发者不再需要担心在两个地方进行更改,并且可以继续享受使用 JS 非模块版本和全局 var 导入样式,如果他们喜欢的话。

这种 JS 非模块文件的自动生成可以作为构建过程的一部分来完成,也可以是 package.json 中的一个命令,有人可以手动运行。 虽然我会选择自动生成。

创建这种自动化或以其他方式维护这个库的 JS 非模块版本是我可以贡献我的时间的事情。 如果只跳到 ES6 背后的原因不仅仅是消除了手动更新同一事物的两个并行版本的需要,那么我也很乐意讨论其他解决方案来解决这些问题。

描述您考虑过的替代方案

另一个明显的考虑是保持原样并继续维护所有库的 JS 和 JSM 版本。 尽管考虑到这些已被弃用的公告,但我觉得这不太可能。 但是,如果我们决定走这条路,我很乐意负责确保 JS 库手动与 JSM 同行保持同步。

附加上下文

非常喜欢这个库以及所有贡献代码或报告/讨论问题的人。

Suggestion

最有用的评论

感谢大家分享利弊。 总是很高兴分享这些以确保我们做出明智的决定。

这是我今年花费了一些脑力周期的事情,我什至询问了浏览器供应商他们的优先事项,以便我能够提前计划。

我同意 ES6 模块是未来,但是在没有导入映射的情​​况下使用它们进行开发可能会导致巨大的头痛并完全破坏你的流程。 当我们决定弃用examples/js时,我希望 Import maps 会有更大的吸引力,但似乎它目前不是浏览器的优先事项。

因此,我决定暂停examples/js文件夹的弃用,直到浏览器实现导入映射。 我讨厌强迫新手学习 polyfills 或 bundlers 来渲染他们的第一个立方体。

我得出了与@Bug-Reaper 相同的结论。 今天,我正在研究创建一个脚本,该脚本从examples/jsm文件中构建examples/js

所有51条评论

教人们基本的普通 CSS/JS/HTML 会容易得多,而不必同时将整个 node/npm + 框架堆栈推到他们的喉咙里。 静态库在这里保持低门槛。

只是为了澄清这个项目中维护的示例 js 模块不需要使用 node、npm 或任何构建框架。 它们可以用作静态服务文件,就像旧的全局导入一样。 它们只需要 es6 导入语法即可使用,但这适用于所有现代浏览器。

只是为了澄清这个项目中维护的示例 js 模块不需要使用 node、npm 或任何构建框架。 它们可以用作静态服务文件,就像旧的全局导入一样。 它们只需要 es6 导入语法即可使用,但这适用于所有现代浏览器。

谢谢你的澄清! 这确实是一个好点!
我相信:

<script type="module">

  import { OrbitControls } from 'https://unpkg.com/three@<VERSION>/examples/jsm/controls/OrbitControls.js';

  const controls = new OrbitControls();

</script>
````
is perhaps less intuitive and harder to understand for newcomers than: 

等一下……我们还有:

<script src="path/to/local/build/three.js"></script>

反对:

<script type=module src="path/to/local/build/three.module.js"></script>

第一个是静态脚本,可以按照旧的全局方式在某人的 html 中使用......对吗? 在过渡到 ES6 之后,你不能再做的事情是什么?

如果我理解正确,计划是除了“/build/three.module.js”之外还包括一个“/build/three.js”。

是的。 但是,这种方法是否有意义是值得怀疑的。 当examples/js被删除时,只剩下几个用例three.jsthree.min.js仍然有用。

删除three.jsthree.min.js实际上是有益的,因为它允许我们更改npm包的main入口点,请参阅 #19575。

如果我们可以轻松做到这一点,我相信继续支持 /examples/js 是有意义的,通过 ES6 捆绑器脚本自动生成它们作为构建过程的一部分。

这个想法是将examples/jsm移动到更现代的 JavaScript 语言功能,如类。 由于examples/js仍应适用于旧版浏览器,因此有必要配置具有代码转换功能的新(示例)构建。 此外,我们仍然会保留重复的代码库( examples/js vs examples/jsm ),在我看来这是一个不好的方法。 它使维护更加复杂。

如果需要,我相信用户必须处理 ES6 到 ES5 的转换。 代码缩小或其他与构建相关的任务也是如此。

我相信你是对的。 如果我理解正确,计划是除了“ /build/three.module.js ”之外还包括一个“ /build/three.js

真的

来自/examples文件夹的填充的问题是,当您使用/build/three.js $ 时,您需要使用来自/examples/js的文件,而当您使用/build/three.module.js时,您需要使用来自/examples/jsm的文件/build/three.module.js ,也就是保持加载方法的一致性。

为什么? 因为当使用模块导入时,主THREE对象不再是一个普通的 js 对象THREE = {}而是一个内部浏览器的模块对象,它是密封的(不可扩展),因此,来自/examples/js的文件THREE对象中写入新属性失败。

所以你不能混合使用import * as THREE from '/build/three.module.jsTHREE.WhateverExample = function() ...

一种可能的方法是将导入的 lib 的名称更改为THREE以外的任何名称,然后重新创建一个普通的 js THREE全局对象,以便在其中编写示例...

这通常是问题

传统的 JS 包括

这会污染全局空间命名,并且因为您无法将名称修改到加载的文件中,您可能会收到类似的错误......
另一方面,对于模块,用户在导入过程中获得了命名的权力,而选择结果名称的不再是 lib 的作者......

前任:

<script>
// a script you can't modify already use the name THREE
var THREE = document.getElementById('div-nb-3')
</script>

<script type="module">
import * as foo from '/build/three.module.js'

THREE.appendChild( new foo.WebGLRenderer().domElement )
</script>

@Mugen87你是 100% 正确的。 如果我们放弃/examples/js ,我们不妨放弃three.js 和three.min.js ,因为它们本质上与任何附加模块都不兼容。 他们的用例将是利基市场,这几乎肯定会造成混乱。

@devingfx您说得对,模块具有优势并消除了潜在的全局名称冲突。 在多年的使用中,我从未与三个全局变量发生任何冲突,我认为这是一种不太可能发生的情况,但您的观点在技术上是正确的。

在我看来,这是一个不好的方法。 它使维护更加复杂。

如果需要,我相信用户必须处理 ES6 到 ES5 的转换。 代码缩小或其他与构建相关的任务也是如此。

@Mugen87维护一个除了模块之外还使用全局变量的传统 js 包含真的那么糟糕吗? 许多库都支持这两者,据我所知,传统的 JS 版本通常与模块版本对应物一样普遍使用。 两者都有优点/缺点,其中一些归结为偏好。 让开发人员选择在非模块上下文中使用库不是很好吗?

我愿意负责创建/测试必要的代码转换功能,以从three.module.js 和 /examples/jsm自动生成three.min.js、three.js 和 /examples/js 。 在精通转译工作流程后,它可能需要一些最少的维护,但它!= 维护两个并行版本。 在大多数情况下,只需要在模块文件上更新代码,并且只是偶尔需要修复一些编译错误。

我有足够多的项目依赖于传统的全局语法,其中包括我将要做的工作是自动编译模块。 我认为至少我们可以在 package.json 中包含一个命令并将其称为“legacy-build”,它将模块转换为与原始模块类似的three.min.js、three.js 和 /examples/js现在的文件。 这些文件甚至不必提交到 repo 或默认创建。 我们也可以警告他们是为了遗留支持,他们不能保证工作,我们建议使用模块等等......

实际上,尽管我认为将它们保留在 repo 中并简单地通过编译时自动生成它们更有意义。

package.json 中的一个命令并将其称为“legacy-build”,它会转换模块

似乎有道理。 babel最近不是合并了吗? 所以我认为这可能是可行的

编辑:澄清一下,除了想要说构建的用户之外,不要说任何人都可以运行新命令

维护一个除了模块之外还使用全局变量的传统 js 包含真的那么糟糕吗?

我认为维护它的复杂性被低估了。 不幸的是,我认为在项目中设置示例的方式并不那么简单。

我们以 GLTFLoader 为例。 现在所有的 GLTFLoader 都包含在一个文件中,这使得它很容易包含在 HTML 文件的顶部。 模块的好处之一是一些较大的文件可以分解成单独的文件,GLTFLoader 可以将这些文件作为依赖项导入。 一旦 GLTFLoader 依赖于其中一些共享的四个外部文件,构建的全局脚本应该包括什么? 构建的全局脚本的用户现在是否必须单独包含所有这些示例 js 文件? 或者将一些文件捆绑在一起,这需要手动维护一个可以捆绑在一起的文件列表,哪些不是?

我认为唯一真正设置并忘记的情况是将所有示例 js 文件捆绑到一个单一的整体 blob 中,我认为这是不合理的。 我认为使用这些解决方案中的任何一个都会有一些其他的发布和文档开销。

也许有更好的方法来做到这一点,但是当我尝试制作一个保留向后兼容性或至少与现有 js 文件保持一致结构的汇总构建时,这些就是我遇到的问题。

如果我理解正确,计划是除了“/build/three.module.js”之外还包括一个“/build/three.js”。

是的。 但是,这种方法是否有意义是值得怀疑的。
删除 examples/js 后,只剩下少数几个用例,其中 three.js 和 three.min.js 仍然有用。

@Mugen87 @mrdoob

迈克尔,
事实上,将“three.min.js”至少保留 2 年以上是必须的。
不是因为我所有的样本都是基于它的。
但是因为成千上万的文件和谷歌的顶级狗都是基于它的!
示例: https ://www.google.com/search?source=hp&q=webgl+benchmark

另一方面,在我看来,“three.min.js”意味着更快的开发和测试。
更不用说它可以离线工作并且您不需要本地主机。
只需将所有文件放在某个文件夹中,使用 Firefox 并双击 HTML 文件。
我一直喜欢它的发展!

里卡多也应该考虑所有这些。
干杯

three.jsthree.min.js的删除是可以在examples/js消失时讨论和计划的。 当您不能再从examples/js导入文件时,强调它们失去的意义对我来说很重要。

我认为维护它的复杂性被低估了。 不幸的是,我认为在项目中设置示例的方式并不那么简单。

我真的很喜欢你继续提出的观点。 捆绑中绝对有不可预见的复杂性,嵌套模块的例子就是一个很好的例子。 就您的观点而言,我认为我们可以在那个时候就如何处理捆绑嵌套模块做出明智的决定。 我并不是说捆绑脚本将是一个设置它并忘记它的情况,只是它会降低维护。

如果到了难以维护的时候,我们总是可以放弃它,但我认为考虑到我们还没有遇到的问题而放弃尝试是愚蠢的。 现在实现起来最容易,因为/examples/jsmexamples/js之间仍然存在 1 对 1 的奇偶校验。 我们可能不会大规模地重新组织/example/jsm模块层次结构,我认为我们可以在这样做时对捆绑器进行增量更新。 我将继续并开始为此做工作证明(使用 babel,因为它已经添加了?)就像他们说的那样,把钱放在嘴边。

就 Mugen 而言,这将有助于在我们继续维护它们的同时保持与three.jsthree.min.js的相关性。 它还可以帮助数百个可能正在寻找与其基于非模块的三实现兼容的更新的站点。 即使您知道自己在做什么,重构三个项目以使用模块也可能非常广泛。

我不能代表其他合作者发言,但我不会在这个话题上改变主意。 我投票决定在 2020 年 12 月发布的版本中删除examples/js ,如在此处讨论和承诺的 #18749。

我投票删除了 2020 年 12 月发布的示例/js,如在此处讨论和提交的 #18749。

我对此没有任何问题。
只要“three.min.js”可以再使用几年......

感谢 Mugen 的输入,我确实通读了该线程,但它似乎更像是一个公告,而不是对决定的解释。 我的假设是简化开发是朝着这个方向发展的主要原因,还有其他原因吗?

我认为拥有一个我们可以运行以生成/examples/js样式包含的编译脚本在这里应该是一个不错的折衷方案。 它应该大大减少此处所需的维护/复杂性。 如果它只是你必须自己运行的 package.json 中的一个命令并且默认情况下不会生成文件,我什至可以。 对于一些开发人员和其他需要转译的开发人员来说,这是有好处的。 当可以将某些内容保存在主存储库中以更好地允许我们协作时,我宁愿我们所有人都不要单独创建转译/捆绑工作流。 :)

我确实通读了该线程,但它似乎更像是一个公告,而不是对该决定的解释。

不幸的是,我们不能总是将所有有效的论点固定在一个线程上,要么是因为多年来在多次讨论中设计更改的实现进展缓慢,要么仅仅是因为关于同一主题的多个线程被一遍又一遍地创建(比如这个)。 合作者试图尽量减少噪音和分割,但这并不总是可能的。

我的假设是简化开发是朝着这个方向发展的主要原因,还有其他原因吗?

我看到的最大的一个是使用和导入子模块的能力,这些子模块可以最大限度地减少冗余代码并实现可重用的实现。

例如,大多数加载器需要创建某种数据解析结构/类,这是因为每个加载器都需要自给自足,以便example/js文件可重用。 但是,如果我们完全取消非模块化限制,我们将能够创建一个DataParser类的单个实例,并将该标准实现导入所有加载程序,立即使开发更容易,并删除冗余代码所有的装载机。

是的,好点。 我们已经不得不进行一些肮脏的修改,比如将Pass类(所有 FX 通道的基类)嵌入到EffectComposer中,以确保遗留代码不会损坏。

非常好的点。

让人们保持正轨/最新听起来(根据我自己的经验)是一个棘手的问题。 会尝试对此进行一些思考。

事实上,将“three.min.js”至少保留 2 年以上是必须的。

总是可以使用 Babel 生成 ES5 构建。 当涉及到这个问题时,我们需要回答的问题是,这件事的责任在于我们还是在于使用three.js 的开发人员。

我们已经决定由开发人员来创建示例文件的 ES5 版本,因此对构建文件做同样的事情可能是有意义的。 在我看来,在一个版本中在整个库中执行此操作也很有意义,而不是将其间隔开,但是将 three.min.js 保持更长的时间也可以。

但是因为成千上万的文件和谷歌的顶级狗都是基于它的!
示例:google.com/search?source=hp&q=webgl+benchmark

这是该搜索为我提供的顶级网站,他们正在使用 R53,因此我认为此更改不会对他们造成太大影响: https ://www.wirple.com/bmark/

如您所见,three.js 的旧版本仍然可以正常工作。 在我们过渡到模块之后,我们可以指导任何想要在不使用 Babel 的情况下构建 ES5 的人使用我们删除 ES5 文件之前的最后一个版本。 他们可以查看该版本的整个回购并使用该版本的文档。

@looeee您谈到了一些优点。 如上所述,我同意在这里同时弃用 ES5 three.min.jsthree.js是有意义的。 也许这应该是它自己的单独讨论?

无论哪种方式,我都想就在主仓库中包含一个 babel 脚本达成共识,该脚本可用于生成老式 ES5 风格的/js/example文件。 这与是否有人负责提供这种支持无关。 有些贡献者,比如我自己,会需要这个功能。 对于一些开发人员和其他需要转译的开发人员来说,这是有好处的。 当可以将某些内容保存在主存储库中以更好地允许我们协作时,我宁愿我们所有人都不要单独创建转译/捆绑工作流。

我认为允许我们在主 repo 中有一个文件是一个公平的妥协,这样我们就可以一起处理 babel ES6 到 ES5 的转译器脚本。 那里真的有问题吗? 允许贡献者在主仓库中共同开发他们需要的功能?

我并没有要求合作者为此提供任何帮助或资源,我只是要求您允许需要此功能的人能够在主仓库中一起工作。 如果我为此做 PR 并且它有效,你真的会投票否决它吗?

如果我为此做一个 PR 并且它有效

我的意思是,我很高兴看到这个开始

你真的会投票否决吗?

如果 linting pass 失败,所有的赌注都将被取消😂

那里真的有问题吗?

是的,因为存储库不应推广已弃用的编码模式。

是的,因为存储库不应推广已弃用的编码模式。

如果我们不解雇three.js + three.min.js (承认 ITT 的共识是我们也应该解雇这些)并且拥有一个你必须自己手动运行的 babel 脚本,它还没有被正式弃用几乎没有一个发光的代言。 我同意我们绝对应该鼓励人们改用模块,并在 babel 脚本和生成的文件中发出警告。 我不同意让贡献者一起为那些由于某种原因而无法使用模块的人一起编写 babel 脚本,从而促进了一种已弃用的编码模式。 主要是因为仍然存在使用模块不可行/不切实际的情况。 文档承认这种需要。 我认为我们可以安全地添加一个文件,供需要它的人一起处理。

我同意在这里同时弃用 ES5 three.min.js 和 three.js 是有意义的。

我的意思是我们应该同时弃用examples/js、three.min.js 和three.js,即在一个版本中删除所有ES5 代码,而不是分散在多个版本中。

@穆根87

是的,因为存储库不应推广已弃用的编码模式。

您仍然可以在 Windows 10 中运行 DOS 游戏。
这并不意味着微软正在推广“过时的编码模式”。

只是为了澄清这个项目中维护的示例 js 模块不需要使用 node、npm 或任何构建框架。

好吧,我们不要忘记构建一个生产就绪的应用程序意味着捆绑你的代码:)

我很欣赏像 Rollup 这样可用的捆绑工具,但认为我们应该考虑几个问题:

  • 假设如果开发人员想在生产中使用 THREE,他们还需要使用其中一种捆绑工具,这是否公平?
  • 放弃对依赖于示例文件夹中 ES5/UMD 模块更新的其他库的支持是否公平?

对此我个人的感受:

这个图书馆已有十年历史。 有一个庞大的生态系统依赖于用 ES5/UMD 编写的示例文件夹中的模块。 我认为放弃对整个生态系统的支持是不公平的。

我认为人们忘记了你仍然可以在没有模块捆绑模式的情况下使用 ES6。 我每天都使用 ES6,但不在我的前端应用程序中使用模块捆绑模式。 我曾在企业商店工作过,在这些商店中,构建工具必须变得非常定制,并且无法合并模块捆绑模式。

我们应该做什么?

让我们在每次发布后将 ES6 模块编译成 ES5/UMD 模块,用于给定的发行版。

是的,因为存储库不应推广已弃用的编码模式。

对于生活中的几乎所有事情,使用旧模式、技术和工具的解决方案仍然可以保持高质量。

打个比方——在空闲时间,我喜欢用尖凿凿石。 工具和技术与电动工具不同,但最终雕塑仍然是高质量的。 我个人偏好使用尖凿,因为我喜欢使用它们,并且拥有制作自己或他人满意的东西所需的技能。

我对 ES5/UMD 模块也有同感。 我已经能够找到支持真正高质量代码库并希望继续行使个人偏好的模式、技术和工具。

让我们在每次发布后将 ES6 模块编译成 ES5/UMD 模块,用于给定的发行版。

我同意 looeee 所说的。

假设 [...]

什么? 我们正在谈论我们更喜欢哪种方法,“假设”随后出现。 偏好似乎是鼓励其他人使用模块,但(假设有些人仍然想要旧的三)为那些真正想要它的人提供了一条途径。

让我们在每次发布后将 ES6 模块编译成 ES5/UMD 模块,用于给定的发行版。

这可以由任何人完成; 这笔费用不需要由 three.js 维护者承担。 我想重申上面@gkjohnson所说的话,维护examples/jsexamples/jsm目录的成本很高。 我们不能无限期地这样做,很明显 ES6 模块是这两种方法中更现代的。 考虑以下成本:

  • 创建和维护自动化
  • 自动化中断时调试发布失败
  • 确保所有拉取请求更新源文件,而不是生成的
  • 维护解释如何使用这两种工作流程的文档
  • 回答尝试使用 CJS 和 ES6 工作流的用户的错误报告和支持问题

最后一项很可能是最大的。 只要此存储库中提供所有内容的两个副本,两者都将被视为完全受支持。 我们经常花时间帮助混淆这两个工作流程的用户,或者尝试将 ES6 模块加载器与 CJS 核心库一起使用,这会以复杂的方式失败。

我们可以简单地重新表述这个问题:我们所有的示例——它们是 three.js 库的重要但可选的部分——目前根本不使用任何模块语法。 不是 UMD,不是 CommonJS,不是 ES6 模块。 他们只是修补THREE全局命名空间。 我们想更新它,改用 ES6 导入/导出语法,并且有许多早期警告表明此更改是计划中的。

有一个庞大的生态系统依赖于用 ES5/UMD 编写的示例文件夹中的模块。 我认为放弃对整个生态系统的支持是不公平的。

我认为说 three.js 生态系统中的任何东西都如此依赖全局THREE.*命名空间以致无法更新以使用导入/导出语法或转译为 ES5 或使用捆绑器。 这里有许多解决方法,我们很乐意与用户合作,帮助他们找到合适的选项。

维护examples/js 和examples/jsm 目录的成本很高。

我想深入研究一下。 我已经为前端应用程序编写了许多自定义工具和构建自动化脚本,并且很乐意以任何方式提供帮助。

创建和维护自动化
自动化中断时调试发布失败

帮助我进一步了解维护税,这是三个代码库独有的东西吗? 以我的经验,这种类型的代码通常是寿命最长的,需要最少的维护。 这些是您编写一次并且很长时间不会再查看的脚本。

确保所有拉取请求更新源文件,而不是生成的

也许一个小脚本或测试可以在发布工作流程中对此有所帮助。

维护解释如何使用这两种工作流程的文档

我也会投票放弃全局命名空间的文档。 我认为支持两个工作流程的文档是愚蠢的。 这不是一件坏事。 大多数为不同上下文捆绑代码的库,UMD/ES6 模块只有一组文档。

回答尝试使用 CJS 和 ES6 工作流的用户的错误报告和支持问题。

我认为与此类问题相关的问题数量与 THREE 的受欢迎程度有关。 你和我一直在 Stack Overflow 上看到这些类型的问题。 无法区分这两个工作流程的用户可能是受库启发的新程序员,并且只是试图学习一般的编程基础知识。

如果目标是减少与两个工作流之间的混淆特别相关的问题的数量,那么删除 ES5 代码可能会对此有所帮助——但我怀疑整体问题的数量会发生变化。 新程序员总是会被困在下一个可能与库相关或不相关的问题上。

如何减少整体问题的数量?

如果真正的目标是减少整体问题的数量,那么更严格的问题政策可能会有所帮助。 我看到你们已经在使用Help (please use the forum)之类的标签方面做得很好,但也许需要更多这些类型的东西。

更一般地说,最好只对三位贡献者愿意讨论和调查的某些类型的问题进行梳理,如果他们目前对总数量感到不知所措。

几个想法:

  • 在撰写本文时, suggestionsenhancements有 (271) 个未解决的问题。 这些标签似乎会产生很多噪音。 也许只将 PR 准备好/通过检查作为实际建议。 Insta 关闭其他所有内容并标记为Discussion (please use the forum)
  • 在撰写本文时, loaders有 (61) 个未解决的问题。 这个标签似乎也产生了很多噪音。 我看到这个标签有很多与suggestionsenhancements或格式不正确的错误报告相关的问题。 也许只需要格式良好的错误报告和 PR 准备好/通过检查以获取建议。 Insta-关闭其他所有内容并相应标记。

我认为说 three.js 生态系统中的任何东西都如此依赖于全局 THREE.* 命名空间以致无法更新以使用导入/导出语法、转译为 ES5 或使用捆绑器。

我同意任何东西都可以更新,但如果我们能找到一种方法来做一些工作,以可持续的方式继续支持这些用户,我同意@Bug-Reaper 的说法:

当可以将某些内容保存在主存储库中以更好地允许我们协作时,我宁愿我们所有人都不要单独创建转译/捆绑工作流。

我们将共同为这些用户节省大量时间来升级他们的应用程序/库、构建系统和文档。

我想深入研究一下。 我已经为前端应用程序编写了许多自定义工具和构建自动化脚本,并且很乐意以任何方式提供帮助。

好的。

如何减少整体问题的数量?

请让我们保持正轨。 很高兴在另一个线程上进一步讨论。 这有点与我之前的评论有关。

我同意@Bug-Reaper 的说法:

我宁愿我们都没有创建转译/捆绑工作流程 [...]

我想我们都同意这一点。

感谢大家分享利弊。 总是很高兴分享这些以确保我们做出明智的决定。

这是我今年花费了一些脑力周期的事情,我什至询问了浏览器供应商他们的优先事项,以便我能够提前计划。

我同意 ES6 模块是未来,但是在没有导入映射的情​​况下使用它们进行开发可能会导致巨大的头痛并完全破坏你的流程。 当我们决定弃用examples/js时,我希望 Import maps 会有更大的吸引力,但似乎它目前不是浏览器的优先事项。

因此,我决定暂停examples/js文件夹的弃用,直到浏览器实现导入映射。 我讨厌强迫新手学习 polyfills 或 bundlers 来渲染他们的第一个立方体。

我得出了与@Bug-Reaper 相同的结论。 今天,我正在研究创建一个脚本,该脚本从examples/jsm文件中构建examples/js

@mrdoob

我决定暂停 examples/js 文件夹的弃用,直到浏览器实现导入映射。
我得出了与@Bug-Reaper 相同的结论。 今天我正在研究如何创建一个脚本,该脚本从示例/jsm 文件中构建示例/js。

一个明智的决定。
👍

@mrdoob我当然接受你的决定,但我认为这是一个错失的机会。 迟早,开发人员将不得不远离全局脚本。 而且我不认为Import Maps在这里会有很大的不同。 我们允许他们继续使用全局脚本,而不是“强迫”用户进入更好和面向未来的工作流程。 2020 年。

而且我不认为Import Maps在这里会有很大的不同。

前几天我看到有人这样做:

<script src="js/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/loaders/GLTFLoader.js"></script>
<script type="module" src="js/main.js"></script>

而且,在main.js中,他们正在这样做:

import {OrbitControls} from "https://threejsfundamentals.org/threejs/resources/threejs/r119/examples/jsm/controls/OrbitControls.js";

事情确实奏效了......😐

我们不能只期望用户做正确的事,他们正在学习,他们正在尝试,直到某些事情奏效。 挑战在于找到一种设计,帮助他们在没有意识到的情况下做正确的事情。

没有导入映射的 ES6 模块的问题在于,用户不能像以前一样将OrbitControls.js $ 复制到自己项目中的/js文件夹中并导入它。 它不起作用,因为OrbitControls.js寻找../../build/three.module.js

使用导入地图, OrbitControls.js只会从three导入。 用户可以将文件复制到任何他们想要的位置,然后在导入映射中调整路径。

导入地图让我们更接近像“旧”时代那样轻松导入文件。 它不会像以前那么容易,但至少用户在导入文件时不必担心顺序。 赢得一些东西失去一些东西。

同意导入地图将使导入可配置,因此更加灵活。 尽管用户仍然需要调整导入图(从而了解它实际上是什么)。

我只是认为整个“将 JS 文件复制到文件夹中”是一种邪恶的反模式,我希望我们可以通过推荐新用户/初学者使用 CDN 导入来防止这种情况(对于不这样做的开发人员来说,这也是一个选择)无论出于何种原因都不想使用构建)。 无论如何,适当的应用程序(应该)使用构建工具。

我真的不认为这是一种反模式。

这就是我学会做网站的方式。 可以将.css文件放在/css文件夹中,然后将图像放在/img中,将.js文件放在/js中。

在过去的几个月里,我一直在使用 ES6 模块/CDN 方法进行一些实验,但我感觉这些库来自与我的项目所在的不同域。

不复制文件时我们失去的一件大事是能够编辑它们。 examples/js中的文件始终应该是您可以在其上构建的示例。 如果我在我的项目中复制OrbitControls.js并且它没有完全满足我的需要,我可以修改它,因为它只是一个本地文件。

这就是我过去设置项目的方式:

<script src="js/libs/three.js"></script>
<script src="js/libs/three/OBJLoader.js"></script>
<script src="js/libs/three/OrbitControls.js"></script>
<script>
    console.log( THREE, THREE.OBJLoader, THREE.OrbitControls );
</script>

使用导入映射,它看起来像这样:

<script type="importmap">
{
  "imports": {
    "three": "js/libs/three.module.js",
    "OBJLoader": "js/libs/three/OBJLoader.js",
    "OrbitControls": "js/libs/three/OrbitControls.js"
  }
}
</script>
<script type="module">
    import * as THREE from 'three';
    import { OBJLoader } from 'OBJLoader';
    import { OrbitControls } from 'OrbitControls';

    console.log( THREE, OBJLoader, OrbitControls );
</script>

不像以前那么漂亮,但会为您处理导入依赖项/订单,并且不需要捆绑器。

然而,它仍然适用于进行基于捆绑开发的人。 事实上,这对他们来说更好,因为插件现在从three而不是../../build/three.module.js导入。

事情确实奏效了......😐

FWIW 这似乎只在少数情况下真正起作用。 当它不起作用时,它会以极其令人困惑的方式失败,并且我们已经提交了几个与此相关的问题,这些问题也发生在构建过程中。 可以说,如果您担心新手为他们提供多种使用相同文件的方法,则更容易出错和混淆。

也许是相切的,但可能值得通过控制台中的警告通知人们他们在页面中包含了两个three.js 副本(即使它们是相同的版本),这可能会导致问题,除非注意不要混合它们。 〜我相信 React 这样做是出于类似的原因〜 React 实际上可能只是指出这可能是错误的来源。 这至少可以帮助人们在学习时避免混合这些方式。

我得出了与@Bug-Reaper 相同的结论。 今天我正在研究如何创建一个脚本,该脚本从示例/jsm 文件中构建示例/js。

如果这是新计划,我很乐意帮助恢复 #15526 / #15543(现已从项目中删除),它将每个模块文件构建为 ES6 文件。 鉴于一些示例分布在如此多的文件(例如着色器节点)中,并且我们可能有兴趣将一些模块拆分为多个文件,因此可能值得升级汇总脚本以获取我们想要转换的文件的明确列表和输出。 我们也应该能够在输出的文件之间自动创建依赖关系。

不复制文件时我们失去的一件大事是能够编辑它们

我同意,但如果我们可以到处上课,我希望是这样的:

import orbitalcontrols from  orbitalcontrolsURL

class mycontrols extends orbitalcontrols {
// do the edits I care about
}

然后稍后

let controls = new myorbitalcontrols

不复制文件时我们失去的一件大事是能够编辑它们

我同意,但如果我们可以到处上课,我希望是这样的:

从 orbitalcontrolsURL 导入 o​​rbitalcontrols

类 mycontrols 扩展轨道控制 {
// 做我关心的编辑
}

然后稍后

让控制 = 新的 myorbitalcontrols

你已经可以这样做了……即使父“类”是简单的 js 函数!

实际工作的代码(在调试器快速测试中):

Promise.all([
    import('https://unpkg.com/three/build/three.module.js')
        .then( mod=> [mod.Camera, mod.WebGLRenderer] ),
    import('https://unpkg.com/three/examples/jsm/controls/OrbitControls.js')
        .then( mod=> mod.OrbitControls )
])
.then( ([
    [ Camera, WebGLRenderer ],
    OrbitControls
])=> new ( class extends OrbitControls {} )( new Camera, (new WebGLRenderer).domElement )
)
.then( console.log )

...或更简单的语法:

(async function() {

let { Camera, WebGLRenderer } = await import('https://unpkg.com/three/build/three.module.js')
,   { OrbitControls } = await import('https://unpkg.com/three/examples/jsm/controls/OrbitControls.js')

class Con extends OrbitControls { }

let my = new Con( new Camera, (new WebGLRenderer).domElement )
console.log( my )

})()

除了那个 aynom 功能和担心 async/await 承诺之外,很酷

class mycontrols extend orbitalcontrols {
 // do the edits I care about
 }

理想情况下,这是我们应该推广的模式,而不是告诉用户在进行更改时编辑原始文件。 但是,编写这些示例时并没有考虑到可扩展性,因此您可以实现的目标有很大的限制。 根据我的经验,您最终必须将整个原始示例复制到扩展类的构造函数中才能使其工作,因此使用extend毫无意义。

例如, OrbitControls最常见的请求更改是限制 pan 。 正如@Mugen87从该线程的小提琴中所展示的那样,这很容易实现。

简而言之,您添加minPanmaxPan向量并在controls.update方法中钳制controls.target

我尝试通过扩展OrbitControls来做到这一点。 您可以创建一个扩展类,它工作正常。 但是,当您开始进行更改时,问题就会变得很明显。 您不能简单地扩展update方法:

class OrbitControlsPanLimit extends OrbitControls {
    constructor(object, domElement) {
        super(object, domElement);
    }

    update() {
        super.update();
        console.log('Custom update function');
    }
}

这个扩展类有效(故障),但是这个新的OrbitControlsPanLimit.update方法被忽略了。 原来的OrbitControls.update方法仍然使用。

您可以通过在构造函数中重新定义它来覆盖它:

class OrbitControlsPanLimit extends OrbitControls {
    constructor(object, domElement) {
        super(object, domElement);

        this.update = () => {
            console.log('Custom update function');
        }
    }
}

您不能在此处使用super.update() ,因此唯一的选择是复制整个原始更新方法。 但是,该方法依赖于OrbitControls中的许多此类内容,这些内容在所有方法之间共享。

    //
    // internals
    //

    var scope = this;

    var changeEvent = { type: 'change' };
    var startEvent = { type: 'start' };
    var endEvent = { type: 'end' };

    var STATE = {
        NONE: - 1,
        ROTATE: 0,
        DOLLY: 1,
        PAN: 2,
        TOUCH_ROTATE: 3,
        TOUCH_PAN: 4,
        TOUCH_DOLLY_PAN: 5,
        TOUCH_DOLLY_ROTATE: 6
    };

    var state = STATE.NONE;

    var EPS = 0.000001;

    // current position in spherical coordinates
    var spherical = new THREE.Spherical();
    var sphericalDelta = new THREE.Spherical();

    var scale = 1;
    var panOffset = new THREE.Vector3();
    var zoomChanged = false;

    var rotateStart = new THREE.Vector2();
    var rotateEnd = new THREE.Vector2();
    var rotateDelta = new THREE.Vector2();

    var panStart = new THREE.Vector2();
    var panEnd = new THREE.Vector2();
    var panDelta = new THREE.Vector2();

    var dollyStart = new THREE.Vector2();
    var dollyEnd = new THREE.Vector2();
    var dollyDelta = new THREE.Vector2();

最终结果是您必须将几乎整个原始OrbitControls复制到OrbitControlsPanLimit构造函数中,这违背了扩展类的目的。 除非我们将控件编写为一个考虑到可扩展性的类,否则我认为扩展它是不可行的。

感谢@looeee的参与。 我在想也许我在自己的努力中错过了一个简单的解决方案,但现在你提到它,这几乎就是我自己的地方。

理想情况下,这是我们应该推广的模式,而不是告诉用户在进行更改时编辑原始文件。

小心,这与继承与组合的争论密切相关。

理想情况下,图书馆不应该推广任何模式。 它应该宣传它的功能以及它如何解决您的问题。

它也不应该假设开发人员的工作流程、堆栈、构建系统、用例。 一个伟大的图书馆尽可能地满足其社区的许多复杂需求。

今天的新东西明天就旧了,模式来来去去。 唯一不变的将是软件,它为沿途的许多用例提供强大的支持,以保持尽可能多的向后兼容性。

您仍然可以在 Windows 10 中运行 DOS 游戏。

继承与组合论点

请不。 这个“争论”的解决方案是使用最好的工具来完成这项工作。 有一个继承、组合、功能、测试驱动的地方......你说的。

由于我们正在讨论其他开发人员如何(使用、重用、修改)three.js,因此推广一种无需超出 js 浏览器功能即可轻松理解和使用的模式是有效的。

推广并不意味着不能使用不同的风格。

尽可能向后兼容

是和不是。

它应该宣传它的功能以及它旨在如何解决您的问题

也许是为了让我们清楚,为您设置的问题/功能是什么?

它也不应该假设开发人员的工作流程、堆栈、构建系统、用例

我大多同意。 threejs 用例目前是浏览器。 需要注意的是,据我所知,我们的一些加载器对某些节点应用程序很有用。

唯一不变的将是为沿途的许多用例提供强大支持的软件

变化是唯一不变的。 开发人员使用他们喜欢的工具,有时我们也会尝试其他的东西。

作为旁白:

它应该宣传它的功能以及它旨在如何解决您的问题

哪个先出现? 特征、模式还是问题?
当然,该模式有助于解决问题,然后成为一个特征
...或者是造成问题的功能,我们找到了解决问题的模式?

哪个先出现? 特征、模式还是问题?

哪个先出现? 母鸡还是鸡蛋?
有人说公鸡...

到处都是很好的讨论,感谢大家的所有投入。

我很想知道你们对哪个捆绑器(rollup、babel、parcel、webpack 等)最适合转译我们的 ES6 示例模块的任务的看法。 我相信@gigablox提到在这里有经验,我相信其他人也会这样做。

当前的 repo 已经包含 babel、rollup 和几个相关的插件。 我继续前进,今晚开始着手解决这个问题,我有一个非常粗略的汇总配置脚本要分享:

// jsm-transpiler.js
export default [
  {
    input: './examples/jsm/controls/OrbitControls.js',
    output: {
      banner:"//warning this file was generated automatically",
      file: './examples/js/controls/OrbitControls.js',
      name:'OC',
      footer:'THREE["OrbitControls"]=OC.OrbitControls',
      format: 'umd'
    }
  }
];

这个汇总配置脚本确实将OrbitControls模块转换为非模块.js文件包含,它为 THREE.OribitControls 分配了适当的构造函数。 它奏效了,这很酷:)! 它还将 40k 行的 THREE.js 捆绑到输出文件中,不是很酷哈哈。 我还懒惰地通过声明一个名为 OC 的中间全局变量来污染全局变量空间,以帮助将 OrbitControls 构造函数传输到三个。

Rollup 似乎有一些非常酷的功能,我认为可以解决我们的很多问题。 尤其是映射和其他控件,用于确保包含/排除正确的嵌套模块。 通过 header/footer/intro/output 属性在转译的有效负载之前和之后注入代码的能力。

我谨慎乐观地认为,我们可以通过精心设计的汇总配置脚本来完成我们需要的工作。 但是,如果有人研究/理解了许多捆绑器之间的差异,但可以在这里权衡一下,那就太好了。 我们需要一些相当健壮的东西来处理模块,因为它们变得更棒了,我敢打赌,一些转译代码比其他代码更好。

这是我的看法:
https://github.com/mrdoob/three.js/pull/20529

这是一个 poc 自定义构建脚本,可在大约 30 秒内将所有 JSM 模块转换为 JS 全局命名空间模块。 用这种方法取得了相当大的成功。 需要更多测试,但在 hello world 中尝试了一些更复杂的模块,例如 GLTFLoader,效果很好。

可以使用任何经验丰富的 RegExp 向导的帮助 :) 来解决一些您可以在 PR 中阅读更多信息的边缘案例。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

WaltzBinaire picture WaltzBinaire  ·  67评论

RicoLiu picture RicoLiu  ·  100评论

Mugen87 picture Mugen87  ·  68评论

mrdoob picture mrdoob  ·  75评论

qornflex picture qornflex  ·  113评论