@needle-tools/engine 4.7.0-next.4ec920e → 4.7.0-next.598a777

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/{needle-engine.bundle-CaRf1SjI.umd.cjs → needle-engine.bundle-C6_Q9p9H.umd.cjs} +117 -114
  3. package/dist/{needle-engine.bundle-C00XXoql.min.js → needle-engine.bundle-DNmnR0bU.min.js} +115 -112
  4. package/dist/{needle-engine.bundle-DDM6UaI6.js → needle-engine.bundle-DWTaMxD9.js} +3911 -3901
  5. package/dist/needle-engine.js +2 -2
  6. package/dist/needle-engine.min.js +1 -1
  7. package/dist/needle-engine.umd.cjs +1 -1
  8. package/lib/engine/engine_addressables.d.ts +1 -3
  9. package/lib/engine/engine_addressables.js +5 -12
  10. package/lib/engine/engine_addressables.js.map +1 -1
  11. package/lib/engine/engine_context.js +1 -1
  12. package/lib/engine/engine_context.js.map +1 -1
  13. package/lib/engine/engine_lightdata.js +12 -2
  14. package/lib/engine/engine_lightdata.js.map +1 -1
  15. package/lib/engine/engine_three_utils.js +4 -0
  16. package/lib/engine/engine_three_utils.js.map +1 -1
  17. package/lib/engine/webcomponents/needle-engine.attributes.d.ts +1 -1
  18. package/lib/engine-components/Camera.js +1 -1
  19. package/lib/engine-components/Camera.js.map +1 -1
  20. package/lib/engine-components/CameraUtils.js +5 -3
  21. package/lib/engine-components/CameraUtils.js.map +1 -1
  22. package/lib/engine-components/Skybox.js +4 -4
  23. package/lib/engine-components/Skybox.js.map +1 -1
  24. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +8 -1
  25. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  26. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.js +1 -1
  27. package/lib/engine-components/postprocessing/PostProcessingHandler.js +15 -1
  28. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  29. package/lib/engine-components/postprocessing/Volume.js +1 -1
  30. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  31. package/package.json +2 -2
  32. package/plugins/common/logger.js +321 -0
  33. package/plugins/types/userconfig.d.ts +6 -2
  34. package/plugins/vite/alias.js +55 -40
  35. package/plugins/vite/imports-logger.js +1 -1
  36. package/plugins/vite/index.js +4 -0
  37. package/plugins/vite/logger.client.js +262 -0
  38. package/plugins/vite/logger.js +101 -0
  39. package/plugins/vite/materialx.js +32 -0
  40. package/plugins/vite/transform.js +1 -0
  41. package/src/engine/engine_addressables.ts +6 -15
  42. package/src/engine/engine_context.ts +1 -1
  43. package/src/engine/engine_lightdata.ts +10 -2
  44. package/src/engine/engine_three_utils.ts +3 -0
  45. package/src/engine/webcomponents/needle-engine.attributes.ts +1 -1
  46. package/src/engine-components/Camera.ts +1 -1
  47. package/src/engine-components/CameraUtils.ts +5 -3
  48. package/src/engine-components/Skybox.ts +5 -5
  49. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +8 -1
  50. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +1 -1
  51. package/src/engine-components/postprocessing/PostProcessingHandler.ts +14 -2
  52. package/src/engine-components/postprocessing/Volume.ts +1 -1
@@ -1,5 +1,4 @@
1
1
  import { License } from "./license";
2
- import { NeedlePWAOptions } from "./webmanifest.js";
3
2
 
4
3
  export type needleModules = {
5
4
  webpack: object | undefined
@@ -49,7 +48,7 @@ export type userSettings = {
49
48
  /** Custom configuration for facebook instant games. */
50
49
  facebookInstantGames?: {}
51
50
  /** Set to true to create an imports.log file that shows all module imports. The file is generated when stopping the server. */
52
- logModuleImportChains?: boolean;
51
+ debugImportChains?: boolean;
53
52
 
54
53
  /** Set to true to disable generating the buildinfo.json file in your output directory */
55
54
  noBuildInfo?: boolean;
@@ -117,4 +116,9 @@ export type userSettings = {
117
116
  * @default undefined
118
117
  */
119
118
  openBrowser?: boolean;
119
+
120
+ /** Automatically import MaterialX for needle engine in 'main.ts' */
121
+ loadMaterialX?: boolean;
122
+
123
+ disableLogging?: boolean;
120
124
  }
@@ -3,14 +3,33 @@ import path from 'path';
3
3
 
4
4
  const projectDir = process.cwd() + "/";
5
5
 
6
+ /**
7
+ * @typedef {"auto-resolve" | ((res: string, packageName: string, index: number, path: string) => string | null | void)} PackageResolveValue
8
+ */
9
+
6
10
  /** these are alias callbacks as in the vite.alias dictionary
7
11
  * the first argument is the already resoled absolute path (it is only invoked if the path was found in node_modules)
8
12
  * the 2,3,4 args are the same as in vite.alias (packageName, index, path);
9
13
  */
10
14
  /**
11
- * @type {Record<string, null | ((res: string, packageName: string, index: number, path: string) => string | null | void)>}
15
+ * @type {Record<string, PackageResolveValue>}
12
16
  */
13
17
  const packages_to_resolve = {
18
+ // We are currently overriding "three" resolution to ensure that all dependencies resolve to the same three.js version.
19
+ // This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
20
+ // stubborn with their peer dependencies or just slow (slower as we) with updating.
21
+ // NOT adding this allows node.js to correctly resolve `exports` specified in three.js package.json;
22
+ // since we're overriding resolution here we need to manually resolve the subset of exports that we use.
23
+ 'three/addons': (res, packageName, index, path) => {
24
+ return "three/examples/jsm";
25
+ },
26
+ 'three/nodes': (res, packageName, index, path) => {
27
+ return "three/examples/jsm/nodes/Nodes.js";
28
+ },
29
+ 'three': (res, packageName, index, _path) => {
30
+ return path.resolve(projectDir, 'node_modules', 'three');
31
+ },
32
+
14
33
  // Handle all previous imports where users did import using @needle-engine/src
15
34
  '@needle-tools/engine/src': (res, packageName, index, path) => {
16
35
  // resolve old engine/src imports UNLESS it's the asap plugin (the asap plugin currently only exists in the src folder)
@@ -23,21 +42,25 @@ const packages_to_resolve = {
23
42
  because this is automatically done by vite according to whatever we define in our package.json exports
24
43
  This did previously prevent us from declaring proper exports in package.json
25
44
  */
26
- '@needle-tools/engine': (res, packageName, index, path) => {
45
+ '@needle-tools/engine': (res, packageName, index, _path) => {
27
46
  // Check if the import is something like @needle-tools/engine/engine/engine_utils
28
47
  // in which case we want to resolve into the lib directory
29
- if (path.startsWith("@needle-tools/engine/engine")) {
48
+ if (_path.startsWith("@needle-tools/engine/engine")) {
30
49
  return res + "/lib";
31
50
  }
51
+ const node_modules_path = path.resolve(projectDir, 'node_modules', '@needle-tools/engine');
52
+ if (existsSync(node_modules_path)) {
53
+ return node_modules_path;
54
+ }
32
55
  },
33
56
 
34
- /*
35
- Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
36
- 'three': null,
37
- */
38
- 'peerjs': null,
39
- 'websocket-ts': null,
40
- 'md5': null,
57
+ // Auto resolve to 'node_modules/<name>' or '@needle-tools/engine/node_modules/<name>'
58
+ 'peerjs': "auto-resolve",
59
+ 'websocket-ts': "auto-resolve",
60
+ 'md5': "auto-resolve",
61
+ 'three-mesh-bvh': "auto-resolve",
62
+ 'postprocessing': "auto-resolve",
63
+ '@dimforge/rapier3d-compat': "auto-resolve",
41
64
  }
42
65
 
43
66
  /**
@@ -67,6 +90,7 @@ export const needleViteAlias = (command, config, userSettings) => {
67
90
  }
68
91
 
69
92
 
93
+ /** @type {import("vite").Plugin} */
70
94
  const aliasPlugin = {
71
95
  name: "needle-alias",
72
96
  config(config) {
@@ -75,11 +99,9 @@ export const needleViteAlias = (command, config, userSettings) => {
75
99
  if (!config.resolve.alias) config.resolve.alias = {};
76
100
  const aliasDict = config.resolve.alias;
77
101
 
78
- addThreeJSResolvers(aliasDict);
79
-
80
102
  for (const name in packages_to_resolve) {
81
103
  if (!aliasDict[name]) {
82
- addPathResolver(name, aliasDict, packages_to_resolve[name]);
104
+ addPathResolver(name, aliasDict, packages_to_resolve[name], debug ?? false);
83
105
  }
84
106
  }
85
107
 
@@ -127,43 +149,36 @@ export const needleViteAlias = (command, config, userSettings) => {
127
149
  if (debug) return [debuggingPlugin, aliasPlugin];
128
150
  return [aliasPlugin];
129
151
 
130
- function addThreeJSResolvers(aliasDict) {
131
- // We are currently overriding "three" resolution to ensure that all dependencies resolve to the same three.js version.
132
- // This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
133
- // stubborn with their peer dependencies or just slow (slower as we) with updating.
134
- // NOT adding this allows node.js to correctly resolve `exports` specified in three.js package.json;
135
- // since we're overriding resolution here we need to manually resolve the subset of exports that we use.
136
- aliasDict['three/addons'] = (res, packageName, index, path) => {
137
- return "three/examples/jsm";
138
- };
139
- aliasDict['three/nodes'] = (res, packageName, index, path) => {
140
- return "three/examples/jsm/nodes/Nodes.js";
141
- };
142
- aliasDict['three'] = (res, packageName, index, _path) => {
143
- return path.resolve(projectDir, 'node_modules', 'three');
144
- };
145
- }
146
-
147
- function addPathResolver(name, aliasDict, cb) {
152
+ /**
153
+ * Adds a path resolver to the alias dictionary.
154
+ * @param {string} name - The name of the package to resolve.
155
+ * @param {import("vite").AliasOptions} aliasDict - The alias dictionary to add the resolver to.
156
+ * @param {PackageResolveValue | null} value - A callback function to override the default resolution behavior.
157
+ * @param {boolean} debug - Whether to log debug information.
158
+ * @returns {void}
159
+ */
160
+ function addPathResolver(name, aliasDict, value, debug) {
148
161
  // If a package at the node_modules path exist we resolve the request there
149
162
  // introduced in 89a50718c38940abb99ee16c5e029065e41d7d65
150
- const fullpath = path.resolve(projectDir, 'node_modules', name);
151
- if (typeof cb !== "function") cb = null;
152
- const isDevEnvironment = process.env.NODE_ENV === "development";
163
+ const callback = typeof value === "function" ? value : null;
164
+
165
+ let fullpath = path.resolve(projectDir, 'node_modules', name);
166
+ if (!existsSync(fullpath)) {
167
+ fullpath = path.resolve(projectDir, 'node_modules', "@needle-tools/engine", "node_modules", name);
168
+ }
153
169
 
154
170
  if (existsSync(fullpath)) {
155
171
  aliasDict[name] = (packageName, index, path) => {
156
- if (cb !== null && !isDevEnvironment) {
157
- const overrideResult = cb(fullpath, packageName, index, path);
158
- if (overrideResult !== undefined)
172
+ if (callback !== null) {
173
+ const overrideResult = callback(fullpath, packageName, index, path);
174
+ if (typeof overrideResult === "string")
159
175
  if (existsSync(overrideResult)) {
160
- console.warn(`[needle-alias] \"${path}\" was requested and resolved to \"${overrideResult}\"`);
176
+ if (debug) console.warn(`[needle-alias] \"${path}\" was requested and resolved to \"${overrideResult}\"`);
161
177
  return overrideResult;
162
178
  }
163
179
  }
164
180
 
165
- if (path != packageName) {
166
- // TODO: we might want to check if the package.json exports contains the path to see if it's valid
181
+ if (debug && path != packageName) {
167
182
  console.warn(`[needle-alias] \"${path}\" was requested and resolved to \"${fullpath}\"`);
168
183
  }
169
184
 
@@ -6,7 +6,7 @@ import fs from 'fs';
6
6
  */
7
7
  export const needleImportsLogger = (command, config, userSettings) => {
8
8
 
9
- if (!userSettings.logModuleImportChains) return;
9
+ if (!userSettings.debugImportChains) return;
10
10
 
11
11
  const graph = {
12
12
  allNodes: new Map(),
@@ -66,6 +66,8 @@ export { needleBuildInfo } from "./buildinfo.js";
66
66
  import { needleServer } from "./server.js";
67
67
  import { needleNPM } from "./npm.js";
68
68
  import { needleTransformCode } from "./transform.js";
69
+ import { needleMaterialXLoader } from "./materialx.js";
70
+ import { needleLogger } from "./logger.js";
69
71
  export { needleServer } from "./server.js";
70
72
 
71
73
 
@@ -110,6 +112,7 @@ export const needlePlugins = async (command, config = undefined, userSettings =
110
112
  userSettings = { ...defaultUserSettings, ...userSettings };
111
113
 
112
114
  const array = [
115
+ needleLogger(command, config, userSettings),
113
116
  needleDefines(command, config, userSettings),
114
117
  needleLicense(command, config, userSettings),
115
118
  needleViteAlias(command, config, userSettings),
@@ -133,6 +136,7 @@ export const needlePlugins = async (command, config = undefined, userSettings =
133
136
  needlePWA(command, config, userSettings),
134
137
  needleServer(command, config, userSettings),
135
138
  needleNPM(command, config, userSettings),
139
+ needleMaterialXLoader(command, config, userSettings),
136
140
  ];
137
141
 
138
142
  const asap = await needleAsap(command, config, userSettings);
@@ -0,0 +1,262 @@
1
+
2
+ /**
3
+ * Patches console methods to capture log messages and send them to the server.
4
+ * This is useful for debugging and logging in the client.
5
+ * @param {"log" | "warn" | "info" | "debug" | "error" | "internal"} level
6
+ * @param {any} message - The log message to capture.
7
+ */
8
+ function sendLogToServer(level, ...message) {
9
+ if ("hot" in import.meta) {
10
+ message = stringifyLog(message);
11
+ // @ts-ignore
12
+ import.meta.hot.send("needle:client-log", { level, message: message });
13
+ }
14
+ }
15
+
16
+ // const obj = {
17
+ // hello: "world"
18
+ // }
19
+ // obj["test"] = obj;
20
+ // sendLogToServer("internal", "Test circular reference", obj);
21
+
22
+ if (import.meta && "hot" in import.meta) {
23
+
24
+ const originalLog = console.log;
25
+ const originalWarn = console.warn;
26
+ const originalInfo = console.info;
27
+ const originalDebug = console.debug;
28
+ const originalError = console.error;
29
+
30
+ console.log = (...args) => {
31
+ originalLog(...args);
32
+ sendLogToServer("log", ...args);
33
+ }
34
+ console.warn = (...args) => {
35
+ originalWarn(...args);
36
+ sendLogToServer("warn", ...args);
37
+ }
38
+ console.info = (...args) => {
39
+ originalInfo(...args);
40
+ sendLogToServer("info", ...args);
41
+ }
42
+ console.debug = (...args) => {
43
+ originalDebug(...args);
44
+ sendLogToServer("debug", ...args);
45
+ }
46
+ console.error = (...args) => {
47
+ originalError(...args);
48
+ sendLogToServer("error", ...args);
49
+ }
50
+
51
+ try {
52
+ sendLogToServer("internal", `Page loaded
53
+ URL: ${window.location.href}
54
+ UserAgent: ${navigator.userAgent}
55
+ Screen: ${window.innerWidth} x ${window.innerHeight}px
56
+ Device Pixel Ratio: ${window.devicePixelRatio}
57
+ Device Memory: ${"deviceMemory" in navigator ? navigator.deviceMemory : "Not available"} GB
58
+ Online: ${navigator.onLine}
59
+ Language: ${navigator.language}
60
+ Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}
61
+ Connection: ${"connection" in navigator ? JSON.stringify(navigator.connection) : "Not available"}
62
+ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.userActivation) : "Not available"}
63
+ `);
64
+
65
+ if ("gpu" in navigator) {
66
+
67
+ // @ts-ignore
68
+ navigator.gpu.requestAdapter()
69
+ .then(adapter => adapter ? adapter.requestDevice() : null)
70
+ .then(device => {
71
+ if (device) {
72
+ const adapterInfo = device.adapterInfo;
73
+ if (adapterInfo) {
74
+ sendLogToServer("internal", [`WebGPU adapter info`, {
75
+ vendor: adapterInfo.vendor,
76
+ architecture: adapterInfo.architecture,
77
+ device: adapterInfo.device,
78
+ description: adapterInfo.description,
79
+ features: adapterInfo.features,
80
+ limits: adapterInfo.limits
81
+ }]);
82
+ }
83
+ }
84
+ });
85
+ }
86
+ }
87
+ catch (e) {
88
+ // silently fail
89
+ sendLogToServer("error", `Error during initial log: ${e.message}`);
90
+ }
91
+
92
+ window.addEventListener('error', (event) => {
93
+ const errorMessage = event.error ? event.error.stack || event.error.message : event.message;
94
+ sendLogToServer("error", errorMessage);
95
+ });
96
+ window.addEventListener('unhandledrejection', (event) => {
97
+ const reason = event.reason ? event.reason.stack || event.reason.message : "Unhandled rejection without reason";
98
+ sendLogToServer("error", `Unhandled promise rejection: ${reason}`);
99
+ });
100
+ window.addEventListener('beforeunload', () => {
101
+ sendLogToServer("internal", "Page is unloading");
102
+ });
103
+ document.addEventListener('visibilitychange', () => {
104
+ console.log("Visibility changed:", document.visibilityState);
105
+ if (document.visibilityState === 'hidden') {
106
+ sendLogToServer("internal", "Page is hidden");
107
+ }
108
+ else if (document.visibilityState === 'visible') {
109
+ sendLogToServer("internal", "Page is visible again");
110
+ }
111
+ else {
112
+ sendLogToServer("internal", `Page visibility changed to ${document.visibilityState}`);
113
+ }
114
+ });
115
+ window.addEventListener("focus", () => {
116
+ sendLogToServer("internal", "Page gained focus");
117
+ });
118
+ window.addEventListener("blur", () => {
119
+ sendLogToServer("internal", "Page lost focus");
120
+ });
121
+ window.addEventListener('load', () => {
122
+ sendLogToServer("internal", "Page fully loaded");
123
+ });
124
+ window.addEventListener('DOMContentLoaded', () => {
125
+ sendLogToServer("internal", "DOM fully loaded and parsed");
126
+ });
127
+ window.addEventListener('online', () => {
128
+ sendLogToServer("internal", "Browser is online");
129
+ });
130
+ window.addEventListener('offline', () => {
131
+ sendLogToServer("warn", "Browser is offline");
132
+ });
133
+ window.addEventListener('resize', () => {
134
+ sendLogToServer("internal", `Window resized to ${window.innerWidth}x${window.innerHeight}px`);
135
+ });
136
+ window.addEventListener('orientationchange', () => {
137
+ sendLogToServer("internal", `Orientation changed to ${screen.orientation.type}`);
138
+ });
139
+ window.addEventListener('fullscreenchange', () => {
140
+ if (document.fullscreenElement) {
141
+ sendLogToServer("internal", "Entered fullscreen mode");
142
+ } else {
143
+ sendLogToServer("internal", "Exited fullscreen mode");
144
+ }
145
+ });
146
+
147
+
148
+ // url change event
149
+ window.addEventListener('hashchange', () => {
150
+ sendLogToServer("internal", `URL hash changed to ${location.hash}`);
151
+ });
152
+ window.addEventListener('popstate', () => {
153
+ sendLogToServer("internal", `History state changed: ${JSON.stringify(history.state)}`);
154
+ });
155
+
156
+
157
+
158
+ }
159
+
160
+
161
+
162
+
163
+
164
+ // #region copied from common/logger.js
165
+
166
+
167
+ /**
168
+ * Stringifies a log message, handling circular references and formatting.
169
+ * @param {any} log
170
+ * @param {Set<any>} [seen]
171
+ */
172
+ function stringifyLog(log, seen = new Set(), depth = 0) {
173
+
174
+ const isServer = typeof window === "undefined";
175
+ const stringify_limits = {
176
+ string: isServer ? 100_000 : 2000,
177
+ object_keys: isServer ? 100 : 200,
178
+ array_items: isServer ? 2_000 : 100,
179
+ }
180
+
181
+ if (typeof log === "string") {
182
+ if (log.length > stringify_limits.string) log = `${log.slice(0, stringify_limits.string)}... <truncated ${log.length - stringify_limits.string} characters>`;
183
+ return log;
184
+ }
185
+ if (typeof log === "number" || typeof log === "boolean") {
186
+ return String(log);
187
+ }
188
+ if (log === null) {
189
+ return "null";
190
+ }
191
+ if (log === undefined) {
192
+ return "undefined";
193
+ }
194
+ if (typeof log === "function") {
195
+ return "<function>";
196
+ }
197
+
198
+ if (seen.has(log)) return "<circular>";
199
+
200
+ if (Array.isArray(log)) {
201
+ seen.add(log);
202
+ return stringifyArray(log);
203
+ }
204
+ if (typeof log === "object") {
205
+ seen.add(log);
206
+ // const str = JSON.stringify(log, (key, value) => {
207
+ // if (typeof value === "function") return "<function>";
208
+ // if (typeof value === "string") return stringifyLog(value, seen, depth + 1);
209
+ // if (typeof value === "object") {
210
+ // if (seen.has(value)) return "<circular>";
211
+ // seen.add(value);
212
+ // }
213
+ // return value;
214
+ // });
215
+ // return str;
216
+ const keys = Object.keys(log);
217
+ let res = "{";
218
+ for (let i = 0; i < keys.length; i++) {
219
+ const key = keys[i];
220
+ let value = log[key];
221
+
222
+ if (typeof value === "number") {
223
+ // clamp precision for numbers
224
+ value = Number(value.toFixed(6));
225
+ }
226
+ let str = stringifyLog(value, seen, depth + 1);
227
+ if (typeof value === "object") {
228
+ if (Array.isArray(value)) {
229
+ str = `[${str}]`;
230
+ }
231
+ }
232
+ else if (typeof value === "string") {
233
+ str = `"${str}"`;
234
+ }
235
+ if (i > 0) res += ", ";
236
+ res += `"${key}":${str}`;
237
+ }
238
+ res += "}";
239
+ return res;
240
+ // let entries = Object.entries(log).map(([key, value], index) => {
241
+ // if (index > stringify_limits.object_keys) return `"${key}": <truncated>`;
242
+ // return `"${key}": ${stringifyLog(value, seen, depth + 1)}`;
243
+ // });
244
+ // return `{ ${entries.join(", ")} }`;
245
+ }
246
+ return String(log);
247
+
248
+ function stringifyArray(arr) {
249
+ let res = "";
250
+ for (let i = 0; i < arr.length; i++) {
251
+ let entry = arr[i];
252
+ if (res && i > 0) res += ", ";
253
+ if (i > stringify_limits.array_items) {
254
+ res += "<truncated " + (arr.length - i) + ">";
255
+ break;
256
+ }
257
+ res += stringifyLog(entry, seen, depth + 1);
258
+ }
259
+ return res;
260
+ }
261
+ }
262
+
@@ -0,0 +1,101 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { captureLogMessage, patchConsoleLogs } from '../common/logger.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+
10
+ /**
11
+ * write logs to local file
12
+ * @param {import('../types/userconfig.js').userSettings} userSettings
13
+ * @returns {import('vite').Plugin | null}
14
+ */
15
+ export const needleLogger = (command, config, userSettings) => {
16
+
17
+ if (userSettings?.disableLogging === true) {
18
+ return null;
19
+ }
20
+
21
+ patchConsoleLogs();
22
+ captureLogMessage("server", "info", "Vite started with command \"" + command + "\" in " + __dirname, null);
23
+
24
+ return {
25
+ name: "needle:logger",
26
+ enforce: 'pre',
27
+ configureServer(server) {
28
+ logRequests(server);
29
+ },
30
+ configurePreviewServer(server) {
31
+ logRequests(server);
32
+ },
33
+ transformIndexHtml: {
34
+ order: 'pre',
35
+ handler(html, ctx) {
36
+ // inject client logger script during development
37
+ if (command === 'serve') {
38
+ const file = path.join(__dirname, 'logger.client.js');
39
+ if (existsSync(file)) {
40
+ const scriptContent = readFileSync(file, 'utf8');
41
+ return [
42
+ {
43
+ tag: 'script',
44
+ attrs: {
45
+ type: 'module',
46
+ },
47
+ children: scriptContent,
48
+ injectTo: 'head-prepend',
49
+ },
50
+ ];
51
+ }
52
+ }
53
+ },
54
+ },
55
+ }
56
+ }
57
+
58
+
59
+ /**
60
+ * Logs HTTP requests to the console.
61
+ * This function is used in the Vite server to log incoming HTTP requests.
62
+ * @param {import('vite').PreviewServer | import('vite').ViteDevServer} server
63
+ */
64
+ function logRequests(server, log_http_requests = false) {
65
+ /**
66
+ * Logs a message to the server console and captures it.
67
+ * @type {Map<import("vite").WebSocket, {id:string}>}
68
+ */
69
+ const connectedClients = new Map();
70
+ let index = 0;
71
+
72
+ if ("ws" in server) {
73
+ // Clent connections
74
+ server.ws.on('connection', (socket, request) => {
75
+ const clientId = String(index++);
76
+ connectedClients.set(socket, { id: clientId });
77
+ const ip = request.socket.remoteAddress || 'unknown';
78
+ captureLogMessage("server", "connection", `New websocket connection established ${clientId} from ${ip}`, clientId);
79
+ socket.on('close', () => {
80
+ captureLogMessage("server", "connection", `Websocket connection closed ${clientId}`, clientId);
81
+ });
82
+ });
83
+ // Client log messages via websocket
84
+ server.ws.on('needle:client-log', async (data, client) => {
85
+ if (!data || !data.level || !data.message) {
86
+ console.warn("Received empty log data, ignoring");
87
+ return;
88
+ }
89
+ const socket = client.socket;
90
+ const info = connectedClients.get(socket);
91
+ captureLogMessage("client", data.level, data.message, info ? info.id : null);
92
+ });
93
+ }
94
+ // Log HTTP requests
95
+ if (log_http_requests) {
96
+ server.middlewares.use((req, res, next) => {
97
+ captureLogMessage("client-http", "info", [req.method, req.url], null);
98
+ next();
99
+ });
100
+ }
101
+ }
@@ -0,0 +1,32 @@
1
+ import { existsSync } from 'fs';
2
+
3
+ const materialx_packagejson_path = "node_modules/@needle-tools/materialx/package.json";
4
+ const materialx_import_chunk = `
5
+ import { useNeedleMaterialX } from "@needle-tools/materialx/needle";
6
+ useNeedleMaterialX();
7
+ `
8
+
9
+ /**
10
+ * Vite plugin to automatically setup the MaterialX loader for Needle Engine.
11
+ * @param {string} command
12
+ * @param {object} config
13
+ * @param {import('../types/userconfig.js').userSettings} userSettings
14
+ * @returns {import('vite').Plugin}
15
+ */
16
+ export const needleMaterialXLoader = (command, config, userSettings) => {
17
+
18
+ return {
19
+ name: 'needle-materialx-loader',
20
+ transform: (code, id) => {
21
+ if (id.endsWith("src/main.ts")) {
22
+ if (userSettings?.loadMaterialX !== false && existsSync(materialx_packagejson_path)) {
23
+ if (!code.includes("@needle-tools/materialx")) {
24
+ console.log("[needle-materialx-loader] Adding MaterialX import to main.ts");
25
+ code = materialx_import_chunk + "\n" + code;
26
+ }
27
+ }
28
+ }
29
+ return code;
30
+ }
31
+ }
32
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  /**
5
5
  * @param {import('../types').userSettings} userSettings
6
+ * @returns {import('vite').Plugin[]}
6
7
  */
7
8
  export const needleTransformCode = (command, config, userSettings) => {
8
9