From 890e6db5c9f95e260372fadcc6a0433efac4031a Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 15:11:58 -0600 Subject: [PATCH 01/11] Load postcss config --- .../plugin/minify-css.js | 14 +++- .../standard-minifier-css/plugin/postcss.js | 80 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 packages/standard-minifier-css/plugin/postcss.js diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 18d49f6d4f..2c8048f4f0 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -1,6 +1,7 @@ import sourcemap from "source-map"; import { createHash } from "crypto"; import LRU from "lru-cache"; +import { loadPostCss } from './postcss.js'; Plugin.registerMinifier({ extensions: ["css"], @@ -45,12 +46,17 @@ class CssToolsMinifier { return result; } - async processFilesForBundle (files, options) { - const mode = options.minifyMode; - + async processFilesForBundle (files, { mode }) { if (! files.length) return; - const stylesheets = await this.minifyFiles(files, mode); + const postcssInfo = await loadPostCss(); + + if (postcssInfo.error) { + files[0].error(postcssInfo.error); + return; + } + + const stylesheets = await this.minifyFiles(files, mode, postcssInfo); stylesheets.forEach(stylesheet => { files[0].addStylesheet(stylesheet); diff --git a/packages/standard-minifier-css/plugin/postcss.js b/packages/standard-minifier-css/plugin/postcss.js new file mode 100644 index 0000000000..fa18e436f8 --- /dev/null +++ b/packages/standard-minifier-css/plugin/postcss.js @@ -0,0 +1,80 @@ +let postcssInfo; +let loaded = false; + +const missingPostCssError = new Error([ + '', + `The postcss npm package could not be found in your node_modules`, + 'directory. Please run the following command to install it:', + ' meteor npm install postcss@8', + 'or disable postcss by removing the postcss config.', + '' + ].join('\n')); + +export async function loadPostCss() { + if (loaded) { + return postcssInfo; + } + + let loadConfig; + try { + loadConfig = require('postcss-load-config'); + } catch (e) { + // The app doesn't have this package installed + // Assuming the app doesn't use PostCSS + loaded = true; + + return; + } + + let config; + try { + config = await loadConfig({ meteor: true }); + } catch (e) { + if (e.message.includes('No PostCSS Config found in')) { + // PostCSS is not used by this app + loaded = true; + + return; + } + + if (e.message.includes('Cannot find module \'postcss\'')) { + return { error: missingPostCssError }; + } + + e.message = `While loading postcss config: ${e.message}`; + return { + error: e + }; + } + + let postcss; + try { + postcss = require('postcss'); + } catch (e) { + return { error: missingPostCssError }; + } + + const postcssVersion = require('postcss/package.json').version; + const major = parseInt(postcssVersion.split('.')[0], 10); + if (major !== 8) { + // TODO: should this just be a warning instead? + const error = new Error([ + '', + `Found version ${postcssVersion} of postcss in your node_modules`, + 'directory. standard-minifier-css is only compatible with', + 'version 8 of PostCSS. Please restart Meteor after installing', + 'a supported version of PostCSS', + '' + ].join('\n')); + + return { error }; + } + + loaded = true; + postcssInfo = { + config, + postcss + }; + + return postcssInfo; +} From 81e3d7864be500306b7b0addf8883d81ed6a29b8 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 19:32:28 -0600 Subject: [PATCH 02/11] Run postcss plugins --- .../standard-minifier-css/plugin/postcss.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/standard-minifier-css/plugin/postcss.js b/packages/standard-minifier-css/plugin/postcss.js index fa18e436f8..dc036a7b5d 100644 --- a/packages/standard-minifier-css/plugin/postcss.js +++ b/packages/standard-minifier-css/plugin/postcss.js @@ -1,4 +1,4 @@ -let postcssInfo; +let postcssConfig; let loaded = false; const missingPostCssError = new Error([ @@ -12,18 +12,19 @@ const missingPostCssError = new Error([ export async function loadPostCss() { if (loaded) { - return postcssInfo; + return { postcssConfig }; } let loadConfig; try { loadConfig = require('postcss-load-config'); } catch (e) { + console.log('no postcss-load-config'); // The app doesn't have this package installed // Assuming the app doesn't use PostCSS loaded = true; - return; + return {}; } let config; @@ -32,9 +33,10 @@ export async function loadPostCss() { } catch (e) { if (e.message.includes('No PostCSS Config found in')) { // PostCSS is not used by this app + console.log('no PostCSS config'); loaded = true; - return; + return {}; } if (e.message.includes('Cannot find module \'postcss\'')) { @@ -71,10 +73,8 @@ export async function loadPostCss() { } loaded = true; - postcssInfo = { - config, - postcss - }; + config.postcss = postcss; + postcssConfig = config; - return postcssInfo; + return { postcssConfig }; } From 334fd285009a8a0ef9b0fe801fee5f811237433e Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 20:09:20 -0600 Subject: [PATCH 03/11] Run postcss plugins --- .../plugin/minify-css.js | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 2c8048f4f0..366212fadb 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -18,7 +18,7 @@ class CssToolsMinifier { }); } - async minifyFiles (files, mode) { + async minifyFiles (files, mode, postcssConfig) { const cacheKey = createCacheKey(files, { mode }); const cachedResult = this.cache.get(cacheKey); if (cachedResult) { @@ -26,7 +26,7 @@ class CssToolsMinifier { } let result = []; - const merged = await mergeCss(files); + const merged = await mergeCss(files, postcssConfig); if (mode === 'development') { result = [{ @@ -46,17 +46,17 @@ class CssToolsMinifier { return result; } - async processFilesForBundle (files, { mode }) { + async processFilesForBundle(files, { minifyMode }) { if (! files.length) return; - const postcssInfo = await loadPostCss(); + const { error, postcssConfig } = await loadPostCss(); - if (postcssInfo.error) { + if (error) { files[0].error(postcssInfo.error); return; } - const stylesheets = await this.minifyFiles(files, mode, postcssInfo); + const stylesheets = await this.minifyFiles(files, minifyMode, postcssConfig); stylesheets.forEach(stylesheet => { files[0].addStylesheet(stylesheet); @@ -81,17 +81,37 @@ function disableSourceMappingURLs(css) { // Lints CSS files and merges them into one file, fixing up source maps and // pulling any @import directives up to the top since the CSS spec does not // allow them to appear in the middle of a file. -const mergeCss = Profile("mergeCss", async function (css) { +const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { // Filenames passed to AST manipulator mapped to their original files const originals = {}; - const cssAsts = css.map(function (file) { + const astPromises = css.map(async function (file) { const filename = file.getPathInBundle(); originals[filename] = file; + let ast; try { + let content = file.getContentsAsString(); + + if (postcssConfig) { + const result = await postcssConfig.postcss( + postcssConfig.plugins + ).process(file.getContentsAsString(), { + // TODO: review this + from: process.cwd() + file._source.url?.replace('/__cordova', ''), + parser: postcssConfig.options.parser + }); + + // TODO: test this + result.warnings().forEach(warning => { + warnCb(filename, warning.toString()); + }); + content = result.css; + } + const parseOptions = { source: filename, position: true }; - const css = disableSourceMappingURLs(file.getContentsAsString()); + // TODO: check this + const css = disableSourceMappingURLs(content); ast = CssTools.parseCss(css, parseOptions); ast.filename = filename; } catch (e) { @@ -112,6 +132,8 @@ const mergeCss = Profile("mergeCss", async function (css) { return ast; }); + const cssAsts = await Promise.all(astPromises); + const warnCb = (filename, msg) => { // XXX make this a buildmessage.warning call rather than a random log. // this API would be like buildmessage.error, but wouldn't cause From db3a005f8198592ed3435a24878f4ef931f15011 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 22:17:32 -0600 Subject: [PATCH 04/11] Fix logging postcss warnings --- .../standard-minifier-css/plugin/minify-css.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 366212fadb..8123ecb704 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -102,7 +102,6 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { parser: postcssConfig.options.parser }); - // TODO: test this result.warnings().forEach(warning => { warnCb(filename, warning.toString()); }); @@ -134,13 +133,6 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { const cssAsts = await Promise.all(astPromises); - const warnCb = (filename, msg) => { - // XXX make this a buildmessage.warning call rather than a random log. - // this API would be like buildmessage.error, but wouldn't cause - // the build to fail. - console.log(`${filename}: warn: ${msg}`); - }; - const mergedCssAst = CssTools.mergeCssAsts(cssAsts, warnCb); // Overwrite the CSS files list with the new concatenated file @@ -259,3 +251,10 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { sourceMap: newMap.toString() }; }); + +function warnCb (filename, msg) { + // XXX make this a buildmessage.warning call rather than a random log. + // this API would be like buildmessage.error, but wouldn't cause + // the build to fail. + console.log(`${filename}: warn: ${msg}`); +}; From b6b0a96fa9b91fd9e35cad66b2cbd5a3a54721a0 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 22:18:07 -0600 Subject: [PATCH 05/11] Remove source mapping url before running postcss --- packages/standard-minifier-css/plugin/minify-css.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 8123ecb704..ed80c73fe2 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -91,13 +91,13 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { let ast; try { - let content = file.getContentsAsString(); + let content = disableSourceMappingURLs(file.getContentsAsString()); if (postcssConfig) { const result = await postcssConfig.postcss( postcssConfig.plugins - ).process(file.getContentsAsString(), { - // TODO: review this + ).process(content, { + // TODO: provide a better way to get the file's path from: process.cwd() + file._source.url?.replace('/__cordova', ''), parser: postcssConfig.options.parser }); @@ -109,9 +109,7 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { } const parseOptions = { source: filename, position: true }; - // TODO: check this - const css = disableSourceMappingURLs(content); - ast = CssTools.parseCss(css, parseOptions); + ast = CssTools.parseCss(content, parseOptions); ast.filename = filename; } catch (e) { if (e.reason) { From 92ba7c23faacc16a17903ac5a2d335c042773389 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 22:23:18 -0600 Subject: [PATCH 06/11] Fix return value when stringifiedCss is empty --- packages/standard-minifier-css/plugin/minify-css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index ed80c73fe2..376b7a6c79 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -141,7 +141,7 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { }); if (! stringifiedCss.code) { - return merged; + return { code: '' }; } // Add the contents of the input files to the source map of the new file From dc3c6b42a4f3d66ce06687acd3661606a38639f7 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 22:24:40 -0600 Subject: [PATCH 07/11] Show full stack for unexpected errors from postcss --- packages/standard-minifier-css/plugin/minify-css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 376b7a6c79..7e1337eb59 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -120,7 +120,7 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { }); } else { // Just in case it's not the normal error the library makes. - file.error({message: e.message}); + file.error({message: e.stack}); } return { type: "stylesheet", stylesheet: { rules: [] }, filename }; From 0e85386de358c4d664eac625e4ed23eb467f36b4 Mon Sep 17 00:00:00 2001 From: zodern Date: Tue, 8 Feb 2022 23:12:29 -0600 Subject: [PATCH 08/11] Support postcss dependency messages --- .../plugin/minifyStdCSS/npm-shrinkwrap.json | 30 ++++++++ packages/standard-minifier-css/package.js | 3 +- .../plugin/minify-css.js | 54 +++++++++++--- .../standard-minifier-css/plugin/postcss.js | 74 +++++++++++++++++++ 4 files changed, 151 insertions(+), 10 deletions(-) diff --git a/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json b/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json index 93eb46b3bd..f4ccfc2319 100644 --- a/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json +++ b/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json @@ -6,11 +6,36 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==" }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -21,6 +46,11 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/packages/standard-minifier-css/package.js b/packages/standard-minifier-css/package.js index cf7516ae6d..a00642330b 100644 --- a/packages/standard-minifier-css/package.js +++ b/packages/standard-minifier-css/package.js @@ -14,7 +14,8 @@ Package.registerBuildPlugin({ npmDependencies: { "@babel/runtime": "7.15.3", "source-map": "0.7.3", - "lru-cache": "6.0.0" + "lru-cache": "6.0.0", + "micromatch": "4.0.4" }, sources: [ 'plugin/minify-css.js' diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 7e1337eb59..b1903774a9 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -1,7 +1,7 @@ import sourcemap from "source-map"; import { createHash } from "crypto"; import LRU from "lru-cache"; -import { loadPostCss } from './postcss.js'; +import { loadPostCss, watchAndHashDeps } from './postcss.js'; Plugin.registerMinifier({ extensions: ["css"], @@ -16,13 +16,38 @@ class CssToolsMinifier { this.cache = new LRU({ max: 100 }); + + this.depsHashCache = Object.create(null); + } + + beforeMinify() { + this.depsHashCache = Object.create(null); + } + + watchAndHashDeps(deps, file) { + const cacheKey = JSON.stringify(deps); + + if (cacheKey in this.depsHashCache) { + return this.depsHashCache[cacheKey]; + } + + let hash = watchAndHashDeps(deps, (filePath) => { + return file.readAndWatchFileWithHash(filePath).hash; + }); + this.depsHashCache[cacheKey] = hash; + + return hash; } async minifyFiles (files, mode, postcssConfig) { - const cacheKey = createCacheKey(files, { mode }); + const cacheKey = createCacheKey(files, mode); const cachedResult = this.cache.get(cacheKey); - if (cachedResult) { - return cachedResult; + + if ( + cachedResult && + cachedResult.depsCacheKey === this.watchAndHashDeps(cachedResult.deps, files[0]) + ) { + return cachedResult.stylesheets; } let result = []; @@ -42,7 +67,11 @@ class CssToolsMinifier { })); } - this.cache.set(cacheKey, result); + this.cache.set(cacheKey, { + stylesheets: result, + deps: merged.deps, + depsCacheKey: this.watchAndHashDeps(merged.deps, files[0]) + }); return result; } @@ -64,9 +93,9 @@ class CssToolsMinifier { } } -const createCacheKey = Profile("createCacheKey", function (files, options = {}) { +const createCacheKey = Profile("createCacheKey", function (files, minifyMode) { const hash = createHash("sha1"); - hash.update(JSON.stringify(options)).update("\0"); + hash.update(minifyMode).update("\0"); files.forEach(f => { hash.update(f.getSourceHash()).update("\0"); }); @@ -84,6 +113,7 @@ function disableSourceMappingURLs(css) { const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { // Filenames passed to AST manipulator mapped to their original files const originals = {}; + const deps = []; const astPromises = css.map(async function (file) { const filename = file.getPathInBundle(); @@ -105,6 +135,11 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { result.warnings().forEach(warning => { warnCb(filename, warning.toString()); }); + result.messages.forEach(message => { + if (['dependency', 'dir-dependency'].includes(message.type)) { + deps.push(message); + } + }); content = result.css; } @@ -141,7 +176,7 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { }); if (! stringifiedCss.code) { - return { code: '' }; + return { code: '', deps }; } // Add the contents of the input files to the source map of the new file @@ -246,7 +281,8 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { return { code: stringifiedCss.code, - sourceMap: newMap.toString() + sourceMap: newMap.toString(), + deps }; }); diff --git a/packages/standard-minifier-css/plugin/postcss.js b/packages/standard-minifier-css/plugin/postcss.js index dc036a7b5d..5a0e165588 100644 --- a/packages/standard-minifier-css/plugin/postcss.js +++ b/packages/standard-minifier-css/plugin/postcss.js @@ -1,3 +1,12 @@ +import { createHash } from "crypto"; +import micromatch from 'micromatch'; +import { performance } from 'perf_hooks'; + +var fs = Plugin.fs; +var path = Plugin.path; + +const DEBUG_CACHE = process.env.DEBUG_METEOR_POSTCSS_DEP_CACHE === 'true'; + let postcssConfig; let loaded = false; @@ -78,3 +87,68 @@ export async function loadPostCss() { return { postcssConfig }; } + +export const watchAndHashDeps = Profile( + 'watchAndHashDeps', + function (deps, hashAndWatchFile) { + const hash = createHash('sha1'); + const globsByDir = Object.create(null); + let fileCount = 0; + let folderCount = 0; + let start = performance.now(); + + deps.forEach(dep => { + if (dep.type === 'dependency') { + fileCount += 1; + const fileHash = hashAndWatchFile(dep.file); + hash.update(fileHash).update('\0'); + } else if (dep.type === 'dir-dependency') { + if (dep.dir in globsByDir) { + globsByDir[dep.dir].push(dep.glob || '**'); + } else { + globsByDir[dep.dir] = [dep.glob || '**']; + } + } + }); + + + Object.entries(globsByDir).forEach(([parentDir, globs]) => { + const matchers = globs.map(glob => micromatch.matcher(glob)); + + function walk(relDir) { + const absDir = path.join(parentDir, relDir); + hash.update(absDir).update('\0'); + folderCount += 1; + + const entries = fs.readdirWithTypesSync(absDir); + for (const entry of entries) { + const relPath = path.join(relDir, entry.name); + + if (entry.isFile() && matchers.some(isMatch => isMatch(relPath))) { + const absPath = path.join(absDir, entry.name); + fileCount += 1; + hash.update(hashAndWatchFile(absPath)).update('\0'); + } else if ( + entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.meteor' + ) { + walk(relPath); + } + } + } + + walk('./'); + }); + + let digest = hash.digest('hex'); + + if (DEBUG_CACHE) { + console.log('--- PostCSS Cache Info ---'); + console.log('Glob deps', JSON.stringify(globsByDir, null, 2)); + console.log('File dep count', fileCount); + console.log('Walked folders', folderCount); + console.log('Created dep cache key in', performance.now() - start, 'ms'); + console.log('--------------------------'); + } + + return digest; +}); From f1d8494156f0afc5f5491cd1674657452a5d3e26 Mon Sep 17 00:00:00 2001 From: zodern Date: Wed, 9 Feb 2022 12:37:41 -0600 Subject: [PATCH 09/11] Add excludedMeteorPackages postcss option --- .../standard-minifier-css/plugin/minify-css.js | 4 ++-- packages/standard-minifier-css/plugin/postcss.js | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index b1903774a9..ddb887e292 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -1,7 +1,7 @@ import sourcemap from "source-map"; import { createHash } from "crypto"; import LRU from "lru-cache"; -import { loadPostCss, watchAndHashDeps } from './postcss.js'; +import { loadPostCss, watchAndHashDeps, usePostCss } from './postcss.js'; Plugin.registerMinifier({ extensions: ["css"], @@ -123,7 +123,7 @@ const mergeCss = Profile("mergeCss", async function (css, postcssConfig) { try { let content = disableSourceMappingURLs(file.getContentsAsString()); - if (postcssConfig) { + if (usePostCss(file, postcssConfig)) { const result = await postcssConfig.postcss( postcssConfig.plugins ).process(content, { diff --git a/packages/standard-minifier-css/plugin/postcss.js b/packages/standard-minifier-css/plugin/postcss.js index 5a0e165588..a1c2e655dc 100644 --- a/packages/standard-minifier-css/plugin/postcss.js +++ b/packages/standard-minifier-css/plugin/postcss.js @@ -42,7 +42,6 @@ export async function loadPostCss() { } catch (e) { if (e.message.includes('No PostCSS Config found in')) { // PostCSS is not used by this app - console.log('no PostCSS config'); loaded = true; return {}; @@ -88,6 +87,21 @@ export async function loadPostCss() { return { postcssConfig }; } +export function usePostCss(file, postcssConfig) { + if (!postcssConfig) { + return false; + } + + const excludedPackages = postcssConfig.options.excludedMeteorPackages || []; + const path = file.getPathInBundle(); + + const excluded = excludedPackages.some(name => { + return path.includes(`packages/${name.replace(':', '_')}`) + }); + + return !excluded; +} + export const watchAndHashDeps = Profile( 'watchAndHashDeps', function (deps, hashAndWatchFile) { From 989d0071305bc80d0ed1b74a9bc6a968312f957f Mon Sep 17 00:00:00 2001 From: zodern Date: Wed, 9 Feb 2022 12:55:14 -0600 Subject: [PATCH 10/11] Add postcss docs --- packages/standard-minifier-css/README.md | 45 ++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/standard-minifier-css/README.md b/packages/standard-minifier-css/README.md index bf0d0f58db..15a45d089e 100644 --- a/packages/standard-minifier-css/README.md +++ b/packages/standard-minifier-css/README.md @@ -7,5 +7,46 @@ Standard Minifier for CSS This package provides a minifier plugin used for Meteor apps by default. -The CSS minifier mostly reduces amount of white-space parsing CSS with -ParseCSS. \ No newline at end of file +## Post CSS + +This package can optionally run [PostCSS](https://postcss.org/) plugins on the css files in your app. To enable: + +1. Install npm peer dependencies: + +```sh +meteor npm install -D postcss postcss-load-config +``` + +2. Add PostCSS Config. Create a `postcss.config.js` file and add a config: + +```js +module.exports = { + plugins: { + autoprefixer: {}, + } +}; +``` + +> The example config enables the `autoprefixer` postcss plugin. You can install the plugin by running `meteor npm install -D autoprefixer`. + +Learn more about [configuring postcss](https://github.com/postcss/postcss-load-config#packagejson) or find a list of [available plugins](https://www.postcss.parts/). + +### Exclude Meteor Packages + +In addition to the css files in your app, PostCSS will also process the css files added from Meteor packages. In case you do not want these files to be processed, or they are not compatible with your PostCSS config, you can have PostCSS ignore them by using the `excludedMeteorPackages` option: + +```js +module.exports = { + plugins: { + autoprefixer: {}, + }, + excludedMeteorPackages: [ + 'github-config-ui', + 'constellation:console' + ] +}; +``` + +### Tailwind CSS + +Tailwind CSS is fully supported. Since HMR applies updates to js files earlier than the css is updated, there can be a delay when using a Tailwind CSS class the first time before the styles are applied. From 0540e7f5df62fcd4f008dcd7f274b4b0eb0cf5be Mon Sep 17 00:00:00 2001 From: zodern Date: Wed, 9 Feb 2022 12:55:46 -0600 Subject: [PATCH 11/11] Remove minifier-css-postcss --- packages/minifier-css-postcss/.gitignore | 3 - packages/minifier-css-postcss/LICENSE | 21 - packages/minifier-css-postcss/README.md | 251 ----------- .../minifier-css-postcss/package-lock.json | 57 --- packages/minifier-css-postcss/package.js | 23 -- packages/minifier-css-postcss/package.json | 6 - .../plugin/minify-css-postcss.js | 391 ------------------ 7 files changed, 752 deletions(-) delete mode 100644 packages/minifier-css-postcss/.gitignore delete mode 100644 packages/minifier-css-postcss/LICENSE delete mode 100644 packages/minifier-css-postcss/README.md delete mode 100644 packages/minifier-css-postcss/package-lock.json delete mode 100644 packages/minifier-css-postcss/package.js delete mode 100644 packages/minifier-css-postcss/package.json delete mode 100644 packages/minifier-css-postcss/plugin/minify-css-postcss.js diff --git a/packages/minifier-css-postcss/.gitignore b/packages/minifier-css-postcss/.gitignore deleted file mode 100644 index 688b8efb92..0000000000 --- a/packages/minifier-css-postcss/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.npm -node_modules/ -.idea/ diff --git a/packages/minifier-css-postcss/LICENSE b/packages/minifier-css-postcss/LICENSE deleted file mode 100644 index c7cacd950a..0000000000 --- a/packages/minifier-css-postcss/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Julian Ćwirko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/packages/minifier-css-postcss/README.md b/packages/minifier-css-postcss/README.md deleted file mode 100644 index 7917c9f345..0000000000 --- a/packages/minifier-css-postcss/README.md +++ /dev/null @@ -1,251 +0,0 @@ -# CSS Minifier with PostCSS - -Meteor CSS Minifier with [PostCSS](https://github.com/postcss/postcss) processing. - -This package allows you to use PostCSS plugins with **.css files**. You can add your custom plugins by adding Npm packages using `package.json`. You can also use your favourite preprocessor side by side with this package. - -It allows you to enable many PostCSS plugins, for example **Autoprefixer** for all preprocessors you use. - -## Usage - -1. Remove `standard-minifier-css` package - - ```sh - meteor remove standard-minifier-css - ``` - -2. Add `minifier-css-postcss` package - - ```sh - meteor add minifier-css-postcss - ``` - -3. Add peer NPM dependencies - - ```sh - meteor npm install -D postcss@8.4.6 postcss-load-config@3.1.1 - ``` - -4. Add PostCSS plugins: - - You can also install it like `npm install -D autoprefixer`. - - Then you need to prepare PostCSS configuration under the `postcss.plugins`. - - **Important:** Even if you don't want to provide any options you should list your PostCSS plugins in `postcss.plugins` key. This works that way because order here is important. For example 'postcss-easy-import' should be always first PostCSS plugin on the list and 'autoprefixer' should be the last PostCSS plugin on the list. And devDependencies items can be automatically reordered when installing new by `npm install ... -D`. - - See example: - - **package.json (example):** - - ```json - { - "name": "demo PostCSS app", - "version": "1.0.0", - "description": "", - "author": "", - "devDependencies": { - "autoprefixer": "^6.5.1", - "mocha": "^3.1.2", - "postcss": "^6.0.22", - "postcss-easy-import": "^1.0.1", - "postcss-load-config": "^1.2.0", - "postcss-nested": "^1.0.0", - "postcss-simple-vars": "^3.0.0", - "rucksack-css": "^0.8.6" - }, - "postcss": { - "plugins": { - "postcss-easy-import": {}, - "postcss-nested": {}, - "postcss-simple-vars": {}, - "rucksack-css": {}, - "autoprefixer": {"browsers": ["last 2 versions"]} - } - } - } - ``` - - Make sure that the plugins that you list in "plugins" are also in "devDependencies" as well. You may not need the plugins in this example, so please include them only if you need them. - - Remember to run `npm install` or `npm update` after changes. - - You can add more plugins here. - - If you want to change something in postcss config later, you should restart your app and also change any .css file to rerun build plugin. - -6. Create your standard `.css` files with additional features according to PostCSS plugins you use. - -## PostCSS parsers - -You can configure parser for PostCSS. To do this you can add `parser` key in the `package.json` file under the `postcss` key. Let's see an example: - -```json -{ - "name": "demo PostCSS app", - "version": "1.0.0", - "description": "", - "author": "", - "devDependencies": { - "autoprefixer": "^6.5.1", - "postcss-safe-parser": "^2.0.0" - }, - "postcss": { - "plugins": { - "autoprefixer": {"browsers": ["last 2 versions"]} - }, - "parser": "postcss-safe-parser" - } -} -``` - -As you can see we use here `postcss-safe-parser` which will repair broken css syntax. This is just one example. You can find a list of parsers here: [https://github.com/postcss/postcss#syntaxes](https://github.com/postcss/postcss#syntaxes). You can use `postcss-scss` parser or `postcss-less` parser. - -## Exclude Meteor Packages - -Because PostCSS processes all CSS files in Meteor, it will also process CSS files from Meteor packages. This is good in most cases and will not break anything, but sometimes it could be problematic. - -If you have installed a package which is problematic and PostCSS plugins can't process the CSS files from that package you can exclude it in the process. See for example this issue: [#14](https://github.com/juliancwirko/meteor-postcss/issues/14). In this case you need to exclude `constellation:console` package because it uses not standard CSS in its files. PostCSS plugin can't process that file. You can exclude it so it will be not processed by PostCSS, but it will be still bundled as is. - -If you want to exclude a package you need to use `postcss.excludedPackages` key, see the example below: - -```json -{ - "name": "demo PostCSS app", - "version": "1.0.0", - "description": "", - "author": "", - "devDependencies": { - "autoprefixer": "^6.5.1", - "postcss-safe-parser": "^2.0.0" - }, - "postcss": { - "plugins": { - "autoprefixer": {"browsers": ["last 2 versions"]} - }, - "parser": "postcss-safe-parser", - "excludedPackages": ["constellation:console"] - } -} -``` - -**Remember that you should provide a package name which contains a problematic CSS file and not global wrapper package** In this example you want to install `babrahams:constellation` but in fact the problematic package is `constellation:console` which is installed with `babrahams:constellation`. You'll find which package makes troubles by looking into the consolle errors. For example here we have something like: - -```sh -While minifying app stylesheet: - packages/constellation_console/client/Constellation.css:118:3: postcss-simple-vars: - /workspace/meteor/postcss-demo/packages/constellation_console/client/Constellation.css:118:3: Undefined variable $c1 - - Css Syntax Error. - - postcss-simple-vars: /workspace/meteor/postcss-demo/packages/constellation_console/client/Constellation.css:118:3: Undefined variable $c1 - background-image: -o-linear-gradient(#000, #000); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$c1', endColorstr='$c2',GradientType=0); - ^ - color: rgba(255, 255, 255, 0.6); -``` - -So we know that this is the problem with `constellation:console` package. - -## Imports with PostCSS - -You can use imports with [postcss-easy-import](https://github.com/postcss/postcss-easy-import) plugin. **Remember that postcss-easy-import plugin should be loaded first (so put it on the first place in the packages.json file under the 'postcss.plugins' key)**. - -You need to use `.import.css` extension and standard import like with preprocessors `@import "my-file.import.css";` Files with `.import.css` will be ommited by css minifier from this package. You can also put them in an `imports` folder (from Meteor 1.3). Also read more about `postcss-easy-import` and `postcss-import` which is a part of the first one. - -Imports from Meteor packages will not work. But there is a good news too. from Meteor 1.3 you can use standard Npm packages and imports from `node_modules` should work. So you will be able to import css files from instaled Npm packages. You will be able to do something like: `@import 'my-npm-lib/styles.css'`; - -## Usage with preprocessors like Stylus and Sass - -You can use it side by side with your favourite preprocessor. There is an example in the demo app. - -You should be able to use PostCSS plugins syntax in the .styl or .scss files too. (Tested only with Stylus). - -## Tailwind CSS - -Tailwind CSS is supported when used with Meteor `2.6.1` or newer. - -Since HMR applies updates to js files earlier than the css is updated, there can be a delay when using a Tailwind CSS class the first time before the styles are applied. - -## Alternative configuration locations - -This package uses [postcss-load-config](https://github.com/michael-ciniawsky/postcss-load-config) to load -configuration for PostCSS. This allows you to put PostCSS configuration into alternative locations and not -just `package.json`. An interesting option is to put configuration into `.postcssrc.js` file in the root -directory of your app, which allows you to dynamically decide on the configuration. Example: - -```js -module.exports = (ctx) => { - // This flag is set when loading configuration by this package. - if (ctx.meteor) { - const config = { - plugins: { - 'postcss-easy-import': {}, - }, - }; - - if (ctx.env === 'production') { - // "autoprefixer" is reported to be slow, - // so we use it only in production. - config.plugins.autoprefixer = { - browsers: [ - 'last 2 versions', - ], - }; - } - - return config; - } - else { - return {}; - } -}; -``` - -## Demo test repo - -Check out the demo repo. This is the best way of learning. - -- [https://github.com/juliancwirko/meteor-postcss-test](https://github.com/juliancwirko/meteor-postcss-test) -- [Discussion and updates](https://forums.meteor.com/t/postcss-package-and-meteor-build-plugin-questions/12454?u=juliancwirko) - -## License - -MIT - -## Also check out - -- Blog post: [Some things you may think about PostCSS... and you might be wrong](http://julian.io/some-things-you-may-think-about-postcss-and-you-might-be-wrong/) -- Blog post: [How to use PostCSS in Meteor](http://julian.io/how-to-use-postcss-in-meteor/) -- Blog post: [How to use CSS linter in Meteor](https://medium.com/@juliancwirko/how-to-use-css-linter-in-meteor-c60b2f24f969) (example of PostCSS plugin usage) -- [PostCSS and Bootstrap 4 with Scss from Npm package - experiment](https://github.com/juliancwirko/meteor-bootstrap-postcss-test) -- [sGrid - Flexbox Grid System for Stylus with PostCSS](https://packosphere.com/juliancwirko/s-grid) - -## Changelog - -This package was incorporated into core in Meteor 2.6.1 so the changes after the one below are going to appear in Meteor [changelog](https://docs.meteor.com/changelog.html). - -The last community maintained version of this package was [juliancwirko:postcss](https://github.com/Meteor-Community-Packages/meteor-postcss). - -- v2.0.7 Update postcss to 8.3.x and postcss-load-config to 3.1.x, other dependencies updated as well. -- v2.0.6 Update tmeasday:check-npm-versions dep to 1.0.1 -- v2.0.5 Update/fix dependency checks -- v2.0.3 Restoring the use of app-module-path -- v2.0.2 Moved to use peer NPM dependencies -- v2.0.1 Bumping PostCSS to 6.0.22 -- v2.0.0 Started using postcss-load-config for loading configuration -- v1.3.0 Bumping PostCSS to 6.0.17 -- v1.2.0 Updates (works quite well with Meteor 1.4.2) -- v1.1.1 Removed `fs.existsSync` call because of [#18](https://github.com/juliancwirko/meteor-postcss/issues/18) -- v1.1.0 Exclude Meteor package option [#14](https://github.com/juliancwirko/meteor-postcss/issues/14) -- v1.0.0 Version bump for Meteor 1.3 -- v1.0.0-rc.12 Version bump for Meteor 1.3 rc 12 -- v1.0.0-rc.10 Version bump for Meteor 1.3 rc 10 -- v1.0.0-rc.4 Version bump for Meteor 1.3 rc 4 -- v1.0.0-rc.2 Version bump for Meteor 1.3 rc 2 -- v1.0.0-beta.11 Versions bump for Meteor 1.3 beta 11 -- v1.0.0-beta.1 Modifications for Meteor 1.3 beta 4 -- v0.2.5 Removed Promise Polyfill [#4](https://github.com/juliancwirko/meteor-postcss/pull/4) -- v0.2.4 Catch PostCSS 'CssSyntaxError' [#3](https://github.com/juliancwirko/meteor-postcss/issues/3) -- v0.2.3 PostCSS (v5.0.12) version bump -- v0.2.2 PostCSS (v5.0.11) version bump - [performance improvements](https://evilmartians.com/chronicles/postcss-1_5x-faster) diff --git a/packages/minifier-css-postcss/package-lock.json b/packages/minifier-css-postcss/package-lock.json deleted file mode 100644 index 5d1b52ad43..0000000000 --- a/packages/minifier-css-postcss/package-lock.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true - }, - "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", - "dev": true, - "requires": { - "nanoid": "^3.2.0", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-load-config": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.1.tgz", - "integrity": "sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.4", - "yaml": "^1.10.2" - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - } - } -} diff --git a/packages/minifier-css-postcss/package.js b/packages/minifier-css-postcss/package.js deleted file mode 100644 index 63c4c462f8..0000000000 --- a/packages/minifier-css-postcss/package.js +++ /dev/null @@ -1,23 +0,0 @@ -Package.describe({ - summary: 'Minifier for Meteor with PostCSS processing', - version: '1.0.0-beta261.0', - name: 'minifier-css-postcss' -}); - -Package.registerBuildPlugin({ - name: 'minifier-css-postcss', - use: ['ecmascript', 'minifier-css'], - npmDependencies: { - 'source-map': '0.5.6', - 'app-module-path': '2.2.0', - 'lru-cache': '6.0.0', - 'micromatch': '4.0.4' - }, - sources: [ - 'plugin/minify-css-postcss.js' - ] -}); - -Package.onUse(function (api) { - api.use('isobuild:minifier-plugin@1.0.0'); -}); diff --git a/packages/minifier-css-postcss/package.json b/packages/minifier-css-postcss/package.json deleted file mode 100644 index 62edf7acee..0000000000 --- a/packages/minifier-css-postcss/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "postcss": "^8.4.6", - "postcss-load-config": "^3.1.1" - } -} diff --git a/packages/minifier-css-postcss/plugin/minify-css-postcss.js b/packages/minifier-css-postcss/plugin/minify-css-postcss.js deleted file mode 100644 index 25d77891c4..0000000000 --- a/packages/minifier-css-postcss/plugin/minify-css-postcss.js +++ /dev/null @@ -1,391 +0,0 @@ -// Makes sure we can load peer dependencies from app's directory. -// See: https://github.com/juliancwirko/meteor-postcss/issues/15 -// https://github.com/meteor/meteor/issues/10827 -Npm.require('app-module-path/cwd'); - -import Future from 'fibers/future'; -import sourcemap from 'source-map'; -import { createHash } from "crypto"; -import micromatch from 'micromatch'; -import LRU from 'lru-cache'; -import { performance } from 'perf_hooks'; - -const DEBUG_CACHE = process.env.DEBUG_METEOR_POSTCSS_CACHE === 'true'; - -var fs = Plugin.fs; -var path = Plugin.path; - -Plugin.registerMinifier({ - extensions: ['css'] -}, function () { - const minifier = new CssToolsMinifier(); - return minifier; -}); - -var loaded = false; -var postcssConfigPlugins = []; -var postcssConfigParser = null; -var postcssConfigExcludedPackages = []; - -var loadPostcssConfig = function () { - if (!loaded) { - loaded = true; - - var config; - try { - const load = require('postcss-load-config'); - config = Promise.await(load({meteor: true})); - postcssConfigPlugins = config.plugins || []; - postcssConfigParser = config.options.parser || null; - postcssConfigExcludedPackages = config.options.excludedPackages || []; - // There is also "config.file" which is a path to the file we use to force - // Meteor reload on any change, but it seems this is not (yet) possible. - } - catch (error) { - // Do not emit an error if the error is that no config can be found. - if (error.message.indexOf('No PostCSS Config found') < 0) { - throw error; - } - } - } -}; - -var isNotInExcludedPackages = function (excludedPackages, pathInBundle) { - let processedPackageName; - let exclArr = []; - if (excludedPackages && excludedPackages instanceof Array) { - exclArr = excludedPackages.map(packageName => { - processedPackageName = packageName && packageName.replace(':', '_'); - return pathInBundle && pathInBundle.indexOf('packages/' + processedPackageName) > -1; - }); - } - return exclArr.indexOf(true) === -1; -}; - -var isNotImport = function (inputFileUrl) { - return !(/\.import\.css$/.test(inputFileUrl) || - /(?:^|\/)imports\//.test(inputFileUrl)); -}; - -var watchAndHashDeps = Profile('watchAndHashDeps', function (deps, hashAndWatchFile) { - const hash = createHash('sha1'); - const globsByDir = Object.create(null); - let fileCount = 0; - let folderCount = 0; - let start = performance.now(); - - deps.forEach(dep => { - if (dep.type === 'dependency') { - fileCount += 1; - const fileHash = hashAndWatchFile(dep.file); - hash.update(fileHash).update('\0'); - } else if (dep.type === 'dir-dependency') { - if (dep.dir in globsByDir) { - globsByDir[dep.dir].push(dep.glob || '**'); - } else { - globsByDir[dep.dir] = [dep.glob || '**']; - } - } - }); - - - Object.entries(globsByDir).forEach(([ parentDir, globs ]) => { - const matchers = globs.map(glob => micromatch.matcher(glob)); - - function walk(relDir) { - const absDir = path.join(parentDir, relDir); - hash.update(absDir).update('\0'); - folderCount += 1; - - const entries = fs.readdirWithTypesSync(absDir); - for (const entry of entries) { - const relPath = path.join(relDir, entry.name); - - if (entry.isFile() && matchers.some(isMatch => isMatch(relPath))) { - const absPath = path.join(absDir, entry.name); - fileCount += 1; - hash.update(hashAndWatchFile(absPath)).update('\0'); - } else if ( - entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.meteor' - ) { - walk(relPath); - } - } - } - - walk('./'); - }); - - let digest = hash.digest('hex'); - - if (DEBUG_CACHE) { - console.log('--- PostCSS Cache Info ---'); - console.log('Glob deps', JSON.stringify(globsByDir, null, 2)); - console.log('File dep count', fileCount); - console.log('Walked folders', folderCount); - console.log('Created dep cache key in', performance.now() - start, 'ms'); - console.log('--------------------------'); - } - - return digest; -}); - -const createCacheKey = Profile("createCacheKey", function (files, mode) { - const hash = createHash("sha1"); - hash.update(mode).update("\0"); - files.forEach(f => { - hash.update(f.getSourceHash()).update("\0"); - }); - return hash.digest("hex"); -}); - -function CssToolsMinifier() { - this.mergeCache = new LRU({ - max: 100 - }); - this.depsHashCache = Object.create(null); -}; - -CssToolsMinifier.prototype.watchAndHashDeps = function (deps, file) { - if (typeof file.readAndWatchFileWithHash !== 'function') { - DEBUG_CACHE && console.log('PostCSS: ignoring deps; old Meteor version'); - - return null; - } - - const cacheKey = JSON.stringify(deps); - - if (cacheKey in this.depsHashCache) { - return this.depsHashCache[cacheKey] - } - - let hash = watchAndHashDeps(deps, (filePath) => { - // Calling readAndWatchFileWithHash on a path ensures Meteor will - // rebuild if that file is modified - return file.readAndWatchFileWithHash(filePath).hash; - }); - this.depsHashCache[cacheKey] = hash; - - return hash; -}; - -CssToolsMinifier.prototype.beforeMinify = function () { - this.depsHashCache = Object.create(null); -}; - -CssToolsMinifier.prototype.processFilesForBundle = function (files, options) { - loadPostcssConfig(); - - var mode = options.minifyMode; - - if (!files.length) return; - - var filesToMerge = []; - - files.forEach(function (file) { - if (isNotImport(file._source.url)) { - filesToMerge.push(file); - } - }); - - const cacheKey = createCacheKey(filesToMerge, mode); - let merged = this.mergeCache.get(cacheKey); - - // watchAndHashDeps has to be run at least once during every (re)build - // to ensure Meteor watches all of the deps (the list of files to watch is - // reset during every build) - if ( - !merged || merged.depsCacheKey !== this.watchAndHashDeps(merged.deps, files[0]) - ) { - DEBUG_CACHE && console.log('PostCSS - not cached'); - - merged = mergeCss(filesToMerge); - this.mergeCache.set(cacheKey, { - ...merged, - depsCacheKey: this.watchAndHashDeps(merged.deps, files[0]) - }); - } else if (DEBUG_CACHE) { - console.log('PostCSS - using cached result'); - } - - if (mode === 'development') { - files[0].addStylesheet({ - data: merged.code, - sourceMap: merged.sourceMap, - path: 'merged-stylesheets.css' - }); - return; - } - - var minifiedFiles = CssTools.minifyCss(merged.code); - - if (files.length) { - minifiedFiles.forEach(function (minified) { - files[0].addStylesheet({ - data: minified - }); - }); - } -}; - -// Lints CSS files and merges them into one file, fixing up source maps and -// pulling any @import directives up to the top since the CSS spec does not -// allow them to appear in the middle of a file. -var mergeCss = function (css) { - // Filenames passed to AST manipulator mapped to their original files - var originals = {}; - var postCSS = require('postcss'); - var deps = []; - - var cssAsts = css.map(function (file) { - var filename = file.getPathInBundle(); - originals[filename] = file; - - var f = new Future; - - var css; - var postres; - var isFileForPostCSS; - - if (isNotInExcludedPackages(postcssConfigExcludedPackages, file.getPathInBundle())) { - isFileForPostCSS = true; - } else { - isFileForPostCSS = false; - } - - postCSS(isFileForPostCSS ? postcssConfigPlugins : []) - .process(file.getContentsAsString(), { - from: process.cwd() + file._source.url?.replace('/__cordova', ''), - parser: postcssConfigParser - }) - .then(function (result) { - result.warnings().forEach(function (warn) { - process.stderr.write(warn.toString()); - }); - f.return(result); - }) - .catch(function (error) { - var errMsg = error.message; - if (error.name === 'CssSyntaxError') { - errMsg = error.message + '\n\n' + 'Css Syntax Error.' + '\n\n' + error.message + error.showSourceCode() - } - error.message = errMsg; - f.return(error); - }); - - try { - var parseOptions = { - source: filename, - position: true - }; - - postres = f.wait(); - - if (postres.name === 'CssSyntaxError') { - throw postres; - } - - if (postres.messages) { - postres.messages.forEach(message => { - if ( - message.type === 'dependency' || - message.type === 'dir-dependency' - ) { - deps.push(message); - } - }); - } - - css = postres.css; - - var ast = CssTools.parseCss(css, parseOptions); - ast.filename = filename; - } catch (e) { - - if (e.name === 'CssSyntaxError') { - file.error({ - message: e.message, - line: e.line, - column: e.column - }); - } else if (e.reason) { - file.error({ - message: e.reason, - line: e.line, - column: e.column - }); - } else { - // Just in case it's not the normal error the library makes. - file.error({ - message: e.message - }); - } - - return { - type: "stylesheet", - stylesheet: { - rules: [] - }, - filename: filename - }; - } - - return ast; - }); - - var warnCb = function (filename, msg) { - // XXX make this a buildmessage.warning call rather than a random log. - // this API would be like buildmessage.error, but wouldn't cause - // the build to fail. - console.log(filename + ': warn: ' + msg); - }; - - var mergedCssAst = CssTools.mergeCssAsts(cssAsts, warnCb); - - // Overwrite the CSS files list with the new concatenated file - var stringifiedCss = CssTools.stringifyCss(mergedCssAst, { - sourcemap: true, - // don't try to read the referenced sourcemaps from the input - inputSourcemaps: false - }); - - if (!stringifiedCss.code) { - return { - code: '', - deps - }; - } - - // Add the contents of the input files to the source map of the new file - stringifiedCss.map.sourcesContent = - stringifiedCss.map.sources.map(function (filename) { - return originals[filename].getContentsAsString(); - }); - - // If any input files had source maps, apply them. - // Ex.: less -> css source map should be composed with css -> css source map - var newMap = sourcemap.SourceMapGenerator.fromSourceMap( - new sourcemap.SourceMapConsumer(stringifiedCss.map)); - - Object.keys(originals).forEach(function (name) { - var file = originals[name]; - if (!file.getSourceMap()) - return; - try { - newMap.applySourceMap( - new sourcemap.SourceMapConsumer(file.getSourceMap()), name); - } catch (err) { - // If we can't apply the source map, silently drop it. - // - // XXX This is here because there are some less files that - // produce source maps that throw when consumed. We should - // figure out exactly why and fix it, but this will do for now. - } - }); - - return { - code: stringifiedCss.code, - sourceMap: newMap.toString(), - deps - }; -};