2023-06-07 17:28:22 +00:00
|
|
|
import { deleteAsync } from 'del';
|
|
|
|
import { exec as execCallback, spawn as spawnCallback } from 'child_process';
|
2023-06-06 21:02:15 +00:00
|
|
|
import { globby } from 'globby';
|
2023-06-06 13:21:07 +00:00
|
|
|
import browserSync from 'browser-sync';
|
|
|
|
import chalk from 'chalk';
|
2023-06-06 21:02:15 +00:00
|
|
|
import chokidar from 'chokidar';
|
2023-06-06 13:21:07 +00:00
|
|
|
import commandLineArgs from 'command-line-args';
|
2023-06-06 21:02:15 +00:00
|
|
|
import copy from 'recursive-copy';
|
2021-06-17 21:38:48 +00:00
|
|
|
import esbuild from 'esbuild';
|
2023-06-07 17:28:22 +00:00
|
|
|
import fs from 'fs/promises';
|
2023-06-06 21:02:15 +00:00
|
|
|
import getPort, { portNumbers } from 'get-port';
|
2023-06-07 17:28:22 +00:00
|
|
|
import ora from 'ora';
|
|
|
|
import util from 'util';
|
2021-02-26 14:09:13 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
const exec = util.promisify(execCallback);
|
|
|
|
const spawn = util.promisify(spawnCallback);
|
|
|
|
|
|
|
|
const outdir = 'dist';
|
2023-06-06 19:46:50 +00:00
|
|
|
const abortController = new AbortController();
|
|
|
|
const abortSignal = abortController.signal;
|
2023-06-07 17:28:22 +00:00
|
|
|
const spinner = ora().start();
|
|
|
|
let buildResult;
|
|
|
|
|
|
|
|
const { bundle, copydir, dir, serve, types } = commandLineArgs([
|
|
|
|
{ name: 'bundle', type: Boolean },
|
|
|
|
{ name: 'copydir', type: String },
|
|
|
|
{ name: 'serve', type: Boolean },
|
|
|
|
{ name: 'types', type: Boolean }
|
|
|
|
]);
|
2023-06-06 19:46:50 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
async function buildTheDocs(watch = false) {
|
|
|
|
await deleteAsync('_site');
|
2023-06-06 21:02:15 +00:00
|
|
|
|
2023-06-06 19:46:50 +00:00
|
|
|
if (!watch) {
|
2023-06-07 17:28:22 +00:00
|
|
|
return execCallback('npx @11ty/eleventy --quiet', { stdio: 'inherit', cwd: 'docs', signal: abortSignal });
|
2023-06-06 19:46:50 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
return spawnCallback('npx', ['@11ty/eleventy', '--watch', '--incremental', '--quiet'], {
|
|
|
|
stdio: 'pipe',
|
2023-06-06 21:02:15 +00:00
|
|
|
cwd: 'docs',
|
|
|
|
signal: abortSignal
|
|
|
|
});
|
2023-06-06 13:21:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
async function buildTheSource() {
|
|
|
|
const alwaysExternal = ['@lit-labs/react', 'react'];
|
|
|
|
return await esbuild.build({
|
|
|
|
format: 'esm',
|
|
|
|
target: 'es2017',
|
|
|
|
entryPoints: [
|
|
|
|
//
|
|
|
|
// NOTE: Entry points must be mapped in package.json > exports, otherwise users won't be able to import them!
|
|
|
|
//
|
|
|
|
// The whole shebang
|
|
|
|
'./src/shoelace.ts',
|
|
|
|
// The auto-loader
|
|
|
|
'./src/shoelace-autoloader.ts',
|
|
|
|
// Components
|
|
|
|
...(await globby('./src/components/**/!(*.(style|test)).ts')),
|
|
|
|
// Translations
|
|
|
|
...(await globby('./src/translations/**/*.ts')),
|
|
|
|
// Public utilities
|
|
|
|
...(await globby('./src/utilities/**/!(*.(style|test)).ts')),
|
|
|
|
// Theme stylesheets
|
|
|
|
...(await globby('./src/themes/**/!(*.test).ts')),
|
|
|
|
// React wrappers
|
|
|
|
...(await globby('./src/react/**/*.ts'))
|
|
|
|
],
|
|
|
|
outdir,
|
|
|
|
chunkNames: 'chunks/[name].[hash]',
|
|
|
|
incremental: serve,
|
|
|
|
define: {
|
|
|
|
// Floating UI requires this to be set
|
|
|
|
'process.env.NODE_ENV': '"production"'
|
|
|
|
},
|
|
|
|
bundle: true,
|
|
|
|
//
|
|
|
|
// We don't bundle certain dependencies in the unbundled build. This ensures we ship bare module specifiers,
|
|
|
|
// allowing end users to better optimize when using a bundler. (Only packages that ship ESM can be external.)
|
|
|
|
//
|
|
|
|
// We never bundle React or @lit-labs/react though!
|
|
|
|
//
|
|
|
|
external: bundle
|
|
|
|
? alwaysExternal
|
|
|
|
: [...alwaysExternal, '@floating-ui/dom', '@shoelace-style/animations', 'lit', 'qr-creator'],
|
|
|
|
splitting: true,
|
|
|
|
plugins: []
|
|
|
|
});
|
|
|
|
}
|
2021-05-11 12:35:31 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
function handleCleanup() {
|
|
|
|
buildResult.rebuild.dispose();
|
|
|
|
abortController.abort(); // Stops the child process
|
|
|
|
}
|
2021-10-13 21:12:50 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
async function nextTask(label, action) {
|
|
|
|
spinner.text = label;
|
|
|
|
spinner.start();
|
2021-02-26 14:09:13 +00:00
|
|
|
|
2021-10-13 21:12:50 +00:00
|
|
|
try {
|
2023-06-07 17:28:22 +00:00
|
|
|
await action();
|
|
|
|
spinner.stop();
|
|
|
|
console.log(`${chalk.green('✔')} ${label}`);
|
2021-10-13 21:12:50 +00:00
|
|
|
} catch (err) {
|
2023-06-07 17:28:22 +00:00
|
|
|
spinner.stop();
|
|
|
|
console.error(`${chalk.red('✘')} ${err}`);
|
2021-10-13 21:12:50 +00:00
|
|
|
process.exit(1);
|
|
|
|
}
|
2023-06-07 17:28:22 +00:00
|
|
|
}
|
2021-10-13 21:12:50 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
await nextTask('Cleaning up the previous build', async () => {
|
|
|
|
await deleteAsync(outdir);
|
|
|
|
await fs.mkdir(outdir, { recursive: true });
|
|
|
|
});
|
2021-02-26 14:09:13 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
await nextTask('Generating component metadata', () => {
|
|
|
|
return exec(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
|
|
|
|
|
|
|
await nextTask('Wrapping components for React', () => {
|
|
|
|
return exec(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
|
|
|
|
|
|
|
await nextTask('Generating Web Types', () => {
|
|
|
|
return exec(`node scripts/make-web-types.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
2021-10-22 14:51:17 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
await nextTask('Generating themes', () => {
|
|
|
|
return exec(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
|
|
|
|
|
|
|
await nextTask('Packaging up icons', () => {
|
|
|
|
return exec(`node scripts/make-icons.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
|
|
|
|
|
|
|
await nextTask('Running the TypeScript compiler', () => {
|
|
|
|
return exec(`tsc --project ./tsconfig.prod.json --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
});
|
|
|
|
|
|
|
|
await nextTask('Building source files', async () => {
|
|
|
|
return (buildResult = await buildTheSource());
|
|
|
|
});
|
|
|
|
|
|
|
|
// Copy the build output to an additional directory
|
|
|
|
if (copydir) {
|
|
|
|
await nextTask(`Copying the build to "${copydir}"`, async () => {
|
|
|
|
await deleteAsync(copydir);
|
|
|
|
await copy(outdir, copydir);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (serve) {
|
|
|
|
const deferredOutput = [];
|
|
|
|
let hasBuilt = false;
|
|
|
|
|
|
|
|
// 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 () => {
|
|
|
|
const child = await buildTheDocs(true);
|
|
|
|
|
|
|
|
// Store Eleventy's output for later
|
|
|
|
child.stdout.on('data', data => {
|
|
|
|
if (hasBuilt) {
|
|
|
|
console.log(data.toString());
|
|
|
|
} else {
|
|
|
|
deferredOutput.push(data.toString());
|
2023-06-06 21:02:15 +00:00
|
|
|
}
|
2023-06-06 13:21:07 +00:00
|
|
|
});
|
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const bs = browserSync.create();
|
|
|
|
const port = await getPort({ port: portNumbers(4000, 4999) });
|
|
|
|
const browserSyncConfig = {
|
|
|
|
startPath: '/',
|
|
|
|
port,
|
|
|
|
logLevel: 'silent',
|
|
|
|
logPrefix: '[shoelace]',
|
|
|
|
logFileChanges: true,
|
|
|
|
notify: false,
|
|
|
|
single: false,
|
|
|
|
ghostMode: false,
|
|
|
|
server: {
|
|
|
|
baseDir: '_site',
|
|
|
|
routes: {
|
|
|
|
'/dist': './dist'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
});
|
2021-10-08 14:11:12 +00:00
|
|
|
|
2023-06-07 17:28:22 +00:00
|
|
|
// Rebuild and reload when source files change
|
|
|
|
bs.watch(['src/**/!(*.test).*']).on('change', async filename => {
|
|
|
|
try {
|
|
|
|
// Rebuild and reload
|
|
|
|
await buildResult.rebuild();
|
|
|
|
|
|
|
|
// Rebuild stylesheets when a theme file changes
|
|
|
|
if (/^src\/themes/.test(filename)) {
|
|
|
|
await exec(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip metadata when styles are changed
|
|
|
|
if (/(\.css|\.styles\.ts)$/.test(filename)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await exec(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
|
|
|
|
|
|
|
bs.reload();
|
|
|
|
} catch (err) {
|
|
|
|
console.error(chalk.red(err));
|
|
|
|
}
|
2023-06-06 19:46:50 +00:00
|
|
|
});
|
2023-06-07 17:28:22 +00:00
|
|
|
|
|
|
|
// Reload without rebuilding when the docs change
|
|
|
|
bs.watch(['_site/**/*.*']).on('change', filename => {
|
|
|
|
bs.reload();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prod build
|
|
|
|
if (!serve) {
|
|
|
|
await nextTask('Building the docs', () => {
|
|
|
|
return buildTheDocs();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup on exit
|
|
|
|
process.on('SIGINT', handleCleanup);
|
|
|
|
process.on('SIGTERM', handleCleanup);
|