Cannot Import from Three.js Examples (Script error for “three/examples/jsm/loaders/OBJLoader2”)

跟風遠走 提交于 2021-01-05 08:07:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!