@displayxr/inline3d 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/js/inline3d-model.js +17 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
|
|
|
5
5
|
`./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
|
|
6
6
|
which tier they touch, because that is what tells you whether an upgrade can move your pixels.
|
|
7
7
|
|
|
8
|
+
## 1.7.1 — 2026-09-20
|
|
9
|
+
|
|
10
|
+
Touches the **preview tier** (`./model`) only, and fixes exactly one thing: under a bundler, a
|
|
11
|
+
compressed glTF never loaded at all. Uncompressed assets are unaffected, and so is every page that
|
|
12
|
+
loads compressed ones through a bare importmap — same pixels, same timing. If your page builds with
|
|
13
|
+
webpack / Turbopack / Vite / rollup and loads a Draco-, KTX2- or meshopt-compressed model, the tile
|
|
14
|
+
that used to stay empty with a rejected `handle.ready` now renders the product.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`addModel` could not load ANY compressed asset under a bundler — all three decoders**
|
|
19
|
+
(preview tier). The decoders were resolved with `await import(spec.module)`, the specifier read
|
|
20
|
+
out of the `DECODERS` table — an *expression*, which no bundler can follow. The build printed
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Critical dependency: the request of a dependency is an expression
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
and shipped a stub that throws `Cannot find module 'three/addons/…'` at runtime, which `addModel`
|
|
27
|
+
then reported honestly as a module-resolution failure in its own decoder error. Draco, KTX2/Basis
|
|
28
|
+
**and** meshopt all went through that one call, so the blast radius was the whole compressed path
|
|
29
|
+
— and a catalogue GLB out of a real pipeline is nearly always compressed, which is precisely the
|
|
30
|
+
case `/model` exists for. Each decoder now has a literal `import()` of its own
|
|
31
|
+
(`load: () => import('three/addons/loaders/DRACOLoader.js')`, and so on): that is what a build
|
|
32
|
+
tool can analyse, and it resolves unchanged under the `"three/addons/"` importmap prefix the
|
|
33
|
+
samples use.
|
|
34
|
+
|
|
35
|
+
It survived four releases because neither path that was exercised has the fault — `samples/` runs
|
|
36
|
+
on a bare importmap, which resolves specifiers at runtime and does not care, and the test suite is
|
|
37
|
+
deliberately dependency-free, so it never imported a decoder at all. The guard added with this fix
|
|
38
|
+
is therefore a source-level one: it reads `js/` the way a bundler does and fails if any `import()`
|
|
39
|
+
is handed a computed specifier again.
|
|
40
|
+
|
|
41
|
+
Nothing else moved. Injection (`{ DRACOLoader }`, `{ KTX2Loader }`, `{ meshoptDecoder }`, a class
|
|
42
|
+
or a ready instance you configured yourself), the shared ref-counted decoder cache, `decoderPath`
|
|
43
|
+
and the serve-the-decoder-files-yourself requirement are all as they were, and a decoder is still
|
|
44
|
+
imported only for an asset that declares its extension — bundlers now code-split each one into its
|
|
45
|
+
own chunk, so an uncompressed GLB downloads none of them.
|
|
46
|
+
|
|
47
|
+
Verified in Chrome against a Next.js 15 / webpack app loading an `EXT_meshopt_compression` GLB:
|
|
48
|
+
before, the build warning above plus `Cannot find module
|
|
49
|
+
'three/addons/libs/meshopt_decoder.module.js'` and an empty stage; after, no warning, the meshopt
|
|
50
|
+
decoder arriving as its own chunk, and the model on screen.
|
|
51
|
+
|
|
8
52
|
## 1.7.0 — 2026-09-19
|
|
9
53
|
|
|
10
54
|
Touches the **preview tier** (`./splat`) only, and additively: `addSplat` with no new options
|
package/js/inline3d-model.js
CHANGED
|
@@ -63,12 +63,23 @@ async function resolveLoader(injected) {
|
|
|
63
63
|
* One entry per decoder: the glTF extension that demands it, where its class lives, where its
|
|
64
64
|
* runtime files live, and which option overrides each. The error messages are generated from
|
|
65
65
|
* this table, so a message can never name an option that does not exist.
|
|
66
|
+
*
|
|
67
|
+
* `load` is a thunk around a **literal** `import()` and `module` is the same specifier as a
|
|
68
|
+
* string, and the duplication is deliberate. A bundler can only follow an import whose specifier
|
|
69
|
+
* is written out at the call site: `import(spec.module)` — reading the string out of this table —
|
|
70
|
+
* is an *expression*, which webpack/Turbopack/rollup cannot resolve, so they emit
|
|
71
|
+
* "Critical dependency: the request of a dependency is an expression" at build time and a stub
|
|
72
|
+
* that throws `Cannot find module …` at runtime. That made EVERY compressed asset unloadable for
|
|
73
|
+
* every bundler consumer, while the bare-importmap path (which resolves at runtime and does not
|
|
74
|
+
* care) kept working — so it survived the samples. The string stays because the error messages
|
|
75
|
+
* quote it; the thunk is what actually loads.
|
|
66
76
|
*/
|
|
67
77
|
const DECODERS = {
|
|
68
78
|
draco: {
|
|
69
79
|
ext: 'KHR_draco_mesh_compression',
|
|
70
80
|
label: 'Draco mesh compression',
|
|
71
81
|
module: 'three/addons/loaders/DRACOLoader.js',
|
|
82
|
+
load: () => import('three/addons/loaders/DRACOLoader.js'),
|
|
72
83
|
exportName: 'DRACOLoader',
|
|
73
84
|
option: 'DRACOLoader',
|
|
74
85
|
pathKey: 'draco',
|
|
@@ -79,6 +90,7 @@ const DECODERS = {
|
|
|
79
90
|
ext: 'KHR_texture_basisu',
|
|
80
91
|
label: 'KTX2 / Basis Universal textures',
|
|
81
92
|
module: 'three/addons/loaders/KTX2Loader.js',
|
|
93
|
+
load: () => import('three/addons/loaders/KTX2Loader.js'),
|
|
82
94
|
exportName: 'KTX2Loader',
|
|
83
95
|
option: 'KTX2Loader',
|
|
84
96
|
pathKey: 'basis',
|
|
@@ -89,6 +101,7 @@ const DECODERS = {
|
|
|
89
101
|
ext: 'EXT_meshopt_compression',
|
|
90
102
|
label: 'meshopt compression',
|
|
91
103
|
module: 'three/addons/libs/meshopt_decoder.module.js',
|
|
104
|
+
load: () => import('three/addons/libs/meshopt_decoder.module.js'),
|
|
92
105
|
exportName: 'MeshoptDecoder',
|
|
93
106
|
option: 'meshoptDecoder',
|
|
94
107
|
pathKey: null, // pure JS + inlined wasm; nothing for the page to serve
|
|
@@ -159,7 +172,10 @@ async function buildDecoder(kind, paths, injected) {
|
|
|
159
172
|
const spec = DECODERS[kind];
|
|
160
173
|
let thing = injected;
|
|
161
174
|
if (!thing) {
|
|
162
|
-
|
|
175
|
+
// spec.load(), never `import(spec.module)` — see the note on DECODERS. One literal specifier
|
|
176
|
+
// per kind is what makes this analysable, and a bundler then code-splits each decoder into
|
|
177
|
+
// its own chunk, still fetched only for an asset that declares the extension.
|
|
178
|
+
const mod = await spec.load();
|
|
163
179
|
thing = mod[spec.exportName];
|
|
164
180
|
if (!thing) throw new Error(`${spec.module} has no export "${spec.exportName}"`);
|
|
165
181
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Turn any HTML <canvas> into a glasses-free-3D window on a DisplayXR display, inside an ordinary web page. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|