Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: properly strip types for JS registry #1371

Merged
merged 9 commits into from
Oct 27, 2024
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
6 changes: 3 additions & 3 deletions packages/cli/src/utils/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ export function cn(...inputs: ClassValue[]) {

export const UTILS_JS = `import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import tailwindcssAnimate from "tailwindcss-animate";

export function cn(...inputs) {
return twMerge(clsx(inputs));
}
`;

const TAILWIND_JS = `import { fontFamily } from "tailwindcss/defaultTheme";
import tailwindcssAnimate from "tailwindcss-animate";

/** @type {import('tailwindcss').Config} */
const config = {`;
Expand Down Expand Up @@ -96,10 +96,10 @@ const TAILWIND_WITH_VARIABLES = `
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
to: { height: "var(--bits-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
from: { height: "var(--bits-accordion-content-height)" },
to: { height: "0" },
},
"caret-blink": {
Expand Down
27 changes: 19 additions & 8 deletions pnpm-lock.yaml

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

1 change: 1 addition & 0 deletions sites/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"tailwind-variants": "^0.2.1",
"tailwindcss": "^3.4.4",
"tailwindcss-animate": "^1.0.7",
"ts-blank-space": "^0.4.1",
"tslib": "^2.6.3",
"tsx": "^4.16.2",
"typescript": "~5.5.3",
Expand Down
21 changes: 17 additions & 4 deletions sites/docs/scripts/build-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@ import { buildRegistry } from "./registry";
import { BASE_STYLES, BASE_STYLES_WITH_VARIABLES, THEME_STYLES_WITH_VARIABLES } from "./templates";
import { getChunks } from "./transform-chunks";
import { transformContent } from "./transformers";
import prettier from "prettier";
import prettierPluginSvelte from "prettier-plugin-svelte";

const REGISTRY_PATH = path.resolve("static", "registry");
const REGISTRY_IGNORE = ["super-form"];

const prettierConfig: prettier.Config = {
useTabs: true,
singleQuote: false,
trailingComma: "es5",
printWidth: 100,
};

function writeFileWithDirs(
filePath: string,
data: string,
Expand Down Expand Up @@ -178,11 +187,15 @@ export const Index = {

const jsFiles = await Promise.all(
files.map(async (file) => {
const content = (await transformContent(file.content, file.name)).replaceAll(
" ",
"\t"
);
let content = await transformContent(file.content, file.name);
const fileName = file.name.replace(".ts", ".js");
// format
content = await prettier.format(content, {
...prettierConfig,
filepath: fileName,
plugins: [prettierPluginSvelte],
overrides: [{ files: "*.svelte", options: { parser: "svelte" } }],
});
return {
name: fileName,
content,
Expand Down
148 changes: 94 additions & 54 deletions sites/docs/scripts/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,119 @@
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { preprocess } from "svelte/compiler";
import ts from "typescript";
import { walk } from "estree-walker";
import { preprocess, parse, type PreprocessorGroup } from "svelte/compiler";
import tsBlankSpace from "ts-blank-space";
import MagicString from "magic-string";

export type TransformOpts = {
filename: string;
content: string;
// baseColor?: z.infer<typeof registryBaseColorSchema>; - will use later
};

// const sharedPrettierConfig = {
// useTabs: true,
// tabWidth: 4,
// singleQuote: false,
// trailingComma: "es5" as const,
// printWidth: 100,
// endOfLine: "lf" as const,
// bracketSameLine: false,
// };

// const registrySveltePrettierConfig = {
// ...sharedPrettierConfig,
// pluginSearchDirs: ["./node_modules/prettier-plugin-svelte"],
// parser: "svelte",
// svelteStrictMode: false,
// plugins: ["prettier-plugin-svelte"],
// };

// const registryJSPrettierConfig = {
// ...sharedPrettierConfig,
// parser: "babel",
// };

export async function transformContent(content: string, filename: string) {
if (filename.endsWith(".svelte")) {
return transformSvelteTStoJS(content, filename);
} else {
return transformTStoJS(content, filename);
}
}

async function transformSvelteTStoJS(content: string, filename: string) {
try {
const { code } = await preprocess(content, [vitePreprocess()]);
let s = code.replaceAll(/<script lang=['"]ts['"]>/g, "<script>");
s = s.replaceAll(/void 0/g, "undefined");
return s;
const { code } = await preprocess(content, [stripMarkupTypes(), stripScriptTypes()], {
filename,
});
// strip out empty module blocks
return code.replace("<script module></script>", "").trimStart();
} catch (e) {
throw new Error(`Error preprocessing Svelte file: ${filename} \n ${e}`);
}
}

const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ESNext,
isolatedModules: true,
preserveValueImports: true,
lib: ["esnext", "DOM", "DOM.Iterable"],
moduleResolution: ts.ModuleResolutionKind.Bundler,
esModuleInterop: true,
ignoreDeprecations: "5.0",
};

function transformTStoJS(content: string, filename: string) {
const { outputText, diagnostics } = ts.transpileModule(content, {
compilerOptions,
reportDiagnostics: true,
});

// Check for compilation errors
if (diagnostics && diagnostics.length > 0) {
// Throw the errors so the user can see them/create an issue about them.
const output = tsBlankSpace(content, (node) => {
throw new Error(
`Error compiling TypeScript to JavaScript for file: ${filename} \n ${diagnostics}`
`Error compiling TypeScript to JavaScript for file: ${filename} \n ${node.pos}`
);
} else {
return outputText;
}
});

return output;
}

function stripScriptTypes(): PreprocessorGroup {
return {
// strip the `lang="ts"` attribute
script: ({ content, attributes, filename }) => {
if (attributes["lang"] !== "ts") return;
delete attributes["lang"];
delete attributes["generics"];
return { code: transformTStoJS(content, filename!).trim(), attributes };
},
};
}

function stripMarkupTypes(): PreprocessorGroup {
return {
markup: ({ content, filename }) => {
const ms = new MagicString(content);
const ast = parse(content, { filename, modern: true });

// @ts-expect-error simmer down
walk(ast.fragment, {
enter(node: (typeof ast)["fragment"]["nodes"][number]) {
// ignore typescript specific nodes
if (node.type.startsWith("TS")) return;

if (node.type === "SnippetBlock") {
if (node.parameters.length === 0) return;
// @ts-expect-error relax buddy
const start = node.parameters.at(0)!.start;
const end =
// @ts-expect-error you too
node.parameters.at(-1)!.typeAnnotation?.end ??
// @ts-expect-error and you
node.parameters.at(-1)!.end;

const params = content.substring(start, end);
// temporarily wraps the params in an arrow function so that it's parsable
const arrow = " => {}";
const wrapped = `(${params})${arrow}`;
const stripped = transformTStoJS(wrapped, filename!)
.replace(arrow, "")
.slice(1, -1);
ms.update(start, end, stripped);
} else if (node.type === "ConstTag") {
// @ts-expect-error hush
const { start, end } = node.declaration;
const expression = content.substring(start, end);
const wrapped = `(${expression})`;
const stripped = transformTStoJS(wrapped, filename!).slice(1, -1);
ms.update(start, end, stripped);

this.skip();
} else if (node.type === "RenderTag" || node.type === "ExpressionTag") {
// @ts-expect-error take a breather
const { start, end } = node.expression;
const expression = content.substring(start, end);
const wrapped = `(${expression})`;
const stripped = transformTStoJS(wrapped, filename!).slice(1, -1);
ms.update(start, end, stripped);

this.skip();
} else if ("expression" in node) {
// @ts-expect-error trust me
const { start, end } = node.expression;

const expression = content.substring(start, end);
const wrapped = `(${expression})`;

// removes the `()`
const stripped = transformTStoJS(wrapped, filename!).slice(1, -1);

ms.update(start, end, stripped);
}
},
});

return { code: ms.toString() };
},
};
}
4 changes: 2 additions & 2 deletions sites/docs/src/__registry__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ export const Index = {
"select-demo": {
name: "select-demo",
type: "registry:example",
registryDependencies: ["select","label"],
registryDependencies: ["select"],
component: () => import("../lib/registry/default/example/select-demo.svelte").then((m) => m.default),
files: ["../lib/registry/default/example/select-demo.svelte"],
raw: () => import("../lib/registry/default/example/select-demo.svelte?raw").then((m) => m.default),
Expand Down Expand Up @@ -1703,7 +1703,7 @@ export const Index = {
"select-demo": {
name: "select-demo",
type: "registry:example",
registryDependencies: ["select","label"],
registryDependencies: ["select"],
component: () => import("../lib/registry/new-york/example/select-demo.svelte").then((m) => m.default),
files: ["../lib/registry/new-york/example/select-demo.svelte"],
raw: () => import("../lib/registry/new-york/example/select-demo.svelte?raw").then((m) => m.default),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
<script lang="ts" module>
import type { CellContext, ColumnDefTemplate, HeaderContext } from "@tanstack/table-core";
import {
RenderComponentConfig,
RenderSnippetConfig,
} from "$lib/registry/default/ui/data-table/render-helpers.js";

type TData = unknown;
type TValue = unknown;
type TContext = unknown;
Expand All @@ -13,6 +10,10 @@
lang="ts"
generics="TData, TValue, TContext extends HeaderContext<TData, TValue> | CellContext<TData, TValue>"
>
import {
RenderComponentConfig,
RenderSnippetConfig,
} from "$lib/registry/default/ui/data-table/render-helpers.js";
type Props = {
/** The cell or header field of the current cell's column definition. */
content?: TContext extends HeaderContext<TData, TValue>
Expand Down
Loading