@@ -15,14 +15,14 @@
|
|
15
15
|
return res + "/../lib";
|
16
16
|
}
|
17
17
|
},
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
|
18
|
+
'@needle-tools/engine': (res, packageName, index, path) => {
|
19
|
+
// Check if the import is something like @needle-tools/engine/engine/engine_utils
|
20
|
+
// in which case we want to resolve into the lib directory
|
21
|
+
if (path.startsWith("@needle-tools/engine/engine")) {
|
22
|
+
return res + "/lib";
|
23
|
+
}
|
24
|
+
},
|
25
|
+
/* Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
|
26
26
|
'three': null,
|
27
27
|
*/
|
28
28
|
'peerjs': null,
|
@@ -1,85 +1,21 @@
|
|
1
|
-
import { ChildProcess, exec } from 'child_process';
|
2
|
-
import { getOutputDirectory, loadConfig } from './config.js';
|
3
|
-
import { existsSync,
|
4
|
-
import { copyFilesSync } from '../common/files.js';
|
5
|
-
import { delay } from '../common/timers.js';
|
1
|
+
import { ChildProcess, exec, execSync, spawn } from 'child_process';
|
2
|
+
import { getOutputDirectory, loadConfig, tryLoadProjectConfig } from './config.js';
|
3
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs';
|
6
4
|
|
7
|
-
/**
|
8
|
-
* @param {import('../types').userSettings} config
|
9
|
-
* @returns {boolean}
|
10
|
-
*/
|
11
|
-
function validateCloudBuildConfiguration(config) {
|
12
|
-
if (config.buildPipeline != undefined) {
|
13
|
-
if (process.env.CI) {
|
14
|
-
if ((process.env.NEEDLE_CLOUD_TOKEN == undefined || process.env.NEEDLE_CLOUD_TOKEN.length <= 0)) {
|
15
|
-
const isGithubAction = process.env.CI && process.env.GITHUB_ACTION;
|
16
|
-
let msg = `Missing Needle Cloud access token. Please set your Needle Cloud token via \`process.env.NEEDLE_CLOUD_TOKEN\`.`;
|
17
|
-
if (isGithubAction) {
|
18
|
-
msg += `
|
19
|
-
Make sure to pass the token as a secret to the Github action.
|
20
|
-
For this you may have to modify your .github workflow yaml file to include the following code:
|
21
|
-
env:
|
22
|
-
- NEEDLE_CLOUD_TOKEN: \${{ secrets.NEEDLE_CLOUD_TOKEN }}
|
23
|
-
`;
|
24
|
-
let error = `${msg}`;
|
25
|
-
throw new Error(error);
|
26
|
-
}
|
27
|
-
}
|
28
|
-
}
|
29
|
-
}
|
30
|
-
|
31
|
-
return true;
|
32
|
-
}
|
33
|
-
|
34
5
|
// see https://linear.app/needle/issue/NE-3798
|
35
6
|
|
7
|
+
export let buildPipelineTask;
|
36
8
|
|
37
|
-
/** @type {Promise<any>|null} */
|
38
|
-
let buildPipelineTask;
|
39
|
-
/** @type {null | {tempDirectory:string, outputDirectory:string}} */
|
40
|
-
let buildPipelineTaskResults = null;
|
41
|
-
|
42
|
-
export function waitForBuildPipelineToFinish() {
|
43
|
-
return buildPipelineTask;
|
44
|
-
}
|
45
|
-
|
46
|
-
/** This time is set when a build plugin is triggered to run
|
47
|
-
* We increase the time by 10-20 seconds each time because we might have a multi step process
|
48
|
-
* where the build pipeline is triggered in the SSR build (which we can not always detect)
|
49
|
-
* and the final client build is triggered later (but the build pipeline is still running and waiting)
|
50
|
-
*/
|
51
|
-
let maxOutputDirectoryCreatedWaitTime = 0;
|
52
9
|
/**
|
53
|
-
* @param {boolean} debugLog
|
54
|
-
*/
|
55
|
-
function increaseMaxWaitTime(debugLog) {
|
56
|
-
maxOutputDirectoryCreatedWaitTime = Date.now();
|
57
|
-
let maxWaitTime = 10_000;
|
58
|
-
if (process.env.CI) {
|
59
|
-
maxWaitTime += 50_000;
|
60
|
-
}
|
61
|
-
maxOutputDirectoryCreatedWaitTime += maxWaitTime;
|
62
|
-
if (debugLog) {
|
63
|
-
log(`Increased max wait time by ${maxWaitTime / 1000}sec until ${new Date(maxOutputDirectoryCreatedWaitTime).toISOString()}`);
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
|
-
/**
|
68
10
|
* Runs the needle build pipeline as part of the vite build process
|
69
|
-
* @param {string} command
|
70
|
-
* @param {import('vite').UserConfig} config
|
71
11
|
* @param {import('../types').userSettings} userSettings
|
72
|
-
* @returns {
|
12
|
+
* @returns {import('vite').Plugin}
|
73
13
|
*/
|
74
14
|
export const needleBuildPipeline = async (command, config, userSettings) => {
|
75
15
|
|
76
16
|
// we only want to run compression here if this is a distribution build
|
77
17
|
// this is handled however in the `apply` hook
|
78
|
-
if (userSettings.noBuildPipeline) return
|
79
|
-
if (userSettings.buildPipeline?.enabled === false) {
|
80
|
-
log("Skipping build pipeline because it is disabled in the user settings via `buildPipeline.enabled: false`");
|
81
|
-
return null;
|
82
|
-
}
|
18
|
+
if (userSettings.noBuildPipeline) return;
|
83
19
|
|
84
20
|
const packageJsonPath = process.cwd() + "/package.json";
|
85
21
|
await fixPackageJson(packageJsonPath);
|
@@ -90,102 +26,53 @@
|
|
90
26
|
if (productionArgument >= 0) {
|
91
27
|
shouldRun = true;
|
92
28
|
}
|
29
|
+
else {
|
30
|
+
const meta = await loadConfig();
|
31
|
+
if (meta && meta.developmentBuild === false) {
|
32
|
+
shouldRun = true;
|
33
|
+
}
|
34
|
+
}
|
93
35
|
|
94
36
|
if (!shouldRun) {
|
95
37
|
log("Skipping build pipeline because this is a development build.\n- Invoke with `--production` to run the build pipeline.\n- For example \"vite build -- --production\".");
|
96
38
|
await new Promise((resolve, _) => setTimeout(resolve, 1000));
|
97
|
-
return
|
39
|
+
return;
|
98
40
|
}
|
99
41
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
validateCloudBuildConfiguration(userSettings);
|
105
|
-
|
106
|
-
const verboseOutput = userSettings?.buildPipeline?.verbose || false;
|
107
|
-
let taskHasCompleted = false;
|
108
|
-
|
42
|
+
/** @type {Promise<any>|null} */
|
43
|
+
let task = null;
|
44
|
+
let taskFinished = false;
|
45
|
+
let taskSucceeded = false;
|
109
46
|
return {
|
110
47
|
name: 'needle-buildpipeline',
|
111
48
|
enforce: "post",
|
112
|
-
apply:
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
if (env.command === "build") {
|
121
|
-
increaseMaxWaitTime(verboseOutput);
|
122
|
-
if (buildPipelineTask) {
|
123
|
-
return false;
|
124
|
-
}
|
125
|
-
return true;
|
126
|
-
}
|
127
|
-
return false;
|
49
|
+
apply: 'build',
|
50
|
+
buildEnd() {
|
51
|
+
// start the compression process once vite is done copying the files
|
52
|
+
task = invokeBuildPipeline(userSettings).then((res) => {
|
53
|
+
taskFinished = true;
|
54
|
+
taskSucceeded = res;
|
55
|
+
});
|
56
|
+
buildPipelineTask = task;
|
128
57
|
},
|
129
|
-
buildEnd(error) {
|
130
|
-
increaseMaxWaitTime(verboseOutput);
|
131
|
-
|
132
|
-
if (verboseOutput) {
|
133
|
-
log("Build end:", error ?? "No error");
|
134
|
-
}
|
135
|
-
if (error) {
|
136
|
-
// if there was an error during the build we should not run the build pipeline
|
137
|
-
}
|
138
|
-
else {
|
139
|
-
if (buildPipelineTask) {
|
140
|
-
log("Build pipeline already running...");
|
141
|
-
return;
|
142
|
-
}
|
143
|
-
let taskSucceeded = false;
|
144
|
-
// start the compression process once vite is done copying the files
|
145
|
-
buildPipelineTask = invokeBuildPipeline(userSettings)
|
146
|
-
.then((res) => {
|
147
|
-
taskSucceeded = res;
|
148
|
-
})
|
149
|
-
.finally(() => {
|
150
|
-
taskHasCompleted = true;
|
151
|
-
if (!taskSucceeded) {
|
152
|
-
throw new Error("[needle-buildpipeline] - Build pipeline failed. Check the logs for more information.");
|
153
|
-
}
|
154
|
-
else {
|
155
|
-
log("Finished successfully");
|
156
|
-
}
|
157
|
-
});
|
158
|
-
}
|
159
|
-
},
|
160
58
|
closeBundle() {
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
// // this is the last hook that is called, so we can wait for the task to finish here
|
168
|
-
return buildPipelineTask = buildPipelineTask?.then(() => {
|
169
|
-
// Copy the results to their final output directory.
|
170
|
-
if (buildPipelineTaskResults != null) {
|
171
|
-
log("Copying files from temporary output directory to final output directory...");
|
172
|
-
const ctx = { count: 0 }
|
173
|
-
copyFilesSync(buildPipelineTaskResults.tempDirectory, buildPipelineTaskResults.outputDirectory, true, ctx);
|
174
|
-
log(`Copied ${ctx.count} file(s)`);
|
175
|
-
}
|
176
|
-
else {
|
177
|
-
log("No files to copy - build pipeline did not run or did not finish successfully");
|
178
|
-
}
|
59
|
+
log("Waiting for postprocessing to finish...");
|
60
|
+
// this is the last hook that is called, so we can wait for the task to finish here
|
61
|
+
if (taskFinished) return;
|
62
|
+
// delay the final log slightly to give other plugins a chance to log their stuff
|
63
|
+
wait(100).then(() => {
|
64
|
+
if (!taskFinished) log("Waiting for postprocessing to finish...")
|
179
65
|
});
|
66
|
+
return task.then(_ => {
|
67
|
+
log("finished", taskSucceeded ? "successfully" : "with errors");
|
68
|
+
});
|
180
69
|
},
|
181
70
|
}
|
182
71
|
}
|
183
72
|
|
184
|
-
|
185
73
|
/**
|
186
74
|
* Previously we did always install the build pipeline and run an extra command to invoke the build pipeline.
|
187
75
|
* This is now done automatically by the needle build pipeline plugin - so we update all legacy projects to use the new method.
|
188
|
-
* @param {string} packageJsonPath
|
189
76
|
*/
|
190
77
|
async function fixPackageJson(packageJsonPath) {
|
191
78
|
if (!existsSync(packageJsonPath)) {
|
@@ -202,11 +89,9 @@
|
|
202
89
|
writeFileSync(packageJsonPath, fixed);
|
203
90
|
}
|
204
91
|
|
205
|
-
/** @param {any} args */
|
206
92
|
function log(...args) {
|
207
93
|
console.log("[needle-buildpipeline]", ...args);
|
208
94
|
}
|
209
|
-
/** @param {any} args */
|
210
95
|
function warn(...args) {
|
211
96
|
console.warn("WARN: [needle-buildpipeline]", ...args);
|
212
97
|
}
|
@@ -219,127 +104,63 @@
|
|
219
104
|
const installPath = "node_modules/@needle-tools/gltf-build-pipeline";
|
220
105
|
const fullInstallPath = process.cwd() + "/" + installPath;
|
221
106
|
const existsLocally = existsSync(fullInstallPath);
|
222
|
-
if (existsLocally) {
|
223
|
-
|
107
|
+
if (!existsLocally) {
|
108
|
+
// await execSync("npx --yes @needle-tools/n")
|
109
|
+
// throw new Error(`ERR: Build pipeline not found at \"${fullInstallPath}\". \nTo disable this plugin you can pass \"noBuildPipeline: true\" to the needle config in vite.config.js`);
|
110
|
+
warn("@needle-tools/gltf-build-pipeline not found in package - using latest version")
|
224
111
|
}
|
225
|
-
await
|
112
|
+
await wait(500);
|
226
113
|
const outputDirectory = getOutputDirectory() + "/assets";
|
227
|
-
|
228
|
-
const maxEndTime = startWaitTime + 120_000;
|
229
|
-
/** wait until the output directory exists
|
230
|
-
* @param {number} iteration
|
231
|
-
* @returns {Promise<boolean>}
|
232
|
-
*/
|
114
|
+
// wait until the output directory exists - this depends on speed
|
233
115
|
function waitForOutputDirectory(iteration) {
|
234
|
-
// we wait for the output directory
|
235
116
|
if (!existsSync(outputDirectory)) {
|
236
|
-
if (
|
117
|
+
if (iteration > 10) {
|
237
118
|
return Promise.resolve(false);
|
238
119
|
}
|
239
|
-
|
240
|
-
|
241
|
-
return Promise.resolve(false);
|
242
|
-
}
|
243
|
-
if (iteration <= 0) log(`Waiting for output directory to be created... (${outputDirectory})`);
|
244
|
-
return delay(1000).then(() => waitForOutputDirectory(iteration + 1));
|
120
|
+
if (iteration <= 0) log("Waiting for output directory to be created...");
|
121
|
+
return wait(1000).then(() => waitForOutputDirectory(iteration + 1));
|
245
122
|
}
|
246
123
|
return Promise.resolve(true);
|
247
124
|
}
|
248
125
|
if (!await waitForOutputDirectory(0)) {
|
249
|
-
warn(
|
250
|
-
return
|
126
|
+
warn("Directory not found at " + outputDirectory);
|
127
|
+
return;
|
251
128
|
}
|
252
|
-
const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf")
|
253
|
-
log(
|
129
|
+
const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf"));
|
130
|
+
log(files.length + " file(s) to process");
|
254
131
|
|
255
132
|
/** @type {null | ChildProcess} */
|
256
|
-
let
|
133
|
+
let sub = null;
|
257
134
|
|
258
|
-
|
259
|
-
|
260
|
-
// if a user has defined the build pipeline settings object but not passed in a token we should print out some information
|
261
|
-
// or perhaps log an error / prevent the build from running completely
|
262
|
-
if (opts.buildPipeline && !runInCloud && process.env.CI) {
|
263
|
-
warn(`No cloud access token found. Please set it via process.env.NEEDLE_CLOUD_TOKEN`);
|
264
|
-
return false;
|
265
|
-
}
|
266
|
-
|
267
|
-
|
268
|
-
// put the processed files first in a temporary directory. They will be moved to the output directory at the end of the buildstep
|
269
|
-
// this is so that processes like sveltekit-static-adapter can run first and does not override already compressed files
|
270
|
-
const tempOutputPath = process.cwd() + "/node_modules/.needle/build-pipeline/output";
|
271
|
-
if (existsSync(tempOutputPath)) {
|
272
|
-
log("Removing temporary output directory at " + tempOutputPath);
|
273
|
-
rmSync(tempOutputPath, { recursive: true, force: true });
|
274
|
-
}
|
275
|
-
mkdirSync(tempOutputPath, { recursive: true });
|
276
|
-
|
277
|
-
/** @param {number} code */
|
278
|
-
function onExit(code) {
|
279
|
-
if (code === 0)
|
280
|
-
buildPipelineTaskResults = {
|
281
|
-
tempDirectory: tempOutputPath,
|
282
|
-
outputDirectory: outputDirectory
|
283
|
-
}
|
284
|
-
}
|
285
|
-
|
286
|
-
// allow running the build pipeline in the cloud. It requires and access token to be set in the vite.config.js
|
287
|
-
// this can be set via e.g. process.env.NEEDLE_CLOUD_TOKEN
|
288
|
-
if (runInCloud) {
|
289
|
-
if (!cloudAccessToken || !(typeof cloudAccessToken === "string") || cloudAccessToken.length <= 0) {
|
290
|
-
throw new Error("No cloud access token configured. Please set it via process.env.NEEDLE_CLOUD_TOKEN or in the vite.config.js");
|
291
|
-
}
|
292
|
-
let cmd = `npx --yes @needle-tools/cloud-cli@latest optimize "${outputDirectory}" --accessToken ${cloudAccessToken}`;
|
293
|
-
const projectName = opts.buildPipeline?.projectName;
|
294
|
-
if (projectName) {
|
295
|
-
cmd += ` --name "${projectName}"`;
|
296
|
-
}
|
297
|
-
if (opts.buildPipeline?.verbose === true) {
|
298
|
-
cmd += " --verbose";
|
299
|
-
}
|
300
|
-
cmd += " --outdir \"" + tempOutputPath + "\"";
|
301
|
-
console.log("\n")
|
302
|
-
log("Running compression in cloud ⛅");
|
303
|
-
proc = exec(cmd);
|
304
|
-
}
|
305
|
-
else if (existsLocally) {
|
306
|
-
const cmd = `needle-gltf transform "${outputDirectory}" \"${tempOutputPath}\"`;
|
135
|
+
if (existsLocally) {
|
136
|
+
const cmd = `needle-gltf transform "${outputDirectory}"`;
|
307
137
|
log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
|
308
|
-
|
138
|
+
sub = exec(cmd, { cwd: installPath });
|
309
139
|
}
|
310
140
|
else {
|
311
|
-
const version = opts.
|
312
|
-
const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}"
|
313
|
-
log(
|
314
|
-
|
141
|
+
const version = opts.buildPipelineVersion || "latest";
|
142
|
+
const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}"`;
|
143
|
+
log("Running command \"" + cmd);
|
144
|
+
sub = exec(cmd);
|
315
145
|
}
|
316
|
-
|
317
|
-
function onLog(data) {
|
146
|
+
sub.stdout.on('data', data => {
|
318
147
|
if (data.length <= 0) return;
|
319
148
|
// ensure that it doesnt end with a newline
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
return;
|
325
|
-
}
|
326
|
-
else if (data.startsWith("WARN:")) {
|
327
|
-
console.warn(data);
|
328
|
-
return;
|
329
|
-
}
|
330
|
-
// Ignore empty lines
|
331
|
-
else if (data.trim().length <= 0) {
|
332
|
-
return;
|
333
|
-
}
|
334
|
-
}
|
335
|
-
log(data);
|
336
|
-
}
|
337
|
-
proc.stdout?.on('data', onLog);
|
338
|
-
proc.stderr?.on('data', onLog);
|
149
|
+
if (data.endsWith("\n")) data = data.slice(0, -1);
|
150
|
+
console.log(data);
|
151
|
+
});
|
152
|
+
sub.stderr.on('data', console.error);
|
339
153
|
return new Promise((resolve, reject) => {
|
340
|
-
|
341
|
-
onExit(code || 0);
|
154
|
+
sub.on('exit', (code) => {
|
342
155
|
resolve(code === 0);
|
343
156
|
});
|
344
157
|
});
|
345
158
|
}
|
159
|
+
|
160
|
+
function wait(ms) {
|
161
|
+
return new Promise((resolve, reject) => {
|
162
|
+
setTimeout(() => {
|
163
|
+
resolve();
|
164
|
+
}, ms);
|
165
|
+
});
|
166
|
+
}
|
@@ -16,11 +16,11 @@
|
|
16
16
|
totalsize: 0,
|
17
17
|
files: []
|
18
18
|
};
|
19
|
-
console.log(
|
19
|
+
console.log("[needle-buildinfo] - Collect files in " + buildDirectory);
|
20
20
|
recursivelyCollectFiles(buildDirectory, "", buildInfo);
|
21
21
|
const buildInfoPath = `${buildDirectory}/needle.buildinfo.json`;
|
22
22
|
const totalSizeInMB = buildInfo.totalsize / 1024 / 1024;
|
23
|
-
console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB).
|
23
|
+
console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Write info to \"${buildInfoPath}\"`);
|
24
24
|
fs.writeFileSync(buildInfoPath, JSON.stringify(buildInfo));
|
25
25
|
}
|
26
26
|
|
@@ -39,11 +39,10 @@
|
|
39
39
|
}
|
40
40
|
const newPath = path?.length <= 0 ? entry.name : `${path}/${entry.name}`;
|
41
41
|
const newDirectory = `${directory}/${entry.name}`;
|
42
|
-
console.log(
|
42
|
+
console.log("[needle-buildinfo] - Collect files in " + newPath);
|
43
43
|
recursivelyCollectFiles(newDirectory, newPath, info);
|
44
44
|
} else {
|
45
45
|
const relpath = `${path}/${entry.name}`;
|
46
|
-
// console.log("[needle-buildinfo] - New File: " + relpath);
|
47
46
|
const filehash = crypto.createHash('sha256');
|
48
47
|
const fullpath = `${directory}/${entry.name}`;
|
49
48
|
filehash.update(fs.readFileSync(fullpath));
|
@@ -1,38 +1,32 @@
|
|
1
1
|
import { createBuildInfoFile } from '../common/buildinfo.js';
|
2
|
-
import {
|
2
|
+
import { buildPipelineTask } from './build-pipeline.js';
|
3
3
|
import { getOutputDirectory } from './config.js';
|
4
4
|
|
5
|
-
let level = 0;
|
6
5
|
|
6
|
+
|
7
7
|
/** Create a buildinfo file in the build directory
|
8
8
|
* @param {import('../types').userSettings} userSettings
|
9
|
-
* @returns {import('vite').Plugin
|
9
|
+
* @returns {import('vite').Plugin}
|
10
10
|
*/
|
11
11
|
export const needleBuildInfo = (command, config, userSettings) => {
|
12
12
|
|
13
|
-
if (userSettings?.noBuildInfo) return
|
13
|
+
if (userSettings?.noBuildInfo) return;
|
14
14
|
|
15
15
|
return {
|
16
16
|
name: 'needle-buildinfo',
|
17
17
|
apply: "build",
|
18
18
|
enforce: "post",
|
19
|
-
buildStart: () => {
|
20
|
-
level++;
|
21
|
-
},
|
22
19
|
closeBundle: async () => {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
await delay(500);
|
34
|
-
const buildDirectory = getOutputDirectory();
|
35
|
-
createBuildInfoFile(buildDirectory);
|
20
|
+
return new Promise(async res => {
|
21
|
+
if (buildPipelineTask instanceof Promise) {
|
22
|
+
await buildPipelineTask.catch(() => { });
|
23
|
+
}
|
24
|
+
// wait for gzip
|
25
|
+
await delay(500);
|
26
|
+
const buildDirectory = getOutputDirectory();
|
27
|
+
createBuildInfoFile(buildDirectory);
|
28
|
+
res();
|
29
|
+
});
|
36
30
|
}
|
37
31
|
}
|
38
32
|
}
|
@@ -1,52 +1,41 @@
|
|
1
1
|
|
2
2
|
import { resolve, join, isAbsolute } from 'path'
|
3
|
-
import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir
|
3
|
+
import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
|
4
4
|
import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
|
5
|
-
import { copyFilesSync } from '../common/files.js';
|
6
5
|
|
7
|
-
const pluginName = "needle-copy-files";
|
8
6
|
|
9
|
-
|
10
7
|
/** copy files on build from assets to dist
|
11
8
|
* @param {import('../types').userSettings} userSettings
|
12
|
-
* @returns {import('vite').Plugin | null}
|
13
9
|
*/
|
14
10
|
export const needleCopyFiles = (command, config, userSettings) => {
|
15
11
|
|
16
12
|
if (config?.noCopy === true || userSettings?.noCopy === true) {
|
17
|
-
return
|
13
|
+
return;
|
18
14
|
}
|
19
15
|
|
20
16
|
return {
|
21
17
|
name: 'needle-copy-files',
|
22
|
-
// explicitly don't enforce post or pre because we want to run before the build-pipeline plugin
|
23
|
-
enforce: "pre",
|
24
18
|
buildStart() {
|
25
|
-
return run(
|
19
|
+
return run(false, config);
|
26
20
|
},
|
27
21
|
closeBundle() {
|
28
|
-
return run(
|
22
|
+
return run(true, config);
|
29
23
|
},
|
30
24
|
}
|
31
25
|
}
|
32
26
|
|
33
|
-
|
34
|
-
* @param {"start" | "end"} buildstep
|
35
|
-
* @param {import('../types').userSettings} config
|
36
|
-
*/
|
37
|
-
async function run(buildstep, config) {
|
38
|
-
console.log(`[${pluginName}] - Copy files at ${buildstep}`);
|
27
|
+
async function run(isBuild, config) {
|
39
28
|
const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
|
40
29
|
|
41
30
|
const baseDir = process.cwd();
|
42
|
-
const
|
31
|
+
const pluginName = "needle-copy-files";
|
43
32
|
|
44
33
|
let assetsDirName = "assets";
|
45
34
|
let outdirName = "dist";
|
46
35
|
|
47
36
|
const needleConfig = tryLoadProjectConfig();
|
48
37
|
if (needleConfig) {
|
49
|
-
assetsDirName = needleConfig.assetsDirectory
|
38
|
+
assetsDirName = needleConfig.assetsDirectory;
|
50
39
|
while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
|
51
40
|
|
52
41
|
if (needleConfig.buildDirectory)
|
@@ -59,67 +48,60 @@
|
|
59
48
|
if (existsSync(engineIncludeDir)) {
|
60
49
|
console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
|
61
50
|
const projectIncludeDir = resolve(baseDir, 'include');
|
62
|
-
|
51
|
+
copyRecursiveSync(engineIncludeDir, projectIncludeDir);
|
63
52
|
}
|
64
53
|
}
|
65
54
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
55
|
+
if (isBuild) {
|
56
|
+
const outDir = resolve(baseDir, outdirName);
|
57
|
+
if (!existsSync(outDir)) {
|
58
|
+
mkdirSync(outDir);
|
59
|
+
}
|
70
60
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
61
|
+
// copy a list of files or directories declared in build.copy = [] in the needle.config.json
|
62
|
+
/*
|
63
|
+
"build": {
|
64
|
+
"copy": ["myFolder", "myFile.txt"]
|
65
|
+
}
|
66
|
+
*/
|
67
|
+
if (needleConfig?.build?.copy) {
|
68
|
+
const arr = needleConfig.build.copy;
|
69
|
+
for (let i = 0; i < arr.length; i++) {
|
70
|
+
const entry = arr[i];
|
71
|
+
if (Array.isArray(entry)) {
|
72
|
+
console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
|
73
|
+
continue;
|
74
|
+
}
|
75
|
+
const src = resolve(baseDir, entry);
|
76
|
+
const dest = resolvePath(outDir, entry);
|
77
|
+
if (existsSync(src)) {
|
78
|
+
console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
|
79
|
+
copyRecursiveSync(src, dest);
|
80
|
+
}
|
84
81
|
}
|
85
|
-
const src = resolve(baseDir, entry);
|
86
|
-
const dest = resolvePath(outDir, entry);
|
87
|
-
if (existsSync(src) && dest) {
|
88
|
-
console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
|
89
|
-
copyFilesSync(src, dest, override);
|
90
|
-
}
|
91
82
|
}
|
92
|
-
}
|
93
83
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
if (override && existsSync(targetDir)) {
|
101
|
-
console.log(`[${pluginName}] - Clearing target directory \"${targetDir}\"`);
|
102
|
-
rmSync(targetDir, { recursive: true, force: true });
|
84
|
+
// copy assets dir
|
85
|
+
const assetsDir = resolve(baseDir, assetsDirName);
|
86
|
+
if (existsSync(assetsDir)) {
|
87
|
+
console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
|
88
|
+
const targetDir = resolve(outDir, 'assets');
|
89
|
+
copyRecursiveSync(assetsDir, targetDir);
|
103
90
|
}
|
104
|
-
console.log(`
|
105
|
-
|
91
|
+
else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
|
92
|
+
// copy include dir
|
93
|
+
const includeDir = resolve(baseDir, 'include');
|
94
|
+
if (existsSync(includeDir)) {
|
95
|
+
console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
|
96
|
+
const targetDir = resolve(outDir, 'include');
|
97
|
+
copyRecursiveSync(includeDir, targetDir);
|
98
|
+
}
|
106
99
|
}
|
107
|
-
else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
|
108
|
-
|
109
|
-
// copy include dir
|
110
|
-
const includeDir = resolve(baseDir, 'include');
|
111
|
-
if (existsSync(includeDir)) {
|
112
|
-
console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
|
113
|
-
const targetDir = resolve(outDir, 'include');
|
114
|
-
copyFilesSync(includeDir, targetDir, override);
|
115
|
-
}
|
116
100
|
}
|
117
101
|
|
118
102
|
/** resolves relative or absolute paths to a path inside the out directory
|
119
103
|
* for example D:/myFile.txt would resolve to outDir/myFile.txt
|
120
104
|
* wherereas "some/relative/path" would become outDir/some/relative/path
|
121
|
-
* @param {string} outDir
|
122
|
-
* @param {string} pathValue
|
123
105
|
*/
|
124
106
|
function resolvePath(outDir, pathValue) {
|
125
107
|
if (isAbsolute(pathValue)) {
|
@@ -128,12 +110,29 @@
|
|
128
110
|
var stats = exists && statSync(pathValue);
|
129
111
|
if (stats.isDirectory()) {
|
130
112
|
const dirName = pathValue.replaceAll('\\', '/').split('/').pop();
|
131
|
-
if (!dirName) return null;
|
132
113
|
return resolve(outDir, dirName);
|
133
114
|
}
|
134
115
|
const fileName = pathValue.replaceAll('\\', '/').split('/').pop();
|
135
|
-
if (!fileName) return null;
|
136
116
|
return resolve(outDir, fileName);
|
137
117
|
}
|
138
118
|
return resolve(outDir, pathValue);
|
139
|
-
}
|
119
|
+
}
|
120
|
+
|
121
|
+
function copyRecursiveSync(src, dest) {
|
122
|
+
if (dest === null) {
|
123
|
+
console.log(`[${pluginName}] - Copy ${src} to ${dest} - dest is null`)
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
var exists = existsSync(src);
|
127
|
+
var stats = exists && statSync(src);
|
128
|
+
var isDirectory = exists && stats.isDirectory();
|
129
|
+
if (isDirectory) {
|
130
|
+
if (!existsSync(dest))
|
131
|
+
mkdirSync(dest, { recursive: true });
|
132
|
+
readdirSync(src).forEach(function (childItemName) {
|
133
|
+
copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
|
134
|
+
});
|
135
|
+
} else {
|
136
|
+
copyFileSync(src, dest);
|
137
|
+
}
|
138
|
+
};
|
@@ -7,90 +7,28 @@
|
|
7
7
|
* @param {import('../types').userSettings} userSettings
|
8
8
|
*/
|
9
9
|
export const needleDependencies = (command, config, userSettings) => {
|
10
|
-
|
11
|
-
const handleChunks = true;
|
12
|
-
|
13
10
|
/**
|
14
11
|
* @type {import('vite').Plugin}
|
15
12
|
*/
|
16
13
|
return {
|
17
14
|
name: 'needle-dependencies',
|
18
15
|
enforce: 'pre',
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
config: (config) => {
|
17
|
+
if (config.optimizeDeps?.include?.includes("three-mesh-bvh")) {
|
18
|
+
console.log("[needle-dependencies] three-mesh-bvh is included in the optimizeDeps.include array. This may cause issues with the worker import.");
|
19
|
+
}
|
20
|
+
else {
|
21
|
+
if (!config.optimizeDeps) {
|
22
|
+
config.optimizeDeps = {};
|
23
|
+
}
|
24
|
+
if (!config.optimizeDeps.exclude) {
|
25
|
+
config.optimizeDeps.exclude = [];
|
26
|
+
}
|
27
|
+
console.log("[needle-dependencies] Adding three-mesh-bvh to the optimizeDeps.exclude array.");
|
28
|
+
// This needs to be excluded from optimization because otherwise the worker import fails
|
29
|
+
// three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker
|
30
|
+
config.optimizeDeps.exclude.push("three-mesh-bvh");
|
31
|
+
}
|
25
32
|
}
|
26
33
|
}
|
27
34
|
}
|
28
|
-
|
29
|
-
/**
|
30
|
-
* @param {import('vite').UserConfig} config
|
31
|
-
*/
|
32
|
-
function handleOptimizeDeps(config) {
|
33
|
-
if (config.optimizeDeps?.include?.includes("three-mesh-bvh")) {
|
34
|
-
console.log("[needle-dependencies] three-mesh-bvh is included in the optimizeDeps.include array. This may cause issues with the worker import.");
|
35
|
-
}
|
36
|
-
else {
|
37
|
-
if (!config.optimizeDeps) {
|
38
|
-
config.optimizeDeps = {};
|
39
|
-
}
|
40
|
-
if (!config.optimizeDeps.exclude) {
|
41
|
-
config.optimizeDeps.exclude = [];
|
42
|
-
}
|
43
|
-
console.log("[needle-dependencies] Adding three-mesh-bvh to the optimizeDeps.exclude array.");
|
44
|
-
// This needs to be excluded from optimization because otherwise the worker import fails
|
45
|
-
// three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker
|
46
|
-
config.optimizeDeps.exclude.push("three-mesh-bvh");
|
47
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
/**
|
51
|
-
* @param {import('vite').UserConfig} config
|
52
|
-
*/
|
53
|
-
function handleManualChunks(config) {
|
54
|
-
if (!config.build) {
|
55
|
-
config.build = {};
|
56
|
-
}
|
57
|
-
if (!config.build.rollupOptions) {
|
58
|
-
config.build.rollupOptions = {};
|
59
|
-
}
|
60
|
-
if (!config.build.rollupOptions.output) {
|
61
|
-
config.build.rollupOptions.output = {};
|
62
|
-
}
|
63
|
-
|
64
|
-
const rollupOutput = config.build.rollupOptions.output;
|
65
|
-
|
66
|
-
if (Array.isArray(rollupOutput)) {
|
67
|
-
// append the manualChunks function to the array
|
68
|
-
console.log("[needle-dependencies] registering manualChunks");
|
69
|
-
rollupOutput.push({
|
70
|
-
manualChunks: needleManualChunks
|
71
|
-
})
|
72
|
-
}
|
73
|
-
else {
|
74
|
-
if (rollupOutput.manualChunks) {
|
75
|
-
// if the user has already defined manualChunks, we don't want to overwrite it
|
76
|
-
console.log("[needle-dependencies] manualChunks already defined");
|
77
|
-
}
|
78
|
-
else {
|
79
|
-
console.log("[needle-dependencies] registering manualChunks");
|
80
|
-
rollupOutput.manualChunks = needleManualChunks;
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
function needleManualChunks(id) {
|
85
|
-
// Push the pmndrs postprocessing package into a separate postprocessing chunk
|
86
|
-
if (id.includes("node_modules/postprocessing/" || id.includes("node_modules/n8ao"))) {
|
87
|
-
return "postprocessing";
|
88
|
-
}
|
89
|
-
else if (id.includes("node_modules/three-mesh-ui")) {
|
90
|
-
return "three-mesh-ui";
|
91
|
-
}
|
92
|
-
else if (id.includes("@dimforge/rapier3d")) {
|
93
|
-
return "rapier3d";
|
94
|
-
}
|
95
|
-
}
|
96
|
-
}
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
2
|
-
import { join } from "path";
|
3
|
-
|
4
|
-
/**
|
5
|
-
* @param {string} src
|
6
|
-
* @param {string} dest
|
7
|
-
* @param {boolean} override
|
8
|
-
* @param {{count:number} | undefined} ctx
|
9
|
-
*/
|
10
|
-
export function copyFilesSync(src, dest, override = true, ctx = undefined) {
|
11
|
-
if (dest === null) {
|
12
|
-
console.log(`[needle-copy] - Copy ${src} to ${dest} - dest is null`)
|
13
|
-
return;
|
14
|
-
}
|
15
|
-
let exists = existsSync(src);
|
16
|
-
let stats = exists && statSync(src);
|
17
|
-
let isDirectory = exists && typeof stats != "boolean" && stats.isDirectory();
|
18
|
-
if (isDirectory) {
|
19
|
-
if (!existsSync(dest))
|
20
|
-
mkdirSync(dest, { recursive: true });
|
21
|
-
readdirSync(src).forEach(function (childItemName) {
|
22
|
-
// recurse
|
23
|
-
copyFilesSync(join(src, childItemName), join(dest, childItemName), override, ctx);
|
24
|
-
});
|
25
|
-
}
|
26
|
-
else if (override || !existsSync(dest)) {
|
27
|
-
if (ctx) {
|
28
|
-
ctx.count += 1;
|
29
|
-
}
|
30
|
-
copyFileSync(src, dest);
|
31
|
-
}
|
32
|
-
};
|
@@ -96,7 +96,7 @@
|
|
96
96
|
}
|
97
97
|
* ```
|
98
98
|
* @param {string} command
|
99
|
-
* @param {import('../types
|
99
|
+
* @param {import('../types').userSettings} userSettings
|
100
100
|
*/
|
101
101
|
export const needlePlugins = async (command, config, userSettings) => {
|
102
102
|
|
@@ -1,8 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @param {number} ms
|
5
|
-
*/
|
6
|
-
export function delay(ms) {
|
7
|
-
return new Promise(res => setTimeout(res, ms));
|
8
|
-
}
|
@@ -9,7 +9,8 @@
|
|
9
9
|
const changed = val !== this._fov;
|
10
10
|
this._fov = val;
|
11
11
|
if (changed && this.view !== undefined) this.updateProjectionMatrix();
|
12
|
-
}
|
12
|
+
},
|
13
|
+
configurable: true
|
13
14
|
});
|
14
15
|
|
15
16
|
Object.defineProperty(PerspectiveCamera.prototype, "near", {
|
@@ -20,7 +21,8 @@
|
|
20
21
|
const changed = val !== this._near;
|
21
22
|
this._near = val;
|
22
23
|
if (changed && this.view !== undefined) this.updateProjectionMatrix();
|
23
|
-
}
|
24
|
+
},
|
25
|
+
configurable: true
|
24
26
|
});
|
25
27
|
|
26
28
|
Object.defineProperty(PerspectiveCamera.prototype, "far", {
|
@@ -31,5 +33,6 @@
|
|
31
33
|
const changed = val !== this._far;
|
32
34
|
this._far = val;
|
33
35
|
if (changed && this.view !== undefined) this.updateProjectionMatrix();
|
34
|
-
}
|
36
|
+
},
|
37
|
+
configurable: true
|
35
38
|
});
|
@@ -140,6 +140,7 @@
|
|
140
140
|
width: max(600px, 100%);
|
141
141
|
height: max(300px, 100%);
|
142
142
|
touch-action: none;
|
143
|
+
|
143
144
|
-webkit-tap-highlight-color: transparent;
|
144
145
|
}
|
145
146
|
|
@@ -156,10 +157,16 @@
|
|
156
157
|
:host canvas {
|
157
158
|
position: absolute;
|
158
159
|
user-select: none;
|
160
|
+
-webkit-user-select: none;
|
161
|
+
|
159
162
|
/** allow touch panning but no pinch zoom **/
|
160
163
|
/** but this doesnt work yet:
|
161
164
|
* touch-action: pan-x, pan-y;
|
162
165
|
**/
|
166
|
+
|
167
|
+
-webkit-touch-callout: none;
|
168
|
+
-webkit-user-drag: none;
|
169
|
+
-webkit-user-modify: none;
|
163
170
|
}
|
164
171
|
:host .content {
|
165
172
|
position: absolute;
|
@@ -299,12 +299,14 @@
|
|
299
299
|
|
300
300
|
private intersect(raycaster: Raycaster, objects: Object3D[], results: Intersection[], options: IRaycastOptions) {
|
301
301
|
for (const obj of objects) {
|
302
|
+
|
302
303
|
// handle case where null or undefined object is in the scene
|
303
304
|
if (!obj) continue;
|
304
305
|
// dont raycast invisible objects
|
305
306
|
if (obj.visible === false) continue;
|
306
307
|
|
307
308
|
if (Gizmos.isGizmo(obj)) continue;
|
309
|
+
|
308
310
|
// dont raycast object if it's a line and the line threshold is < 0
|
309
311
|
if (options.lineThreshold !== undefined && options.lineThreshold < 0) {
|
310
312
|
if (obj instanceof Line) {
|
@@ -312,20 +314,13 @@
|
|
312
314
|
}
|
313
315
|
}
|
314
316
|
|
315
|
-
|
317
|
+
let shouldIntersectObject = true;
|
318
|
+
const mesh = obj as Mesh | SkinnedMesh;
|
316
319
|
const geo = mesh.geometry;
|
317
320
|
|
318
|
-
|
319
|
-
if (
|
320
|
-
|
321
|
-
}
|
322
|
-
// check if the geometry is valid
|
323
|
-
else if (shouldIntersectObject && !canRaycastGeometry(geo)) {
|
324
|
-
shouldIntersectObject = false;
|
325
|
-
}
|
326
|
-
|
327
|
-
if (shouldIntersectObject && options.testObject) {
|
328
|
-
const testResult = options.testObject(obj);
|
321
|
+
// We need to run this first because of "EventSystem.testObject" implementation
|
322
|
+
if (options.testObject) {
|
323
|
+
const testResult = options.testObject?.(obj);
|
329
324
|
if (testResult === false) {
|
330
325
|
continue;
|
331
326
|
}
|
@@ -335,6 +330,17 @@
|
|
335
330
|
}
|
336
331
|
|
337
332
|
if (shouldIntersectObject) {
|
333
|
+
if (!geo) {
|
334
|
+
shouldIntersectObject = false;
|
335
|
+
}
|
336
|
+
// check if the geometry is valid
|
337
|
+
else if (!canRaycastGeometry(geo)) {
|
338
|
+
shouldIntersectObject = false;
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
|
343
|
+
if (shouldIntersectObject) {
|
338
344
|
const raycastMesh = getRaycastMesh(obj);
|
339
345
|
if (raycastMesh) mesh.geometry = raycastMesh;
|
340
346
|
const lastResultsCount = results.length;
|
@@ -380,15 +386,6 @@
|
|
380
386
|
const mesh = obj as Mesh;
|
381
387
|
const geo = mesh.geometry;
|
382
388
|
|
383
|
-
// check if geometry exists
|
384
|
-
if (!geo) {
|
385
|
-
shouldIntersectObject = false;
|
386
|
-
}
|
387
|
-
// check if the geometry is valid
|
388
|
-
else if (!canRaycastGeometry(geo)) {
|
389
|
-
shouldIntersectObject = false;
|
390
|
-
}
|
391
|
-
|
392
389
|
if (shouldIntersectObject && shouldRaycast) {
|
393
390
|
const testResult = shouldRaycast(obj);
|
394
391
|
if (testResult === false) {
|
@@ -399,7 +396,16 @@
|
|
399
396
|
}
|
400
397
|
}
|
401
398
|
|
399
|
+
// check if geometry exists
|
400
|
+
if (!geo) {
|
401
|
+
shouldIntersectObject = false;
|
402
|
+
}
|
403
|
+
// check if the geometry is valid
|
404
|
+
else if (!canRaycastGeometry(geo)) {
|
405
|
+
shouldIntersectObject = false;
|
406
|
+
}
|
402
407
|
|
408
|
+
|
403
409
|
if (shouldIntersectObject) {
|
404
410
|
if (useBvh) {
|
405
411
|
const sphere = this.sphere;
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { Camera, Color, ColorRepresentation, PerspectiveCamera } from "three";
|
2
2
|
|
3
3
|
import { Renderer } from "../engine-components/Renderer.js";
|
4
|
-
import { RGBAColor } from "./js-extensions/index.js";
|
5
4
|
import { getComponentsInChildren } from "./engine_components.js";
|
6
5
|
import { ContextRegistry } from "./engine_context_registry.js";
|
7
6
|
import { Context } from "./engine_setup.js";
|
8
7
|
import { ICamera } from "./engine_types.js";
|
8
|
+
import { RGBAColor } from "./js-extensions/index.js";
|
9
9
|
|
10
10
|
declare type ScreenshotImageMimeType = "image/webp" | "image/png";
|
11
11
|
|
@@ -235,6 +235,9 @@
|
|
235
235
|
* For example if an event component has only an onPointerClick method we don't need to raycast during movement events
|
236
236
|
* */
|
237
237
|
private shouldRaycastObject = (obj: Object3D): RaycastTestObjectReturnType => {
|
238
|
+
// TODO: this implementation below should be removed and we should regularly raycast objects in the scene unless marked as "do not raycast"
|
239
|
+
// with the introduction of the mesh-bvh based raycasting the performance impact should be greatly reduced. But this needs further testing
|
240
|
+
|
238
241
|
// check if this object is actually a UI shadow hierarchy object
|
239
242
|
let uiOwner: Object3D | null = null;
|
240
243
|
const isUI = isUIObject(obj);
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import { LCP } from "./lcp.js";
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @param args - The arguments to initialize the performance analytics with.
|
5
|
-
*/
|
6
|
-
export module NeedleEnginePerformanceAnalytics {
|
7
|
-
export function init(...args: Array<"lcp">) {
|
8
|
-
if (args.includes("lcp"))
|
9
|
-
LCP.observe();
|
10
|
-
}
|
11
|
-
}
|
@@ -1,35 +0,0 @@
|
|
1
|
-
|
2
|
-
// https://web.dev/articles/lcp
|
3
|
-
export module LCP {
|
4
|
-
export function observe() {
|
5
|
-
const observer = new PerformanceObserver((list) => {
|
6
|
-
let perfEntries = list.getEntries();
|
7
|
-
let lastEntry = perfEntries[perfEntries.length - 1] as PerformanceEntry & { url?: string };
|
8
|
-
// Process the latest candidate for largest contentful paint
|
9
|
-
console.log('LCP candidate:', lastEntry);
|
10
|
-
|
11
|
-
const el = createElementFromUrl(lastEntry.url);
|
12
|
-
if (el) document.body.appendChild(el);
|
13
|
-
});
|
14
|
-
observer.observe({ entryTypes: ['largest-contentful-paint'] });
|
15
|
-
}
|
16
|
-
|
17
|
-
|
18
|
-
function createElementFromUrl(str: string | null | undefined) {
|
19
|
-
if (!str) return null;
|
20
|
-
|
21
|
-
if (!str.startsWith('data:image/')) {
|
22
|
-
return null;
|
23
|
-
}
|
24
|
-
else if (!str.includes("base64")) {
|
25
|
-
return null;
|
26
|
-
}
|
27
|
-
const img = document.createElement('img');
|
28
|
-
img.src = str;
|
29
|
-
img.onerror = err => {
|
30
|
-
console.error(err);
|
31
|
-
img.remove();
|
32
|
-
};
|
33
|
-
return img;
|
34
|
-
}
|
35
|
-
}
|
@@ -74,22 +74,8 @@
|
|
74
74
|
|
75
75
|
|
76
76
|
function insertTemporaryContentWhileEngineHasntLoaded(needleEngineElement: HTMLElement) {
|
77
|
-
|
77
|
+
if (needleEngineHasLoaded()) return;
|
78
78
|
|
79
|
-
const asapdiv = document.createElement("div");
|
80
|
-
asapdiv.style.cssText = `
|
81
|
-
position: absolute;
|
82
|
-
top: 0;
|
83
|
-
left: 0;
|
84
|
-
width: 100%;
|
85
|
-
height: 100%;
|
86
|
-
z-index: -1;
|
87
|
-
display: flex;
|
88
|
-
justify-content: center;
|
89
|
-
align-items: center;
|
90
|
-
pointer-events: none;
|
91
|
-
`
|
92
|
-
|
93
79
|
const img = document.createElement("img");
|
94
80
|
|
95
81
|
// if a custom logo is assigned we should use this here
|
@@ -99,22 +85,24 @@
|
|
99
85
|
img.src = customLogoUrl;
|
100
86
|
else
|
101
87
|
img.src = needleLogoOnlySVG;
|
88
|
+
img.style.position = "absolute";
|
89
|
+
img.style.top = "50%";
|
90
|
+
img.style.left = "50%";
|
91
|
+
img.style.transform = "translate(-50%, -50%)";
|
102
92
|
img.style.zIndex = "-10";
|
103
|
-
img.style.
|
93
|
+
img.style.pointerEvents = "none";
|
94
|
+
img.style.width = "33px";
|
104
95
|
img.style.height = "auto";
|
105
|
-
img.style.
|
96
|
+
img.style.filter = "grayscale(100%)";
|
97
|
+
img.style.opacity = "0";
|
106
98
|
img.style.textShadow = "0 0 10px 0px rgba(0,0,0,.5)";
|
99
|
+
needleEngineElement.appendChild(img);
|
107
100
|
|
108
|
-
asapdiv.appendChild(img);
|
109
|
-
needleEngineElement.appendChild(asapdiv);
|
110
|
-
|
111
101
|
// animation to pulsate
|
112
|
-
// don't animate to opacity 0 because for LCP we need to keep the element in the DOM and visible to the user
|
113
|
-
// see https://web.dev/articles/lcp#what-elements-are-considered
|
114
102
|
img.animate([
|
115
|
-
{ opacity:
|
116
|
-
{ opacity: .
|
117
|
-
{ opacity:
|
103
|
+
{ opacity: 0 },
|
104
|
+
{ opacity: .5 },
|
105
|
+
{ opacity: 0 }
|
118
106
|
], {
|
119
107
|
duration: 3000,
|
120
108
|
iterations: Infinity
|
@@ -595,7 +595,7 @@
|
|
595
595
|
<slot name="end"></slot>
|
596
596
|
</div>
|
597
597
|
</div>
|
598
|
-
<div class="logo">
|
598
|
+
<div style="user-select:none" class="logo">
|
599
599
|
<span class="madewith notranslate">powered by</span>
|
600
600
|
</div>
|
601
601
|
</div>
|
@@ -22,7 +22,4 @@
|
|
22
22
|
assetsDirectory?: string;
|
23
23
|
scriptsDirectory?: string;
|
24
24
|
codegenDirectory?: string;
|
25
|
-
build?: {
|
26
|
-
copy?: Array<string>;
|
27
|
-
}
|
28
25
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/* eslint-disable */
|
2
2
|
import { TypeStore } from "./../engine_typestore.js"
|
3
|
-
|
3
|
+
|
4
4
|
// Import types
|
5
5
|
import { __Ignore } from "../../engine-components/codegen/components.js";
|
6
6
|
import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
|
@@ -220,7 +220,7 @@
|
|
220
220
|
import { XRFlag } from "../../engine-components/webxr/XRFlag.js";
|
221
221
|
import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
|
222
222
|
import { XRState } from "../../engine-components/webxr/XRFlag.js";
|
223
|
-
|
223
|
+
|
224
224
|
// Register types
|
225
225
|
TypeStore.add("__Ignore", __Ignore);
|
226
226
|
TypeStore.add("ActionBuilder", ActionBuilder);
|
@@ -5,9 +5,6 @@
|
|
5
5
|
webpack: object | undefined
|
6
6
|
}
|
7
7
|
|
8
|
-
/**
|
9
|
-
* Settings for the Needle plugin
|
10
|
-
*/
|
11
8
|
export type userSettings = {
|
12
9
|
|
13
10
|
/** disable needle asap plugin */
|
@@ -23,8 +20,6 @@
|
|
23
20
|
|
24
21
|
/** disable automatic copying of files to include and output directory (dist) */
|
25
22
|
noCopy?: boolean;
|
26
|
-
/** When enabled the needle-engine include directory will be copied */
|
27
|
-
copyIncludesFromEngine?: boolean;
|
28
23
|
/** set to false to tree-shake rapier physics engine to the reduce bundle size */
|
29
24
|
useRapier?: boolean;
|
30
25
|
noDependencyWatcher?: boolean;
|
@@ -49,25 +44,12 @@
|
|
49
44
|
|
50
45
|
/** Set to true to disable the needle build pipeline (running compression and optimization as a postprocessing step on the exported glTF files) */
|
51
46
|
noBuildPipeline?: boolean;
|
47
|
+
/** Set to a specific version of the Needle Build Pipeline.
|
48
|
+
* @default "latest"
|
49
|
+
* @example "2.2.0-alpha"
|
50
|
+
*/
|
51
|
+
buildPipelineVersion?: string;
|
52
52
|
|
53
|
-
/**
|
54
|
-
* Use to configure optimized builds
|
55
|
-
*/
|
56
|
-
buildPipeline?: {
|
57
|
-
/** Set to false to prevent the build pipeline from running */
|
58
|
-
enabled?: boolean,
|
59
|
-
/** Set a project name (cloud only) */
|
60
|
-
projectName?: string,
|
61
|
-
/** Enable for verbose log output */
|
62
|
-
verbose?: boolean,
|
63
|
-
|
64
|
-
/** Set to a specific version of the Needle Build Pipeline.
|
65
|
-
* @default "latest"
|
66
|
-
* @example "2.2.0-alpha"
|
67
|
-
*/
|
68
|
-
version?: string;
|
69
|
-
}
|
70
|
-
|
71
53
|
/** required for @serializable https://github.com/vitejs/vite/issues/13736 */
|
72
54
|
vite44Hack?: boolean;
|
73
55
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import "./engine_hot_reload.js";
|
2
|
+
import "./tests/test_utils.js";
|
3
|
+
|
4
|
+
import { RGBAColor } from "../engine-components/js-extensions/RGBAColor.js";
|
5
|
+
import * as engine_scenetools from "./engine_scenetools.js";
|
6
|
+
import * as engine_setup from "./engine_setup.js";
|
7
|
+
|
8
|
+
const engine : any = {
|
9
|
+
...engine_setup,
|
10
|
+
...engine_scenetools,
|
11
|
+
RGBAColor,
|
12
|
+
};
|
13
|
+
|
14
|
+
export { engine as engine }
|