Three.js: Passer à une architecture modulaire

Créé le 6 mai 2014  ·  153Commentaires  ·  Source: mrdoob/three.js

Parcourir
Le passage à cette architecture présente des avantages et des inconvénients. S'il vous plaît ajouter vos pensées.

Remarque : cela ne nécessite pas que les consommateurs three.js utilisent browserify.

Suggestion

Commentaire le plus utile

Bon, il faudra donc un peu de refactorisation...

Je t'ai eu! Depuis que ce fil s'est animé ces derniers jours, j'ai travaillé un peu plus sur three-jsnext . C'est un projet qui prend la base de code Three.js existante et la transforme automatiquement en modules ES. Je me dispute juste avec quelques dépendances cycliques délicates (en particulier autour de KeyframeTrack ), mais devrait avoir quelque chose à partager très bientôt. Pour autant que je sache, tous les exemples continuent de fonctionner et la version minifiée est plus petite que la version actuelle (en utilisant Rollup pour générer un fichier UMD), donc c'est une bonne nouvelle.

Tous les 153 commentaires

Un avantage est que cela imposerait une architecture modulaire pour le développement continu de three.js.

Le style commun dans node/browserify fait que chaque fichier déclare ses dépendances en haut et considère les variables globales comme un anti-modèle.

Voici un exemple d'extrait :

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

function BoxGeometry() {
  // ...
}

BoxGeometry.prototype = Geometry.prototype;

Un autre avantage est que les consommateurs de three.js utilisant browserify seraient en mesure de choisir les pièces qu'ils souhaitent. Ils pourraient simplement importer Scene , BoxGeometry , PerspectiveCamera , et WebGLRenderer , obtenir les dépendances pour tous ceux-ci automatiquement ( Object3D etc ), et ont un petit paquet de javascript qui prend en charge uniquement l'ensemble de fonctionnalités qu'ils souhaitent.

Cela pourrait être fait d'une manière qui n'impose aucun changement de rupture. Au niveau supérieur, nous exporterions toutes les classes que nous considérons comme faisant partie du package standard

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

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

note : je n'exige pas exactement les dépendances en haut dans cet exemple, car ce fichier serait presque exclusivement des instructions require.

Enfin, nous encapsulerions cela dans une définition de module universelle qui détecte si un système de module (nœud/navigateur, AMD) est en cours d'utilisation, et si c'est le cas, l'exporte ou l'ajoute à l'objet global ( window ).

Revoyons:

  • applique un bon style modulaire
  • permet aux consommateurs three.js utilisant browserify de choisir des fonctionnalités
  • pas de changements de rupture

Cela nécessiterait de remplacer le système de construction, mais le nouveau serait assez simple.

Quelques autres avantages :

  • Vous pouvez structurer votre code
  • Vous pouvez créer/réutiliser des modules sans polluer l'espace de noms global
  • Vous pouvez construire pour la production
  • Vous pouvez déboguer plus facilement puisque chaque module a son propre fichier, vous n'avez pas besoin de rechercher le module correspondant dans un gros fichier three.js

@shi-314 Je suppose que je suis un peu confus, j'ai l'impression que You can structure your code et You can build for production sont des choses que vous pouvez faire sans le changement architectural ? Parlez-vous de la source de three.js ou de choses construites à l'aide de three.js ?

Une pratique que three.js utilise et qui la rend difficile à utiliser dans les environnements commonjs est l'utilisation de instanceof : https://github.com/mrdoob/three.js/blob/master/src/core/Geometry .js#L82

En effet, dans une application, vous vous retrouvez souvent avec différentes versions de la même bibliothèque dans votre arborescence source, donc la vérification d'instanceof ne fonctionne pas entre différentes versions de la même bibliothèque. Il serait bon, en prévision d'un passage à un système de modules commonjs, de remplacer ces instances de vérification par une vérification des fonctionnalités derrière une interface de style Geometry.isGeometry(geom) .

@kumavis Je parle de choses construites dans three.js. Supposons que vous souhaitiez créer votre propre matériel avec vos shaders, etc. Pour le moment, vous devez étendre l'objet global THREE pour rester cohérent avec le reste du code three.js :

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

Mais si nous avions Browserify que vous ne pourriez faire :

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

Ainsi, votre espace de noms reste cohérent et vous n'avez pas besoin d'utiliser THREE.MeshLambertMaterial et MeshMyCoolMaterial dans votre code.

Et avec You can build for production je voulais dire en gros la même chose que vous avez mentionnée : allows three.js consumers using browserify to pick and choose functionality .

@shi-314 merci, c'est plus clair. Cela a un impact sur ma solution générale proposée pour la désérialisation des classes définies par le consommateur :

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

Ceci provient de mon refactor de sérialisation / désérialisation proposé
https://github.com/mrdoob/three.js/pull/4621

Les performances ne devraient pas être affectées par un changement comme celui-ci.

C'est un changement assez énorme, mais je suis également en faveur de cela.

Quelques autres avantages majeurs :

  • Vous pouvez utiliser l'option standalone browserify pour générer une version UMD pour vous. Pas besoin de bricoler manuellement avec les wrappers UMD.
  • Le package peut facilement être consommé par les utilisateurs de browserify/NPM
  • L'extraction de dépendances pour threejs (comme poly2tri, color-string , etc.) devient beaucoup plus facile
  • Les modules qui "n'appartiennent pas vraiment" à une bibliothèque de rendu (comme les bibliothèques vectorielles/mathématiques) peuvent être extraits en tant que modules NPM séparés et réutilisés pour de nombreux autres types de projets. L'un des principaux avantages de ceci est que les modules individuels ont leur propre référentiel pour les bogues/problèmes, les PR, etc. (nettoyage des problèmes de ThreeJS).
  • NPM s'occupera de la gestion des versions sémantiques pour nous. Par exemple, nous pouvons pousser un changement radical dans le threejs-vecmath sans nous soucier de la rupture de code de tout le monde. Et d'un autre côté, si nous faisons un correctif ou une version mineure dans un module particulier, les personnes utilisant ces modules pourront obtenir les modifications automatiquement.
  • Cela rend les "extras" comme EffectComposer et divers shaders faciles à emballer et à consommer (imaginez npm install threejs-shader-bloom )
  • Au fur et à mesure que les modules sont retirés, la taille de distribution finale commencera à devenir plus petite et plus spécifique à l'application. Il n'y aura finalement pas besoin de différents "types de builds" puisque nous allons juste require() les modules que notre application utilise réellement.

À @mrdoob et aux autres auteurs ; si vous n'avez pas beaucoup d'expérience avec NPM/Browserify, je vous suggère de faire quelques petits projets avec et de vous faire une idée de sa "philosophie". C'est très différent de l'architecture ThreeJS ; plutôt que de grands frameworks, il encourage beaucoup de petites choses .

Un autre avantage de cette approche est qu'il peut y avoir un écosystème de modules tiers open source, Three.JS, en particulier des shaders, des géométries, des chargeurs de modèles, etc. Publié via NPM ou Github/Component que les gens peuvent ensuite facilement référencer et utiliser. Pour le moment, les choses sont partagées en hébergeant une démo sur laquelle les gens 'visualisent la source'. Three.JS mérite mieux !

Je pense que l'un des problèmes que j'ai avec Three.JS est la rapidité avec laquelle le code devient incompatible avec la version actuelle de Three.JS. Un autre avantage de passer à quelque chose comme ça est de pouvoir spécifier des versions spécifiques de _bits_ de Three.JS serait très puissant et pratique.

+1

+1 pour une architecture CommonJS/browserify, cela rendrait le noyau plus léger et les extensions s'adapteraient même si elles proviennent de tiers

La fragmentation de three.js en petits modules a également beaucoup de coûts. Le système actuel permet des addons tiers assez simples (témoin par exemple les modules THREEx de jetienne). Il y a beaucoup à dire sur la simplicité de la configuration actuelle, tant que les systèmes de modules JS ne sont que des enveloppes autour des systèmes de construction.

Une autre façon de minimiser la taille de la construction est ce que fait ClojureScript. Ils suivent certaines conventions pour permettre au compilateur Closure de Google d'effectuer une analyse de l'ensemble du programme et l'élimination du code mort.

+1 pour l'élégance de la simplicité méconnue et souvent négligée

+1

La fragmentation de three.js en petits modules a également beaucoup de coûts. Le système actuel permet des addons tiers assez simples (témoin par exemple les modules THREEx de jetienne).

L'idée ici est qu'une version UMD serait toujours fournie pour les environnements non-Node. Des plugins comme THREEx fonctionneraient de la même manière pour ceux qui dépendent de ThreeJS avec de simples balises <script> .

Le plus délicat sera : comment require() un plugin particulier si nous sommes dans un environnement CommonJS ? Peut-être que browserify-shim pourrait vous aider.

Il y a beaucoup à dire sur la simplicité de la configuration actuelle, tant que les systèmes de modules JS ne sont que des enveloppes autour des systèmes de construction.

Le système de plugin/extension actuel de ThreeJS est assez horrible à utiliser, et loin d'être "simple" ou facile. La plupart des projets ThreeJS ont tendance à utiliser une forme de plugin ou d'extension, comme EffectComposer, ou FirstPersonControls, ou un chargeur de modèle, ou l'un des nombreux autres fichiers JS flottant dans le dossier examples . À l'heure actuelle, la seule façon de dépendre de ces plugins :

  • Téléchargez la version actuelle de ThreeJS
  • Copiez-collez les fichiers nécessaires dans votre dossier vendor
  • Câblez des tâches gulp/grunt pour concaténer et réduire tous les plugins dont vous avez besoin ; en veillant à les concaté _dans le bon ordre_ sinon les choses vont casser. Maintenez manuellement cette liste au fur et à mesure que vous ajoutez d'autres plugins.
  • Répétez les étapes 1 et 2 à chaque mise à jour de ThreeJS ; puis arrachez vos cheveux lorsque vous réalisez que le nouveau code n'est pas rétrocompatible

Maintenant, imaginez, avec browserify, vous pourriez faire quelque chose comme ceci :

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

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

Ces plugins seront require('threejs') et tout ce dont ils pourraient avoir besoin (comme les extraits de code GLSL ou la triangulation de texte ). La gestion des dépendances/versions est entièrement cachée à l'utilisateur, et il n'est pas nécessaire de gérer manuellement les tâches de concat de grunt/gulp.

Le plus délicat sera : comment exiger () un plugin particulier si nous sommes dans un environnement CommonJS ?

J'utilise CommonJS pour les projets THREE.js depuis un certain temps maintenant. C'est un peu un processus manuel, la conversion de morceaux de code d'autres personnes en modules et je ne pense pas qu'il y aura un moyen facile d'éviter cela pour le code hérité qui n'est pas converti par les auteurs ou les contributeurs.

Le point important est qu'il existe un module exportant l'intégralité de l'objet THREE « standard », qui peut ensuite être requis par tout ce qui souhaite l'étendre.

var THREE = require('three');

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

Cela a plutôt bien fonctionné pour moi, d'autant plus que le projet grandit et que je commence à ajouter mes propres shaders et géométries dans leurs propres modules, etc.

Tant qu'il existe un package npm 'threejs-full' ou 'threejs-classic', cela devient un moyen assez viable de travailler avec d'anciens éléments Three.js dans un environnement CommonJS, mais je soupçonne que c'est une niche!

+1
Je crois qu'une fois les modules threejs fragmentés sont disponibles dans npm, plugin
les développeurs adoreront migrer vers CommonJS env.
Le 5 juin 2014 à 21h19, "Charlotte Gore" [email protected] a écrit :

Le plus délicat sera : comment exiger () un plugin particulier si nous
sont dans un environnement CommonJS ?

J'utilise CommonJS pour les projets THREE.js depuis un certain temps maintenant. C'est un peu
d'un processus manuel, convertissant des morceaux de code d'autres personnes en modules
et je ne pense pas qu'il y aura un moyen facile d'éviter cela pour le code hérité
qui n'est pas converti par les auteurs ou contributeurs.

L'important est qu'il y ait un module exportant l'intégralité du "standard"
TROIS objet, qui peut alors être requis par tout ce qui souhaite étendre
ce.

var TROIS = require('trois');
THREE.EffectComposer = // ... etc, en n'oubliant pas d'inclure les mentions de copyright :)

Cela a plutôt bien fonctionné pour moi, d'autant plus que le projet grandit et que je
commencer à ajouter mes propres shaders et géométries dans leurs propres modules, etc.

Tant qu'il y a un package npm 'threejs-full' ou 'threejs-classic' alors
cela devient un moyen assez viable de travailler avec d'anciens éléments Three.js dans un
Environnement CommonJS mais je soupçonne que c'est un joli créneau !

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/mrdoob/three.js/issues/4776#issuecomment -45236911.

Cela pourrait également rendre les shaders modulaires, par exemple en utilisant glslify . Même des choses comme la création d'un middleware Express qui génère des shaders à la demande deviennent alors plus faciles.

Il y a quelques mois, j'ai déplacé frame.js vers require.js et j'ai enfin compris comment fonctionnait ce matériel AMD.

J'ai encore besoin d'apprendre, cependant, comment "compiler" ceci. Quel est l'outil/workflow pour générer un three.min.js partir d'une liste de modules ?

Je préfère gulp.js comme système de build avec le plugin gulp-browserify . C'est vraiment facile à comprendre et le code semble plus propre que grunt à mon avis. Vérifiez ceci : http://travismaynard.com/writing/no-need-to-grunt-take-a-gulp-of-fresh-air :wink:

quelques réflexions : (basé sur mon expérience limitée avec node, npm, browserify bien sûr)

  1. je pense que les modules node.js sont géniaux (c'est npm, modules et require()s)
  2. Je pense que browserify est aussi génial

cela dit, suite à la discussion sur ce fil, je ne sais pas si tout le monde avait la même compréhension de browserify (browserify, commonjs, requirejs, amd, umd sont quelque peu liés bien qu'ils ne soient pas nécessairement la même chose).

maintenant si vous pouvez suivre un peu ma chaîne de pensées.

  1. JS est génial, il fonctionne rapidement sur tous les navigateurs.
  2. wow, maintenant JS fonctionne aussi côté serveur.
  3. c'est node.js, c'est cool, alors codons des trucs dans node.js
  4. mais je ne veux pas écrire/je ne peux pas tout écrire/trouver quelque chose à utiliser.
  5. pas de soucis, lancez maintenant les modules d'installation npm
  6. maintenant besoin de ces modules sympas pour que nous puissions les utiliser.
  7. fonctionne très bien!
  8. maintenant attendez, nous venons d'écrire tout un tas de trucs en JS qui s'exécute sur node.js
  9. js n'est-il pas supposé dans les navigateurs ? comment faire pour que ces codes s'exécutent à nouveau là-bas ?

C'est là que Browserify entre en jeu. Eh bien, techniquement, on peut utiliser requireJS dans le navigateur. Mais vous souhaitez regrouper les fichiers js sans faire trop d'appels réseau (contrairement aux exigences du système de fichiers () qui sont rapides). C'est là que Browserify fait des trucs sympas comme l'analyse statique pour voir quels modules doivent être importés et crée des builds qui sont plus optimisés pour votre application. (Il y a bien sûr des limitations, il ne peut probablement pas analyser require('bla' + variable)) il peut même échanger des parties qui nécessitent une couche d'émulation pour les éléments dépendants de node.js. oui, il génère une version js que je peux maintenant inclure dans mon navigateur.

Voici quelques-unes des choses que browserify peut faire https://github.com/substack/node-browserify#usage

On dirait que tout va bien jusqu'à présent... mais il y a quelques points que j'ai pensé qu'il valait la peine de considérer que nous passons à une "navigation architecturale"

  • il doit y avoir un changement d'état d'esprit pour les développeurs de three.js (le système de module requis doit être utilisé bien sûr)
  • une couche de compatibilité peut être construite afin que les utilisateurs de three.js puissent toujours utiliser three.js à l'ancienne sans profiter des avantages modulaires
  • pour pouvoir produire des builds optimisés, les utilisateurs de three.js devraient passer au système require
  • le nouveau processus de construction impliquerait probablement la chaîne d'outils browserify (actuellement, nous pourrions utiliser python, node.js, ou un simple copier-coller, etc.) ou certains outils requireJS.
  • si nous voudrions que three.js soit vraiment plus modulaire, avec une gestion des versions sur chaque composant, comme par exemple TrackballControls, nous aurions besoin de les séparer, ce qui pourrait conduire à une fragmentation
  • cela pourrait également conduire à la diversité, mais une force de three.js semble actuellement être un point centralisé de nombreuses extensions

Donc, si nous voyons que cette diversité et ce chargement de module pratique (principalement sur l'écosystème npm) ainsi que des versions personnalisées sont une bonne chose, alors cela pourrait valoir la peine d'avoir un changement de paradigme, de refactoriser le code et de changer notre système de construction actuel.

@mrdoob certains outils autour de browserify sont répertoriés ici : https://github.com/substack/node-browserify/wiki/browserify-tools.

concernant le three.min.js , vous n'utiliseriez pas le code minifié dans votre projet. tout ce que vous faites est de var three = require('three') dans votre project.js , puis exécutez browserify project.js > bundle.js && uglifyjs bundle.js > bundle.min.js . Remarque : vous pouvez toujours envoyer du code minifié pour <script src="min.js"> .

j'enveloppe actuellement three.js avec

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

et

module.exports = THREE

puis j'enveloppe les extensions avec

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

donc je peux l'exiger comme ça:

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

ce n'est donc pas optimal, mais personnellement, je peux vivre avec et je pense que ce n'est pas si mal - bien que la proposition de

mais peut-être qu'il serait logique d'en diviser trois et de mettre toutes les choses dans des modules séparés juste pour voir comment cela fonctionnerait.

consultez également http://modules.gl/ qui est fortement basé sur browserify (bien que vous puissiez utiliser chaque module seul sans browserify).

@mrdoob @shi-314 gulp-browserify a été mis sur liste noire en faveur de l'utilisation directe de browserify (c'est-à-dire via vinyl-source-stream).

Des outils comme grunt/gulp/etc sont en constante évolution et vous trouverez de nombreuses opinions divergentes. En fin de compte, peu importe ce que vous choisissez, ou si vous le faites simplement avec un script personnalisé. Les questions les plus importantes sont les suivantes : comment les utilisateurs consommeront-ils ThreeJS et quel degré de compatibilité ascendante souhaitez-vous maintenir ?

Après réflexion, je pense qu'il sera _vraiment_ difficile de tout modulariser sans refactoriser complètement le framework et son architecture. Voici quelques problèmes :

  • Tout le code de l'espace de noms doit changer pour les exportations/exigences CommonJS. C'est une entreprise assez énorme et il y aurait beaucoup de ../../../math/Vector2 laids, etc.
  • Dans un monde idéal, la bibliothèque serait fragmentée, donc three-scene serait découplé de three-lights etc. Ensuite, vous pouvez versionner chaque package séparément. Ce type de fragmentation semble irréaliste pour un framework aussi grand que ThreeJS, et serait un casse-tête à maintenir.
  • Si nous _ne fragmentons pas_ le framework en minuscules composants, alors le versionnage sémantique sera un cauchemar. Un petit changement de rupture n'importe où dans le framework nécessiterait une modification majeure de la version pour l'ensemble. Et consommer l'API serait assez moche : require('three/src/math/Vector2')

Ma suggestion? Nous considérons deux choses pour l'avenir :

  1. Commencer petit; extrayez quelques fonctionnalités essentielles et réutilisables telles que Vector/Quaternion, les conversions de couleurs, la triangulation, etc. Ces éléments sont de bons candidats pour NPM car ils sont utiles en dehors de la portée de ThreeJS. Ils peuvent également avoir leur propre suite de tests, gestion des versions et suivi des problèmes.
  2. Lorsqu'un nouveau code doit être ajouté à ThreeJS, comme une nouvelle fonctionnalité ou une dépendance (par exemple poly2tri/Tess2), envisagez de le retirer en tant que module séparé et d'en dépendre via NPM.

J'aimerais que tout soit modularisé, mais je ne suis pas sûr d'une approche réaliste pour ThreeJS. Peut-être que quelqu'un devrait faire des expériences dans une fourchette pour voir à quel point les choses sont réalisables.

Merci pour les explications les gars !

Ce que je crains, c'est de compliquer les choses pour les gens qui commencent tout juste. Les forcer à apprendre ce truc de browserify/modules n'est peut-être pas une bonne idée...

Je devrais être d'accord avec

Avec un UMD précompilé ( browserify --umd ) dans le référentiel, il n'y a aucun changement dans le flux de travail pour les développeurs existants.

@mrdoob L'idée d'un système de gestion des dépendances est la simplicité. Lire des dizaines de messages sur les options et les systèmes de construction peut être écrasant, mais en fin de compte, le système actuel n'est pas durable. Chaque fois qu'un fichier dépend d'un autre, c'est une chasse et une recherche que tout nouveau développeur doit effectuer pour trouver une référence. Avec browserify, la dépendance est explicite et il existe un chemin vers le fichier.

@repsac Un système de dépendances devrait rendre Three plus accessible aux utilisateurs d'autres langues car il évite la portée globale, les cauchemars d'ordre de chargement et suit un paradigme similaire à d'autres langues populaires. var foo = require('./foo'); est (loosly) semblable à C # 's using foo; ou Java de import foo;

J'aimerais que tout soit modularisé, mais je ne suis pas sûr d'une approche réaliste pour ThreeJS. Peut-être que quelqu'un devrait faire des expériences dans une fourchette pour voir à quel point les choses sont réalisables

Je pense que c'est la voie à suivre, vraiment. Faites le travail, montrez comment cela fonctionne.

Et consommer l'API serait assez ugly: require('three/src/math/Vector2')

À titre d'expérience, je viens de convertir le « démarrage » des trois documents à cette nouvelle approche modulaire. Je peux imaginer qu'il y ait beaucoup de références à moins que les gens ne soient assez stricts pour diviser leur code en modules minuscules.

Le principal avantage de cela serait que la taille de construction résultante serait une infime fraction de la taille du fichier Three.js complet, car vous n'incluriez que les éléments spécifiquement référencés ici, puis les éléments dont ces éléments dépendent.

Je suppose que référencer toutes les dépendances dont vous avez besoin (et les installer toutes individuellement) pourrait s'avérer un peu trop horrible dans la pratique.

Si vous ciblez explicitement les appareils mobiles, cette approche hautement granulaire serait parfaite, mais en réalité, je soupçonne que nous aurons besoin de packages qui exportent l'intégralité des TROIS API qui fonctionneront normalement, puis de packages plus petits qui encapsulent toute la géométrie bonus, tous les les moteurs de rendu, tous les calculs, tous les matériaux, etc., puis jusqu'au niveau du module individuel afin que les développeurs puissent décider eux-mêmes.

Et oui, coder pour le Web est pénible.

Quoi qu'il en soit, continuons l'expérience...

Installer nos dépendances..

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

Écrivez notre code...

// 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();

puis construisons notre fichier

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

alors incluez-le dans notre page

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

Je suppose que référencer toutes les dépendances dont vous avez besoin (et les installer toutes individuellement) pourrait s'avérer un peu trop horrible dans la pratique.

L'utilisateur final n'a pas nécessairement besoin d'utiliser browserify dans son projet pour que Three utilise browserify pour gérer sa base de code. Trois peuvent être exposés en tant que THREE global tel qu'il est actuellement... incluez le fichier de construction et exécutez-le.

@repsac @mrdoob les modifications seraient rétrocompatibles, de sorte que les utilisateurs actuels n'aient pas besoin de modifier quoi que ce soit s'ils ne le souhaitent pas. Ces suggestions visent à améliorer la maintenabilité et la longévité à long terme de la base de code tentaculaire et monolithique de ThreeJS. Des choses comme la gestion des dépendances et des versions peuvent sembler un casse-tête pour les non-initiés, mais elles sont géniales pour ceux qui développent des outils, des frameworks, des plugins et des sites Web à grande échelle au-dessus de ThreeJS.

c'est-à-dire que le code de l'utilisateur final peut toujours avoir le même aspect, et le examples n'a pas besoin de changer du tout :

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

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

Pour les développeurs plus ambitieux qui recherchent une version modulaire, _ou_ pour ceux qui cherchent à développer des solutions à long terme sur ThreeJS (c'est-à-dire à tirer parti de la gestion des versions/dépendances), cela pourrait ressembler davantage à ceci :
npm install three-vecmath --save

Ensuite, dans le code :

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

//.. do something with Vector2

Et, en outre, cela permet aux gens d'utiliser des choses comme les mathématiques vectorielles de ThreeJS, les conversions de couleurs, la triangulation, etc. en dehors de la portée de ThreeJS.

Même si je pense que le désordre require() est une mauvaise idée et un mauvais compromis, ce serait une idée encore pire d'exposer les utilisateurs à deux types différents de code three.js, en leur disant que l'un est la saveur du système de module de fantaisie et l'autre est le saveur de système de module plus simple (mais de seconde classe).

@erno Je pense que vous avez manqué le point, three.js serait organisé par une structure de module en interne, mais qui est utilisé pour produire un fichier de construction qui n'est pas différent de la configuration actuelle.

Le principal gain est d'améliorer l'expérience de développement et de maintenance de three.js .

@kumavis - non @erno n'a pas manqué cela, mais j'ai compris (*) qu'il fait remarquer que si three.js est parfois utilisé via require et parfois non, cela peut être déroutant. Par exemple, quelqu'un regarde à la fois les trois sources, puis des exemples tiers et rencontre des différences dans la façon dont tout cela est et fonctionne.

(*) nous en avons parlé sur irc plus tôt dans la journée.

Je pense que c'est une sorte de point valable, mais je ne sais pas si / comment cela fonctionne à la fin - s'il s'agit vraiment d'un problème avec les utilisations du module et de la construction. Mais cela semble certainement mériter une réflexion et dans l'ensemble, il m'a semblé bon que la question globale ait été examinée attentivement ici, merci pour les informations et les points de vue si éloignés de ma part.

@antont je vois. Les gens ont suggéré une variété d'approches différentes ici, je supposais que nous fournirions principalement de la documentation pour une utilisation de haut niveau (en retirant tout de THREE ), mais d'autres peuvent créer des exemples qui ne suivraient pas cela et cela peut conduire à une certaine confusion. Et c'est une préoccupation valable.

Je pense que j'étais un peu confus par la langue.

et l'autre est la saveur du système de modules plus simple (mais de seconde classe).

Cela fait juste référence au fichier de construction, oui?

À ma connaissance, oui. Je ne peux pas imaginer quoi d'autre, mais il peut manquer quelque chose.

antont, kumavis : Les propositions ici ont parlé d'exposer le code de style require() aux utilisateurs finaux également, voir par exemple. le dernier commentaire de mattdesl.

"Pour les développeurs plus ambitieux qui recherchent une construction modulaire, ou pour ceux qui cherchent à développer des solutions à long terme sur ThreeJS (c'est-à-dire tirer parti de la gestion des versions/dépendances) [...]"

une façon d'avoir des versions plus optimisées est en fait d'avoir un script qui détermine automatiquement vos dépendances et génère les modules requis.

pour le moment, bower & browserify se débarrasse de require, mais ce ne sont pas les seules solutions. Je ne sais pas s'il existe d'autres projets open source prêts à l'emploi qui le font (peut-être comme les dépendances ng), mais j'ai déjà écrit de tels outils avant que je pense qu'il y aurait d'autres approches pour résoudre ces problèmes.

le compilateur de fermeture de google pourrait être un tel outil ?

Du côté de l'utilisateur, cela peut-il être utile ?
http://marcinwieprzkowicz.github.io/three.js-builder/

c'est assez intéressant @erichlof :) je me demande si @marcinwieprzkowicz a généré cela à la main... https://github.com/marcinwieprzkowicz/three.js-builder/blob/gh-pages/threejs-src/r66/modules.json

Une pratique que three.js utilise et qui la rend difficile à utiliser dans les environnements commonjs est l'utilisation d'instanceof : https://github.com/mrdoob/three.js/blob/master/src/core/Geometry.js#L82

En effet, dans une application, vous vous retrouvez souvent avec différentes versions de la même bibliothèque dans votre arborescence source, donc la vérification d'instanceof ne fonctionne pas entre différentes versions de la même bibliothèque. Il serait bon, en préparation d'un passage à un système de module commonjs, de remplacer ces instances de vérifications par une vérification des fonctionnalités derrière une interface de style Geometry.isGeometry(geom).

dans git/three.js/src :

grep -r instanceof . | wc -l 
164

dans git/three.js/examples :

grep -r instanceof . | wc -l 
216

il y a donc au total 380 utilisations de instanceof dans three.js. Quelle serait la meilleure implémentation en remplacement ?

J'ai récemment ajouté une propriété type qui peut être utilisée pour remplacer la plupart d'entre elles.

J'ai récemment ajouté une propriété de type qui peut être utilisée pour remplacer la plupart d'entre elles.

joli! Préparera un PR.

Pour un exemple de la façon dont cela est géré dans une autre bibliothèque JS populaire et importante, consultez https://github.com/facebook/react . La base de code est structurée à l'aide du système de module basé sur le style de nœud (que browserify implémente) mais est conçue pour être publiée à l'aide de grunt. Cette solution est flexible pour 3 cas d'utilisation.

  1. Le pur consommateur de Three.js écrivant vanilla JS peut toujours utiliser le fichier de construction comme toujours. Il n'y a pas de changement pour ce cas d'utilisation.
  2. Le consommateur de Three.js utilisant browserify peut déclarer Three.js comme dépendance dans un projet et seulement require dépendances spécifiques. Les avantages d'une bonne gestion de la dépendance ont été bien documentés.
  3. Contribuer à Three.js devrait maintenant être plus simple car les dépendances entre les composants sont explicitement documentées.

J'ai fait quelques recherches...

Hier, j'ai piraté un script (plutôt stupide) qui transforme le code source Three.js pour utiliser les instructions CommonJS require() pour déclarer les dépendances entre les fichiers. Juste pour voir ce qui se passe... Ceci :

  1. Il finit par avoir des instructions require assez ridicules comme celle-ci (de 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');

Nous aurions besoin d'une refactorisation majeure, peut-être en divisant WebGLRenderer (et autres) en plusieurs modules (atm, le fichier fait plus de 6000 lignes).

  1. Nous devons trouver une solution pour les morceaux GLSL. Atm, ces fichiers sont compilés dans THREE.ShaderChunk au moment de la compilation, puis dans THREE.ShaderLib au moment de l'exécution (rejoindre des tableaux de THREE.ShaderChunk s), ce qui est assez difficile à faire avec uniquement browserify. Je suppose que cela nécessiterait une transformation browserify qui fait la même chose.

React.js utilise commoner pour rechercher leurs modules sans avoir à s'y référer par chemin de fichier. Peut-être pourrions-nous faire de même et définir des règles personnalisées qui nous permettent de require fichiers GLSL en les transformant en syntaxe JS.

@rasteiner, vous serez peut-être très heureux d'en savoir plus sur https://github.com/stackgl/glslify , cela vient de la famille grandissante http://stack.gl

J'ai eu pas mal d'expérience avec les modules et l'approche "unixy" au cours des deux derniers mois et je pense maintenant qu'il est trop peu trop tard, et refactoriser threejs pour la modularité ou les modules npm serait un objectif irréaliste.

Voici ce que je fais actuellement pour résoudre le problème de modularité/réutilisabilité :

  • Je mets des shaders réutilisables pour blur/fxaa/etc sur NPM. Regarde ça:
    https://www.npmjs.org/package/three-shader-fxaa (qui utilise le moteur agnostique glsl-fxaa)
  • des composants réutilisables comme OrbitController et EffectComposer sont également publiés selon les besoins. Par exemple:
    https://www.npmjs.org/package/three-orbit-controls
  • au lieu de dépendre de "trois", ces modules exportent une fonction qui prend TROIS, et renvoie la classe utilitaire. De cette façon, il peut être utilisé avec des threejs globaux ou des commonjs.
  • la gestion des versions est une douleur. J'essaie d'aligner mes versions principales avec les numéros de version de threejs. Chaque nouvelle version de threejs introduit de nombreux changements de rupture, elle devra donc être gérée avec précaution.
  • les modules qui traitent des mathématiques devraient simplement fonctionner sur des tableaux et utiliser des modules modulaires gl-vec3, gl-mat4, etc. De cette façon, ils sont génériques et utiles en dehors de threejs. Ensuite, les utilisateurs de threejs n'auront plus qu'à gérer l'emballage/développement dans des tableaux. Voir verlet-system, simplifier-path, etc.
  • si j'ai besoin d'une fonctionnalité webGL vraiment modulaire ou minuscule, j'utilise stackgl/glslify à l'avenir. Ceux-ci peuvent également fonctionner dans ThreeJS tant que vous réinitialisez l'état GL. Par exemple : https://www.npmjs.org/package/gl-sprite-text

Mes nouveaux projets ont tendance à utiliser "trois" sur npm juste pour être opérationnels. Ce serait vraiment génial si ThreeJS publiait officiellement la version sur npm en utilisant des balises de version qui s'alignent sur les numéros de version.

PS : à ceux qui souhaitent intégrer des shaders réutilisables/modulaires à leur flux de travail :
https://gist.github.com/mattdesl/b04c90306ee0d2a412ab

Envoyé de mon iPhone

Le 20 novembre 2014, à 7h42, aaron [email protected] a écrit :

@rasteiner, vous serez peut-être très heureux d'en savoir plus sur https://github.com/stackgl/glslify , cela vient de la famille grandissante http://stack.gl

-
Répondez directement à cet e-mail ou consultez-le sur GitHub.

Au cas où cela aiderait d'autres personnes qui pourraient chercher à utiliser Three.js avec browserify, et tomber sur ce fil, la façon dont je viens de le configurer moi-même est d'utiliser browserify-shim .

Suite à la section README sur _"Vous allez parfois a) Exposer des variables globales via global"_, j'ai inclus une balise de script distincte pour Three.js et l'ai configurée pour exposer la variable globale THREE.

Et puis le peu que j'ai dû travailler moi-même était de savoir comment inclure des extras comme ColladaLoader, OrbitControls, etc. J'ai fait comme ceci:

À partir de 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" }
    };

Ensuite, dans mon propre script, 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 nécessite de reconstruire l'intégralité du script lorsque vous modifiez même sur des octets. Une fois, j'utilise browserify pour emballer un projet qui nécessite THREE.js, puis il faut plus de deux secondes pour créer une limite et bloquer le livereload à chaque fois que j'apporte une modification. C'est trop frustrant.

Vous utilisez normalement watchify pendant le développement avec livereload. Celui-là construit le bundle progressivement.

watchify ne fonctionne pas pour moi. Lorsque je modifie un fichier et que je l'enregistre, watchify et livereload de beefy servent l'ancienne version/mise en cache. Je ne sais pas pourquoi cela se produit. Heureusement, browserify fonctionne déjà assez bien.

@ChiChou Passez --noparse=three pour naviguer. Cela fait passer l'étape du bundle de navigateur de 1000 ms à 500 ms sur ma machine, ce qui est assez décent pour une sensation de retour instantanée.

@rasteiner Je tiens à vous remercier à nouveau pour vos recherches informelles sur les interdépendances de three.js. Bien que cette liste massive de deps soit un code d'apparence moche, cette laideur est vraiment présente telle quelle, juste invisible. La force de Browserify est qu'il nous oblige à aérer notre linge sale et à rechercher des systèmes moins enchevêtrés.

Il y a beaucoup d'endroits dans Three.js où nous prenons un objet, percevons son type et effectuons différentes tâches en fonction de ce type. Dans la plupart de ces cas, ce code dépendant du type pourrait être déplacé vers le type lui-même, et nous n'aurions pas besoin de comprendre tous les types possibles sur lesquels nous opérons.

Ce qui suit est un exemple abrégé de WebGLRenderer :

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

devrait être plus de la forme

texture.processTexImage( _gl, mipmaps, otherData )

Laissez le type déterminer comment se gérer lui-même. Cela permet également au consommateur de bibliothèque d'utiliser de nouveaux types de texture auxquels nous n'avions pas pensé. Cette structure devrait réduire l'interdépendance.

Je pense que passer à une architecture de navigateur est définitivement la voie à suivre. La version UMD facilitera la consommation de THREE.js. Cela nous permettra également de diviser le WebGLRenderer en plusieurs fichiers, car pour le moment, il semble plutôt monolithique et effrayant.

J'ai lancé une branche où je travaille actuellement à la déplacer ici : https://github.com/coballast/three.js/tree/browserify-build-system

S'il vous plait, faite moi part de votre avis.

Voici les changements de @coballast .

Il semble que vous adoptiez l'approche de conversion automatisée avec votre fichier browserifyify.js , ce qui, je pense, est la bonne voie à suivre.

Une chose dont nous n'avons pas encore beaucoup discuté est la meilleure façon de faire la transition de cette grande bibliothèque en constante évolution vers browserify. Vous pouvez apporter les modifications, puis ouvrir un PR, mais il sera immédiatement obsolète. C'est ce qui est convaincant dans l'approche automatisée.

Si nous pouvons:

  • fournir un script de conversion src three.js (comme votre browserifyify.js )
  • fournir un document qui explique le fonctionnement du processus de conversion
  • fournir un document qui explique comment fonctionne le nouveau système de construction
  • exécuter des tests après la conversion
  • ne pas inclure de modifications aux fichiers existants qui pourraient conduire à un conflit de fusion

... alors nous pouvons transformer cela en une conversion à bouton-poussoir qui fonctionnera toujours dans un avenir prévisible. cette simplicité permet d'accomplir cette notion onirique d'un changement d'architecture fondamentale vers un projet d'une telle envergure lorsque les arguments idéologiques l'emportent.

@coballast à cette fin, je supprimerais les modifications apportées à src/Three.js si cela fonctionne de la même manière.

Remarque : non seulement revenir en arrière, mais supprimer ces modifications de l'historique de la branche via une nouvelle branche ou une poussée forcée

@coballast Je me demande s'il serait plus logique que l'utilitaire de conversion ne soit pas un fork de three.js , mais un utilitaire externe que vous pointez sur le dossier three.js développement

@kumavis Je suis d'accord pour laisser le répertoire src seul. Je pense que la chose à faire est de demander à l'utilitaire d'écrire un répertoire en double avec le code commonjs, et nous pouvons tester et exécuter la version browserify à partir de là pour nous assurer que tous les exemples fonctionnent avant d'essayer de faire quoi que ce soit de majeur.

Il existe également une opportunité intéressante ici d'écrire des éléments d'analyse statique qui vérifieront et appliqueront automatiquement un style cohérent dans l'ensemble de la base de code.

@coballast sonne bien.
Il existe une multitude d'outils pour la réécriture de code automatisée, par exemple escodegen . Nous devons nous assurer que nous maintenons les commentaires, etc.
Vous voulez démarrer un dépôt threejs-conversion-utility ?

@coballast cela dit, il est important de rester concentré sur cet utilitaire

@kumavis Considérez que c'est fait. Je veux vraiment que cela se produise. Ce n'est qu'un des deux projets sur lesquels je travaille en ce moment.

@kumavis @mrdoob Une partie de la discussion ici semble

Je serai curieux de voir quel est le résultat de cet utilitaire ^^

@coballast lie un

https://github.com/coballast/threejs-browserify-conversion-utility

Le code est un gâchis, sera bientôt nettoyé.

nous y voilà! :fusée:

J'ai maintenant l'utilitaire dans un état où il génère browserify src et le construira sans problème. Je mettrai à jour le référentiel avec des instructions sur la façon de le faire vous-même. À ce stade, les exemples ne fonctionnent pas. Plusieurs problèmes doivent être résolus pour résoudre ce problème. Je les ajouterai au dépôt si quelqu'un veut retrousser ses manches et aider.

@coballast ouais, veuillez classer les problèmes en tant que TODO, et moi ou d'autres nous interviendrons autant que possible.

De sérieux problèmes sont apparus. Voir #6241

Voici mon analyse de ce qui devrait se passer pour que cela fonctionne : https://github.com/coballast/threejs-browserify-conversion-utility/issues/9#issuecomment -83147463

browserify est pour le moins redondant de transport (conjectif) en raison de sa conception. Cela rend son utilisation coûteuse (plan de données, n'importe qui ?) et lente.

Une solution simple consiste à séparer le document du code de la bibliothèque, ce qui impliquerait deux fichiers client et non un. C'est une pratique courante pour les js côté client.

Si, au départ, browserify est défectueux et qu'il doit lui-même être corrigé, je ne vois pas pourquoi il devrait même être envisagé d'améliorer quoi que ce soit, encore moins quelque chose comme threejs.

@spaesani Parce que les données de threejs doivent être téléchargées de toute façon. Si nous divisons threejs en modules plus petits et laissons un système de construction automatisé choisir ce dont il a besoin pour une seule application, en fait, la plupart des applications threejs seraient plus légères.

Si, pour une raison quelconque, vous souhaitez toujours séparer le "document du code de la bibliothèque", vous pouvez toujours le faire et utiliser une version pré-construite comme nous le faisons maintenant. Vous pouvez même diviser votre application construite par navigateur en modules séparés en utilisant les indicateurs --standalone et --exclude .

Browserify est juste un moyen d'utiliser une API de définition de module éprouvée (CommonJS) sur le navigateur. Cela simplifierait grandement le développement de plugins threejs et améliorerait la clarté du code et donc la productivité, cela nous permettrait de nous intégrer dans un plus grand écosystème (npm) où le code est intrinsèquement maintenu par plus de personnes tout en maintenant l'intégrité à travers le système de versioning (pensez à la famille stackgl ), et cela ne forcerait même pas les gens à utiliser CommonJS s'ils n'en veulent pas.

Bien sûr, il y a des inconvénients, mais ce ne sont pas ceux que vous avez mentionnés.

three.js et three.min.js peuvent être mis en cache pour économiser sur le transport (données) par un proxy, la solution mobile commune ou un navigateur de mise en cache.
Au moment où vous choisissez et agrégez le code threejs avec le code spécifique au document, la mise en cache n'est pas possible.
Si browserify permet de

Questions connexes

seep picture seep  ·  3Commentaires

zsitro picture zsitro  ·  3Commentaires

filharvey picture filharvey  ·  3Commentaires

akshaysrin picture akshaysrin  ·  3Commentaires

jlaquinte picture jlaquinte  ·  3Commentaires