Não é possível importar de exemplos Three.js (erro de script para “three / examples / jsm / loaders / OBJLoader2”)
Eu tenho um aplicativo Angular com uma configuração básica de Three.js . Eu uso o Bazel para compilar e executar o aplicativo. Tudo funciona bem até eu tentar importar o OBJLoader2de three/examples/jsm/loaders/OBJLoader2.
const module = await import('three/examples/jsm/loaders/OBJLoader2')
const loader = new module.OBJLoader2()
O trecho de código acima não funciona com o ts_devserver . Recebo este erro no console do navegador:
GET http://localhost:4200/three/examples/jsm/loaders/OBJLoader2.js net::ERR_ABORTED 404 (Not Found)
Error: Script error for "three/examples/jsm/loaders/OBJLoader2"
at makeError (ts_scripts.js?v=03400874:175)
at HTMLScriptElement.onScriptError (ts_scripts.js?v=03400874:1745)
Portanto, o ts_devserver não pode encontrar o script importado. Mas executar o aplicativo em produção funciona perfeitamente bem.
Reprodução Mínima
Você pode tentar você mesmo: https://github.com/drakery3d/angular-bazel-three-starter/tree/ac0c89abf267af094a555c5e4867a46e2ee33a8f
Basta executar yarn installe então yarn dev(isso causa o erro no console do navegador @ http: // localhost: 4200). E não que yarn pwa@ http: // localhost: 8080 funcione muito bem!
Respostas
Acabou a minha última resposta não é totalmente correta, já que ele fica próximo erro: TypeError: Cannot read property 'Objloader2' of undefined. Eu perdi porque estava olhando para a guia Rede no console dev. Mas obter o arquivo do servidor retorna 200 de qualquer maneira, isso é alguma coisa.
O que acontece aí é que ts_devserverespera apenas entradas UMD, mas nosso script é outra coisa. Então, a próxima coisa que tentei fazer foi embrulhar por meio de npm_umd_bundle:
load("@build_bazel_rules_nodejs//internal/npm_install:npm_umd_bundle.bzl", "npm_umd_bundle")
npm_umd_bundle(
name = "objloader_umd",
package_name = "objloader",
entry_point = "@npm//:node_modules/three/examples/jsm/loaders/OBJLoader2.js",
package = "@npm//three",
)
e adicionando-o a ts_devserver como script:
ts_devserver(
...,
scripts = [
...,
":objloader_umd",
],
)
O que isso nos dá? Agora podemos importar coisas do nosso módulo criado artificialmente objloader!
await import('objloader')
Em geral, pode ser isso. Mas vamos fazer mais uma coisa por conveniência. Já temos rxjs_shims.js para importações de profundidade de rxjs. Vamos adicionar mais um script semelhante de maneira semelhante:
// three/examples/jsm/loaders/OBJLoader2 -> objloader
(function (factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
} else if (typeof define === 'function' && define.amd) {
define('three/examples/jsm/loaders/OBJLoader2', ['exports', 'objloader'], factory);
}
})(function (exports, objloader) {
'use strict';
Object.keys(objloader).forEach(function (key) {
exports[key] = objloader[key];
});
Object.defineProperty(exports, '__esModule', {value: true});
});
O que ele basicamente faz é three/examples/jsm/loaders/OBJLoader2criar um alias para o caminho de importação, objloaderespecialmente para devserver.
Portanto, agora nem precisamos distinguir as compilações de dev e prod porque as importações podem ser as mesmas. Não há necessidade de alterar o código-fonte do TS!
O Bazel não oferece suporte à divisão de código (ou importação de arquivos js preguiçosamente) no development mode.
Por favor, leia isso em seu angular-bazel-example. Acompanhe esta edição no Github.
Observação: a divisão de código ainda não é compatível com o modo de desenvolvimento, portanto, o // src: devserver target não serve um pacote de divisão de código. As instruções import () dinâmicas serão resolvidas para módulos que são servidos na carga útil JS inicial.
Então, ele está tentando http://localhost:4200/three/examples/jsm/loaders/OBJLoader2.jse não consegue. Faz sentido.
Primeira observação: se você alterar o caminho de importação para
// @ts-ignore
await import('npm/node_modules/three/examples/jsm/loaders/OBJLoader2')
isso funciona bem! (ts-ignore é necessário para contornar o tsc). Você nem mesmo precisa adicionar esses additional_root_pathsou quaisquer outros threecampos relacionados ao destino.
Segunda observação: se somarmos "npm/node_modules/three"aos additional_root_pathse corrigir a importação
// @ts-ignore
await import('examples/jsm/loaders/OBJLoader2')
então funciona também.
Mas, infelizmente, adicionar de "npm/node_modules"forma semelhante não faz com que a importação original funcione ... Caramba.
Portanto, não sei como fazer essa importação funcionar tanto no dev quanto no prod. Mas posso propor uma solução alternativa! Como é uma importação dinâmica, você pode usar uma abordagem semelhante a esta:
let module;
if (isDev) {
// @ts-ignore
module = await import('npm/node_modules/three/examples/jsm/loaders/OBJLoader2')
} else {
module = await import('three/examples/jsm/loaders/OBJLoader2')
}
Eu acredito que você já sabe como distinguir dev e prod build e pode formar isDevvar sozinho;)
Além disso, ouvi que é um padrão ruim colocar variáveis diretamente nas funções de importação, pois isso quebraria os analisadores estáticos. É por isso que não recomendo fazer isso:
const prefix = isDev ? 'npm/node_modules/' : '';
const module = await import(`${prefix}examples/jsm/loaders/OBJLoader2`)
Não completamente certo sobre a cláusula if-else, porém, deve estar OK, uma vez que o argumento de import ainda é estático.