@holoscript/cdn 1.0.0
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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/index.cdn.js +207 -0
- package/dist/index.cdn.js.map +1 -0
- package/dist/index.cdn.mjs +177 -0
- package/dist/index.cdn.mjs.map +1 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.d.ts +68 -0
- package/error-tsc-utf8.log +0 -0
- package/error-tsc.log +0 -0
- package/error-utf8.log +262 -0
- package/error.log +0 -0
- package/package.json +43 -0
- package/src/HoloSceneElement.ts +40 -0
- package/src/HoloSceneRenderer.ts +159 -0
- package/src/__tests__/Sprint44.test.d.ts +1 -0
- package/src/__tests__/Sprint44.test.js +152 -0
- package/src/__tests__/Sprint44.test.ts +188 -0
- package/src/config.ts +44 -0
- package/src/index.ts +17 -0
- package/src/types.ts +44 -0
- package/tsconfig.json +21 -0
- package/tsup.config.d.ts +2 -0
- package/tsup.config.js +12 -0
- package/tsup.config.ts +13 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint 44 — @holoscript/holoscript-cdn acceptance tests
|
|
3
|
+
* Covers: defaultCDNConfig, detectOptimalTarget, checkXRSupport,
|
|
4
|
+
* HoloSceneRenderer constructor/render, HoloCDNConfig shape,
|
|
5
|
+
* type exports (HoloSceneTarget, HoloSceneFallback, HoloSceneLoadingState)
|
|
6
|
+
*
|
|
7
|
+
* Environment: jsdom (browser APIs available)
|
|
8
|
+
* NOTE: HoloSceneElement.ts is an empty stub; HoloSceneElement/registerHoloScene
|
|
9
|
+
* are not tested here.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { defaultCDNConfig, HoloSceneRenderer } from '../index';
|
|
13
|
+
import { detectOptimalTarget, checkXRSupport, type HoloCDNConfig } from '../config';
|
|
14
|
+
|
|
15
|
+
// ═══════════════════════════════════════════════
|
|
16
|
+
// defaultCDNConfig
|
|
17
|
+
// ═══════════════════════════════════════════════
|
|
18
|
+
describe('defaultCDNConfig', () => {
|
|
19
|
+
it('is defined', () => {
|
|
20
|
+
expect(defaultCDNConfig).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('cdnBase is a valid URL string', () => {
|
|
24
|
+
expect(typeof defaultCDNConfig.cdnBase).toBe('string');
|
|
25
|
+
expect(defaultCDNConfig.cdnBase).toContain('holoscript');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('defaultTarget is a string', () => {
|
|
29
|
+
expect(typeof defaultCDNConfig.defaultTarget).toBe('string');
|
|
30
|
+
expect(defaultCDNConfig.defaultTarget.length).toBeGreaterThan(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('debug defaults to false', () => {
|
|
34
|
+
expect(defaultCDNConfig.debug).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('loadTimeoutMs is a positive number', () => {
|
|
38
|
+
expect(typeof defaultCDNConfig.loadTimeoutMs).toBe('number');
|
|
39
|
+
expect(defaultCDNConfig.loadTimeoutMs).toBeGreaterThan(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('has all HoloCDNConfig required fields', () => {
|
|
43
|
+
const config: HoloCDNConfig = defaultCDNConfig;
|
|
44
|
+
expect(config).toHaveProperty('cdnBase');
|
|
45
|
+
expect(config).toHaveProperty('defaultTarget');
|
|
46
|
+
expect(config).toHaveProperty('debug');
|
|
47
|
+
expect(config).toHaveProperty('loadTimeoutMs');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ═══════════════════════════════════════════════
|
|
52
|
+
// detectOptimalTarget
|
|
53
|
+
// ═══════════════════════════════════════════════
|
|
54
|
+
describe('detectOptimalTarget', () => {
|
|
55
|
+
it('is a function', () => {
|
|
56
|
+
expect(typeof detectOptimalTarget).toBe('function');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns a string', () => {
|
|
60
|
+
const target = detectOptimalTarget();
|
|
61
|
+
expect(typeof target).toBe('string');
|
|
62
|
+
expect(target.length).toBeGreaterThan(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns a valid HoloSceneTarget value', () => {
|
|
66
|
+
const valid = [
|
|
67
|
+
'webxr',
|
|
68
|
+
'threejs',
|
|
69
|
+
'babylon',
|
|
70
|
+
'unity',
|
|
71
|
+
'godot',
|
|
72
|
+
'visionos',
|
|
73
|
+
'android-xr',
|
|
74
|
+
'auto',
|
|
75
|
+
'webgpu',
|
|
76
|
+
];
|
|
77
|
+
const target = detectOptimalTarget();
|
|
78
|
+
expect(valid).toContain(target);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns "threejs" in jsdom (no WebXR or WebGPU)', () => {
|
|
82
|
+
// jsdom has no navigator.xr or navigator.gpu
|
|
83
|
+
const target = detectOptimalTarget();
|
|
84
|
+
expect(target).toBe('threejs');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ═══════════════════════════════════════════════
|
|
89
|
+
// checkXRSupport
|
|
90
|
+
// ═══════════════════════════════════════════════
|
|
91
|
+
describe('checkXRSupport', () => {
|
|
92
|
+
it('is a function', () => {
|
|
93
|
+
expect(typeof checkXRSupport).toBe('function');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns a Promise', () => {
|
|
97
|
+
const result = checkXRSupport('immersive-vr');
|
|
98
|
+
expect(result).toBeInstanceOf(Promise);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('resolves to false in jsdom (no XR)', async () => {
|
|
102
|
+
const result = await checkXRSupport('immersive-vr');
|
|
103
|
+
expect(result).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('resolves to false for immersive-ar in jsdom', async () => {
|
|
107
|
+
const result = await checkXRSupport('immersive-ar');
|
|
108
|
+
expect(result).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ═══════════════════════════════════════════════
|
|
113
|
+
// HoloSceneRenderer
|
|
114
|
+
// ═══════════════════════════════════════════════
|
|
115
|
+
describe('HoloSceneRenderer', () => {
|
|
116
|
+
it('constructs with a DOM container', () => {
|
|
117
|
+
const container = document.createElement('div');
|
|
118
|
+
const renderer = new HoloSceneRenderer(container);
|
|
119
|
+
expect(renderer).toBeDefined();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('render() returns a Promise', () => {
|
|
123
|
+
const container = document.createElement('div');
|
|
124
|
+
const renderer = new HoloSceneRenderer(container);
|
|
125
|
+
const result = renderer.render('cube { @color(red) }', {
|
|
126
|
+
target: 'threejs',
|
|
127
|
+
width: 640,
|
|
128
|
+
height: 480,
|
|
129
|
+
enableVR: false,
|
|
130
|
+
enableAR: false,
|
|
131
|
+
});
|
|
132
|
+
expect(result).toBeInstanceOf(Promise);
|
|
133
|
+
return result; // let it resolve
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('render() resolves without throwing for threejs target', async () => {
|
|
137
|
+
const container = document.createElement('div');
|
|
138
|
+
document.body.appendChild(container);
|
|
139
|
+
const renderer = new HoloSceneRenderer(container);
|
|
140
|
+
await expect(
|
|
141
|
+
renderer.render('cube { @color(red) }', {
|
|
142
|
+
target: 'threejs',
|
|
143
|
+
width: 320,
|
|
144
|
+
height: 240,
|
|
145
|
+
enableVR: false,
|
|
146
|
+
enableAR: false,
|
|
147
|
+
})
|
|
148
|
+
).resolves.not.toThrow();
|
|
149
|
+
document.body.removeChild(container);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('render() resolves for "auto" target', async () => {
|
|
153
|
+
const container = document.createElement('div');
|
|
154
|
+
const renderer = new HoloSceneRenderer(container);
|
|
155
|
+
await expect(
|
|
156
|
+
renderer.render('sphere { @color(blue) }', {
|
|
157
|
+
target: 'auto',
|
|
158
|
+
width: 100,
|
|
159
|
+
height: 100,
|
|
160
|
+
enableVR: false,
|
|
161
|
+
enableAR: false,
|
|
162
|
+
})
|
|
163
|
+
).resolves.not.toThrow();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('render() resolves for static target (unity)', async () => {
|
|
167
|
+
const container = document.createElement('div');
|
|
168
|
+
const renderer = new HoloSceneRenderer(container);
|
|
169
|
+
await expect(
|
|
170
|
+
renderer.render('cube {}', {
|
|
171
|
+
target: 'unity',
|
|
172
|
+
width: 100,
|
|
173
|
+
height: 100,
|
|
174
|
+
enableVR: false,
|
|
175
|
+
enableAR: false,
|
|
176
|
+
})
|
|
177
|
+
).resolves.not.toThrow();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('creates two renderers independently', () => {
|
|
181
|
+
const c1 = document.createElement('div');
|
|
182
|
+
const c2 = document.createElement('div');
|
|
183
|
+
const r1 = new HoloSceneRenderer(c1);
|
|
184
|
+
const r2 = new HoloSceneRenderer(c2);
|
|
185
|
+
expect(r1).toBeDefined();
|
|
186
|
+
expect(r2).toBeDefined();
|
|
187
|
+
});
|
|
188
|
+
});
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN configuration and defaults
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface HoloCDNConfig {
|
|
6
|
+
cdnBase: string;
|
|
7
|
+
defaultTarget: string;
|
|
8
|
+
debug: boolean;
|
|
9
|
+
loadTimeoutMs: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const defaultCDNConfig: HoloCDNConfig = {
|
|
13
|
+
cdnBase: 'https://cdn.holoscript.net',
|
|
14
|
+
defaultTarget: 'threejs',
|
|
15
|
+
debug: false,
|
|
16
|
+
loadTimeoutMs: 10000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function detectOptimalTarget(): string {
|
|
20
|
+
if (typeof navigator === 'undefined') return 'threejs';
|
|
21
|
+
|
|
22
|
+
if ('xr' in navigator) {
|
|
23
|
+
const nav = navigator as any;
|
|
24
|
+
if (nav.xr?.isSessionSupported) {
|
|
25
|
+
return 'webxr';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ('gpu' in navigator) {
|
|
30
|
+
return 'webgpu';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return 'threejs';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function checkXRSupport(mode: 'immersive-vr' | 'immersive-ar'): Promise<boolean> {
|
|
37
|
+
if (typeof navigator === 'undefined' || !('xr' in navigator)) return false;
|
|
38
|
+
try {
|
|
39
|
+
const nav = navigator as any;
|
|
40
|
+
return await nav.xr.isSessionSupported(mode);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holoscript/cdn — Browser CDN distribution
|
|
3
|
+
*
|
|
4
|
+
* Exposes <holo-scene> custom element for embedding HoloScript scenes
|
|
5
|
+
* in any webpage via a CDN script tag.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* <script src="https://cdn.holoscript.net/v4.min.js"></script>
|
|
9
|
+
* <holo-scene src="./my-world.hs" target="webxr" fallback="threejs"></holo-scene>
|
|
10
|
+
*
|
|
11
|
+
* @version 1.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { HoloSceneElement, registerHoloScene } from './HoloSceneElement.js';
|
|
15
|
+
export { HoloSceneRenderer } from './HoloSceneRenderer.js';
|
|
16
|
+
export { HoloCDNConfig, defaultCDNConfig } from './config.js';
|
|
17
|
+
export type { HoloSceneAttributes, HoloSceneTarget, HoloSceneFallback } from './types.js';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HoloScene custom element attribute types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type HoloSceneTarget =
|
|
6
|
+
| 'webxr'
|
|
7
|
+
| 'threejs'
|
|
8
|
+
| 'babylon'
|
|
9
|
+
| 'unity'
|
|
10
|
+
| 'godot'
|
|
11
|
+
| 'visionos'
|
|
12
|
+
| 'android-xr'
|
|
13
|
+
| 'auto';
|
|
14
|
+
|
|
15
|
+
export type HoloSceneFallback = 'threejs' | 'canvas2d' | 'static-image' | 'none';
|
|
16
|
+
|
|
17
|
+
export type HoloSceneLoadingState = 'idle' | 'loading' | 'compiling' | 'rendering' | 'error';
|
|
18
|
+
|
|
19
|
+
export interface HoloSceneAttributes {
|
|
20
|
+
src?: string;
|
|
21
|
+
inline?: string;
|
|
22
|
+
target?: HoloSceneTarget;
|
|
23
|
+
fallback?: HoloSceneFallback;
|
|
24
|
+
width?: string | number;
|
|
25
|
+
height?: string | number;
|
|
26
|
+
autoplay?: boolean;
|
|
27
|
+
vr?: boolean;
|
|
28
|
+
ar?: boolean;
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
class?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface HoloSceneLoadEvent {
|
|
34
|
+
type: 'holo-load';
|
|
35
|
+
source: string;
|
|
36
|
+
target: HoloSceneTarget;
|
|
37
|
+
compileTimeMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface HoloSceneErrorEvent {
|
|
41
|
+
type: 'holo-error';
|
|
42
|
+
message: string;
|
|
43
|
+
source?: string;
|
|
44
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"resolveJsonModule": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
21
|
+
}
|
package/tsup.config.d.ts
ADDED
package/tsup.config.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
export default defineConfig({
|
|
3
|
+
entry: ['src/index.ts'],
|
|
4
|
+
format: ['cjs', 'esm'],
|
|
5
|
+
dts: true,
|
|
6
|
+
clean: true,
|
|
7
|
+
sourcemap: true,
|
|
8
|
+
outDir: 'dist',
|
|
9
|
+
outExtension({ format }) {
|
|
10
|
+
return { js: format === 'esm' ? '.cdn.mjs' : '.cdn.js' };
|
|
11
|
+
},
|
|
12
|
+
});
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
format: ['cjs', 'esm'],
|
|
6
|
+
dts: true,
|
|
7
|
+
clean: true,
|
|
8
|
+
sourcemap: true,
|
|
9
|
+
outDir: 'dist',
|
|
10
|
+
outExtension({ format }) {
|
|
11
|
+
return { js: format === 'esm' ? '.cdn.mjs' : '.cdn.js' };
|
|
12
|
+
},
|
|
13
|
+
});
|