UNPKG

@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.

292 lines (261 loc) 10.6 kB
import path from 'path'; import { existsSync } from 'fs'; import { tryGetNeedleEngineVersion, tryGetPackageVersion } from '../common/version.js'; import { needleLog } from './logging.js'; /** * @type {string[]} */ export const preloadScriptPaths = []; /** * Returns true when \`@needle-tools/engine\` is installed as a local package * (i.e. it has its own nested `node_modules`). Vite's optimiser must skip such * packages, otherwise it tries to pre-bundle source that was never meant to be * pre-bundled and fails at dev-server start. * * @param {string} [root] – project root; defaults to `process.cwd()`. * @returns {boolean} */ export function isLocalNeedleEngineInstalled(root = process.cwd()) { const lockPath = path.resolve(root, 'node_modules', '@needle-tools/engine', 'node_modules', '.package-lock.json'); return existsSync(lockPath); } /** * @param {"build" | "serve"} command * @param {import('../types/needleConfig').needleMeta | null | undefined} config * @param {import('../types').userSettings} userSettings * @returns {import('vite').Plugin[]} */ export function needleDependencies(command, config, userSettings) { /** * @type {import('vite').Plugin} */ return [ { name: 'needle:dependencies', enforce: 'pre', /** * @param {import('vite').UserConfig} config */ config: (config, env) => { handleOptimizeDeps(config); handleManualChunks(config); }, }, ] } const excludeDependencies = [ "three-mesh-bvh", "@needle-tools/gltf-progressive" ] /** * @param {import('vite').UserConfig} config */ function handleOptimizeDeps(config) { excludeDependencies.forEach(dep => { if (config.optimizeDeps?.include?.includes(dep)) { needleLog("needle-dependencies", `${dep} is included in the optimizeDeps.include array. This may cause issues with the worker import.`, "warn", { dimBody: false }); } else { if (!config.optimizeDeps) { config.optimizeDeps = {}; } if (!config.optimizeDeps.exclude) { config.optimizeDeps.exclude = []; } // This needs to be excluded from optimization because otherwise the worker import fails // three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker // same for gltf-progressive needleLog("needle-dependencies", `Adding ${dep} to the optimizeDeps.exclude array to support workers.`); config.optimizeDeps.exclude.push(dep); if (!config.server) config.server = {}; if (!config.server.fs) config.server.fs = {}; if (config.server.fs.strict === undefined) { // we need to disable strictness to allow importing the worker from three-mesh-bvh node_modules in our GenerateMeshBVHWorker.js file config.server.fs.strict = false; } } }); // is needle engine a local package? exclude it from vite's optimizeDeps if (isLocalNeedleEngineInstalled()) { config.optimizeDeps ??= {}; config.optimizeDeps.exclude ??= []; if (!config.optimizeDeps.include?.includes('@needle-tools/engine') && !config.optimizeDeps.exclude.includes('@needle-tools/engine')) { config.optimizeDeps.exclude.push('@needle-tools/engine'); needleLog("needle-dependencies", 'Detected local @needle-tools/engine package → will exclude it from optimization'); } } } /** * @param {import('vite').UserConfig} config */ function handleManualChunks(config) { if (!config.build) { config.build = {}; } if (!config.build.rollupOptions) { config.build.rollupOptions = {}; } if (!config.build.rollupOptions.output) { config.build.rollupOptions.output = {}; } const rollupOutput = config.build.rollupOptions.output; if (Array.isArray(rollupOutput)) { // append the manualChunks function to the array if (process.env.DEBUG) needleLog("needle-dependencies", "registering manualChunks"); rollupOutput.push({ manualChunks: needleManualChunks }) } else { // If preserveModules is true we can not modify the chunkFileNames let allowManualChunks = true; if ("manualChunks" in rollupOutput) { allowManualChunks = false; // if the user has already defined manualChunks (even when set to undefined), we don't want to overwrite it needleLog("needle-dependencies", "manualChunks already found in vite config - will not overwrite it"); } else if (rollupOutput.preserveModules === true) { allowManualChunks = false; needleLog("needle-dependencies", "manualChunks can not be registered because preserveModules is true"); } if (rollupOutput.inlineDynamicImports === true) { allowManualChunks = false; needleLog("needle-dependencies", "manualChunks can not be registered because inlineDynamicImports is true"); } if (allowManualChunks) { if (process.env.DEBUG) needleLog("needle-dependencies", "registering manualChunks"); rollupOutput.manualChunks = needleManualChunks; } if (rollupOutput.chunkFileNames) { needleLog("needle-dependencies", "chunkFileNames already defined"); } else { rollupOutput.chunkFileNames = handleChunkFileNames; } if (rollupOutput.assetFileNames) { needleLog("needle-dependencies", "assetFileNames already defined"); } else { rollupOutput.assetFileNames = assetFileNames; } } // TODO: this was a test if it allows us to remove the sync import of postprocessing due to n8ao's import // config.build.rollupOptions.external = (source, importer, isResolved) => { // if (importer?.includes("node_modules/n8ao/") || importer?.includes("node_modules/postprocessing/")) { // console.log("EXTERNAL", importer); // return true; // } // } /** https://rollupjs.org/configuration-options/#output-assetfilenames * @param {import("vite").Rollup.PreRenderedAsset} chunkInfo */ function assetFileNames(chunkInfo) { // "assets/..." is the default // this happens if e.g. a glTF or GLB file is link preloaded if (chunkInfo.name?.toLowerCase().includes(".glb") || chunkInfo.name?.toLowerCase().includes(".gltf")) { return `assets/[name][extname]`; } return `assets/[name].[hash][extname]`; } /** @param {import("vite").Rollup.PreRenderedChunk} chunk */ function handleChunkFileNames(chunk) { if (chunk.name === 'needle-engine') { try { const version = tryGetNeedleEngineVersion(); if (version) { const name = `assets/needle-engine@${version}.js`; preloadScriptPaths.push(`./${name}`); return name; } } catch (e) { needleLog("needle-dependencies", "Error reading version " + e, "warn"); } } else if (chunk.name === 'three' || chunk.name === 'three-examples') { // 'three' may be aliased to @needle-tools/three — check both package locations const version = tryGetPackageVersion("three") ?? tryGetPackageVersion("@needle-tools/three"); const name = version ? `assets/${chunk.name}@${version}.js` : `assets/${chunk.name}.js`; // Only eagerly preload three (the core math/scene-graph lib). // three-examples (OrbitControls, loaders, etc.) is loaded on-demand — don't preload it. if (chunk.name === 'three') { preloadScriptPaths.push(`./${name}`); } return name; } // else if(chunk.name === 'index') { // console.log(chunk); // debugger // // this is the main chunk // // we don't want to add a hash here to be able to easily import the main script // return `index.js`; // } return `assets/[name].[hash].js`; } /** * @param {string} id * @param {import('vite').Rollup.ManualChunkMeta | null} meta */ function needleManualChunks(id, meta) { // console.log(id); if (id.includes("three/examples")) { return "three-examples"; } if (id.includes('/three/')) { return "three"; } if (id.includes("node_modules/n8ao/")) { detectSyncImports(id, meta); return "postprocessing.ao"; } if (id.includes("node_modules/postprocessing/")) { // postprocessing bundle is preloaded https://github.com/vitejs/vite/pull/9938 detectSyncImports(id, meta); return "postprocessing"; } if (id.includes("@dimforge/rapier3d")) { detectSyncImports(id, meta); return "rapier3d"; } if (id.includes("@needle-tools/materialx")) { detectSyncImports(id, meta); return "materialx"; } if (id.includes("node_modules/three-mesh-ui")) { return "three-mesh-ui"; } if (id.includes("node_modules/three.quarks")) { return "three-quarks"; } // we want to bundle gltf-progressive separately because it also initiates Draco and KTX worker preloading if (id.includes("/gltf-progressive/")) { return "gltf-progressive"; } if (id.includes("node_modules/needle-engine") // DEV // || id.includes("/package~/src/") ) { return "needle-engine"; } } } /** * @param {string} id * @param {import('vite').Rollup.ManualChunkMeta | null} meta */ function detectSyncImports(id, meta) { if (meta) { if (meta.getModuleInfo) { const info = meta.getModuleInfo(id); if (info && info.importers.length > 0) { const isNeedleDev = id.includes("/package~/src/"); if (isNeedleDev) { console.warn(`WARN: SYNC IMPORTER DETECTED of ${id}`); console.warn(info.importers) } } } } }