Impossibile importare da Three.js examples (errore di script per "three / examples / jsm / loaders / OBJLoader2")
Ho un'applicazione Angular con una configurazione di base Three.js . Uso Bazel build ed eseguo l'app. Tutto funziona bene finché non provo a importare il file OBJLoader2da three/examples/jsm/loaders/OBJLoader2.
const module = await import('three/examples/jsm/loaders/OBJLoader2')
const loader = new module.OBJLoader2()
Lo snippet di codice sopra non funziona con ts_devserver . Ottengo questo errore nella console del browser:
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)
Quindi ts_devserver non riesce a trovare lo script importato. Ma l'esecuzione dell'app in produzione funziona perfettamente.
Riproduzione minima
Puoi provarlo tu stesso: https://github.com/drakery3d/angular-bazel-three-starter/tree/ac0c89abf267af094a555c5e4867a46e2ee33a8f
Esegui yarn installe poi yarn dev(causa l'errore nella console del browser @ http: // localhost: 4200). E non che yarn pwa@ http: // localhost: 8080 funzioni benissimo!
Risposte
Si è rivelato il mio ultimo risposta non è del tutto corretta dal momento che ottiene errore successivo: TypeError: Cannot read property 'Objloader2' of undefined. Mi mancava perché stavo guardando la scheda Rete nella console di sviluppo. Ma ottenere il file dal server restituisce comunque 200, è già qualcosa.
Quello che succede lì è che si ts_devserveraspetta solo input UMD ma il nostro script è qualcosa di diverso. Quindi, la prossima cosa che ho provato a fare è avvolgerlo tramite 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 aggiungendolo a ts_devserver come script:
ts_devserver(
...,
scripts = [
...,
":objloader_umd",
],
)
Cosa ci dà? Ora possiamo importare cose dal nostro modulo creato artificialmente objloader!
await import('objloader')
In generale, potrebbe essere così. Ma facciamo un'altra cosa per comodità. Abbiamo già rxjs_shims.js per le importazioni profonde di rxjs. Aggiungiamo un altro script simile in modo simile:
// 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});
});
Quello che fondamentalmente fa è creare un alias three/examples/jsm/loaders/OBJLoader2per il percorso di importazione objloaderspecialmente per devserver.
Quindi, ora non abbiamo nemmeno bisogno di distinguere le build di sviluppo e produzione perché le importazioni possono essere le stesse. Non è necessario modificare il codice sorgente di TS!
Bazel non supporta la suddivisione del codice (o l'importazione di file js pigramente) in development mode.
Si prega di leggere questo dal loro angular-bazel-example. Segui questo problema su GitHub.
Nota: la suddivisione del codice non è ancora supportata in modalità di sviluppo, quindi il target // src: devserver non serve un bundle di suddivisione del codice. Le istruzioni di importazione dinamica () verranno risolte nei moduli che vengono serviti nel payload JS iniziale.
Quindi, sta cercando di ottenerlo http://localhost:4200/three/examples/jsm/loaders/OBJLoader2.jse non può ottenerlo. Ha senso.
Prima osservazione: se modifichi il percorso di importazione in
// @ts-ignore
await import('npm/node_modules/three/examples/jsm/loaders/OBJLoader2')
Funziona bene! (ts-ignore è necessario per bypassare tsc). Non è nemmeno necessario aggiungere quelli additional_root_pathso altri threecampi correlati al target.
Seconda osservazione: se si aggiunge "npm/node_modules/three"alla additional_root_pathse fissiamo l'importazione
// @ts-ignore
await import('examples/jsm/loaders/OBJLoader2')
allora funziona anche lui.
Ma sfortunatamente l'aggiunta "npm/node_modules"in un modo simile non fa funzionare l'importazione originale ... Meh.
Quindi, non so come far funzionare questa importazione sia in dev che in prod. Ma posso proporre una soluzione alternativa! Poiché si tratta di un'importazione dinamica, puoi utilizzare un approccio simile a questo:
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')
}
Credo che tu sappia già come distinguere dev e prod build e puoi formare isDevvar da solo;)
Inoltre, ho sentito che è un cattivo schema mettere le variabili direttamente nelle funzioni di importazione perché romperebbe gli analizzatori statici. Ecco perché non consiglio di farlo:
const prefix = isDev ? 'npm/node_modules/' : '';
const module = await import(`${prefix}examples/jsm/loaders/OBJLoader2`)
Tuttavia, non completamente sicuro della clausola if-else, deve essere OK, poiché l'argomento di import è ancora statico.