Shoemaker rework

This commit is contained in:
Cory LaViska
2021-02-26 09:09:13 -05:00
parent 4eeeffc493
commit fe45f2159f
202 changed files with 6778 additions and 19633 deletions

140
scripts/build.cjs Normal file
View File

@@ -0,0 +1,140 @@
//
// Builds the project. To spin up a dev server, pass the --serve flag.
//
const bs = require('browser-sync').create();
const chalk = require('chalk');
const commandLineArgs = require('command-line-args');
const copy = require('recursive-copy');
const del = require('del');
const esbuild = require('esbuild');
const execSync = require('child_process').execSync;
const getPort = require('get-port');
const glob = require('tiny-glob');
const inlineImportPlugin = require('esbuild-plugin-inline-import');
const path = require('path');
const sass = require('sass');
const sassPlugin = require('esbuild-plugin-sass');
const { build } = require('esbuild');
const options = commandLineArgs({
name: 'serve',
type: Boolean
});
execSync(`rm -rf ./dist`, { stdio: 'inherit' });
execSync('node scripts/make-metadata.cjs', { stdio: 'inherit' });
execSync('node scripts/make-icons.cjs', { stdio: 'inherit' });
(async () => {
const entryPoints = [
// The main dist
'./src/shoelace.ts',
// The whole shebang
'./src/all.shoelace.ts',
// Components
...(await glob('./src/components/**/*.ts')),
// Public utilities
...(await glob('./src/utilities/**/*.ts')),
// Theme stylesheets
...(await glob('./src/themes/**/*.ts'))
];
const buildResult = await esbuild
.build({
format: 'esm',
target: 'es2017',
entryPoints,
outdir: './dist',
incremental: options.serve,
define: {
// Popper.js expects this to be set
'process.env.NODE_ENV': '"production"'
},
bundle: true,
splitting: true,
plugins: [
// Run inline style imports through Sass
inlineImportPlugin({
filter: /^sass:/,
transform: async (contents, args) => {
return await new Promise((resolve, reject) => {
sass.render(
{
data: contents,
includePaths: [path.dirname(args.path)]
},
(err, result) => {
if (err) {
reject(err);
return;
}
resolve(result.css.toString());
}
);
});
}
}),
// Run all other stylesheets through Sass
sassPlugin()
]
})
.catch(err => {
console.error(chalk.red(err));
process.exit(1);
});
// Create the docs distribution by copying dist into docs/dist. This is what powers the website. It can't exist in dev
// because it will conflict with browser sync's routing to the actual dist dir.
await del('./docs/dist');
if (!options.serve) {
await copy('./dist', './docs/dist');
}
console.log(chalk.green('The build has been generated! 📦'));
if (options.serve) {
const port = await getPort({
port: getPort.makeRange(4000, 4999)
});
console.log(chalk.cyan(`\nLaunching the Shoelace dev server at http://localhost:${port}! 🥾\n`));
// Launch browser sync
bs.init({
startPath: '/',
port,
logLevel: 'silent',
logPrefix: '[shoelace]',
logFileChanges: true,
notify: false,
server: {
baseDir: 'docs',
routes: {
'/dist': './dist'
}
}
});
// Rebuild and reload when source files change
bs.watch(['src/**/*']).on('change', async filename => {
console.log(`Source file changed - ${filename}`);
// NOTE: we don't run TypeDoc on every change because it's quite heavy, so changes to the docs won't be included
// until the next time the build script runs.
buildResult
.rebuild()
.then(() => bs.reload())
.catch(err => console.error(chalk.red(err)));
});
// Reload without rebuilding when the docs change
bs.watch(['docs/**/*']).on('change', filename => {
console.log(`Docs file changed - ${filename}`);
bs.reload();
});
// Cleanup on exit
process.on('SIGTERM', () => buildResult.rebuild.dispose());
}
})();

68
scripts/make-icons.cjs Normal file
View File

@@ -0,0 +1,68 @@
//
// This script downloads and generates icons and icon metadata.
//
const Promise = require('bluebird');
const promisify = require('util').promisify;
const chalk = require('chalk');
const copy = require('recursive-copy');
const del = require('del');
const download = require('download');
const mkdirp = require('mkdirp');
const fm = require('front-matter');
const fs = require('fs').promises;
const glob = promisify(require('glob'));
const path = require('path');
const baseDir = path.dirname(__dirname);
const iconDir = './dist/assets/icons';
let numIcons = 0;
(async () => {
try {
const version = require('bootstrap-icons/package').version;
const srcPath = `./.cache/icons/icons-${version}`;
const url = `https://github.com/twbs/icons/archive/v${version}.zip`;
try {
await fs.stat(`${srcPath}/LICENSE.md`);
console.log(chalk.cyan('Generating icons from cache'));
} catch {
// Download the source from GitHub (since not everything is published to NPM)
console.log(chalk.cyan(`Downloading and extracting Bootstrap Icons ${version} 📦`));
await download(url, './.cache/icons', { extract: true });
}
// Copy icons
console.log(chalk.cyan(`Copying icons and license`));
await del([iconDir]);
await mkdirp(iconDir);
await Promise.all([
copy(`${srcPath}/icons`, iconDir),
copy(`${srcPath}/LICENSE.md`, path.join(iconDir, 'LICENSE.md')),
copy(`${srcPath}/bootstrap-icons.svg`, './docs/assets/icons/sprite.svg', { overwrite: true })
]);
// Generate metadata
console.log(chalk.cyan(`Generating icon metadata`));
const files = await glob(`${srcPath}/docs/content/icons/**/*.md`);
const metadata = await Promise.map(files, async file => {
const name = path.basename(file, path.extname(file));
const data = fm(await fs.readFile(file, 'utf8')).attributes;
numIcons++;
return {
name,
title: data.title,
categories: data.categories,
tags: data.tags
};
});
await fs.writeFile(path.join(iconDir, 'icons.json'), JSON.stringify(metadata, null, 2), 'utf8');
console.log(chalk.green(`Successfully processed ${numIcons} icons ✨\n`));
} catch (err) {
console.error(err);
}
})();

168
scripts/make-metadata.cjs Normal file
View File

@@ -0,0 +1,168 @@
//
// This script runs TypeDoc and uses its output to generate components.json which is used for the docs.
//
const chalk = require('chalk');
const execSync = require('child_process').execSync;
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const package = require('../package.json');
const { parse } = require('comment-parser/lib');
function getTagName(className) {
return className.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`).replace(/^-/, '');
}
function getTypeInfo(item) {
let type = item.type.name || '';
if (item.type.type === 'union') {
const types = item.type.types.map(t => {
if (t.type === 'literal' || t.type === 'reference') {
type = `'${item.type.types.map(t => t.value).join(`' | '`)}'`;
}
if (t.type === 'intrinsic') {
type = item.type.types.map(t => t.name).join(' | ');
}
});
}
return type;
}
// Splits a string of tag text into a { name, description } object
function splitText(text) {
const shouldSplit = text.indexOf(' - ') > -1;
let name = '';
let description = '';
if (shouldSplit) {
const split = text.split(' - ');
name = split[0].trim();
description = split.slice(1).join(' - ').replace(/^- /, '');
} else {
description = text.trim().replace(/^-\s/, '');
}
return { name, description };
}
// Run typedoc
console.log(chalk.cyan('Generating type data with TypeDoc'));
mkdirp.sync('./.cache');
execSync(
'typedoc --json .cache/typedoc.json --entryPoints src/shoelace.ts --exclude "**/*+(index|.spec|.e2e).ts" --excludeExternals --excludeProtected --excludeInternal'
);
const data = JSON.parse(fs.readFileSync('.cache/typedoc.json', 'utf8'));
const modules = data.children;
const components = modules.filter(module => module.kindString === 'Class');
const output = {
name: package.name,
description: package.description,
version: package.version,
author: package.author,
homepage: package.homepage,
license: package.license,
components: []
};
components.map(async component => {
const api = {
className: component.name,
tag: getTagName(component.name),
file: component.sources[0].fileName,
since: '',
status: '',
props: [],
methods: [],
events: [],
slots: [],
cssCustomProperties: [],
parts: [],
dependencies: []
};
// Metadata
if (component.comment) {
const tags = component.comment.tags;
const dependencies = tags.filter(item => item.tag === 'dependency');
const slots = tags.filter(item => item.tag === 'slot');
const parts = tags.filter(item => item.tag === 'part');
const events = tags.filter(item => item.tag === 'emit');
api.since = tags.find(item => item.tag === 'since').text.trim();
api.status = tags.find(item => item.tag === 'status').text.trim();
api.dependencies = dependencies.map(tag => tag.text.trim());
api.slots = slots.map(tag => splitText(tag.text));
api.parts = parts.map(tag => splitText(tag.text));
api.events = events.map(tag => splitText(tag.text));
} else {
console.error(chalk.yellow(`Missing comment block for ${component.name} - skipping metadata`));
}
// Props
const props = component.children
.filter(child => child.kindString === 'Property' && !child.flags.isStatic)
.filter(child => child.comment && child.comment.shortText); // only with comments
props.map(prop => {
const type = getTypeInfo(prop);
api.props.push({
name: prop.name,
description: prop.comment.shortText,
type,
defaultValue: prop.defaultValue
});
});
// Methods
const methods = component.children
.filter(child => child.kindString === 'Method' && !child.flags.isStatic)
.filter(child => child.signatures[0].comment && child.signatures[0].comment.shortText); // only with comments
methods.map(method => {
const signature = method.signatures[0];
const params = Array.isArray(signature.parameters)
? signature.parameters.map(param => {
const type = getTypeInfo(param);
return {
name: param.name,
type,
defaultValue: param.defaultValue
};
})
: [];
api.methods.push({
name: method.name,
description: signature.comment.shortText,
params
});
});
// CSS custom properties
const stylesheet = path.resolve(path.dirname(api.file), path.parse(api.file).name + '.scss');
if (fs.existsSync(stylesheet)) {
const styles = fs.readFileSync(stylesheet, 'utf8');
const parsed = parse(styles);
const tags = parsed[0] ? parsed[0].tags : [];
const cssCustomProperties = tags
.filter(tag => tag.tag === 'prop')
.map(tag => api.cssCustomProperties.push({ name: tag.tag, description: tag.description }));
}
output.components.push(api);
});
// Generate components.json
(async () => {
const filename = path.join('./dist/components.json');
const outputJson = JSON.stringify(output, null, 2);
await mkdirp(path.dirname(filename));
fs.writeFileSync(filename, outputJson, 'utf8');
console.log(chalk.green(`Successfully generated components.json 🏷\n`));
})();