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
7 changes: 7 additions & 0 deletions .changeset/new-coins-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"sideffect": patch
---

- Remove TypeScript from the Sideffect runtime dependency graph to reduce install size.
- Lazy-load the consuming project's TypeScript parser only when the Vite workflow discovery adapter scans workflow files.
- Avoid a TypeScript peer range so beta and RC releases are not blocked by npm prerelease range matching.
4 changes: 1 addition & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions packages/sideffect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@
"prepublishOnly": "vp run build",
"prepare": "vp config"
},
"dependencies": {
"typescript": ">=5.0.0 <7"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.40.0",
"@cloudflare/workers-types": "4.20260605.1",
Expand All @@ -44,6 +41,7 @@
"bumpp": "11.1.0",
"defu": "6.1.7",
"effect": "4.0.0-beta.78",
"typescript": "catalog:",
"vite-plus": "catalog:"
},
"peerDependencies": {
Expand Down
62 changes: 52 additions & 10 deletions packages/sideffect/src/vite/workflow-discovery.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, extname, join, resolve } from "node:path";

import ts from "typescript";
import { Cause, Effect, Exit } from "effect";
import type * as TypeScript from "typescript";

import { validateWorkflowExportName } from "../entrypoints.ts";
import type { WorkflowConfigEntry } from "../types.ts";
Expand All @@ -18,6 +20,46 @@ const sourceFileExtensions = new Set([
]);
const declarationFileExtensions = [".d.ts", ".d.mts", ".d.cts"];
const identifierName = /^[$A-Z_a-z][$\w]*$/;
type TypeScriptModule = typeof TypeScript;

class TypeScriptWorkflowDiscoveryError extends Error {
constructor(cause: unknown) {
super(
'Sideffect workflow discovery requires TypeScript\'s parser, but the "typescript" package could not be resolved. Install TypeScript in the project that uses `sideffect/vite`, for example `npm install -D typescript`. Runtime usage of `sideffect` and `sideffect/cloudflare` is unaffected.',
{ cause },
);
this.name = "TypeScriptWorkflowDiscoveryError";
}
}

const require = createRequire(import.meta.url);
let loadedTypeScript: TypeScriptModule | undefined;

function loadTypeScript(): TypeScriptModule {
if (loadedTypeScript) {
return loadedTypeScript;
}

const loaded = Effect.runSyncExit(
Effect.try({
try: () => require("typescript") as TypeScriptModule,
catch: (cause) => new TypeScriptWorkflowDiscoveryError(cause),
}),
);

if (Exit.isSuccess(loaded)) {
loadedTypeScript = loaded.value;
return loaded.value;
}

throw Cause.squash(loaded.cause);
}

const ts = new Proxy({} as TypeScriptModule, {
get(_target, property) {
return loadTypeScript()[property as keyof TypeScriptModule];
},
});

/** Workflow source paths scanned for static `Workflow.make(...).toLayer(...)` exports. */
export type WorkflowDiscoveryPaths = Array<string>;
Expand Down Expand Up @@ -407,7 +449,7 @@ function analyzeWorkflowSourceFile(
return { exports, definitionExports, reExports };
}

function collectWorkflowBindings(sourceFile: ts.SourceFile): {
function collectWorkflowBindings(sourceFile: TypeScript.SourceFile): {
readonly names: Set<string>;
readonly namespaces: Set<string>;
} {
Expand Down Expand Up @@ -446,7 +488,7 @@ function collectWorkflowBindings(sourceFile: ts.SourceFile): {
}

function collectImportedWorkflowDefinitions(
sourceFile: ts.SourceFile,
sourceFile: TypeScript.SourceFile,
filePath: string,
visited: Set<string>,
): Map<string, string> {
Expand All @@ -470,7 +512,7 @@ function collectImportedWorkflowDefinitions(
return definitions;
}

function collectLocalImports(sourceFile: ts.SourceFile): Array<LocalImportDeclaration> {
function collectLocalImports(sourceFile: TypeScript.SourceFile): Array<LocalImportDeclaration> {
return sourceFile.statements.flatMap((statement) => {
if (
!ts.isImportDeclaration(statement) ||
Expand Down Expand Up @@ -515,7 +557,7 @@ function collectLocalImports(sourceFile: ts.SourceFile): Array<LocalImportDeclar
}

function workflowNameFromLayerExpression(
expression: ts.Expression,
expression: TypeScript.Expression,
workflowBindings: { readonly names: Set<string>; readonly namespaces: Set<string> },
workflowDefinitions: Map<string, string>,
): string | undefined {
Expand All @@ -538,7 +580,7 @@ function workflowNameFromLayerExpression(
}

function workflowNameFromMakeCall(
expression: ts.Expression,
expression: TypeScript.Expression,
workflowBindings: { readonly names: Set<string>; readonly namespaces: Set<string> },
): string | undefined {
const call = skipOuterExpressions(expression);
Expand Down Expand Up @@ -595,8 +637,8 @@ function workflowNameFromMakeCall(
}

function exportSpecifierNames(
declaration: ts.ExportDeclaration,
exports: ts.NamedExports,
declaration: TypeScript.ExportDeclaration,
exports: TypeScript.NamedExports,
): Array<{ readonly imported: string; readonly exported: string }> {
if (declaration.isTypeOnly) {
return [];
Expand Down Expand Up @@ -626,7 +668,7 @@ function isLocalSpecifier(specifier: string): boolean {
return specifier.startsWith(".") || specifier.startsWith("/");
}

function skipOuterExpressions(expression: ts.Expression): ts.Expression {
function skipOuterExpressions(expression: TypeScript.Expression): TypeScript.Expression {
let current = expression;
while (
ts.isParenthesizedExpression(current) ||
Expand All @@ -640,7 +682,7 @@ function skipOuterExpressions(expression: ts.Expression): ts.Expression {
return current;
}

function scriptKindForFile(path: string): ts.ScriptKind {
function scriptKindForFile(path: string): TypeScript.ScriptKind {
switch (extname(path)) {
case ".tsx":
return ts.ScriptKind.TSX;
Expand Down
Loading