2023-06-06 09:21:07 -04:00
|
|
|
import browserSync from 'browser-sync';
|
|
|
|
|
import chalk from 'chalk';
|
2024-12-14 17:00:28 -05:00
|
|
|
import { execSync } from 'child_process';
|
|
|
|
|
import { deleteAsync } from 'del';
|
2021-06-17 17:38:48 -04:00
|
|
|
import esbuild from 'esbuild';
|
2024-12-14 17:00:28 -05:00
|
|
|
import { replace } from 'esbuild-plugin-replace';
|
2025-03-18 13:04:24 -04:00
|
|
|
|
2024-12-14 17:00:28 -05:00
|
|
|
import { mkdir, readFile } from 'fs/promises';
|
2023-06-06 17:02:15 -04:00
|
|
|
import getPort, { portNumbers } from 'get-port';
|
2024-12-14 17:00:28 -05:00
|
|
|
import { globby } from 'globby';
|
2024-04-17 11:20:27 -04:00
|
|
|
import ora from 'ora';
|
2024-12-14 17:00:28 -05:00
|
|
|
import { dirname, join, relative } from 'path';
|
2024-04-17 11:20:27 -04:00
|
|
|
import process from 'process';
|
2024-12-14 17:00:28 -05:00
|
|
|
import copy from 'recursive-copy';
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
import { cdnDir, distDir, docsDir, rootDir, runScript, siteDir } from './utils.js';
|
2023-06-06 15:46:50 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
const isDeveloping = process.argv.includes('--develop');
|
|
|
|
|
const spinner = ora({ text: 'Web Awesome', color: 'cyan' }).start();
|
|
|
|
|
const packageData = JSON.parse(await readFile(join(rootDir, 'package.json'), 'utf-8'));
|
|
|
|
|
const version = JSON.stringify(packageData.version.toString());
|
2024-09-11 10:25:42 -04:00
|
|
|
let buildContexts = {
|
|
|
|
|
bundledContext: {},
|
2024-12-14 17:08:29 -05:00
|
|
|
unbundledContext: {},
|
2024-09-11 10:25:42 -04:00
|
|
|
};
|
2024-02-05 11:02:14 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
/**
|
|
|
|
|
* Runs the full build.
|
|
|
|
|
*/
|
|
|
|
|
async function buildAll() {
|
|
|
|
|
const start = Date.now();
|
2024-02-05 11:02:14 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
try {
|
|
|
|
|
await cleanup();
|
|
|
|
|
await generateManifest();
|
|
|
|
|
await generateReactWrappers();
|
|
|
|
|
await generateTypes();
|
|
|
|
|
await generateStyles();
|
2024-09-11 10:25:42 -04:00
|
|
|
|
|
|
|
|
// copy everything to unbundled before we generate bundles.
|
|
|
|
|
await copy(cdnDir, distDir, { overwrite: true });
|
|
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
await generateBundle();
|
|
|
|
|
await generateDocs();
|
|
|
|
|
|
|
|
|
|
const time = (Date.now() - start) / 1000 + 's';
|
|
|
|
|
spinner.succeed(`The build is complete ${chalk.gray(`(finished in ${time})`)}`);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
spinner.fail();
|
|
|
|
|
console.log(chalk.red(`\n${err}`));
|
2024-02-05 11:02:14 -05:00
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
2023-06-07 16:16:00 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
/** Empties the dist directory. */
|
|
|
|
|
async function cleanup() {
|
|
|
|
|
spinner.start('Cleaning up dist');
|
|
|
|
|
|
|
|
|
|
await deleteAsync(distDir);
|
2024-09-11 10:25:42 -04:00
|
|
|
await deleteAsync(cdnDir);
|
2024-04-17 11:20:27 -04:00
|
|
|
await mkdir(distDir, { recursive: true });
|
2024-09-11 10:25:42 -04:00
|
|
|
await mkdir(cdnDir, { recursive: true });
|
2024-04-17 11:20:27 -04:00
|
|
|
|
|
|
|
|
spinner.succeed();
|
2023-06-06 09:21:07 -04:00
|
|
|
}
|
|
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
/**
|
|
|
|
|
* Analyzes components and generates the custom elements manifest file.
|
|
|
|
|
*/
|
|
|
|
|
function generateManifest() {
|
|
|
|
|
spinner.start('Generating CEM');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
execSync('cem analyze --config "custom-elements-manifest.js"');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`\n\n${error.message}`);
|
2025-01-10 13:29:14 -05:00
|
|
|
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates React wrappers for all components.
|
|
|
|
|
*/
|
|
|
|
|
function generateReactWrappers() {
|
|
|
|
|
spinner.start('Generating React wrappers');
|
|
|
|
|
|
|
|
|
|
try {
|
2024-09-11 10:25:42 -04:00
|
|
|
execSync(`node scripts/make-react.js --outdir "${cdnDir}"`, { stdio: 'inherit' });
|
2024-04-17 11:20:27 -04:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`\n\n${error.message}`);
|
2025-01-10 13:29:14 -05:00
|
|
|
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Copies theme stylesheets to the dist.
|
|
|
|
|
*/
|
|
|
|
|
async function generateStyles() {
|
|
|
|
|
spinner.start('Copying stylesheets');
|
|
|
|
|
|
2025-05-08 13:58:26 -04:00
|
|
|
await copy(join(rootDir, 'src/styles'), join(cdnDir, 'styles'), { overwrite: true });
|
2024-04-17 11:20:27 -04:00
|
|
|
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Runs TypeScript to generate types.
|
|
|
|
|
*/
|
2024-06-18 12:56:55 -04:00
|
|
|
async function generateTypes() {
|
2024-04-17 11:20:27 -04:00
|
|
|
spinner.start('Running the TypeScript compiler');
|
|
|
|
|
|
|
|
|
|
try {
|
2024-09-11 10:25:42 -04:00
|
|
|
execSync(`tsc --project ./tsconfig.prod.json --outdir "${cdnDir}"`);
|
2024-04-17 11:20:27 -04:00
|
|
|
} catch (error) {
|
2025-01-10 13:29:14 -05:00
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
return Promise.reject(error.stdout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Runs esbuild to generate the final dist.
|
|
|
|
|
*/
|
|
|
|
|
async function generateBundle() {
|
|
|
|
|
spinner.start('Bundling with esbuild');
|
2023-06-12 10:54:33 -04:00
|
|
|
|
2024-09-11 10:25:42 -04:00
|
|
|
// Bundled config
|
2024-04-17 11:20:27 -04:00
|
|
|
const config = {
|
2023-06-07 13:28:22 -04:00
|
|
|
format: 'esm',
|
2024-04-17 11:20:27 -04:00
|
|
|
target: 'es2020',
|
2023-06-07 13:28:22 -04:00
|
|
|
entryPoints: [
|
|
|
|
|
//
|
2024-04-17 11:20:27 -04:00
|
|
|
// IMPORTANT: Entry points MUST be mapped in package.json => exports
|
2023-06-07 13:28:22 -04:00
|
|
|
//
|
2024-04-17 11:20:27 -04:00
|
|
|
// Utilities
|
2023-09-08 13:45:49 -04:00
|
|
|
'./src/webawesome.ts',
|
2024-04-17 11:20:27 -04:00
|
|
|
// Autoloader + utilities
|
|
|
|
|
'./src/webawesome.loader.ts',
|
2024-09-11 10:25:42 -04:00
|
|
|
'./src/webawesome.ssr-loader.ts',
|
2024-04-17 11:20:27 -04:00
|
|
|
// Individual components
|
2023-06-07 13:28:22 -04:00
|
|
|
...(await globby('./src/components/**/!(*.(style|test)).ts')),
|
|
|
|
|
// Translations
|
|
|
|
|
...(await globby('./src/translations/**/*.ts')),
|
|
|
|
|
// React wrappers
|
2024-12-14 17:08:29 -05:00
|
|
|
...(await globby('./src/react/**/*.ts')),
|
2023-06-07 13:28:22 -04:00
|
|
|
],
|
2024-09-11 10:25:42 -04:00
|
|
|
outdir: cdnDir,
|
2023-06-07 13:28:22 -04:00
|
|
|
chunkNames: 'chunks/[name].[hash]',
|
|
|
|
|
define: {
|
2024-12-14 17:08:29 -05:00
|
|
|
'process.env.NODE_ENV': '"production"', // required by Floating UI
|
2023-06-07 13:28:22 -04:00
|
|
|
},
|
|
|
|
|
bundle: true,
|
|
|
|
|
splitting: true,
|
2024-09-11 10:25:42 -04:00
|
|
|
minify: false,
|
2024-12-16 14:05:29 -05:00
|
|
|
plugins: [replace({ __WEBAWESOME_VERSION__: version })],
|
2024-12-12 12:30:13 -05:00
|
|
|
loader: {
|
2024-12-14 17:08:29 -05:00
|
|
|
'.css': 'text',
|
|
|
|
|
},
|
2023-06-12 11:39:56 -04:00
|
|
|
};
|
2023-06-12 10:54:33 -04:00
|
|
|
|
2024-09-11 10:25:42 -04:00
|
|
|
const unbundledConfig = {
|
|
|
|
|
...config,
|
|
|
|
|
splitting: true,
|
|
|
|
|
treeShaking: true,
|
|
|
|
|
// Don't inline libraries like Lit etc.
|
|
|
|
|
packages: 'external',
|
2024-12-14 17:08:29 -05:00
|
|
|
outdir: distDir,
|
2024-09-11 10:25:42 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (isDeveloping) {
|
|
|
|
|
buildContexts.bundledContext = await esbuild.context(config);
|
|
|
|
|
buildContexts.unbundledContext = await esbuild.context(unbundledConfig);
|
|
|
|
|
|
|
|
|
|
await buildContexts.bundledContext.rebuild();
|
|
|
|
|
await buildContexts.unbundledContext.rebuild();
|
|
|
|
|
} else {
|
|
|
|
|
// One-time build for production
|
|
|
|
|
await esbuild.build(config);
|
|
|
|
|
await esbuild.build(unbundledConfig);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
spinner.fail();
|
|
|
|
|
console.log(chalk.red(`\n${error}`));
|
2025-01-10 13:29:14 -05:00
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2023-06-13 11:48:17 -04:00
|
|
|
}
|
2023-06-07 14:12:34 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
spinner.succeed();
|
2023-06-07 13:28:22 -04:00
|
|
|
}
|
2021-10-13 17:12:50 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
/**
|
|
|
|
|
* Incrementally rebuilds the source files. Must be called only after `generateBundle()` has been called.
|
|
|
|
|
*/
|
|
|
|
|
async function regenerateBundle() {
|
2023-12-07 16:30:37 -05:00
|
|
|
try {
|
2024-04-17 11:20:27 -04:00
|
|
|
spinner.start('Re-bundling with esbuild');
|
2024-09-11 10:25:42 -04:00
|
|
|
await buildContexts.bundledContext.rebuild();
|
|
|
|
|
await buildContexts.unbundledContext.rebuild();
|
2024-04-17 11:20:27 -04:00
|
|
|
} catch (error) {
|
|
|
|
|
spinner.fail();
|
|
|
|
|
console.log(chalk.red(`\n${error}`));
|
2025-01-10 13:29:14 -05:00
|
|
|
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2021-10-13 17:12:50 -04:00
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
|
|
|
|
|
spinner.succeed();
|
2023-06-07 13:28:22 -04:00
|
|
|
}
|
2021-10-13 17:12:50 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
/**
|
|
|
|
|
* Generates the documentation site.
|
|
|
|
|
*/
|
|
|
|
|
async function generateDocs() {
|
2025-03-18 13:04:24 -04:00
|
|
|
/**
|
|
|
|
|
* Used by the webawesome-app to skip doc generation since it will do its own.
|
|
|
|
|
*/
|
|
|
|
|
if (process.env.SKIP_ELEVENTY === 'true') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
spinner.start('Writing the docs');
|
|
|
|
|
|
2024-06-18 12:56:55 -04:00
|
|
|
const args = [];
|
|
|
|
|
if (isDeveloping) args.push('--develop');
|
|
|
|
|
|
2025-01-10 13:29:14 -05:00
|
|
|
let output;
|
|
|
|
|
try {
|
|
|
|
|
// 11ty
|
|
|
|
|
output = (await runScript(join(__dirname, 'docs.js'), args))
|
|
|
|
|
// Cleanup the output
|
|
|
|
|
.replace('[11ty]', '')
|
|
|
|
|
.replace(' seconds', 's')
|
|
|
|
|
.replace(/\(.*?\)/, '')
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.trim();
|
|
|
|
|
|
|
|
|
|
// Copy dist (production only)
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
await copy(cdnDir, join(siteDir, 'dist'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('\n\n' + chalk.red(error) + '\n');
|
2024-04-17 15:11:10 -04:00
|
|
|
|
2025-01-10 13:29:14 -05:00
|
|
|
spinner.fail(chalk.red(`Error while writing the docs.`));
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
2021-02-26 09:09:13 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Initial build
|
|
|
|
|
await buildAll();
|
2023-06-07 13:28:22 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
if (!isDeveloping) {
|
|
|
|
|
console.log(); // just a newline for readability
|
|
|
|
|
}
|
2023-06-07 13:28:22 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Launch the dev server
|
|
|
|
|
if (isDeveloping) {
|
|
|
|
|
spinner.start('Launching the dev server');
|
|
|
|
|
|
|
|
|
|
const bs = browserSync.create();
|
|
|
|
|
const port = await getPort({ port: portNumbers(4000, 4999) });
|
|
|
|
|
const url = `http://localhost:${port}/`;
|
|
|
|
|
const reload = () => {
|
|
|
|
|
spinner.start('Reloading browser');
|
|
|
|
|
bs.reload();
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
};
|
2023-06-07 13:28:22 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Launch browser sync
|
|
|
|
|
bs.init(
|
|
|
|
|
{
|
|
|
|
|
startPath: '/',
|
|
|
|
|
port,
|
|
|
|
|
logLevel: 'silent',
|
|
|
|
|
logPrefix: '[webawesome]',
|
|
|
|
|
logFileChanges: true,
|
|
|
|
|
notify: false,
|
|
|
|
|
single: false,
|
|
|
|
|
ghostMode: false,
|
|
|
|
|
server: {
|
|
|
|
|
baseDir: siteDir,
|
|
|
|
|
routes: {
|
2024-12-14 17:08:29 -05:00
|
|
|
'/dist/': './dist-cdn/',
|
|
|
|
|
},
|
2024-04-17 11:20:27 -04:00
|
|
|
},
|
|
|
|
|
callbacks: {
|
|
|
|
|
ready: (_err, instance) => {
|
|
|
|
|
// 404 errors
|
2024-12-05 16:34:07 -05:00
|
|
|
instance.addMiddleware('*', async (req, res) => {
|
2024-04-17 11:20:27 -04:00
|
|
|
if (req.url.toLowerCase().endsWith('.svg')) {
|
|
|
|
|
// Make sure SVGs error out in dev instead of serve the 404 page
|
|
|
|
|
res.writeHead(404);
|
|
|
|
|
} else {
|
2024-12-05 16:34:07 -05:00
|
|
|
try {
|
|
|
|
|
const notFoundTemplate = await readFile(join(siteDir, '404.html'), 'utf-8');
|
|
|
|
|
res.writeHead(404);
|
|
|
|
|
res.write(notFoundTemplate || 'Page Not Found');
|
|
|
|
|
} catch {
|
|
|
|
|
// We're probably disconnected for some reason, so fail gracefully
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.end();
|
|
|
|
|
});
|
2024-12-14 17:08:29 -05:00
|
|
|
},
|
|
|
|
|
},
|
2024-04-17 11:20:27 -04:00
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
spinner.succeed();
|
|
|
|
|
console.log(`\nThe dev server is running at ${chalk.cyan(url)}\n`);
|
2024-12-14 17:08:29 -05:00
|
|
|
},
|
2024-04-17 11:20:27 -04:00
|
|
|
);
|
2023-06-07 13:28:22 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Rebuild and reload when source files change
|
|
|
|
|
bs.watch('src/**/!(*.test).*').on('change', async filename => {
|
|
|
|
|
spinner.info(`File modified ${chalk.gray(`(${relative(rootDir, filename)})`)}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const isTestFile = filename.includes('.test.ts');
|
|
|
|
|
const isCssStylesheet = filename.includes('.css');
|
|
|
|
|
const isComponent =
|
2024-11-18 16:54:39 -05:00
|
|
|
filename.includes('components/') && filename.includes('.ts') && !isCssStylesheet && !isTestFile;
|
2024-04-17 11:20:27 -04:00
|
|
|
|
|
|
|
|
// Re-bundle when relevant files change
|
2024-12-18 13:05:08 -05:00
|
|
|
if (isTestFile) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-17 13:22:10 -05:00
|
|
|
|
2025-05-08 16:05:52 -04:00
|
|
|
if (filename.includes('src/') && /\.(js|ts|css)$/.test(filename)) {
|
|
|
|
|
await regenerateBundle();
|
|
|
|
|
}
|
2023-06-12 10:54:33 -04:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Copy stylesheets when CSS files change
|
|
|
|
|
if (isCssStylesheet) {
|
|
|
|
|
await generateStyles();
|
|
|
|
|
}
|
2023-12-07 15:59:53 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Regenerate metadata when components change
|
|
|
|
|
if (isComponent) {
|
|
|
|
|
await generateManifest();
|
|
|
|
|
}
|
2024-02-05 11:02:14 -05:00
|
|
|
|
2024-12-17 13:22:10 -05:00
|
|
|
// This needs to be outside of "isComponent" check because SSR needs to run on CSS files too.
|
2025-05-08 16:05:52 -04:00
|
|
|
if (filename.includes('/docs/')) {
|
|
|
|
|
await generateDocs();
|
|
|
|
|
}
|
2024-12-17 13:22:10 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
reload();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(chalk.red(err));
|
2025-01-10 13:29:14 -05:00
|
|
|
|
|
|
|
|
if (!isDeveloping) {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
2024-04-17 11:20:27 -04:00
|
|
|
}
|
|
|
|
|
});
|
2024-02-05 11:02:14 -05:00
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
// Rebuild the docs and reload when the docs change
|
|
|
|
|
bs.watch(`${docsDir}/**/*.*`).on('change', async filename => {
|
|
|
|
|
spinner.info(`File modified ${chalk.gray(`(${relative(rootDir, filename)})`)}`);
|
|
|
|
|
await generateDocs();
|
|
|
|
|
reload();
|
|
|
|
|
});
|
2023-06-07 13:28:22 -04:00
|
|
|
}
|
|
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
//
|
|
|
|
|
// Cleanup everything when the process terminates
|
|
|
|
|
//
|
|
|
|
|
function terminate() {
|
2024-09-11 10:25:42 -04:00
|
|
|
// dispose of contexts.
|
|
|
|
|
Object.values(buildContexts).forEach(context => context?.dispose?.());
|
2024-04-17 11:20:27 -04:00
|
|
|
|
|
|
|
|
if (spinner) {
|
|
|
|
|
spinner.stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process.exit();
|
2023-06-07 13:28:22 -04:00
|
|
|
}
|
|
|
|
|
|
2024-04-17 11:20:27 -04:00
|
|
|
process.on('SIGINT', terminate);
|
|
|
|
|
process.on('SIGTERM', terminate);
|