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.

179 lines (146 loc) 6.83 kB
import fs from 'fs'; import { tryGetNeedleEngineVersion } from '../common/version.js'; import { loadConfig } from './config.js'; import { getPosterPath } from './poster.js'; import { needleGreenBold, needleLog } from './logging.js'; /** * @param {"build" | "serve"} command * @param {import('../types/needleConfig').needleMeta | null} config * @param {import('../types').userSettings} userSettings * @returns {import('vite').Plugin} */ export function needleMeta(command, config, userSettings) { // we can check if this is a build const isBuild = command === 'build'; async function updateConfig() { config = await loadConfig(); } if (!userSettings) userSettings = {}; return { // replace meta tags name: 'needle:meta', transformIndexHtml: { order: 'pre', handler(/** @type {string} */ html, /** @type {unknown} */ _ctx) { if (userSettings.allowMetaPlugin === false) return []; // this is useful to get the latest config exported from editor // whenever vite wants to transform the html updateConfig(); if (!config?.license) html = insertNeedleCredits(html); // early out of the config is invalid / doesn't contain meta information // TODO might be better to handle these edge cases / special cases right from Unity/Blender and not here if (!config) return []; let meta = config.meta; if (!meta) { meta = {}; meta.title = config.sceneName; } if (!meta.image?.length) { const path = getPosterPath(); if (fs.existsSync('./' + path)) meta.image = path; } /** @type {Array<{ tag: string, attrs: Record<string, string> }>} */ const tags = []; // tags.push({"tag": "meta", attrs: { name: "viewport", content: "width=device-width, initial-scale=1" }}); let img = meta.image; if (img?.length) { // for a regular build the absolutePath is url (since we dont know the deployment target) if (config.absolutePath?.length) { const baseUrl = config.absolutePath; let url = baseUrl + "/" + img; url = removeDuplicateSlashesInUrl(url); // url = appendVersion(url); tags.push({ tag: 'meta', attrs: { name: 'twitter:card', content: 'summary_large_image' } }); tags.push({ tag: 'meta', attrs: { name: 'twitter:image', content: url } }); tags.push({ tag: 'meta', attrs: { name: 'og:image', content: url } }); // Keep in sync with poster tags.push({ tag: 'meta', attrs: { name: 'og:image:width', content: 1080 } }); tags.push({ tag: 'meta', attrs: { name: 'og:image:height', content: 1080 } }); } else if (isBuild) { needleLog("needle-meta", `meta.image is set but absolutePath is ${config.absolutePath} in userSettings. Skipping meta.image.`, "warn", { dimBody: false }); } } if (config.absolutePath?.length) { html = updateUrlMetaTag(html, config.absolutePath); } if (meta.title?.length) { tags.push({ tag: 'meta', attrs: { name: 'og:title', content: meta.title } }); html = html.replace( /<title>(.*?)<\/title>/, `<title>${meta.title}</title>`, ); if (userSettings.allowRemoveMetaTags !== false) html = removeMetaTag(html, 'og:title'); } if (meta.description?.length) { tags.push({ tag: 'meta', attrs: { name: 'description', content: meta.description } }); tags.push({ tag: 'meta', attrs: { name: 'og:description', content: meta.description } }); if (userSettings.allowRemoveMetaTags !== false) { html = removeMetaTag(html, 'description'); html = removeMetaTag(html, 'og:description'); } } let generator = "Needle"; if (config.generator?.length > 5) { generator = config.generator; } tags.push({ tag: 'meta', attrs: { name: 'generator', content: generator } }); const needleEngineVersion = tryGetNeedleEngineVersion(); if (needleEngineVersion) { if (command === "build") needleLog("needle-meta", needleGreenBold(`✨ Needle Engine version: ${needleEngineVersion} 🌵`), "log", { dimBody: false }); tags.push({ tag: 'meta', attrs: { name: 'needle-engine', content: needleEngineVersion } }); } else needleLog("needle-meta", "Could not find needle engine package.json", "warn", { dimBody: false }) tags.push({ tag: 'meta', attrs: { name: 'needle:buildtime', content: new Date().toISOString() } }); return { html, tags } }, } } } /** * @param {string} html * @param {string} url * @returns {string} */ function updateUrlMetaTag(html, url) { const result = `<meta name="url" content="${url}">`; html = html.replace(`<meta name="url" content="https://needle.tools">`, result); html = html.replace(`<meta name="url" content="http://needle.tools">`, result); return html; } /** @param {string} str @returns {string} */ function appendVersion(str) { return str + "?v=" + Date.now(); } /** @param {string} url @returns {string} */ function removeDuplicateSlashesInUrl(url) { return url.replace(/([^:]\/)\/+/g, "$1"); } /** * @param {string} html * @param {string} name * @returns {string} */ function removeMetaTag(html, name) { // TODO: maybe we could also just replace the content const regex = new RegExp(`<meta (name|property)="${name}".+?\/?>`, 'gs'); const newHtml = html.replace( regex, '', ); // console.log(newHtml); return newHtml; } /** @param {string} html @returns {string} */ function insertNeedleCredits(html) { const needleCredits = `<!-- 🌵 Made with Needle — https://needle.tools -->`; html = html.replace( /<head/, needleCredits + "\n<head", ); return html; }