Three.js 예제에서 가져올 수 없음 (“three / examples / jsm / loaders / OBJLoader2”에 대한 스크립트 오류)

Oct 05 2020

기본 Three.js 설정 이있는 Angular 응용 프로그램이 있습니다. Bazel 빌드를 사용하고 앱을 실행합니다. 내가 가져 오려고 할 때까지 모든 것이 잘 작동 OBJLoader2에서 three/examples/jsm/loaders/OBJLoader2.

const module = await import('three/examples/jsm/loaders/OBJLoader2')
const loader = new module.OBJLoader2()

위의 코드 스 니펫은 ts_devserver에서 작동하지 않습니다 . 브라우저 콘솔에서이 오류가 발생합니다.

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)

따라서 ts_devserver 는 가져온 스크립트를 찾을 수 없습니다. 그러나 프로덕션 에서 앱을 실행하면 완전히 잘 작동합니다.

최소 재생산

직접 시도해 볼 수 있습니다. https://github.com/drakery3d/angular-bazel-three-starter/tree/ac0c89abf267af094a555c5e4867a46e2ee33a8f

실행 yarn install한 다음 yarn dev브라우저 콘솔 @ http : // localhost : 4200에 오류가 발생합니다. 그리고 yarn pwa@ http : // localhost : 8080이 잘 작동 하지 않습니다 !

답변

2 Ray Oct 11 2020 at 23:44

내 마지막 답변은 다음 오류가 발생하기 때문에 완전히 정확하지 않습니다 TypeError: Cannot read property 'Objloader2' of undefined. 개발 콘솔의 네트워크 탭을보고 있었기 때문에 놓쳤습니다. 그러나 서버에서 파일을 가져 오면 어쨌든 200이 반환됩니다.

어떻게이 일이 즉 ts_devserver전용 UMD 입력이 기대하지만, 우리의 스크립트는 다른 무언가이다. 그래서 다음으로 시도한 것은 다음을 통해 포장하는 것입니다 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",
)

ts_devserver에 스크립트로 추가합니다.

ts_devserver(
  ...,
  scripts = [
    ...,
    ":objloader_umd",
  ],
)

그것은 우리에게 무엇을 제공합니까? 이제 인공적으로 만든 모듈에서 가져올 수 있습니다 objloader!

await import('objloader')

일반적으로 그럴 수 있습니다. 하지만 편의를 위해 한 가지 더 해보겠습니다. rxjs 딥 임포트를위한 rxjs_shims.js가 이미 있습니다. 비슷한 방식으로 비슷한 스크립트를 하나 더 추가해 보겠습니다.

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

기본적으로하는 일은 특히 devserver에 대한 three/examples/jsm/loaders/OBJLoader2가져 오기 경로 별칭 objloader을 지정하는 것입니다.

따라서 이제 우리는 dev 및 prod 빌드를 구분할 필요조차 없습니다. 가져 오기가 동일 할 수 있기 때문입니다. TS 소스 코드를 변경할 필요가 없습니다!

1 Akash Oct 11 2020 at 05:50

Bazel은 .NET에서 코드 분할 (또는 js 파일을 느리게 가져 오기)을 지원하지 않습니다 development mode.

그들의에서 이것을 읽으십시오 angular-bazel-example. Github 에서이 문제를 따르십시오 .

참고 : 코드 분할은 아직 개발 모드에서 지원되지 않으므로 // src : devserver 대상은 코드 분할 번들을 제공하지 않습니다. 동적 import () 문은 초기 JS 페이로드에서 제공되는 모듈로 확인됩니다.

1 Ray Oct 11 2020 at 21:00

그래서, 그것은 그것을 얻으려고 노력 http://localhost:4200/three/examples/jsm/loaders/OBJLoader2.js하고 있고 그것을 얻을 수 없습니다. 말이된다.

첫 번째 관찰 : 가져 오기 경로를

// @ts-ignore
await import('npm/node_modules/three/examples/jsm/loaders/OBJLoader2')

잘 작동한다! (ts-ignore는 tsc를 우회하는 데 필요합니다). 대상 필드 additional_root_paths또는 기타 three관련 필드를 추가 할 필요도 없습니다 .

둘째 관찰 : 우리는 추가 할 경우 "npm/node_modules/three"에 additional_root_paths과를 가져 오기를 수정

// @ts-ignore
await import('examples/jsm/loaders/OBJLoader2')

그럼 그것도 작동합니다.

하지만 안타깝게도 "npm/node_modules"비슷한 방식으로 추가 해도 원래 가져 오기가 작동하지 않습니다.

그래서이 가져 오기를 dev와 prod 모두에서 작동하도록 만드는 방법을 모르겠습니다. 하지만 해결 방법을 제안 할 수 있습니다! 동적 가져 오기이므로 다음과 유사한 접근 방식을 사용할 수 있습니다.

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')
}

나는 당신이 이미 dev와 prod 빌드를 구별하는 방법을 알고 있다고 믿고 isDevvar를 직접 만들 수 있습니다 .

또한 함수를 가져 오기 위해 변수를 직접 넣는 것은 잘못된 패턴으로 인해 정적 분석기가 손상 될 수 있다고 들었습니다. 이것이 내가 이것을 권장하지 않는 이유입니다.

const prefix = isDev ? 'npm/node_modules/' : '';
const module = await import(`${prefix}examples/jsm/loaders/OBJLoader2`)

if-else 절에 대해 완전히 확신하지 못하더라도 import의 인수가 여전히 정적이기 때문에 OK 여야합니다.