问题
I have an Angular application with a basic Three.js setup. I use Bazel build and run the app.
Everything works fine until I try to import the OBJLoader2
from three/examples/jsm/loaders/OBJLoader2
.
const module = await import('three/examples/jsm/loaders/OBJLoader2')
const loader = new module.OBJLoader2()
The code snippet above doesn't work with the ts_devserver. I get this error in the browser console:
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)
So the ts_devserver cannot find the imported script. But running the app in production works totally fine.
Minimal Reproduction
You can try it yourself: https://github.com/drakery3d/angular-bazel-three-starter/tree/ac0c89abf267af094a555c5e4867a46e2ee33a8f
Just run yarn install
and then yarn dev
(it causes the error in the browser console @ http://localhost:4200). And not that yarn pwa
@ http://localhost:8080 works just fine!
回答1:
Turned out my last answer is not fully correct since it gets next error: TypeError: Cannot read property 'Objloader2' of undefined
. I missed it cause was looking at Network tab in dev console. But getting the file from server returns 200 anyways, that's something.
What happens there is that ts_devserver
expects only UMD inputs but our script is something other. So, next thing I tried to do is wrapping it via 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",
)
and adding it to ts_devserver as script:
ts_devserver(
...,
scripts = [
...,
":objloader_umd",
],
)
What it gives us? Now we can import things from our artifically created module objloader
!
await import('objloader')
In general, that could be it. But let's do one more thing for convenience. We already have rxjs_shims.js for rxjs deep imports. Let's add one more similar script in a similar manner:
// 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});
});
What it basically does is aliasing three/examples/jsm/loaders/OBJLoader2
import path to objloader
especially for devserver.
So, now we even don't need to distinguish dev and prod builds cause imports can be the same. No need to change TS source code!
回答2:
Bazel does not support code splitting (or importing js files lazily) in development mode
.
Please read this from their angular-bazel-example
. Follow this issue on the Github.
Note: code splitting is not supported in development mode yet so the //src:devserver target does not serve a code split bundle. The dynamic import() statements will resolve to modules that are served in the initial JS payload.
回答3:
So, it's trying to get http://localhost:4200/three/examples/jsm/loaders/OBJLoader2.js
and can't get it. Makes sense.
First observation: if you change the import path to
// @ts-ignore
await import('npm/node_modules/three/examples/jsm/loaders/OBJLoader2')
it works well! (ts-ignore is needed to bypass tsc).
You even don't need to add those additional_root_paths
or any other three
-related fields to target.
Second observation: if we add "npm/node_modules/three"
to additional_root_paths
and fix the import to
// @ts-ignore
await import('examples/jsm/loaders/OBJLoader2')
then it works too.
But unfortunately adding "npm/node_modules"
in a similar manner doesn't lead the original import to work... Meh.
So, I don't know how to make this import work both in dev and prod. But I can propose a workaround! Since it's a dynamic import, you can use an approach similar to this:
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')
}
I believe you know already how to distinguish dev and prod build and can form isDev
var yourself ;)
Also, I've heard it's bad pattern to put variables straight to import functions cause it would break static analyzers. That's why I don't recommend to do this:
const prefix = isDev ? 'npm/node_modules/' : '';
const module = await import(`${prefix}examples/jsm/loaders/OBJLoader2`)
Not completely sure about if-else clause though, must be OK, since import's argument is still static.
来源:https://stackoverflow.com/questions/64209912/cannot-import-from-three-js-examples-script-error-for-three-examples-jsm-loade