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

6 Commits

Author SHA1 Message Date
Chauncey McAskill
f4087b7859 [WIP] Replace svg-mixer with svg-sprite 2021-11-03 14:57:52 -04:00
Chauncey McAskill
b19c18b18c Update NPM dependencies
Updated:
- autoprefixer v10.3.7 → v10.4.0
- browser-sync v2.26.13 → v2.27.7
- esbuild v0.13.4 → v0.13.12
- postcss v8.3.9 → v8.3.11
2021-11-03 13:21:11 -04:00
Chauncey McAskill
db85740a18 Merge pull request #96 from locomotivemtl/mcaskill-refactor-build-options 2021-11-03 13:16:43 -04:00
Chauncey McAskill
5c24fabaa2 Compile assets 2021-11-03 10:50:33 -04:00
Chauncey McAskill
9e3d304654 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
2021-11-03 10:49:35 -04:00
Chauncey McAskill
6ded72bc79 Refactor postcss.js
Moved creation of Processor from utility file to styles.js to allow for future customization of `postcss` and `autoprefixer`.
2021-11-03 10:49:35 -04:00
11 changed files with 3027 additions and 4045 deletions

View File

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

View File

@@ -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) {

View File

@@ -1,7 +1,9 @@
import loconfig from '../../loconfig.json';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import postcss 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,13 +12,91 @@ import sass from 'node-sass';
const sassRender = promisify(sass.render);
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
@@ -30,25 +110,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 (postcss) {
result = await postcss.process(result.css, {
from: outfile,
to: outfile,
map: {
annotation: false,
inline: false,
sourcesContent: true
}
});
if (postcss && postcssOptions) {
if (typeof postcssProcessor === 'undefined') {
postcssProcessor = createPostCSSProcessor(
postcssPluginsMap,
postcssOptions
);
}
result = await postcssProcessor.process(
result.css,
Object.assign({}, postcssOptions.processor, {
from: outfile,
to: outfile,
})
);
if (result.warnings) {
const warnings = result.warnings();
@@ -103,3 +184,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

@@ -1,17 +1,100 @@
import loconfig from '../../loconfig.json';
import glob from '../utils/glob.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import template from '../utils/template.js';
import { basename } from 'node:path';
import mixer from 'svg-mixer';
import {
mkdir,
readFile,
writeFile
} from 'node:fs/promises';
import {
basename,
dirname,
join,
resolve
} from 'node:path';
import SVGSpriter from 'svg-sprite';
import Vinyl from 'vinyl';
/**
* @const {object} defaultSVOOptions - The default shared SVO options.
* @const {object} developmentSVOOptions - The predefined SVO options for development.
* @const {object} productionSVOOptions - The predefined SVO options for production.
*/
export const defaultSVOOptions = {
};
export const developmentSVOOptions = Object.assign({}, defaultSVOOptions);
export const productionSVOOptions = Object.assign({}, defaultSVOOptions);
/**
* @const {object} defaultSVGSpriterOptions - The default shared SVGSpriter options.
* @const {object} developmentSVGSpriterOptions - The predefined SVGSpriter options for development.
* @const {object} productionSVGSpriterOptions - The predefined SVGSpriter options for production.
*/
export const defaultSVGSpriterOptions = {
mode: {},
shape: {
transform: [],
},
svg: {
doctypeDeclaration: false,
xmlDeclaration: false,
},
};
export const developmentSVGSpriterOptions = Object.assign({}, defaultSVGSpriterOptions);
export const productionSVGSpriterOptions = Object.assign({}, defaultSVGSpriterOptions);
/**
* @const {object} developmentSVGsArgs - The predefined `compileSVGs()` options for development.
* @const {object} productionSVGsArgs - The predefined `compileSVGs()` options for production.
*/
export const developmentSVGsArgs = [
developmentSVGSpriterOptions,
developmentSVOOptions,
];
export const productionSVGsArgs = [
productionSVGSpriterOptions,
productionSVOOptions,
];
/**
* Generates and transforms SVG spritesheets.
*
* @async
* @param {object} [spriterOptions=null] - Customize the SVGSpriter API options.
* If `null`, default production options are used.
* @param {object} [svgoOptions=null] - Customize the SVGO API options.
* If `null`, default production options are used.
* @return {Promise}
*/
export default async function compileSVGs() {
export default async function compileSVGs(spriterOptions = null, svgoOptions = null) {
if (spriterOptions == null) {
spriterOptions = productionSVGSpriterOptions;
} else if (
spriterOptions !== developmentSVGSpriterOptions &&
spriterOptions !== productionSVGSpriterOptions
) {
spriterOptions = Object.assign({}, defaultSVGSpriterOptions, spriterOptions);
}
if (svgoOptions == null) {
svgoOptions = productionSVOOptions;
} else if (
svgoOptions !== developmentSVOOptions &&
svgoOptions !== productionSVOOptions
) {
svgoOptions = Object.assign({}, defaultSVOOptions, svgoOptions);
}
if (svgoOptions) {
spriterOptions.shape.transform.push({
svgo: svgoOptions,
});
}
const svgSpriter = new SVGSpriter(spriterOptions);
loconfig.tasks.svgs.forEach(async ({
includes,
outfile
@@ -25,14 +108,22 @@ export default async function compileSVGs() {
includes = includes.map((path) => template(path));
outfile = template(outfile);
const result = await mixer(includes, {
spriteConfig: {
usages: false
svgSpriter.compile({
symbol: {
dest: dirname(outfile),
sprite: filename,
},
}, (error, result) => {
for (const mode in result) {
for (const resource in result[mode]) {
await mkdir(path.dirname(result[mode][resource].path), {
recursive: true
});
await writeFile(result[mode][resource].path, result[mode][resource].contents);
}
}
});
await result.write(outfile);
message(`${filename} compiled`, 'success', timeLabel);
} catch (err) {
message(`Error compiling ${filename}`, 'error');

View File

@@ -1,14 +1,28 @@
/**
* @file If available, returns the PostCSS processor with any plugins.
* @file If available, returns the PostCSS Processor creator and
* any the Autoprefixer PostCSS plugin.
*/
try {
var { default: postcss } = await import('postcss');
let { default: autoprefixer } = await import('autoprefixer');
let postcss, autoprefixer;
postcss = postcss([ autoprefixer ]);
try {
postcss = await import('postcss');
postcss = postcss.default;
autoprefixer = await import('autoprefixer');
autoprefixer = autoprefixer.default;
} catch (err) {
postcss = null;
postcss = null;
autoprefixer = null;
}
export default postcss;
export const pluginsList = [
autoprefixer,
];
export const pluginsMap = {
'autoprefixer': autoprefixer,
};
export {
autoprefixer
};

View File

@@ -1,8 +1,14 @@
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 +36,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 +56,7 @@ server.watch(
join(paths.scripts.src, '**/*.js'),
]
).on('change', () => {
compileScripts();
compileScripts(...developmentScriptsArgs);
});
// Watch concats
@@ -69,14 +75,14 @@ server.watch(
join(paths.styles.src, '**/*.scss'),
]
).on('change', () => {
compileStyles();
compileStyles(...developmentStylesArgs);
});
// Watch svgs
server.watch(
[
join(paths.svgs.src, '*.svg'),
join(paths.svgs.src, '**/*.svg'),
]
).on('change', () => {
compileSVGs();
compileSVGs(...developmentSVGsArgs);
});

View File

@@ -52,7 +52,7 @@
"svgs": [
{
"includes": [
"{% paths.svgs.src %}/*.svg"
"{% paths.svgs.src %}/**/*.svg"
],
"outfile": "{% paths.svgs.dest %}/sprite.svg"
}

6684
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,15 +21,15 @@
"svg4everybody": "^2.1.9"
},
"devDependencies": {
"autoprefixer": "^10.3.7",
"browser-sync": "^2.26.13",
"autoprefixer": "^10.4.0",
"browser-sync": "^2.27.7",
"concat": "^1.0.3",
"esbuild": "^0.13.4",
"esbuild": "^0.13.12",
"kleur": "^4.1.4",
"node-notifier": "^10.0.0",
"node-sass": "^6.0.1",
"postcss": "^8.3.9",
"svg-mixer": "^2.3.14",
"postcss": "^8.3.11",
"svg-sprite": "^1.5.3",
"tiny-glob": "^0.2.9"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long