Three.js: 转向模块化架构

创建于 2014-05-06  ·  153评论  ·  资料来源: mrdoob/three.js

浏览器化
迁移到这种架构有优点也有缺点。 请补充您的想法。

注意:这不需要three.js消费者使用browserify。

Suggestion

最有用的评论

是的,所以它需要一些重构......

我接到你了! 由于这个线程在过去几天变得活跃,我一直在研究三 jsnext 。 这是一个采用现有 Three.js 代码库并自动将其转换为 ES 模块的项目。 只是在处理一些棘手的循环依赖项(特别是在KeyframeTrack周围),但很快就会有一些东西可以真正分享。 据我所知,所有示例都可以继续工作,并且缩小版本比当前版本小(使用 Rollup 生成 UMD 文件),所以这都是好消息。

所有153条评论

一个优势是,这将为three.js 的持续开发强制实施模块化架构。

node/browserify 中的常见样式是每个文件在顶部声明其依赖项,并将全局变量视为反模式。

这是一个示例片段:

// src/geometry/BoxGeometry.js
var Geometry = require('./Geometry.js');
var Vector3 = require('../core/Vector3.js');
module.exports = BoxGeometry;

function BoxGeometry() {
  // ...
}

BoxGeometry.prototype = Geometry.prototype;

另一个优点是使用 browserify 的three.js消费者将能够挑选他们想要的部分。 他们可以只导入SceneBoxGeometryPerspectiveCameraWebGLRenderer ,自动获取所有这些的依赖项( Object3D等),并有一小部分 javascript 支持他们想要的功能集。

这可以通过不施加破坏性更改的方式来完成。 在顶层,我们将导出我们认为是标准包一部分的所有类

// src/three.js
var THREE = { rev: 101 }
module.exports = THREE

THREE.Geometry = require('./geometry/Geometry.js')
THREE.BoxGeometry = require('./geometry/BoxGeometry.js')
// ...

注意:在此示例中,我并不完全需要顶部的依赖项,因为此文件几乎完全是 require 语句。

最后,我们将其包装在通用模块定义中,该window )。

我们来复习:

  • 强制执行良好的模块化风格
  • 允许three.js消费者使用 browserify 选择功能
  • 没有重大变化

这将需要更换构建系统,但新的将非常简单。

其他一些优点:

  • 您可以构建您的代码
  • 您可以在不污染全局命名空间的情况下创建/重用模块
  • 您可以为生产而构建
  • 您可以更轻松地调试,因为每个模块都有自己的文件,您不需要在一个大的three.js 文件中搜索相应的模块

@shi-314 我想我有点困惑,我觉得You can structure your codeYou can build for production是您可以在没有架构转变的情况下做的事情吗? 您是在谈论three.js 源代码还是使用three.js 构建的东西?

Three.js 使用的一种使在 commonjs 环境中使用变得棘手的做法是使用instanceofhttps :

这是因为在应用程序中,您的源代码树中经常会出现同一库的不同版本,因此检查 instanceof 在同一库的不同版本之间不起作用。 为迁移到 commonjs 模块系统做准备,用Geometry.isGeometry(geom)样式界面后面的功能检查替换那些 instanceof 检查会很好。

@kumavis我说的是在three.js 中构建的东西。 假设你想用你的着色器等创建你自己的材质。目前你需要扩展全局 THREE 对象以保持与 Three.js 代码的其余部分保持一致:

THREE.MeshMyCoolMaterial = function (...) { ... }

但是如果我们有 Browserify 比你可以做的:

var MeshLambertMaterial = require('./../MeshLambertMaterial');
var MeshMyCoolMaterial = function (...) {...}

所以你的命名空间保持一致,你不需要在你的代码中使用THREE.MeshLambertMaterialMeshMyCoolMaterial

而对于You can build for production我的意思基本上和你提到的一样: allows three.js consumers using browserify to pick and choose functionality

@shi-314 谢谢你,这更清楚。 这确实影响了我提出的反序列化消费者定义类的通用解决方案:

// given that `data` is a hash of a serialized object
var ObjectClass = THREE[ data.type ]
new ObjectClass.fromJSON( data )

这是我提议的序列化/反序列化重构
https://github.com/mrdoob/three.js/pull/4621

性能不应受到此类更改的影响。

这是一个相当大的变化,但我也赞成。

其他一些主要优点:

  • 您可以使用 browserify 的standalone选项为您生成 UMD 构建。 无需手动修改 UMD 包装器。
  • 该包可以很容易地被 browserify/NPM 的用户使用
  • 为 Threejs(如 poly2tri、 color-string等)引入依赖变得更加容易
  • 渲染库中“不真正属于”的模块(如矢量/数学库)可以作为单独的 NPM 模块拉出并重新用于许多其他类型的项目。 这样做的一个主要好处是,各个模块都有自己的错误/问题、PR 等存储库(清理 ThreeJS 问题)。
  • NPM 将为我们处理语义版本控制。 例如,我们可以在threejs-vecmath进行重大更改,而不必担心每个人的代码被破坏。 另一方面,如果我们在特定模块中发布补丁或次要版本,使用这些模块的人将能够自动获得更改。
  • 它使像 EffectComposer 和各种着色器这样的“附加功能”易于打包和使用(想象一下npm install threejs-shader-bloom
  • 随着模块被拉出,最终的分发规模将开始变得更小,并且更加特定于应用程序。 最终将不需要不同的“构建类型”,因为我们只需要require()应用程序实际使用的模块。

@mrdoob和其他作者; 如果您对 NPM/Browserify 没有太多经验,我建议您用它制作几个小项目并了解它的“哲学”。 它与 ThreeJS 架构非常不同; 它鼓励许多小事情,而不是大框架。

这种方法的另一个优点是可以有一个开源的生态系统,第三方 Three.JS 模块,特别是着色器、几何图形、模型加载器等。 通过 NPM 或 Github/Component 发布,人们可以轻松地引用和使用。 目前,通过主持一个演示来共享内容,然后人们可以在该演示上“查看源代码”。 三.JS值得更好!

我认为 Three.JS 的问题之一是代码与 Three.JS 的当前版本不兼容的速度有多快。 切换到这样的东西的另一个优点是能够指定 Three.JS 的 _bits_ 的特定版本会非常强大和方便。

+1

+1 对于 CommonJS/browserify 架构,它将使核心更轻量级,即使扩展来自第三方也适合

将three.js 分成小模块也有很多成本。 当前系统允许非常简单的第三方插件(例如jetienne 的THREEx 模块)。 关于当前设置的简单性,有很多要说的,只要 JS 模块系统只是构建系统的包装器。

另一种最小化构建大小的方法是 ClojureScript 所做的。 它们遵循一些约定以允许 Google 的 Closure 编译器进行整个程序分析和死代码消除。

+1 表示未被重视且经常被忽视的简洁优雅

+1

将three.js 分成小模块也有很多成本。 当前系统允许非常简单的第三方插件(例如jetienne 的THREEx 模块)。

这里的想法是仍然会为非节点环境提供 UMD 构建。 像 THREEx 这样的插件对于那些依赖于 ThreeJS 和简单的<script>标签的插件来说也是一样的。

棘手的事情是:如果我们在 CommonJS 环境中,我们如何require()一个特定的插件? 也许 browserify-shim 可以提供帮助。

关于当前设置的简单性,有很多要说的,只要 JS 模块系统只是构建系统的包装器。

ThreeJS 当前的插件/扩展系统很难使用,而且远非“简单”或容易。 大多数 ThreeJS 项目倾向于使用某种形式的插件或扩展,比如 EffectComposer,或 FirstPersonControls,或模型加载器,或漂浮在examples文件夹中的其他许多 JS 文件之一。 现在依赖这些插件的唯一方法:

  • 下载 ThreeJS 的当前版本
  • 将必要的文件复制粘贴到您的vendor文件夹中
  • 连接 gulp/grunt 任务以连接和缩小您需要的所有插件; 确保以正确的顺序连接它们,否则事情会破裂。 添加更多插件时手动维护此列表。
  • 每次更新 ThreeJS 时,重复步骤 1 和 2; 然后当你意识到新代码不向后兼容时拔掉你的头发

现在,想象一下,使用 browserify 你可以做这样的事情:

var FirstPersonControls = require('threejs-controls').FirstPersonControls;

//more granular, only requiring necessary files
var FirstPersonControls = require('threejs-controls/lib/FirstPersonControls');

这些插件将require('threejs')以及他们可能需要的任何其他内容(如GLSL 片段文本三角剖分)。 依赖/版本管理对用户都是隐藏的,不需要手动维护 grunt/gulp concat 任务。

棘手的事情是:如果我们在 CommonJS 环境中,我们如何 require() 一个特定的插件?

我已经将 CommonJS 用于 THREE.js 项目一段时间了。 这是一个有点手动的过程,将其他人的代码块转换为模块,我认为没有一种简单的方法可以避免作者或贡献者未转换的遗留代码。

重要的一点是有一个模块导出整个“标准”三对象,然后任何希望扩展它的东西都可以需要它。

var THREE = require('three');

THREE.EffectComposer = // ... etc, remembering to include copyright notices :)

这对我来说效果很好,尤其是随着项目的发展,我开始将自己的着色器和几何图形添加到他们自己的模块等中。

只要有一个 'threejs-full' 或 'threejs-classic' npm 包,那么这就会成为在 CommonJS 环境中使用旧 Three.js 东西的一种非常可行的方式,但我怀疑这非常小众!

+1
我相信曾经在 npm、plugin 中可以使用碎片化的 Threejs 模块
开发人员会喜欢迁移到 CommonJS 环境。
2014 年 6 月 5 日晚上 9:19,“Charlotte Gore”通知@ github.com 写道:

棘手的事情是:如果我们需要()一个特定的插件
在 CommonJS 环境中?

我已经将 CommonJS 用于 THREE.js 项目一段时间了。 有点
手动过程,将其他人的代码块转换为模块
而且我认为对于遗留代码没有一种简单的方法可以避免这种情况
不是由作者或贡献者转换的。

重要的一点是有一个模块导出整个“标准”
三个对象,然后可以被任何希望扩展的对象所需要
它。

var THREE = require('三');
THREE.EffectComposer = // ... 等等,记得包括版权声明:)

这对我来说效果很好,尤其是随着项目的发展,我
开始将我自己的着色器和几何图形添加到他们自己的模块等中。

只要有一个 'threejs-full' 或 'threejs-classic' npm 包然后
这成为处理旧 Three.js 东西的一种非常可行的方式
CommonJS 环境,但我怀疑这是非常小众的!


直接回复此邮件或在 GitHub 上查看
https://github.com/mrdoob/three.js/issues/4776#issuecomment -45236911。

它还可以使着色器也模块化,例如使用glslify 。 甚至像制作一个按需生成着色器的 Express 中间件这样的事情也变得更容易了。

几个月前,我将frame.js移到了 require.js,我终于明白了 AMD 的东西是如何工作的。

但是,我仍然需要学习如何“编译”它。 从模块列表中生成three.min.js的工具/工作流程是什么?

我更喜欢gulp.js作为带有-browserify插件的构建系统。 在我看来,它真的很容易理解,而且代码看起来比 grunt 更简洁。 看看这个: http ://travismaynard.com/writing/no-need-to-grunt-take-a-gulp-of-fresh-air :wink:

一些想法:(当然基于我对 node、npm、browserify 的有限经验)

  1. 我认为 node.js 模块很棒(即 npm、modules 和 require()s)
  2. 我认为 browserify 也很棒

也就是说,在讨论完这个线程之后,我不确定每个人是否对 browserify 有相同的理解(browserify、commonjs、requirejs、amd、umd 有点相关,尽管它们可能不一定是同一件事)。

现在,如果您可以稍微遵循我的思路。

  1. JS 很棒,它在浏览器上运行得很快。
  2. 哇,现在 JS 也运行在服务器端了。
  3. 那是 node.js,它很酷,所以让我们在 node.js 中编写代码
  4. 但我不想写/不能写所有东西/找东西用。
  5. 不用担心,现在运行 npm install modules
  6. 现在需要这些很酷的模块,以便我们可以使用它们。
  7. 效果很好!
  8. 现在等一下,我们刚刚用 JS 写了一大堆在 node.js 上运行的东西
  9. 浏览器中不应该使用 js 吗? 我们如何让这些代码再次运行?

这就是 Browserify 出现的地方。 好吧,技术上可以在浏览器中使用 requireJS。 但是你希望在不进行太多网络调用的情况下将 js 文件捆绑在一起(与快速的文件系统 require() 不同)。 Browserify 在那里做了一些很酷的事情,比如静态分析,以查看需要导入哪些模块并创建更适合您的应用程序的构建。 (当然有限制,它可能无法解析 require('bla' + variable))它甚至可以换出需要 node.js 依赖项的仿真层的部分。 是的,它生成了一个 js 版本,我现在可以将其包含在我的浏览器中。

以下是 browserify 可以做的一些事情https://github.com/substack/node-browserify#usage

听起来到目前为止一切都很好……但我认为有几点值得考虑我们转向“浏览器架构”

  • Three.js 开发人员的思维方式需要转变(当然必须使用 require 模块系统)
  • 可以构建一个兼容层,因此three.js 用户仍然可以以旧方式使用three.js 而不会获得模块化的好处
  • 为了能够生成优化的构建,three.js 用户需要转移到 require 系统
  • 新的构建过程可能会涉及到 browserify 工具链(目前我们可以使用 python、node.js 或简单的复制和粘贴等)或一些 requireJS 工具。
  • 如果我们希望three.js真正更加模块化,对每个组件进行版本控制,比如TrackballControls,我们需要将它们分开,这可能会导致碎片化
  • 这也可能导致多样性,但是目前 Three.js 的一个优势似乎是许多扩展的集中点

因此,如果我们看到这种多样性和方便的模块加载(主要依赖于 npm 生态系统)以及定制构建是一件好事,那么改变范式、重构代码和改变我们当前的构建系统可能值得一试。

@mrdoob这里列出了一些关于https :

关于three.min.js ,您不会在项目中使用缩小的代码。 你所做的只是var three = require('three')中的project.js然后运行browserify project.js > bundle.js && uglifyjs bundle.js > bundle.min.js 。 注意:您仍然可以为<script src="min.js">发送缩小代码。

我目前正在用 Three.js 包装

if ('undefined' === typeof(window))
  var window = global && global.window ? global.window : this
var self = window

module.exports = THREE

然后我用

module.exports = function(THREE) { /* extension-code here */ }

所以我可以这样要求:

var three = require('./wrapped-three.js')
require('./three-extension')(three)

所以这不是最优的,但我个人实际上可以接受它并认为它不是那么糟糕 - 尽管@kumavis提议将是一个_巨大的_优势。

但也许分叉三个并将所有东西放在单独的模块中只是为了看看它会如何工作是有意义的。

还可以查看http://modules.gl/ ,它在很大程度上基于 browserify(尽管您可以在没有 browserify 的情况下单独使用每个模块)。

@mrdoob @shi-314 browserify已被列入黑名单,支持直接使用 browserify(即通过vinyl-source-stream)。

像 grunt/gulp/etc 这样的工具在不断变化,你会发现很多不同的意见。 最后,您选择哪个并不重要,或者您是否只是使用自定义脚本来完成它。 更重要的问题是:用户将如何使用 ThreeJS,以及您希望保持多少向后兼容性?

经过更多思考,我认为在不完全重构框架及其架构的情况下将所有内容模块化将_真的_很难。 这里有一些问题:

  • 所有命名空间代码都必须更改为 CommonJS 导出/要求。 这是一项非常艰巨的任务,并且会有很多丑陋的../../../math/Vector2等。
  • 在理想的世界中,库将是碎片化的,因此three-scene将与three-lights等解耦。然后您可以分别对每个包进行版本控制。 这种碎片化对于 ThreeJS 这么大的框架来说似乎不太现实,维护起来也很麻烦。
  • 如果我们_不_将框架分割成微小的组件,那么语义版本控制将是一场噩梦。 框架中任何地方的微小变化都需要整个版本的主要版本。 并且使用 API 会非常难看: require('three/src/math/Vector2')

我的建议? 我们考虑向前推进的两件事:

  1. 从小处着手; 提取一些基本的和可重用的特性,如向量/四元数、颜色转换、三角剖分等。 这些东西是 NPM 的很好的候选者,因为它们在 ThreeJS 的范围之外很有用。 他们还可以拥有自己的测试套件、版本控制和问题跟踪。
  2. 当需要将新代码添加到 ThreeJS 时,例如新功能或依赖项(例如 poly2tri/Tess2),请考虑将其作为单独的模块拉出并通过 NPM 依赖它。

我很想看到所有东西都模块化,但我不确定是否有一种适合 ThreeJS 的方法。 也许有人应该在叉子上做一些实验,看看事情的可行性。

谢谢各位大佬的解释!

我害怕让刚开始的人把事情复杂化。 强迫他们学习这个 browserify/modules 的东西可能不是一个好主意......

在这里必须同意@mrdoob 。 我和很多同事都不是网络程序员(而是 VFX/动画 TD)。 除了我们当前的工作量之外,学习 WebGL 和 Three 肯定已经足够了(在某些情况下,我们中的一些人不得不当场学习 js)。 我在这个线程中读到的大部分内容有时让我不寒而栗,想到如果三搬到这个结构,我的盘子里会增加多少工作。 我可能是错的,但这对我来说肯定是这样。

使用 repo 中的预编译 UMD ( browserify --umd ) 构建,现有开发人员的工作流程没有变化。

@mrdoob依赖管理系统的想法很简单。 阅读有关选项和构建系统的数十篇文章可能会让人不知所措,但最终当前的系统是不可持续的。 任何时候一个文件依赖于另一个文件,这是任何新开发人员必须执行的搜寻搜索以找到参考。 使用 browserify,依赖项是显式的,并且有一个文件路径。

@repsac依赖系统应该使其他语言的用户更容易访问 Three,因为它避免了全局范围、加载顺序噩梦并遵循类似于其他流行语言的范式。 var foo = require('./foo');是(松散地)类似于 C# 的using foo;或 Java 的import foo;

我很想看到所有东西都模块化,但我不确定是否有一种适合 ThreeJS 的方法。 也许有人应该在叉子上做一些实验来看看事情的可行性

我认为这是要走的路,真的。 完成工作,展示它是如何工作的。

并且使用 API 会非常好ugly: require('three/src/math/Vector2')

作为一项实验,我刚刚将“入门”从三个文档转换为这种新的模块化方法。 我可以想象会有很多引用,除非人们非常严格地将他们的代码分解成小模块。

这样做的主要优点是生成的构建大小将是完整 Three.js 大小的一小部分,因为您将只包含此处特别引用的内容以及这些内容所依赖的内容。

我想引用您需要的所有依赖项(并单独安装它们)在实践中可能会证明有点太糟糕了。

如果您明确针对移动设备,那么这种高度细化的方法将是完美的,但实际上我怀疑我们需要导出整个三个 api 的包,这些包将正常工作,然后是封装所有奖励几何的较小包,所有渲染器、所有数学、所有材料等,然后向下到单个模块级别,以便开发人员可以自己决定。

是的,为网络编码是一种痛苦。

无论如何,继续实验......

安装我们的依赖项..

npm install three-scene three-perspective-camera three-webgl-renderer three-cube-geometry three-mesh-basic-material three-mesh three-raf

编写我们的代码...

// import our dependencies..
var Scene = require('three-scene'),
  Camera = require('three-perspective-camera'),
  Renderer = require('three-webgl-renderer'),
  CubeGeometry = require('three-cube-geometry'),
  MeshBasicMaterial = require('three-mesh-basic-material'),
  Mesh = require('three-mesh'),
  requestAnimationFrame = require('three-raf');

// set up our scene...
var scene = new Scene();
var camera = new Camera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new Renderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// create the cube...
var geometry = new CubeGeometry(1, 1, 1);
var material = new MeshBasicMaterial({color: 0x00ff00});
var cube = new Mesh(geometry, material);
scene.add(cube);
// position the camera...
camera.position.z = 5;
// animate the cube..
var render = function () {
  requestAnimationFrame(render);
  cube.rotation.x += 0.1;
  cube.rotation.y += 0.1;
  renderer.render(scene, camera);
};
// begin!
render();

然后构建我们的文件

browserify entry.js -o scripts/hello-world.js

然后将其包含在我们的页面中

<script src="/scripts/hello-world.js" type="text/javascript"></script>

我想引用您需要的所有依赖项(并单独安装它们)在实践中可能会证明有点太糟糕了。

最终用户不一定需要在他们的项目中使用 browserify,以便 Three 使用 browserify 来管理其代码库。 三个可以作为全局THREE公开,因为它现在......包括构建文件并使用它运行。

@repsac @mrdoob更改将向后兼容,因此如果当前用户不想更改,则无需更改任何内容。 这些建议是为了提高 ThreeJS 庞大而单一的代码库的长期可维护性和寿命。 依赖和版本管理之类的事情对于初学者来说可能听起来很头疼,但对于那些在 ThreeJS 之上开发工具、框架、插件和大型网站的人来说,它们非常棒。

即最终用户代码看起来仍然相同,并且examples根本不需要更改:

<script src="three.min.js"></script>

<script>
var renderer = new THREE.WebGLRenderer();
</script>

对于正在寻找模块化构建的雄心勃勃的开发人员,_或_对于那些希望在 ThreeJS 之上开发长期解决方案的开发人员(即并利用版本/依赖项管理),它可能看起来更像这样:
npm install three-vecmath --save

然后,在代码中:

var Vector2 = require('three-vecmath').Vector2;

//.. do something with Vector2

此外,这允许人们在 ThreeJS 的范围之外使用 ThreeJS 的向量数学、颜色转换、三角剖分等。

尽管我认为 require() 混乱是一个坏主意和不好的权衡,但让用户接触两种不同类型的three.js 代码将是一个更糟糕的主意,告诉用户一种是花哨的模块系统风格,另一种是更简单(但二等)的模块系统风格。

@erno我认为您已经错过了这一点, three.js将在内部由模块结构组织,但这用于生成与当前设置没有区别的构建文件。

主要的收获是改进了开发和维护three.js的体验。

@kumavis - 不, @erno实际上并没有错过这一点,但我明白(*)他指出,如果three.js有时通过 require 使用,有时不使用,这可能会令人困惑。 例如,有人同时查看三个来源,然后查看一些 3rd 方示例,并在这一切和工作方式方面遇到差异。

(*)我们今天早些时候在irc上讨论过这个。

我认为这是一种有效的观点,但我不确定它最终是否/如何解决 - 模块和构建事物的使用是否真的存在问题。 但似乎确实值得一想,总体而言,我认为这里已经仔细考虑了整个问题,这对我来说似乎很好,感谢到目前为止我提供的信息和观点。

@antont我明白了。 人们在这里提出了各种不同的方法,我假设我们将主要提供顶级使用的文档(从THREE提取所有内容),但其他人可能会创建不遵循此的示例,并且可能导致一些混乱。 这是一个有效的关注。

我想我对语言有点困惑。

另一个是更简单(但二等)的模块系统风格。

这只是指构建文件,是吗?

在我的理解中,是的。 无法想象还有什么,但可能会错过一些东西。

antont, kumavis:这里的提案也谈到了将 require() 风格的代码暴露给最终用户,参见例如。 mattdesl 的最新评论。

“对于正在寻找模块化构建的更有雄心的开发人员,或者对于那些希望在 ThreeJS 之上开发长期解决方案的开发人员(即利用版本/依赖项管理)[...]”

获得更优化构建的一种方法实际上是使用一个脚本来自动确定您的依赖项并生成所需的模块。

现在 bower 和 browserify 没有 require,但它们不是唯一的解决方案。 我不知道是否有其他现成的开源项目可以做到这一点(可能像 ng-dependencies),但我之前写过这样的工具,我认为还有其他方法可以解决这些问题。

谷歌的闭包编译器可能是这样的工具吗?

在用户方面,这可能有帮助吗?
http://marcinwieprzkowicz.github.io/three.js-builder/

Three.js 使用的一种在 commonjs 环境中使用起来很棘手的做法是使用 instanceof: https :

这是因为在应用程序中,您的源代码树中经常会出现同一库的不同版本,因此检查 instanceof 在同一库的不同版本之间不起作用。 准备迁移到 commonjs 模块系统以用 Geometry.isGeometry(geom) 样式界面后面的功能检查替换那些 instanceof 检查会很好。

在 git/three.js/src 中:

grep -r instanceof . | wc -l 
164

在 git/three.js/examples 中:

grep -r instanceof . | wc -l 
216

所以在three.js 中总共有380 次使用instanceof 。 作为替代品的最佳实现是什么?

我最近添加了一个type属性,可以用来替换大部分属性。

我最近添加了一个 type 属性,可以用来替换其中的大部分。

好的! 准备PR。

有关如何在另一个流行的大型 JS 库中处理此问题的示例,请查看https://github.com/facebook/react 。 代码库是使用基于节点样式的模块系统(browserify 实现的)构建的,但它是为使用 grunt 发布而构建的。 此解决方案可灵活用于 3 个用例。

  1. Three.js 的纯消费者编写 vanilla JS 仍然可以像往常一样使用构建文件。 此用例没有变化。
  2. Three.js 的消费者使用 browserify 可以将 Three.js 声明为项目中的依赖项,并且只能声明require特定的依赖项。 适当的依赖管理的好处已被充分证明。
  3. 对 Three.js 的贡献现在应该更简单,因为组件之间的依赖关系被明确记录。

我做了一些研究...

昨天我编写了一个(相当愚蠢的)脚本,该require()语句来声明文件之间的依赖关系。 只是为了看看会发生什么......这个:

  1. 它最终得到了像这样的相当荒谬的 require 语句(来自 WebGLRenderer):

var THREE = require('../Three.js'); require('../math/Color.js'); require('../math/Frustum.js'); require('../math/Matrix4.js'); require('../math/Vector3.js'); require('./webgl/WebGLExtensions.js'); require('./webgl/plugins/ShadowMapPlugin.js'); require('./webgl/plugins/SpritePlugin.js'); require('./webgl/plugins/LensFlarePlugin.js'); require('../core/BufferGeometry.js'); require('./WebGLRenderTargetCube.js'); require('../materials/MeshFaceMaterial.js'); require('../objects/Mesh.js'); require('../objects/PointCloud.js'); require('../objects/Line.js'); require('../cameras/Camera.js'); require('../objects/SkinnedMesh.js'); require('../scenes/Scene.js'); require('../objects/Group.js'); require('../lights/Light.js'); require('../objects/Sprite.js'); require('../objects/LensFlare.js'); require('../math/Matrix3.js'); require('../core/Geometry.js'); require('../extras/objects/ImmediateRenderObject.js'); require('../materials/MeshDepthMaterial.js'); require('../materials/MeshNormalMaterial.js'); require('../materials/MeshBasicMaterial.js'); require('../materials/MeshLambertMaterial.js'); require('../materials/MeshPhongMaterial.js'); require('../materials/LineBasicMaterial.js'); require('../materials/LineDashedMaterial.js'); require('../materials/PointCloudMaterial.js'); require('./shaders/ShaderLib.js'); require('./shaders/UniformsUtils.js'); require('../scenes/FogExp2.js'); require('./webgl/WebGLProgram.js'); require('../materials/ShaderMaterial.js'); require('../scenes/Fog.js'); require('../lights/SpotLight.js'); require('../lights/DirectionalLight.js'); require('../textures/CubeTexture.js'); require('../lights/AmbientLight.js'); require('../lights/PointLight.js'); require('../lights/HemisphereLight.js'); require('../math/Math.js'); require('../textures/DataTexture.js'); require('../textures/CompressedTexture.js');

我们需要一些重大的重构,可能将 WebGLRenderer(等)拆分为多个模块(atm 文件超过 6000 行)。

  1. 我们需要为 GLSL 块找到一个解决方案。 Atm这些文件在编译时被编译为THREE.ShaderChunk ,然后在运行时编译为THREE.ShaderLib (将THREE.ShaderChunk的数组连接在一起),这对于仅使用 browserify 来说是相当棘手的。 我认为它需要一个执行相同操作的 browserify 转换。

React.js 使用commoner来查找它们的模块,而不必通过文件路径来引用它们。 也许我们可以做同样的事情并定义自定义规则,允许我们将require GLSL 文件转换为 JS 语法。

@rasteiner你可能很高兴了解https://github.com/stackgl/glslify ,它来自不断增长的http://stack.gl家族

在过去的几个月里,我在模块和“unixy”方法方面有相当多的经验,现在我的想法是太少太晚了,为模块化或 npm 模块重构 Threejs 将是一个不切实际的目标。

这是我目前为解决模块化/可重用性问题所做的工作:

  • 我在 NPM 上放置了一些用于模糊/fxaa/etc 的可重用着色器。 看到这个:
    https://www.npmjs.org/package/three-shader-fxaa (使用与引擎无关的 glsl-fxaa)
  • OrbitController 和 EffectComposer 等可重用组件也将根据需要发布。 例如:
    https://www.npmjs.org/package/three-orbit-controls
  • 这些模块不依赖于“三个”,而是导出一个接受三个的函数,并返回实用程序类。 这样它就可以与全局threejs或commonjs一起使用。
  • 版本控制是一种痛苦。 我正在尝试将我的主要版本与 Threejs 版本号保持一致。 Threejs 的每一个新版本都会引入很多破坏性的变化,所以必须小心处理。
  • 处理数学的模块应该只对数组进行操作并使用模块化的 gl-vec3、gl-mat4 等。这样它们在 Threejs 之外是通用的和有用的。 然后,threejs 用户只需要处理数组的包装/解包。 参见 verlet-system、simple-path 等。
  • 如果我需要一个真正模块化或微小的 webGL 功能,我将使用 stackgl/glslify。 只要您重置 GL 状态,这些也可以在 ThreeJS 中工作。 例如: https :

我的新项目倾向于在 npm 上使用“三个”来启动和运行。 如果 ThreeJS 使用与版本号一致的版本标签将构建正式发布到 npm,那将是非常棒的。

PS:对于那些有兴趣将可重用/模块化着色器引入他们的工作流程的人:
https://gist.github.com/mattdesl/b04c90306ee0d2a412ab

从我的iPhone发送

在二零一四年十一月二十日,在上午07点42分,阿隆[email protected]写道:

@rasteiner你可能很高兴了解https://github.com/stackgl/glslify ,它来自不断增长的http://stack.gl家族


直接回复此邮件或在 GitHub 上查看。

如果它可以帮助可能正在寻找如何将 Three.js 与 browserify 一起使用的其他人,并偶然发现这个线程,我自己设置的方法是使用browserify-shim

在 _“您有时会 a) 通过全局公开全局变量”_ 的自述部分之后,我为 Three.js 包含了一个单独的脚本标记,并将其配置为公开全局变量 THREE。

然后我必须自己解决的一点是如何包含诸如 ColladaLoader、OrbitControls 等附加功能。我是这样做的:

从 package.json:

    "browser": {
        "three": "bower_components/threejs/build/three.js"
    },
    "browserify-shim": "browserify-shim-config.js",
    "browserify": {
        "transform": [ "browserify-shim" ]
    }

browserify-shim-config.js:

    module.exports = {
        "three": { exports: "global:THREE" },
        "./vendor/threejs-extras/ColladaLoader.js": { depends: {"three": null}, exports: "global:THREE.ColladaLoader" },
        "./vendor/threejs-extras/OrbitControls.js": { depends: {"three": null}, exports: "global:THREE.OrbitControls" }
    };

然后在我自己的脚本 main.js 中:

    require('../vendor/threejs-extras/ColladaLoader.js');
    require('../vendor/threejs-extras/OrbitControls.js');

    var loader = new THREE.ColladaLoader(),
        controls = new THREE.OrbitControls(camera);
...

当您修改字节时,Browserify 需要重建整个脚本。 我曾经用browserify打包了一个需要THREE.js的项目,然后每次修改都需要两秒多的时间来构建boundle并阻塞livereload。 这太令人沮丧了。

您通常在使用livereload进行开发期间使用 watchify。 那个以增量方式构建捆绑包。

watchify 对我不起作用。 当我更改一个文件并保存它时,watchify 和 beefy 的 livereload 会提供旧的/缓存版本。 我不知道为什么会发生这种情况。 值得庆幸的是,bro​​wserify 已经运行良好。

@ChiChou 传入--noparse=three到 browserify。 这使我的机器上的 browserify 捆绑步骤从 1000 毫秒减少到 500 毫秒,这对于即时反馈感觉来说已经足够了。

@rasteiner我想再次感谢您对three.js 相互依赖的非正式研究。 虽然庞大的 deps 列表是一些看起来很丑的代码,但实际上这种丑陋是存在的,只是不可见。 Browserify 的优势在于它要求我们晾晒脏衣服并追求不那么复杂的系统。

Three.js 中有很多地方我们接收某个对象,感知它的类型,并根据该类型执行不同的任务。 在大多数情况下,依赖于类型的代码可以移动到类型本身,我们不必了解我们正在操作的所有可能的类型。

以下是来自WebGLRenderer的节略示例:

if ( texture instanceof THREE.DataTexture ) {
  // ...
} else if ( texture instanceof THREE.CompressedTexture ) {
  // ...
} else { // regular Texture (image, video, canvas)
  // ...
}

应该更多的形式

texture.processTexImage( _gl, mipmaps, otherData )

让类型决定如何处理自己。 这也允许库使用者使用我们没有想到的新颖的纹理类型。 这种结构应该减少相互依赖。

我认为转向 browserify 架构绝对是要走的路。 UMD 构建将使使用 THREE.js 更容易。 它还允许我们将 WebGLRenderer 拆分为多个文件,因为现在它看起来相当单一和可怕。

我已经开始了一个分支,我目前正在将它移到这里: https :

请让我知道你的想法。

这是@coballast变化

看起来您正在对browserifyify.js文件采用自动转换方法,我认为这是正确的方法。

我们还没有讨论过的一件事是如何最好地将这个庞大的、不断变化的库过渡到 browserify。 您可以进行更改,然后打开 PR,但它会立即过时。 这就是自动化方法的引人注目之处。

如果我们可以:

  • 提供一个 Three.js src 转换脚本(就像你的browserifyify.js
  • 提供解释转换过程如何工作的文档
  • 提供解释新构建系统如何工作的文档
  • 转换后运行测试
  • 不包括对现有文件的任何可能导致合并冲突的更改

...然后我们可以把它变成一个按钮转换,在可预见的未来仍然有效。 当意识形态争论胜出时,这种简单性使这个梦幻般的基本架构转变为如此大的项目的概念得以实现。

@coballast为此,如果它的工作原理相同,我将删除对 src/Three.js 的更改。

注意:不仅仅是恢复,而是通过新分支或强制推送从分支的历史记录中删除这些更改

@coballast我想知道转换实用程序不是three.js的分支是否更有意义,而是指向three.js开发目录的外部实用程序,它会转换源文件,添加构建脚本并运行测试。

@kumavis我同意单独留下 src 目录。 我认为要做的事情是让实用程序使用 commonjs 代码编写一个重复的目录,我们可以从那里测试和运行 browserify 构建,以确保示例在我们尝试做任何重大事情之前都能正常工作。

这里还有一个有趣的机会来编写一些静态分析的东西,这些东西将自动检查并在整个代码库中强制执行一致的风格。

@coballast听起来很棒。
有大量工具可用于自动代码重写,例如escodegen 。 需要确保我们维护评论等。
想要启动一个 Threejs-conversion-utility 仓库吗?

@coballast说,保持对该实用程序的

@kumavis认为它完成了。 我真的希望这发生。 这只是我目前正在进行的两个项目之一。

@kumavis @mrdoob这里的一些讨论似乎是围绕着将三个模块分成多个单独的模块的想法,这些模块大概可以通过 npm 安装,然后用 browserify 编译。 我并不完全反对这个想法,但我认为这应该是一个单独的问题。 在这一点上,我唯一提倡的是使用 browserify 在内部管理 THREE 的代码库。 把它移过来,让它像对用户一样的方式工作,然后评估什么是有意义的。

我很想知道该实用程序的输出是什么^^

@coballast链接一个 repo 供我们跟踪,即使此时它只是空的。 我们可以从那里建造。

开始了! :火箭:

我现在拥有该实用程序处于生成 browserify src 的状态,并且可以毫无问题地构建它。 我将使用有关如何自己执行此操作的说明更新存储库。 在这一点上,这些示例不起作用。 有几个问题需要解决才能解决。 如果有人想挽起袖子并提供帮助,我会将它们添加到回购中。

@coballast是的,请将问题作为我们所能。

严重的问题出现了。 见#6241

这是我对需要发生什么才能使其起作用的分析: https :

由于它的设计,browserify 至少是传输冗余(conjestive)。 这使得它的使用成本膨胀(任何人都有数据计划?)而且速度很慢。

对此的一个简单补救措施是将文档与库代码分开,这将需要两个客户端文件而不是一个。 这是客户端 js 的常见做法。

如果一开始 browserify 有问题并且本身需要修复,我几乎不明白为什么它甚至应该被考虑改进任何东西,更不用说像 Threejs 这样的东西了。

@spaesani因为无论如何都必须下载

如果出于某种原因您仍然想将“文档与库代码”分开,您仍然可以这样做并像我们现在一样使用预先构建的版本。 您甚至可以使用--standalone 和 --exclude标志将

Browserify 只是一种在浏览器上使用经过战斗验证的模块定义 API (CommonJS) 的方法。 它将极大地简化 Threejs 插件的开发并提高代码清晰度并因此提高生产力,它将使我们能够集成到更大的生态系统(npm)中,其中代码本质上由更多人维护,同时仍然通过版本控制系统保持完整性(想想stackgl家族),如果人们不想要它,它甚至不会强迫人们使用 CommonJS。

当然有缺点,但它们不是你提到的那些。

Three.js 和three.min.js 可以通过代理、通用移动解决方案或缓存浏览器缓存以节省传输(数据)。
当您挑选并聚合 Threejs 代码与文档特定代码时,缓存是不可行的。
如果 browserify 允许一个

相关问题

jlaquinte picture jlaquinte  ·  3评论

yqrashawn picture yqrashawn  ·  3评论

zsitro picture zsitro  ·  3评论

scrubs picture scrubs  ·  3评论

akshaysrin picture akshaysrin  ·  3评论