diff --git a/gulpfile.js b/gulpfile.js index c294d02..16eb599 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,10 +9,18 @@ const browserSync = require('browser-sync').create() const chalk = require('chalk'); const rename = require('gulp-rename'); const filter = require('gulp-filter'); +const flatten = require('gulp-flatten') +const postcssSassParser = require('postcss-scss') +const postcssCssVariables = require('postcss-css-variables') const paths = { styles: { src: 'src/**/*.scss', + variables: { + src: 'src/variables-*.scss', + compiled: 'src/_variables/_variables-*.scss', + dest: 'src/_variables', + }, dest: 'dist' }, html: { @@ -22,65 +30,105 @@ const paths = { // https://stackoverflow.com/a/20732091 function humanFileSize(size) { - var i = Math.floor( Math.log(size) / Math.log(1024) ); - return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; + var i = Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; }; function formatByteMessage(source, data) { - const change = (data.savings > 0 ? 'saved' : 'gained') - const prettySavings = humanFileSize(Math.abs(data.savings)) const prettyStartSize = humanFileSize(data.startSize) - let prettyEndSize = humanFileSize(data.endSize) + let message = ''; - if (data.endSize > data.startSize) { - prettyEndSize = chalk.yellow(prettyEndSize) - } + if (data.startSize !== data.endSize) { + const change = (data.savings > 0 ? 'saved' : 'gained') + const prettySavings = humanFileSize(Math.abs(data.savings)) + let prettyEndSize = humanFileSize(data.endSize) - if (data.endSize < data.startSize) { - prettyEndSize = chalk.green(prettyEndSize) - } + if (data.endSize > data.startSize) prettyEndSize = chalk.yellow(prettyEndSize) + if (data.endSize < data.startSize) prettyEndSize = chalk.green(prettyEndSize) - return `${chalk.cyan(source.padStart(12, ' '))}: ${data.fileName} ${change} ${prettySavings} (${prettyStartSize} -> ${prettyEndSize})` + message = chalk`${change} ${prettySavings} (${prettyStartSize} -> {bold ${prettyEndSize}})` + } else message = chalk`kept original filesize. ({bold ${prettyStartSize}})` + + return chalk`{cyan ${(source.padStart(12, ' '))}}: {bold ${data.fileName}} ${message}` } -function style() { +/* Inlines variable references within the variable files themselves. */ +/* Allows computing new variables based on previous ones, e.g. with `lighten()` */ +function computeVariables() { + const plugins = [postcssCssVariables({ preserve: 'computed' })] + const parser = postcssSassParser + + return gulp.src(paths.styles.variables.src) + .pipe(postcss(plugins, { parser })) + .pipe(rename({ prefix: '_' })) + .pipe(gulp.dest(paths.styles.variables.dest)); +} + +function compileStyles() { + const isLegacyOrStandalone = path => /standalone|legacy/.test(path) + + const excludeModern = filter(file => isLegacyOrStandalone(file.path), { restore: true }) + const excludeLegacy = filter(file => !isLegacyOrStandalone(file.path), { restore: true }) + return ( - gulp.src(paths.styles.src) - // Add sourcemaps - .pipe(sourcemaps.init()) - // Create a human readable sass file - .pipe(sass({outputStyle: 'expanded'})) - // Catch any sass errors - .on('error', sass.logError) - // Calculate size before autoprefixing - .pipe(bytediff.start()) - // autoprefix - .pipe(postcss([ autoprefixer()])) - // Write the amount gained by autoprefixing - .pipe(bytediff.stop((data) => formatByteMessage('autoprefixer', data))) - // Write the sourcemaps after making pre-minified changes - .pipe(sourcemaps.write('.')) - // Write pre-minified styles - .pipe(gulp.dest(paths.styles.dest)) - // Remove sourcemaps from the pipeline, only keep css - .pipe(filter('**/*.css')) - // Calculate size before minifying - .pipe(bytediff.start()) - // Minify using cssnano - .pipe(postcss([cssnano()])) - // Write the amount saved by minifying - .pipe(bytediff.stop((data) => formatByteMessage('cssnano', data))) - // Rename the files have the .min suffix - .pipe(rename({suffix: '.min' })) - // Write the sourcemaps after making all changes - .pipe(sourcemaps.write('.')) - // Write the minified files - .pipe(gulp.dest(paths.styles.dest)) - // Stream any changes to browserSync - .pipe(browserSync.stream()) + gulp.src(paths.styles.src, { ignore: paths.styles.variables.src }) + // Add sourcemaps + .pipe(sourcemaps.init()) + // Create a human readable sass file + .pipe(sass({ outputStyle: 'expanded' })) + // Catch any sass errors + .on('error', sass.logError) + + // * Process legacy & standalone builds * + .pipe(excludeModern) + // Inline variable values so CSS works in legacy browsers + .pipe(postcss([postcssCssVariables()])) + // Calculate size before autoprefixing + .pipe(bytediff.start()) + // autoprefix + .pipe(postcss([autoprefixer()])) + // Write the amount gained by autoprefixing + .pipe(bytediff.stop((data) => formatByteMessage('autoprefixer', data))) + .pipe(excludeModern.restore) + + // * Process modern builds * + .pipe(excludeLegacy) + // Calculate size before autoprefixing + .pipe(bytediff.start()) + // autoprefix modern builds + // TODO: Use separate browserslist to only apply prefixes needed in *modern* browsers + .pipe(postcss([autoprefixer()])) + // Write the amount gained by autoprefixing + .pipe(bytediff.stop((data) => formatByteMessage('autoprefixer', data))) + .pipe(excludeLegacy.restore) + + // Write the sourcemaps after making pre-minified changes + .pipe(sourcemaps.write('.')) + // Flatten output so files end up in dist/*, not dist/builds/* + .pipe(flatten()) + // Write pre-minified styles + .pipe(gulp.dest(paths.styles.dest)) + // Remove sourcemaps from the pipeline, only keep css + .pipe(filter('**/*.css')) + // Calculate size before minifying + .pipe(bytediff.start()) + // Minify using cssnano + .pipe(postcss([cssnano()])) + // Write the amount saved by minifying + .pipe(bytediff.stop((data) => formatByteMessage('cssnano', data))) + // Rename the files have the .min suffix + .pipe(rename({ suffix: '.min' })) + // Write the sourcemaps after making all changes + .pipe(sourcemaps.write('.')) + // Write the minified files + .pipe(gulp.dest(paths.styles.dest)) + // Stream any changes to browserSync + .pipe(browserSync.stream()) ) } +const style = gulp.series(computeVariables, compileStyles) + function reload() { browserSync.reload() } @@ -95,7 +143,10 @@ function watch() { startPath: 'index.html' }) - gulp.watch(paths.styles.src, style) + // Don't watch compiled variables or every build triggers the watcher again (infinite loop) + const watched = [paths.styles.src, `!${paths.styles.variables.compiled}`] + + gulp.watch(watched, style) gulp.watch(paths.html.src, reload) } diff --git a/package.json b/package.json index 3d7cf5c..28f3479 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,14 @@ "cssnano": "^4.1.10", "gulp": "^4.0.0", "gulp-bytediff": "^1.0.0", + "gulp-filter": "^5.1.0", + "gulp-flatten": "^0.4.0", "gulp-postcss": "^8.0.0", + "gulp-rename": "^1.4.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-filter": "^5.1.0", - "gulp-rename": "^1.4.0" + "postcss-css-variables": "^0.12.0", + "postcss-scss": "^2.0.0" }, "browserslist": [ "defaults AND not android 4.4.3" diff --git a/yarn.lock b/yarn.lock index 7b90684..ed284ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1504,7 +1504,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -1588,7 +1588,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.1, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2033,6 +2033,14 @@ gulp-filter@^5.1.0: plugin-error "^0.1.2" streamfilter "^1.0.5" +gulp-flatten@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/gulp-flatten/-/gulp-flatten-0.4.0.tgz#d9ac819416c30fd5dfb3dea9da79c83a1bcd61d1" + integrity sha512-eg4spVTAiv1xXmugyaCxWne1oPtNG0UHEtABx5W8ScLiqAYceyYm6GYA36x0Qh8KOIXmAZV97L2aYGnKREG3Sg== + dependencies: + plugin-error "^0.1.2" + through2 "^2.0.0" + gulp-postcss@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-8.0.0.tgz#8d3772cd4d27bca55ec8cb4c8e576e3bde4dc550" @@ -3820,6 +3828,15 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-css-variables@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/postcss-css-variables/-/postcss-css-variables-0.12.0.tgz#0137b6ff15fb051bd29b36eabfea03472ccdd14c" + integrity sha512-fSgIfR+g/kZ2GeV3GH7wyNslDi7KH/Z+zwtHdEcmHv86mu+o71va9r9rqFLPR0VKi2BzoQZbCyw89oXK/XevIQ== + dependencies: + escape-string-regexp "^1.0.3" + extend "^3.0.1" + postcss "^6.0.8" + postcss-discard-comments@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" @@ -4028,6 +4045,13 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-scss@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1" + integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug== + dependencies: + postcss "^7.0.0" + postcss-selector-parser@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" @@ -4070,6 +4094,15 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss@^6.0.8: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.5: version "7.0.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" @@ -4930,7 +4963,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==