Three.js: Mudança para uma arquitetura modular

Criado em 6 mai. 2014  ·  153Comentários  ·  Fonte: mrdoob/three.js

Browserify
Mudar para essa arquitetura tem vantagens e desvantagens. Por favor, adicione seus pensamentos.

Nota: isso não requer que os consumidores three.js usem o browserify.

Suggestion

Comentários muito úteis

Certo, então vai exigir um pouco de refatoração ...

Te peguei! Desde que este tópico ficou animado nos últimos dias, tenho trabalhado um pouco mais no three-jsnext . É um projeto que pega a base de código Three.js existente e a transforma em módulos ES automaticamente. Apenas brigando com algumas dependências cíclicas complicadas (particularmente em torno de KeyframeTrack ), mas deve haver algo para compartilhar em breve. Até onde eu posso dizer, todos os exemplos continuam funcionando, e a compilação minimizada é menor que a atual (usando Rollup para gerar um arquivo UMD), então é uma boa notícia.

Todos 153 comentários

Uma vantagem é que isso reforçaria uma arquitetura modular para o desenvolvimento contínuo de three.js.

O estilo comum em node / browserify faz com que cada arquivo declare suas dependências no topo e considera as variáveis ​​globais um antipadrão.

Aqui está um exemplo de snippet:

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

function BoxGeometry() {
  // ...
}

BoxGeometry.prototype = Geometry.prototype;

Outra vantagem é que os consumidores de three.js usando o browserify poderiam escolher as peças que desejam. Eles poderiam apenas importar Scene , BoxGeometry , PerspectiveCamera e WebGLRenderer , obter as dependências para todos eles automaticamente ( Object3D etc), e tem um pequeno pacote de javascript que suporta apenas o conjunto de recursos que eles desejam.

Isso pode ser feito de uma forma que não imponha alterações significativas. No nível superior, exportaríamos todas as classes que consideramos parte do pacote padrão

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

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

nota: não estou exatamente exigindo as dependências na parte superior neste exemplo, porque esse arquivo seria quase exclusivamente instruções require.

Finalmente, envolveríamos isso em uma Definição de Módulo Universal que detecta se um sistema de módulo (node ​​/ browserify, AMD) está em uso e, se estiver, o exporta ou, de outra forma, o anexa ao objeto global ( window ).

Vamos revisar:

  • impõe um bom estilo modular
  • permite que three.js consumidores que usam o browserify escolham e escolham a funcionalidade
  • sem mudanças significativas

Isso exigiria a substituição do sistema de construção, mas o novo seria bastante simples.

Algumas outras vantagens:

  • Você pode estruturar seu código
  • Você pode criar / reutilizar módulos sem poluir o namespace global
  • Você pode construir para produção
  • Você pode depurar mais facilmente, pois cada módulo tem seu próprio arquivo, você não precisa procurar o módulo correspondente em um grande arquivo three.js

@ shi-314 Acho que estou um pouco confuso. Acho que You can structure your code e You can build for production são coisas que você pode fazer sem a mudança arquitetônica? Você está falando sobre o código-fonte do three.js ou coisas construídas usando o three.js?

Uma prática que o three.js usa e que o torna difícil de usar em ambientes commonjs é o uso de instanceof : https://github.com/mrdoob/three.js/blob/master/src/core/Geometry .js # L82

Isso ocorre porque, em um aplicativo, você geralmente acaba com versões diferentes da mesma biblioteca em sua árvore de origem, portanto, verificar instanceof não funciona entre versões diferentes da mesma biblioteca. Seria uma boa preparação para uma mudança para um sistema de módulo commonjs para substituir essas verificações de instância por verificação de recursos por trás de uma interface de estilo Geometry.isGeometry(geom) .

@kumavis Estou falando sobre coisas construídas em three.js. Digamos que você queira criar seu próprio material com seus shaders etc. No momento, você precisa estender o objeto THREE global para permanecer consistente com o resto do código three.js:

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

Mas se tivéssemos o Browserify, você poderia fazer:

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

Para que seu namespace permaneça consistente e você não precise usar THREE.MeshLambertMaterial e MeshMyCoolMaterial em seu código.

E com You can build for production eu basicamente quis dizer a mesma coisa que você mencionou: allows three.js consumers using browserify to pick and choose functionality .

@ shi-314 obrigado, isso é mais claro. Isso tem impacto na minha proposta de solução geral para desserialização de classes definidas pelo consumidor:

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

Isto é da minha proposta de refatoração de serialização / desserialização
https://github.com/mrdoob/three.js/pull/4621

O desempenho não deve ser afetado por uma mudança como essa.

Esta é uma grande mudança, mas também sou a favor dela.

Algumas outras vantagens importantes:

  • Você pode usar a opção standalone do browserify para gerar uma compilação UMD para você. Não há necessidade de mexer manualmente com invólucros UMD.
  • O pacote pode ser facilmente consumido por usuários de browserify / NPM
  • Puxar dependências para threejs (como poly2tri, string de cores , etc) se torna muito mais fácil
  • Módulos que "realmente não pertencem" a uma biblioteca de renderização (como bibliotecas vetoriais / matemáticas) podem ser retirados como módulos NPM separados e reutilizados para muitos outros tipos de projetos. Um grande benefício disso é que os módulos individuais têm seu próprio repositório para bugs / problemas, PRs, etc (limpando os problemas do ThreeJS).
  • O NPM cuidará do controle de versão semântico para nós. por exemplo, podemos empurrar uma alteração importante no threejs-vecmath sem nos preocupar com a quebra de código de todos. E por outro lado, se fizermos um patch ou lançamento menor em um módulo específico, as pessoas que consomem esses módulos serão capazes de obter as alterações automaticamente.
  • Ele torna "extras" como EffectComposer e vários shaders fáceis de empacotar e consumir (imagine npm install threejs-shader-bloom )
  • Conforme os módulos são retirados, o tamanho final da distribuição começará a ficar menor e mais específico do aplicativo. Eventualmente, não haverá necessidade de diferentes "tipos de construção", uma vez que iremos apenas require() os módulos que nosso aplicativo está realmente usando.

Para @mrdoob e os demais autores; Se você não tem muita experiência com NPM / Browserify, sugiro fazer alguns pequenos projetos com ele e ter uma ideia de sua "filosofia". É muito diferente da arquitetura ThreeJS; em vez de grandes estruturas, ele incentiva muitas coisas pequenas .

Outra vantagem dessa abordagem é que pode haver um ecossistema de código aberto, módulos Three.JS de terceiros, especialmente sombreadores, geometrias, carregadores de modelo etc. Publicado por meio do NPM ou Github / Component que as pessoas podem facilmente consultar e usar. No momento, as coisas são compartilhadas hospedando-se uma demonstração na qual as pessoas então 'visualizam o código-fonte'. Three.JS merece melhor!

Acho que um dos problemas que tenho com o Three.JS é a rapidez com que o código se torna incompatível com a versão atual do Three.JS. Outra vantagem de mudar para algo assim é ser capaz de especificar versões específicas de _bits_ de Three.JS seria muito poderoso e prático.

+1

+1 para uma arquitetura CommonJS / browserify, tornaria o núcleo mais leve e as extensões caberiam mesmo se fossem de terceiros

Fragmentar three.js em pequenos módulos também acarreta muitos custos. O sistema atual permite addons de terceiros bastante simples (testemunha, por exemplo, os módulos THREEx de jetienne). Há muito a ser dito sobre a simplicidade da configuração atual, contanto que os sistemas de módulo JS sejam apenas invólucros em torno de sistemas de construção.

Outra maneira de minimizar o tamanho da compilação é o que o ClojureScript faz. Eles seguem algumas convenções para permitir que o compilador Closure do Google faça a análise de todo o programa e elimine o código morto.

+1 para o não apreciado, e muitas vezes esquecido, elegância da simplicidade

+1

Fragmentar three.js em pequenos módulos também acarreta muitos custos. O sistema atual permite addons de terceiros bastante simples (testemunha, por exemplo, os módulos THREEx de jetienne).

A ideia aqui é que um build UMD ainda seria fornecido para ambientes não-Node. Plugins como o THREEx funcionariam da mesma maneira para aqueles que dependem do ThreeJS com tags <script> simples.

O complicado será: como podemos require() um plugin específico se estivermos em um ambiente CommonJS? Talvez o browserify-shim possa ajudar.

Há muito a ser dito sobre a simplicidade da configuração atual, contanto que os sistemas de módulo JS sejam apenas invólucros em torno de sistemas de construção.

O sistema de plug-in / extensão atual do ThreeJS é horrível de se trabalhar e está longe de ser "simples" ou fácil. A maioria dos projetos ThreeJS tende a usar alguma forma de plugin ou extensão, como EffectComposer, ou FirstPersonControls, ou um carregador de modelo, ou um dos outros muitos arquivos JS flutuando na pasta examples . No momento, a única maneira de depender desses plug-ins:

  • Baixe a versão atual do ThreeJS
  • Copie e cole os arquivos necessários em sua pasta vendor
  • Conecte tarefas gulp / grunt para concat e minimizar todos os plug-ins de que você precisa; certificando-se de concatá-los _na ordem correta_, caso contrário, as coisas vão quebrar. Mantenha essa lista manualmente à medida que adiciona mais plug-ins.
  • Repita as etapas 1 e 2 sempre que ThreeJS for atualizado; e então puxe seu cabelo para fora quando perceber que o novo código não é compatível com versões anteriores

Agora, imagine, com o browserify você poderia fazer algo assim:

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

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

Esses plug-ins irão require('threejs') e qualquer outra coisa que eles possam precisar (como fragmentos GLSL ou triangulação de texto ). O gerenciamento de dependência / versão fica totalmente oculto para o usuário e não há necessidade de tarefas de concat grunt / gulp mantidas manualmente.

O complicado será: como exigimos () um plug-in específico se estivermos em um ambiente CommonJS?

Estou usando CommonJS para projetos THREE.js há um tempo. É um processo meio manual, converter pedaços de código de outras pessoas em módulos e não acho que haverá uma maneira fácil de evitar isso para código legado que não é convertido pelos autores ou contribuidores.

A parte importante é que há um módulo exportando todo o objeto TRÊS 'padrão', que pode então ser exigido por qualquer coisa que deseje estendê-lo.

var THREE = require('three');

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

Isso tem funcionado muito bem para mim, especialmente conforme o projeto cresce e eu começo a adicionar meus próprios sombreadores e geometrias em seus próprios módulos, etc.

Contanto que haja um pacote npm 'threejs-full' ou 'threejs-classic', então esta se torna uma maneira bastante viável de trabalhar com coisas antigas do Three.js em um ambiente CommonJS, mas eu suspeito que este seja um nicho bastante!

+1
Acredito que, uma vez que os módulos threejs fragmentados estão disponíveis no npm, o plugin
os desenvolvedores adorarão migrar para o ambiente CommonJS.
Em 5 de junho de 2014, 21:19, "Charlotte Gore" [email protected] escreveu:

A coisa complicada será: como exigimos () um plugin específico se nós
estão em um ambiente CommonJS?

Estou usando CommonJS para projetos THREE.js há um tempo. É um pouco
de um processo manual, convertendo pedaços do código de outras pessoas em módulos
e não acho que haverá uma maneira fácil de evitar isso para o código legado
que não é convertido pelos autores ou colaboradores.

O importante é que há um módulo exportando todo o 'padrão'
TRÊS objeto, que pode então ser exigido por qualquer coisa que deseja estender
isto.

var TRÊS = requer ('três');
THREE.EffectComposer = // ... etc, lembrando-se de incluir avisos de direitos autorais :)

Isso funcionou muito bem para mim, especialmente à medida que o projeto cresce e eu
comece a adicionar meus próprios sombreadores e geometrias em seus próprios módulos, etc.

Contanto que haja um pacote npm 'threejs-full' ou 'threejs-classic', então
isso se torna uma maneira bastante viável de trabalhar com coisas antigas do Three.js em um
Ambiente CommonJS, mas eu suspeito que este seja um nicho bonito!

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/mrdoob/three.js/issues/4776#issuecomment -45236911.

Ele também pode tornar os shaders modulares, por exemplo, usando glslify . Até coisas como criar um middleware Express que gere sombreadores sob demanda se tornam mais fáceis.

Alguns meses atrás, mudei o frame.js para o require.js e finalmente entendi como essa coisa da AMD funciona.

Ainda preciso aprender, entretanto, como "compilar" isso. Qual é a ferramenta / fluxo de trabalho para gerar three.min.js de uma lista de módulos?

Eu prefiro gulp.js como um sistema de construção com o plug - in http://travismaynard.com/writing/no-need-to-grunt-take-a-gulp-of-fresh-air : wink:

algumas idéias: (com base em minha experiência limitada com node, npm, browserify, é claro)

  1. Acho que os módulos node.js são ótimos (ou seja, npm, modules e require () s)
  2. Eu acho que o browserify também é ótimo

Dito isso, seguindo a discussão neste tópico, não tenho certeza se todos tinham o mesmo entendimento de browserify (browserify, commonjs, requirejs, amd, umd estão de alguma forma relacionados, embora possam não ser necessariamente a mesma coisa).

agora, se você pode seguir minha cadeia de pensamentos um pouco.

  1. JS é ótimo, é executado rapidamente em todos os navegadores.
  2. uau, agora JS roda no lado do servidor também.
  3. isso é node.js, é legal, então vamos codificar coisas em node.js
  4. mas não desejo escrever / não consigo escrever tudo / encontrar algo para usar.
  5. não se preocupe, agora execute módulos de instalação npm
  6. agora exigem esses módulos legais para que possamos usá-los.
  7. funciona bem!
  8. agora espere, acabamos de escrever um monte de coisas em JS que é executado em node.js
  9. não é js suposto em navegadores? como fazemos com que esse código seja executado lá novamente?

É aí que o Browserify entra em cena. Bem, tecnicamente, pode-se usar requireJS no navegador. Mas você deseja agrupar os arquivos js sem fazer muitas chamadas de rede (ao contrário dos require () s do sistema de arquivos, que são rápidos). É aí que o Browserify faz algumas coisas legais como análise estática para ver quais módulos precisam ser importados e cria compilações que são mais otimizadas para o seu aplicativo. (Existem limitações, é claro, ele provavelmente não pode analisar require ('bla' + variável)) ele pode até mesmo trocar partes que requerem uma camada de emulação para coisas dependentes de node.js. sim, ele gera uma compilação js que agora posso incluir no meu navegador.

Aqui estão algumas das coisas que o browserify pode fazer https://github.com/substack/node-browserify#usage

Parece que está tudo ótimo até agora ... mas há alguns pontos que considero que vale a pena considerar que mudamos para uma "arquitetura de navegador"

  • é necessário que haja uma mudança de mentalidade para os desenvolvedores do three.js (o sistema de módulo necessário deve ser usado, é claro)
  • uma camada de compatibilidade pode ser construída para que os usuários do three.js ainda possam usar o three.js da maneira antiga, sem colher os benefícios modulares
  • para ser capaz de produzir compilações otimizadas, os usuários do three.js precisariam passar para o sistema necessário
  • o novo processo de construção provavelmente envolveria a cadeia de ferramentas browserify (atualmente podemos usar python, node.js ou copiar e colar simples, etc.) ou algumas ferramentas requireJS.
  • se quisermos que o three.js seja realmente mais modular, com controle de versão em cada componente, como, por exemplo, TrackballControls, precisaremos separá-los, e isso pode levar à fragmentação
  • isso também pode levar à diversidade, no entanto, um ponto forte do three.js atualmente parece ser um ponto centralizado de muitas extensões

Portanto, se vemos essa diversidade e carregamento de módulo conveniente (principalmente no ecossistema npm) junto com compilações personalizadas, então pode valer a pena ter uma mudança de paradigma, refatorar o código e mudar nosso sistema de compilação atual.

@mrdoob algumas ferramentas do browserify estão listadas aqui: https://github.com/substack/node-browserify/wiki/browserify-tools.

em relação a three.min.js , você não usaria o código reduzido em seu projeto. tudo o que você faz é var three = require('three') em seu project.js e então executa browserify project.js > bundle.js && uglifyjs bundle.js > bundle.min.js . observação: você ainda pode enviar o código reduzido por <script src="min.js"> .

estou atualmente envolvendo three.js com

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

e

module.exports = THREE

então eu envolvo extensões com

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

então eu posso exigir assim:

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

então isso não é o ideal, mas eu pessoalmente posso viver com isso e acho que não é tão ruim - embora a proposta da @kumavis seja uma _grande_ vantagem.

mas talvez fizesse sentido dividir três e colocar todas as coisas em módulos separados apenas para ver como funcionaria.

também verifique http://modules.gl/ que é fortemente baseado no browserify (embora você possa usar cada módulo sozinho sem o browserify).

@mrdoob @ shi-314 gulp-browserify foi colocado na lista negra em favor de apenas usar o browserify diretamente (ou seja, via vinyl-source-stream).

Ferramentas como grunt / gulp / etc estão constantemente mudando, e você encontrará muitas opiniões divergentes. No final, não importa qual você escolhe ou se você apenas faz isso com um script personalizado. As questões mais importantes são: como os usuários consumirão o ThreeJS e quanta compatibilidade com versões anteriores você deseja manter?

Depois de pensar um pouco mais, acho que será _realmente_ difícil modularizar tudo sem refatorar completamente o framework e sua arquitetura. Aqui estão alguns problemas:

  • Todo o código do namespace precisa ser alterado para CommonJS exports / require. Este é um empreendimento muito grande e haveria muitos ../../../math/Vector2 feios etc.
  • Em um mundo ideal, a biblioteca seria fragmentada, então three-scene seria desacoplado de three-lights etc. Então você pode criar uma versão de cada pacote separadamente. Esse tipo de fragmentação parece irreal para uma estrutura tão grande como o ThreeJS e seria um saco de mantê-la.
  • Se _não_ estivermos fragmentando a estrutura em componentes minúsculos, então o versionamento semântico será um pesadelo. Uma alteração minúscula em qualquer lugar da estrutura precisaria de um aumento de versão principal para a coisa toda. E consumir a API seria muito feio: require('three/src/math/Vector2')

Minha sugestão? Consideramos duas coisas no futuro:

  1. Comece pequeno; extraia alguns recursos essenciais e reutilizáveis ​​como vetor / quaternion, conversões de cores, triangulação, etc. Essas coisas são boas candidatas para NPM, pois são úteis fora do escopo do ThreeJS. Eles também podem ter seu próprio conjunto de testes, controle de versão e rastreamento de problemas.
  2. Quando um novo código precisa ser adicionado ao ThreeJS, como um novo recurso ou uma dependência (por exemplo, poly2tri / Tess2), considere retirá-lo como um módulo separado e dependendo dele via NPM.

Adoraria ver tudo modularizado, mas não tenho certeza de uma abordagem realista para o ThreeJS. Talvez alguém devesse fazer alguns experimentos em uma bifurcação para ver como as coisas são viáveis.

Obrigado pelas explicações galera!

O que temo é complicar as coisas para as pessoas que estão começando. Forçá-los a aprender essas coisas de browserify / modules pode não ser uma boa ideia ...

Teria que concordar com @mrdoob aqui. Eu, e muitos colegas, não somos programadores da web (em vez disso, VFX / TDs de animação). Escolher o WebGL e o Three certamente já deu trabalho suficiente, pois está no topo de nossa carga de trabalho atual (e em alguns casos alguns de nós tiveram que aprender js na hora). Muito do que li neste tópico, às vezes, me faz estremecer pensando em quanto mais trabalho seria adicionado ao meu prato se Três mudasse para esta estrutura. Eu posso estar errado, mas é certamente o que parece para mim.

Com um UMD pré-compilado ( browserify --umd ) compilado no repo, não há nenhuma mudança no fluxo de trabalho para os desenvolvedores existentes.

@mrdoob A ideia de um sistema de gerenciamento de dependências é a simplicidade. Ler dezenas de postagens sobre opções e sistemas de construção pode ser opressor, mas no final das contas o sistema atual não é sustentável. Sempre que um arquivo depende de outro, isso é uma caça -e- pesquisa que qualquer novo desenvolvedor deve realizar para encontrar uma referência. Com o browserify, a dependência é explícita e há um caminho para o arquivo.

@repsac Um sistema de dependência deve tornar o Three mais acessível aos usuários de outras linguagens, pois evita o escopo global, pesadelos na ordem de carregamento e segue um paradigma semelhante a outras linguagens populares. var foo = require('./foo'); é (loosly) semelhante ao do C # using foo; ou de Java import foo;

Adoraria ver tudo modularizado, mas não tenho certeza de uma abordagem realista para o ThreeJS. Talvez alguém devesse fazer alguns experimentos em uma bifurcação para ver como as coisas são viáveis

Acho que esse é o caminho a percorrer, realmente. Faça o trabalho, mostre como funciona.

E consumir a API seria bastante ugly: require('three/src/math/Vector2')

Como um experimento, acabei de converter o "início" dos documentos Três para esta nova abordagem modular. Posso imaginar que haja muitas referências, a menos que as pessoas sejam bastante rígidas quanto a dividir seu código em módulos minúsculos.

A principal vantagem de fazer isso seria que o tamanho de compilação resultante seria uma pequena fração do tamanho do Three.js completo, porque você só incluiria as coisas especificamente referenciadas aqui e depois as coisas das quais essas coisas dependem.

Acho que fazer referência a todas as dependências de que você precisa (e instalá-las individualmente) pode ser um pouco terrível na prática.

Se você está segmentando explicitamente dispositivos móveis, essa abordagem altamente granular seria perfeita, mas na realidade, suspeito que precisaremos de pacotes que exportem as TRÊS api inteiras que funcionarão normalmente, e pacotes menores que encapsulem toda a geometria bônus, todos os renderizadores, toda a matemática, todos os materiais, etc., depois até o nível de módulo individual para que os desenvolvedores possam decidir por si próprios.

E sim, programar para a web é uma dor.

De qualquer forma, continue com o experimento ...

Instale nossas dependências ..

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

Escreva nosso código ...

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

então construa nosso arquivo

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

então inclua em nossa página

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

Acho que fazer referência a todas as dependências de que você precisa (e instalá-las individualmente) pode ser um pouco terrível na prática.

O usuário final não precisa necessariamente usar o browserify em seu projeto para que o Three use o browserify para gerenciar sua base de código. Três podem ser expostos como o THREE global como está agora ... inclua o arquivo de construção e execute com ele.

@repsac @mrdoob as mudanças seriam compatíveis com versões anteriores, de forma que os usuários atuais não precisem mudar nada se não quiserem. Essas sugestões são para melhorar a manutenção a longo prazo e longevidade da base de código extensa e monolítica do ThreeJS. Coisas como dependência e gerenciamento de versão podem soar como uma dor de cabeça para os não iniciados, mas são fantásticas para ferramentas de desenvolvimento, frameworks, plug-ins e sites de grande escala além do ThreeJS.

ou seja, o código do usuário final ainda pode ter a mesma aparência, e examples não precisa ser alterado:

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

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

Para desenvolvedores mais ambiciosos que procuram uma construção modular, _ou_ para aqueles que procuram desenvolver soluções de longo prazo com base no ThreeJS (ou seja, e tirar proveito do gerenciamento de versão / dependência), pode ser mais assim:
npm install three-vecmath --save

Então, no código:

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

//.. do something with Vector2

E, além disso, isso permite que as pessoas usem coisas como matemática vetorial de ThreeJS, conversões de cores, triangulação, etc. fora do escopo de ThreeJS.

Mesmo que eu ache que a bagunça de require () é uma má ideia e uma péssima troca, seria uma ideia ainda pior expor os usuários a dois tipos diferentes de código three.js, dizendo aos usuários que um é o sabor do sistema de módulo extravagante e o outro é o sabor de sistema de módulo mais simples (mas de segunda classe).

@erno Acho que você perdeu o ponto, three.js seria organizado por uma estrutura de módulo internamente, mas que é usada para produzir um arquivo de compilação não diferente da configuração atual.

O principal ganho é melhorar a experiência de desenvolvimento e manutenção de three.js .

@kumavis - no @erno realmente não perdeu isso, mas eu entendi (*) que ele afirma que se three.js às vezes é usado via require e às vezes não, pode ser confuso. Por exemplo, alguém olha as três fontes e, em seguida, alguns exemplos de terceiros e encontra diferenças em como tudo é e funciona.

(*) falamos sobre isso no irc hoje cedo.

Eu acho que é um tipo de ponto válido, mas não tenho certeza se / como funciona no final - se é um problema com o uso de módulo e construção. Mas certamente parece valer a pena pensar e, no geral, me pareceu bom que o assunto geral tenha sido considerado aqui com cuidado, obrigado pelas informações e pontos de vista de minha parte.

@antont eu vejo. Pessoas sugeriram uma variedade de abordagens diferentes aqui, eu estava assumindo que forneceríamos principalmente documentação para uso de nível superior (retirando tudo de THREE ), mas outros podem criar exemplos que não seguiriam isso e podem levar a alguma confusão. E essa é uma preocupação válida.

Acho que fiquei um pouco confuso com a linguagem.

e outro é o tipo de sistema de módulo mais simples (mas de segunda classe).

Isso se refere apenas ao arquivo de construção, certo?

No meu entendimento, sim. Não consigo imaginar o que mais, mas pode estar faltando alguma coisa.

antont, kumavis: As propostas aqui falam sobre expor o código de estilo require () também para usuários finais, consulte, por exemplo. Comentário mais recente de mattdesl.

"Para desenvolvedores mais ambiciosos que procuram uma construção modular, ou para aqueles que procuram desenvolver soluções de longo prazo em cima do ThreeJS (ou seja, e tirar vantagem do gerenciamento de versão / dependência) [...]"

uma maneira de ter compilações mais otimizadas é ter um script que descubra suas dependências automaticamente e produza os módulos necessários.

agora mesmo o bower & browserify não precisa mais, mas eles não são as únicas soluções. Não sei se existem outros projetos de código aberto prontos para o uso que fazem isso (talvez como dependências de ng), mas já escrevi essas ferramentas antes que acho que haveria outras abordagens para resolver esses problemas.

O compilador de encerramento do Google pode ser essa ferramenta?

Do lado do usuário, isso pode ajudar?
http://marcinwieprzkowicz.github.io/three.js-builder/

isso é muito interessante @erichlof :) Eu me pergunto se @marcinwieprzkowicz gerou isso manualmente ... https://github.com/marcinwieprzkowicz/three.js-builder/blob/gh-pages/threejs-src/r66/modules.json

Uma prática que o three.js usa e que o torna difícil de usar em ambientes commonjs é o uso de instanceof: https://github.com/mrdoob/three.js/blob/master/src/core/Geometry.js#L82

Isso ocorre porque, em um aplicativo, você geralmente acaba com versões diferentes da mesma biblioteca em sua árvore de origem, portanto, verificar instanceof não funciona entre versões diferentes da mesma biblioteca. Seria uma boa preparação para uma mudança para um sistema de módulo commonjs para substituir essas verificações de instância com verificação de recursos por trás de uma interface de estilo Geometry.isGeometry (geom).

em git / three.js / src:

grep -r instanceof . | wc -l 
164

em git / three.js / examples:

grep -r instanceof . | wc -l 
216

portanto, há um total de 380 usos de instanceof em three.js. Qual seria a melhor implementação como substituto?

Recentemente, adicionei uma propriedade type que pode ser usada para substituir a maioria delas.

Recentemente, adicionei uma propriedade de tipo que pode ser usada para substituir a maioria delas.

legais! Preparará um PR.

Para obter um exemplo de como isso é tratado em outra biblioteca JS popular e grande, dê uma olhada em https://github.com/facebook/react . A base de código é estruturada usando o sistema de módulo baseado em estilo de nó (que o browserify implementa), mas é construída para liberação usando grunt. Esta solução é flexível para 3 casos de uso.

  1. O consumidor puro de Three.js escrevendo vanilla JS ainda pode apenas usar o arquivo de construção como sempre. Não há mudança para este caso de uso.
  2. O consumidor de Three.js usando browserify pode declarar Three.js como uma dependência em um projeto e apenas require dependências específicas. Os benefícios do gerenciamento adequado de dependências foram bem documentados.
  3. Contribuir para Three.js agora deve ser mais simples, pois as dependências entre os componentes são explicitamente documentadas.

Eu fiz algumas pesquisas ...

Ontem eu hackeei um script (bastante estúpido) que transforma o código fonte Three.js para usar instruções CommonJS require() para declarar dependências entre arquivos. Só para ver o que acontece ... Este:

  1. Isso acaba tendo declarações de requerimento bastante ridículas como esta (do 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');

Precisaríamos de uma grande refatoração, talvez dividindo o WebGLRenderer (e outros) em vários módulos (atm, o arquivo tem mais de 6.000 linhas).

  1. Precisamos encontrar uma solução para os pedaços GLSL. Atm, esses arquivos são compilados em THREE.ShaderChunk em tempo de compilação e depois em THREE.ShaderLib em tempo de execução (juntando arrays de THREE.ShaderChunk s), o que é bastante complicado de fazer apenas com o browserify. Presumo que isso exigiria uma transformação browserify que fizesse o mesmo.

React.js usa commoner para pesquisar seus módulos sem ter que se referir a eles por caminho de arquivo. Talvez pudéssemos fazer o mesmo e também definir regras personalizadas que nos permitem require arquivos GLSL transformando-os na sintaxe JS.

@rasteiner, você pode ficar muito feliz em saber mais sobre https://github.com/stackgl/glslify , ele vem da crescente família http://stack.gl

Eu tive um pouco de experiência com módulos e a abordagem "unixy" nos últimos dois meses e meu pensamento agora é que é um pouco tarde demais, e refatorar threejs para modularidade ou módulos npm seria uma meta irreal.

Aqui está o que estou fazendo atualmente para resolver o problema de modularidade / reutilização:

  • Estou colocando alguns shaders reutilizáveis ​​para blur / fxaa / etc no NPM. Veja isso:
    https://www.npmjs.org/package/three-shader-fxaa (que usa o mecanismo agnóstico glsl-fxaa)
  • componentes reutilizáveis ​​como OrbitController e EffectComposer também estão sendo publicados conforme necessário. Por exemplo:
    https://www.npmjs.org/package/three-orbit-controls
  • em vez de depender de "três", esses módulos exportam uma função que recebe TRÊS e retorna a classe de utilitário. Dessa forma, ele pode ser usado com threejs ou commonjs globais.
  • versionamento é uma dor. Estou tentando alinhar minhas versões principais com os números de lançamento do threejs. Cada nova versão do threejs apresenta muitas mudanças importantes, por isso terá que ser tratada com cuidado.
  • os módulos que lidam com matemática devem operar apenas em matrizes e usar gl-vec3, gl-mat4 modular, etc. dessa forma, eles são genéricos e úteis fora do threejs. Em seguida, os usuários do threejs terão apenas que lidar com o empacotamento / desempacotamento dos arrays. Veja verlet-system, simplify-path, etc.
  • se eu precisar de um recurso webGL realmente modular ou minúsculo, estou usando stackgl / glslify daqui para frente. Eles também podem funcionar no ThreeJS, desde que você reinicie o estado GL. Por exemplo: https://www.npmjs.org/package/gl-sprite-text

Meus novos projetos tendem a usar "três" no npm apenas para começar a funcionar. Seria muito bom se ThreeJS publicasse oficialmente a construção para o npm usando tags de versão que se alinham com os números de lançamento.

PS: para aqueles interessados ​​em trazer shaders reutilizáveis ​​/ modulares para seu fluxo de trabalho:
https://gist.github.com/mattdesl/b04c90306ee0d2a412ab

Enviado do meu iPhone

Em 20 de novembro de 2014, às 7h42, aaron [email protected] escreveu:

@rasteiner, você pode ficar muito feliz em saber mais sobre https://github.com/stackgl/glslify , ele vem da crescente família http://stack.gl

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

No caso de ajudar outras pessoas que possam estar procurando como usar o Three.js com o browserify, e tropeçar neste tópico, a maneira que eu mesmo configurei é usando o browserify-shim .

Seguindo a seção README em _ "Você às vezes a) Exporá variáveis ​​globais via global" _, incluí uma tag de script separada para Three.js e a configurei para expor a variável global THREE.

E então apenas uma parte que eu tive que descobrir sozinho foi como incluir extras como ColladaLoader, OrbitControls etc. Eu fiz assim:

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" }
    };

Então, em meu próprio 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);
...

O Browserify requer a reconstrução de todo o script quando você modifica até mesmo em bytes. Certa vez, usei o browserify para empacotar um projeto que requer THREE.js, então levo mais de dois segundos para construir um boundle e bloqueia o livereload toda vez que faço uma alteração. Isso é muito frustrante.

Você normalmente usa watchify durante o desenvolvimento com livereload. Esse constrói o pacote incrementalmente.

watchify não funciona para mim. Quando eu mudo um arquivo e o salvo, watchify e beefy livereload fornecem a versão mais antiga / em cache. Não tenho ideia de por que isso acontece. Felizmente, o browserify já funciona muito bem.

@ChiChou Passe em --noparse=three para o navegador. Isso leva o pacote do browserify de 1000 ms para 500 ms na minha máquina, o que é decente o suficiente para uma sensação de feedback instantâneo.

@rasteiner Quero agradecer novamente por sua pesquisa informal sobre as interdependências do three.js. Embora essa lista massiva de dependências seja um código de aparência feia, na verdade essa feiura está presente como está, apenas invisível. O ponto forte do Browserify é exigir que arejemos nossas roupas sujas e busquemos sistemas menos emaranhados.

Há muitos lugares em Three.js onde pegamos algum objeto, percebemos seu tipo e executamos diferentes tarefas com base nesse tipo. Na maioria desses casos, esse código dependente do tipo poderia ser movido para o próprio tipo, e não precisaríamos ter uma compreensão de todos os tipos possíveis em que estamos operando.

O seguinte é um exemplo resumido do WebGLRenderer :

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

deveria ser mais da forma

texture.processTexImage( _gl, mipmaps, otherData )

Deixe o tipo determinar como se comportar. Isso também permite que o consumidor da biblioteca use novos tipos de textura nos quais não havíamos pensado. Essa estrutura deve reduzir a interdependência.

Acho que mudar para uma arquitetura de browserify é definitivamente o caminho a percorrer. A construção UMD tornará o consumo de THREE.js mais fácil. Também nos permitirá dividir o WebGLRenderer em vários arquivos, porque agora ele parece um tanto monolítico e assustador.

Eu comecei um branch onde estou trabalhando atualmente para movê-lo aqui: https://github.com/coballast/three.js/tree/browserify-build-system

Por favor, deixe-me saber o que você pensa.

Aqui estão as alterações de @coballast .

Parece que você está adotando a abordagem de conversão automatizada com seu arquivo browserifyify.js , que eu acho que é o caminho certo a seguir.

Uma coisa que todos nós não discutimos muito ainda é a melhor forma de fazer a transição desta biblioteca grande e em constante mudança para o navegador. Você pode fazer as alterações e abrir um PR, mas ele ficará imediatamente desatualizado. Isso é o que é atraente sobre a abordagem automatizada.

Se pudermos:

  • fornecer um script de conversão three.js src (como seu browserifyify.js )
  • fornecer um documento que explica como funciona o processo de conversão
  • fornecer um documento que explica como o novo sistema de compilação funciona
  • executar testes pós-conversão
  • não incluir quaisquer alterações nos arquivos existentes que possam levar a um conflito de mesclagem

... então podemos transformar isso em uma conversão de botão que ainda funcionará no futuro previsível. essa simplicidade permite que essa noção onírica de uma mudança fundamental da arquitetura para um projeto tão grande seja realizada quando os argumentos ideológicos vencem.

@coballast para esse fim, eu removeria as alterações em src / Three.js se ele funcionar da mesma forma.

Nota: não apenas reverter, mas remover essas mudanças do histórico do branch por meio de um novo branch ou um push forçado

@coballast Eu me pergunto se faria mais sentido para o utilitário de conversão não ser um fork de three.js , mas um utilitário externo que você aponta para o diretório de desenvolvimento three.js e converte o código-fonte arquivos, adiciona um script de construção e executa os testes.

@kumavis Eu concordo em deixar o diretório src sozinho. Acho que a coisa a fazer é fazer com que o utilitário escreva um diretório duplicado com o código commonjs, e podemos testar e executar a compilação do browserify a partir daí para garantir que todos os exemplos funcionem antes de tentar fazer algo importante.

Também há uma oportunidade interessante aqui de escrever algumas coisas de análise estática que irão verificar e aplicar automaticamente um estilo consistente em toda a base de código.

@coballast parece ótimo.
Existe uma grande variedade de ferramentas para reescrever código automatizado, por exemplo, escodegen . Precisamos ter certeza de que estamos mantendo comentários, etc.
Deseja iniciar um repositório de utilitário de conversão threejs?

@coballast que disse, manter um foco nítido para este utilitário é importante

@kumavis Considere feito. Eu realmente quero que isso aconteça Este é apenas um dos dois projetos em que estou trabalhando no momento.

@kumavis @mrdoob Parte da discussão aqui parece girar em torno da ideia de dividir TRÊS em vários módulos separados que poderiam ser instalados via npm e depois compilados com o browserify. Não sou totalmente contra a ideia, mas acho que deveria ser uma questão separada. A única coisa que estou defendendo neste momento é usar o browserify para gerenciar a base de código do THREE internamente. Faça com que ele seja transferido e funcione da mesma forma que sempre funcionou para os usuários e, em seguida, avalie o que faz sentido.

Estou curioso para ver qual é o resultado desse utilitário ^^

@coballast vincula um

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

O código está uma bagunça, vai limpar em breve.

aqui vamos nós! :foguete:

Agora tenho o utilitário em um estado em que ele gera browserify src e o criarei sem problemas. Atualizarei o repo com instruções sobre como fazer isso sozinho. Neste ponto, os exemplos não funcionam. Existem vários problemas que precisam ser resolvidos para corrigir isso. Vou adicioná-los ao repo se alguém quiser arregaçar as mangas e ajudar.

@coballast sim, por favor, registre os problemas como TODOs, e eu ou outras pessoas entraremos em ação como pudermos.

Sérios problemas surgiram. Veja # 6241

Aqui está minha análise do que precisa acontecer para que isso funcione: https://github.com/coballast/threejs-browserify-conversion-utility/issues/9#issuecomment -83147463

browserify é no mínimo redundante de transporte (conjestivo) devido ao seu design. Isso torna seu custo de uso inflativo (plano de dados, alguém?) E lento.

Uma solução simples para isso é separar o documento do código da biblioteca, o que implicaria em dois arquivos de cliente e não um. Esta é uma prática comum para js do lado do cliente.

Se no início o browserify está com defeito e precisa ser consertado, dificilmente posso ver por que ele deve ser considerado para melhorar alguma coisa, muito menos algo como threejs.

@spaesani Porque os dados para threejs devem ser baixados de qualquer maneira. Se dividirmos o threejs em módulos menores e deixarmos um sistema de compilação automatizado escolher o que precisa para um único aplicativo, na verdade a maioria dos aplicativos do threejs que existem seriam mais leves.

Se por algum motivo você ainda deseja separar "documento do código de biblioteca", você ainda pode fazer isso e usar uma versão pré-construída como fazemos agora. Você pode até mesmo dividir seu aplicativo integrado no browserify em módulos separados usando os sinalizadores --standalone e --exclude .

Browserify é apenas uma maneira de usar uma API de definição de módulo comprovada em batalha (CommonJS) no navegador. Isso simplificaria muito o desenvolvimento de plug-ins threejs e aumentaria a clareza do código e, portanto, a produtividade, permitiria a integração em um ecossistema maior (npm), onde o código é inerentemente mantido por mais pessoas, mantendo a integridade por meio do sistema de controle de versão (pense sobre família stackgl ), e nem mesmo forçaria as pessoas a entrar no CommonJS se elas não quisessem.

Claro que existem desvantagens, mas não são as que você mencionou.

three.js e three.min.js podem ser armazenados em cache para economizar no transporte (dados) por um proxy, a solução móvel comum ou um navegador de armazenamento em cache.
No momento em que você seleciona e agrega o código threejs com o código específico do documento, o armazenamento em cache não é viável.
Se o browserify permitir

Questões relacionadas

akshaysrin picture akshaysrin  ·  3Comentários

ghost picture ghost  ·  3Comentários

danieljack picture danieljack  ·  3Comentários

donmccurdy picture donmccurdy  ·  3Comentários

jack-jun picture jack-jun  ·  3Comentários