Skip to content
Open
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
63 changes: 63 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,69 @@
}
}
}
},
{
"includes": [
"rivetkit-typescript/**/*.browser.ts",
"rivetkit-typescript/**/*.browser.tsx"
],
"linter": {
"rules": {
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"paths": {
"node:fs": "Cannot use Node.js built-in 'fs' in .browser files",
"node:fs/promises": "Cannot use Node.js built-in 'fs/promises' in .browser files",
"node:path": "Cannot use Node.js built-in 'path' in .browser files",
"node:crypto": "Cannot use Node.js built-in 'crypto' in .browser files",
"node:os": "Cannot use Node.js built-in 'os' in .browser files",
"node:child_process": "Cannot use Node.js built-in 'child_process' in .browser files",
"node:stream": "Cannot use Node.js built-in 'stream' in .browser files",
"node:net": "Cannot use Node.js built-in 'net' in .browser files",
"node:url": "Cannot use Node.js built-in 'url' in .browser files",
"node:buffer": "Cannot use Node.js built-in 'buffer' in .browser files",
"node:http": "Cannot use Node.js built-in 'http' in .browser files",
"node:https": "Cannot use Node.js built-in 'https' in .browser files",
"node:zlib": "Cannot use Node.js built-in 'zlib' in .browser files",
"node:events": "Cannot use Node.js built-in 'events' in .browser files",
"node:util": "Cannot use Node.js built-in 'util' in .browser files",
"node:assert": "Cannot use Node.js built-in 'assert' in .browser files",
"node:tls": "Cannot use Node.js built-in 'tls' in .browser files",
"node:dns": "Cannot use Node.js built-in 'dns' in .browser files",
"node:cluster": "Cannot use Node.js built-in 'cluster' in .browser files",
"node:worker_threads": "Cannot use Node.js built-in 'worker_threads' in .browser files",
"node:timers": "Cannot use Node.js built-in 'timers' in .browser files",
"node:process": "Cannot use Node.js built-in 'process' in .browser files",
"fs": "Cannot use Node.js built-in 'fs' in .browser files",
"fs/promises": "Cannot use Node.js built-in 'fs/promises' in .browser files",
"path": "Cannot use Node.js built-in 'path' in .browser files",
"crypto": "Cannot use Node.js built-in 'crypto' in .browser files",
"os": "Cannot use Node.js built-in 'os' in .browser files",
"child_process": "Cannot use Node.js built-in 'child_process' in .browser files",
"stream": "Cannot use Node.js built-in 'stream' in .browser files",
"net": "Cannot use Node.js built-in 'net' in .browser files",
"url": "Cannot use Node.js built-in 'url' in .browser files",
"buffer": "Cannot use Node.js built-in 'buffer' in .browser files",
"http": "Cannot use Node.js built-in 'http' in .browser files",
"https": "Cannot use Node.js built-in 'https' in .browser files",
"zlib": "Cannot use Node.js built-in 'zlib' in .browser files",
"events": "Cannot use Node.js built-in 'events' in .browser files",
"util": "Cannot use Node.js built-in 'util' in .browser files",
"assert": "Cannot use Node.js built-in 'assert' in .browser files",
"tls": "Cannot use Node.js built-in 'tls' in .browser files",
"dns": "Cannot use Node.js built-in 'dns' in .browser files",
"cluster": "Cannot use Node.js built-in 'cluster' in .browser files",
"worker_threads": "Cannot use Node.js built-in 'worker_threads' in .browser files",
"timers": "Cannot use Node.js built-in 'timers' in .browser files",
"process": "Cannot use Node.js built-in 'process' in .browser files"
}
}
}
}
}
}
}
]
}
2 changes: 2 additions & 0 deletions tsup.base.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Options } from "tsup";
import { createBoundaryEnforcementPlugin } from "./tsup.plugin.boundaries.ts";

const FAST_BUILD = process.env.FAST_BUILD === "1";

export default {
esbuildPlugins: [createBoundaryEnforcementPlugin()],
target: "node16",
platform: "node",
format: ["cjs", "esm"],
Expand Down
83 changes: 83 additions & 0 deletions tsup.plugin.boundaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/// <reference types="@types/node" />

import { builtinModules } from "node:module";
import { readFile } from "node:fs/promises";
import type { Plugin } from "esbuild";

/**
* Creates an esbuild plugin that enforces browser code boundaries.
*
* This plugin prevents .browser files from importing Node.js built-in modules.
*
* File naming convention:
* - *.browser.ts/tsx - Browser-only code (cannot use fs, path, crypto, etc.)
* - *.ts/tsx - Universal or server code (can use any APIs)
*
* Note: This uses onLoad instead of onResolve because tsup's `external` config
* prevents onResolve from being called for Node built-in modules.
*/
export function createBoundaryEnforcementPlugin(): Plugin {
const nodeBuiltinSet = new Set(builtinModules);

// Add node: prefixed versions
for (const mod of builtinModules) {
nodeBuiltinSet.add(`node:${mod}`);
}

// Regex to extract import/require statements
const importRegex =
/(?:import\s+(?:[\w*{}\s,]+\s+from\s+)?|require\s*\(\s*)['"]([^'"]+)['"]/g;

function isNodeBuiltin(modulePath: string): boolean {
const moduleName = modulePath.replace("node:", "").split("/")[0];
return nodeBuiltinSet.has(modulePath) || nodeBuiltinSet.has(moduleName);
}

return {
name: "enforce-boundaries",
setup(build) {
// Check .browser files for Node.js built-in imports
build.onLoad(
{ filter: /\.browser\.(ts|tsx|js|jsx|mts|mjs|cts|cjs)$/ },
async (args) => {
const contents = await readFile(args.path, "utf8");
const errors: {
text: string;
location?: { file: string; line: number; column: number };
}[] = [];

// Find all imports
let match: RegExpExecArray | null;
while ((match = importRegex.exec(contents)) !== null) {
const importPath = match[1];

// Check for Node built-in imports
if (isNodeBuiltin(importPath)) {
// Find line number
const lineNumber =
contents.slice(0, match.index).split("\n").length;
errors.push({
text: `❌ BOUNDARY VIOLATION: Cannot use Node.js built-in "${importPath}" in browser context`,
location: {
file: args.path,
line: lineNumber,
column: 0,
},
});
}
}

// Reset regex state
importRegex.lastIndex = 0;

if (errors.length > 0) {
return { errors };
}

// Return undefined to let esbuild handle the file normally
return undefined;
},
);
},
};
}
Loading