Skip to content

KTX2Loader: Add setTranscoderUrls() for custom transcoder URLs#31446

Open
saqsun wants to merge 3 commits into
mrdoob:devfrom
saqsun:ktx2loader-transcoder-urls
Open

KTX2Loader: Add setTranscoderUrls() for custom transcoder URLs#31446
saqsun wants to merge 3 commits into
mrdoob:devfrom
saqsun:ktx2loader-transcoder-urls

Conversation

@saqsun
Copy link
Copy Markdown

@saqsun saqsun commented Jul 19, 2025

What

Adds a new method setTranscoderUrls() to KTX2Loader:

ktx2Loader.setTranscoderUrls({
  jsUrl: 'path/to/basis_transcoder.js',
  wasmUrl: 'path/to/basis_transcoder.wasm',
});

This method allows to provide explicit URLs for the transcoder JS and WASM files, instead of relying on fixed filenames and directory paths.

Why

The existing setTranscoderPath() method assumes that basis_transcoder.js and .wasm are located in the same directory and have fixed names. This is not always suitable for:

  • Projects using bundlers like Vite, Webpack, or Rollup
  • Single-file deployments (e.g., HTML5 playable ads)
  • CDN-hosted or dynamically resolved asset setups

setTranscoderUrls() provides more control and flexibility while remaining fully backward-compatible.

Implementation Notes

  • If setTranscoderUrls() is used, it overrides setTranscoderPath().
  • If neither method is called, the default path-based behavior is preserved.
  • No external dependencies added.
  • The new method includes full JSDoc for in-editor discoverability.

@Mugen87
Copy link
Copy Markdown
Collaborator

Mugen87 commented Jul 20, 2025

@donmccurdy Looking good to you?

@donmccurdy
Copy link
Copy Markdown
Collaborator

donmccurdy commented Jul 27, 2025

Thanks @saqsun!

I agree that being able to specify one or more file paths, not folder paths, would be better for bundlers. It might be ideal if only a single .js path were required, and that file had a dynamic import for the .wasm...

https://web.dev/articles/bundling-non-js-resources#universal_pattern_for_browsers_and_bundlers

... but I suspect that is more complex and require us customizing the transcoder build, which we probably don't want to do.

So assuming we want to go with the approach in this PR instead, all that I would change would be the naming. Elsewhere (as in LoadingManager) we use all-uppercase URL in methods like .setURLModifier and .resolveURL, and the method is still accepting paths and not full URLs, so I'm not sure "URL" is a good way to differentiate... perhaps one of these?

// (a)
.setTranscoderPath('path/to/script.js', 'path/to/binary.wasm')

// (b)
.setTranscoderPath({js: 'path/to/script.js', wasm: 'path/to/binary.wasm'})

Note that THREE.DRACOLoader might benefit from the same changes, but that doesn't need to be included in this PR.

@saqsun
Copy link
Copy Markdown
Author

saqsun commented Jul 29, 2025

@donmccurdy Thanks for the feedback! 🙌

I've updated the API to use the object-based signature as suggested:

.setTranscoderPath({ js: 'path/to/script.js', wasm: 'path/to/binary.wasm' });

I agree this version is clearer, more extensible, and aligns well with modern API conventions, similar to PixiJS v8’s setBasisTranscoderPath.

Also, I totally see what you meant about the ideal setup where only a single .js path is needed and it dynamically imports the .wasm. That universal bundling pattern from web.dev is really clean, and it's nicely implemented in gltfpack. Agree it would require customizing the transcoder build, which is probably out of scope here, but definitely worth considering for future improvements.

Note: This is a breaking change, previous usages will need to be updated.

@donmccurdy
Copy link
Copy Markdown
Collaborator

donmccurdy commented Sep 28, 2025

Sorry for the delayed response. Thinking about this further, I'd like to spend a "breaking change" on this API only if the result would be that users with modern tools would not be required to configure the path at all (using the web.dev pattern), perhaps with the option of setting the path kept for users of older tools.

But I don't think this PR needs to go that far. It would be a simpler first step to support the two-path API you're requesting, with a fallback if only a single path is given, to avoid a breaking change. I think my preference then would be:

.setTranscoderPath(scriptOrDirectoryPath: string, wasmPath?: string)

@Mugen87 @gkjohnson just want to confirm with you that this sounds OK?

@gkjohnson
Copy link
Copy Markdown
Collaborator

gkjohnson commented Oct 1, 2025

@donmccurdy this sounds good to me. I prefer the more explicit paths for the sake of bundling - and I mention it every once and awhile but I also want to raise the idea of switching the internals of the DRACO and KTX2 loaders to use a new URL( ..., import.meta.url ) syntax. The last time this was discussed was in #24946, over 2.5 years ago.

The blocking issue previously was that the import.metal syntax may not be supported in Webpack 4 but at this point Webpack 5 was released 5 years ago and there is a plugin for supporting import.meta in Webpack 4 so I think it's time (if not past) to keep the project and import ergonomics moving forward and not be held back by old and unsupported bundlers.

If we switch to this model then the setTranscoderPath function will really only be needed for legacy support, I think.

@donmccurdy
Copy link
Copy Markdown
Collaborator

donmccurdy commented Oct 3, 2025

Related:

@gkjohnson I think I agree that we could ship import.meta.url now. We should double-check the modern bundlers and browsers but I believe you're right. I don't see any signs of movement on google/draco#977, so we might need a small patch to the Draco decoder .js file for ESM but that's fine with me.

Also OK with merging this PR after the changes in #31446 (comment), as a quicker fix.

@gkjohnson
Copy link
Copy Markdown
Collaborator

I don't see any signs of movement on google/draco#977, so we might need a small patch to the Draco decoder .js file for ESM but that's fine with me.

Why would would a change be needed? new URL( './path/to/file', import.meta.url ) should generally just inform the bundler that a file is needed and allow it to be included in the bundle. The browser should otherwise be able to load the file just as it is now. We should test it, of course, but if it's working currently I'd expect it to just work?

@donmccurdy
Copy link
Copy Markdown
Collaborator

Oh, yes that might be enough then. There's one note in the Vite docs about SSR (and presumably Node.js more broadly) but we're depending on Web Workers in both KTX2 and Draco loaders already so I don't think that's a concern.

@saqsun
Copy link
Copy Markdown
Author

saqsun commented Oct 15, 2025

Thanks everyone for the detailed discussion, just to confirm that I understand the next steps correctly:

  • For now, the preferred approach is to keep setTranscoderPath() as a non-breaking API and extend it to support both modes:
// Old behavior (folder)
loader.setTranscoderPath('/path/to/folder/');
// New behavior (explicit files)
loader.setTranscoderPath('/path/to/script.js', '/path/to/binary.wasm');

This provides flexibility for bundlers and single-file setups without changing existing usage.

  • In the future, the goal is to move KTX2Loader (and likely DRACOLoader) toward resolving assets internally using:
new URL('./basis_transcoder.js', import.meta.url)

so that modern bundlers (Vite, Webpack 5, Rollup, etc.) can handle these dependencies automatically, and setTranscoderPath() would remain only for legacy overrides.

If so, I can adjust the PR to the two-path version.

@donmccurdy please let me know.

@gkjohnson
Copy link
Copy Markdown
Collaborator

I'm wondering if #33603 address the intent behind this PR? It will no longer necessary to call "setTranscoderPath" when instantiating KTX2Loader assuming your bundler supports the new URL syntax.

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.

4 participants