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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ apps/site/build
apps/site/public/blog-data.json
apps/site/next-env.d.ts

# Generated Build Artifacts
apps/site/generated

# Test Runner
junit.xml
lcov.info
Expand Down
8 changes: 4 additions & 4 deletions apps/site/components/Blog/BlogPostCard/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
}

.subtitle {
@apply mt-6
@apply text-brand-600
dark:text-brand-400
mt-6
mb-2
inline-block
text-xs
font-semibold
text-green-600
dark:text-green-400;
font-semibold;
}

.title {
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/Common/FormattedTime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const FormattedTime: FC<FormattedTimeProps> = ({ date, format }) => {
const formatter = useFormatter();

// "date" is deterministic
// eslint-disable-next-line @eslint-react/purity

const dateObject = new Date(date);

return (
Expand Down
8 changes: 4 additions & 4 deletions apps/site/components/Common/LinkWithArrow/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
}

.button {
@apply text-green-600
hover:text-green-900
dark:text-green-400
dark:hover:text-green-200;
@apply text-brand-600
hover:text-brand-900
dark:text-brand-400
dark:hover:text-brand-200;
}
2 changes: 1 addition & 1 deletion apps/site/components/Downloads/Release/ReleaseCodeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const ReleaseCodeBox: FC = () => {
{/* Code display with skeleton loading */}
<Skeleton loading={isLoading}>
<CodeBox language={displayLanguage} className="min-h-[19rem]">
{/* eslint-disable-next-line @eslint-react/dom/no-dangerously-set-innerhtml */}
{/* eslint-disable-next-line @eslint-react/dom-no-dangerously-set-innerhtml */}
<code dangerouslySetInnerHTML={{ __html: parsedSnippets }} />
</CodeBox>
</Skeleton>
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/withMetaBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const WithMetaBar: FC = () => {
const formatter = useFormatter();
const lastUpdated = frontmatter.date
? // "frontmatter.date" is deterministic
// eslint-disable-next-line @eslint-react/purity

formatter.dateTime(new Date(frontmatter.date), DEFAULT_DATE_FORMAT)
: undefined;
const readingTimeText = formatter.number(readingTime.minutes, {
Expand Down
30 changes: 30 additions & 0 deletions apps/site/mdx/create-vfs-twoslasher.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

/**
* Creates a Twoslash instance backed by a virtual filesystem for environments
* without real filesystem access (e.g. Cloudflare Workers).
*
* Uses a pre-built JSON map of TypeScript lib declarations and @types/node
* generated at build time by `scripts/twoslash-fsmap/index.mjs`.
*/
export async function createVfsTwoslasher() {
const { createTwoslasher } = await import('twoslash/core');
const ts = (await import('typescript')).default;
const fsMapJson = (
await import('../generated/twoslash-fsmap.json', { with: { type: 'json' } })
).default;

const fsMap = new Map(Object.entries(fsMapJson));

return createTwoslasher({
fsMap,
tsModule: ts,
vfsRoot: '/',
compilerOptions: {
moduleResolution: ts.ModuleResolutionKind.Bundler,
// Explicitly include @types/node so that the VFS resolves Node.js
// globals and `node:*` module imports from the bundled declarations.
types: ['node'],
},
});
}
12 changes: 10 additions & 2 deletions apps/site/mdx/plugins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import rehypeSlug from 'rehype-slug';
import remarkGfm from 'remark-gfm';
import readingTime from 'remark-reading-time';

import { createVfsTwoslasher } from './create-vfs-twoslasher.mjs';
import remarkTableTitles from '../util/table';

// TODO(@avivkeller): When available, use `OPEN_NEXT_CLOUDFLARE` environment
Expand All @@ -25,8 +26,15 @@ const singletonShiki = await rehypeShikiji({
// for security reasons.
wasm: !OPEN_NEXT_CLOUDFLARE,

// TODO(@avivkeller): Find a way to enable Twoslash w/ a VFS on Cloudflare
twoslash: !OPEN_NEXT_CLOUDFLARE,
twoslash: true,

// On Cloudflare Workers, the default filesystem-backed Twoslash cannot work
// because there is no real filesystem. Instead, we provide a custom twoslasher
// backed by an in-memory VFS pre-populated at build time with TypeScript
// lib declarations and @types/node.
twoslashOptions: OPEN_NEXT_CLOUDFLARE
? { twoslasher: await createVfsTwoslasher() }
: undefined,
});

/**
Expand Down
6 changes: 4 additions & 2 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"name": "@node-core/website",
"type": "module",
"scripts": {
"prebuild": "node --run build:blog-data",
"prebuild": "node --run build:blog-data && node --run build:twoslash-fsmap",
"build": "cross-env NODE_NO_WARNINGS=1 next build",
"build:blog-data": "cross-env NODE_NO_WARNINGS=1 node ./scripts/blog-data/index.mjs",
"build:blog-data:watch": "node --watch --watch-path=pages/en/blog ./scripts/blog-data/index.mjs",
"build:twoslash-fsmap": "node ./scripts/twoslash-fsmap/index.mjs",
"cloudflare:build:worker": "OPEN_NEXT_CLOUDFLARE=true opennextjs-cloudflare build",
"cloudflare:deploy": "opennextjs-cloudflare deploy",
"cloudflare:preview": "wrangler dev",
Expand Down Expand Up @@ -47,6 +48,7 @@
"@tailwindcss/postcss": "~4.3.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@typescript/vfs": "^1.6.4",
"@vcarl/remark-headings": "~0.1.0",
"@vercel/analytics": "~2.0.1",
"@vercel/otel": "~2.1.2",
Expand Down Expand Up @@ -79,7 +81,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260418.1",
"@eslint-react/eslint-plugin": "~3.0.0",
"@eslint-react/eslint-plugin": "~5.8.6",
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1",
"@next/eslint-plugin-next": "16.2.4",
"@node-core/remark-lint": "workspace:*",
Expand Down
66 changes: 66 additions & 0 deletions apps/site/scripts/twoslash-fsmap/generate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

import { readdirSync, readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join, resolve } from 'node:path';

import { createDefaultMapFromNodeModules } from '@typescript/vfs';
import ts from 'typescript';

const require = createRequire(import.meta.url);

/**
* Recursively collects all `.d.ts` files from a directory into the fsMap.
*
* @param {Map<string, string>} fsMap The map to populate
* @param {string} dir The directory to walk
* @param {string} virtualPrefix The virtual path prefix (e.g., "/node_modules/@types/node")
* @param {string} baseDir The base directory for computing relative paths
*/
function collectDtsFiles(fsMap, dir, virtualPrefix, baseDir) {
const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) =>
a.name.localeCompare(b.name)
);

for (const entry of entries) {
const fullPath = join(dir, entry.name);

if (entry.isDirectory()) {
collectDtsFiles(fsMap, fullPath, virtualPrefix, baseDir);
} else if (entry.isFile() && /\.d\.([^.]+\.)?[cm]?ts$/i.test(entry.name)) {
const relativePath = fullPath.slice(baseDir.length).replace(/\\/g, '/');
const virtualPath = `${virtualPrefix}${relativePath}`;

fsMap.set(virtualPath, readFileSync(fullPath, 'utf8'));
}
}
}

/**
* Generates a virtual filesystem map containing all TypeScript library
* declaration files and `@types/node` declarations needed for Twoslash
* to run without real filesystem access (e.g., on Cloudflare Workers).
*
* @returns {Map<string, string>} A map of virtual paths to file contents
*/
export default function generateTwoslashFsMap() {
// 1. Collect TypeScript lib .d.ts files using @typescript/vfs
// This returns a Map keyed as "/lib.es5.d.ts", "/lib.dom.d.ts", etc.
const fsMap = createDefaultMapFromNodeModules({}, ts);

// 2. Collect @types/node .d.ts files
// These are keyed as "/node_modules/@types/node/index.d.ts", etc.
const typesNodeDir = resolve(
require.resolve('@types/node/package.json'),
'..'
);

collectDtsFiles(
fsMap,
typesNodeDir,
'/node_modules/@types/node',
typesNodeDir
);

return fsMap;
}
15 changes: 15 additions & 0 deletions apps/site/scripts/twoslash-fsmap/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

import { mkdirSync, writeFileSync } from 'node:fs';

import generateTwoslashFsMap from './generate.mjs';

const fsMap = generateTwoslashFsMap();

const outputPath = new URL(
'../../generated/twoslash-fsmap.json',
import.meta.url
);

mkdirSync(new URL('.', outputPath), { recursive: true });
writeFileSync(outputPath, JSON.stringify(Object.fromEntries(fsMap)), 'utf8');
8 changes: 6 additions & 2 deletions apps/site/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
]
},
"build": {
"dependsOn": ["build:blog-data", "^build"],
"dependsOn": ["build:blog-data", "build:twoslash-fsmap", "^build"],
"inputs": [
"{app,components,hooks,i18n,layouts,middlewares,pages,providers,types,util}/**/*.{ts,tsx}",
"{app,components,layouts,pages,styles}/**/*.css",
Expand Down Expand Up @@ -137,8 +137,12 @@
"ENABLE_EXPERIMENTAL_COREPACK"
]
},
"build:twoslash-fsmap": {
"inputs": ["scripts/twoslash-fsmap/**", "../../pnpm-lock.yaml"],
"outputs": ["generated/twoslash-fsmap.json"]
},
"cloudflare:build:worker": {
"dependsOn": ["build:blog-data"],
"dependsOn": ["build:blog-data", "build:twoslash-fsmap"],
"inputs": [
"{app,components,hooks,i18n,layouts,middlewares,pages,providers,types,util}/**/*.{ts,tsx}",
"{app,components,layouts,pages,styles}/**/*.css",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"dependencies": {
"husky": "9.1.7",
"lint-staged": "16.4.0",
"turbo": "2.9.6"
"turbo": "2.9.14"
},
"devDependencies": {
"@eslint/js": "~10.0.1",
Expand Down
95 changes: 60 additions & 35 deletions packages/rehype-shiki/src/transformers/twoslash/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { transformerTwoslash } from '@shikijs/twoslash';
import {
createTransformerFactory,
rendererRich,
transformerTwoslash,
} from '@shikijs/twoslash';

const compose = ({ token, cursor, popup }) => [
{
Expand All @@ -10,39 +14,60 @@ const compose = ({ token, cursor, popup }) => [
popup,
];

export const twoslash = (options = {}) =>
transformerTwoslash({
langs: ['ts', 'js', 'cjs', 'mjs'],
rendererRich: {
jsdoc: false,
hast: {
hoverToken: { tagName: 'MDXTooltip' },
hoverPopup: { tagName: 'MDXTooltipContent' },
hoverCompose: compose,

queryToken: { tagName: 'MDXTooltip' },
queryPopup: { tagName: 'MDXTooltipContent' },
queryCompose: compose,

errorToken: { tagName: 'MDXTooltip' },
errorPopup: { tagName: 'MDXTooltipContent' },
errorCompose: compose,

completionToken: {
tagName: 'MDXTooltip',
properties: {
open: true,
},
},
completionPopup: {
tagName: 'MDXTooltipContent',
properties: {
align: 'start',
},
},
completionCompose: compose,
const rendererOptions = {
jsdoc: false,
hast: {
hoverToken: { tagName: 'MDXTooltip' },
hoverPopup: { tagName: 'MDXTooltipContent' },
hoverCompose: compose,

queryToken: { tagName: 'MDXTooltip' },
queryPopup: { tagName: 'MDXTooltipContent' },
queryCompose: compose,

errorToken: { tagName: 'MDXTooltip' },
errorPopup: { tagName: 'MDXTooltipContent' },
errorCompose: compose,

completionToken: {
tagName: 'MDXTooltip',
properties: {
open: true,
},
},
completionPopup: {
tagName: 'MDXTooltipContent',
properties: {
align: 'start',
},
},
throws: false,
...options,
});
completionCompose: compose,
},
};

const transformerOptions = {
langs: ['ts', 'js', 'cjs', 'mjs'],
rendererRich: rendererOptions,
throws: false,
};

/**
* Creates the Twoslash Shiki transformer.
*
* When `options.twoslasher` is provided, uses `createTransformerFactory`
* directly to avoid importing the default Node.js-dependent twoslasher from
* `twoslash`. This is needed for environments like Cloudflare Workers where
* the filesystem-backed default twoslasher cannot be used.
*
* @param {import('@shikijs/twoslash').TransformerTwoslashIndexOptions} [options]
*/
export const twoslash = (options = {}) => {
if (options.twoslasher) {
return createTransformerFactory(
options.twoslasher,
rendererRich(rendererOptions)
)({ ...transformerOptions, ...options });
}

return transformerTwoslash({ ...transformerOptions, ...options });
};
16 changes: 8 additions & 8 deletions packages/ui-components/__design__/colors.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ export const Colors: StoryObj = {
<div className="flex flex-row justify-between">
<div className="flex w-full flex-col items-center justify-between gap-1">
<div className="flex flex-row gap-1">
<div className="h-20 w-20 bg-green-100" />
<div className="h-20 w-20 bg-green-200" />
<div className="h-20 w-20 bg-green-300" />
<div className="h-20 w-20 bg-green-400" />
<div className="h-20 w-20 bg-green-600" />
<div className="h-20 w-20 bg-green-700" />
<div className="h-20 w-20 bg-green-800" />
<div className="h-20 w-20 bg-green-900" />
<div className="bg-brand-100 h-20 w-20" />
<div className="bg-brand-200 h-20 w-20" />
<div className="bg-brand-300 h-20 w-20" />
<div className="bg-brand-400 h-20 w-20" />
<div className="bg-brand-600 h-20 w-20" />
<div className="bg-brand-700 h-20 w-20" />
<div className="bg-brand-800 h-20 w-20" />
<div className="bg-brand-900 h-20 w-20" />
</div>
<div className="flex flex-row gap-1">
<div className="h-20 w-20 bg-neutral-100" />
Expand Down
Loading
Loading