Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ES usage in node #5522

Merged
merged 12 commits into from
May 22, 2024
Merged

Fix ES usage in node #5522

merged 12 commits into from
May 22, 2024

Conversation

vincentfretin
Copy link
Contributor

Description:

The npm run test:node command was broken since we moved to using three ES imports, because of aframe/src/three.js that is requiring an ES module aframe/src/lib/three.module.js and that was not supported in node, only working in webpack environment.
And using require('aframe/src') like described in the documentation https://aframe.io/docs/master/core/globals.html#requiring-aframe-in-a-node-js-environment was also broken.
Now that node 22 supports requiring an ES module with require("somemodule.mjs") currently behind a --experimental-require-module flag, see https://nodejs.org/en/blog/announcements/v22-release-announce#support-requireing-synchronous-esm-graphs, I made an attempt to fix it.

This also simplify for me using aframe as an ES module with import "aframe/src/index.js"; and my own super-three version in a webpack project using only the Buffer polyfill in webpack config, I had issues using the process polyfill with some other dependencies. And it was a nightmare to setup a three-mesh-bvh Worker with three imports because the Worker ends up with no three at all when using THREE external and no way to include an aframe build in the Worker.

Changes proposed:

  • Rename lib/three.module.js to lib/three.mjs for node module resolution to work
  • Move the require('../../vendor/DeviceOrientationControls'); in three.js
  • Remove process polyfill for the dist, only using it for karma to have process.browser and process.nextTick for the tests.
  • Fix isBrowserEnvironment function to properly check for process undefined, also fix condition for setting window.logs to use isBrowserEnvironment
  • Define the three to super-three alias at the npm level instead of webpack
  • Use jsdom-global to set all the window properties on global
  • Run the test:node command with --experimental-require-module node flag, works only on node 22

@@ -40,7 +40,7 @@
"deep-assign": "^2.0.0",
"load-bmfont": "^1.2.3",
"super-animejs": "^3.1.0",
"super-three": "0.164.0",
"three": "npm:super-three@0.164.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just asked chatgpt, this syntax may works only with npm and yarn 1.x, and may not work on yarn 2 or pnpm. I know some of you are using pnpm so we need to verify that. I tried the write a node loader to specify an alias but didn't succeed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was late yesterday for me, so I didn't test with pnpm. Actually it works just fine with it, really don't trust anything that chatgpt is saying, even with gpt-4o, always verify things yourself.
Just tested in the aframe repo, rm -rf node_modules package-lock.json; pnpm install it works just fine

...
dependencies:
+ buffer 6.0.3
+ debug 4.3.4
+ deep-assign 2.0.0 (3.0.0 is available)
+ load-bmfont 1.4.1
+ super-animejs 3.1.0
+ three <- super-three 0.164.0
+ three-bmfont-text 3.0.0
+ webvr-polyfill 0.10.12
...

@dmarcos
Copy link
Member

dmarcos commented May 21, 2024

Thanks for the detail explanation. Do we need to wait for confirmation that this works with pnpm?

src/index.js Outdated
@@ -80,7 +80,7 @@ require('./extras/primitives/');

console.log('A-Frame Version: 1.5.0 (Date 2024-05-03, Commit #9fe641ce)');
console.log('THREE Version (https://github.com/supermedium/three.js):',
pkg.dependencies['super-three']);
pkg.dependencies['three']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would now log the npm alias npm:[email protected] which isn't really informative to the end user. We could consider THREE.REVISION instead, although that wouldn't include patch version AFAIK, so for example 0.164.1 would simply become 164

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, THREE.REVISION is probably enough here. I'll make the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or I can just remove the "npm:super-three@" part with pkg.dependencies['three'].replace('npm:super-three@', '')
so that stays the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's better THREE.REVISION. I have potentially a more recent version of super-three with patches in my project, and returning the version specified in aframe dependency is actually confusing.

@mrxz
Copy link
Contributor

mrxz commented May 21, 2024

It might be a good idea to change the code in inspector.js#L19 as well. Although the webpack.DefinePlugin provides a value for it, so it doesn't actually use/need process it is quite confusing esp. now that process isn't available outside node.

@vincentfretin
Copy link
Contributor Author

Thanks @mrxz for the feedback, I'll make the two changes you suggested.

@vincentfretin
Copy link
Contributor Author

Ok I think it's all good.

@vincentfretin
Copy link
Contributor Author

Just to show you what my project looks like using aframe as an ES module.

in package.json

{
  "overrides": {
    "three": "vincentfretin/three.js#super-r164-lut",
  },
  "dependencies": {
    "aframe": "aframevr/aframe#81caf89",
    "aframe-extras": "^7.3.1",
    "three": "vincentfretin/three.js#super-r164-lut"
  }
}

with currently a patch with this PR changes using patch-package

in webpack.config.js

  ...
  entry: path.resolve(__dirname, "src/index.js"),
  ...
  plugins: [
    new webpack.ProvidePlugin({
      Buffer: ["buffer", "Buffer"],
    }),
   ...
  ],
  externals: {
    // nothing
  }

in src/index.js

import "aframe/src/index.js";                                                      
import "aframe-extras/controls/index.js";                                          
import "aframe-extras/pathfinding/index.js";                                       
import "aframe-extras/misc/sphere-collider.js";

@dmarcos dmarcos merged commit 7ac47c0 into aframevr:master May 22, 2024
1 check passed
@dmarcos
Copy link
Member

dmarcos commented May 22, 2024

Thanks. Should we explore this? #5363

@vincentfretin
Copy link
Contributor Author

I'm all in to make steps towards using ESM modules everywhere, if I can strip some aframe components I'm not using, or switch the renderer with some other implementation, that's clearly great for me. For no build tools, I don't know if we can achieve that, like @mrxz said in #5363 (comment) there are still some dependencies that aren't ESM.

@vincentfretin
Copy link
Contributor Author

That's really awesome to just update in my project package.json

"aframe": "aframevr/aframe#08fe993"

and be done with the update, still using my three fork.

@vincentfretin vincentfretin deleted the test-node branch May 22, 2024 08:57
@mrxz
Copy link
Contributor

mrxz commented May 22, 2024

We can already start transitioning to import syntax in preparation of going fully ESM modules. While we still have a build-step, it can take care of transpiling the non-ESM dependencies. But ultimately we'd have to decide how to deal with them (either drop or replace them). At the time I made a hackish no-build PoC and had to drop the text component as it relies on several small (mostly unmaintained) dependencies.

In any case, replacing require and module.exports with import/export is more a chore than anything difficult.

[...] if I can strip some aframe components I'm not using, or switch the renderer with some other implementation, that's clearly great for me.

Totally agreed, but there is a fundamental problem with A-Frame components. They don't communicate their dependencies. Any setAttribute or even THREE.something basically assumes it to be (globally) present. While it is possible to create a slimmed down A-Frame and only (side-effect) importing components you want, this comes with a lot of trial-and-error to ensure you've got all (transient) dependencies imported as well.

In an ideal world the dependencies would also be imported using import statements. That way the end user would only have to import what they want/need and all dependencies would be pulled in automatically. Likewise three-shaking would "just work". I have been contemplating some approached this could be done, for example allowing setAttribute to accept a component constructor:

import * as AFRAME from 'aframe';
import { SomeComponent } from 'aframe-some-component';

export const OtherComponent = AFRAME.registerComponent('other-component', {
    init: function() {
        this.el.setAttribute(SomeComponent, { color: 'red' });
    }
});

But everything can be done in gradual steps. Having the code use ESM modules internally while still compiling to a traditional bundle would already be a big step.

@dmarcos
Copy link
Member

dmarcos commented May 22, 2024

Would like to retain the script tag syntax alongside modules. While modules are useful I still think they are a worse developer experience for casual prototyping and beginners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants