Skip to content

Commit 279a84b

Browse files
committed
Extend library to add cutom loader, text extensions and update test cases
1 parent 6f46cec commit 279a84b

File tree

7 files changed

+113
-32
lines changed

7 files changed

+113
-32
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default defineConfig({
7676

7777
## IDE Setup for IntelliSense and Type Checking
7878

79-
Add following to your declaration file. If you do not have one, create `declarations.d.ts` file and add following.
79+
Add the following to your declaration file. If you do not have one, create a `declarations.d.ts` file and add the following:
8080

8181
```typescript
8282
declare module "*?raw" {
@@ -122,6 +122,17 @@ export interface RawPluginOptions {
122122
* You can provide your own extensions to optimize build performance or extend the list based on your use case.
123123
*/
124124
ext?: string[];
125+
126+
/**
127+
* Custom loader for file processing.
128+
* @defaultValue "text"
129+
*/
130+
loader?: "text" | "base64" | "dataurl" | "file" | "binary" | "default";
131+
132+
/**
133+
* Extensions to be treated as text files.
134+
*/
135+
textExtensions?: string[];
125136
}
126137
```
127138

lib/__tests__/declarations.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
declare module "*?raw";
2+
declare module "*.md";

lib/__tests__/index.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeAll, describe, test } from "vitest";
1+
import { describe, test } from "vitest";
22
import esbuild, { BuildOptions } from "esbuild";
33
import path from "node:path";
44
import { raw } from "../src";
@@ -16,7 +16,7 @@ const buildOptions: BuildOptions = {
1616
plugins: [raw()],
1717
};
1818

19-
describe("WebGL plugins", () => {
19+
describe("Raw plugin", () => {
2020
test("test raw import", async ({ expect }) => {
2121
await esbuild.build(buildOptions);
2222
const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts"), "utf-8");
@@ -42,4 +42,29 @@ describe("WebGL plugins", () => {
4242
}
4343
expect(didThrow).toBe(true);
4444
});
45+
46+
test("textExtensions", async ({ expect }) => {
47+
await esbuild.build({
48+
...buildOptions,
49+
plugins: [raw({ textExtensions: [".md"] })],
50+
entryPoints: [path.resolve(__dirname, "test3.ts")],
51+
});
52+
53+
const fileContent = fs.readFileSync(path.resolve(__dirname, "test.md"), "utf-8");
54+
// @ts-ignore
55+
const generatedCodeContent = (await import("./dist/test3.js")).getText();
56+
expect(fileContent).toBe(generatedCodeContent);
57+
});
58+
59+
test("custom loader", async ({ expect }) => {
60+
await esbuild.build({
61+
...buildOptions,
62+
entryPoints: [path.resolve(__dirname, "test-loader.ts")],
63+
plugins: [raw({ loader: "base64" })],
64+
});
65+
const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts"), "utf-8");
66+
// @ts-ignore
67+
const generatedCodeContent = (await import("./dist/test-loader.js")).getText();
68+
expect(fileContent).toBe(atob(generatedCodeContent));
69+
});
4570
});

lib/__tests__/test-loader.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import text from "../src/index.ts?raw";
2+
3+
export const getText = () => text;

lib/__tests__/test.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I am not supported by default

lib/__tests__/test3.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// test cutom extension
2+
3+
import text from "./test.md";
4+
5+
export const getText = () => text;

lib/src/index.ts

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,95 @@ import path from "node:path";
44

55
export interface RawPluginOptions {
66
/**
7-
* Extensions to check in order if the file does not exist.
7+
* File extensions to check in order of priority if the specified file is missing.
88
* If it's a directory, the plugin will look for `dir/index.[ext]`.
9-
* @defaultValue ["tsx", "ts", "jsx", "js", "mjs", "mts", "module.css", "module.scss", "css", "scss"]
10-
*
11-
* You can provide your own extensions to optimize build performance or extend the list based on your use case.
9+
* @defaultValue ["ts", "tsx", "js", "jsx", "mjs", "mts", "module.css", "module.scss", "css", "scss"]
1210
*/
1311
ext?: string[];
12+
13+
/**
14+
* Custom loader for file processing.
15+
* @defaultValue "text"
16+
*/
17+
loader?: "text" | "base64" | "dataurl" | "file" | "binary" | "default";
18+
19+
/**
20+
* Extensions to be treated as text files.
21+
*/
22+
textExtensions?: string[];
1423
}
1524

16-
/** Plugin to load `.glsl` files as minified strings */
25+
/**
26+
* ESBuild plugin to enable raw file imports.
27+
*
28+
* This plugin allows importing files with a `?raw` suffix,
29+
* treating them as raw text content. It supports resolving file
30+
* extensions in order of priority and handling custom loaders.
31+
*/
1732
export const raw: (options?: RawPluginOptions) => Plugin = options => ({
18-
/** generate randmo name to avoid collision among the plugins */
19-
name: `raw-${(Date.now() * Math.random()).toString(36).slice(0, 8)}`,
33+
name: `raw-${Math.random().toString(36).slice(2, 10)}`,
2034
setup(build: PluginBuild) {
2135
const ext = options?.ext ?? [
22-
"tsx",
2336
"ts",
24-
"jsx",
37+
"tsx",
2538
"js",
39+
"jsx",
2640
"mjs",
2741
"mts",
2842
"module.css",
2943
"module.scss",
3044
"css",
3145
"scss",
3246
];
33-
build.onResolve({ filter: /\?raw$/ }, args => {
34-
const filePath = args.path;
35-
return {
36-
path: filePath,
37-
pluginData: path.resolve(args.resolveDir, filePath).replace(/\?raw$/, ""),
38-
namespace: "raw",
39-
};
40-
});
47+
48+
build.onResolve({ filter: /\?raw$/ }, args => ({
49+
path: args.path,
50+
pluginData: path.resolve(args.resolveDir, args.path).replace(/\?raw$/, ""),
51+
namespace: "raw",
52+
}));
53+
4154
build.onLoad({ filter: /\?raw$/, namespace: "raw" }, args => {
4255
let filePath = args.pluginData;
43-
if (fs.existsSync(filePath) && fs.lstatSync(filePath).isDirectory())
44-
filePath += path.sep + "index";
45-
if (!fs.existsSync(filePath))
46-
for (const e of ext)
47-
if (fs.existsSync(filePath + "." + e)) {
48-
filePath += "." + e;
56+
if (options?.loader && options.loader !== "text") {
57+
return { contents: fs.readFileSync(filePath, "utf8"), loader: options.loader };
58+
}
59+
60+
if (fs.existsSync(filePath) && fs.lstatSync(filePath).isDirectory()) {
61+
filePath = path.join(filePath, "index");
62+
}
63+
64+
if (!fs.existsSync(filePath)) {
65+
for (const e of ext) {
66+
if (fs.existsSync(`${filePath}.${e}`)) {
67+
filePath += `.${e}`;
4968
break;
5069
}
51-
if (!fs.existsSync(filePath))
70+
}
71+
}
72+
73+
if (!fs.existsSync(filePath)) {
5274
throw new Error(
5375
/* v8 ignore next */
54-
`File not found: ${args.pluginData}\nWe checked for following extensions: ${ext.join(", ")}. You can customise by passing {ext: [...]} to raw({ext:[...]})`,
76+
`File not found: ${args.pluginData}\nChecked extensions: ${ext.join(", ")}. You can customize this using { ext: [...] }.`,
5577
/* v8 ignore next */
5678
);
57-
return {
58-
contents: fs.readFileSync(filePath, "utf8"),
59-
loader: "text",
60-
};
79+
}
80+
81+
return { contents: fs.readFileSync(filePath, "utf8"), loader: "text" };
6182
});
83+
84+
if (options?.textExtensions?.length) {
85+
build.onLoad(
86+
{
87+
filter: new RegExp(
88+
`\.(${options.textExtensions.map(e => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`,
89+
),
90+
},
91+
args => ({
92+
contents: fs.readFileSync(args.path, "utf8"),
93+
loader: "text",
94+
}),
95+
);
96+
}
6297
},
6398
});

0 commit comments

Comments
 (0)