@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in
175 lines (153 loc) • 7.82 kB
JavaScript
import { createWriteStream, existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import path from 'path';
const projectDir = process.cwd() + "/";
/** these are alias callbacks as in the vite.alias dictionary
* the first argument is the already resoled absolute path (it is only invoked if the path was found in node_modules)
* the 2,3,4 args are the same as in vite.alias (packageName, index, path);
*/
/**
* @type {Record<string, null | ((res: string, packageName: string, index: number, path: string) => string | null | void)>}
*/
const packages_to_resolve = {
// Handle all previous imports where users did import using @needle-engine/src
'@needle-tools/engine/src': (res, packageName, index, path) => {
// resolve old engine/src imports UNLESS it's the asap plugin (the asap plugin currently only exists in the src folder)
if (!path.startsWith("@needle-tools/engine/src/asap")) {
return res + "/../lib";
}
},
/*
Removed the previously present @needle-tools/engine entry
because this is automatically done by vite according to whatever we define in our package.json exports
This did previously prevent us from declaring proper exports in package.json
*/
'@needle-tools/engine': (res, packageName, index, path) => {
// Check if the import is something like @needle-tools/engine/engine/engine_utils
// in which case we want to resolve into the lib directory
if (path.startsWith("@needle-tools/engine/engine")) {
return res + "/lib";
}
},
/*
Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
'three': null,
*/
'peerjs': null,
'websocket-ts': null,
'md5': null,
}
/**
* @param {import('../types').userSettings} userSettings
*/
export const needleViteAlias = (command, config, userSettings) => {
if (config?.noAlias === true || userSettings?.noAlias === true)
return;
const debug = userSettings.debugAlias;
let outputDebugFile = null;
function log(...msg) {
console.log(...msg);
if (debug && outputDebugFile) outputDebugFile.write(msg.join(" ") + "\n");
}
if (debug) {
const outputFilePath = path.resolve(projectDir, 'node_modules/.vite/needle.alias.log');
const outputDirectory = path.dirname(outputFilePath);
if (!existsSync(outputDirectory)) mkdirSync(outputDirectory, { recursive: true });
outputDebugFile = createWriteStream(outputFilePath, { flags: "a" });
const timestamp = new Date().toISOString();
outputDebugFile.write("\n\n\n--------------------------\n");
outputDebugFile.write(`[needle-alias] Logging to: ${outputFilePath} (${timestamp})\n`);
log("[needle-alias] Logging to: ", outputFilePath);
}
const aliasPlugin = {
name: "needle-alias",
config(config) {
if (debug) console.log('[needle-alias] ProjectDirectory: ' + projectDir);
if (!config.resolve) config.resolve = {};
if (!config.resolve.alias) config.resolve.alias = {};
const aliasDict = config.resolve.alias;
addThreeJSResolvers(aliasDict);
for (const name in packages_to_resolve) {
if (!aliasDict[name]) {
addPathResolver(name, aliasDict, packages_to_resolve[name]);
}
}
if (debug) {
const testResults = [];
for (const name in aliasDict) {
const entry = aliasDict[name];
let res = entry;
if (typeof entry === "function") res = entry(name, 0, name);
testResults.push({ name, entry: res });
}
console.log('[needle-alias] Aliases: ', testResults);
}
},
}
let lastImporter = "";
/** This plugin logs all imports. This helps to find cases where incorrect folders are found/resolved. */
/** @type {import("vite").Plugin} */
const debuggingPlugin = {
name: "needle:alias-debug",
// needs to run before regular resolver
enforce: 'pre',
resolveId(id, importer, options) {
if (importer) {
// simplify paths for better readability
if (importer.includes("js/package~")) importer = "package~" + importer.split("js/package~")[1];
if (importer.includes("node_modules/@needle-tools")) importer = "node_modules/@needle-tools" + importer.split("node_modules/@needle-tools")[1];
if (importer.includes("node_modules/.vite")) importer = ".vite" + importer.split("node_modules/.vite")[1];
}
// could filter here, e.g. for things related to three
// if (id.includes("three")) return;
// verbose logging for all imports
if (lastImporter !== importer) {
lastImporter = importer;
log(`[needle-alias] Resolving: ${importer} (file${options?.ssr ? ", SSR" : ""})`);
}
log(`[needle-alias] → ${id}`);
return;
},
}
if (debug) return [debuggingPlugin, aliasPlugin];
return [aliasPlugin];
function addThreeJSResolvers(aliasDict) {
// We are currently overriding "three" resolution to ensure that all dependencies resolve to the same three.js version.
// This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
// stubborn with their peer dependencies or just slow (slower as we) with updating.
// NOT adding this allows node.js to correctly resolve `exports` specified in three.js package.json;
// since we're overriding resolution here we need to manually resolve the subset of exports that we use.
aliasDict['three/addons'] = (res, packageName, index, path) => {
return "three/examples/jsm";
};
aliasDict['three/nodes'] = (res, packageName, index, path) => {
return "three/examples/jsm/nodes/Nodes.js";
};
aliasDict['three'] = (res, packageName, index, _path) => {
return path.resolve(projectDir, 'node_modules', 'three');
};
}
function addPathResolver(name, aliasDict, cb) {
// If a package at the node_modules path exist we resolve the request there
// introduced in 89a50718c38940abb99ee16c5e029065e41d7d65
const fullpath = path.resolve(projectDir, 'node_modules', name);
if (typeof cb !== "function") cb = null;
const isDevEnvironment = process.env.NODE_ENV === "development";
if (existsSync(fullpath)) {
aliasDict[name] = (packageName, index, path) => {
if (cb !== null && !isDevEnvironment) {
const overrideResult = cb(fullpath, packageName, index, path);
if (overrideResult !== undefined)
if (existsSync(overrideResult)) {
console.warn(`[needle-alias] \"${path}\" was requested and resolved to \"${overrideResult}\"`);
return overrideResult;
}
}
if (path != packageName) {
// TODO: we might want to check if the package.json exports contains the path to see if it's valid
console.warn(`[needle-alias] \"${path}\" was requested and resolved to \"${fullpath}\"`);
}
return fullpath;
}
}
}
};