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.

232 lines (206 loc) • 7.66 kB
import https from 'https'; import { createHash } from 'crypto'; import { runInNewContext } from 'vm'; /** * @typedef {{pluginContext:import('rollup').TransformPluginContext, cache:Cache}} Context */ /** * Download files and rewrite code * @param {string} command - The command that is being run * @param {object} config - The config object * @param {import('../types/userconfig.js').userSettings} userSettings * @returns {import('vite').Plugin} */ export const needleMakeFilesLocal = (command, config, userSettings) => { if (!userSettings?.makeFilesLocal?.enabled) { return; } console.log(`[needle:local-files] Local files plugin is enabled`); const cache = new Cache(); return { name: "needle:local-files", enforce: 'pre', apply: "build", // transform bundle async transform(src, id) { src = await makeLocal(src, "ext/", "", { pluginContext: this, cache: cache, }); return { code: src, map: null, }; } } } /** * Rewrites the source code to make local files * @param {string} src - The source code to rewrite * @param {string} basePath - The base path where the files will be saved * @param {string} currentDir - The current directory of the file being processed * @param {Context} context - The Vite plugin context */ async function makeLocal(src, basePath, currentDir, context) { // find all google font urls fonts.googleapis.com const googleFontRegex = /["'](https:\/\/fonts\.googleapis\.com\/.+?)["']/g; let match; while ((match = googleFontRegex.exec(src)) !== null) { const fontUrl = match[1]; console.log(`\nFound google font URL: ${fontUrl}`); // Check if the font URL is already in the cache const cachedPath = context.cache.getFromCache(fontUrl); if (cachedPath) { console.log(`Using cached font URL: ${cachedPath}`); src = src.replace(fontUrl, cachedPath); continue; // Skip downloading if already cached } let font = await downloadText(fontUrl); const familyNameMatch = /family=([^&]+)/.exec(fontUrl); const familyName = getValidFilename(familyNameMatch[1], font); font = await makeLocal(font, basePath, basePath, context); const fontFileName = `font-${familyName}.css`; const outputPath = basePath + fontFileName; const referenceId = context.pluginContext.emitFile({ type: 'asset', fileName: outputPath, source: font, }); const localPath = `${context.pluginContext.getFileName(referenceId)}`; const newPath = getRelativeToBasePath(localPath, currentDir); context.cache.addToCache(fontUrl, newPath); src = src.replace(fontUrl, newPath); } const gstatic = /["'(](https:\/\/fonts\.gstatic\.com\/)(.+?)["')]/g; while ((match = gstatic.exec(src)) !== null) { const fontPath = match[2]; const fontUrl = match[1] + fontPath; console.log(`\nFound gstatic URL: ${fontUrl}`); // Check if the font URL is already in the cache const cachedPath = context.cache.getFromCache(fontUrl); if (cachedPath) { console.log(`Using cached gstatic font URL: ${cachedPath}`); src = src.replace(fontUrl, cachedPath); continue; // Skip downloading if already cached } let font = await downloadBinary(fontUrl); const filename = getValidFilename(fontPath, font); console.log(`Saving font to: ${basePath + filename}`); const referenceId = context.pluginContext.emitFile({ type: 'asset', fileName: basePath + filename, source: font, }); const localPath = `${context.pluginContext.getFileName(referenceId)}`; const newPath = getRelativeToBasePath(localPath, currentDir); context.cache.addToCache(fontUrl, newPath); src = src.replace(fontUrl, newPath); } return src; } class Cache { __cache = new Map(); /** * Adds a key-value pair to the cache. * @param {string} key - The key to store the value under * @param {string|Uint8Array} value - The value to store in the cache * @returns {void} */ addToCache(key, value) { if (this.__cache.has(key)) { console.warn(`Key ${key} already exists in cache, overwriting.`); } this.__cache.set(key, value); } /** * Retrieves a value from the cache by its key. * @param {string} key - The key to look up in the cache */ getFromCache(key) { if (this.__cache.has(key)) { return this.__cache.get(key); } else { return null; } } } function getRelativeToBasePath(path, basePath) { if (basePath?.length && path.startsWith(basePath)) { return "./" + path.substring(basePath.length); } return path; } /** * Generates a valid filename from a given path. * @param {string} path - The path to generate a filename from * @param {string|Uint8Array} content - The content to hash for uniqueness (not used in this example) */ function getValidFilename(path, content) { // Remove any characters that are not valid in filenames let name = path.replace(/[^a-z0-9_\-\.\+]/gi, '-'); const maxLength = 200; if (path.length > maxLength) { // If the name is too long, hash it to create a unique identifier const hash = createContentMd5(content); let ext = ""; const extIndex = name.lastIndexOf('.'); if (extIndex !== -1) { ext = name.substring(extIndex + 1); name = name.substring(0, extIndex); } name = `${name.substring(0, maxLength)}-${hash}${ext ? `.${ext}` : ''}`; } return name; } /** * Creates a hash of the content using MD5. * @param {string|Uint8Array} str - The content to hash */ function createContentMd5(str) { return createHash('md5') .update(str) .digest('hex'); } /** * @param {string} url - The URL of the font to download */ function downloadText(url) { return new Promise((res => { https.get(url, (response) => { if (response.statusCode !== 200) { console.error(`Failed to download font from ${url}: ${response.statusCode}`); res(null); return; } let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { // Here you can save the data to a file or process it as needed console.log(`Downloaded from ${url}`); res(data); }); }); })) } function downloadBinary(url) { return new Promise((res, rej) => { https.get(url, (response) => { if (response.statusCode !== 200) { console.error(`Failed to download font from ${url}: ${response.statusCode}`); rej(new Error(`Failed to download font from ${url}: ${response.statusCode}`)); return; } const chunks = []; response.on('data', (chunk) => { chunks.push(chunk); }); response.on('end', () => { // Here you can save the data to a file or process it as needed console.log(`Downloaded from ${url}`); res(Buffer.concat(chunks)); }); }); }); }