Skip to content

Commit 373ec4c

Browse files
committed
Cleanup glob handling and do ignore checks in memory using micromatch
1 parent 2a05604 commit 373ec4c

File tree

8 files changed

+77
-40
lines changed

8 files changed

+77
-40
lines changed

integration-test/reload-cross-workspace-lazy/test.sh

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ echo "Made initial request to server"
3434
# modify the file in the side package and expect the main script to reload
3535
sed -i 's/Hello, World/Hey, Pluto/g' $DIR/side/run-scratch.ts
3636

37+
echo "Made change to side package"
38+
3739
counter=0
3840
until curl -s localhost:8080 | grep "Pluto"
3941
do

integration-test/reload-cross-workspace/test.sh

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ echo "Made initial request to server"
3434
# modify the file in the side package and expect the main script to reload
3535
sed -i 's/Hello, World/Hey, Pluto/g' $DIR/side/run-scratch.ts
3636

37+
echo "Made change to side package"
38+
3739
counter=0
3840
until curl -s localhost:8080 | grep "Pluto"
3941
do

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"fs-extra": "^11.2.0",
4949
"globby": "^11.1.0",
5050
"lodash": "^4.17.20",
51+
"micromatch": "^4.0.8",
5152
"node-object-hash": "^3.0.0",
5253
"oxc-resolver": "^1.12.0",
5354
"pkg-dir": "^5.0.0",
@@ -62,6 +63,7 @@
6263
"@types/find-root": "^1.1.4",
6364
"@types/fs-extra": "^11.0.4",
6465
"@types/lodash": "^4.17.13",
66+
"@types/micromatch": "^4.0.9",
6567
"@types/node": "^22.9.3",
6668
"@types/write-file-atomic": "^4.0.3",
6769
"@types/yargs": "^15.0.19",

pnpm-lock.yaml

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/SwcCompiler.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ test("throws if the file is ignored", async () => {
4141
}
4242
}
4343

44-
expect(error).toBeDefined();
44+
expect(error).toBeTruthy();
4545
expect(error?.ignoredFile).toBeTruthy();
4646
expect(error?.message).toMatch(
47-
/File .+ignored\.ts is imported but not being built because it is explicitly ignored in the wds project config\. It is being ignored by the provided glob pattern '!ignored\.ts', remove this pattern from the project config or don't import this file to fix./
47+
/File .+ignored\.ts is imported but not being built because it is explicitly ignored in the wds project config\. It is being ignored by the provided glob pattern 'ignored\.ts', remove this pattern from the project config or don't import this file to fix./
4848
);
4949
});
5050

src/ProjectConfig.ts

+29-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Options as SwcOptions } from "@swc/core";
22
import fs from "fs-extra";
33
import _ from "lodash";
4+
import micromatch from "micromatch";
45
import path from "path";
56
import { log } from "./utils.js";
67

@@ -13,44 +14,61 @@ export interface RunOptions {
1314
}
1415

1516
export interface ProjectConfig {
17+
root: string;
1618
ignore: string[];
19+
includeGlob: string;
20+
includedMatcher: (filePath: string) => boolean;
1721
swc?: SwcConfig;
1822
esm?: boolean;
1923
extensions: string[];
2024
cacheDir: string;
2125
}
2226

23-
export const projectConfig = async (root: string): Promise<ProjectConfig> => {
27+
export const projectConfig = _.memoize(async (root: string): Promise<ProjectConfig> => {
2428
const location = path.join(root, "wds.js");
2529
const base: ProjectConfig = {
26-
ignore: [],
30+
root,
2731
extensions: [".ts", ".tsx", ".jsx"],
2832
cacheDir: path.join(root, "node_modules/.cache/wds"),
2933
esm: true,
34+
/** The list of globby patterns to use when searching for files to build */
35+
includeGlob: `**/*`,
36+
/** The list of globby patterns to ignore use when searching for files to build */
37+
ignore: [],
38+
/** A micromatch matcher for userland checking if a file is included */
39+
includedMatcher: () => true,
3040
};
3141

42+
let exists = false;
3243
try {
3344
await fs.access(location);
45+
exists = true;
3446
} catch (error: any) {
3547
log.debug(`Not loading project config from ${location}`);
36-
return base;
3748
}
3849

39-
let required = await import(location);
40-
if (required.default) {
41-
required = required.default;
50+
let result: ProjectConfig;
51+
if (exists) {
52+
let required = await import(location);
53+
if (required.default) {
54+
required = required.default;
55+
}
56+
log.debug(`Loaded project config from ${location}`);
57+
result = _.defaults(required, base);
58+
} else {
59+
result = base;
4260
}
43-
log.debug(`Loaded project config from ${location}`);
44-
const result = _.defaults(required, base);
4561

4662
const projectRootDir = path.dirname(location);
4763
// absolutize the cacheDir if not already
4864
if (!result.cacheDir.startsWith("/")) {
4965
result.cacheDir = path.resolve(projectRootDir, result.cacheDir);
5066
}
5167

52-
// absolutize the ignore paths if not already
53-
result.ignore = result.ignore.map((p: string) => (p.startsWith("/") ? p : path.resolve(projectRootDir, p)));
68+
// build inclusion glob and matcher
69+
result.ignore = _.uniq([`node_modules`, `**/*.d.ts`, `.git/**`, ...result.ignore]);
70+
result.includeGlob = `**/*{${result.extensions.join(",")}}`;
71+
result.includedMatcher = micromatch.matcher(result.includeGlob, { cwd: result.root, ignore: result.ignore });
5472

5573
return result;
56-
};
74+
});

src/SwcCompiler.ts

+18-24
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import path from "path";
1313
import { fileURLToPath } from "url";
1414
import writeFileAtomic from "write-file-atomic";
1515
import type { Compiler } from "./Compiler.js";
16-
import { projectConfig, type ProjectConfig } from "./ProjectConfig.js";
16+
import { projectConfig } from "./ProjectConfig.js";
1717
import { log } from "./utils.js";
1818

1919
const __filename = fileURLToPath(import.meta.url);
@@ -184,11 +184,14 @@ export class SwcCompiler implements Compiler {
184184
swcConfig = config.swc;
185185
}
186186

187-
const globs = [...this.fileGlobPatterns(config), ...this.ignoreFileGlobPatterns(config)];
187+
log.debug("searching for filenames", { filename, config });
188188

189-
log.debug("searching for filenames", { filename, config, root, globs });
190-
191-
let fileNames = await globby(globs, { cwd: root, absolute: true });
189+
let fileNames = await globby(config.includeGlob, {
190+
onlyFiles: true,
191+
cwd: root,
192+
absolute: true,
193+
ignore: config.ignore,
194+
});
192195

193196
if (process.platform === "win32") {
194197
fileNames = fileNames.map((fileName) => fileName.replace(/\//g, "\\"));
@@ -276,16 +279,6 @@ export class SwcCompiler implements Compiler {
276279
}
277280
}
278281

279-
/** The list of globby patterns to use when searching for files to build */
280-
private fileGlobPatterns(config: ProjectConfig) {
281-
return [`**/*{${config.extensions.join(",")}}`];
282-
}
283-
284-
/** The list of globby patterns to ignore use when searching for files to build */
285-
private ignoreFileGlobPatterns(config: ProjectConfig) {
286-
return [`!node_modules`, `!**/*.d.ts`, ...(config.ignore || []).map((ignore) => `!${ignore}`)];
287-
}
288-
289282
/**
290283
* Detect if a file is being ignored by the ignore glob patterns for a given project
291284
*
@@ -294,16 +287,17 @@ export class SwcCompiler implements Compiler {
294287
private async isFilenameIgnored(filename: string): Promise<string | false> {
295288
const root = findRoot(filename);
296289
const config = await projectConfig(root);
297-
const includeGlobs = this.fileGlobPatterns(config);
298-
const ignoreGlobs = this.ignoreFileGlobPatterns(config);
299-
300-
const actual = await globby([...includeGlobs, ...ignoreGlobs], { cwd: root, absolute: true });
301-
const all = await globby(includeGlobs, { cwd: root, absolute: true });
302290

303-
// if the file isn't returned when we use the ignores, but is when we don't use the ignores, it means were ignoring it. Figure out which ignore is causing this
304-
if (!actual.includes(filename) && all.includes(filename)) {
305-
for (const ignoreGlob of ignoreGlobs) {
306-
const withThisIgnore = await globby([...includeGlobs, ignoreGlob], { cwd: root, absolute: true });
291+
// check if the file is ignored by any of the ignore patterns using micromatch
292+
const included = config.includedMatcher(filename.replace(root + path.sep, ""));
293+
if (!included) {
294+
// figure out which ignore pattern is causing the file to be ignored for a better error message
295+
for (const ignoreGlob of config.ignore) {
296+
const withThisIgnore = await globby(config.includeGlob, {
297+
cwd: root,
298+
absolute: true,
299+
ignore: [ignoreGlob],
300+
});
307301
if (!withThisIgnore.includes(filename)) {
308302
return ignoreGlob;
309303
}

src/index.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,19 @@ const startFilesystemWatcher = (project: Project) => {
7070
const watcher = new Watcher([project.workspaceRoot], {
7171
ignoreInitial: true,
7272
recursive: true,
73-
ignore: ((filePath: string) => {
73+
ignore: (filePath: string) => {
7474
if (filePath.includes(nodeModulesDir)) return true;
75+
if (filePath == project.workspaceRoot) return false;
76+
if (filePath == project.config.root) return false;
7577
if (filePath.endsWith(".d.ts")) return true;
7678
if (filePath.endsWith(".map")) return true;
7779
if (filePath.includes(gitDir)) return true;
7880
if (filePath.endsWith(".DS_Store")) return true;
7981
if (filePath.endsWith(".tsbuildinfo")) return true;
8082

81-
return project.config.ignore?.some((ignore) => filePath.startsWith(ignore)) ?? false;
82-
}) as any,
83+
// allow files that match the include glob to be watched, or directories (since they might contain files)
84+
return !project.config.includedMatcher(filePath) && path.extname(filePath) != "";
85+
},
8386
});
8487

8588
log.debug("started watcher", { root: project.workspaceRoot });

0 commit comments

Comments
 (0)