Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
* });
*/

import type { Plugin, ResolvedConfig } from "vite";
import type { Plugin, ResolvedConfig, ViteDevServer } from "vite";
import type { SolidGrabPluginOptions } from "./types.js";

const VIRTUAL_INIT = "virtual:solid-grab-init";
const RESOLVED_VIRTUAL_INIT = "\0" + VIRTUAL_INIT;

// ── Regex-based JSX transform ────────────────────────────────────────
//
// We intentionally avoid a full AST parse here for speed. Solid JSX is
Expand Down Expand Up @@ -191,6 +194,17 @@ export default function solidGrab(
isDev = config.command === "serve" || config.mode === "development";
},

// Virtual module that imports the runtime — resolved by Vite's pipeline
resolveId(id) {
if (id === VIRTUAL_INIT) return RESOLVED_VIRTUAL_INIT;
},

load(id) {
if (id === RESOLVED_VIRTUAL_INIT) {
return `import "solid-grab";`;
}
},

transform(code, id) {
// Only transform in dev mode
if (!isDev) return null;
Expand All @@ -217,17 +231,27 @@ export default function solidGrab(
};
},

// Auto-inject the runtime script in dev mode
transformIndexHtml(html) {
if (!autoImport) return html;
if (!isDev) return html;

// Inject a module script that imports solid-grab
const script = `<script type="module">
import("solid-grab").catch(() => {});
</script>`;
// Serve the virtual init module through Vite's transform pipeline
configureServer(server: ViteDevServer) {
server.middlewares.use((req, _res, next) => {
// Rewrite the URL so Vite's built-in module serving handles it
if (req.url === "/@solid-grab/init") {
req.url = `/@id/${VIRTUAL_INIT}`;
}
next();
});
},

return html.replace("</head>", `${script}\n</head>`);
// Inject a <script src> that Vite's dev server will resolve
transformIndexHtml() {
if (!autoImport || !isDev) return;
return [
{
tag: "script",
attrs: { type: "module", src: "/@solid-grab/init" },
injectTo: "head" as const,
},
];
},
};
}
27 changes: 14 additions & 13 deletions tests/vite-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,28 +208,29 @@ describe("transform", () => {
});

describe("transformIndexHtml", () => {
test("injects runtime script in dev mode", () => {
test("returns tag descriptors in dev mode", () => {
const plugin = createPlugin();
const html = "<html><head></head><body></body></html>";
const result = (plugin as any).transformIndexHtml(html);
const result = (plugin as any).transformIndexHtml();

expect(result).toContain('import("solid-grab")');
expect(result).toContain("</head>");
expect(result).toBeArray();
expect(result).toHaveLength(1);
expect(result[0].tag).toBe("script");
expect(result[0].attrs.type).toBe("module");
expect(result[0].attrs.src).toBe("/@solid-grab/init");
expect(result[0].injectTo).toBe("head");
});

test("does not inject in production mode", () => {
test("returns undefined in production mode", () => {
const plugin = createPlugin({}, "production");
const html = "<html><head></head><body></body></html>";
const result = (plugin as any).transformIndexHtml(html);
const result = (plugin as any).transformIndexHtml();

expect(result).not.toContain("solid-grab");
expect(result).toBeUndefined();
});

test("respects autoImport: false", () => {
test("returns undefined when autoImport is false", () => {
const plugin = createPlugin({ autoImport: false });
const html = "<html><head></head><body></body></html>";
const result = (plugin as any).transformIndexHtml(html);
const result = (plugin as any).transformIndexHtml();

expect(result).not.toContain("solid-grab");
expect(result).toBeUndefined();
});
});