From 9e3d304654ddfebfed02ece517a312117119f2d5 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Tue, 21 Sep 2021 17:33:32 -0400 Subject: [PATCH] Add support for task options Added support for customizing processors in `scripts.js` (esbuild), `styles.js` (node-sass, postcss, autoprefixer), and `svgs.js` (svg-mixer), via arguments for the exported task functions. Added: - Constants to decouple shared default options, options for development, and options for production. - Constants to export default options for development and production as an array of arguments to pass to task functions. Changed: - watch.js to apply development args to tasks --- build/tasks/concats.js | 2 + build/tasks/scripts.js | 57 ++++++++++++---- build/tasks/styles.js | 143 ++++++++++++++++++++++++++++++++++------- build/tasks/svgs.js | 43 +++++++++++-- build/utils/postcss.js | 5 +- build/watch.js | 18 +++--- 6 files changed, 218 insertions(+), 50 deletions(-) diff --git a/build/tasks/concats.js b/build/tasks/concats.js index 304daf4..ff48963 100644 --- a/build/tasks/concats.js +++ b/build/tasks/concats.js @@ -9,6 +9,8 @@ import { basename } from 'node:path'; /** * Concatenates groups of files. * + * @todo Add support for minification. + * * @async * @return {Promise} */ diff --git a/build/tasks/scripts.js b/build/tasks/scripts.js index fdda69b..e7305c2 100644 --- a/build/tasks/scripts.js +++ b/build/tasks/scripts.js @@ -5,13 +5,54 @@ import template from '../utils/template.js'; import esbuild from 'esbuild'; import { basename } from 'node:path'; +/** + * @const {object} defaultESBuildOptions - The default shared ESBuild options. + * @const {object} developmentESBuildOptions - The predefined ESBuild options for development. + * @const {object} productionESBuildOptions - The predefined ESBuild options for production. + */ +export const defaultESBuildOptions = { + bundle: true, + color: true, + sourcemap: true, + target: [ + 'es2015', + ], +}; +export const developmentESBuildOptions = Object.assign({}, defaultESBuildOptions); +export const productionESBuildOptions = Object.assign({}, defaultESBuildOptions, { + logLevel: 'warning', + minify: true, +}); + +/** + * @const {object} developmentScriptsArgs - The predefined `compileScripts()` options for development. + * @const {object} productionScriptsArgs - The predefined `compileScripts()` options for production. + */ +export const developmentScriptsArgs = [ + developmentESBuildOptions, +]; +export const productionScriptsArgs = [ + productionESBuildOptions, +]; + /** * Bundles and minifies main JavaScript files. * * @async + * @param {object} [esBuildOptions=null] - Customize the ESBuild build API options. + * If `null`, default production options are used. * @return {Promise} */ -export default async function compileScripts() { +export default async function compileScripts(esBuildOptions = null) { + if (esBuildOptions == null) { + esBuildOptions = productionESBuildOptions; + } else if ( + esBuildOptions !== developmentESBuildOptions && + esBuildOptions !== productionESBuildOptions + ) { + esBuildOptions = Object.assign({}, defaultESBuildOptions, esBuildOptions); + } + loconfig.tasks.scripts.forEach(async ({ includes, outdir = '', @@ -35,19 +76,11 @@ export default async function compileScripts() { ); } - await esbuild.build({ + await esbuild.build(Object.assign({}, esBuildOptions, { entryPoints: includes, - bundle: true, - minify: true, - sourcemap: true, - color: true, - logLevel: 'error', - target: [ - 'es2015', - ], outdir, - outfile - }); + outfile, + })); message(`${filename} compiled`, 'success', timeLabel); } catch (err) { diff --git a/build/tasks/styles.js b/build/tasks/styles.js index 1f219fb..0e9d39b 100644 --- a/build/tasks/styles.js +++ b/build/tasks/styles.js @@ -1,7 +1,7 @@ import loconfig from '../../loconfig.json'; import message from '../utils/message.js'; import notification from '../utils/notification.js'; -import postcss, { autoprefixer } from '../utils/postcss.js'; +import postcss, { pluginsMap as postcssPluginsMap } from '../utils/postcss.js'; import template from '../utils/template.js'; import { writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; @@ -10,16 +10,91 @@ import sass from 'node-sass'; const sassRender = promisify(sass.render); -const isPostCSSAvailable = (postcss && autoprefixer); let postcssProcessor; +/** + * @const {object} defaultSassOptions - The default shared Sass options. + * @const {object} developmentSassOptions - The predefined Sass options for development. + * @const {object} productionSassOptions - The predefined Sass options for production. + */ +export const defaultSassOptions = { + omitSourceMapUrl: true, + sourceMap: true, + sourceMapContents: true, +}; +export const developmentSassOptions = Object.assign({}, defaultSassOptions, { + outputStyle: 'expanded', +}); +export const productionSassOptions = Object.assign({}, defaultSassOptions, { + outputStyle: 'compressed', +}); + +/** + * @const {object} defaultPostCSSOptions - The default shared PostCSS options. + * @const {object} developmentPostCSSOptions - The predefined PostCSS options for development. + * @const {object} productionPostCSSOptions - The predefined PostCSS options for production. + */ +export const defaultPostCSSOptions = { + processor: { + map: { + annotation: false, + inline: false, + sourcesContent: true, + }, + }, +}; +export const developmentPostCSSOptions = Object.assign({}, defaultPostCSSOptions); +export const productionPostCSSOptions = Object.assign({}, defaultPostCSSOptions); + +/** + * @const {object} developmentStylesArgs - The predefined `compileStyles()` options for development. + * @const {object} productionStylesArgs - The predefined `compileStyles()` options for production. + */ +export const developmentStylesArgs = [ + developmentSassOptions, + developmentPostCSSOptions, +]; +export const productionStylesArgs = [ + productionSassOptions, + productionPostCSSOptions, +]; + /** * Compiles and minifies main Sass files to CSS. * + * @todo Add deep merge of `postcssOptions` to better support customization + * of default processor options. + * * @async + * @param {object} [sassOptions=null] - Customize the Sass render API options. + * If `null`, default production options are used. + * @param {object|boolean} [postcssOptions=null] - Customize the PostCSS processor API options. + * If `null`, default production options are used. + * If `false`, PostCSS processing will be ignored. * @return {Promise} */ -export default async function compileStyles() { +export default async function compileStyles(sassOptions = null, postcssOptions = null) { + if (sassOptions == null) { + sassOptions = productionSassOptions; + } else if ( + sassOptions !== developmentSassOptions && + sassOptions !== productionSassOptions + ) { + sassOptions = Object.assign({}, defaultSassOptions, sassOptions); + } + + if (postcss) { + if (postcssOptions == null) { + postcssOptions = productionPostCSSOptions; + } else if ( + postcssOptions !== false && + postcssOptions !== developmentSassOptions && + postcssOptions !== productionSassOptions + ) { + postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions); + } + } + loconfig.tasks.styles.forEach(async ({ infile, outfile @@ -33,31 +108,26 @@ export default async function compileStyles() { infile = template(infile); outfile = template(outfile); - let result = await sassRender({ + let result = await sassRender(Object.assign({}, sassOptions, { file: infile, - omitSourceMapUrl: true, outFile: outfile, - outputStyle: 'compressed', - sourceMap: true, - sourceMapContents: true - }); + })); - if (isPostCSSAvailable) { + if (postcss && postcssOptions) { if (typeof postcssProcessor === 'undefined') { - postcssProcessor = postcss([ - autoprefixer, - ]); + postcssProcessor = createPostCSSProcessor( + postcssPluginsMap, + postcssOptions + ); } - result = await postcssProcessor.process(result.css, { - from: outfile, - to: outfile, - map: { - annotation: false, - inline: false, - sourcesContent: true - } - }); + result = await postcssProcessor.process( + result.css, + Object.assign({}, postcssOptions.processor, { + from: outfile, + to: outfile, + }) + ); if (result.warnings) { const warnings = result.warnings(); @@ -112,3 +182,32 @@ export default async function compileStyles() { } }); }; + +/** + * Creates a PostCSS Processor with the given plugins and options. + * + * @param {array<(function|object)>|object} pluginsListOrMap - + * A list or map of plugins. + * If a map of plugins, the plugin name looks up `options`. + * @param {object} options - The PostCSS options. + */ +function createPostCSSProcessor(pluginsListOrMap, options) +{ + let plugins; + + if (Array.isArray(pluginsListOrMap)) { + plugins = pluginsListOrMap; + } else { + plugins = []; + + for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) { + if (name in options) { + plugin = plugin[name](options[name]); + } + + plugins.push(plugin); + } + } + + return postcss(plugins); +} diff --git a/build/tasks/svgs.js b/build/tasks/svgs.js index 6be6121..a3fc453 100644 --- a/build/tasks/svgs.js +++ b/build/tasks/svgs.js @@ -5,13 +5,48 @@ import template from '../utils/template.js'; import { basename } from 'node:path'; import mixer from 'svg-mixer'; +/** + * @const {object} defaultMixerOptions - The default shared Mixer options. + * @const {object} developmentMixerOptions - The predefined Mixer options for development. + * @const {object} productionMixerOptions - The predefined Mixer options for production. + */ +export const defaultMixerOptions = { + spriteConfig: { + usages: false, + }, +}; +export const developmentMixerOptions = Object.assign({}, defaultMixerOptions); +export const productionMixerOptions = Object.assign({}, defaultMixerOptions); + +/** + * @const {object} developmentSVGsArgs - The predefined `compileSVGs()` options for development. + * @const {object} productionSVGsArgs - The predefined `compileSVGs()` options for production. + */ +export const developmentSVGsArgs = [ + developmentMixerOptions, +]; +export const productionSVGsArgs = [ + productionMixerOptions, +]; + /** * Generates and transforms SVG spritesheets. * * @async + * @param {object} [mixerOptions=null] - Customize the Mixer API options. + * If `null`, default production options are used. * @return {Promise} */ -export default async function compileSVGs() { +export default async function compileSVGs(mixerOptions = null) { + if (mixerOptions == null) { + mixerOptions = productionMixerOptions; + } else if ( + mixerOptions !== developmentMixerOptions && + mixerOptions !== productionMixerOptions + ) { + mixerOptions = Object.assign({}, defaultMixerOptions, mixerOptions); + } + loconfig.tasks.svgs.forEach(async ({ includes, outfile @@ -25,11 +60,7 @@ export default async function compileSVGs() { includes = includes.map((path) => template(path)); outfile = template(outfile); - const result = await mixer(includes, { - spriteConfig: { - usages: false - } - }); + const result = await mixer(includes, mixerOptions); await result.write(outfile); diff --git a/build/utils/postcss.js b/build/utils/postcss.js index cba2a90..75866c6 100644 --- a/build/utils/postcss.js +++ b/build/utils/postcss.js @@ -17,9 +17,12 @@ try { } export default postcss; -export const plugins = [ +export const pluginsList = [ autoprefixer, ]; +export const pluginsMap = { + 'autoprefixer': autoprefixer, +}; export { autoprefixer }; diff --git a/build/watch.js b/build/watch.js index afe63a1..bd9ff1c 100644 --- a/build/watch.js +++ b/build/watch.js @@ -1,8 +1,8 @@ import loconfig from '../loconfig.json'; import concatFiles from './tasks/concats.js'; -import compileScripts from './tasks/scripts.js'; -import compileStyles from './tasks/styles.js' ; -import compileSVGs from './tasks/svgs.js'; +import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js'; +import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ; +import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js'; import template from './utils/template.js'; import server from 'browser-sync'; import { join } from 'node:path'; @@ -30,9 +30,9 @@ server.init(serverConfig); // Build scripts, compile styles, concat files, // and generate spritesheets on first hit concatFiles(); -compileScripts(); -compileStyles(); -compileSVGs(); +compileScripts(...developmentScriptsArgs); +compileStyles(...developmentStylesArgs); +compileSVGs(...developmentSVGsArgs); // and call any methods on it. server.watch( @@ -50,7 +50,7 @@ server.watch( join(paths.scripts.src, '**/*.js'), ] ).on('change', () => { - compileScripts(); + compileScripts(...developmentScriptsArgs); }); // Watch concats @@ -69,7 +69,7 @@ server.watch( join(paths.styles.src, '**/*.scss'), ] ).on('change', () => { - compileStyles(); + compileStyles(...developmentStylesArgs); }); // Watch svgs @@ -78,5 +78,5 @@ server.watch( join(paths.svgs.src, '*.svg'), ] ).on('change', () => { - compileSVGs(); + compileSVGs(...developmentSVGsArgs); });