Skip to content

Commit e996b5e

Browse files
feat(core): support exclude option for transformers (#2960)
This PR adds general support for an `exclude` option to file transformers. This allows plugin to skip processing excluded files. The exclude array can be a `string`, a `RegExp` or a glob pattern. Fixes #2937
1 parent 107c25b commit e996b5e

File tree

4 files changed

+79
-4
lines changed

4 files changed

+79
-4
lines changed

plugin-tailwindcss/src/mod.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import type { TailwindPluginOptions } from "./types.ts";
22
import { initTailwind } from "./compiler.ts";
3-
import type { Builder } from "fresh/dev";
3+
import type { FreshBuilder } from "fresh/dev";
44
import type { App } from "fresh";
55

66
export function tailwind<T>(
7-
builder: Builder,
7+
builder: FreshBuilder,
88
app: App<T>,
99
options: TailwindPluginOptions = {},
1010
): void {
1111
let processor: ReturnType<typeof initTailwind> | null;
1212

1313
builder.onTransformStaticFile(
14-
{ pluginName: "tailwind", filter: /\.css$/ },
14+
{ pluginName: "tailwind", filter: /\.css$/, exclude: options.exclude },
1515
async (args) => {
1616
if (!processor) processor = initTailwind(app.config, options);
1717
const instance = await processor;

plugin-tailwindcss/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { OnTransformOptions } from "@fresh/core/dev";
2+
13
export interface AutoprefixerOptions {
24
/** environment for `Browserslist` */
35
env?: string;
@@ -41,4 +43,6 @@ export interface AutoprefixerOptions {
4143

4244
export interface TailwindPluginOptions {
4345
autoprefixer?: AutoprefixerOptions;
46+
/** Exclude paths or globs that should not be processed */
47+
exclude?: OnTransformOptions["exclude"];
4448
}

src/dev/builder_test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,53 @@ Deno.test({
159159
sanitizeOps: false,
160160
sanitizeResources: false,
161161
});
162+
163+
Deno.test({
164+
name: "Builder - exclude files",
165+
fn: async () => {
166+
const logs: string[] = [];
167+
const builder = new Builder();
168+
169+
// String
170+
builder.onTransformStaticFile(
171+
{ pluginName: "A", filter: /\.css$/, exclude: ["foo.css"] },
172+
(args) => {
173+
logs.push(`A: ${path.basename(args.path)}`);
174+
},
175+
);
176+
177+
// Regex
178+
builder.onTransformStaticFile(
179+
{ pluginName: "B", filter: /\.css$/, exclude: [/foo\.css$/] },
180+
(args) => {
181+
logs.push(`B: ${path.basename(args.path)}`);
182+
},
183+
);
184+
185+
// Glob
186+
builder.onTransformStaticFile(
187+
{ pluginName: "C", filter: /\.css$/, exclude: ["**/foo.css"] },
188+
(args) => {
189+
logs.push(`C: ${path.basename(args.path)}`);
190+
},
191+
);
192+
193+
const tmp = await Deno.makeTempDir();
194+
await Deno.writeTextFile(path.join(tmp, "foo.css"), "body { color: red; }");
195+
await Deno.writeTextFile(
196+
path.join(tmp, "bar.css"),
197+
"body { color: blue; }",
198+
);
199+
const app = new App({
200+
staticDir: tmp,
201+
build: {
202+
outDir: path.join(tmp, "dist"),
203+
},
204+
});
205+
await builder.build(app);
206+
207+
expect(logs).toEqual(["A: bar.css", "B: bar.css", "C: bar.css"]);
208+
},
209+
sanitizeOps: false,
210+
sanitizeResources: false,
211+
});

src/dev/file_transformer.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { globToRegExp, isGlob } from "@std/path";
12
import type { FsAdapter } from "../fs.ts";
23
import { BUILD_ID } from "../runtime/build_id.ts";
34
import { assetInternal } from "../runtime/shared_internal.tsx";
@@ -7,6 +8,7 @@ export type TransformMode = "development" | "production";
78
export interface OnTransformOptions {
89
pluginName: string;
910
filter: RegExp;
11+
exclude?: Array<string | RegExp>;
1012
}
1113

1214
export interface OnTransformResult {
@@ -112,7 +114,7 @@ export class FreshFileTransformer {
112114
seen.add(req.filePath);
113115

114116
let transformed = false;
115-
for (let i = 0; i < this.#transformers.length; i++) {
117+
outer: for (let i = 0; i < this.#transformers.length; i++) {
116118
const transformer = this.#transformers[i];
117119

118120
const { options, fn } = transformer;
@@ -121,6 +123,25 @@ export class FreshFileTransformer {
121123
continue;
122124
}
123125

126+
// Check if file is excluded
127+
if (options.exclude !== undefined) {
128+
for (let j = 0; j < options.exclude.length; j++) {
129+
const exclude = options.exclude[j];
130+
if (exclude instanceof RegExp) {
131+
if (exclude.test(filePath)) {
132+
continue outer;
133+
}
134+
} else if (isGlob(exclude)) {
135+
const regex = globToRegExp(exclude);
136+
if (regex.test(filePath)) {
137+
continue outer;
138+
}
139+
} else if (filePath.includes(exclude)) {
140+
continue outer;
141+
}
142+
}
143+
}
144+
124145
const result = await fn({
125146
path: req.filePath,
126147
mode,

0 commit comments

Comments
 (0)