1
0
mirror of https://github.com/locomotivemtl/locomotive-boilerplate.git synced 2026-01-15 00:55:08 +08:00

Refactor build utilities

Separated generic functions from build helpers.

Changed:
- Moved 'utils/*.js' to 'helpers/*.js'
- From 'utils/config.js':
  - Moved function `merge` to 'utils/index.js'.
  - Moved function `isObjectLike` to 'utils/index.js'.
- From 'utils/template.js':
  - Moved function `flatten` to 'utils/index.js'.
  - Moved function `escapeRegExp` to 'utils/index.js'.
- From 'tasks/styles.js':
  - Moved function `createPostCSSProcessor` to 'helpers/postcss.js' as `createProcessor`.
- Replaced function `Object.assign` with `merge` for task options parsing in all tasks.
This commit is contained in:
Chauncey McAskill
2022-10-04 17:46:12 -04:00
parent 9d758f3b2c
commit c9a9209b4b
14 changed files with 242 additions and 171 deletions

19
build/helpers/config.js Normal file
View File

@@ -0,0 +1,19 @@
/**
* @file Provides simple user configuration options.
*/
import loconfig from '../../loconfig.json' assert { type: 'json' };
import { merge } from '../utils/index.js';
try {
const usrconfig = await import('../../loconfig.local.json', {
assert: { type: "json" },
});
usrconfig = usrconfig.default;
merge(loconfig, usrconfig);
} catch (err) {
// do nothing
}
export default loconfig;

111
build/helpers/postcss.js Normal file
View File

@@ -0,0 +1,111 @@
/**
* @file If available, returns the PostCSS Processor creator and
* any the Autoprefixer PostCSS plugin.
*/
/**
* @typedef {import('autoprefixer').autoprefixer.Options} AutoprefixerOptions
*/
/**
* @typedef {import('postcss').AcceptedPlugin} AcceptedPlugin
*/
/**
* @typedef {import('postcss').ProcessOptions} ProcessOptions
*/
/**
* @typedef {import('postcss').Processor} Processor
*/
/**
* @typedef {AcceptedPlugin[]} PluginList
*/
/**
* @typedef {object<string, AcceptedPlugin>} PluginMap
*/
/**
* @typedef {PluginList|PluginMap} PluginCollection
*/
/**
* @typedef {object} PostCSSOptions
*
* @property {ProcessOptions} processor - The `Processor#process()` options.
* @property {AutoprefixerOptions} autoprefixer - The `autoprefixer()` options.
*/
let postcss, autoprefixer;
try {
postcss = await import('postcss');
postcss = postcss.default;
autoprefixer = await import('autoprefixer');
autoprefixer = autoprefixer.default;
} catch (err) {
// do nothing
}
export const supportsPostCSS = (typeof postcss === 'function');
export default postcss;
export const pluginsList = [
autoprefixer,
];
export const pluginsMap = {
'autoprefixer': autoprefixer,
};
export {
autoprefixer
};
/**
* Attempts to create a PostCSS Processor with the given plugins and options.
*
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
* If a map of plugins, the plugin name looks up `options`.
* @param {PostCSSOptions} options - The PostCSS wrapper options.
* @return {?Processor}
*/
export function createProcessor(pluginsListOrMap, options)
{
if (!postcss) {
return null;
}
const plugins = parsePlugins(pluginsListOrMap, options);
return postcss(plugins);
}
/**
* Parses the PostCSS plugins and options.
*
* @param {PluginCollection} pluginsListOrMap - A list or map of plugins.
* If a map of plugins, the plugin name looks up `options`.
* @param {PostCSSOptions} options - The PostCSS wrapper options.
* @return {PluginList}
*/
export function parsePlugins(pluginsListOrMap, options)
{
if (Array.isArray(pluginsListOrMap)) {
return pluginsListOrMap;
}
/** @type {PluginList} */
const plugins = [];
for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
if (name in options) {
plugin = plugin[name](options[name]);
}
plugins.push(plugin);
}
return plugins;
}

View File

@@ -3,6 +3,10 @@
*/
import loconfig from './config.js';
import {
escapeRegExp,
flatten
} from '../utils/index.js';
const templateData = flatten({
paths: loconfig.paths
@@ -92,56 +96,3 @@ export function resolveValue(input, data = templateData) {
return '';
});
}
/**
* Creates a new object with all nested object properties
* concatenated into it recursively.
*
* Nested keys are flattened into a property path:
*
* ```js
* {
* a: {
* b: {
* c: 1
* }
* },
* d: 1
* }
* ```
*
* ```js
* {
* "a.b.c": 1,
* "d": 1
* }
* ```
*
* @param {object} input - The object to flatten.
* @param {string} prefix - The parent key prefix.
* @param {object} target - The object that will receive the flattened properties.
* @return {object} Returns the `target` object.
*/
function flatten(input, prefix, target = {}) {
for (let key in input) {
let field = (prefix ? prefix + '.' + key : key);
if (typeof input[key] === 'object') {
flatten(input[key], field, target);
} else {
target[field] = input[key];
}
}
return target;
}
/**
* Quotes regular expression characters.
*
* @param {string} str - The input string.
* @return {string} Returns the quoted (escaped) string.
*/
function escapeRegExp(str) {
return str.replace(/[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g, '\\$&');
}

View File

@@ -1,8 +1,9 @@
import loconfig from '../utils/config.js';
import glob, { supportsGlob } from '../utils/glob.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import loconfig from '../helpers/config.js';
import glob, { supportsGlob } from '../helpers/glob.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import concat from 'concat';
import {
basename,
@@ -72,7 +73,7 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
globOptions !== developmentGlobOptions &&
globOptions !== productionGlobOptions
) {
globOptions = Object.assign({}, defaultGlobOptions, globOptions);
globOptions = merge({}, defaultGlobOptions, globOptions);
}
}
@@ -82,7 +83,7 @@ export default async function concatFiles(globOptions = null, concatOptions = nu
concatOptions !== developmentConcatOptions &&
concatOptions !== productionConcatOptions
) {
concatOptions = Object.assign({}, defaultConcatOptions, concatOptions);
concatOptions = merge({}, defaultConcatOptions, concatOptions);
}
loconfig.tasks.concats.forEach(async ({

View File

@@ -1,7 +1,8 @@
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import esbuild from 'esbuild';
import { basename } from 'node:path';
@@ -50,7 +51,7 @@ export default async function compileScripts(esBuildOptions = null) {
esBuildOptions !== developmentESBuildOptions &&
esBuildOptions !== productionESBuildOptions
) {
esBuildOptions = Object.assign({}, defaultESBuildOptions, esBuildOptions);
esBuildOptions = merge({}, defaultESBuildOptions, esBuildOptions);
}
loconfig.tasks.scripts.forEach(async ({

View File

@@ -1,11 +1,13 @@
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import postcss, {
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import {
createProcessor,
pluginsMap as postcssPluginsMap,
supportsPostCSS
} from '../utils/postcss.js';
import resolve from '../utils/template.js';
} from '../helpers/postcss.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
@@ -85,7 +87,7 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
sassOptions !== developmentSassOptions &&
sassOptions !== productionSassOptions
) {
sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
sassOptions = merge({}, defaultSassOptions, sassOptions);
}
if (supportsPostCSS) {
@@ -96,7 +98,7 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
postcssOptions !== developmentPostCSSOptions &&
postcssOptions !== productionPostCSSOptions
) {
postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions);
postcssOptions = merge({}, defaultPostCSSOptions, postcssOptions);
}
}
@@ -121,7 +123,7 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
if (supportsPostCSS && postcssOptions) {
if (typeof postcssProcessor === 'undefined') {
postcssProcessor = createPostCSSProcessor(
postcssProcessor = createProcessor(
postcssPluginsMap,
postcssOptions
);
@@ -194,35 +196,6 @@ export default async function compileStyles(sassOptions = null, postcssOptions =
});
};
/**
* 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);
}
/**
* Purge unused styles from CSS files.
*
@@ -235,16 +208,17 @@ function createPostCSSProcessor(pluginsListOrMap, options)
*/
async function purgeUnusedCSS(outfile, label) {
label = label ?? basename(outfile);
const timeLabel = `${label} purged in`;
console.time(timeLabel);
const purgeCSSContentFiles = Array.from(loconfig.tasks.purgeCSS.content);
const purgeCSSResults = await new PurgeCSS().purge({
const purgeCSSResults = await (new PurgeCSS()).purge({
content: purgeCSSContentFiles,
css: [ outfile ],
rejected: true,
defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
defaultExtractor: (content) => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
safelist: {
standard: [ /^((?!\bu-gc-).)*$/ ]
}

View File

@@ -1,7 +1,8 @@
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import resolve from '../utils/template.js';
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import { basename } from 'node:path';
import mixer from 'svg-mixer';
@@ -44,7 +45,7 @@ export default async function compileSVGs(mixerOptions = null) {
mixerOptions !== developmentMixerOptions &&
mixerOptions !== productionMixerOptions
) {
mixerOptions = Object.assign({}, defaultMixerOptions, mixerOptions);
mixerOptions = merge({}, defaultMixerOptions, mixerOptions);
}
loconfig.tasks.svgs.forEach(async ({

View File

@@ -1,6 +1,7 @@
import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import resolve from '../utils/template.js';
import loconfig from '../helpers/config.js';
import message from '../helpers/message.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import { randomBytes } from 'node:crypto';
import events from 'node:events';
import {
@@ -94,7 +95,7 @@ export default async function bumpVersions(versionOptions = null) {
versionOptions !== developmentVersionOptions &&
versionOptions !== productionVersionOptions
) {
versionOptions = Object.assign({}, defaultVersionOptions, versionOptions);
versionOptions = merge({}, defaultVersionOptions, versionOptions);
}
const queue = new Map();

View File

@@ -1,23 +1,70 @@
/**
* @file Provides simple user configuration options.
* @file Provides generic functions and constants.
*/
import loconfig from '../../loconfig.json' assert { type: 'json' };
let usrconfig;
try {
usrconfig = await import('../../loconfig.local.json', {
assert: { type: 'json' }
});
usrconfig = usrconfig.default;
merge(loconfig, usrconfig);
} catch (err) {
// do nothing
/**
* Quotes regular expression characters.
*
* @param {string} str - The input string.
* @return {string} Returns the quoted (escaped) string.
*/
export function escapeRegExp(str) {
return str.replace(regexUnescaped, '\\$&');
}
export default loconfig;
/**
* Creates a new object with all nested object properties
* concatenated into it recursively.
*
* Nested keys are flattened into a property path:
*
* ```js
* {
* a: {
* b: {
* c: 1
* }
* },
* d: 1
* }
* ```
*
* ```js
* {
* "a.b.c": 1,
* "d": 1
* }
* ```
*
* @param {object} input - The object to flatten.
* @param {string} prefix - The parent key prefix.
* @param {object} target - The object that will receive the flattened properties.
* @return {object} Returns the `target` object.
*/
export function flatten(input, prefix, target = {}) {
for (const key in input) {
const field = (prefix ? prefix + '.' + key : key);
if (isObjectLike(input[key])) {
flatten(input[key], field, target);
} else {
target[field] = input[key];
}
}
return target;
}
/**
* Determines whether the passed value is an `Object`.
*
* @param {*} value - The value to be checked.
* @return {boolean} Returns `true` if the value is an `Object`,
* otherwise `false`.
*/
export function isObjectLike(value) {
return (value != null && typeof value === 'object');
}
/**
* Creates a new object with all nested object properties
@@ -55,12 +102,6 @@ export function merge(target, ...sources) {
}
/**
* Determines whether the passed value is an `Object`.
*
* @param {*} value - The value to be checked.
* @return {boolean} Returns `true` if the value is an `Object`,
* otherwise `false`.
* @type {RegExp} - Match all special characters.
*/
function isObjectLike(value) {
return (value != null && typeof value === 'object');
}
export const regexUnescaped = /[\[\]\{\}\(\)\-\*\+\?\.\,\\\^\$\|\#\s]/g;

View File

@@ -1,29 +0,0 @@
/**
* @file If available, returns the PostCSS Processor creator and
* any the Autoprefixer PostCSS plugin.
*/
let postcss, autoprefixer;
try {
postcss = await import('postcss');
postcss = postcss.default;
autoprefixer = await import('autoprefixer');
autoprefixer = autoprefixer.default;
} catch (err) {
// do nothing
}
export const supportsPostCSS = (typeof postcss === 'function');
export default postcss;
export const pluginsList = [
autoprefixer,
];
export const pluginsMap = {
'autoprefixer': autoprefixer,
};
export {
autoprefixer
};

View File

@@ -2,10 +2,10 @@ import concatFiles, { developmentConcatFilesArgs } from './tasks/concats.js';
import compileScripts, { developmentScriptsArgs } from './tasks/scripts.js';
import compileStyles, { developmentStylesArgs } from './tasks/styles.js' ;
import compileSVGs, { developmentSVGsArgs } from './tasks/svgs.js';
import loconfig, { merge } from './utils/config.js';
import message from './utils/message.js';
import notification from './utils/notification.js';
import resolve from './utils/template.js';
import loconfig, { merge } from './helpers/config.js';
import message from './helpers/message.js';
import notification from './helpers/notification.js';
import resolve from './helpers/template.js';
import browserSync from 'browser-sync';
import { join } from 'node:path';