@needle-tools/engine 4.15.0-next.f391a30 → 4.15.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/components.needle.json +1 -1
- package/dist/{gltf-progressive-CTlvpS3A.js → gltf-progressive-Bm_6aEi4.js} +1 -1
- package/dist/{gltf-progressive-CMwJPwEt.umd.cjs → gltf-progressive-BttGBXw6.umd.cjs} +1 -1
- package/dist/{gltf-progressive-DYL3SLVb.min.js → gltf-progressive-T5WKTux5.min.js} +1 -1
- package/dist/materialx-CJyQZtjt.min.js +90 -0
- package/dist/materialx-DMs1E08Z.js +4636 -0
- package/dist/materialx-DaKKOoVk.umd.cjs +90 -0
- package/dist/{needle-engine.bundle-DsTdfmeb.min.js → needle-engine.bundle-CBq_OMnI.min.js} +122 -124
- package/dist/{needle-engine.bundle-DB4kLWO_.js → needle-engine.bundle-DGyiwNWR.js} +3226 -3232
- package/dist/{needle-engine.bundle-C1BFRZDF.umd.cjs → needle-engine.bundle-JN3eiiYc.umd.cjs} +113 -115
- package/dist/needle-engine.d.ts +52 -33
- package/dist/needle-engine.js +288 -287
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-BN-f4viE.min.js → postprocessing-06AXuvdv.min.js} +1 -1
- package/dist/{postprocessing-De9ZpJrk.js → postprocessing-CI2x8Cln.js} +1 -1
- package/dist/{postprocessing-DYmYOVm4.umd.cjs → postprocessing-CPDcA21P.umd.cjs} +1 -1
- package/dist/{three-examples-BHqRVpO_.umd.cjs → three-examples-BMmNgNCN.umd.cjs} +12 -12
- package/dist/{three-examples-C0ZCCA_K.js → three-examples-CMYCd5nH.js} +192 -182
- package/dist/{three-examples-DmTY8tGr.min.js → three-examples-CQl1fFZp.min.js} +14 -14
- package/lib/engine/api.d.ts +2 -0
- package/lib/engine/api.js +2 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug.js +1 -1
- package/lib/engine/debug/debug.js.map +1 -1
- package/lib/engine/debug/debug_spatial_console.js +1 -1
- package/lib/engine/debug/debug_spatial_console.js.map +1 -1
- package/lib/engine/engine_accessibility.d.ts +1 -1
- package/lib/engine/engine_accessibility.js +1 -1
- package/lib/engine/engine_accessibility.js.map +1 -1
- package/lib/engine/engine_context.d.ts +1 -1
- package/lib/engine/engine_context.js +2 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.js +1 -1
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gizmos.js +1 -1
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_license.js +2 -7
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_test_utils.d.ts +39 -0
- package/lib/engine/engine_test_utils.js +84 -0
- package/lib/engine/engine_test_utils.js.map +1 -0
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/export/gltf/index.js +1 -1
- package/lib/engine/export/gltf/index.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +3 -6
- package/lib/engine/webcomponents/logo-element.js +0 -18
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +7 -10
- package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -14
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +1 -10
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +0 -3
- package/lib/engine/webcomponents/needle-engine.js +0 -10
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.js +1 -0
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +2 -24
- package/lib/engine-components/ReflectionProbe.js +2 -28
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Skybox.js +2 -4
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
- package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
- package/package.json +14 -18
- package/plugins/common/buildinfo.js +10 -46
- package/plugins/common/files.js +1 -2
- package/plugins/common/license.js +69 -144
- package/plugins/common/logger.js +11 -172
- package/plugins/common/worker.js +4 -5
- package/plugins/types/userconfig.d.ts +2 -40
- package/plugins/vite/alias.js +5 -6
- package/plugins/vite/asap.js +5 -6
- package/plugins/vite/build-pipeline.js +41 -224
- package/plugins/vite/buildinfo.js +6 -66
- package/plugins/vite/copyfiles.js +12 -41
- package/plugins/vite/custom-element-data.js +16 -26
- package/plugins/vite/defines.js +5 -8
- package/plugins/vite/dependencies.js +10 -16
- package/plugins/vite/dependency-watcher.js +7 -35
- package/plugins/vite/drop-client.js +5 -7
- package/plugins/vite/drop.js +14 -16
- package/plugins/vite/editor-connection.js +16 -18
- package/plugins/vite/imports-logger.js +2 -12
- package/plugins/vite/index.js +3 -8
- package/plugins/vite/local-files.js +441 -2
- package/plugins/vite/logger.client.js +35 -45
- package/plugins/vite/logger.js +3 -6
- package/plugins/vite/meta.js +4 -18
- package/plugins/vite/needle-app.js +3 -4
- package/plugins/vite/peer.js +1 -2
- package/plugins/vite/pwa.js +17 -33
- package/plugins/vite/reload.js +2 -24
- package/src/engine/api.ts +3 -0
- package/src/engine/debug/debug.ts +1 -1
- package/src/engine/debug/debug_spatial_console.ts +1 -5
- package/src/engine/engine_accessibility.ts +1 -2
- package/src/engine/engine_context.ts +2 -2
- package/src/engine/engine_create_objects.ts +1 -1
- package/src/engine/engine_gizmos.ts +5 -9
- package/src/engine/engine_license.ts +2 -7
- package/src/engine/engine_test_utils.ts +109 -0
- package/src/engine/engine_utils.ts +2 -2
- package/src/engine/export/gltf/index.ts +1 -1
- package/src/engine/webcomponents/logo-element.ts +3 -20
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -6
- package/src/engine/webcomponents/needle menu/needle-menu.ts +11 -23
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +2 -13
- package/src/engine/webcomponents/needle-engine.ts +1 -13
- package/src/engine-components/Component.ts +2 -1
- package/src/engine-components/ReflectionProbe.ts +9 -33
- package/src/engine-components/Skybox.ts +2 -4
- package/src/engine-components/export/gltf/GltfExport.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
- package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
- package/src/include/draco/draco_decoder.js +34 -0
- package/src/include/draco/draco_decoder.wasm +0 -0
- package/src/include/draco/draco_wasm_wrapper.js +117 -0
- package/src/include/ktx2/basis_transcoder.js +19 -0
- package/src/include/ktx2/basis_transcoder.wasm +0 -0
- package/src/include/needle/arial-msdf.json +1472 -0
- package/src/include/needle/arial.png +0 -0
- package/src/include/needle/poweredbyneedle.webp +0 -0
- package/dist/materialx-4jJLLe9Q.js +0 -4174
- package/dist/materialx-Bt9FHwco.min.js +0 -158
- package/dist/materialx-NDD0y4JY.umd.cjs +0 -158
- package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
- package/plugins/common/needle-engine-skill.md +0 -175
- package/plugins/vite/ai.js +0 -71
- package/plugins/vite/local-files-analysis.js +0 -789
- package/plugins/vite/local-files-core.js +0 -992
- package/plugins/vite/local-files-internals.js +0 -28
- package/plugins/vite/local-files-types.d.ts +0 -111
- package/plugins/vite/local-files-utils.js +0 -359
- package/plugins/vite/logging.js +0 -129
- /package/lib/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
- /package/lib/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.js +0 -0
- /package/src/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.js +0 -0
|
@@ -1,992 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
import {
|
|
3
|
-
SKYBOX_MAGIC_KEYWORDS,
|
|
4
|
-
buildAutoPolicy,
|
|
5
|
-
getLocalFilesAutoPolicy,
|
|
6
|
-
getWebXRProfilesForMode,
|
|
7
|
-
hasAutoFeatureSelection,
|
|
8
|
-
makeFilesLocalIsEnabled,
|
|
9
|
-
resolveOptions,
|
|
10
|
-
resolveSkyboxSelectionUrls,
|
|
11
|
-
resolveSkyboxValueToUrl,
|
|
12
|
-
shouldHandleUrlInAutoMode,
|
|
13
|
-
} from './local-files-analysis.js';
|
|
14
|
-
|
|
15
|
-
/** @typedef {import('./local-files-types.js').LocalizationContext} LocalizationContext */
|
|
16
|
-
/** @typedef {import('./local-files-types.js').LocalizationOptions} LocalizationOptions */
|
|
17
|
-
/** @typedef {import('./local-files-types.js').LocalizationStats} LocalizationStats */
|
|
18
|
-
/** @typedef {import('./local-files-types.js').AutoPolicy} AutoPolicy */
|
|
19
|
-
/** @typedef {import('./local-files-types.js').UrlHandler} UrlHandler */
|
|
20
|
-
import {
|
|
21
|
-
Cache,
|
|
22
|
-
downloadBinary,
|
|
23
|
-
downloadText,
|
|
24
|
-
ensureTrailingSlash,
|
|
25
|
-
fixRelativeNewURL,
|
|
26
|
-
getRelativeToBasePath,
|
|
27
|
-
getShortUrlName,
|
|
28
|
-
getValidFilename,
|
|
29
|
-
normalizeWebPath,
|
|
30
|
-
recordFailedDownload,
|
|
31
|
-
replaceAll,
|
|
32
|
-
finishMakeLocalProgress,
|
|
33
|
-
} from './local-files-utils.js';
|
|
34
|
-
import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';
|
|
35
|
-
|
|
36
|
-
const debug = false;
|
|
37
|
-
|
|
38
|
-
/** @param {unknown} err @returns {string} */
|
|
39
|
-
function getErrMessage(err) { return err instanceof Error ? getErrMessage(err) : String(err); }
|
|
40
|
-
|
|
41
|
-
/** @type {UrlHandler[]} */
|
|
42
|
-
const urlHandlers = [
|
|
43
|
-
{
|
|
44
|
-
name: "Google Fonts CSS",
|
|
45
|
-
pattern: /["'`](https:\/\/fonts\.googleapis\.com\/css2?\?[^"'`]+)["'`]/g,
|
|
46
|
-
type: "css",
|
|
47
|
-
feature: "fonts",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "Google Fonts gstatic",
|
|
51
|
-
pattern: /["'`(](https:\/\/fonts\.gstatic\.com\/[^"'`)]+)["'`)]/g,
|
|
52
|
-
type: "binary",
|
|
53
|
-
feature: "fonts",
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "QRCode.js",
|
|
57
|
-
pattern: /["'`](https:\/\/cdn\.jsdelivr\.net\/gh\/davidshimjs\/qrcodejs@[^"'`]+\/qrcode\.min\.js)["'`]/g,
|
|
58
|
-
type: "binary",
|
|
59
|
-
feature: "cdn-scripts",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "vConsole",
|
|
63
|
-
pattern: /["'`](https:\/\/cdn\.jsdelivr\.net\/npm\/vconsole@[^"'`]+\/dist\/vconsole\.min\.js)["'`]/g,
|
|
64
|
-
type: "binary",
|
|
65
|
-
feature: "cdn-scripts",
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: "HLS.js",
|
|
69
|
-
pattern: /["'`](https:\/\/cdn\.jsdelivr\.net\/npm\/hls\.js@[^"'`]+)["'`]/g,
|
|
70
|
-
type: "binary",
|
|
71
|
-
feature: "cdn-scripts",
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: "WebXR Input Profiles",
|
|
75
|
-
pattern: /["'`](https:\/\/cdn\.jsdelivr\.net\/npm\/@webxr-input-profiles\/assets@[^"'`]*\/dist\/profiles)\/?["'`]/g,
|
|
76
|
-
type: "webxr-profiles",
|
|
77
|
-
feature: "xr",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "Polyhaven",
|
|
81
|
-
pattern: /["'`](https:\/\/dl\.polyhaven\.org\/file\/[^"'`]+)["'`]/g,
|
|
82
|
-
type: "binary",
|
|
83
|
-
feature: "polyhaven",
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "Needle CDN skybox",
|
|
87
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/skybox\/[^"'`]+)["'`]/g,
|
|
88
|
-
type: "binary",
|
|
89
|
-
feature: "skybox",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "Needle CDN fonts",
|
|
93
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/fonts\/[^"'`]+)["'`]/g,
|
|
94
|
-
type: "binary",
|
|
95
|
-
feature: "needle-fonts",
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
name: "Needle CDN models",
|
|
99
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/models\/[^"'`]+)["'`]/g,
|
|
100
|
-
type: "binary",
|
|
101
|
-
feature: "needle-models",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: "Needle CDN avatars",
|
|
105
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/avatars\/[^"'`]+)["'`]/g,
|
|
106
|
-
type: "binary",
|
|
107
|
-
feature: "needle-avatars",
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: "Needle CDN branding",
|
|
111
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/branding\/[^"'`]+)["'`]/g,
|
|
112
|
-
type: "binary",
|
|
113
|
-
feature: "needle-branding",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
name: "Needle CDN basis decoder",
|
|
117
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/three\/[^"'`]*\/basis2\/?)['"`]/g,
|
|
118
|
-
type: "decoder-dir",
|
|
119
|
-
decoderFiles: ["basis_transcoder.js", "basis_transcoder.wasm"],
|
|
120
|
-
localDirName: "basis",
|
|
121
|
-
feature: "ktx2",
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: "Draco decoder (gstatic)",
|
|
125
|
-
pattern: /["'`](https:\/\/www\.gstatic\.com\/draco\/versioned\/decoders\/[^"'`]*\/?)['"`]/g,
|
|
126
|
-
type: "decoder-dir",
|
|
127
|
-
decoderFiles: ["draco_decoder.js", "draco_decoder.wasm", "draco_wasm_wrapper.js"],
|
|
128
|
-
localDirName: "draco",
|
|
129
|
-
feature: "draco",
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "Needle CDN MaterialX",
|
|
133
|
-
pattern: /["'`](https:\/\/cdn\.needle\.tools\/static\/materialx\/[^"'`]*\/?)['"`]/g,
|
|
134
|
-
type: "decoder-dir",
|
|
135
|
-
decoderFiles: ["JsMaterialXCore.wasm", "JsMaterialXGenShader.wasm", "JsMaterialXGenShader.data.txt"],
|
|
136
|
-
localDirName: "materialx",
|
|
137
|
-
feature: "materialx",
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: "Needle uploads",
|
|
141
|
-
pattern: /["'`](https:\/\/uploads\.needle\.tools\/include\/[^"'`]+)["'`]/g,
|
|
142
|
-
type: "binary",
|
|
143
|
-
feature: "needle-uploads",
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: "GitHub raw content",
|
|
147
|
-
pattern: /["'`](https:\/\/raw\.githubusercontent\.com\/[^"'`$]+\.[a-z]{2,5})["'`]/g,
|
|
148
|
-
type: "binary",
|
|
149
|
-
feature: "github-content",
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: "threejs.org models",
|
|
153
|
-
pattern: /["'`](https:\/\/threejs\.org\/examples\/models\/[^"'`]+)["'`]/g,
|
|
154
|
-
type: "binary",
|
|
155
|
-
feature: "threejs-models",
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
export { makeFilesLocalIsEnabled };
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* @returns {LocalizationStats}
|
|
163
|
-
*/
|
|
164
|
-
function createLocalizationStats() {
|
|
165
|
-
return {
|
|
166
|
-
fileCount: 0,
|
|
167
|
-
totalBytes: 0,
|
|
168
|
-
mimeCounts: new Map(),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* @param {string} url
|
|
174
|
-
* @param {string} [fallback]
|
|
175
|
-
* @returns {string}
|
|
176
|
-
*/
|
|
177
|
-
function inferMimeType(url, fallback = "application/octet-stream") {
|
|
178
|
-
const value = String(url || "").split("?")[0].toLowerCase();
|
|
179
|
-
if (value.endsWith(".css")) return "text/css";
|
|
180
|
-
if (value.endsWith(".json")) return "application/json";
|
|
181
|
-
if (value.endsWith(".js") || value.endsWith(".mjs")) return "application/javascript";
|
|
182
|
-
if (value.endsWith(".wasm")) return "application/wasm";
|
|
183
|
-
if (value.endsWith(".ttf")) return "font/ttf";
|
|
184
|
-
if (value.endsWith(".woff2")) return "font/woff2";
|
|
185
|
-
if (value.endsWith(".woff")) return "font/woff";
|
|
186
|
-
if (value.endsWith(".otf")) return "font/otf";
|
|
187
|
-
if (value.endsWith(".png")) return "image/png";
|
|
188
|
-
if (value.endsWith(".jpg") || value.endsWith(".jpeg")) return "image/jpeg";
|
|
189
|
-
if (value.endsWith(".webp")) return "image/webp";
|
|
190
|
-
if (value.endsWith(".exr")) return "image/exr";
|
|
191
|
-
if (value.endsWith(".glb")) return "model/gltf-binary";
|
|
192
|
-
if (value.endsWith(".gltf")) return "model/gltf+json";
|
|
193
|
-
return fallback;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* @param {LocalizationStats} stats
|
|
198
|
-
* @param {string} url
|
|
199
|
-
* @param {number} sizeBytes
|
|
200
|
-
* @param {string | null | undefined} mimeType
|
|
201
|
-
*/
|
|
202
|
-
function recordLocalizedAsset(stats, url, sizeBytes, mimeType) {
|
|
203
|
-
if (!stats || !Number.isFinite(sizeBytes) || sizeBytes < 0) return;
|
|
204
|
-
stats.fileCount += 1;
|
|
205
|
-
stats.totalBytes += sizeBytes;
|
|
206
|
-
const mime = mimeType || inferMimeType(url);
|
|
207
|
-
stats.mimeCounts.set(mime, (stats.mimeCounts.get(mime) || 0) + 1);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* @param {string} command
|
|
212
|
-
* @param {unknown} _config
|
|
213
|
-
* @param {import('../types').userSettings} userSettings
|
|
214
|
-
* @returns {import('vite').Plugin | null}
|
|
215
|
-
*/
|
|
216
|
-
export const needleMakeFilesLocal = (command, _config, userSettings) => {
|
|
217
|
-
if (!makeFilesLocalIsEnabled(userSettings)) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const options = resolveOptions(userSettings);
|
|
222
|
-
|
|
223
|
-
const startupLines = ["Local files plugin is enabled"];
|
|
224
|
-
if (options.platform) startupLines.push("Platform: " + options.platform);
|
|
225
|
-
if (options.exclude?.length) startupLines.push("Custom excludes: " + options.exclude.join(", "));
|
|
226
|
-
if (options.features === "auto") startupLines.push("Feature detection: auto");
|
|
227
|
-
else if (Array.isArray(options.features) && options.features.length) startupLines.push("Features: " + options.features.join(", "));
|
|
228
|
-
if (options.excludeFeatures?.length) startupLines.push("Excluded features: " + options.excludeFeatures.join(", "));
|
|
229
|
-
if (options.packages?.length) startupLines.push("Package filter: " + options.packages.join(", "));
|
|
230
|
-
const projectDir = process.cwd();
|
|
231
|
-
const autoPolicy = getLocalFilesAutoPolicy(projectDir) ?? buildAutoPolicy(options, projectDir);
|
|
232
|
-
|
|
233
|
-
const activeHandlers = getActiveHandlers(options, autoPolicy);
|
|
234
|
-
{
|
|
235
|
-
const featureSet = Array.from(new Set(activeHandlers.map(h => h.feature)));
|
|
236
|
-
const allFeatureSet = Array.from(new Set(urlHandlers.map(h => h.feature)));
|
|
237
|
-
if (options.features === "auto") {
|
|
238
|
-
startupLines.push("Auto-detected features (" + featureSet.length + "/" + allFeatureSet.length + "): " + featureSet.join(", "));
|
|
239
|
-
}
|
|
240
|
-
else if (Array.isArray(options.features) && options.features.length || options.excludeFeatures?.length) {
|
|
241
|
-
startupLines.push("Active features (" + featureSet.length + "): " + featureSet.join(", "));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
needleLog("needle:local-files", startupLines.join("\n"), "log", { dimBody: false });
|
|
245
|
-
|
|
246
|
-
const cache = new Cache();
|
|
247
|
-
/** @type {Map<string, string>} */
|
|
248
|
-
const failedDownloads = new Map();
|
|
249
|
-
const localizationStats = createLocalizationStats();
|
|
250
|
-
|
|
251
|
-
/** @type {import('vite').ResolvedConfig | null} */
|
|
252
|
-
let viteConfig = null;
|
|
253
|
-
|
|
254
|
-
const plugin = /** @type {import('vite').Plugin} */ ({
|
|
255
|
-
name: "needle:local-files",
|
|
256
|
-
apply: "build",
|
|
257
|
-
configResolved(config) {
|
|
258
|
-
viteConfig = config;
|
|
259
|
-
},
|
|
260
|
-
async buildStart() {
|
|
261
|
-
await prefetchConfiguredAssets({
|
|
262
|
-
pluginContext: this,
|
|
263
|
-
cache,
|
|
264
|
-
command,
|
|
265
|
-
viteConfig,
|
|
266
|
-
options,
|
|
267
|
-
autoPolicy,
|
|
268
|
-
failedDownloads,
|
|
269
|
-
localizationStats,
|
|
270
|
-
}, activeHandlers);
|
|
271
|
-
},
|
|
272
|
-
async transform(src, _id) {
|
|
273
|
-
if (options.packages?.length && _id) {
|
|
274
|
-
const matchesPackage = options.packages.some(pkg => _id.includes('/node_modules/' + pkg + '/') || _id.includes('\\node_modules\\' + pkg + '\\'));
|
|
275
|
-
if (!matchesPackage) {
|
|
276
|
-
const isProjectFile = !_id.includes('/node_modules/') && !_id.includes('\\node_modules\\');
|
|
277
|
-
if (!isProjectFile) return { code: src, map: null };
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
const assetsDir = normalizeWebPath(ensureTrailingSlash(viteConfig?.build?.assetsDir || "assets"));
|
|
282
|
-
const isCssTransform = /\.css($|\?)/i.test(_id || "");
|
|
283
|
-
const currentDir = isCssTransform ? assetsDir : "";
|
|
284
|
-
src = await makeLocal(src, "ext/", currentDir, {
|
|
285
|
-
pluginContext: this,
|
|
286
|
-
cache,
|
|
287
|
-
command,
|
|
288
|
-
viteConfig,
|
|
289
|
-
options,
|
|
290
|
-
autoPolicy,
|
|
291
|
-
failedDownloads,
|
|
292
|
-
localizationStats,
|
|
293
|
-
}, activeHandlers);
|
|
294
|
-
src = fixRelativeNewURL(src);
|
|
295
|
-
}
|
|
296
|
-
catch (err) {
|
|
297
|
-
needleLog("needle:local-files", "Error in transform: " + getErrMessage(err), "error");
|
|
298
|
-
}
|
|
299
|
-
return {
|
|
300
|
-
code: src,
|
|
301
|
-
map: null,
|
|
302
|
-
};
|
|
303
|
-
},
|
|
304
|
-
renderChunk(code, chunk) {
|
|
305
|
-
if (!chunk.fileName?.endsWith(".js")) return null;
|
|
306
|
-
const fixed = fixRelativeNewURL(code);
|
|
307
|
-
if (fixed === code) return null;
|
|
308
|
-
return {
|
|
309
|
-
code: fixed,
|
|
310
|
-
map: null,
|
|
311
|
-
};
|
|
312
|
-
},
|
|
313
|
-
generateBundle(_options, bundle) {
|
|
314
|
-
for (const output of Object.values(bundle)) {
|
|
315
|
-
if (output.type !== "chunk") continue;
|
|
316
|
-
if (!output.fileName?.endsWith(".js")) continue;
|
|
317
|
-
const fixed = fixRelativeNewURL(output.code);
|
|
318
|
-
if (fixed !== output.code) output.code = fixed;
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
transformIndexHtml: {
|
|
322
|
-
order: 'pre',
|
|
323
|
-
async handler(html, _ctx) {
|
|
324
|
-
try {
|
|
325
|
-
html = await makeLocalHtml(html, "ext/", {
|
|
326
|
-
pluginContext: null,
|
|
327
|
-
cache,
|
|
328
|
-
command,
|
|
329
|
-
viteConfig,
|
|
330
|
-
options,
|
|
331
|
-
autoPolicy,
|
|
332
|
-
failedDownloads,
|
|
333
|
-
localizationStats,
|
|
334
|
-
}, activeHandlers);
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
needleLog("needle:local-files", "Error in transformIndexHtml: " + getErrMessage(err), "error");
|
|
338
|
-
}
|
|
339
|
-
return html;
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
buildEnd() {
|
|
343
|
-
finishMakeLocalProgress();
|
|
344
|
-
const map = cache.map;
|
|
345
|
-
const shorten = (value, max = 140) => value.length > max ? `${value.slice(0, Math.max(0, max - 1))}…` : value;
|
|
346
|
-
const supportsColor = needleSupportsColor();
|
|
347
|
-
const key = (text) => supportsColor ? needleBlue(text) : text;
|
|
348
|
-
const localized = ["Local files summary"];
|
|
349
|
-
localized.push(key("Files made local") + " : " + localizationStats.fileCount);
|
|
350
|
-
localized.push(key("Total size") + " : " + (localizationStats.totalBytes / 1024).toFixed(2) + " kB");
|
|
351
|
-
localized.push(key("Unique mime types") + " : " + localizationStats.mimeCounts.size);
|
|
352
|
-
if (localizationStats.mimeCounts.size > 0) {
|
|
353
|
-
const mimeBreakdown = Array.from(localizationStats.mimeCounts.entries())
|
|
354
|
-
.sort((a, b) => b[1] - a[1])
|
|
355
|
-
.map(([mime, count]) => supportsColor ? `${mime}${needleDim(` ${count}×`)}` : `${mime} ${count}×`)
|
|
356
|
-
.join(", ");
|
|
357
|
-
localized.push(key("Mime types") + " : " + mimeBreakdown);
|
|
358
|
-
}
|
|
359
|
-
const remoteUrls = Array.from(map.keys()).filter(url => /^https?:\/\//i.test(url));
|
|
360
|
-
const previewLimit = 5;
|
|
361
|
-
needleLog("needle:local-files", localized.join("\n"), "log", { dimBody: false });
|
|
362
|
-
if (remoteUrls.length > 0) {
|
|
363
|
-
needleLog("needle:local-files", remoteUrls.slice(0, previewLimit).map(url => "- " + shorten(url)).join("\n") + (remoteUrls.length > previewLimit ? `\n... and ${remoteUrls.length - previewLimit} more` : ""), "log", { showHeader: false, dimBody: true });
|
|
364
|
-
}
|
|
365
|
-
if (failedDownloads.size > 0) {
|
|
366
|
-
const failed = ["Failed to make local:"];
|
|
367
|
-
for (const [url, message] of Array.from(failedDownloads.entries())) {
|
|
368
|
-
const shortName = getShortUrlName(url);
|
|
369
|
-
failed.push(" " + shortName + " (" + url + ")" + (message ? " - " + message : ""));
|
|
370
|
-
}
|
|
371
|
-
needleLog("needle:local-files", failed.join("\n"), "warn");
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
return plugin;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* @param {LocalizationOptions} options
|
|
380
|
-
* @param {AutoPolicy | null} autoPolicy
|
|
381
|
-
* @returns {UrlHandler[]}
|
|
382
|
-
*/
|
|
383
|
-
export function getActiveHandlers(options, autoPolicy) {
|
|
384
|
-
let handlers = urlHandlers;
|
|
385
|
-
|
|
386
|
-
const hasAuto = hasAutoFeatureSelection(options.features);
|
|
387
|
-
|
|
388
|
-
if (hasAuto && Array.isArray(options.features) && options.features.length) {
|
|
389
|
-
const detected = autoPolicy?.features ?? new Set();
|
|
390
|
-
const includeSet = new Set(options.features.filter(f => f !== "auto"));
|
|
391
|
-
handlers = handlers.filter(h => detected.has(h.feature) || includeSet.has(h.feature));
|
|
392
|
-
}
|
|
393
|
-
else if (hasAuto) {
|
|
394
|
-
const detected = autoPolicy?.features ?? new Set();
|
|
395
|
-
handlers = handlers.filter(h => detected.has(h.feature));
|
|
396
|
-
}
|
|
397
|
-
else if (Array.isArray(options.features) && options.features.length) {
|
|
398
|
-
const includeSet = new Set(options.features.filter(f => f !== "auto"));
|
|
399
|
-
handlers = handlers.filter(h => includeSet.has(h.feature));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (options.excludeFeatures?.length) {
|
|
403
|
-
const excludeSet = new Set(options.excludeFeatures);
|
|
404
|
-
handlers = handlers.filter(h => !excludeSet.has(h.feature));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return handlers;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* @param {string} src
|
|
412
|
-
* @returns {string}
|
|
413
|
-
*/
|
|
414
|
-
function stripComments(src) {
|
|
415
|
-
let result = '';
|
|
416
|
-
let i = 0;
|
|
417
|
-
const len = src.length;
|
|
418
|
-
|
|
419
|
-
while (i < len) {
|
|
420
|
-
const ch = src[i];
|
|
421
|
-
const next = i + 1 < len ? src[i + 1] : '';
|
|
422
|
-
|
|
423
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
424
|
-
const quote = ch;
|
|
425
|
-
result += ch;
|
|
426
|
-
i++;
|
|
427
|
-
while (i < len) {
|
|
428
|
-
const c = src[i];
|
|
429
|
-
if (c === '\\') {
|
|
430
|
-
result += src[i] + (i + 1 < len ? src[i + 1] : '');
|
|
431
|
-
i += 2;
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
if (c === quote) {
|
|
435
|
-
result += c;
|
|
436
|
-
i++;
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
result += c;
|
|
440
|
-
i++;
|
|
441
|
-
}
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (ch === '/' && next === '/') {
|
|
446
|
-
const prev = i > 0 ? src[i - 1] : '';
|
|
447
|
-
if (prev !== ':') {
|
|
448
|
-
while (i < len && src[i] !== '\n') {
|
|
449
|
-
result += ' ';
|
|
450
|
-
i++;
|
|
451
|
-
}
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (ch === '/' && next === '*') {
|
|
457
|
-
result += ' ';
|
|
458
|
-
i += 2;
|
|
459
|
-
while (i < len) {
|
|
460
|
-
if (src[i] === '*' && i + 1 < len && src[i + 1] === '/') {
|
|
461
|
-
result += ' ';
|
|
462
|
-
i += 2;
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
result += src[i] === '\n' ? '\n' : ' ';
|
|
466
|
-
i++;
|
|
467
|
-
}
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
result += ch;
|
|
472
|
-
i++;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return result;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* @param {string} url
|
|
480
|
-
* @param {LocalizationOptions} options
|
|
481
|
-
* @returns {boolean}
|
|
482
|
-
*/
|
|
483
|
-
function shouldExclude(url, options) {
|
|
484
|
-
if (url.includes("${")) return true;
|
|
485
|
-
|
|
486
|
-
if (options.platform === "facebook-instant") {
|
|
487
|
-
if (url.includes("connect.facebook.net")) return true;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (options.exclude) {
|
|
491
|
-
for (const pattern of options.exclude) {
|
|
492
|
-
if (typeof pattern === "string") {
|
|
493
|
-
if (url.includes(pattern)) return true;
|
|
494
|
-
} else if (pattern instanceof RegExp) {
|
|
495
|
-
if (pattern.test(url)) return true;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @param {string} src
|
|
504
|
-
* @param {string} stripped
|
|
505
|
-
* @param {string} basePath
|
|
506
|
-
* @param {string} currentDir
|
|
507
|
-
* @param {LocalizationContext} context
|
|
508
|
-
* @returns {Promise<string>}
|
|
509
|
-
*/
|
|
510
|
-
async function expandTemplateUrls(src, stripped, basePath, currentDir, context) {
|
|
511
|
-
const expansions = context.options.templateExpansions;
|
|
512
|
-
if (!expansions?.length) return src;
|
|
513
|
-
|
|
514
|
-
for (const expansion of expansions) {
|
|
515
|
-
const { cdnPrefix, variables } = expansion;
|
|
516
|
-
if (!cdnPrefix || !variables || !Object.keys(variables).length) continue;
|
|
517
|
-
|
|
518
|
-
const localPrefix = expansion.localPrefix || deriveLocalPrefix(cdnPrefix);
|
|
519
|
-
const localBasePath = basePath + localPrefix + "/";
|
|
520
|
-
|
|
521
|
-
const escapedPrefix = cdnPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
522
|
-
const templateRegex = new RegExp('`' + escapedPrefix + '([^`]*\\$\\{[^`]*)`', 'g');
|
|
523
|
-
|
|
524
|
-
const templateSuffixes = new Set();
|
|
525
|
-
let match;
|
|
526
|
-
while ((match = templateRegex.exec(stripped)) !== null) {
|
|
527
|
-
templateSuffixes.add(match[1]);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (templateSuffixes.size === 0) continue;
|
|
531
|
-
|
|
532
|
-
for (const suffix of Array.from(templateSuffixes)) {
|
|
533
|
-
const expandedSuffixes = expandVariables(suffix, variables);
|
|
534
|
-
|
|
535
|
-
for (const expanded of expandedSuffixes) {
|
|
536
|
-
const concreteUrl = cdnPrefix + expanded;
|
|
537
|
-
const localFilePath = localBasePath + expanded;
|
|
538
|
-
|
|
539
|
-
if (context.cache.getFromCache(concreteUrl)) continue;
|
|
540
|
-
|
|
541
|
-
try {
|
|
542
|
-
const data = await downloadBinary(concreteUrl);
|
|
543
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
544
|
-
context.pluginContext.emitFile({
|
|
545
|
-
type: 'asset',
|
|
546
|
-
fileName: localFilePath,
|
|
547
|
-
source: data,
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
context.cache.addToCache(concreteUrl, localFilePath);
|
|
551
|
-
if (debug) console.log("[needle:local-files] Template expansion: " + concreteUrl + " → " + localFilePath);
|
|
552
|
-
}
|
|
553
|
-
catch (err) {
|
|
554
|
-
needleLog("needle:local-files", "Failed to download template expansion: " + concreteUrl + " - " + getErrMessage(err), "warn", { dimBody: false });
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const replaceRegex = new RegExp('`' + escapedPrefix + '([^`]*\\$\\{[^`]*)`', 'g');
|
|
560
|
-
src = src.replace(replaceRegex, (/** @type {string} */ fullMatch, /** @type {string} */ rest) => {
|
|
561
|
-
const newPrefix = getRelativeToBasePath(localBasePath, currentDir);
|
|
562
|
-
return '`' + newPrefix + rest + '`';
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return src;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* @param {string} cdnPrefix
|
|
571
|
-
* @returns {string}
|
|
572
|
-
*/
|
|
573
|
-
function deriveLocalPrefix(cdnPrefix) {
|
|
574
|
-
try {
|
|
575
|
-
const url = new URL(cdnPrefix);
|
|
576
|
-
const segments = url.pathname.split('/').filter(Boolean);
|
|
577
|
-
return segments[segments.length - 1] || 'cdn';
|
|
578
|
-
}
|
|
579
|
-
catch {
|
|
580
|
-
return 'cdn';
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* @param {string} template
|
|
586
|
-
* @param {Record<string, string[]>} variables
|
|
587
|
-
* @returns {string[]}
|
|
588
|
-
*/
|
|
589
|
-
function expandVariables(template, variables) {
|
|
590
|
-
const varRefs = /** @type {string[]} */ ([]);
|
|
591
|
-
const varRegex = /\$\{(\w+)\}/g;
|
|
592
|
-
/** @type {RegExpExecArray | null} */
|
|
593
|
-
let m = null;
|
|
594
|
-
while ((m = varRegex.exec(template)) !== null) {
|
|
595
|
-
if (!varRefs.includes(m[1])) {
|
|
596
|
-
varRefs.push(m[1]);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
for (const v of varRefs) {
|
|
601
|
-
if (!variables[v]?.length) return [];
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (varRefs.length === 0) return [template];
|
|
605
|
-
|
|
606
|
-
const results = /** @type {string[]} */ ([]);
|
|
607
|
-
/**
|
|
608
|
-
* @param {number} idx
|
|
609
|
-
* @param {Record<string, string>} current
|
|
610
|
-
*/
|
|
611
|
-
function expand(idx, current) {
|
|
612
|
-
if (idx === varRefs.length) {
|
|
613
|
-
let expanded = template;
|
|
614
|
-
for (const [key, value] of Object.entries(current)) {
|
|
615
|
-
expanded = expanded.split('${' + key + '}').join(value);
|
|
616
|
-
}
|
|
617
|
-
results.push(expanded);
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
const varName = varRefs[idx];
|
|
621
|
-
for (const value of variables[varName]) {
|
|
622
|
-
current[varName] = value;
|
|
623
|
-
expand(idx + 1, current);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
expand(0, {});
|
|
627
|
-
return results;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* @param {string} src
|
|
632
|
-
* @param {string} basePath
|
|
633
|
-
* @param {string} currentDir
|
|
634
|
-
* @param {LocalizationContext} context
|
|
635
|
-
* @param {UrlHandler[]} [handlers]
|
|
636
|
-
* @returns {Promise<string>}
|
|
637
|
-
*/
|
|
638
|
-
export async function makeLocal(src, basePath, currentDir, context, handlers) {
|
|
639
|
-
if (!handlers) handlers = urlHandlers;
|
|
640
|
-
|
|
641
|
-
const stripped = stripComments(src);
|
|
642
|
-
|
|
643
|
-
for (const handler of handlers) {
|
|
644
|
-
handler.pattern.lastIndex = 0;
|
|
645
|
-
|
|
646
|
-
const matches = /** @type {string[]} */ ([]);
|
|
647
|
-
/** @type {RegExpExecArray | null} */
|
|
648
|
-
let match = null;
|
|
649
|
-
while ((match = handler.pattern.exec(stripped)) !== null) {
|
|
650
|
-
const url = match[1];
|
|
651
|
-
if (!url || url.length < 10) continue;
|
|
652
|
-
if (shouldExclude(url, context.options)) continue;
|
|
653
|
-
if (!shouldHandleUrlInAutoMode(url, handler, context)) continue;
|
|
654
|
-
if (url.endsWith("/") && handler.type !== "decoder-dir" && handler.type !== "webxr-profiles") continue;
|
|
655
|
-
matches.push(url);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if (matches.length === 0) continue;
|
|
659
|
-
|
|
660
|
-
const uniqueUrls = Array.from(new Set(matches));
|
|
661
|
-
|
|
662
|
-
for (const url of uniqueUrls) {
|
|
663
|
-
try {
|
|
664
|
-
const handlerBasePath = getFeatureBasePath(basePath, handler.feature);
|
|
665
|
-
if (handler.type === "decoder-dir") {
|
|
666
|
-
const localPath = await handleDecoderDir(url, handlerBasePath, currentDir, handler, context);
|
|
667
|
-
if (localPath) src = replaceAll(src, url, localPath);
|
|
668
|
-
}
|
|
669
|
-
else if (handler.type === "webxr-profiles") {
|
|
670
|
-
const localPath = await handleWebXRProfiles(url, handlerBasePath, currentDir, context);
|
|
671
|
-
if (localPath) src = replaceAll(src, url, localPath);
|
|
672
|
-
}
|
|
673
|
-
else if (handler.type === "css") {
|
|
674
|
-
const localPath = await handleCssUrl(url, handlerBasePath, currentDir, context, handlers);
|
|
675
|
-
if (localPath) src = replaceAll(src, url, localPath);
|
|
676
|
-
}
|
|
677
|
-
else {
|
|
678
|
-
const localPath = await handleBinaryUrl(url, handlerBasePath, currentDir, context);
|
|
679
|
-
if (localPath) src = replaceAll(src, url, localPath);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
catch (err) {
|
|
683
|
-
recordFailedDownload(context, url, err);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
src = await expandTemplateUrls(src, stripped, basePath, currentDir, context);
|
|
689
|
-
|
|
690
|
-
return src;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* @param {string} html
|
|
695
|
-
* @param {string} basePath
|
|
696
|
-
* @param {LocalizationContext} context
|
|
697
|
-
* @param {UrlHandler[]} [handlers]
|
|
698
|
-
* @returns {Promise<string>}
|
|
699
|
-
*/
|
|
700
|
-
export async function makeLocalHtml(html, basePath, context, handlers) {
|
|
701
|
-
if (!handlers) handlers = urlHandlers;
|
|
702
|
-
|
|
703
|
-
for (const handler of handlers) {
|
|
704
|
-
handler.pattern.lastIndex = 0;
|
|
705
|
-
/** @type {RegExpExecArray | null} */
|
|
706
|
-
let match = null;
|
|
707
|
-
const matches = /** @type {string[]} */ ([]);
|
|
708
|
-
while ((match = handler.pattern.exec(html)) !== null) {
|
|
709
|
-
const url = match[1];
|
|
710
|
-
if (!url || url.length < 10) continue;
|
|
711
|
-
if (shouldExclude(url, context.options)) continue;
|
|
712
|
-
if (!shouldHandleUrlInAutoMode(url, handler, context)) continue;
|
|
713
|
-
if (url.endsWith("/") && handler.type !== "decoder-dir" && handler.type !== "webxr-profiles") continue;
|
|
714
|
-
matches.push(url);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const uniqueUrls = Array.from(new Set(matches));
|
|
718
|
-
for (const url of uniqueUrls) {
|
|
719
|
-
try {
|
|
720
|
-
const handlerBasePath = getFeatureBasePath(basePath, handler.feature);
|
|
721
|
-
if (handler.type === "css") {
|
|
722
|
-
const localPath = await handleCssUrl(url, handlerBasePath, "", context, handlers);
|
|
723
|
-
if (localPath) html = replaceAll(html, url, localPath);
|
|
724
|
-
}
|
|
725
|
-
else {
|
|
726
|
-
const localPath = await handleBinaryUrl(url, handlerBasePath, "", context);
|
|
727
|
-
if (localPath) html = replaceAll(html, url, localPath);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
catch (err) {
|
|
731
|
-
recordFailedDownload(context, url, err);
|
|
732
|
-
needleLog("needle:local-files", "Failed to make HTML URL local: " + url + " - " + getErrMessage(err), "warn", { dimBody: false });
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return html;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* @param {string} basePath
|
|
741
|
-
* @param {string} feature
|
|
742
|
-
* @returns {string}
|
|
743
|
-
*/
|
|
744
|
-
export function getFeatureBasePath(basePath, feature) {
|
|
745
|
-
const normalized = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
746
|
-
const mappedFeatureDir = mapFeatureToOutputDir(feature);
|
|
747
|
-
if (normalized.endsWith("/" + mappedFeatureDir + "/")) return normalized;
|
|
748
|
-
return normalized + mappedFeatureDir + "/";
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* @param {string} feature
|
|
753
|
-
* @returns {string}
|
|
754
|
-
*/
|
|
755
|
-
function mapFeatureToOutputDir(feature) {
|
|
756
|
-
if (feature === "cdn-scripts") return "scripts";
|
|
757
|
-
if (feature === "needle-fonts") return "fonts";
|
|
758
|
-
if (feature === "needle-avatars") return "xr/avatars";
|
|
759
|
-
if (feature === "polyhaven") return "skybox";
|
|
760
|
-
return feature;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* @param {string} url
|
|
765
|
-
* @param {string} basePath
|
|
766
|
-
* @param {string} currentDir
|
|
767
|
-
* @param {LocalizationContext} context
|
|
768
|
-
* @param {UrlHandler[]} handlers
|
|
769
|
-
* @returns {Promise<string|undefined>}
|
|
770
|
-
*/
|
|
771
|
-
async function handleCssUrl(url, basePath, currentDir, context, handlers) {
|
|
772
|
-
const cached = context.cache.getFromCache(url);
|
|
773
|
-
if (cached) return cached;
|
|
774
|
-
|
|
775
|
-
let cssContent = await downloadText(url);
|
|
776
|
-
cssContent = await makeLocal(cssContent, basePath, basePath, context, handlers);
|
|
777
|
-
recordLocalizedAsset(context.localizationStats, url, Buffer.byteLength(cssContent, "utf8"), "text/css");
|
|
778
|
-
|
|
779
|
-
const familyNameMatch = /family=([^&]+)/.exec(url);
|
|
780
|
-
const familyName = familyNameMatch ? getValidFilename(familyNameMatch[1], cssContent) : getValidFilename(url, cssContent);
|
|
781
|
-
const fileName = "font-" + familyName + ".css";
|
|
782
|
-
const outputPath = basePath + fileName;
|
|
783
|
-
|
|
784
|
-
/** @type {string | undefined} */
|
|
785
|
-
let newPath;
|
|
786
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
787
|
-
const referenceId = context.pluginContext.emitFile({
|
|
788
|
-
type: 'asset',
|
|
789
|
-
fileName: outputPath,
|
|
790
|
-
source: cssContent,
|
|
791
|
-
});
|
|
792
|
-
const localPath = "" + context.pluginContext.getFileName(referenceId);
|
|
793
|
-
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
const base64 = Buffer.from(cssContent).toString('base64');
|
|
797
|
-
newPath = "data:text/css;base64," + base64;
|
|
798
|
-
}
|
|
799
|
-
if (newPath) context.cache.addToCache(url, newPath);
|
|
800
|
-
return newPath;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* @param {string} url
|
|
805
|
-
* @param {string} basePath
|
|
806
|
-
* @param {string} currentDir
|
|
807
|
-
* @param {LocalizationContext} context
|
|
808
|
-
* @returns {Promise<string|undefined>}
|
|
809
|
-
*/
|
|
810
|
-
async function handleBinaryUrl(url, basePath, currentDir, context) {
|
|
811
|
-
const cached = context.cache.getFromCache(url);
|
|
812
|
-
if (cached) return cached;
|
|
813
|
-
|
|
814
|
-
const data = await downloadBinary(url);
|
|
815
|
-
recordLocalizedAsset(context.localizationStats, url, data.length, inferMimeType(url));
|
|
816
|
-
const filename = getValidFilename(url, data);
|
|
817
|
-
|
|
818
|
-
/** @type {string | undefined} */
|
|
819
|
-
let newPath;
|
|
820
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
821
|
-
const referenceId = context.pluginContext.emitFile({
|
|
822
|
-
type: 'asset',
|
|
823
|
-
fileName: basePath + filename,
|
|
824
|
-
source: data,
|
|
825
|
-
});
|
|
826
|
-
const localPath = "" + context.pluginContext.getFileName(referenceId);
|
|
827
|
-
newPath = getRelativeToBasePath(localPath, currentDir);
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
const base64 = Buffer.from(data).toString('base64');
|
|
831
|
-
newPath = "data:application/octet-stream;base64," + base64;
|
|
832
|
-
}
|
|
833
|
-
if (newPath) context.cache.addToCache(url, newPath);
|
|
834
|
-
return newPath;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* @param {string} baseUrl
|
|
839
|
-
* @param {string} basePath
|
|
840
|
-
* @param {string} currentDir
|
|
841
|
-
* @param {UrlHandler} handler
|
|
842
|
-
* @param {LocalizationContext} context
|
|
843
|
-
* @returns {Promise<string>}
|
|
844
|
-
*/
|
|
845
|
-
async function handleDecoderDir(baseUrl, basePath, currentDir, handler, context) {
|
|
846
|
-
const cached = context.cache.getFromCache(baseUrl);
|
|
847
|
-
if (cached) return cached;
|
|
848
|
-
|
|
849
|
-
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
|
|
850
|
-
const localDir = ensureTrailingSlash(basePath);
|
|
851
|
-
const files = handler.decoderFiles || [];
|
|
852
|
-
|
|
853
|
-
for (const file of files) {
|
|
854
|
-
const fileUrl = normalizedBaseUrl + file;
|
|
855
|
-
const fileCached = context.cache.getFromCache(fileUrl);
|
|
856
|
-
if (fileCached) continue;
|
|
857
|
-
|
|
858
|
-
try {
|
|
859
|
-
const data = await downloadBinary(fileUrl);
|
|
860
|
-
recordLocalizedAsset(context.localizationStats, fileUrl, data.length, inferMimeType(fileUrl));
|
|
861
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
862
|
-
const referenceId = context.pluginContext.emitFile({
|
|
863
|
-
type: 'asset',
|
|
864
|
-
fileName: localDir + file,
|
|
865
|
-
source: data,
|
|
866
|
-
});
|
|
867
|
-
const localPath = "" + context.pluginContext.getFileName(referenceId);
|
|
868
|
-
context.cache.addToCache(fileUrl, localPath);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
catch (err) {
|
|
872
|
-
recordFailedDownload(context, fileUrl, err);
|
|
873
|
-
needleLog("needle:local-files", "Failed to download decoder file: " + fileUrl + " - " + getErrMessage(err), "warn", { dimBody: false });
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const newBasePath = getRelativeToBasePath(localDir, currentDir);
|
|
878
|
-
context.cache.addToCache(baseUrl, newBasePath);
|
|
879
|
-
return newBasePath;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* @param {string} baseUrl
|
|
884
|
-
* @param {string} basePath
|
|
885
|
-
* @param {string} currentDir
|
|
886
|
-
* @param {LocalizationContext} context
|
|
887
|
-
* @returns {Promise<string>}
|
|
888
|
-
*/
|
|
889
|
-
export async function handleWebXRProfiles(baseUrl, basePath, currentDir, context) {
|
|
890
|
-
const cached = context.cache.getFromCache(baseUrl);
|
|
891
|
-
if (cached) return cached;
|
|
892
|
-
|
|
893
|
-
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
|
|
894
|
-
const localDir = basePath + "webxr-profiles/";
|
|
895
|
-
const profiles = context.autoPolicy?.selectedWebXRProfiles?.length
|
|
896
|
-
? context.autoPolicy.selectedWebXRProfiles
|
|
897
|
-
: getWebXRProfilesForMode(context.options.webxr);
|
|
898
|
-
|
|
899
|
-
try {
|
|
900
|
-
const profilesListUrl = normalizedBaseUrl + "profilesList.json";
|
|
901
|
-
const profilesList = await downloadBinary(profilesListUrl);
|
|
902
|
-
recordLocalizedAsset(context.localizationStats, profilesListUrl, profilesList.length, "application/json");
|
|
903
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
904
|
-
context.pluginContext.emitFile({
|
|
905
|
-
type: 'asset',
|
|
906
|
-
fileName: localDir + "profilesList.json",
|
|
907
|
-
source: profilesList,
|
|
908
|
-
});
|
|
909
|
-
context.cache.addToCache(profilesListUrl, localDir + "profilesList.json");
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
catch (err) {
|
|
913
|
-
recordFailedDownload(context, normalizedBaseUrl + "profilesList.json", err);
|
|
914
|
-
needleLog("needle:local-files", "Failed to download profilesList.json: " + getErrMessage(err), "warn", { dimBody: false });
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
for (const profile of profiles) {
|
|
918
|
-
const profileDir = localDir + profile + "/";
|
|
919
|
-
const profileBaseUrl = normalizedBaseUrl + profile + "/";
|
|
920
|
-
|
|
921
|
-
const filesToDownload = [
|
|
922
|
-
"profile.json",
|
|
923
|
-
"left.glb",
|
|
924
|
-
"right.glb",
|
|
925
|
-
];
|
|
926
|
-
|
|
927
|
-
for (const file of filesToDownload) {
|
|
928
|
-
const fileUrl = profileBaseUrl + file;
|
|
929
|
-
const fileCached = context.cache.getFromCache(fileUrl);
|
|
930
|
-
if (fileCached) continue;
|
|
931
|
-
|
|
932
|
-
try {
|
|
933
|
-
const data = await downloadBinary(fileUrl);
|
|
934
|
-
recordLocalizedAsset(context.localizationStats, fileUrl, data.length, inferMimeType(fileUrl));
|
|
935
|
-
if (context.command === 'build' && context.pluginContext) {
|
|
936
|
-
context.pluginContext.emitFile({
|
|
937
|
-
type: 'asset',
|
|
938
|
-
fileName: profileDir + file,
|
|
939
|
-
source: data,
|
|
940
|
-
});
|
|
941
|
-
context.cache.addToCache(fileUrl, profileDir + file);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
catch (err) {
|
|
945
|
-
recordFailedDownload(context, fileUrl, err);
|
|
946
|
-
if (debug) needleLog("needle:local-files", "Failed to download WebXR profile file: " + fileUrl + " - " + getErrMessage(err), "warn", { dimBody: false });
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
const newBasePath = getRelativeToBasePath(localDir, currentDir);
|
|
952
|
-
context.cache.addToCache(baseUrl, newBasePath);
|
|
953
|
-
return newBasePath;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* @param {LocalizationContext} context
|
|
958
|
-
* @param {UrlHandler[]} handlers
|
|
959
|
-
* @returns {Promise<void>}
|
|
960
|
-
*/
|
|
961
|
-
async function prefetchConfiguredAssets(context, handlers) {
|
|
962
|
-
const skyboxHandler = handlers.find(h => h.feature === "skybox");
|
|
963
|
-
if (!skyboxHandler) return;
|
|
964
|
-
|
|
965
|
-
const configuredSkyboxUrls = resolveSkyboxSelectionUrls(context.options.skybox, new Set());
|
|
966
|
-
if (!configuredSkyboxUrls || configuredSkyboxUrls.size === 0) {
|
|
967
|
-
if (context.options.skybox === "all") {
|
|
968
|
-
for (const keyword of SKYBOX_MAGIC_KEYWORDS) {
|
|
969
|
-
const url = resolveSkyboxValueToUrl(keyword);
|
|
970
|
-
if (!url) continue;
|
|
971
|
-
try {
|
|
972
|
-
await handleBinaryUrl(url, getFeatureBasePath("ext/", "skybox"), "", context);
|
|
973
|
-
}
|
|
974
|
-
catch (err) {
|
|
975
|
-
recordFailedDownload(context, url, err);
|
|
976
|
-
if (debug) needleLog("needle:local-files", "Failed to prefetch skybox: " + url + " - " + getErrMessage(err), "warn", { dimBody: false });
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
for (const url of configuredSkyboxUrls) {
|
|
984
|
-
try {
|
|
985
|
-
await handleBinaryUrl(url, getFeatureBasePath("ext/", "skybox"), "", context);
|
|
986
|
-
}
|
|
987
|
-
catch (err) {
|
|
988
|
-
recordFailedDownload(context, url, err);
|
|
989
|
-
if (debug) console.warn("[needle:local-files] Failed to prefetch configured skybox: " + url + " - " + getErrMessage(err));
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|