From ca07d970e31687369b3dcd8cad57e4efbaaab431 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Mon, 17 May 2021 14:35:49 +0100 Subject: [PATCH 1/5] Add support for `dir-dependency` messages --- index.js | 31 ++++++++++++++++++++++++++++--- lib/DependencyGraph.js | 15 +++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 66ef2af..d596f6f 100644 --- a/index.js +++ b/index.js @@ -123,8 +123,14 @@ Promise.resolve() if (input.includes(file)) recompile.push(file) + const dependants = depGraph + .dependantsOf(file) + .concat(getAncestorDirs(file).flatMap(depGraph.dependantsOf)) + // deduplicate + .filter((value, index, array) => array.indexOf(value) === index) + recompile = recompile.concat( - depGraph.dependantsOf(file).filter((file) => input.includes(file)) + dependants.filter((file) => input.includes(file)) ) if (!recompile.length) recompile = input @@ -271,9 +277,17 @@ function dependencies(results) { if (result.messages <= 0) return result.messages - .filter((msg) => (msg.type === 'dependency' ? msg : '')) + .filter((msg) => + msg.type === 'dependency' || msg.type === 'dir-dependency' ? msg : '' + ) .map(depGraph.add) - .forEach((dependency) => messages.push(dependency.file)) + .forEach((dependency) => { + if (dependency.type === 'dependency') { + messages.push(dependency.file) + } else { + messages.push(dependency.dir) + } + }) }) return messages @@ -298,3 +312,14 @@ function error(err) { if (argv.watch) return process.exit(1) } + +// Input: '/imports/components/button.css' +// Output: ['/imports/components', '/imports', '/'] +function getAncestorDirs(fileOrDir) { + const { root } = path.parse(fileOrDir) + if (fileOrDir === root) { + return [] + } + const parentDir = path.dirname(fileOrDir) + return [parentDir, ...getAncestorDirs(parentDir)] +} diff --git a/lib/DependencyGraph.js b/lib/DependencyGraph.js index 03fae2e..1035dbb 100644 --- a/lib/DependencyGraph.js +++ b/lib/DependencyGraph.js @@ -7,11 +7,18 @@ module.exports = function () { return { add(message) { message.parent = path.resolve(message.parent) - message.file = path.resolve(message.file) - graph.addNode(message.parent) - graph.addNode(message.file) - graph.addDependency(message.parent, message.file) + + if (message.type === 'dependency') { + message.file = path.resolve(message.file) + graph.addNode(message.file) + graph.addDependency(message.parent, message.file) + } else { + message.dir = path.resolve(message.dir) + graph.addNode(message.dir) + graph.addDependency(message.parent, message.dir) + } + return message }, dependantsOf(node) { From 7cc9d4b46ea97cf5e9df7e2b5a178a5c1af47e7d Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Mon, 17 May 2021 15:04:52 +0100 Subject: [PATCH 2/5] Fix DependencyGraph test --- lib/DependencyGraph.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/DependencyGraph.js b/lib/DependencyGraph.js index 1035dbb..c293cda 100644 --- a/lib/DependencyGraph.js +++ b/lib/DependencyGraph.js @@ -9,14 +9,14 @@ module.exports = function () { message.parent = path.resolve(message.parent) graph.addNode(message.parent) - if (message.type === 'dependency') { - message.file = path.resolve(message.file) - graph.addNode(message.file) - graph.addDependency(message.parent, message.file) - } else { + if (message.type === 'dir-dependency') { message.dir = path.resolve(message.dir) graph.addNode(message.dir) graph.addDependency(message.parent, message.dir) + } else { + message.file = path.resolve(message.file) + graph.addNode(message.file) + graph.addDependency(message.parent, message.file) } return message From 2d58ff6d1292dff1d6957c27004dc64f8b27db1c Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Wed, 26 May 2021 13:46:05 +0100 Subject: [PATCH 3/5] update `if` statement for consistency --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index d596f6f..d917c2f 100644 --- a/index.js +++ b/index.js @@ -282,10 +282,10 @@ function dependencies(results) { ) .map(depGraph.add) .forEach((dependency) => { - if (dependency.type === 'dependency') { - messages.push(dependency.file) - } else { + if (dependency.type === 'dir-dependency') { messages.push(dependency.dir) + } else { + messages.push(dependency.file) } }) }) From 4d280c87f782b3e3f448e177b8ebd1cdd92d6aa3 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Tue, 1 Jun 2021 15:52:21 +0100 Subject: [PATCH 4/5] Deduplicate recompile files using a set --- index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/index.js b/index.js index d917c2f..945611a 100644 --- a/index.js +++ b/index.js @@ -126,8 +126,6 @@ Promise.resolve() const dependants = depGraph .dependantsOf(file) .concat(getAncestorDirs(file).flatMap(depGraph.dependantsOf)) - // deduplicate - .filter((value, index, array) => array.indexOf(value) === index) recompile = recompile.concat( dependants.filter((file) => input.includes(file)) @@ -135,7 +133,7 @@ Promise.resolve() if (!recompile.length) recompile = input - return files(recompile) + return files([...new Set(recompile)]) .then((results) => watcher.add(dependencies(results))) .then(printMessage) .catch(error) From 57a2ef4c9e68c15a2489ccfb788fb8ed57c96b54 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Tue, 1 Jun 2021 15:53:34 +0100 Subject: [PATCH 5/5] Add dependency tests --- test/watch.js | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/test/watch.js b/test/watch.js index a2377a8..5d2fdfe 100644 --- a/test/watch.js +++ b/test/watch.js @@ -299,3 +299,198 @@ testCb('--watch does exit on closing stdin (Ctrl-D/EOF)', (t) => { }) cp.stdin.end() }) + +testCb('--watch watches dependencies', (t) => { + let cp + + t.plan(2) + + ENV('', ['s.css', 'a.css', 'b.css']).then((dir) => { + fs.writeFile( + path.join(dir, 'postcss.config.js'), + ` + const fs = require('fs') + module.exports = { + plugins: [ + (root, result) => { + const file = '${path.resolve(dir, 'a.css')}' + result.messages.push({ + plugin: 'test', + type: 'dependency', + file, + parent: result.opts.from, + }) + root.nodes = [] + root.append(fs.readFileSync(file, 'utf8')) + return root + } + ] + } + ` + ) + .then(() => { + // Init watcher: + const watcher = chokidar.watch('.', { + cwd: dir, + ignoreInitial: true, + awaitWriteFinish: true, + }) + + // On the first output: + watcher.on('add', (p) => { + // Assert, then change the source file + if (p === 'output.css') { + isEqual(p, 'test/fixtures/a.css') + .then(() => read('test/fixtures/b.css')) + .then((css) => fs.writeFile(path.join(dir, 'a.css'), css)) + .catch(done) + } + }) + + // When the change is picked up: + watcher.on('change', (p) => { + if (p === 'output.css') { + isEqual(p, 'test/fixtures/b.css') + .then(() => done()) + .catch(done) + } + }) + + // Start postcss-cli: + watcher.on('ready', () => { + // Using exec() and quoting "*.css" to test watch's glob handling: + cp = exec( + `node ${path.resolve( + 'bin/postcss' + )} "s.css" -o output.css --no-map -w`, + { cwd: dir } + ) + cp.on('error', t.end) + cp.on('exit', (code) => { + if (code) t.end(code) + }) + }) + + // Helper functions: + function isEqual(p, expected) { + return Promise.all([read(path.join(dir, p)), read(expected)]).then( + ([a, e]) => t.is(a, e) + ) + } + + function done(err) { + try { + cp.kill() + } catch {} + + t.end(err) + } + }) + .catch(t.end) + }) + + // Timeout: + setTimeout(() => t.end('test timeout'), 50000) +}) + +testCb('--watch watches directory dependencies', (t) => { + let cp + + t.plan(2) + + ENV('', ['s.css', 'base/level-1/b.css', 'base/level-1/level-2/a.css']).then( + (dir) => { + fs.writeFile( + path.join(dir, 'postcss.config.js'), + ` + const fs = require('fs') + module.exports = { + plugins: [ + (root, result) => { + result.messages.push({ + plugin: 'test', + type: 'dir-dependency', + dir: '${path.resolve(dir, 'base')}', + parent: result.opts.from, + }) + root.nodes = [] + root.append(fs.readFileSync('${path.resolve( + dir, + 'base/level-1/level-2/a.css' + )}', 'utf8')) + return root + } + ] + } + ` + ) + .then(() => { + // Init watcher: + const watcher = chokidar.watch('.', { + cwd: dir, + ignoreInitial: true, + awaitWriteFinish: true, + }) + + // On the first output: + watcher.on('add', (p) => { + // Assert, then change the source file + if (p === 'output.css') { + isEqual(p, 'test/fixtures/base/level-1/level-2/a.css') + .then(() => read('test/fixtures/base/level-1/b.css')) + .then((css) => + fs.writeFile( + path.join(dir, 'base/level-1/level-2/a.css'), + css + ) + ) + .catch(done) + } + }) + + // When the change is picked up: + watcher.on('change', (p) => { + if (p === 'output.css') { + isEqual(p, 'test/fixtures/base/level-1/b.css') + .then(() => done()) + .catch(done) + } + }) + + // Start postcss-cli: + watcher.on('ready', () => { + // Using exec() and quoting "*.css" to test watch's glob handling: + cp = exec( + `node ${path.resolve( + 'bin/postcss' + )} "s.css" -o output.css --no-map -w`, + { cwd: dir } + ) + cp.on('error', t.end) + cp.on('exit', (code) => { + if (code) t.end(code) + }) + }) + + // Helper functions: + function isEqual(p, expected) { + return Promise.all([read(path.join(dir, p)), read(expected)]).then( + ([a, e]) => t.is(a, e) + ) + } + + function done(err) { + try { + cp.kill() + } catch {} + + t.end(err) + } + }) + .catch(t.end) + } + ) + + // Timeout: + setTimeout(() => t.end('test timeout'), 50000) +})