Skip to content

DRACOLoader: Use relative file urls by default.#33564

Merged
Mugen87 merged 1 commit into
mrdoob:devfrom
gkjohnson:relative-load
May 18, 2026
Merged

DRACOLoader: Use relative file urls by default.#33564
Mugen87 merged 1 commit into
mrdoob:devfrom
gkjohnson:relative-load

Conversation

@gkjohnson
Copy link
Copy Markdown
Collaborator

@gkjohnson gkjohnson commented May 12, 2026

Related issue: #33140 (comment), #31446

Fixed #26403.

Description

This PR allows DRACOLoader to be used without calling setDecoderPath by using new URL( 'filename.js', import.meta.url ).toString() approach, which all modern bundlers support, to enable specifying a relative path to the /libs files. This will improve the ergonomics of using class, which is currently quite difficult with a number of bundlers or (more commonly) requires pointing to a CDN. The PR is structured to backwards compatibility and allow for users to continue specifying their own config paths if needed.

Some open questions (though not blocking)

  • Perhaps not specifically related to this PR, but Is it reasonable to consider dropping support for the non-WASM path?
  • Would we want to consider deprecating the "setDecoderPath" function?

Once merged I can submit future PRs for

  • Update remaining examples, editor, and docs to remove calls to "setDecoderPath".
  • Update KTX2Loader to mirror this approach.

@gkjohnson gkjohnson requested review from Mugen87 and donmccurdy May 12, 2026 14:08
@Mugen87
Copy link
Copy Markdown
Collaborator

Mugen87 commented May 12, 2026

Perhaps not specifically related to this PR, but Is it reasonable to consider dropping support for the non-WASM path?

Why do you want to drop support for the non-WASM path?

Would we want to consider deprecating the "setDecoderPath" function?

Are there use cases where you want to specify a custom path and not rely "coupled" decoder.

@donmccurdy
Copy link
Copy Markdown
Collaborator

Not reviewed closely yet (I think we'll want to try in several bundlers, and perhaps Node.js...) but I support the direction.

I'd also be in favor of dropping the larger/slower JS path. WASM has been available across browsers since 2017 now and is baseline.

I do think we should keep some version of a setDecoderPath method. Ideally that method, and the default import, would point to a single JS entrypoint rather than a folder, but I realize we probably don't want to start customizing the Draco build.

@gkjohnson
Copy link
Copy Markdown
Collaborator Author

gkjohnson commented May 12, 2026

@Mugen87

Why do you want to drop support for the non-WASM path?

Same reason we drop support for any generally unused or unneeded feature: it's less code in the project and simplifies some of the conditions here. Is there a reason we should keep it? As Don has mentioned, the WASM path should be supported everywhere.

@donmccurdy

I do think we should keep some version of a setDecoderPath method.

There are still some bundlers that do not "natively" support this kind of asset hint so I agree we will still need this until this "just works" everywhere. Is there another reason, though?

I think we'll want to try in several bundlers, and perhaps Node.js...

Node JS I'm unsure about, I suppose, but as far as I know this usage of URL & import.meta.url is valid in all modern bundlers. Though Rollup and ESBuild won't necessarily see it as an asset that needs to be bundled and loaded but it shouldn't break (sorry somewhat misstated before). In these cases the existing "setDecoderPath" can be used if needed.

@donmccurdy
Copy link
Copy Markdown
Collaborator

donmccurdy commented May 12, 2026

There are still some bundlers that do not "natively" support this kind of asset hint so I agree we will still need this until this "just works" everywhere. Is there another reason, though?

For dependencies managed by npm, it's an important option that users can override the dependency resolution, e.g. with yarn resolutions: for investigation with debug builds, testing changes to the dependency, or substituting an equivalent dependency. We're not using npm dependencies of course, but I think it's preferable to offer the same flexibility. Particularly since this is a large and complex dependency.

For https://github.com/donmccurdy/three-gltf-viewer, I override the path because hosting large static WASM binaries is expensive, and can be offloaded to a CDN or Object Storage.

Node JS I'm unsure about, I suppose, but as far as I know this usage of URL & import.meta.url is valid in all modern bundlers...

No strong opinion about Node.js at this point! I agree the support "should" be there in bundlers, I just think we should test if this is the first time we're using this strategy. Most bundlers claim to support handling assets for Web Workers, similarly, but in practice I've found some tricky inconsistencies in their implementations in the past few years. But I'm hopeful WASM should be fine.

@gkjohnson
Copy link
Copy Markdown
Collaborator Author

gkjohnson commented May 13, 2026

No strong opinion about Node.js at this point!

It's likely the case that DRACOLoader already won't work in node without some kind of shim, anyway.

I agree the support "should" be there in bundlers, I just think we should test if this is the first time we're using this strategy. Most bundlers claim to support handling assets for Web Workers, similarly, but in practice I've found some tricky inconsistencies in their implementations in the past few years.

Webworkers are a different beast and I'm still incredibly disappointed by the inability to use them practically in packages, but that's a different rant. Dynamic imports have also historically had problems but perhaps that's improved recently.

I've used claude to set up configs for the major bundlers and evaluate whether they can load a wasm file using the above "URL" syntax. The built files are run in node using a shim for "fetch". You can see the test set up here but the results are what I've mentioned above:

  • Webpack, Vite, Parcel all work with minimal config files.
  • Node requires a shim for loading the wasm via "fetch"
  • Rollup works when paired with the @web/rollup-plugin-import-meta-assets plugin. There are no build failures when the plugin is not present.
  • Esbuild completes a build but the required wasm file is not automatically copied / bundled and so it fails on run.

One final thing to maybe be aware of is that vite will automatically inline any asset urls that are under 4kb (meaning these files may be included even if they are never actually loaded) but that's configurable with assetsInlineLimit. A possible problem seems to be that Vite will bundle that file regardless of size if set to "library" mode but imo that's a vite problem.

@gkjohnson
Copy link
Copy Markdown
Collaborator Author

Any further thoughts on this?

@Mugen87
Copy link
Copy Markdown
Collaborator

Mugen87 commented May 18, 2026

Maybe it's best to give it a try and see how this change works in production?

@donmccurdy
Copy link
Copy Markdown
Collaborator

donmccurdy commented May 18, 2026

It's likely the case that DRACOLoader already won't work in node without some kind of shim, anyway.

The draco and dracogltf dependencies on npm are packaged for Node.js (or were last I checked), so that might be another example where overriding the path can be helpful.

I've tried Vite's "library" mode a few times, and each time it hasn't really seemed ready for non-trivial use. I'm not too worried about that part. For applications Vite is great. I would recommend other tools for someone building a library.

OK with me to go ahead!

@Mugen87 Mugen87 added this to the r185 milestone May 18, 2026
@Mugen87 Mugen87 merged commit a0ab997 into mrdoob:dev May 18, 2026
9 checks passed
@Mugen87 Mugen87 changed the title Suggestion: Update DRACOLoader to use relative file urls by default DRACOLoader: Use relative file urls by default. May 18, 2026
@gkjohnson gkjohnson deleted the relative-load branch May 18, 2026 22:03
@gkjohnson
Copy link
Copy Markdown
Collaborator Author

I've tried Vite's "library" mode a few times, and each time it hasn't really seemed ready for non-trivial use.

It occurs to me that this won't really be an issue unless this project is being built to a library. Any other libraries for which DracoLoader is a dependency should still be imported with a bare import.

Comment on lines +16 to +19
const WASM_BIN_URL = new URL( '../libs/draco/draco_decoder.wasm', import.meta.url ).toString();
const WASM_JS_URL = new URL( '../libs/draco/draco_wasm_wrapper.js', import.meta.url ).toString();
const JS_URL = new URL( '../libs/draco/draco_decoder.js', import.meta.url ).toString();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@donmccurdy - I'm realizing that there are two draco loader decoders in this project: libs/draco/* and libs/draco/gltf/*.

What's the difference between these two? Is the glTF version the same as the basic "draco" one but with some unnecessary code stripped out? Does it make sense to adjust all the examples to remove the "setDecoderPath"? It's a bit unfortunate that we have to have two decoders when the gltf path is likely the most commonly used one. I'm wondering what to do here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See:

If that's still correct, then the "draco" decoder is a superset of the "draco/gltf" decoder, and "draco/gltf" may lack support for decoding point clouds. The glTF KHR_draco_mesh_compression extension doesn't include point cloud compression.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Got it - so it's purely a file size issue and the non-glTF version seems like the right default.

I'm wondering what we should do to enable users to specify using the "gltf" variant, then? Looking some issue history it looks like #24946 or #31446 could be viable. Generally I don't think it's great that these classes require and examples demonstrate the use of functions like setDecoderPath that don't work in any bundlers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is still correct – the gltf variant is smaller and supports only the subset of Draco that is allowed in glTF, whereas the other one supports all of the Draco spec (typically .drc files).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Any thoughts on how we should cleanly enable using the gltf-draco-decoders in a bundler-friendly way? Looking around a bit more I'm actually not sure if bundlers support the same kind of URL* syntax with bare modules. Eg:

loader.setDecoderFiles(
  new URL( 'three/addons/libs/draco/decoder.js' ).toString(),
  new URL( 'three/addons/libs/draco/decoder.wasm' ).toString(),
);

So we're back to either copying the files using CDNs if users want to use the gltf files (which they should). A simple option could just be a flag that toggles the default URLs:

const dracoLoader = new DRACOLoader();
dracoLoader.useGLTFVariant = true;

// loader will now load the gltf versions rather than the full draco versions

Open to any other thoughts, though.

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.

Using DRACOLoader with bundlers

4 participants