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.

173 lines (147 loc) • 7.12 kB
import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; import { tryGetNeedleEngineVersion } from '../common/version.js'; import { tryGetGenerator } from '../common/generator.js'; import { getConfig, getMeta } from '../common/config.cjs'; import { alias } from './alias.cjs'; import { createBuildInfoFile } from '../common/buildinfo.js'; import { getPublicIdentifier } from '../common/license.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** Integrates Needle Engine into a Next.js project. * Pass a nextConfig object in to add the needle specific settings. * Optionally omit nextConfig and it will be created for you. * @param {import('next').NextConfig} [nextConfig] * @param {import('../types').userSettings} [userSettings] * @returns {Promise<import('next').NextConfig>} */ export async function needleNext(nextConfig, userSettings) { console.log("Apply 🌵 needle next config"); if (!nextConfig) { nextConfig = { reactStrictMode: true, }; } const needleConfig = getConfig(); // add transpile packages if (!nextConfig.transpilePackages) { nextConfig.transpilePackages = []; } // ESM packages must be transpiled so webpack can resolve their imports nextConfig.transpilePackages.push( "three", "peerjs", "three-mesh-ui", "three-mesh-bvh" ); if (nextConfig.output === undefined) { console.log("Set output to 'export' (see 'https://nextjs.org/docs/pages/building-your-application/deploying/static-exports#configuration' for more information)"); nextConfig.output = "export"; // we *also* need to turn OFF image optimization for static HTML files to be generated // see https://github.com/vercel/next.js/issues/40240 if (nextConfig.images === undefined) { nextConfig.images = { unoptimized: true } } } if (nextConfig.distDir == undefined) { console.log("Export to 'dist'"); if (needleConfig?.buildDirectory) { console.log(`Using build directory from needle config: ${needleConfig.buildDirectory}`); nextConfig.distDir = needleConfig.buildDirectory; } else { console.log("Using default build directory 'dist'. You can override the output directory via the needle config or by setting nextConfig.distDir"); nextConfig.distDir = "dist"; } } const projectId = await getPublicIdentifier(undefined).catch(e => { /*ignore*/ }) // add webpack config if (!nextConfig.webpack) nextConfig.webpack = nextWebPack; else { const webpackFn = nextConfig.webpack; nextConfig.webpack = (config, { buildId, dev, isServer, defaultLoaders, webpack }) => { nextWebPack(config, { buildId, dev, isServer, defaultLoaders, webpack }); return webpackFn(config, { buildId, dev, isServer, defaultLoaders, webpack }); } } /** @param {import ('next').NextConfig config } */ function nextWebPack(config, { buildId, dev, isServer, defaultLoaders, webpack }) { // TODO: get public identifier key from license server const meta = getMeta(); let useRapier = true; if (userSettings.useRapier === false) useRapier = false; else if (meta && meta.useRapier === false) useRapier = false; // add defines const webpackModule = userSettings.modules?.webpack; const definePlugin = webpackModule && new webpackModule.DefinePlugin({ NEEDLE_ENGINE_VERSION: JSON.stringify(tryGetNeedleEngineVersion() ?? "0.0.0"), NEEDLE_ENGINE_GENERATOR: JSON.stringify(tryGetGenerator() ?? "unknown"), NEEDLE_USE_RAPIER: JSON.stringify(useRapier), NEEDLE_PUBLIC_KEY: JSON.stringify(projectId), // TODO globalThis is not solved via DefinePlugin parcelRequire: undefined, }); if (!definePlugin) console.log("WARN: no define plugin provided. Did you miss adding the webpack module to the next config? You can pass it to the Needle plugins via `nextConfig.modules = { webpack };`"); else config.plugins.push(definePlugin); if (!config.module) config.module = {}; if (!config.module.rules) config.module.rules = []; // add license plugin const team_id = userSettings?.license?.team || undefined; config.module.rules.push({ test: /engine_license\.(ts|js)$/, loader: resolve(__dirname, `license.cjs`), options: { team: team_id, accessToken: userSettings?.license?.accessToken, } }); // Rewrite the bare package specifier in GenerateMeshBVHWorker.js to a relative path // so webpack 5 can detect and bundle the worker with its dependencies config.module.rules.push({ test: /GenerateMeshBVHWorker\.js$/, loader: resolve(__dirname, 'meshbvhworker-import.cjs') }); // three.js accesses `window` at module scope, but Workers only have `self`. // BannerPlugin prepends this shim to alias `self.window = self` so three.js doesn't crash. // `raw: true` inserts the string as code, not a comment. // `test: /\.js$/` restricts it to JS files only — without it, the banner would also be // prepended to CSS assets, breaking PostCSS/SCSS parsing during minification. // Only applied to client-side bundles (not server) to avoid `self is not defined` in Node. if (webpackModule && !isServer) { config.plugins.push(new webpackModule.BannerPlugin({ banner: 'if(typeof window==="undefined"&&typeof self!=="undefined")self.window=self;', raw: true, test: /\.js$/, })); } // Handle Vite's ?url imports for wasm and txt files (used by @needle-tools/materialx) config.module.rules.push({ resourceQuery: /url/, type: 'asset/resource', }); config.experiments = config.experiments || {}; config.experiments.asyncWebAssembly = true; alias(config); // these hooks are invoked but nextjs deletes the files again: // add webpack done plugin https://webpack.js.org/api/compiler-hooks/ // config.plugins.push({ // apply(compiler) { // compiler.hooks.shutdown.tap('NeedleDonePlugin', (stats) => { // return createBuildInfoFile(nextConfig.distDir); // }); // } // }); // so as a workaround for above's problem: // hook into process quit event since there doesn't seem to be a next hook for "after emit" // node's beforeExit event is not called :( process.on('exit', (code) => { if (code === 0) return createBuildInfoFile(nextConfig.distDir); }); return config; } return nextConfig; }