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
This commit is contained in:
Chauncey McAskill
2021-09-21 17:33:32 -04:00
parent 6ded72bc79
commit 9e3d304654
6 changed files with 218 additions and 50 deletions

View File

@@ -9,6 +9,8 @@ import { basename } from 'node:path';
/** /**
* Concatenates groups of files. * Concatenates groups of files.
* *
* @todo Add support for minification.
*
* @async * @async
* @return {Promise} * @return {Promise}
*/ */

View File

@@ -5,13 +5,54 @@ import template from '../utils/template.js';
import esbuild from 'esbuild'; import esbuild from 'esbuild';
import { basename } from 'node:path'; 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. * Bundles and minifies main JavaScript files.
* *
* @async * @async
* @param {object} [esBuildOptions=null] - Customize the ESBuild build API options.
* If `null`, default production options are used.
* @return {Promise} * @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 ({ loconfig.tasks.scripts.forEach(async ({
includes, includes,
outdir = '', outdir = '',
@@ -35,19 +76,11 @@ export default async function compileScripts() {
); );
} }
await esbuild.build({ await esbuild.build(Object.assign({}, esBuildOptions, {
entryPoints: includes, entryPoints: includes,
bundle: true,
minify: true,
sourcemap: true,
color: true,
logLevel: 'error',
target: [
'es2015',
],
outdir, outdir,
outfile outfile,
}); }));
message(`${filename} compiled`, 'success', timeLabel); message(`${filename} compiled`, 'success', timeLabel);
} catch (err) { } catch (err) {

View File

@@ -1,7 +1,7 @@
import loconfig from '../../loconfig.json'; import loconfig from '../../loconfig.json';
import message from '../utils/message.js'; import message from '../utils/message.js';
import notification from '../utils/notification.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 template from '../utils/template.js';
import { writeFile } from 'node:fs/promises'; import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path'; import { basename } from 'node:path';
@@ -10,16 +10,91 @@ import sass from 'node-sass';
const sassRender = promisify(sass.render); const sassRender = promisify(sass.render);
const isPostCSSAvailable = (postcss && autoprefixer);
let postcssProcessor; 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. * Compiles and minifies main Sass files to CSS.
* *
* @todo Add deep merge of `postcssOptions` to better support customization
* of default processor options.
*
* @async * @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} * @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 ({ loconfig.tasks.styles.forEach(async ({
infile, infile,
outfile outfile
@@ -33,31 +108,26 @@ export default async function compileStyles() {
infile = template(infile); infile = template(infile);
outfile = template(outfile); outfile = template(outfile);
let result = await sassRender({ let result = await sassRender(Object.assign({}, sassOptions, {
file: infile, file: infile,
omitSourceMapUrl: true,
outFile: outfile, outFile: outfile,
outputStyle: 'compressed', }));
sourceMap: true,
sourceMapContents: true
});
if (isPostCSSAvailable) { if (postcss && postcssOptions) {
if (typeof postcssProcessor === 'undefined') { if (typeof postcssProcessor === 'undefined') {
postcssProcessor = postcss([ postcssProcessor = createPostCSSProcessor(
autoprefixer, postcssPluginsMap,
]); postcssOptions
);
} }
result = await postcssProcessor.process(result.css, { result = await postcssProcessor.process(
from: outfile, result.css,
to: outfile, Object.assign({}, postcssOptions.processor, {
map: { from: outfile,
annotation: false, to: outfile,
inline: false, })
sourcesContent: true );
}
});
if (result.warnings) { if (result.warnings) {
const warnings = 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<string, (function|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);
}

View File

@@ -5,13 +5,48 @@ import template from '../utils/template.js';
import { basename } from 'node:path'; import { basename } from 'node:path';
import mixer from 'svg-mixer'; 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. * Generates and transforms SVG spritesheets.
* *
* @async * @async
* @param {object} [mixerOptions=null] - Customize the Mixer API options.
* If `null`, default production options are used.
* @return {Promise} * @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 ({ loconfig.tasks.svgs.forEach(async ({
includes, includes,
outfile outfile
@@ -25,11 +60,7 @@ export default async function compileSVGs() {
includes = includes.map((path) => template(path)); includes = includes.map((path) => template(path));
outfile = template(outfile); outfile = template(outfile);
const result = await mixer(includes, { const result = await mixer(includes, mixerOptions);
spriteConfig: {
usages: false
}
});
await result.write(outfile); await result.write(outfile);

View File

@@ -17,9 +17,12 @@ try {
} }
export default postcss; export default postcss;
export const plugins = [ export const pluginsList = [
autoprefixer, autoprefixer,
]; ];
export const pluginsMap = {
'autoprefixer': autoprefixer,
};
export { export {
autoprefixer autoprefixer
}; };

View File

@@ -1,8 +1,8 @@
import loconfig from '../loconfig.json'; import loconfig from '../loconfig.json';
import concatFiles from './tasks/concats.js'; import concatFiles from './tasks/concats.js';
import compileScripts from './tasks/scripts.js'; import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
import compileStyles from './tasks/styles.js' ; import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
import compileSVGs from './tasks/svgs.js'; import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
import template from './utils/template.js'; import template from './utils/template.js';
import server from 'browser-sync'; import server from 'browser-sync';
import { join } from 'node:path'; import { join } from 'node:path';
@@ -30,9 +30,9 @@ server.init(serverConfig);
// Build scripts, compile styles, concat files, // Build scripts, compile styles, concat files,
// and generate spritesheets on first hit // and generate spritesheets on first hit
concatFiles(); concatFiles();
compileScripts(); compileScripts(...developmentScriptsArgs);
compileStyles(); compileStyles(...developmentStylesArgs);
compileSVGs(); compileSVGs(...developmentSVGsArgs);
// and call any methods on it. // and call any methods on it.
server.watch( server.watch(
@@ -50,7 +50,7 @@ server.watch(
join(paths.scripts.src, '**/*.js'), join(paths.scripts.src, '**/*.js'),
] ]
).on('change', () => { ).on('change', () => {
compileScripts(); compileScripts(...developmentScriptsArgs);
}); });
// Watch concats // Watch concats
@@ -69,7 +69,7 @@ server.watch(
join(paths.styles.src, '**/*.scss'), join(paths.styles.src, '**/*.scss'),
] ]
).on('change', () => { ).on('change', () => {
compileStyles(); compileStyles(...developmentStylesArgs);
}); });
// Watch svgs // Watch svgs
@@ -78,5 +78,5 @@ server.watch(
join(paths.svgs.src, '*.svg'), join(paths.svgs.src, '*.svg'),
] ]
).on('change', () => { ).on('change', () => {
compileSVGs(); compileSVGs(...developmentSVGsArgs);
}); });