diff --git a/.changeset/fuzzy-waves-thank.md b/.changeset/fuzzy-waves-thank.md new file mode 100644 index 0000000000..6d49cee325 --- /dev/null +++ b/.changeset/fuzzy-waves-thank.md @@ -0,0 +1,7 @@ +--- +'nextra-theme-blog': minor +'nextra-theme-docs': minor +'nextra': minor +--- + +insert `twoslash.css` styles only on pages where it was used diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1604a2249e..2feac10882 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,9 +17,10 @@ jobs: - name: Checkout Main uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Use Node.js + - name: Install Node.js uses: actions/setup-node@v4 with: node-version-file: .node-version diff --git a/.github/workflows/nextjs-bundle-analysis.yml b/.github/workflows/nextjs-bundle-analysis.yml index fc65fa9006..0ccd00d767 100644 --- a/.github/workflows/nextjs-bundle-analysis.yml +++ b/.github/workflows/nextjs-bundle-analysis.yml @@ -27,7 +27,8 @@ jobs: - name: Checkout Main uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - name: Install Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af09a24f44..d8ad91084c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,8 @@ jobs: - name: Checkout Main uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - name: Install Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5fc3264b0f..fcdd5f3e86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,9 +21,10 @@ jobs: - name: Checkout Main uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Use Node.js + - name: Install Node.js uses: actions/setup-node@v4 with: node-version-file: .node-version diff --git a/docs/app/docs/built-ins/head/page.mdx b/docs/app/docs/built-ins/head/page.mdx index b422201ff0..fc222cb264 100644 --- a/docs/app/docs/built-ins/head/page.mdx +++ b/docs/app/docs/built-ins/head/page.mdx @@ -91,7 +91,7 @@ website: | Hue (H) | Saturation (S) | Lightness (L) | Background Color | | -------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | ------------------ | -| | | | | +| | | | | > [!TIP] > diff --git a/packages/nextra/package.json b/packages/nextra/package.json index c7e18a9fa4..7030b3935f 100644 --- a/packages/nextra/package.json +++ b/packages/nextra/package.json @@ -10,6 +10,7 @@ }, "exports": { "./katex.css": "./styles/katex.css", + "./twoslash.css": "./dist/twoslash.css", "./package.json": "./package.json", ".": { "types": "./dist/server/index.d.ts", diff --git a/packages/nextra/postcss.config.js b/packages/nextra/postcss.config.js new file mode 100644 index 0000000000..a814841204 --- /dev/null +++ b/packages/nextra/postcss.config.js @@ -0,0 +1 @@ +export { default } from '../nextra-theme-docs/postcss.config.mjs' diff --git a/packages/nextra/src/client/setup-page.tsx b/packages/nextra/src/client/setup-page.tsx index 5814b5dd5d..5b6b469b0f 100644 --- a/packages/nextra/src/client/setup-page.tsx +++ b/packages/nextra/src/client/setup-page.tsx @@ -3,17 +3,18 @@ * This file should be never used directly, only in loader.ts */ -import { useMDXComponents } from 'next-mdx-import-source-file' +import { useMDXComponents as getMDXComponents } from 'next-mdx-import-source-file' import type { ComponentProps, FC } from 'react' import { createElement } from 'react' import type { Heading, MDXWrapper, PageOpts } from '../types' +const Wrapper = getMDXComponents().wrapper + export function HOC_MDXWrapper( MDXContent: MDXWrapper, hocProps: PageOpts & { toc: Heading[] } ): FC> { return function MDXWrapper(props) { - const Wrapper = useMDXComponents().wrapper const children = createElement(MDXContent, props) return Wrapper ? {children} : children diff --git a/packages/nextra/src/server/rehype-plugins/rehype-twoslash-popup.ts b/packages/nextra/src/server/rehype-plugins/rehype-twoslash-popup.ts index dc97260dd2..6d3ee7f80c 100644 --- a/packages/nextra/src/server/rehype-plugins/rehype-twoslash-popup.ts +++ b/packages/nextra/src/server/rehype-plugins/rehype-twoslash-popup.ts @@ -25,6 +25,24 @@ const TWOSLASH_POPUP_IMPORT_AST = { } } as MdxjsEsm +/** + * To reduce bundle size, we insert css only where twoslash was used + */ +const TWOSLASH_CSS = { + type: 'mdxjsEsm', + data: { + estree: { + body: [ + { + type: 'ImportDeclaration', + source: { type: 'Literal', value: 'nextra/twoslash.css' }, + specifiers: [] + } satisfies ImportDeclaration + ] + } + } +} as any + export const rehypeTwoslashPopup: Plugin<[], Root> = () => ast => { // The tagName is being converted to lowercase when calling the shiki.codeToHtml // method inside rehypePrettyCode. Convert it back to Uppercase. @@ -48,7 +66,7 @@ export const rehypeTwoslashPopup: Plugin<[], Root> = () => ast => { visit(ast, { tagName: 'code' }, node => { if (node.data?.meta === 'twoslash') { - ast.children.unshift(TWOSLASH_POPUP_IMPORT_AST) + ast.children.unshift(TWOSLASH_CSS, TWOSLASH_POPUP_IMPORT_AST) return EXIT } }) diff --git a/packages/nextra/src/twoslash.css b/packages/nextra/src/twoslash.css new file mode 100644 index 0000000000..7b8ab5498b --- /dev/null +++ b/packages/nextra/src/twoslash.css @@ -0,0 +1,200 @@ +/* ===== Basic ===== */ +:root { + --twoslash-border-color: #8888; + --twoslash-underline-color: currentColor; + --twoslash-highlighted-border: 195, 125, 13; + --twoslash-popup-bg: #f8f8f8; + --twoslash-popup-color: inherit; + --twoslash-popup-shadow: rgba(0, 0, 0.08) 0px 1px 4px; + --twoslash-docs-color: #888; + --twoslash-docs-font: sans-serif; + --twoslash-matched-color: inherit; + --twoslash-unmatched-color: #888; + --twoslash-cursor-color: #8888; + --twoslash-error-color: 212, 86, 86; + --twoslash-error-bg: rgba(var(--twoslash-error-color), 0.13); + --twoslash-tag-color: 55, 114, 207; + --twoslash-tag-warn-color: 195, 125, 13; + --twoslash-tag-annotate-color: 27, 166, 115; +} + +.dark { + --twoslash-popup-bg: #000; + --twoslash-border-color: #404040; +} + +/* Respect people's wishes to not have animations */ +@media (prefers-reduced-motion: reduce) { + .twoslash * { + transition: none !important; + } +} + +/* ===== Hover Info ===== */ +.twoslash:hover .twoslash-hover { + border-color: var(--twoslash-underline-color); +} + +.twoslash-hover { + border-bottom: 1px dotted transparent; + transition-timing-function: ease; + transition: border-color 0.3s; + position: relative; +} + +.twoslash-popup-container { + @apply _inline-flex _flex-col _absolute _transition-opacity _duration-300 _z-10 _mt-1.5 _rounded; + transform: translateY(1.1em); + background: var(--twoslash-popup-bg) !important; + color: var(--twoslash-popup-color); + border: 1px solid var(--twoslash-border-color); + text-align: left; + /*box-shadow: var(--twoslash-popup-shadow);*/ +} + +.twoslash-query-presisted .twoslash-popup-container { + z-index: 9; + transform: translateY(1.5em); +} + +.twoslash-popup-arrow { + @apply _absolute _-top-1 _border-t _border-r _size-1.5 _-rotate-45; + @apply _border-[--twoslash-border-color] _bg-[--twoslash-popup-bg]; + left: 1em; + pointer-events: none; +} + +.twoslash-popup-code, +.twoslash-popup-docs { + padding: 6px 8px; +} + +.twoslash-popup-docs { + @apply _text-sm; + color: var(--twoslash-docs-color); + font-family: var(--twoslash-docs-font); + border-top: 1px solid var(--twoslash-border-color); +} + +.twoslash-popup-docs-tags { + display: flex; + flex-direction: column; + font-family: var(--twoslash-docs-font); +} + +.twoslash-popup-docs-tags, +.twoslash-popup-docs-tag-name { + margin-right: 0.5em; +} + +/* ===== Error Line ===== */ +.twoslash-error-line { + position: relative; + background-color: var(--twoslash-error-bg); + border-left: 3px solid currentColor; + color: rgb(var(--twoslash-error-color)); + padding: 6px 12px; + margin: 0.2em 0; +} + +.twoslash-error { + background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") + repeat-x bottom left; + padding-bottom: 2px; +} + +/* ===== Completeions ===== */ +.twoslash-completion-cursor { + position: relative; +} + +.twoslash-completion-cursor .twoslash-completion-list { + @apply _absolute _left-0 _border _top-1 _rounded; + transform: translate(0, 1.2em); + background: var(--twoslash-popup-bg); + border-color: var(--twoslash-border-color); +} + +.twoslash-completion-list { + @apply _py-1 _px-2 _w-60; +} + +.twoslash-completion-list::before { + background-color: var(--twoslash-cursor-color); + width: 2px; + position: absolute; + top: -1.6em; + height: 1.4em; + left: -1px; + content: ' '; +} + +.twoslash-completion-list li { + overflow: hidden; + display: flex; + align-items: center; + gap: 0.25em; + line-height: 1em; +} + +.twoslash-completion-list li span.twoslash-completions-unmatched { + color: var(--twoslash-unmatched-color); +} + +.twoslash-completion-list .deprecated { + text-decoration: line-through; + opacity: 0.5; +} + +.twoslash-completion-list li span.twoslash-completions-matched { + color: var(--twoslash-matched-color); +} + +/* Highlights */ +.twoslash-highlighted { + background-color: rgba(var(--twoslash-highlighted-border), 0.13); + border: 1px solid rgba(var(--twoslash-highlighted-border), 0.31); + padding: 1px 2px; + margin: -1px -3px; + border-radius: 4px; +} + +/* Icons */ +.twoslash-completion-list .twoslash-completions-icon { + color: var(--twoslash-unmatched-color); + width: 1em; + flex: none; +} + +/* Custom Tags */ +.twoslash-tag-line { + position: relative; + background-color: rgba(var(--twoslash-tag-color), 0.13); + border-left: 3px solid currentColor; + color: rgb(var(--twoslash-tag-color)); + padding: 6px 10px; + margin: 0.2em 0; + display: flex; + align-items: center; + gap: 0.3em; +} + +.twoslash-tag-line .twoslash-tag-icon { + width: 1.1em; + color: inherit; +} + +.twoslash-tag-line.twoslash-tag-error-line { + background-color: var(--twoslash-error-bg); + color: rgb(var(--twoslash-error-color)); +} + +.twoslash-tag-line.twoslash-tag-warn-line { + background-color: rgba(var(--twoslash-tag-warn-color), 0.13); + color: rgb(var(--twoslash-tag-warn-color)); +} + +.twoslash-tag-line.twoslash-tag-annotate-line { + background-color: rgba(var(--twoslash-tag-annotate-color), 0.13); + color: rgb(var(--twoslash-tag-annotate-color)); +} diff --git a/packages/nextra/styles/code-block.css b/packages/nextra/styles/code-block.css index 37259db486..7febc1925e 100644 --- a/packages/nextra/styles/code-block.css +++ b/packages/nextra/styles/code-block.css @@ -60,204 +60,3 @@ pre code.nextra-code:not([class*='twoslash-']) { opacity: 1; } } - -/* ===== Basic ===== */ -:root { - --twoslash-border-color: #8888; - --twoslash-underline-color: currentColor; - --twoslash-highlighted-border: 195, 125, 13; - --twoslash-popup-bg: #f8f8f8; - --twoslash-popup-color: inherit; - --twoslash-popup-shadow: rgba(0, 0, 0.08) 0px 1px 4px; - --twoslash-docs-color: #888; - --twoslash-docs-font: sans-serif; - --twoslash-matched-color: inherit; - --twoslash-unmatched-color: #888; - --twoslash-cursor-color: #8888; - --twoslash-error-color: 212, 86, 86; - --twoslash-error-bg: rgba(var(--twoslash-error-color), 0.13); - --twoslash-tag-color: 55, 114, 207; - --twoslash-tag-warn-color: 195, 125, 13; - --twoslash-tag-annotate-color: 27, 166, 115; -} - -.dark { - --twoslash-popup-bg: #000; - --twoslash-border-color: #404040; -} - -/* Respect people's wishes to not have animations */ -@media (prefers-reduced-motion: reduce) { - .twoslash * { - transition: none !important; - } -} - -/* ===== Hover Info ===== */ -.twoslash:hover .twoslash-hover { - border-color: var(--twoslash-underline-color); -} - -.twoslash-hover { - border-bottom: 1px dotted transparent; - transition-timing-function: ease; - transition: border-color 0.3s; - position: relative; -} - -.twoslash-popup-container { - @apply _inline-flex _flex-col _absolute _transition-opacity _duration-300 _z-10 _mt-1.5 _rounded; - transform: translateY(1.1em); - background: var(--twoslash-popup-bg) !important; - color: var(--twoslash-popup-color); - border: 1px solid var(--twoslash-border-color); - text-align: left; - /*box-shadow: var(--twoslash-popup-shadow);*/ -} - -.twoslash-query-presisted .twoslash-popup-container { - z-index: 9; - transform: translateY(1.5em); -} - -.twoslash-popup-arrow { - @apply _absolute _-top-1 _border-t _border-r _size-1.5 _-rotate-45; - @apply _border-[--twoslash-border-color] _bg-[--twoslash-popup-bg]; - left: 1em; - pointer-events: none; -} - -.twoslash-popup-code, -.twoslash-popup-docs { - padding: 6px 8px; -} - -.twoslash-popup-docs { - @apply _text-sm; - color: var(--twoslash-docs-color); - font-family: var(--twoslash-docs-font); - border-top: 1px solid var(--twoslash-border-color); -} - -.twoslash-popup-docs-tags { - display: flex; - flex-direction: column; - font-family: var(--twoslash-docs-font); -} - -.twoslash-popup-docs-tags, -.twoslash-popup-docs-tag-name { - margin-right: 0.5em; -} - -/* ===== Error Line ===== */ -.twoslash-error-line { - position: relative; - background-color: var(--twoslash-error-bg); - border-left: 3px solid currentColor; - color: rgb(var(--twoslash-error-color)); - padding: 6px 12px; - margin: 0.2em 0; -} - -.twoslash-error { - background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") - repeat-x bottom left; - padding-bottom: 2px; -} - -/* ===== Completeions ===== */ -.twoslash-completion-cursor { - position: relative; -} - -.twoslash-completion-cursor .twoslash-completion-list { - @apply _absolute _left-0 _border _top-1 _rounded; - transform: translate(0, 1.2em); - background: var(--twoslash-popup-bg); - border-color: var(--twoslash-border-color); -} - -.twoslash-completion-list { - @apply _py-1 _px-2 _w-60; -} - -.twoslash-completion-list::before { - background-color: var(--twoslash-cursor-color); - width: 2px; - position: absolute; - top: -1.6em; - height: 1.4em; - left: -1px; - content: ' '; -} - -.twoslash-completion-list li { - overflow: hidden; - display: flex; - align-items: center; - gap: 0.25em; - line-height: 1em; -} - -.twoslash-completion-list li span.twoslash-completions-unmatched { - color: var(--twoslash-unmatched-color); -} - -.twoslash-completion-list .deprecated { - text-decoration: line-through; - opacity: 0.5; -} - -.twoslash-completion-list li span.twoslash-completions-matched { - color: var(--twoslash-matched-color); -} - -/* Highlights */ -.twoslash-highlighted { - background-color: rgba(var(--twoslash-highlighted-border), 0.13); - border: 1px solid rgba(var(--twoslash-highlighted-border), 0.31); - padding: 1px 2px; - margin: -1px -3px; - border-radius: 4px; -} - -/* Icons */ -.twoslash-completion-list .twoslash-completions-icon { - color: var(--twoslash-unmatched-color); - width: 1em; - flex: none; -} - -/* Custom Tags */ -.twoslash-tag-line { - position: relative; - background-color: rgba(var(--twoslash-tag-color), 0.13); - border-left: 3px solid currentColor; - color: rgb(var(--twoslash-tag-color)); - padding: 6px 10px; - margin: 0.2em 0; - display: flex; - align-items: center; - gap: 0.3em; -} - -.twoslash-tag-line .twoslash-tag-icon { - width: 1.1em; - color: inherit; -} - -.twoslash-tag-line.twoslash-tag-error-line { - background-color: var(--twoslash-error-bg); - color: rgb(var(--twoslash-error-color)); -} - -.twoslash-tag-line.twoslash-tag-warn-line { - background-color: rgba(var(--twoslash-tag-warn-color), 0.13); - color: rgb(var(--twoslash-tag-warn-color)); -} - -.twoslash-tag-line.twoslash-tag-annotate-line { - background-color: rgba(var(--twoslash-tag-annotate-color), 0.13); - color: rgb(var(--twoslash-tag-annotate-color)); -} diff --git a/packages/nextra/tailwind.config.ts b/packages/nextra/tailwind.config.ts new file mode 100644 index 0000000000..c4d7f505d1 --- /dev/null +++ b/packages/nextra/tailwind.config.ts @@ -0,0 +1 @@ +export { default } from '../nextra-theme-docs/tailwind.config.js' diff --git a/packages/nextra/tsup.config.ts b/packages/nextra/tsup.config.ts index 844ccf4dea..45a0f94983 100644 --- a/packages/nextra/tsup.config.ts +++ b/packages/nextra/tsup.config.ts @@ -8,7 +8,12 @@ import { CWD, IS_PRODUCTION } from './src/server/constants.js' export default defineConfig([ { name: 'nextra', - entry: [...defaultEntry, '!src/types.ts', '!src/client/icons'], + entry: [ + ...defaultEntry, + '!src/types.ts', + '!src/client/icons', + 'src/twoslash.css' + ], target: 'es2020', format: 'esm', dts: true,