mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
oh no where did astro go
This commit is contained in:
499
scripts/build.js
499
scripts/build.js
@@ -1,280 +1,327 @@
|
||||
import { deleteAsync } from 'del';
|
||||
import { exec, spawn } from 'child_process';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import { distDir, docsDir, rootDir, runScript, siteDir } from './utils.js';
|
||||
import { execSync } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { globby } from 'globby';
|
||||
import { mkdir, readFile } from 'fs/promises';
|
||||
import { replace } from 'esbuild-plugin-replace';
|
||||
import browserSync from 'browser-sync';
|
||||
import chalk from 'chalk';
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import copy from 'recursive-copy';
|
||||
import esbuild from 'esbuild';
|
||||
import fs from 'fs/promises';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import util from 'util';
|
||||
import * as path from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import { replace } from 'esbuild-plugin-replace';
|
||||
import { dev, build } from 'astro';
|
||||
import chokidar from 'chokidar';
|
||||
|
||||
const { serve } = commandLineArgs([{ name: 'serve', type: Boolean }]);
|
||||
const outdir = 'dist';
|
||||
const cdndir = 'cdn';
|
||||
const sitedir = '_site';
|
||||
const execPromise = util.promisify(exec);
|
||||
let buildResults = [];
|
||||
|
||||
const bundleDirectories = [cdndir, outdir];
|
||||
let packageData = JSON.parse(readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
|
||||
const shoelaceVersion = JSON.stringify(packageData.version.toString());
|
||||
import ora from 'ora';
|
||||
import process from 'process';
|
||||
|
||||
//
|
||||
// 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.
|
||||
// TODO - CDN dist and unbundled dist
|
||||
//
|
||||
async function buildTheDocs(watch = false) {
|
||||
let args = ['astro', 'build'];
|
||||
|
||||
if (watch) {
|
||||
args.pop();
|
||||
args.push('dev');
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const isDeveloping = process.argv.includes('--develop');
|
||||
const iconDir = join(distDir, 'assets/icons');
|
||||
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());
|
||||
let buildContext;
|
||||
|
||||
console.log(process.cwd());
|
||||
/**
|
||||
* Runs the full build.
|
||||
*/
|
||||
async function buildAll() {
|
||||
const start = Date.now();
|
||||
|
||||
// Rebuild and reload when source files change
|
||||
chokidar.watch('src/**/!(*.test).*').on('change', async filename => {
|
||||
console.log('[build] File changed: ', filename);
|
||||
try {
|
||||
await cleanup();
|
||||
await generateManifest();
|
||||
await generateReactWrappers();
|
||||
await generateTypes();
|
||||
await generateStyles();
|
||||
await generateBundle();
|
||||
await generateDocs();
|
||||
|
||||
try {
|
||||
const isTheme = /^src\/themes/.test(filename);
|
||||
const isStylesheet = /(\.css|\.styles\.ts)$/.test(filename);
|
||||
|
||||
// Rebuild the source
|
||||
const rebuildResults = buildResults.map(result => result.rebuild());
|
||||
await Promise.all(rebuildResults);
|
||||
|
||||
// Rebuild stylesheets when a theme file changes
|
||||
if (isTheme) {
|
||||
await Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
return execPromise(`node scripts/make-themes.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Rebuild metadata (but not when styles are changed)
|
||||
if (!isStylesheet) {
|
||||
await Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
return execPromise(`node scripts/make-metadata.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const siteDistDir = path.join(process.cwd(), 'docs', 'public', 'dist');
|
||||
// await deleteAsync(siteDistDir);
|
||||
|
||||
// We copy the CDN build because that has everything bundled. Yes this looks weird.
|
||||
// But if we do "/cdn" it requires changes all the docs to do /cdn instead of /dist.
|
||||
console.log(`COPYING ${cdndir} to ${siteDistDir}`);
|
||||
await copy(cdndir, siteDistDir, { overwrite: true });
|
||||
} catch (err) {
|
||||
console.error(chalk.red(err), '\n');
|
||||
}
|
||||
});
|
||||
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}`));
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const child = spawn('npx', args, {
|
||||
stdio: 'pipe',
|
||||
cwd: 'docs',
|
||||
shell: true // for Windows
|
||||
});
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stdout.on('data', data => {
|
||||
console.log(data);
|
||||
});
|
||||
child.stderr.on('data', data => {
|
||||
console.error(data);
|
||||
});
|
||||
child.on('error', error => reject(error));
|
||||
child.on('close', () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Builds the source with esbuild.
|
||||
//
|
||||
async function buildTheSource() {
|
||||
const alwaysExternal = ['@lit/react', 'react'];
|
||||
/** Empties the dist directory. */
|
||||
async function cleanup() {
|
||||
spinner.start('Cleaning up dist');
|
||||
|
||||
const cdnConfig = {
|
||||
await deleteAsync(distDir);
|
||||
await mkdir(distDir, { recursive: true });
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates React wrappers for all components.
|
||||
*/
|
||||
function generateReactWrappers() {
|
||||
spinner.start('Generating React wrappers');
|
||||
|
||||
try {
|
||||
execSync(`node scripts/make-react.js --outdir "${distDir}"`, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error(`\n\n${error.message}`);
|
||||
}
|
||||
spinner.succeed();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies theme stylesheets to the dist.
|
||||
*/
|
||||
async function generateStyles() {
|
||||
spinner.start('Copying stylesheets');
|
||||
|
||||
await copy(join(rootDir, 'src/themes'), join(distDir, 'themes'), { overwrite: true });
|
||||
|
||||
spinner.succeed();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs TypeScript to generate types.
|
||||
*/
|
||||
function generateTypes() {
|
||||
spinner.start('Running the TypeScript compiler');
|
||||
|
||||
try {
|
||||
execSync(`tsc --project ./tsconfig.prod.json --outdir "${distDir}"`);
|
||||
} catch (error) {
|
||||
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');
|
||||
|
||||
const config = {
|
||||
format: 'esm',
|
||||
target: 'es2017',
|
||||
target: 'es2020',
|
||||
entryPoints: [
|
||||
//
|
||||
// NOTE: Entry points must be mapped in package.json > exports, otherwise users won't be able to import them!
|
||||
// IMPORTANT: Entry points MUST be mapped in package.json => exports
|
||||
//
|
||||
// The whole shebang
|
||||
// Utilities
|
||||
'./src/webawesome.ts',
|
||||
// The auto-loader
|
||||
'./src/autoloader.ts',
|
||||
// Components
|
||||
// Autoloader + utilities
|
||||
'./src/webawesome.loader.ts',
|
||||
// Individual 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: cdndir,
|
||||
outdir: distDir,
|
||||
chunkNames: 'chunks/[name].[hash]',
|
||||
define: {
|
||||
// Floating UI requires this to be set
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
'process.env.NODE_ENV': '"production"' // required by Floating UI
|
||||
},
|
||||
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/react though!
|
||||
//
|
||||
external: alwaysExternal,
|
||||
splitting: true,
|
||||
plugins: [
|
||||
replace({
|
||||
__WEBAWESOME_VERSION__: shoelaceVersion
|
||||
})
|
||||
]
|
||||
plugins: [replace({ __WEBAWESOME_VERSION__: version })]
|
||||
};
|
||||
|
||||
const npmConfig = {
|
||||
...cdnConfig,
|
||||
external: undefined,
|
||||
minify: false,
|
||||
packages: 'external',
|
||||
outdir
|
||||
};
|
||||
|
||||
if (serve) {
|
||||
// Use the context API to allow incremental dev builds
|
||||
const contexts = await Promise.all([esbuild.context(cdnConfig), esbuild.context(npmConfig)]);
|
||||
await Promise.all(contexts.map(context => context.rebuild()));
|
||||
return contexts;
|
||||
if (isDeveloping) {
|
||||
// Incremental builds for dev
|
||||
buildContext = await esbuild.context(config);
|
||||
await buildContext.rebuild();
|
||||
} else {
|
||||
// Use the standard API for production builds
|
||||
return await Promise.all([esbuild.build(cdnConfig), esbuild.build(npmConfig)]);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Called on SIGINT or SIGTERM to cleanup the build and child processes.
|
||||
//
|
||||
function exit() {
|
||||
buildResults.forEach(result => {
|
||||
if (result.dispose) {
|
||||
result.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Helper function to cleanly log tasks
|
||||
//
|
||||
async function nextTask(label, action) {
|
||||
function clearLine() {
|
||||
if (process.stdout.isTTY) {
|
||||
process.stdout.clearLine();
|
||||
process.stdout.cursorTo(0);
|
||||
} else {
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
// One-time build for production
|
||||
esbuild.build(config);
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Incrementally rebuilds the source files. Must be called only after `generateBundle()` has been called.
|
||||
*/
|
||||
async function regenerateBundle() {
|
||||
try {
|
||||
process.stdout.write(`${chalk.yellow('•')} ${label}`);
|
||||
await action();
|
||||
clearLine();
|
||||
process.stdout.write(`${chalk.green('✔')} ${label}\n`);
|
||||
} catch (err) {
|
||||
clearLine();
|
||||
process.stdout.write(`${chalk.red('✘')} ${label}\n\n`);
|
||||
if (err.stdout) process.stdout.write(`${chalk.red(err.stdout)}\n`);
|
||||
if (err.stderr) process.stderr.write(`${chalk.red(err.stderr)}\n`);
|
||||
exit();
|
||||
spinner.start('Re-bundling with esbuild');
|
||||
await buildContext.rebuild();
|
||||
} catch (error) {
|
||||
spinner.fail();
|
||||
console.log(chalk.red(`\n${error}`));
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
|
||||
await nextTask('Cleaning up the previous build', async () => {
|
||||
await Promise.all([deleteAsync(sitedir), ...bundleDirectories.map(dir => deleteAsync(dir))]);
|
||||
await fs.mkdir(outdir, { recursive: true });
|
||||
});
|
||||
/**
|
||||
* Generates the documentation site.
|
||||
*/
|
||||
async function generateDocs() {
|
||||
spinner.start('Writing the docs');
|
||||
|
||||
await nextTask('Generating component metadata', () => {
|
||||
return Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
return execPromise(`node scripts/make-metadata.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
});
|
||||
// 11ty
|
||||
const output = (await runScript(join(__dirname, 'docs.js'), isDeveloping ? ['--develop'] : undefined))
|
||||
// Cleanup the output
|
||||
.replace('[11ty]', '')
|
||||
.replace(' seconds', 's')
|
||||
.replace(/\(.*?\)/, '')
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
|
||||
await nextTask('Wrapping components for React', () => {
|
||||
return execPromise(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
});
|
||||
// Copy assets
|
||||
await copy(join(docsDir, 'assets'), join(siteDir, 'assets'), { overwrite: true });
|
||||
|
||||
await nextTask('Generating themes', () => {
|
||||
return execPromise(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
});
|
||||
spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`);
|
||||
}
|
||||
|
||||
await nextTask('Running the TypeScript compiler', () => {
|
||||
return execPromise(`tsc --project ./tsconfig.prod.json --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
});
|
||||
// Initial build
|
||||
await buildAll();
|
||||
|
||||
// Copy the above steps to the CDN directory directly so we don't need to twice the work for nothing
|
||||
await nextTask(`Copying CDN files to "${cdndir}"`, async () => {
|
||||
await deleteAsync(cdndir);
|
||||
await copy(outdir, cdndir);
|
||||
});
|
||||
|
||||
await nextTask('Building source files', async () => {
|
||||
buildResults = await buildTheSource();
|
||||
});
|
||||
|
||||
// Copy the CDN build to the docs (prod only; we use a virtual directory in dev)
|
||||
await nextTask(`Copying the build to "${sitedir}"`, async () => {
|
||||
const siteDistDir = path.join('docs', 'public', 'dist');
|
||||
await deleteAsync(siteDistDir);
|
||||
|
||||
// We copy the CDN build because that has everything bundled. Yes this looks weird.
|
||||
// But if we do "/cdn" it requires changes all the docs to do /cdn instead of /dist.
|
||||
await copy(cdndir, siteDistDir);
|
||||
});
|
||||
if (!isDeveloping) {
|
||||
console.log(); // just a newline for readability
|
||||
}
|
||||
|
||||
// Launch the dev server
|
||||
if (serve) {
|
||||
// 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 () => await buildTheDocs(true));
|
||||
}
|
||||
if (isDeveloping) {
|
||||
spinner.start('Launching the dev server');
|
||||
|
||||
// Build for production
|
||||
if (!serve) {
|
||||
await nextTask('Building the docs', async () => {
|
||||
await buildTheDocs();
|
||||
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();
|
||||
};
|
||||
|
||||
// Launch browser sync
|
||||
bs.init(
|
||||
{
|
||||
startPath: '/',
|
||||
port,
|
||||
logLevel: 'silent',
|
||||
logPrefix: '[webawesome]',
|
||||
logFileChanges: true,
|
||||
notify: false,
|
||||
single: false,
|
||||
ghostMode: false,
|
||||
server: {
|
||||
baseDir: siteDir,
|
||||
routes: {
|
||||
'/dist': './dist'
|
||||
}
|
||||
},
|
||||
callbacks: {
|
||||
ready: (_err, instance) => {
|
||||
// 404 errors
|
||||
instance.addMiddleware('*', (req, res) => {
|
||||
if (req.url.toLowerCase().endsWith('.svg')) {
|
||||
// Make sure SVGs error out in dev instead of serve the 404 page
|
||||
res.writeHead(404);
|
||||
} else {
|
||||
res.writeHead(302, { location: '/404.html' });
|
||||
}
|
||||
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {
|
||||
spinner.succeed();
|
||||
console.log(`\nThe dev server is running at ${chalk.cyan(url)}\n`);
|
||||
}
|
||||
);
|
||||
|
||||
// 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 isJsStylesheet = filename.includes('.styles.ts');
|
||||
const isCssStylesheet = filename.includes('.css');
|
||||
const isComponent =
|
||||
filename.includes('components/') &&
|
||||
filename.includes('.ts') &&
|
||||
!isJsStylesheet &&
|
||||
!isCssStylesheet &&
|
||||
!isTestFile;
|
||||
|
||||
// Re-bundle when relevant files change
|
||||
if (!isTestFile && !isCssStylesheet) {
|
||||
await regenerateBundle();
|
||||
}
|
||||
|
||||
// Copy stylesheets when CSS files change
|
||||
if (isCssStylesheet) {
|
||||
await generateStyles();
|
||||
}
|
||||
|
||||
// Regenerate metadata when components change
|
||||
if (isComponent) {
|
||||
await generateManifest();
|
||||
await generateDocs();
|
||||
}
|
||||
|
||||
reload();
|
||||
} catch (err) {
|
||||
console.error(chalk.red(err));
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup on exit
|
||||
process.on('SIGINT', exit);
|
||||
process.on('SIGTERM', exit);
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error(err);
|
||||
exit();
|
||||
});
|
||||
//
|
||||
// Cleanup everything when the process terminates
|
||||
//
|
||||
function terminate() {
|
||||
if (buildContext) {
|
||||
buildContext.dispose();
|
||||
}
|
||||
|
||||
if (spinner) {
|
||||
spinner.stop();
|
||||
}
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
process.on('SIGINT', terminate);
|
||||
process.on('SIGTERM', terminate);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# https://github.com/ds300/patch-package/issues/326#issuecomment-1676204753
|
||||
# For each file in the format <dependency_name>+<version>.patch
|
||||
for PATCH_FILE in ./patches/*+*.patch; do
|
||||
# Check if file exists to avoid issues with the wildcard in case no files match
|
||||
if [[ -f "$PATCH_FILE" ]]; then
|
||||
# Extract dependency name
|
||||
DEP_NAME=$(basename "$PATCH_FILE" | cut -d'+' -f1)
|
||||
|
||||
# Delete the dependency from node_modules
|
||||
if [[ -d "node_modules/$DEP_NAME" ]]; then
|
||||
echo "Deleting node_modules/$DEP_NAME ..."
|
||||
rm -rf "node_modules/$DEP_NAME"
|
||||
else
|
||||
echo "$DEP_NAME not found in node_modules!"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
15
scripts/docs.js
Normal file
15
scripts/docs.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { deleteAsync } from 'del';
|
||||
import { docsDir, siteDir } from './utils.js';
|
||||
import { join } from 'path';
|
||||
import Eleventy from '@11ty/eleventy';
|
||||
|
||||
const elev = new Eleventy(docsDir, siteDir, {
|
||||
quietMode: true,
|
||||
configPath: join(docsDir, '.eleventy.js')
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
await deleteAsync(siteDir);
|
||||
|
||||
// Write it
|
||||
await elev.write();
|
||||
@@ -1,10 +0,0 @@
|
||||
//
|
||||
// This script runs the Custom Elements Manifest analyzer to generate custom-elements.json
|
||||
//
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import commandLineArgs from 'command-line-args';
|
||||
|
||||
const { outdir } = commandLineArgs({ name: 'outdir', type: String });
|
||||
|
||||
execSync(`cem analyze --litelement --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
@@ -23,7 +23,7 @@ for await (const component of components) {
|
||||
const tagWithoutPrefix = component.tagName.replace(/^wa-/, '');
|
||||
const componentDir = path.join(reactDir, tagWithoutPrefix);
|
||||
const componentFile = path.join(componentDir, 'index.ts');
|
||||
const importPath = component.path.replace(/\.js$/, '.component.js');
|
||||
const importPath = component.path.replace(/\.js$/, '.js');
|
||||
const eventImports = (component.events || [])
|
||||
.map(event => `import type { ${event.eventName} } from '../../events/events.js';`)
|
||||
.join('\n');
|
||||
@@ -50,7 +50,6 @@ for await (const component of components) {
|
||||
${eventExports}
|
||||
|
||||
const tagName = '${component.tagName}'
|
||||
Component.define('${component.tagName}')
|
||||
|
||||
${jsDoc}
|
||||
const reactWrapper = createComponent({
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
//
|
||||
// This script bakes and copies themes, then generates a corresponding Lit stylesheet in dist/themes
|
||||
//
|
||||
import chalk from 'chalk';
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import fs from 'fs';
|
||||
import { mkdirSync } from 'fs';
|
||||
import { globbySync } from 'globby';
|
||||
import path from 'path';
|
||||
import prettier from 'prettier';
|
||||
import stripComments from 'strip-css-comments';
|
||||
|
||||
const { outdir } = commandLineArgs({ name: 'outdir', type: String });
|
||||
const files = globbySync('./src/themes/**/[!_]*.css');
|
||||
const filesToEmbed = globbySync('./src/themes/**/_*.css');
|
||||
const themesDir = path.join(outdir, 'themes');
|
||||
const embeds = {};
|
||||
|
||||
mkdirSync(themesDir, { recursive: true });
|
||||
|
||||
// Gather an object containing the source of all files named "_filename.css" so we can embed them later
|
||||
filesToEmbed.forEach(file => {
|
||||
embeds[path.basename(file)] = fs.readFileSync(file, 'utf8');
|
||||
});
|
||||
|
||||
// Loop through each theme file, copying the .css and generating a .js version for Lit users
|
||||
files.forEach(async file => {
|
||||
let source = fs.readFileSync(file, 'utf8');
|
||||
|
||||
// If the source has "/* _filename.css */" in it, replace it with the embedded styles
|
||||
Object.keys(embeds).forEach(key => {
|
||||
source = source.replace(`/* ${key} */`, embeds[key]);
|
||||
});
|
||||
|
||||
const css = await prettier.format(stripComments(source), {
|
||||
parser: 'css'
|
||||
});
|
||||
|
||||
let js = await prettier.format(
|
||||
`
|
||||
import { css } from 'lit';
|
||||
|
||||
export default css\`
|
||||
${css}
|
||||
\`;
|
||||
`,
|
||||
{ parser: 'babel-ts' }
|
||||
);
|
||||
|
||||
let dTs = await prettier.format(
|
||||
`
|
||||
declare const _default: import("lit").CSSResult;
|
||||
export default _default;
|
||||
`,
|
||||
{ parser: 'babel-ts' }
|
||||
);
|
||||
|
||||
const cssFile = path.join(themesDir, path.basename(file));
|
||||
const jsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.js'));
|
||||
const dTsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.d.ts'));
|
||||
|
||||
fs.writeFileSync(cssFile, css, 'utf8');
|
||||
fs.writeFileSync(jsFile, js, 'utf8');
|
||||
fs.writeFileSync(dTsFile, dTs, 'utf8');
|
||||
});
|
||||
@@ -33,11 +33,6 @@ export default function (plop) {
|
||||
{
|
||||
type: 'add',
|
||||
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.ts',
|
||||
templateFile: 'templates/component/define.hbs'
|
||||
},
|
||||
{
|
||||
type: 'add',
|
||||
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.component.ts',
|
||||
templateFile: 'templates/component/component.hbs'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -40,3 +40,9 @@ export default class {{ properCase tag }} extends WebAwesomeElement {
|
||||
return html` <slot></slot> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'{{ tag }}': {{ properCase tag }};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import {{ properCase tag }} from './{{ tagWithoutPrefix tag }}.component.js';
|
||||
|
||||
export * from './{{ tagWithoutPrefix tag }}.component.js';
|
||||
export default {{ properCase tag }};
|
||||
|
||||
{{ properCase tag }}.define('{{ tag }}');
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'{{ tag }}': {{ properCase tag }};
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ meta:
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html:preview
|
||||
```html {.example}
|
||||
<{{ tag }}></{{ tag }}>
|
||||
```
|
||||
|
||||
|
||||
57
scripts/utils.js
Normal file
57
scripts/utils.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import childProcess from 'child_process';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Helpful directories
|
||||
export const rootDir = dirname(__dirname);
|
||||
export const distDir = join(rootDir, 'dist');
|
||||
export const docsDir = join(rootDir, 'docs');
|
||||
export const siteDir = join(rootDir, '_site');
|
||||
|
||||
/**
|
||||
* Runs a script and returns a promise that resolves with the content of stdout when the script exits or rejects with
|
||||
* the content of stderr when the script exits with an error.
|
||||
*/
|
||||
export function runScript(scriptPath, args = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = childProcess.fork(scriptPath, args, { silent: true });
|
||||
let wasInvoked = false;
|
||||
let stderr = '';
|
||||
let stdout = '';
|
||||
|
||||
child.on('error', err => {
|
||||
if (wasInvoked) {
|
||||
return;
|
||||
}
|
||||
|
||||
wasInvoked = true;
|
||||
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// Capture output
|
||||
child.stderr.on('data', data => (stderr += data));
|
||||
child.stdout.on('data', data => (stdout += data));
|
||||
|
||||
// execute the callback once the process has finished running
|
||||
child.on('exit', code => {
|
||||
if (wasInvoked) {
|
||||
return;
|
||||
}
|
||||
|
||||
wasInvoked = true;
|
||||
|
||||
if (code === 0) {
|
||||
// The process exited normally
|
||||
resolve(stdout.trim());
|
||||
} else {
|
||||
// An error code was received
|
||||
reject(new Error(stderr));
|
||||
}
|
||||
|
||||
child.unref();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user