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
65 changes: 56 additions & 9 deletions docs/src/routes/guide/language-switcher.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { withSolidBase } from "@kobalte/solidbase/config";

export default defineConfig(
withSolidBase(
/* your SolidStart config */,
/* your SolidStart config */
{
markdown: {
expressiveCode: {
Expand All @@ -42,15 +42,30 @@ import { withSolidBase } from "@kobalte/solidbase/config";

export default defineConfig(
withSolidBase(
/* your SolidStart config */,
/* your SolidStart config */
{
markdown: {
expressiveCode: {
languageSwitcher: {
showToggleButton: true,
formatter: async (jsCode, isJsx) => {
// Custom formatting
return code;
toggleButtonTsLabel: "TypeScript",
toggleButtonJsLabel: "JavaScript",
conversions: {
// Override default formatter for typescript
typescript: {
to: "javascript",
fileExtensionMap: { ".ts": ".js" },
formatter: (code) => myFormatter(code),
},
// Add a new conversion for Svelte
svelte: {
to: "svelte",
fileExtensionMap: { ".svelte": ".svelte" },
converter: svelteConverter,
formatter: svelteFormatter
},
// Disable the default "tsx" conversion
tsx: false,
},
},
},
Expand All @@ -62,12 +77,12 @@ export default defineConfig(

### Supported Languages

The plugin converts these TypeScript variants to JavaScript:
By default, the plugin converts these TypeScript variants to JavaScript:

- `typescript` / `ts` → `javascript` / `js`
- `tsx` → `jsx`

JavaScript code blocks (`js`, `javascript`, `jsx`) are not converted.
You can extend or override these defaults using the [`conversions`](#conversions) option.

### Meta Options

Expand All @@ -93,8 +108,40 @@ The plugin automatically preserves line markers.
- **Default:** `true`
- **Description:** Whether to show the toggle button in the code block header.

### `toggleButtonTsLabel`

- **Type:** `string`
- **Default:** `"TS"`
- **Description:** The label for the TypeScript toggle button.

### `toggleButtonJsLabel`

- **Type:** `string`
- **Default:** `"JS"`
- **Description:** The label for the JavaScript toggle button.

### `conversions`

- **Type:** `Record<string, ConversionRule | false>`
- **Description:** A map defining language conversion rules.
The key is the source language ID.
This allows you to extend the default conversions (e.g., add Svelte) or override them (e.g., change the formatter for TypeScript).
To disable a default conversion (e.g., for "typescript"), set its value to `false`.

A `ConversionRule` object has the following properties:

- `to?: string`: The target language ID to use for syntax highlighting after conversion.
If not provided, the source language ID is used.
- `fileExtensionMap: Record<string, string>`: A map of source file extensions to their target extensions for this language (e.g., `{ ".ts": ".js" }`).
This is used to update the filename in the code block title.
- `converter: Converter`: A converter for this language.
- `formatter?: Formatter`: An optional formatter for this language's generated code.
This will override the top-level `formatter` option if provided.

### `formatter`

- **Type:** `(jsCode: string, isJsx; boolean) => string | Promise<string>`
- **Type:** `(jsCode: string, isJsx: boolean) => string | Promise<string>`
- **Default:** Formatting with Prettier with default options
- **Description:** A function to format the generated JavaScript code.
- **Description:** A function to format the generated code.
This acts as the default formatter.
It can be overridden by the `formatter` property within the `conversions` map.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
"import": "./src/client/index.tsx",
"types": "./src/client/index.tsx"
},
"./config/plugins": {
"solid": "./src/config/plugins.ts",
"import": "./src/config/plugins.ts",
"types": "./src/config/plugins.ts"
},
"./server": {
"solid": "./src/server.ts",
"import": "./src/server.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,25 +385,44 @@ export type Marker = {
};
export const MarkerTypeOrder: Array<MarkerType> = ["mark", "del", "ins"];

export async function tsToJs(
tsCode: string,
tsMarkers: Array<Marker>,
isJsx?: boolean,
formatter?: (jsCode: string, isJsx?: boolean) => string | Promise<string>,
): Promise<{
jsCode: string;
markers: Array<Marker>;
}> {
export type Formatter = (code: string) => string | Promise<string>;

/**
* A function that transforms code and line markers from a source language to a target language.
*/
export type Converter = (context: {
sourceCode: string;
sourceMarkers: Array<Marker>;
sourceLanguage: string;
formatter?: Formatter;
}) => Promise<{
targetCode: string;
targetMarkers: Array<Marker>;
}>;

export async function defaultFormatter(jsCode: string) {
return await prettier.format(jsCode, {
parser: "babel",
});
}

export const defaultConverter: Converter = async ({
sourceCode,
sourceMarkers,
sourceLanguage,
formatter,
}) => {
const isJsx = ["tsx", "jsx"].includes(sourceLanguage);
const fileName = isJsx ? "temp.tsx" : "temp.ts";
const ast = ts.createSourceFile(
fileName,
tsCode,
sourceCode,
ts.ScriptTarget.Latest,
true,
isJsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
);

const ms = new MagicString(tsCode);
const ms = new MagicString(sourceCode);

removeTripleSlashDirectives(ms, ast);

Expand All @@ -418,18 +437,9 @@ export async function tsToJs(

const unformattedCode = ms.toString();

let formattedCode = unformattedCode;
try {
if (formatter) {
formattedCode = await formatter(unformattedCode, isJsx);
} else {
formattedCode = await prettier.format(unformattedCode, {
parser: "babel",
});
}
} catch (error) {
console.error("Error during post-processing JavaScript code:", error);
}
const formattedCode = formatter
? await formatter(unformattedCode)
: unformattedCode;

const changes = diffChars(unformattedCode, formattedCode);
const formatMs = new MagicString(unformattedCode);
Expand Down Expand Up @@ -490,10 +500,10 @@ export async function tsToJs(
}
});

const jsMarkers: Array<Marker> = [];
for (const tsMarker of tsMarkers) {
const targetMarkers: Array<Marker> = [];
for (const sourceMarker of sourceMarkers) {
const jsMarkerLines = new Set<number>();
for (const tsLine of tsMarker.lines) {
for (const tsLine of sourceMarker.lines) {
const rawLines = tsToJsMap.get(tsLine);
if (rawLines) {
for (const rawLine of rawLines) {
Expand All @@ -506,14 +516,14 @@ export async function tsToJs(
}
}
}
jsMarkers.push({
...tsMarker,
targetMarkers.push({
...sourceMarker,
lines: [...jsMarkerLines].sort((a, b) => a - b),
});
}

return {
jsCode: formattedCode,
markers: jsMarkers,
targetCode: formattedCode,
targetMarkers: targetMarkers,
};
}
};
Loading