three.js icon indicating copy to clipboard operation
three.js copied to clipboard

Codesplit WebGL/WebGPU entrypoints

Open CodyJasonBennett opened this issue 1 year ago β€’ 16 comments

Related issue: #29156

Description

Emits a build where three/webgpu re-exports from core to avoid user configuration or duplication. Each entrypoint will no longer bundle a unique copy of three.js but share a single copy.

To prevent future issues with tree-shaking (#28670), I've code split the common core and code specific to a backend.

// src/Three.core.js -> build/three.core.js -> build/three.core.min.js
export { Vector3 } from './math/Vector3.js';

// window.__THREE__ check for duplication here
// src/Three.js -> build/three.module.js -> build/three.module.min.js -> build/three.cjs

export * from './Three.core.js';
export { WebGLRenderer } from './renderers/WebGLRenderer.js';
// src/Three.WebGPU.js -> build/three.webgpu.js -> build/three.webgpu.min.js

export * from './Three.core.js';
export { WebGPURenderer } from './renderers/webgpu/WebGPURenderer.js';
// src/Three.WebGPU.Nodes.js -> build/three.webgpu.nodes.js -> build/three.webgpu.nodes.min.js

export * from './Three.core.js';
export { WebGPURenderer } from './renderers/webgpu/WebGPURenderer.Nodes.js';

CodyJasonBennett avatar Sep 13 '24 19:09 CodyJasonBennett

πŸ“¦ Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 690.29
171.01
338.07
78.63
-352.22 kB
-92.38 kB
WebGPU 815.35
219.78
467.13
129.1
-348.22 kB
-90.68 kB
WebGPU Nodes 814.86
219.65
466.83
129.03
-348.03 kB
-90.62 kB

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 463.31
111.89
463.4
111.57
+93 B
-314 B
WebGPU 538.09
145.28
538.09
145.28
+0 B
+0 B
WebGPU Nodes 494.2
135.15
494.2
135.15
+0 B
+0 B

github-actions[bot] avatar Sep 13 '24 19:09 github-actions[bot]

I think this looks like a good approach! Shared 'core' that the WebGL and WebGPU entrypoints can use, and that doesn't need to be exposed as a separate public entrypoint to users.

(obsolete / resolved issue)

Minor thing, but I'm seeing some duplication of build artifacts when running npm run build locally:

> ls ./build

three.cjs                 three.module.js           three.webgpu.min.js
three.core.min2.js        three.module.min.js       three.webgpu.nodes.js
three.core2.js            three.webgpu.js           three.webgpu.nodes.min.js

donmccurdy avatar Sep 15 '24 15:09 donmccurdy

Should I just remove the build artifacts from this PR or regenerate them?

CodyJasonBennett avatar Oct 10 '24 13:10 CodyJasonBennett

Removing them is better πŸ‘ .

Mugen87 avatar Oct 12 '24 19:10 Mugen87

@mrdoob @sunag Fixing #29156 is crucial for the community so if there are no objections from your side let's merge this so we have a bit of room to r170.

The only workflow that is affected by this change is when devs copy builds around since there is three.core.js now. However, this is not a workflow that we promote in our installation guide (1. npm/build tool, 2. CDN). So I think this is good to go.

Mugen87 avatar Oct 13 '24 12:10 Mugen87

I like the changes πŸ‘

sunag avatar Oct 13 '24 16:10 sunag

I tried this PR on a large-scale project, and overall, it works well. Auto-complete functions correctly, TypeScript issues are resolved, and the only refactoring I had to perform was updating imports to use three/webgpu for WebGPU-specific code (similar to how we previously handled three/tsl).

For example:

import { Matrix4, WebGPURenderer,  StorageInstancedBufferAttribute } from 'three';
// becomes -->
import { Matrix4 } from 'three';
import { WebGPURenderer,  StorageInstancedBufferAttribute } from 'three/webgpu';

Which makes perfect sense.

However, this PR highlighted an existing issue, in most case addons code that relies on ./build/three.webgpu.js won't be resolved out-of-the-box in developers' projects. For example, when using Vite, the following error occurs:

✘ [ERROR] No matching export in "../node_modules/.pnpm/three@file+frontend+three-0.170.0-dev.tgz/node_modules/three/build/three.module.js" for import "Line2NodeMaterial"

    ../node_modules/.pnpm/three@file+frontend+three-0.170.0-dev.tgz/node_modules/three/examples/jsm/lines/webgpu/LineSegments2.js:12:1:
      12 β”‚   Line2NodeMaterial
         β•΅   ~~~~~~~~~~~~~~~~~
✘ [ERROR] No matching export in "../node_modules/.pnpm/three@file+frontend+three-0.170.0-dev.tgz/node_modules/three/build/three.module.js" for import "Line2NodeMaterial"

    ../node_modules/.pnpm/three@file+frontend+three-0.170.0-dev.tgz/node_modules/three/examples/jsm/lines/webgpu/Line2.js:4:9:
      4 β”‚ import { Line2NodeMaterial } from 'three';

Only a small portion of addon code relies on the WebGPU build. We just need to notify that these codes are part of the webgpu build through correct imports. I will submit a PR with a patch so we can merge it after this one.

Other than that, everything is good to go, and working with the WebGPURenderer feels even more amazing now, especially with auto-complete and TypeScript!

RenaudRohlinger avatar Oct 14 '24 07:10 RenaudRohlinger

Well, seems like we all agree to move on with this? Who's gonna take the lead and merge this (and #29644 next)? @Mugen87 @sunag πŸ˜„

RenaudRohlinger avatar Oct 14 '24 12:10 RenaudRohlinger

I was just starting to build a collection here.

{B5E67FE8-FA97-46FD-8977-875A9FF8B95E}

CodyJasonBennett avatar Oct 14 '24 12:10 CodyJasonBennett

Well, seems like we all agree to move on with this? Who's gonna take the lead and merge this (and https://github.com/mrdoob/three.js/pull/29644 next)?

Sorry to ask this a little late, but can't we merge the class structure and then think about splitting the imports in the examples later?

The syntax below changes a lot to be used as main in the examples, I haven't been able to dedicate myself to it yet to analyze other possibilities.

import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu'

sunag avatar Oct 14 '24 16:10 sunag

The syntax below changes a lot to be used as main in the examples, I haven't been able to dedicate myself to it yet to analyze other possibilities.

import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu'

Maybe I didn't clearly explain my point in the previous message. This PR doesn't change the imports in the examples, as we're still manually overriding the import map with "three": "../build/three.webgpu.js", which is essentially a workaround. So, the syntax:

<script type="importmap">
	{
		"imports": {
			"three": "../build/three.webgpu.js"
		}
	}
</script>
import * as THREE from 'three';
const renderer = THREE.WebGPURenderer()

continues to function without any issues, as seen in this PR, since none of the Puppeteer examples break. /cc @sunag

What I intended to highlight is that with this PR, we now have the option to use the correct imports for WebGPU without relying on the "three": "../build/three.webgpu.js" override, allowing us to simplify the import structure going forward.

RenaudRohlinger avatar Oct 15 '24 00:10 RenaudRohlinger

Well, seems like we all agree to move on with this? Who's gonna take the lead and merge this (and https://github.com/mrdoob/three.js/pull/29644 next)?

@mrdoob should definitely approve this change. The type of builds and the import structure is a fundamental aspect of the library and I'm not sure how he feels about this approach.

I understand the PR is a great solution for our current promoted workflows (npm, CDN) but it moves away from the idea to have a renderer with its core classes in a single self-contained build file. Regarding #29644 I'm not sure it's a good user experience to have three, three/webgpu and three/tsl. Hopefully we can merge three/webgpu and three/tsl somehow so the imports get not too complicated for users.

Mugen87 avatar Oct 15 '24 08:10 Mugen87

If you really need a compromise, we can bundle everything into the minified three.module.min.js and three.webgpu.min.js so they are self-contained, but continue code-splitting for non-minified bundles which NPM will accept. That assumes you'd only use one minified bundle at a time, as Renaud indicated for examples. I'm personally not a fan of example code or imports themselves diverging too much based on environment (CDN, bundler), but that is a casualty of this approach or effective workaround.

<script type="importmap">
	{
		"imports": {
			"three": "../build/three.webgpu.min.js"
		}
	}
</script>
import * as THREE from 'three';
const renderer = THREE.WebGPURenderer();
// npm i three
import * as THREE from 'three/webgpu';
const renderer = THREE.WebGPURenderer();

CodyJasonBennett avatar Oct 15 '24 10:10 CodyJasonBennett

It seems the examples can't be properly debugged and inspected if the builds are minified. Ideally, they use an unminified bundle.

That said, I'm personally fine with the initial approach you have implemented. I'm just curious to see how @mrdoob evaluates the PR and the need for self-contained bundles.

Mugen87 avatar Oct 15 '24 10:10 Mugen87

Is there a reason we don't emit sourcemaps? That would have errors in all bundles point back to source code and allow tracing in Lighthouse.

CodyJasonBennett avatar Oct 15 '24 16:10 CodyJasonBennett

@CodyJasonBennett just checking you're aware that the sourcemaps are currently built with yarn dev but not yarn build β€”Β Are you asking about building and publishing sourcemaps to npm as well?

donmccurdy avatar Oct 19 '24 02:10 donmccurdy

@mrdoob should definitely approve this change. The type of builds and the import structure is a fundamental aspect of the library and I'm not sure how he feels about this approach.

That said, I'm personally fine with the initial approach you have implemented. I'm just curious to see how @mrdoob evaluates the PR and the need for self-contained bundles.

Gentle ping @mrdoob 😊. This PR has received approval from most collaborators, but before proceeding, we would really appreciate your input. The import structure and build types are critical to the library's architecture, and we'd like to understand your perspective on the approach:

Self contained:

// npm i three
import * as THREE from 'three/webgpu';
const renderer = THREE.WebGPURenderer();

Or not:

import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu'

RenaudRohlinger avatar Oct 21 '24 11:10 RenaudRohlinger

This update will still require people to update their build setups (or importmaps). The idea in https://github.com/mrdoob/three.js/issues/29156#issuecomment-2457694632 would fully avoid the requirement for build setups to be updated, while providing optional ways to achieve the same goals as this new three/webgpu import path. That alternative would make ecosystem migration easier. Updating a version number and using new APIs is inevitable, but having to also update build setups is a hassle that could be avoided.

trusktr avatar Nov 05 '24 17:11 trusktr

I elaborated in https://github.com/mrdoob/three.js/issues/29156#issuecomment-2458239359, but this is a gross misdiagnosis.

CodyJasonBennett avatar Nov 05 '24 22:11 CodyJasonBennett

The src code split sounds good to me πŸ‘Œ

The naming is becoming weird though.

This would look better to me:

src/Constants.js
src/Core.js
src/Legacy.js
src/Three.js // do we still need this one?
src/Utils.js
src/WebGL.js
src/WebGPU.js

The builds, I'm not so sure...

What do you guys think of this?

builds/three.core.js
builds/three.webgl.js
builds/three.webgpu.js
builds/three.module.js

three.core.js: Core three.webgl.js: WebGLRenderer only three.webgpu.js: WebGPURenderer only (And NodeMaterials only) three.module.js: Core + WebGLRenderer + WebGPURenderer

mrdoob avatar Nov 06 '24 08:11 mrdoob

@mrdoob For src/ files, I like your suggestion. For build/ files, I would think of it in terms of package.json#exports entrypoints. It is probably too soon for the three entrypoint to include WebGPURenderer. Something like this I would support...

  • three -> Core + WebGLRenderer
  • three/webgpu -> Core + WebGPURenderer + Nodes

... which could be implemented on top of build/three.core.js, build/three.webgl.js, and build/three.webgpu.js bundles, similar to what's in this PR but renamed slightly. Optionally there could be additional three/core, three/webgl, and three/tsl entrypoints... I don't feel strongly about any of those others at the moment.

donmccurdy avatar Nov 06 '24 15:11 donmccurdy

It is probably too soon for the three entrypoint to include WebGPURenderer. Something like this I would support...

Having used WebGPURenderer across all my projects for over a year, I fully agree with the concerns. Given the WebGPU API's ongoing instability, it seems premature to push this onto most Three.js developers, especially since many Three.js developers may not adopt WebGPURenderer for some time. Adding it now could undermine previous efforts to optimize bundle size through tree-shaking:

image

(see https://github.com/mrdoob/three.js/pull/29827)

A structure like this strikes a balanced approach:

three -> Core + WebGLRenderer three/webgpu -> Core + WebGPURenderer + Nodes

This setup respects the longevity of WebGLRenderer, which is likely to remain in widespread use, while also providing a clear, separate path for WebGPURenderer. Merging both renderers into the same module would likely add unnecessary complexity, with WebGLRenderer and WebGLBackend coexisting in a confusing, overlapping space.

There’s no real benefit in bundling both WebGLRenderer and WebGPURenderer together in the same app, as they can’t communicate with each other. Furthermore simply switching WebGLRenderer to WebGPURenderer isn’t feasible either, given that the material systems are fundamentally different.

RenaudRohlinger avatar Nov 07 '24 04:11 RenaudRohlinger

I think the naming suggestion is a good one, as the example Core.js instead of Three.core.js.

Merging the two renderers into a single module could limit flexibility in the creation process. For example, in cases like PMREMGenerator, where we kept the same API/interface but internally moved from GLSL to TSL, this could pose challenges if we need to do it in other core classes. It may also make it more difficult to simply swap the renderer from WebGLRenderer to WebGPURenderer, along with other concerns already mentioned.

I would suggest moving forward with the renaming and merging the PR as proposed, and addressing this additional issue later if needed.

sunag avatar Nov 07 '24 06:11 sunag

I would suggest moving forward with the renaming and merging the PR as proposed, and addressing this additional issue later if needed.

Absolutely. What has been suggested in https://github.com/mrdoob/three.js/pull/29404#issuecomment-2460102908 sounds good to me as well.

Mugen87 avatar Nov 07 '24 09:11 Mugen87

https://github.com/mrdoob/three.js/pull/29404#issuecomment-2460102908 sounds good to me too πŸ‘

mrdoob avatar Nov 07 '24 14:11 mrdoob

Nice πŸš€! Then I think we can merge this PR as it is? /cc @Mugen87

RenaudRohlinger avatar Nov 08 '24 05:11 RenaudRohlinger

Yeah, let's start with this configuration. We can apply further updates with additional PRs if necessary.

Mugen87 avatar Nov 08 '24 09:11 Mugen87

πŸ“¦ Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 690.29
171.01
338.07
78.63
-352.22 kB
-92.38 kB

Hmm, how come the minified build is now -92.38 kB less? πŸ€”

mrdoob avatar Nov 20 '24 06:11 mrdoob

I could imagine the script still measures the sizes based on a single build file and does not honor three.core so far.

Mugen87 avatar Nov 20 '24 09:11 Mugen87

Ah, got it. I'll need to update that table again then...

mrdoob avatar Nov 22 '24 07:11 mrdoob