From 57bd3632e8ad1a411002047693f38b33364839f2 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Wed, 7 Jun 2023 16:16:00 -0400 Subject: [PATCH] fix spinner in prod build --- scripts/build.js | 121 +++++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 3c9b0655a..ef18500db 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,5 +1,5 @@ import { deleteAsync } from 'del'; -import { exec as execCallback, spawn as spawnCallback } from 'child_process'; +import { exec as execCallback, spawn } from 'child_process'; import { globby } from 'globby'; import browserSync from 'browser-sync'; import chalk from 'chalk'; @@ -13,7 +13,6 @@ import ora from 'ora'; import util from 'util'; const exec = util.promisify(execCallback); -const spawn = util.promisify(spawnCallback); const outdir = 'dist'; const spinner = ora({ hideCursor: false }).start(); @@ -27,22 +26,52 @@ const { bundle, copydir, dir, serve, types } = commandLineArgs([ { name: 'types', type: Boolean } ]); +// +// Runs 11ty and builds the docs. The returned promise resolves after the initial publish has completed. The child +// process and an array of strings containing any output are included in the resolved promise. +// async function buildTheDocs(watch = false) { await deleteAsync('_site'); - if (!watch) { - return execCallback('npx @11ty/eleventy --quiet', { stdio: 'inherit', cwd: 'docs' }); - } + return new Promise((resolve, reject) => { + const args = ['@11ty/eleventy', '--quiet']; + const watcher = chokidar.watch('_site', { persistent: true }); + const output = []; - return spawnCallback('npx', ['@11ty/eleventy', '--watch', '--incremental', '--quiet'], { - stdio: 'pipe', - cwd: 'docs', - shell: true + if (watch) { + args.push('--watch'); + args.push('--incremental'); + } + + const child = spawn('npx', args, { + stdio: 'pipe', + cwd: 'docs', + shell: true // for Windows + }); + + child.stdout.on('data', data => { + output.push(data.toString()); + }); + + // Spin up Eleventy and wait for the search index to appear before proceeding. The search index is generated during + // eleventy.after, so it appears only after the docs are fully published. This is a hacky way to detect when the + // initial publish is complete, but here we are. + watcher.on('add', async filename => { + if (filename.endsWith('search.json')) { + await watcher.close(); + + resolve({ child, output }); + } + }); }); } +// +// Builds the source with esbuild. +// async function buildTheSource() { const alwaysExternal = ['@lit-labs/react', 'react']; + return await esbuild.build({ format: 'esm', target: 'es2017', @@ -87,6 +116,9 @@ async function buildTheSource() { }); } +// +// Called on SIGINT or SIGTERM to cleanup the build and child processes. +// function handleCleanup() { buildResult.rebuild.dispose(); @@ -97,6 +129,9 @@ function handleCleanup() { process.exit(); } +// +// Helper function to draw a spinner while tasks run. +// async function nextTask(label, action) { spinner.text = label; spinner.start(); @@ -142,11 +177,11 @@ await nextTask('Running the TypeScript compiler', () => { }); await nextTask('Building source files', async () => { - return (buildResult = await buildTheSource()); + buildResult = await buildTheSource(); }); -// Copy the build output to an additional directory if (copydir) { + // Copy the build output to an additional directory await nextTask(`Copying the build to "${copydir}"`, async () => { await deleteAsync(copydir); await copy(outdir, copydir); @@ -154,33 +189,13 @@ if (copydir) { } if (serve) { - const deferredOutput = []; - let hasBuilt = false; + let result; // Spin up Eleventy and Wait for the search index to appear before proceeding. The search index is generated during // eleventy.after, so it appears after the docs are fully published. This is kinda hacky, but here we are. // Kick off the Eleventy dev server with --watch and --incremental await nextTask('Building docs', async () => { - childProcess = await buildTheDocs(true); - - // Store Eleventy's output for later - childProcess.stdout.on('data', data => { - if (hasBuilt) { - console.log(data.toString()); - } else { - deferredOutput.push(data.toString()); - } - }); - - return new Promise(resolve => { - const watcher = chokidar.watch('_site', { persistent: true }); - watcher.on('add', async filename => { - if (filename.endsWith('search.json')) { - await watcher.close(); - resolve(); - } - }); - }); + result = await buildTheDocs(true); }); const bs = browserSync.create(); @@ -205,29 +220,38 @@ if (serve) { // Launch browser sync bs.init(browserSyncConfig, () => { const url = `http://localhost:${port}`; - console.log(chalk.cyan(`\n🥾 The dev server is available at ${url}\n`)); - console.log(deferredOutput.join('\n')); - hasBuilt = true; + console.log(chalk.cyan(`\n🥾 The dev server is available at ${url}`)); + + // Log deferred output + if (result.output.length > 0) { + console.log('\n' + result.output.join('\n')); + } + + // Log output that comes later on + result.child.stdout.on('data', data => { + console.log(data.toString()); + }); }); // Rebuild and reload when source files change bs.watch(['src/**/!(*.test).*']).on('change', async filename => { try { - // Rebuild and reload + const isTheme = /^src\/themes/.test(filename); + const isStylesheet = /(\.css|\.styles\.ts)$/.test(filename); + + // Rebuild the source await buildResult.rebuild(); // Rebuild stylesheets when a theme file changes - if (/^src\/themes/.test(filename)) { + if (isTheme) { await exec(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' }); } - // Skip metadata when styles are changed - if (/(\.css|\.styles\.ts)$/.test(filename)) { - return; + // Rebuild metadata (but not when styles are changed) + if (!isStylesheet) { + await exec(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' }); } - await exec(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' }); - bs.reload(); } catch (err) { console.error(chalk.red(err)); @@ -242,9 +266,16 @@ if (serve) { // Prod build if (!serve) { - await nextTask('Building the docs', () => { - return buildTheDocs(); + let result; + + await nextTask('Building the docs', async () => { + result = await buildTheDocs(); }); + + // Log deferred output + if (result.output.length > 0) { + console.log('\n' + result.output.join('\n')); + } } // Cleanup on exit