UNPKG

slidev-addon-anipres

Version:

173 lines (162 loc) 6.6 kB
import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import { defineConfig, normalizePath, type Plugin } from "vite"; import Font from "vite-plugin-font"; import regexpEscape from "regexp.escape"; const fontDirPath = normalizePath(path.join(__dirname, "./assets/fonts/")); const xiaolaiFontPath = normalizePath( path.join(fontDirPath, "XiaolaiSC-Regular.ttf"), ); // In the production build, the font path passed to the font plugin is a relative path like "../../node_modules/...", not a full path like "/Users/.../node_modules/...". // So we need to omit the path segment before `node_modules` from the query regex to match the relative path. const fontDirPathFromNodeModules = fontDirPath.replace( /^.*(?=\/node_modules\/)/, "", ); const fontDirRegex = new RegExp( regexpEscape(fontDirPathFromNodeModules) + ".*\\.ttf(\\?.*)?$", ); let root = process.cwd(); function resolveSnapshotPath() { return path.join(root, ".slidev/anipres/snapshots"); } function isPathIn(target: string, maybeParent: string) { const relative = path.relative(maybeParent, target); return relative && !relative.startsWith("..") && !path.isAbsolute(relative); } export default defineConfig(({ mode }) => ({ optimizeDeps: { /* * In dev mode, Vite serves native ESM, so CommonJS packages like "react" don't expose named exports (e.g., useCallback) correctly, * which causes runtime errors like 'The requested module ... does not provide an export named ...' * when this package is used in the dev mode of Slidev. * Configuring `optimizeDeps` here tells Vite to pre-bundle these dependencies so that they behave as ES modules. * * References: * - Vite Dep Pre-Bundling: https://vitejs.dev/guide/dep-pre-bundling.html */ include: [ "slidev-addon-anipres > react", "slidev-addon-anipres > react-dom", "slidev-addon-anipres > react-dom/client", "slidev-addon-anipres > tldraw", ], }, plugins: [ { name: "set-font-subsets", resolveId(id) { if (id === "/@xiaolai-font.ttf") { console.debug(`Resolve ${id} as ${xiaolaiFontPath}`); // Enable extremely lightweight optimization (https://www.npmjs.com/package/vite-plugin-font#extremely-lightweight-optimization) // by adding `?subsets` to the font URL only in production mode. // This setting is effective in combination with the `scanFiles` option of the `Font.vite` plugin below. // We use this plugin-based approach to modify the module name dynamically at build time. return xiaolaiFontPath + (mode === "production" ? "?subsets" : ""); } }, }, Font.vite({ // Enable optimization only in production mode. See the comment in the `resolveId` hook above. scanFiles: mode === "production" ? ["**/.slidev/anipres/snapshots/*.json"] : undefined, // `node_modules` is excluded by default, which is not good in our case where the font file will be in `node_modules` when this package is installed to user's environment by a package manager. // So we unset the `exclude` option to override the default behavior. exclude: [], // Also we set a stricter include path explicitly to avoid unexpected side effects from setting `exclude` to `[]`. include: [fontDirRegex], }) as Plugin, { // Load and save Tldraw snapshots from/to the file system via Vite plugin. // This implementation is based on slidev-addon-graph: // https://github.com/antfu/slidev-addon-graph/blob/5c7dbfbf198c401477f9b50ce4de4e9e50243d16/vite.config.ts name: "anipres-server", configureServer(server) { root = server.config.root; server.ws.on("connection", (socket) => { socket.on("message", async (data) => { const payload = JSON.parse(data.toString()); if ( payload.type === "custom" && payload.event === "anipres-snapshot" ) { const snapshotDir = resolveSnapshotPath(); const snapshotData = JSON.stringify( payload.data.snapshot, null, 2, ); fs.mkdirSync(snapshotDir, { recursive: true }); fs.writeFileSync( path.join(snapshotDir, `${payload.data.id}.json`), snapshotData, ); // Invalidate the module so that the saved snapshot is loaded on the next request. const mod = server.moduleGraph.getModuleById( "/@slidev-anipres-snapshot", ); if (mod) server.moduleGraph.invalidateModule(mod); } }); }); }, configResolved(config) { root = config.root; }, resolveId(id) { if (id === "/@slidev-anipres-snapshot") { return id; } }, load(id) { if (id === "/@slidev-anipres-snapshot") { const snapshotPath = resolveSnapshotPath(); const files = fs.existsSync(snapshotPath) ? fs.readdirSync(snapshotPath) : []; return [ "", ...files.map((file, idx) => { return `import v${idx} from ${JSON.stringify(normalizePath(path.join(snapshotPath, file)))}`; }), "const snapshots = {", files .map((file, idx) => { return ` ${JSON.stringify(file.replace(/\.json$/, ""))}: v${idx}`; }) .join(",\n"), "}", "export default snapshots", "if (import.meta.hot) {", " import.meta.hot.accept(({ default: newSnapshots }) => {", " Object.assign(snapshots, newSnapshots)", " })", "}", ].join("\n"); } }, }, ], build: { rollupOptions: { onwarn(warning, warn) { // Vite reports a missing export just as a warning, // but we want to treat it as an error so that the build fails // in the case where the missing export is a font file. // because it leads to an error and broken styles at runtime. if (warning.code === "MISSING_EXPORT") { if ( warning.exporter && isPathIn(normalizePath(warning.exporter), fontDirPath) ) { throw new Error(`Build failed due to: ${warning.message}`); } } warn(warning); }, }, }, }));