/** * Builds the assets the landing page needs, into docs/. * * The page is served by GitHub Pages straight from the repository, so everything it loads has * to be committed — there is no build step on the hosting side. Everything it loads is also * local: the previous page pulled Tailwind, CodeMirror and Babel from three CDNs, and its * playground died silently when the unpinned `@babel/standalone` URL rolled over to Babel 8 * and the plugin list it passed stopped existing. Vendoring the compiler pins it to the * version in package.json and to a lockfile. */ import { build } from 'esbuild'; import { copyFile, mkdir, readFile, writeFile, stat } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; import { collectDiagnostics } from './diagnostics.mjs'; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const docs = path.join(root, 'docs'); const vendor = path.join(docs, 'vendor'); const size = async (file) => { const { size: bytes } = await stat(file); return `${(bytes / 1024).toFixed(0)} KB`; }; await mkdir(vendor, { recursive: true }); const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8')); // 1. The library itself, as a browser global the playground can pull names out of. await build({ entryPoints: [path.join(root, 'src/index.ts')], bundle: true, format: 'iife', globalName: 'Cereale', minify: true, target: 'es2022', tsconfigRaw: { compilerOptions: { experimentalDecorators: false, useDefineForClassFields: true, target: 'es2022' }, }, outfile: path.join(docs, 'cereale.js'), }); // 2. Facts the page would otherwise hard-code and then get wrong. Everything else it needs — // the decorator count, the export list — it derives from the bundle at runtime. await writeFile( path.join(docs, 'meta.js'), `// Generated by scripts/build-docs.mjs — do not edit.\n` + `window.CEREALE_META = ${JSON.stringify({ version: pkg.version, node: pkg.engines.node }, null, 2)};\n` ); // 3. The compiler errors the page quotes, produced by actually running the compiler. If a // snippet the page calls a compile error ever compiles, this fails the build. const { byCase, problems } = await collectDiagnostics(root); if (problems.length > 0) { console.error('The landing page makes a claim the compiler does not support:\n' + problems.map((p) => ` - ${p}`).join('\n')); process.exit(1); } await writeFile( path.join(docs, 'diagnostics.js'), `// Generated by scripts/build-docs.mjs from real tsc output — do not edit.\n` + `window.CEREALE_DIAGNOSTICS = ${JSON.stringify(byCase, null, 2)};\n` ); // 4. The playground's TypeScript compiler, pinned by package.json rather than by a CDN URL. const babel = path.join(root, 'node_modules/@babel/standalone/babel.min.js'); await copyFile(babel, path.join(vendor, 'babel.min.js')); await writeFile( path.join(vendor, 'README.md'), `# Vendored assets\n\n` + `Generated by \`npm run build:docs\`. Do not edit by hand.\n\n` + `- \`babel.min.js\` — @babel/standalone ${JSON.parse(await readFile(path.join(root, 'node_modules/@babel/standalone/package.json'), 'utf8')).version}, ` + `used by the playground to compile TypeScript with standard decorators in the browser. ` + `Vendored rather than loaded from a CDN: the previous page referenced an unpinned unpkg URL, ` + `which silently began serving Babel 8 and broke the playground.\n` ); console.log(`docs/cereale.js ${await size(path.join(docs, 'cereale.js'))}`); console.log(`docs/meta.js ${await size(path.join(docs, 'meta.js'))} (v${pkg.version})`); console.log(`docs/vendor/babel.min.js ${await size(path.join(vendor, 'babel.min.js'))}`);