UNPKG

@pinegrow/piny-astro

Version:

An Astro plugin that implements Piny integration in dev mode.

130 lines (113 loc) 4.78 kB
/* ──────────────────────────────────────────────────────────────────────────── * PinyAstro – dev-tools integration * * • keeps the original `piny.phone.js` injection * • duplicates `data-astro-source-*` ➜ `data-pg-source-*` * • adds “soft-HMR” for every `.astro` file via a runtime loaded through Vite * * ✱ dev-dependency --> npm i -D micromorph * ──────────────────────────────────────────────────────────────────────────── */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { createRequire } from 'module'; const requireCwd = createRequire(process.cwd() + '/noop.js'); export default function PinyAstro(options = {}) { const { injectScript = true, hotReload = true } = options; /* on-disk helper scripts -------------------------------------------------- */ const dir = path.dirname(fileURLToPath(import.meta.url)); const phoneId = '\0virtual:piny-phone'; const phonePath = path.resolve(dir, 'piny.phone.js'); const runtimeId = '\0virtual:piny-astro-runtime'; const runtimePath = path.resolve(dir, 'piny-astro-runtime.js'); return { name: 'piny-astro-dev-tools', hooks: { /* ─────────────────────── config stage ─────────────────────────────── */ 'astro:config:setup'({ command, injectScript: astroInject, updateConfig }) { if (command !== 'dev') return; // nothing to do for build / preview /* 1️⃣ inject both helper scripts into every page */ if (injectScript) { astroInject('page', `import "${phoneId}";`); } if(hotReload) { try { // will throw if the module cannot be resolved from cwd requireCwd.resolve('micromorph'); astroInject('page', `import "${runtimeId}";`); } catch { throw new Error( '[piny-astro] "micromorph" is required for soft-HMR.\n' + 'Please install it with: npm i -D micromorph' ) } } const plugins = [ debugLocatorPlugin(), virtualFilePlugin({ id: phoneId, path: phonePath }), ] if(hotReload) { plugins.push(virtualFilePlugin({ id: runtimeId, path: runtimePath })) plugins.push(softAstroHmrPlugin()) } /* 2️⃣ merge our Vite plugins into the user config */ updateConfig({ vite: { plugins: plugins }, }); }, }, }; } /* ───────── Vite plugin: duplicate data-attrs for devtools ───────── */ function debugLocatorPlugin() { const hasPgFile = /\bdata-pg-source-file=/; const hasPgLoc = /\bdata-pg-source-loc=/; return { name: 'vite-plugin-piny-data-astro-dup', apply: 'serve', enforce: 'post', transform(code, id) { const isAstroFile = id.endsWith('.astro'); if (!isAstroFile) return; if (hasPgFile.test(code) && hasPgLoc.test(code)) return; // idempotent const out = code .replace( /data-astro-source-file="([^"]*)"(?![^>]*\bdata-pg-source-file\b)/g, 'data-astro-source-file="$1" data-pg-source-file="$1"', ) .replace( /data-astro-source-loc="([^"]*)"(?![^>]*\bdata-pg-source-loc\b)/g, 'data-astro-source-loc="$1" data-pg-source-loc="$1"', ); return out === code ? undefined : { code: out, map: null }; }, }; } /* ───── Vite plugin: serve any on-disk file as a virtual module ───── */ function virtualFilePlugin({ id, path: filePath }) { return { name: `vite-plugin-${id.replace(/\0|:/g, '')}`, apply: 'serve', resolveId(source) { return source === id ? id : undefined; }, load(source) { return source === id ? fs.readFileSync(filePath, 'utf-8') : undefined; }, }; } /* ───── Vite plugin: broadcast “piny:astro-hmr” on every .astro save ───── */ function softAstroHmrPlugin() { return { name: 'vite-plugin-piny-astro-soft-hmr', apply: 'serve', enforce: 'post', handleHotUpdate({ file, server }) { if (!file.endsWith('.astro')) return; server.ws.send({ type: 'custom', event: 'piny:astro-hmr', data: { file } }); return []; // stop Vite’s default hard reload }, }; }