Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: handle multiple input sources in watch mode #14281

Merged
merged 4 commits into from Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
84 changes: 60 additions & 24 deletions packages/babel-cli/src/babel/dir.ts
Expand Up @@ -177,34 +177,70 @@ export default async function ({
// This, alongside with debounce, allows us to only log
// when we are sure that all the files have been compiled.
let processing = 0;
const { filenames } = cliOptions;
let getBase;
if (filenames.length === 1) {
// fast path: If there is only one filenames, we know it must be the base
const base = filenames[0];
const absoluteBase = path.resolve(base);
getBase = filename => {
return filename === absoluteBase ? path.dirname(base) : base;
};
} else {
// A map from absolute compiled file path to its base, from which
// the output destination will be determined
const filenameToBaseMap: Map<string, string> = new Map(
filenames.map(filename => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering whether these could be globs or not, but they are already resolved at

let filenames = commander.args.reduce(function (globbed, input) {
let files = glob.sync(input);
if (!files.length) files = [input];
globbed.push(...files);
return globbed;
}, []);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they are resolved prior to watch setup, which means if you run babel */src --watch and then create a new folder module3/src, it will not be added to the watcher.

const absoluteFilename = path.resolve(filename);
return [absoluteFilename, path.dirname(filename)];
}),
);

const absoluteFilenames: Map<string, string> = new Map(
filenames.map(filename => {
const absoluteFilename = path.resolve(filename);
return [absoluteFilename, filename];
}),
);

const { sep } = path;
// determine base from the absolute file path
getBase = filename => {
const base = filenameToBaseMap.get(filename);
if (base !== undefined) {
return base;
}
for (const [absoluteFilenameOrDir, relative] of absoluteFilenames) {
if (filename.startsWith(absoluteFilenameOrDir + sep)) {
filenameToBaseMap.set(filename, relative);
return relative;
}
}
// Can't determine the base, probably external deps
return "";
};
}

cliOptions.filenames.forEach(filenameOrDir => {
filenames.forEach(filenameOrDir => {
watcher.watch(filenameOrDir);
});

watcher.onFilesChange(async filenames => {
processing++;
if (startTime === null) startTime = process.hrtime();

try {
const written = await Promise.all(
filenames.map(filename =>
handleFile(
filename,
filename === filenameOrDir
? path.dirname(filenameOrDir)
: filenameOrDir,
),
),
);

compiledFiles += written.filter(Boolean).length;
} catch (err) {
console.error(err);
}
watcher.onFilesChange(async filenames => {
processing++;
if (startTime === null) startTime = process.hrtime();

processing--;
if (processing === 0 && !cliOptions.quiet) logSuccess();
});
try {
const written = await Promise.all(
filenames.map(filename => handleFile(filename, getBase(filename))),
);

compiledFiles += written.filter(Boolean).length;
} catch (err) {
console.error(err);
}

processing--;
if (processing === 0 && !cliOptions.quiet) logSuccess();
});
}
}
Expand Up @@ -4,15 +4,16 @@ const assert = require("assert");
// For Node.js <= 10
if (!assert.match) assert.match = (val, re) => assert(re.test(val));

const run = (function* () {
const run = (async function* () {
let files = [yield, yield].sort();
assert.match(files[0], /src[\\/]index.js -> lib[\\/]index.js/);
assert.match(files[1], /src[\\/]main.js -> lib[\\/]main.js/);
assert.match(yield, /Successfully compiled 2 files with Babel \(\d+ms\)\./);

logFile("lib/index.js");
logFile("lib/main.js");

// wait 200ms for watcher setup
await new Promise(resolve => setTimeout(resolve, 200));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhh thanks for this!

fs.writeFileSync("./file.txt", "Updated!");

files = [yield, yield].sort();
Expand All @@ -29,7 +30,7 @@ run.next();
const batchedStrings = [];
let batchId = 0;

process.stdin.on("data", function listener(chunk) {
process.stdin.on("data", async function listener(chunk) {
const str = String(chunk).trim();
if (!str) return;

Expand All @@ -50,7 +51,7 @@ process.stdin.on("data", function listener(chunk) {
console.log(str);
}

if (run.next(str).done) {
if ((await run.next(str)).done) {
process.exit(0);
}
});
Expand Down
Expand Up @@ -2,5 +2,5 @@
"args": ["src", "--out-dir", "lib", "--watch", "--verbose"],
"noBabelrc": true,
"noDefaultPlugins": true,
"minNodeVersion": 8
"minNodeVersion": 10
}
@@ -0,0 +1,37 @@
const fs = require("fs");
const assert = require("assert");

// For Node.js <= 10
if (!assert.match) assert.match = (val, re) => assert(re.test(val));

const run = (async function* () {
assert.match(yield, /Successfully compiled 4 files with Babel \(\d+ms\)\./);

// wait 200ms for watcher setup
await new Promise(resolve => setTimeout(resolve, 200));
// update ./module1/src/index.js
fs.writeFileSync(
"./module1/src/index.js",
`let str = REPLACE_ME + REPLACE_ME;`
);

assert.match(yield, /Successfully compiled 1 file with Babel \(\d+ms\)\./);
})();

run.next();

process.stdin.on("data", async function listener(chunk) {
const str = String(chunk).trim();
if (!str) return;

console.log(str);

if ((await run.next(str)).done) {
process.exit(0);
}
});

setTimeout(() => {
console.error("EXECUTOR TIMEOUT");
process.exit(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could replace exit(1) with exit(0) in all the watch tests and use .log instead of .error, to see at which point they get stuck.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I can't reproduce the timeout error on my local machine, now I have to abuse GitHub CI to see what happens.

}, 5000);
@@ -0,0 +1,25 @@
const fs = require("fs");
const path = require("path");

function inlinePlugin(api, { filename }) {
const { types: t } = api;

const contents = api.cache.using(() => fs.readFileSync(filename, "utf8"));
api.addExternalDependency(filename);

return {
visitor: {
Identifier(path) {
if (path.node.name === "REPLACE_ME") {
path.replaceWith(t.stringLiteral(contents));
}
},
},
};
}

module.exports = {
plugins: [
[inlinePlugin, { filename: path.resolve(__dirname, "./file.txt") }],
],
};
@@ -0,0 +1 @@
Hi :)
@@ -0,0 +1 @@
let str = REPLACE_ME;
@@ -0,0 +1 @@
console.log(REPLACE_ME);
@@ -0,0 +1 @@
let str = REPLACE_ME;
@@ -0,0 +1 @@
console.log(REPLACE_ME);
@@ -0,0 +1,6 @@
{
"args": ["*/src", "--out-dir", "../lib", "--relative", "--watch"],
"noBabelrc": true,
"noDefaultPlugins": true,
"minNodeVersion": 10
}
@@ -0,0 +1 @@
let str = "Hi :)" + "Hi :)";
@@ -0,0 +1 @@
console.log("Hi :)");
@@ -0,0 +1 @@
let str = "Hi :)";
@@ -0,0 +1 @@
console.log("Hi :)");
@@ -0,0 +1,2 @@
Successfully compiled 4 files with Babel (123ms).
Successfully compiled 1 file with Babel (123ms).
Expand Up @@ -4,29 +4,30 @@ const assert = require("assert");
// For Node.js <= 10
if (!assert.match) assert.match = (val, re) => assert(re.test(val));

const run = function* () {
const run = (async function* () {
assert.match(yield, /Successfully compiled 2 files with Babel \(\d+ms\)\./);

logFile("lib/index.js");
logFile("lib/main.js");

// wait 200ms for watcher setup
await new Promise(resolve => setTimeout(resolve, 200));
fs.writeFileSync("./file.txt", "Updated!");

assert.match(yield, /Successfully compiled 2 files with Babel \(\d+ms\)\./);

logFile("lib/index.js");
logFile("lib/main.js");
}();
})();

run.next();

process.stdin.on("data", function listener(chunk) {
process.stdin.on("data", async function listener(chunk) {
const str = String(chunk).trim();
if (!str) return;

console.log(str);

if (run.next(str).done) {
if ((await run.next(str)).done) {
process.exit(0);
}
});
Expand Down
Expand Up @@ -2,5 +2,5 @@
"args": ["src", "--out-dir", "lib", "--watch"],
"noBabelrc": true,
"noDefaultPlugins": true,
"minNodeVersion": 8
"minNodeVersion": 10
}