JSM modules for browser double import with TypeScript

Hello there,

I was very happy to see that in r105 there are now modules that can be easily imported. Sadly this results for me in having three.js twice in my final bundle (at least that’s what I think).

My current pipeline is
Writing stuff in TypeScript
Gulp with tsify, babelify and then browserify to get the code running again in the browser.

In order to bundle the file with browserify I need to jump back to es5 syntax which might create my problem. My new bundle is now almost twice the size and experiences the same problem as described here: https://github.com/mrdoob/three.js/issues/6362

Example import:

    import { Scene, Group, PerspectiveCamera, CubeTexture, Object3D, AmbientLight } from 'three';
    import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

gulp pipeline:

    return browserify({
            debug: true
        })
        .add("./src/main.ts")
        .plugin(tsify, { noImplicitAny: true })
        .transform(
            babelify,
            {
                only: [
                    "./node_modules/three/build/three.module.js",
                    "./node_modules/three/examples/jsm/*"
                  ],
                global: true,
                sourceType: "unambiguous",
                presets: ["@babel/preset-env"],
                plugins: ['@babel/plugin-transform-modules-commonjs']
            }
          )
        .bundle()

When not doing the import of jsm files the problem does not occur. I’m wondering if there is a way to use the jsm files with browserify, or if there is another way to import them in a clean way.

Actually, I just got a replay on github, the following pipeline works for me:

Still, is this the intended way to use the new modules with browserify?

We recently had the same problem here: Are there any known pitfalls resulting in conflicting geometry ids (geometry.id)?

It seems this issue only happens with TypeScript. I’ve provided a sample repository at the end of the linked topic that demonstrates a project setup with JavaScript and rollup that produces a build file without duplicates.

Still, it’s somewhat unfortunate that this happens with TypeScript. Unfortunately, I’m not using Typescript so I’m unable to explain why three.js gets included twice.

Hi!

I believe our problem had to do something with the “separate” nature of modules inside three/examples/jsm/. We were/are using several modules from this direcory : Sky, OBJLoader, SubdivisionModifier & PMREMCubeUVPacker + PMREMGenerator. After doing :point_right: this our bundle size was okay again (not double) + we had no issues with the geometry.id s any more.

All other three/src modules described inside src/Three.d.ts (means not inside the three/examples/jsm folder) didn’t cause any trouble.

Hi! Try not to import the GLTFLoader and see if bundle size is okay then!

Yes, when I don’t include any of the example files the problem does not occur. If you use babel you can kind of foce the source of threejs with .require(require.resolve('three/build/three.module.js'), { expose: 'three' })

So my whole script looks like this now:

function es6Bundle() {
    log("✳️  ES6 Bundling!");
    return browserify({
        debug: true
    })
    .add("./src/main.ts")
    .require(require.resolve('three/build/three.module.js'), { expose: 'three' })
    .plugin(tsify, { noImplicitAny: true })
    .transform(
        babelify,
        {
            only: [
                "./node_modules/three/build/three.module.js",
                "./node_modules/three/examples/jsm/*"
              ],
            global: true,
            sourceType: "unambiguous",
            presets: ["@babel/preset-env"],
            plugins: ['@babel/plugin-transform-modules-commonjs']
        }
      )
    .bundle()
    .on('error', function(e) {log.error('Error when updating the Bundle: \n' + e);})
    .on('end', function() {log("➡️  Bundle created, uploading to dist")})
    .pipe(source('bundle.js'))
    .pipe(gulp.dest("dist"))
    .on('end', function() {log("✅  Bundle Updated")});
}

This does solve the duplicate problem, but now I need to run babel on whole of three.bundle.js in order to get it es5 ready. This takes up quite a lot of time. I’ll try doing the same to get my bundle time down. :slight_smile:

@Mugen87 Thanks for the links! I guess it has to do with the resolution of normal three imports (which probably point to build/three.min.js and the resolution of jsm files (which point to build/three.module.js). This is just a guess though, I don’t know how to prove that.

1 Like

@cream So I tried to follow your solution as well to avoid long compile times. I don’t know about your pipeline, are you using browserify? I just bundle after tsify I will get the following error: SyntaxError: 'import' and 'export' may appear only with 'sourceType: module' (9:0) while parsing
Totally makes sense, since browserify needs es5 code, so imports are not allowed.

If I use babel to transform the file, I’ll get ReferenceError: Unknown option: .extensions. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options. while parsing file: C:\project\src\three-examples\loaders\GLTFLoader.js
Not quite sure why that happens, .extensions is quite often in the GLTFLoader.js

build/three.min.js is an ES5 file, it doesn’t export anything so this can’t be the problem.

@JohannesDeml We’re developing with a TypeScript + Rollup.js (code splitting :point_right: modules / no modules) setup, not using Browserify or Babel, see configs :point_right: here.

I should have been more precise. Not really the module, but babel when trying to convert everything to es5. I have this theory since pointing everything to the model solves the problem for me (.require(require.resolve('three/build/three.module.js'), { expose: 'three' })). At least that’s what I think it does.

I see, thanks a lot for the info. I haven’t tried rollup yet, but it seems like a good solution!

Rollup works fine with TypeScript, Three.js, Cannon-es, and OrbitControls.

Debugging command: rollup -cmw
where: -c - create a bundle, -m - debug mode, -w - watch)

Release commands:

  • rollup -c
  • uglifyjs public/js/bundle.js -o public/js/bundle.min.js

I edited the rollup.config.js script from cannon-es (cannon-es is written in TypeScript): cannon-es/rollup.config.js at 38775398362fabbfa90516b2f4f9664230c8388d · pmndrs/cannon-es · GitHub

rollup.config.js

import babel from "@rollup/plugin-babel";
import resolve from "@rollup/plugin-node-resolve";
import filesize from "rollup-plugin-filesize";

const extensions = [".ts"];

const babelOptions = {
    babelrc: false,
    extensions,
    exclude: "**/node_modules/**",
    babelHelpers: "bundled",
    presets: [
        [
            "@babel/preset-env",
            {
                loose: true,
                modules: false,
                targets: ">1%, not dead, not ie 11, not op_mini all",
            },
        ],
        "@babel/preset-typescript",
    ],
};

export default {
    input: "./src/main.ts",
    output: { file: "public/js/bundle.js" },
    plugins: [ resolve({ extensions }), babel(babelOptions), filesize() ]
}

src/main.ts

import * as THREE from "three";
import * as CANNON from "cannon-es";
import { ColladaLoader } from "../../../../node_modules/three/examples/jsm/loaders/ColladaLoader";
import { OrbitControls } from "../../../../node_modules/three/examples/jsm/controls/OrbitControls.js";

console.log(new THREE.Vector3(1, 2, 3));

const world = new CANNON.World( {gravity: new CANNON.Vec3(0, -9.8, 0)} );
console.log("gravity: ", world.gravity);

const loader = new ColladaLoader();
console.log(loader);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderCanvas = document.getElementById("renderCanvas");
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: renderCanvas });
renderer.setSize(window.innerWidth, window.innerHeight);
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.target = new THREE.Vector3(0, 0, 0);
console.log(orbitControls);

public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="renderCanvas"></canvas>

    <script src="js/bundle.js"></script>
</body>

</html>

image

image