From 8769110adf66a0f8369b96d079faa7575836ee9a Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Fri, 10 Aug 2018 14:24:14 -0700 Subject: [PATCH] [scss] Adds autoprefixer support and improves watcher (#21656) This adds support for autoprefixer which we have been using in Webpack. Additionally, we have improved the watching functionality and now update builds based directly on their dependencies an not an assumption that the files are children. Signed-off-by: Tyler Smalley --- .browserslistrc | 2 + package.json | 3 +- src/dev/build/tasks/transpile_scss_task.js | 15 ++--- src/server/sass/build.js | 37 ++++++----- src/server/sass/build.test.js | 1 - src/server/sass/build_all.js | 21 +++---- src/server/sass/index.js | 71 +++++++++++++++++----- yarn.lock | 60 ++++++++++++------ 8 files changed, 135 insertions(+), 75 deletions(-) create mode 100644 .browserslistrc diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 000000000000..9161c7c0a249 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,2 @@ +last 2 versions +> 5% \ No newline at end of file diff --git a/package.json b/package.json index 2cdf7081261d..165601c4f525 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "angular-route": "1.4.7", "angular-sanitize": "1.5.7", "angular-sortable-view": "0.0.15", - "autoprefixer": "6.5.4", + "autoprefixer": "^9.1.0", "babel-core": "6.21.0", "babel-loader": "7.1.2", "babel-polyfill": "6.20.0", @@ -317,6 +317,7 @@ "nock": "8.0.0", "node-sass": "^4.9.0", "pixelmatch": "4.0.2", + "postcss": "^7.0.2", "prettier": "^1.14.0", "proxyquire": "1.7.11", "simple-git": "1.37.0", diff --git a/src/dev/build/tasks/transpile_scss_task.js b/src/dev/build/tasks/transpile_scss_task.js index 9fc7f4891f8f..69621797ea25 100644 --- a/src/dev/build/tasks/transpile_scss_task.js +++ b/src/dev/build/tasks/transpile_scss_task.js @@ -29,15 +29,12 @@ export const TranspileScssTask = { const { spec$ } = findPluginSpecs({ plugins: { scanDirs, paths: [] } }); const enabledPlugins = await spec$.pipe(toArray()).toPromise(); - function onSuccess(builder) { - log.info(`Compiled SCSS: ${builder.source}`); + try { + const bundles = await buildAll(enabledPlugins); + bundles.forEach(bundle => log.info(`Compiled SCSS: ${bundle.source}`)); + } catch (error) { + const { message, line, file } = error; + throw new Error(`${message} on line ${line} of ${file}`); } - - function onError(builder, e) { - log.error(`Compiling SCSS failed: ${builder.source}`); - throw e; - } - - await buildAll(enabledPlugins, { onSuccess, onError }); } }; \ No newline at end of file diff --git a/src/server/sass/build.js b/src/server/sass/build.js index faeaac0a007a..6aebd9cbb34e 100644 --- a/src/server/sass/build.js +++ b/src/server/sass/build.js @@ -21,16 +21,16 @@ import path from 'path'; import { promisify } from 'util'; import fs from 'fs'; import sass from 'node-sass'; -import minimatch from 'minimatch'; +import autoprefixer from 'autoprefixer'; +import postcss from 'postcss'; const renderSass = promisify(sass.render); const writeFile = promisify(fs.writeFile); export class Build { - constructor(source, options = {}) { + constructor(source) { this.source = source; - this.onSuccess = options.onSuccess || (() => {}); - this.onError = options.onError || (() => {}); + this.includedFiles = [source]; } outputPath() { @@ -46,8 +46,8 @@ export class Build { return path.join(path.dirname(this.source), '**', '*.s{a,c}ss'); } - async buildIfInPath(path) { - if (minimatch(path, this.getGlob())) { + async buildIfIncluded(path) { + if (this.includedFiles && this.includedFiles.includes(path)) { await this.build(); return true; } @@ -60,23 +60,20 @@ export class Build { */ async build() { - try { - const outFile = this.outputPath(); + const outFile = this.outputPath(); + const rendered = await renderSass({ + file: this.source, + outFile, + sourceMap: true, + sourceMapEmbed: true, + }); - const rendered = await renderSass({ - file: this.source, - outFile, - sourceMap: true, - sourceMapEmbed: true, - sourceComments: true, - }); - await writeFile(outFile, rendered.css); + const prefixed = postcss([ autoprefixer ]).process(rendered.css); - this.onSuccess(this); - } catch(e) { - this.onError(this, e); - } + this.includedFiles = rendered.stats.includedFiles; + + await writeFile(outFile, prefixed.css); return this; } diff --git a/src/server/sass/build.test.js b/src/server/sass/build.test.js index 0087040eb414..7e5f55576df8 100644 --- a/src/server/sass/build.test.js +++ b/src/server/sass/build.test.js @@ -38,7 +38,6 @@ describe('SASS builder', () => { expect(sass.render.mock.calls[0][0]).toEqual({ file: '/foo/style.sass', outFile: '/foo/style.css', - sourceComments: true, sourceMap: true, sourceMapEmbed: true }); diff --git a/src/server/sass/build_all.js b/src/server/sass/build_all.js index 461db2e7e2d5..7c616ceeb924 100644 --- a/src/server/sass/build_all.js +++ b/src/server/sass/build_all.js @@ -20,19 +20,18 @@ import { Build } from './build'; import { collectUiExports } from '../../ui/ui_exports'; -export function buildAll(enabledPluginSpecs, { onSuccess, onError }) { +export async function buildAll(enabledPluginSpecs) { const { uiAppSpecs = [] } = collectUiExports(enabledPluginSpecs); - - return Promise.all(uiAppSpecs.reduce((acc, uiAppSpec) => { + const bundles = await Promise.all(uiAppSpecs.map(async uiAppSpec => { if (!uiAppSpec.styleSheetPath) { - return acc; + return; } - const builder = new Build(uiAppSpec.styleSheetPath, { - onSuccess, - onError, - }); + const bundle = new Build(uiAppSpec.styleSheetPath); + await bundle.build(); - return [...acc, builder.build()]; - }, [])); -} \ No newline at end of file + return bundle; + })); + + return bundles.filter(v => v); +} diff --git a/src/server/sass/index.js b/src/server/sass/index.js index ee3eab891d91..5463052e1236 100644 --- a/src/server/sass/index.js +++ b/src/server/sass/index.js @@ -36,18 +36,23 @@ export async function sassMixin(kbnServer, server, config) { } const { buildAll } = require('./build_all'); + let scssBundles = []; + let trackedFiles = new Set(); - function onSuccess(builder) { - server.log(['info', 'scss'], `Compiled CSS: ${builder.source}`); + try { + scssBundles = await buildAll(kbnServer.pluginSpecs); + + scssBundles.forEach(bundle => { + bundle.includedFiles.forEach(file => trackedFiles.add(file)); + server.log(['info', 'scss'], `Compiled CSS: ${bundle.source}`); + }); + } catch(error) { + const { message, line, file } = error; + + trackedFiles.add(file); + server.log(['warning', 'scss'], `${message} on line ${line} of ${file}`); } - function onError(builder, error) { - server.log(['warning', 'scss'], `Compiling CSS failed: ${builder.source}`); - server.log(['warning', 'scss'], error); - } - - const scssBundles = await buildAll(kbnServer.pluginSpecs, { onSuccess, onError }); - /** * Setup Watchers @@ -62,15 +67,51 @@ export async function sassMixin(kbnServer, server, config) { const { FSWatcher } = require('chokidar'); const watcher = new FSWatcher({ ignoreInitial: true }); - scssBundles.forEach(bundle => { - watcher.add(bundle.getGlob()); - }); + watcher.add([...trackedFiles]); watcher.on('all', async (event, path) => { - for (let i = 0; i < scssBundles.length; i++) { - if (await scssBundles[i].buildIfInPath(path)) { + const currentlyTrackedFiles = new Set(); + + server.log(['debug', 'scss'], `${path} triggered ${event}`); + + // build bundles containing the changed file + await Promise.all(scssBundles.map(async bundle => { + try { + if (await bundle.buildIfIncluded(path)) { + bundle.includedFiles.forEach(file => currentlyTrackedFiles.add(file)); + server.log(['info', 'scss'], `Compiled ${bundle.source} due to change in ${path}`); + } + } catch(error) { + const { message, line, file } = error; + currentlyTrackedFiles.add(file); + server.log(['warning', 'scss'], `${message} on line ${line} of ${file}`); + } + }, [])); + + /** + * update watchers + */ + + // un-watch files no longer included in any bundle + trackedFiles.forEach(file => { + if (currentlyTrackedFiles.has(file)) { return; } - } + + watcher.unwatch(file); + server.log(['debug', 'scss'], `No longer watching ${file}`); + }); + + // watch files not previously included in any bundle + currentlyTrackedFiles.forEach(file => { + if (trackedFiles.has(file)) { + return; + } + + watcher.add(file); + server.log(['debug', 'scss'], `Now watching ${file}`); + }); + + trackedFiles = currentlyTrackedFiles; }); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 44fab3137206..5ea8b679b0f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1109,17 +1109,6 @@ autolinker@~0.15.0: version "0.15.3" resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832" -autoprefixer@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.5.4.tgz#1386eb6708ccff36aefff70adc694ecfd60af1b0" - dependencies: - browserslist "~1.4.0" - caniuse-db "^1.0.30000597" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^5.2.6" - postcss-value-parser "^3.2.3" - autoprefixer@^6.3.1: version "6.7.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" @@ -1131,6 +1120,17 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" +autoprefixer@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.1.0.tgz#566a70d1148046b96b31efa08090f1999ffb6d8c" + dependencies: + browserslist "^4.0.1" + caniuse-lite "^1.0.30000872" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.2" + postcss-value-parser "^3.2.3" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -2277,11 +2277,13 @@ browserslist@^1.3.6, browserslist@^1.4.0, browserslist@^1.5.2, browserslist@^1.7 caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.4.0.tgz#9cfdcf5384d9158f5b70da2aa00b30e8ff019049" +browserslist@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.0.1.tgz#61c05ce2a5843c7d96166408bc23d58b5416e818" dependencies: - caniuse-db "^1.0.30000539" + caniuse-lite "^1.0.30000865" + electron-to-chromium "^1.3.52" + node-releases "^1.0.0-alpha.10" bser@^2.0.0: version "2.0.0" @@ -2527,10 +2529,14 @@ caniuse-api@^1.5.2: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000539, caniuse-db@^1.0.30000597, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000815" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000815.tgz#0e218fa133d0d071c886aa041b435258cc746891" +caniuse-lite@^1.0.30000865, caniuse-lite@^1.0.30000872: + version "1.0.30000874" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000874.tgz#a641b1f1c420d58d9b132920ef6ba87bbdcd2223" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -4311,6 +4317,10 @@ electron-to-chromium@^1.2.7: version "1.3.39" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.39.tgz#d7a4696409ca0995e2750156da612c221afad84d" +electron-to-chromium@^1.3.52: + version "1.3.56" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.56.tgz#aad1420d23e9dd8cd2fc2bc53f4928adcf85f02f" + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -9405,6 +9415,12 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" +node-releases@^1.0.0-alpha.10: + version "1.0.0-alpha.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.10.tgz#61c8d5f9b5b2e05d84eba941d05b6f5202f68a2a" + dependencies: + semver "^5.3.0" + node-sass@^4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52" @@ -10501,7 +10517,7 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16, postcss@^5.2.6: +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: version "5.2.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" dependencies: @@ -10518,6 +10534,14 @@ postcss@^6.0.1, postcss@^6.0.2: source-map "^0.6.1" supports-color "^5.3.0" +postcss@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.2.tgz#7b5a109de356804e27f95a960bef0e4d5bc9bb18" + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -12928,7 +12952,7 @@ supports-color@^4.2.1: dependencies: has-flag "^2.0.0" -supports-color@^5.1.0: +supports-color@^5.1.0, supports-color@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" dependencies: