-[](https://github.com/react18-tools/esbuild-raw-plugin/actions/workflows/test.yml) [](https://codeclimate.com/github/react18-tools/esbuild-raw-plugin/maintainability) [](https://codecov.io/gh/react18-tools/esbuild-raw-plugin) [](https://www.npmjs.com/package/esbuild-raw-plugin) [](https://www.npmjs.com/package/esbuild-raw-plugin)  [](https://gitpod.io/from-referrer/)
+[](https://github.com/react18-tools/esbuild-raw-plugin/actions/workflows/test.yml)
+[](https://codeclimate.com/github/react18-tools/esbuild-raw-plugin/maintainability)
+[](https://codecov.io/gh/react18-tools/esbuild-raw-plugin)
+[](https://www.npmjs.com/package/esbuild-raw-plugin)
+[](https://www.npmjs.com/package/esbuild-raw-plugin)
+
+[](https://gitpod.io/from-referrer/)
-**An ESBuild/TSUP plugin to import files as raw text.**
-Ideal for scenarios like importing code files for documentation, interactive tools like `react-live`, or other text-based use cases.
+**Lightweight ESBuild/TSUP plugin to import files as raw content β zero config required.**
->
Star [this repository](https://github.com/react18-tools/esbuild-raw-plugin) and share it with your friends.
+> Import `.ts`, `.js`, `.css`, `.scss`, `.md`, `.html`, `.docx`, and more β perfect for documentation, live editors (`react-live`), markdown tooling, or template-driven workflows.
+> Power users: Load `.docx` templates directly for [mdast2docx](https://github.com/md2docx/mdast2docx).
+
+>
Star [this repository](https://github.com/react18-tools/esbuild-raw-plugin) and share it with your dev circle.
---
-## Features
+## π Features
-- Import any file (e.g., `.js`, `.ts`, `.css`, etc.) as raw text.
-- Works seamlessly with **ESBuild** and **TSUP**.
-- Perfect for documentation generators, live code editors, and similar tools.
+- π§ Supports `?raw`, `?text`, `?base64`, `?dataurl`, `?binary`, and `?file` query suffixes
+- π§ Smart fallback to extensions like `.ts`, `.tsx`, `index.[ext]`, etc.
+- π Custom loader mapping (e.g., `module.scss` β `text`, `png` β `dataurl`)
+- β‘ Ultra-fast using regex-based native `onLoad` filter (Go-native perf)
+- πͺΆ Works seamlessly with both [Tsup](https://tsup.egoist.dev/) and [ESBuild](https://esbuild.github.io/)
---
-## Installation
-
-Using npm:
+## π¦ Installation
```bash
npm install esbuild-raw-plugin --save-dev
```
-Using yarn:
+_or_
```bash
yarn add esbuild-raw-plugin --dev
```
-Using pnpm:
+_or_
```bash
pnpm add esbuild-raw-plugin --save-dev
@@ -39,13 +47,11 @@ pnpm add esbuild-raw-plugin --save-dev
---
-## Usage
+## π Usage
-### ESBuild Configuration
+### β€ With ESBuild
-Add the plugin to your ESBuild configuration:
-
-```js
+```ts
import { build } from "esbuild";
import { raw } from "esbuild-raw-plugin";
@@ -57,11 +63,9 @@ build({
});
```
-### TSUP Configuration
+### β€ With TSUP
-Add the plugin to your TSUP configuration:
-
-```js
+```ts
import { defineConfig } from "tsup";
import { raw } from "esbuild-raw-plugin";
@@ -74,135 +78,141 @@ export default defineConfig({
---
-## IDE Setup for IntelliSense and Type Checking
+## π§ TypeScript Support
-Add the following to your declaration file. If you do not have one, create a `declarations.d.ts` file and add the following:
+Add this to your `declarations.d.ts` file:
-```typescript
+```ts
declare module "*?raw" {
- const value: string;
- export default value;
+ const content: string;
+ export default content;
}
```
-## Importing Files as Raw Text
+> For other suffixes (`?base64`, `?binary`, etc.), add similar declarations if needed.
+
+---
-With the plugin enabled, you can import files as raw text directly:
+## π₯ Importing Raw Files
-```js
-import myCode from "./example.js?raw";
+```ts
+import content from "./example.js?raw";
-console.log(myCode);
-// Outputs the content of 'example.js' as a string.
+console.log(content); // Entire file content as string or Buffer
```
-### Good News:
+### β
Simplified Imports
-With the latest update, you no longer need to specify the file extension explicitly.
+You donβt need to specify full filenames or extensions:
-```js
-import myCode from "./example?raw";
+```ts
+import code from "./utils?raw"; // Resolves to utils/index.ts, utils.js, etc.
```
-This works seamlessly! Additionally, if you're exporting from files like `index.tsx`, `index.jsx`, etc., you can simplify imports. For example, if your file path is `my-lib/index.ts`, you can import the raw content like this:
+Great for:
-```js
-import myCode from "./my-lib?raw";
-```
+- Library or folder-level imports
+- Auto-resolving `.ts`, `.tsx`, `.css`, `.scss`, etc.
-### Extension Options (Optional)
+---
+
+## βοΈ Plugin Options
```ts
export interface RawPluginOptions {
- /**
- * Extensions to check in order if the file does not exist.
- * If it's a directory, the plugin will look for `dir/index.[ext]`.
- * @defaultValue ["tsx", "ts", "jsx", "js", "mjs", "mts", "module.css", "module.scss", "css", "scss"]
- *
- * You can provide your own extensions to optimize build performance or extend the list based on your use case.
- */
ext?: string[];
-
- /**
- * Custom loader for file processing.
- * @defaultValue "text"
- */
loader?: "text" | "base64" | "dataurl" | "file" | "binary" | "default";
-
- /**
- * Extensions to be treated as text files.
- */
- textExtensions?: string[];
+ customLoaders?: Record
Please enroll in [our courses](https://mayank-chaudhari.vercel.app/courses) or [sponsor](https://github.com/sponsors/mayank1513) our work.
+>
Please consider [sponsoring](https://github.com/sponsors/mayank1513) or [joining a course](https://mayank-chaudhari.vercel.app/courses) to support this work.
-with π by Mayank Kumar Chaudhari
diff --git a/lib/__tests__/declarations.d.ts b/lib/__tests__/declarations.d.ts index 7adcba24..9800d7e6 100644 --- a/lib/__tests__/declarations.d.ts +++ b/lib/__tests__/declarations.d.ts @@ -1,2 +1,7 @@ +declare module "*?file"; +declare module "*?base64"; +declare module "*?binary"; +declare module "*?buffer"; +declare module "*?text"; declare module "*?raw"; declare module "*.md"; diff --git a/lib/__tests__/index.test.ts b/lib/__tests__/index.test.ts index 640fd757..805e85ea 100644 --- a/lib/__tests__/index.test.ts +++ b/lib/__tests__/index.test.ts @@ -27,10 +27,24 @@ describe("Raw plugin", () => { test("test raw import with auto ext", async ({ expect }) => { await esbuild.build({ ...buildOptions, entryPoints: [path.resolve(__dirname, "test1.ts")] }); - const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts"), "utf-8"); + const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts")); // @ts-ignore const generatedCodeContent = (await import("./dist/test1.js")).getText(); - expect(fileContent).toBe(generatedCodeContent); + // @ts-ignore + const generatedCodeContent2 = (await import("./dist/test1.js")).getText2(); + expect(generatedCodeContent).toBe(fileContent.toString("base64")); + expect(generatedCodeContent2.toString("base64")).toBe(fileContent.toString("base64")); + }); + + test("test buffer", async ({ expect }) => { + await esbuild.build({ ...buildOptions, entryPoints: [path.resolve(__dirname, "test4.ts")] }); + const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts")); + // @ts-ignore + const generatedCodeContent = (await import("./dist/test4.js")).getBuffer(); + // @ts-ignore + expect(Buffer.from(generatedCodeContent).toString("base64")).toBe( + fileContent.toString("base64"), + ); }); test("throws error if no file is found", async ({ expect }) => { @@ -46,7 +60,7 @@ describe("Raw plugin", () => { test("textExtensions", async ({ expect }) => { await esbuild.build({ ...buildOptions, - plugins: [raw({ textExtensions: [".md"] })], + plugins: [raw({ customLoaders: { md: "text" } })], entryPoints: [path.resolve(__dirname, "test3.ts")], }); @@ -56,15 +70,45 @@ describe("Raw plugin", () => { expect(fileContent).toBe(generatedCodeContent); }); + test("uses custom loader if provided", async ({ expect }) => { + await esbuild.build({ + ...buildOptions, + entryPoints: [path.resolve(__dirname, "test-loader.ts")], + plugins: [raw({ loader: "base64" })], + outdir: "__tests__/dist2", + }); + const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts")); + // @ts-ignore + const generatedCodeContent = (await import("./dist2/test-loader.js")).getText(); + expect(generatedCodeContent).toBe(fileContent.toString("base64")); + }); + + test("uses customLoaders mapping for extension", async ({ expect }) => { + await esbuild.build({ + ...buildOptions, + plugins: [raw({ customLoaders: { md: "text" } })], + entryPoints: [path.resolve(__dirname, "test3.ts")], + outdir: "__tests__/dist3", + }); + const fileContent = fs.readFileSync(path.resolve(__dirname, "test.md"), "utf-8"); + // @ts-ignore + const generatedCodeContent = (await import("./dist3/test3.js")).getText(); + expect(generatedCodeContent).toBe(fileContent); + }); + + test("uses custom plugin name if provided", ({ expect }) => { + const plugin = raw({ name: "custom-plugin-name" }); + expect(plugin.name).toBe("custom-plugin-name"); + }); test("custom loader", async ({ expect }) => { await esbuild.build({ ...buildOptions, entryPoints: [path.resolve(__dirname, "test-loader.ts")], plugins: [raw({ loader: "base64" })], }); - const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts"), "utf-8"); + const fileContent = fs.readFileSync(path.resolve(__dirname, "../src/index.ts")); // @ts-ignore const generatedCodeContent = (await import("./dist/test-loader.js")).getText(); - expect(fileContent).toBe(atob(generatedCodeContent)); + expect(generatedCodeContent).toBe(fileContent.toString("base64")); }); }); diff --git a/lib/__tests__/test1.ts b/lib/__tests__/test1.ts index 4f2e2232..4555ee28 100644 --- a/lib/__tests__/test1.ts +++ b/lib/__tests__/test1.ts @@ -1,5 +1,7 @@ // test auto complete -import text from "../src?raw"; +import text from "../src?base64"; +import text2 from "../src?binary"; export const getText = () => text; +export const getText2 = () => text2; diff --git a/lib/__tests__/test2.ts b/lib/__tests__/test2.ts index ad640664..aa3e32a3 100644 --- a/lib/__tests__/test2.ts +++ b/lib/__tests__/test2.ts @@ -1,5 +1,5 @@ // test auto error -import text from "../src/my-file?raw"; +import text from "../src/my-file?buffer"; export const getText = () => text; diff --git a/lib/__tests__/test4.ts b/lib/__tests__/test4.ts new file mode 100644 index 00000000..44a3afde --- /dev/null +++ b/lib/__tests__/test4.ts @@ -0,0 +1,5 @@ +// test auto complete + +import buffer from "../src?binary"; + +export const getBuffer = () => buffer; diff --git a/lib/src/index.ts b/lib/src/index.ts index 770fe871..592f21b3 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -2,6 +2,19 @@ import type { Plugin, PluginBuild } from "esbuild"; import fs from "node:fs"; import path from "node:path"; +const DEFAULT_EXT_ORDER_LIST = [ + "ts", + "tsx", + "js", + "jsx", + "mjs", + "mts", + "module.css", + "module.scss", + "css", + "scss", +]; + export interface RawPluginOptions { /** * File extensions to check in order of priority if the specified file is missing. @@ -12,14 +25,21 @@ export interface RawPluginOptions { /** * Custom loader for file processing. + * Overridden by import query suffix (?text, ?base64, etc). * @defaultValue "text" */ loader?: "text" | "base64" | "dataurl" | "file" | "binary" | "default"; /** - * Extensions to be treated as text files. + * Map file extensions (without dot) to custom loaders. + * Example: { md: "text", png: "dataurl" } */ - textExtensions?: string[]; + customLoaders?: Record