| author | |
| committer | |
| log | c5e1b81a764db3ca4debd1826c1e779e9e56b4b2 |
| tree | 815bac26ddb794b2462314ce1a6ff24d44faea8a |
| parent | 4a31117a7678a74905aad5c6aec7e1f7d827061e |
| signature | Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs |
Through this process i've found how unstable my marko adapter is.
instead of salvaging it, it will be better to just drop the entire
rendering engine. Marko is doing great for me and I feel safe investing
into their ecosystem. 54 files changed, 3262 insertions(+), 16501 deletions(-)
framework/engine/marko-runtime.ts deleted-139| ... | ... | @@ -1,139 +0,0 @@ |
| 1 | // This file is used to integrate Marko into the Clover Engine and Sitegen | |
| 2 | // To use, replace the "marko/html" import with this file. | |
| 3 | export * from "#marko/html"; | |
| 4 | ||
| 5 | export const _template: typeof marko._template = ( | |
| 6 | templateId, | |
| 7 | renderer, | |
| 8 | ) => { | |
| 9 | const { render: renderFn } = marko._template(templateId, renderer); | |
| 10 | function wrap({ content, ...attrs }: Record<string, unknown>, n: number) { | |
| 11 | // Marko Custom Tags | |
| 12 | const cloverAsyncMarker = { isAsync: false }; | |
| 13 | const r = render.current; | |
| 14 | // Support using Marko outside of Clover SSR | |
| 15 | const props = { | |
| 16 | content: content ? () => content : undefined, | |
| 17 | ...attrs, | |
| 18 | }; | |
| 19 | if (!r) { | |
| 20 | return renderer(props, n); | |
| 21 | } | |
| 22 | render.setCurrent(null); | |
| 23 | const markoResult = renderFn.call(renderer, { | |
| 24 | ...props, | |
| 25 | $global: { clover: r, cloverAsyncMarker }, | |
| 26 | }); | |
| 27 | if (cloverAsyncMarker.isAsync) { | |
| 28 | return markoResult.then(render.raw); | |
| 29 | } | |
| 30 | const rr = markoResult.toString(); | |
| 31 | return render.raw(rr); | |
| 32 | } | |
| 33 | wrap.render = renderFn; | |
| 34 | wrap.unwrapped = renderer; | |
| 35 | return wrap as unknown as Template; | |
| 36 | }; | |
| 37 | ||
| 38 | export const _dynamic_tag: typeof marko._dynamic_tag = ( | |
| 39 | scopeId, | |
| 40 | accessor, | |
| 41 | tag, | |
| 42 | inputOrArgs, | |
| 43 | content, | |
| 44 | inputIsArgs, | |
| 45 | serializeReason, | |
| 46 | ) => { | |
| 47 | if (typeof tag === "function") { | |
| 48 | clover: { | |
| 49 | const unwrapped = (tag as any).unwrapped; | |
| 50 | if (unwrapped) { | |
| 51 | tag = unwrapped; | |
| 52 | break clover; | |
| 53 | } | |
| 54 | const r = render.current ?? (marko.$global().clover as render.State); | |
| 55 | if (!r) throw new Error("No Clover Render Active"); | |
| 56 | const subRender = render.init(r.async !== -1, r.context); | |
| 57 | const resolved = render.resolveNode( | |
| 58 | subRender, | |
| 59 | render.element( | |
| 60 | tag as render.Component, | |
| 61 | inputOrArgs as Record<any, any>, | |
| 62 | ), | |
| 63 | ); | |
| 64 | ||
| 65 | if (subRender.async > 0) { | |
| 66 | const marker = marko.$global().cloverAsyncMarker as Async; | |
| 67 | marker.isAsync = true; | |
| 68 | ||
| 69 | // Wait for async work to finish | |
| 70 | const { resolve, reject, promise } = Promise.withResolvers<string>(); | |
| 71 | subRender.asyncDone = () => { | |
| 72 | const rejections = subRender.rejections; | |
| 73 | if (!rejections) return resolve(render.stringifyNode(resolved)); | |
| 74 | (r.rejections ??= []).push(...rejections); | |
| 75 | return reject(new Error("Render had errors")); | |
| 76 | }; | |
| 77 | marko._await( | |
| 78 | scopeId, | |
| 79 | accessor, | |
| 80 | promise, | |
| 81 | (string: string) => marko._html(string), | |
| 82 | 0, | |
| 83 | ); | |
| 84 | } else { | |
| 85 | marko._html(render.stringifyNode(resolved)); | |
| 86 | } | |
| 87 | return; | |
| 88 | } | |
| 89 | } | |
| 90 | return marko._dynamic_tag( | |
| 91 | scopeId, | |
| 92 | accessor, | |
| 93 | tag, | |
| 94 | inputOrArgs, | |
| 95 | content, | |
| 96 | inputIsArgs, | |
| 97 | serializeReason, | |
| 98 | ); | |
| 99 | }; | |
| 100 | ||
| 101 | export const _await: typeof marko._await = ( | |
| 102 | scopeId, | |
| 103 | accessor, | |
| 104 | promise, | |
| 105 | callback, | |
| 106 | serializeMarker, | |
| 107 | ) => { | |
| 108 | const marker = marko.$global().cloverAsyncMarker as Async; | |
| 109 | marker.isAsync = true; | |
| 110 | marko._await(scopeId, accessor, promise, callback, serializeMarker); | |
| 111 | }; | |
| 112 | ||
| 113 | export function _escape(input: unknown) { | |
| 114 | // The rationale of this check is that the default toString method | |
| 115 | // creating `[object Object]` is universally useless to any end user. | |
| 116 | if ( | |
| 117 | input == null || | |
| 118 | (typeof input === "object" && | |
| 119 | input && | |
| 120 | // only block this if it's the default `toString` | |
| 121 | input.toString === Object.prototype.toString) | |
| 122 | ) { | |
| 123 | throw new Error( | |
| 124 | `Unexpected value in template placeholder: '` + | |
| 125 | render.inspect(input) + | |
| 126 | "'. " + | |
| 127 | `To emit a literal '${input}', use \${String(value)}`, | |
| 128 | ); | |
| 129 | } | |
| 130 | return marko._escape(input); | |
| 131 | } | |
| 132 | ||
| 133 | interface Async { | |
| 134 | isAsync: boolean; | |
| 135 | } | |
| 136 | ||
| 137 | import type { Template } from "marko/common/types"; | |
| 138 | import * as render from "lib/render.ts"; | |
| 139 | import * as marko from "#marko/html"; |
framework/generate.ts+72-60| ... | ... | @@ -183,7 +183,7 @@ export async function scanSiteSection(io: Io, section: sg.Section) { |
| 183 | 183 | dir: sectionPath("pages"), |
| 184 | 184 | list: pages, |
| 185 | 185 | prefix: section.base, |
| 186 | include: [".tsx", ".mdx", ".marko"], | |
| 186 | include: [".tsx", ".marko", ".mdo"], | |
| 187 | 187 | exclude: [".client.ts", ".client.tsx"], |
| 188 | 188 | page: true, |
| 189 | 189 | }, |
| ... | ... | @@ -205,7 +205,7 @@ export async function scanSiteSection(io: Io, section: sg.Section) { |
| 205 | 205 | dir: sectionPath("views"), |
| 206 | 206 | list: views, |
| 207 | 207 | prefix: rootPrefix, |
| 208 | include: [".tsx", ".mdx", ".marko"], | |
| 208 | include: [".tsx", ".marko", ".mdo"], | |
| 209 | 209 | exclude: [".client.ts", ".client.tsx"], |
| 210 | 210 | page: false, |
| 211 | 211 | }, |
| ... | ... | @@ -251,70 +251,79 @@ export async function preparePage( |
| 251 | 251 | io: Io, |
| 252 | 252 | { item, metaTemplate }: { item: sg.FileItem; metaTemplate: meta.Template }, |
| 253 | 253 | ) { |
| 254 | // -- load and validate module -- | |
| 255 | let { | |
| 256 | default: Page, | |
| 257 | meta: metadata, | |
| 258 | theme: pageTheme, | |
| 259 | layout, | |
| 260 | regenerate, | |
| 261 | } = await io.import<sg.PageExports>(item.file); | |
| 262 | if (!Page) throw new Error("Page is missing a 'default' export."); | |
| 263 | if (!metadata) throw new Error("Page is missing 'meta' export with a title."); | |
| 264 | ||
| 265 | // -- css -- | |
| 266 | if (layout?.theme) pageTheme = layout.theme; | |
| 267 | const theme: css.Theme = { | |
| 268 | ...css.defaultTheme, | |
| 269 | ...pageTheme, | |
| 270 | }; | |
| 271 | const cssImports = Array.from( | |
| 272 | new Set([globalCssPath, ...hot.getCssImports(item.file)]), | |
| 273 | (file) => path.relative(hot.projectSrc, file), | |
| 274 | ); | |
| 254 | try { | |
| 255 | // -- load and validate module -- | |
| 256 | let { | |
| 257 | default: Page, | |
| 258 | meta: metadata, | |
| 259 | theme: pageTheme, | |
| 260 | layout, | |
| 261 | regenerate, | |
| 262 | markoOnly, | |
| 263 | } = await io.import<sg.PageExports>(item.file); | |
| 264 | if (!Page) throw new Error("Page is missing a 'default' export."); | |
| 265 | if (!metadata) { | |
| 266 | throw new Error("Page is missing 'meta' export with a title."); | |
| 267 | } | |
| 268 | ||
| 269 | // -- css -- | |
| 270 | if (layout?.theme) pageTheme = layout.theme; | |
| 271 | const theme: css.Theme = { | |
| 272 | ...css.defaultTheme, | |
| 273 | ...pageTheme, | |
| 274 | }; | |
| 275 | const cssImports = Array.from( | |
| 276 | new Set([globalCssPath, ...hot.getCssImports(item.file)]), | |
| 277 | (file) => path.relative(hot.projectSrc, file), | |
| 278 | ); | |
| 275 | 279 | |
| 276 | // -- metadata -- | |
| 277 | const renderedMetaPromise = Promise.resolve( | |
| 278 | typeof metadata === "function" ? metadata({ ssr: true }) : metadata, | |
| 279 | ).then((m) => | |
| 280 | meta.render({ | |
| 280 | // -- metadata -- | |
| 281 | const renderedMeta = meta.render({ | |
| 281 | 282 | canonical: item.id, |
| 282 | ...m, | |
| 283 | }, metaTemplate) | |
| 284 | ); | |
| 283 | ...typeof metadata === "function" | |
| 284 | ? await metadata({ ssr: true }) | |
| 285 | : metadata, | |
| 286 | }, metaTemplate); | |
| 287 | if (!renderedMeta.includes("<title>")) { | |
| 288 | throw new Error( | |
| 289 | "Page is missing 'meta.title'. " + "All pages need a title tag.", | |
| 290 | ); | |
| 291 | } | |
| 285 | 292 | |
| 286 | // -- html -- | |
| 287 | let page = render.element(Page); | |
| 288 | if (layout?.default) { | |
| 289 | page = render.element(layout.default, { content: page }); | |
| 290 | } | |
| 293 | // -- html -- | |
| 294 | let text: string; | |
| 295 | const sitegen = sg.context.init(); | |
| 296 | if ("render" in Page) { | |
| 297 | text = await SitegenWrap.render({ | |
| 298 | cloverScripts: sitegen.scripts, | |
| 299 | page: Page, | |
| 300 | attrs: {}, | |
| 301 | }); | |
| 302 | } else { | |
| 303 | let page = render.element(Page); | |
| 304 | if (layout?.default) { | |
| 305 | page = render.element(layout.default, { content: page }); | |
| 306 | } | |
| 291 | 307 | |
| 292 | const sitegen = sg.context.init(); | |
| 293 | const bodyPromise = render.async(page, [sitegen]); | |
| 308 | text = (await render.async(page, [sitegen])).text; | |
| 309 | } | |
| 294 | 310 | |
| 295 | const [{ text }, renderedMeta] = await Promise.all([ | |
| 296 | bodyPromise, | |
| 297 | renderedMetaPromise, | |
| 298 | ]); | |
| 299 | if (!renderedMeta.includes("<title>")) { | |
| 300 | throw new Error( | |
| 301 | "Page is missing 'meta.title'. " + "All pages need a title tag.", | |
| 302 | ); | |
| 311 | const styleKey = css.styleKey(cssImports, theme); | |
| 312 | return { | |
| 313 | type: "page", | |
| 314 | id: item.id, | |
| 315 | file: item.file, | |
| 316 | regenerate, | |
| 317 | html: text, | |
| 318 | meta: renderedMeta, | |
| 319 | cssImports, | |
| 320 | theme: theme ?? null, | |
| 321 | styleKey, | |
| 322 | clientRefs: Array.from(sitegen.scripts), | |
| 323 | } as const; | |
| 324 | } catch (e) { | |
| 325 | error.rethrowWithMetadata(e, { page: item.id }); | |
| 303 | 326 | } |
| 304 | ||
| 305 | const styleKey = css.styleKey(cssImports, theme); | |
| 306 | return { | |
| 307 | type: "page", | |
| 308 | id: item.id, | |
| 309 | file: item.file, | |
| 310 | regenerate, | |
| 311 | html: text, | |
| 312 | meta: renderedMeta, | |
| 313 | cssImports, | |
| 314 | theme: theme ?? null, | |
| 315 | styleKey, | |
| 316 | clientRefs: Array.from(sitegen.scripts), | |
| 317 | } as const; | |
| 318 | 327 | } |
| 319 | 328 | |
| 320 | 329 | export async function prepareView(io: Io, item: sg.FileItem) { |
| ... | ... | @@ -410,6 +419,9 @@ import * as render from "lib/render.ts"; |
| 410 | 419 | import * as sg from "#sitegen"; |
| 411 | 420 | import type { FileItem } from "#sitegen"; |
| 412 | 421 | import * as progress from "../lib/progress.ts"; |
| 422 | import * as error from "lib/error.ts"; | |
| 423 | ||
| 424 | import SitegenWrap from "./sitegen-wrap.marko"; | |
| 413 | 425 | |
| 414 | 426 | import * as bundle from "./bundle.ts"; |
| 415 | 427 | import * as css from "./css.ts"; |
framework/hot.ts+4-13| ... | ... | @@ -28,7 +28,7 @@ exts[".ts"] = loadEsbuild; |
| 28 | 28 | exts[".tsx"] = loadEsbuild; |
| 29 | 29 | exts[".jsx"] = loadEsbuild; |
| 30 | 30 | exts[".marko"] = loadMarko; |
| 31 | exts[".mdx"] = loadMdx; | |
| 31 | exts[".mdo"] = loadMarkodown; | |
| 32 | 32 | exts[".css"] = loadCss; |
| 33 | 33 | |
| 34 | 34 | // Intercept all module load calls to track CSS imports + file times. |
| ... | ... | @@ -173,16 +173,9 @@ function loadMarko(module: NodeJS.Module, filepath: string) { |
| 173 | 173 | lazyMarko ??= require<typeof import("./marko.ts")>("./framework/marko.ts"); |
| 174 | 174 | lazyMarko.loadMarko(module, filepath); |
| 175 | 175 | } |
| 176 | ||
| 177 | function loadMdx(module: NodeJS.Module, filepath: string) { | |
| 178 | const input = fs.readFileSync(filepath); | |
| 179 | const out = mdx.compileSync(input, { | |
| 180 | jsxImportSource: "#jsx", | |
| 181 | providerImportSource: "#mdx", | |
| 182 | rehypePlugins: [rehypeMdxCodeProps], | |
| 183 | }).value; | |
| 184 | const src = typeof out === "string" ? out : Buffer.from(out).toString("utf8"); | |
| 185 | return loadEsbuildCode(module, filepath, src); | |
| 176 | function loadMarkodown(module: NodeJS.Module, filepath: string) { | |
| 177 | lazyMarko ??= require<typeof import("./marko.ts")>("./framework/marko.ts"); | |
| 178 | lazyMarko.loadMarkodown(module, filepath); | |
| 186 | 179 | } |
| 187 | 180 | |
| 188 | 181 | function loadCss(module: NodeJS.Module, _filepath: string) { |
| ... | ... | @@ -328,8 +321,6 @@ import * as path from "node:path"; |
| 328 | 321 | import { pathToFileURL } from "node:url"; |
| 329 | 322 | import * as esbuild from "esbuild"; |
| 330 | 323 | import { createRequire } from "node:module"; |
| 331 | import * as mdx from "@mdx-js/mdx"; | |
| 332 | import rehypeMdxCodeProps from "rehype-mdx-code-props"; | |
| 333 | 324 | import * as self from "./hot.ts"; |
| 334 | 325 | import { Buffer } from "node:buffer"; |
| 335 | 326 | import { UNWRAP } from "lib/assert.ts"; |
framework/lib/add-script.marko created+1| ... | ... | @@ -0,0 +1 @@ |
| 1 | <const/_=$global.cloverScripts.add(input.value) /> |
framework/lib/sitegen.ts+5-2| ... | ... | @@ -2,6 +2,7 @@ export type ScriptId = string; |
| 2 | 2 | |
| 3 | 3 | export interface PageExports extends ViewExports { |
| 4 | 4 | regenerate?: PageRegenerateOptions; |
| 5 | markoOnly?: boolean; | |
| 5 | 6 | } |
| 6 | 7 | export interface ViewExports { |
| 7 | 8 | default: render.Component; |
| ... | ... | @@ -10,9 +11,9 @@ export interface ViewExports { |
| 10 | 11 | layout?: Layout; |
| 11 | 12 | } |
| 12 | 13 | export interface Layout { |
| 13 | default: render.Component; | |
| 14 | default?: render.Component; | |
| 14 | 15 | theme?: css.Theme; |
| 15 | // TODO: nested layout | |
| 16 | markoOnly?: boolean; | |
| 16 | 17 | } |
| 17 | 18 | export interface PageRegenerateOptions { |
| 18 | 19 | tags?: string[]; |
| ... | ... | @@ -97,6 +98,8 @@ export function joinScripts(scriptSources: string[]) { |
| 97 | 98 | "].map(e=>e())"; |
| 98 | 99 | } |
| 99 | 100 | |
| 101 | export { default as CloverScriptInclude } from "./add-script.marko"; | |
| 102 | ||
| 100 | 103 | import * as render from "lib/render.ts"; |
| 101 | 104 | import type * as meta from "lib/render/meta.ts"; |
| 102 | 105 | import type * as css from "../css.ts"; |
framework/lib/view.ts+20-8| ... | ... | @@ -53,10 +53,10 @@ type PropsFromModule<M extends any> = M extends { |
| 53 | 53 | |
| 54 | 54 | export async function renderToString<K extends Key>( |
| 55 | 55 | id: K, |
| 56 | props: PropsFromModule<ViewMap[K]>, | |
| 56 | attrs: PropsFromModule<ViewMap[K]>, | |
| 57 | 57 | ) { |
| 58 | 58 | const { |
| 59 | component, | |
| 59 | component: Page, | |
| 60 | 60 | inlineCss, |
| 61 | 61 | layout, |
| 62 | 62 | meta: metadata, |
| ... | ... | @@ -67,18 +67,29 @@ export async function renderToString<K extends Key>( |
| 67 | 67 | |
| 68 | 68 | // -- metadata -- |
| 69 | 69 | const renderedMetaPromise = Promise.resolve( |
| 70 | typeof metadata === "function" ? metadata(props) : metadata, | |
| 70 | typeof metadata === "function" ? metadata(attrs) : metadata, | |
| 71 | 71 | ).then((m) => meta.render(m, codegen.metaTemplate)); |
| 72 | 72 | |
| 73 | // -- html -- | |
| 74 | let page: render.Element = render.element(component, props); | |
| 75 | if (layout) page = render.element(layout, { content: page }); | |
| 73 | let text; | |
| 76 | 74 | const sitegen = sg.context.init(); |
| 77 | const { text: body } = await render.async(page, [sitegen]); | |
| 75 | if ("render" in Page) { | |
| 76 | text = await SitegenWrap.render({ | |
| 77 | cloverScripts: sitegen.scripts, | |
| 78 | page: Page, | |
| 79 | attrs, | |
| 80 | }); | |
| 81 | } else { | |
| 82 | let page = render.element(Page, attrs); | |
| 83 | if (layout?.default) { | |
| 84 | page = render.element(layout.default, { content: page }); | |
| 85 | } | |
| 86 | ||
| 87 | text = (await render.async(page, [sitegen])).text; | |
| 88 | } | |
| 78 | 89 | |
| 79 | 90 | // -- join document and send -- |
| 80 | 91 | return sg.wrapDocument({ |
| 81 | body, | |
| 92 | body: text, | |
| 82 | 93 | head: await renderedMetaPromise, |
| 83 | 94 | inlineCss, |
| 84 | 95 | scripts: sg.joinScripts( |
| ... | ... | @@ -116,3 +127,4 @@ import type { |
| 116 | 127 | RegisteredViews as ViewMap, |
| 117 | 128 | } from "../../.clover/ts/view.d.ts"; |
| 118 | 129 | import { UNWRAP } from "lib/assert.ts"; |
| 130 | import SitegenWrap from "../sitegen-wrap.marko"; |
framework/marko.ts+42-3| ... | ... | @@ -6,9 +6,47 @@ export interface MarkoCacheEntry { |
| 6 | 6 | export const markoCache = new Map<string, MarkoCacheEntry>(); |
| 7 | 7 | |
| 8 | 8 | export function loadMarko(module: NodeJS.Module, filepath: string) { |
| 9 | return loadMarkoExtra(module, filepath, "html"); | |
| 10 | } | |
| 11 | export function loadMarkodown(module: NodeJS.Module, filepath: string) { | |
| 12 | return loadMarkoExtra(module, filepath, "markdown"); | |
| 13 | } | |
| 14 | ||
| 15 | function loadMarkoExtra( | |
| 16 | module: NodeJS.Module, | |
| 17 | filepath: string, | |
| 18 | mode: "html" | "markdown", | |
| 19 | ) { | |
| 9 | 20 | let cache = markoCache.get(filepath); |
| 10 | 21 | if (!cache) { |
| 11 | 22 | let src = fs.readFileSync(filepath, "utf8"); |
| 23 | if (mode === "markdown") { | |
| 24 | let transform; | |
| 25 | try { | |
| 26 | transform = markodown.transform({ | |
| 27 | source: src, | |
| 28 | selfImport: `./${path.basename(filepath)}`, | |
| 29 | }); | |
| 30 | } catch (err) { | |
| 31 | throw new Error(`Failed ${filepath}: Markodown Compiler Panic`, { | |
| 32 | cause: err, | |
| 33 | }); | |
| 34 | } | |
| 35 | if (!transform.success) { | |
| 36 | // TODO: better error handling | |
| 37 | throw new Error( | |
| 38 | "Failed " + filepath + ": " + JSON.stringify(transform.errors), | |
| 39 | ); | |
| 40 | } | |
| 41 | src = transform.text; | |
| 42 | ||
| 43 | fs.writeMkdirSync( | |
| 44 | ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") + | |
| 45 | ".marko", | |
| 46 | src, | |
| 47 | ); | |
| 48 | } | |
| 49 | ||
| 12 | 50 | // A non-standard thing here is Clover Sitegen implements |
| 13 | 51 | // its own client side scripting stuff, so it overrides |
| 14 | 52 | // bare client import statements to it's own usage. |
| ... | ... | @@ -26,16 +64,16 @@ export function loadMarko(module: NodeJS.Module, filepath: string) { |
| 26 | 64 | ) |
| 27 | 65 | } />`; |
| 28 | 66 | }, |
| 29 | ) + '\nimport { addScript as CloverScriptInclude } from "#sitegen";\n'; | |
| 67 | ) + '\nimport { CloverScriptInclude } from "#sitegen";\n'; | |
| 30 | 68 | } |
| 31 | 69 | |
| 32 | const compiled = marko.compileSync(src, filepath, { | |
| 70 | let compiled = marko.compileSync(src, filepath, { | |
| 33 | 71 | sourceMaps: true, |
| 34 | 72 | }); |
| 35 | 73 | // workaround an esbuild bug? |
| 36 | 74 | (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/"; |
| 37 | 75 | compiled.map.sources[0] = filepath; |
| 38 | src = compiled.code.replace("marko/debug/html", "#marko/runtime") + | |
| 76 | src = compiled.code + | |
| 39 | 77 | "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + |
| 40 | 78 | Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64"); |
| 41 | 79 | cache = { src, scannedClientRefs: Array.from(scannedClientRefs) }; |
| ... | ... | @@ -54,6 +92,7 @@ export function loadMarko(module: NodeJS.Module, filepath: string) { |
| 54 | 92 | } |
| 55 | 93 | |
| 56 | 94 | import * as marko from "@marko/compiler"; |
| 95 | import * as markodown from "@clo/markodown"; | |
| 57 | 96 | import * as hot from "./hot.ts"; |
| 58 | 97 | import * as fs from "#sitegen/fs"; |
| 59 | 98 | import * as path from "node:path"; |
framework/mdx-hack.tsx deleted-32| ... | ... | @@ -1,32 +0,0 @@ |
| 1 | export function useMDXComponents() { | |
| 2 | return { | |
| 3 | pre: async ( | |
| 4 | { filename, whitespace, tint, content: [, , { className, content }] }, | |
| 5 | ) => { | |
| 6 | const lang = className?.split("-")?.[1] ?? null; | |
| 7 | const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang) | |
| 8 | ? await highlight.highlightCode( | |
| 9 | content.trim(), | |
| 10 | lang, | |
| 11 | ) | |
| 12 | : content.trim(); | |
| 13 | return ( | |
| 14 | <figure class={["code", tint]}> | |
| 15 | {filename && <figcaption>{filename}</figcaption>} | |
| 16 | <pre | |
| 17 | style={whitespace ? { "white-space": whitespace } : {}} | |
| 18 | ><code>{render.raw(code)}</code></pre> | |
| 19 | </figure> | |
| 20 | ); | |
| 21 | }, | |
| 22 | code: ({ content }) => { | |
| 23 | return ( | |
| 24 | <code> | |
| 25 | {content} | |
| 26 | </code> | |
| 27 | ); | |
| 28 | }, | |
| 29 | }; | |
| 30 | } | |
| 31 | import * as highlight from "../src/file-viewer/highlight.ts"; | |
| 32 | import * as render from "lib/render.ts"; |
framework/sitegen-wrap.marko created+2| ... | ... | @@ -0,0 +1,2 @@ |
| 1 | <const/_=$global.cloverScripts=input.cloverScripts /> | |
| 2 | <${input.page} ...input.attrs /> |
lib/render.ts+3| ... | ... | @@ -1,4 +1,7 @@ |
| 1 | 1 | /** |
| 2 | * @deprecated replacing with pure Marko | |
| 3 | * https://git.paperclover.net/clo/sitegen/issues/90 | |
| 4 | * | |
| 2 | 5 | * Clover's rendering engine is the backbone of her website generator. It |
| 3 | 6 | * converts objects and components (functions returning {@linkcode Node}) into |
| 4 | 7 | * HTML. the engine is simple, self-contained, and easily extendable. |
lib/render/meta.ts+4-2| ... | ... | @@ -148,7 +148,7 @@ function renderMetadata(meta: Meta, template: Template): string { |
| 148 | 148 | const referrer = meta.referrer ?? template.referrer ?? null; |
| 149 | 149 | const themeColor = meta.themeColor ?? template.themeColor ?? null; |
| 150 | 150 | const viewport = meta.viewport ?? template.viewport ?? |
| 151 | "width=device-width, initial-scale=1.0"; | |
| 151 | "width=device-width, initial-scale=1, maximum-scale=1"; | |
| 152 | 152 | |
| 153 | 153 | const embed = meta.embed ?? template.embed ?? null; |
| 154 | 154 | let openGraph = meta.openGraph ?? template.openGraph ?? null; |
| ... | ... | @@ -168,7 +168,9 @@ function renderMetadata(meta: Meta, template: Template): string { |
| 168 | 168 | ...twitter, |
| 169 | 169 | }; |
| 170 | 170 | if (thumbnail) { |
| 171 | openGraph.image = embed.thumbnail; | |
| 171 | const resolved = new URL(thumbnail, template.base).href; | |
| 172 | openGraph.image = resolved; | |
| 173 | twitter.image = resolved; | |
| 172 | 174 | } |
| 173 | 175 | if (siteTitle) { |
| 174 | 176 | openGraph.site_name = siteTitle; |
package-lock.json+255-2348| ... | ... | @@ -4,30 +4,27 @@ |
| 4 | 4 | "requires": true, |
| 5 | 5 | "packages": { |
| 6 | 6 | "": { |
| 7 | "name": "sitegen", | |
| 8 | 7 | "dependencies": { |
| 8 | "@clo/markodown": "npm:@paperclover/markodown@rc", | |
| 9 | 9 | "@hono/node-server": "1.14.3", |
| 10 | "@mdx-js/mdx": "3.1.0", | |
| 10 | "@marko/compiler": "^5.39.55", | |
| 11 | "@types/node": "24.0.10", | |
| 12 | "browserslist": "4.28.1", | |
| 11 | 13 | "codemirror": "6.0.1", |
| 12 | "dashjs": "^5.0.3", | |
| 13 | "devalue": "^5.3.2", | |
| 14 | "dashjs": "5.0.3", | |
| 15 | "devalue": "5.6.2", | |
| 14 | 16 | "esbuild": "0.25.5", |
| 15 | 17 | "hls.js": "1.6.5", |
| 16 | "hono": "^4.10.3", | |
| 18 | "hono": "4.11.9", | |
| 17 | 19 | "lib": "file:lib", |
| 18 | "marko": "6.0.82", | |
| 20 | "marko": "^5.38.24", | |
| 19 | 21 | "msgpackr": "1.11.5", |
| 20 | 22 | "puppeteer": "24.23.0", |
| 21 | "rehype-mdx-code-props": "^3.0.1", | |
| 22 | 23 | "source-map-support": "0.5.21", |
| 24 | "typescript": "5.8.3", | |
| 23 | 25 | "unique-names-generator": "4.7.1", |
| 24 | 26 | "vscode-oniguruma": "2.0.1", |
| 25 | 27 | "vscode-textmate": "9.2.0" |
| 26 | }, | |
| 27 | "devDependencies": { | |
| 28 | "@types/node": "24.0.10", | |
| 29 | "baseline-browser-mapping": "^2.9.14", | |
| 30 | "typescript": "5.8.3" | |
| 31 | 28 | } |
| 32 | 29 | }, |
| 33 | 30 | "lib": {}, |
| ... | ... | @@ -45,223 +42,6 @@ |
| 45 | 42 | "node": ">=6.9.0" |
| 46 | 43 | } |
| 47 | 44 | }, |
| 48 | "node_modules/@babel/compat-data": { | |
| 49 | "version": "7.28.4", | |
| 50 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", | |
| 51 | "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", | |
| 52 | "license": "MIT", | |
| 53 | "engines": { | |
| 54 | "node": ">=6.9.0" | |
| 55 | } | |
| 56 | }, | |
| 57 | "node_modules/@babel/core": { | |
| 58 | "version": "7.28.4", | |
| 59 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", | |
| 60 | "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", | |
| 61 | "license": "MIT", | |
| 62 | "peer": true, | |
| 63 | "dependencies": { | |
| 64 | "@babel/code-frame": "^7.27.1", | |
| 65 | "@babel/generator": "^7.28.3", | |
| 66 | "@babel/helper-compilation-targets": "^7.27.2", | |
| 67 | "@babel/helper-module-transforms": "^7.28.3", | |
| 68 | "@babel/helpers": "^7.28.4", | |
| 69 | "@babel/parser": "^7.28.4", | |
| 70 | "@babel/template": "^7.27.2", | |
| 71 | "@babel/traverse": "^7.28.4", | |
| 72 | "@babel/types": "^7.28.4", | |
| 73 | "@jridgewell/remapping": "^2.3.5", | |
| 74 | "convert-source-map": "^2.0.0", | |
| 75 | "debug": "^4.1.0", | |
| 76 | "gensync": "^1.0.0-beta.2", | |
| 77 | "json5": "^2.2.3", | |
| 78 | "semver": "^6.3.1" | |
| 79 | }, | |
| 80 | "engines": { | |
| 81 | "node": ">=6.9.0" | |
| 82 | }, | |
| 83 | "funding": { | |
| 84 | "type": "opencollective", | |
| 85 | "url": "https://opencollective.com/babel" | |
| 86 | } | |
| 87 | }, | |
| 88 | "node_modules/@babel/generator": { | |
| 89 | "version": "7.28.3", | |
| 90 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", | |
| 91 | "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", | |
| 92 | "license": "MIT", | |
| 93 | "dependencies": { | |
| 94 | "@babel/parser": "^7.28.3", | |
| 95 | "@babel/types": "^7.28.2", | |
| 96 | "@jridgewell/gen-mapping": "^0.3.12", | |
| 97 | "@jridgewell/trace-mapping": "^0.3.28", | |
| 98 | "jsesc": "^3.0.2" | |
| 99 | }, | |
| 100 | "engines": { | |
| 101 | "node": ">=6.9.0" | |
| 102 | } | |
| 103 | }, | |
| 104 | "node_modules/@babel/helper-annotate-as-pure": { | |
| 105 | "version": "7.27.3", | |
| 106 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", | |
| 107 | "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", | |
| 108 | "license": "MIT", | |
| 109 | "dependencies": { | |
| 110 | "@babel/types": "^7.27.3" | |
| 111 | }, | |
| 112 | "engines": { | |
| 113 | "node": ">=6.9.0" | |
| 114 | } | |
| 115 | }, | |
| 116 | "node_modules/@babel/helper-compilation-targets": { | |
| 117 | "version": "7.27.2", | |
| 118 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", | |
| 119 | "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", | |
| 120 | "license": "MIT", | |
| 121 | "dependencies": { | |
| 122 | "@babel/compat-data": "^7.27.2", | |
| 123 | "@babel/helper-validator-option": "^7.27.1", | |
| 124 | "browserslist": "^4.24.0", | |
| 125 | "lru-cache": "^5.1.1", | |
| 126 | "semver": "^6.3.1" | |
| 127 | }, | |
| 128 | "engines": { | |
| 129 | "node": ">=6.9.0" | |
| 130 | } | |
| 131 | }, | |
| 132 | "node_modules/@babel/helper-create-class-features-plugin": { | |
| 133 | "version": "7.28.3", | |
| 134 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", | |
| 135 | "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", | |
| 136 | "license": "MIT", | |
| 137 | "dependencies": { | |
| 138 | "@babel/helper-annotate-as-pure": "^7.27.3", | |
| 139 | "@babel/helper-member-expression-to-functions": "^7.27.1", | |
| 140 | "@babel/helper-optimise-call-expression": "^7.27.1", | |
| 141 | "@babel/helper-replace-supers": "^7.27.1", | |
| 142 | "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", | |
| 143 | "@babel/traverse": "^7.28.3", | |
| 144 | "semver": "^6.3.1" | |
| 145 | }, | |
| 146 | "engines": { | |
| 147 | "node": ">=6.9.0" | |
| 148 | }, | |
| 149 | "peerDependencies": { | |
| 150 | "@babel/core": "^7.0.0" | |
| 151 | } | |
| 152 | }, | |
| 153 | "node_modules/@babel/helper-globals": { | |
| 154 | "version": "7.28.0", | |
| 155 | "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", | |
| 156 | "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", | |
| 157 | "license": "MIT", | |
| 158 | "engines": { | |
| 159 | "node": ">=6.9.0" | |
| 160 | } | |
| 161 | }, | |
| 162 | "node_modules/@babel/helper-member-expression-to-functions": { | |
| 163 | "version": "7.27.1", | |
| 164 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", | |
| 165 | "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", | |
| 166 | "license": "MIT", | |
| 167 | "dependencies": { | |
| 168 | "@babel/traverse": "^7.27.1", | |
| 169 | "@babel/types": "^7.27.1" | |
| 170 | }, | |
| 171 | "engines": { | |
| 172 | "node": ">=6.9.0" | |
| 173 | } | |
| 174 | }, | |
| 175 | "node_modules/@babel/helper-module-imports": { | |
| 176 | "version": "7.27.1", | |
| 177 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", | |
| 178 | "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", | |
| 179 | "license": "MIT", | |
| 180 | "dependencies": { | |
| 181 | "@babel/traverse": "^7.27.1", | |
| 182 | "@babel/types": "^7.27.1" | |
| 183 | }, | |
| 184 | "engines": { | |
| 185 | "node": ">=6.9.0" | |
| 186 | } | |
| 187 | }, | |
| 188 | "node_modules/@babel/helper-module-transforms": { | |
| 189 | "version": "7.28.3", | |
| 190 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", | |
| 191 | "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", | |
| 192 | "license": "MIT", | |
| 193 | "dependencies": { | |
| 194 | "@babel/helper-module-imports": "^7.27.1", | |
| 195 | "@babel/helper-validator-identifier": "^7.27.1", | |
| 196 | "@babel/traverse": "^7.28.3" | |
| 197 | }, | |
| 198 | "engines": { | |
| 199 | "node": ">=6.9.0" | |
| 200 | }, | |
| 201 | "peerDependencies": { | |
| 202 | "@babel/core": "^7.0.0" | |
| 203 | } | |
| 204 | }, | |
| 205 | "node_modules/@babel/helper-optimise-call-expression": { | |
| 206 | "version": "7.27.1", | |
| 207 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", | |
| 208 | "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", | |
| 209 | "license": "MIT", | |
| 210 | "dependencies": { | |
| 211 | "@babel/types": "^7.27.1" | |
| 212 | }, | |
| 213 | "engines": { | |
| 214 | "node": ">=6.9.0" | |
| 215 | } | |
| 216 | }, | |
| 217 | "node_modules/@babel/helper-plugin-utils": { | |
| 218 | "version": "7.27.1", | |
| 219 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", | |
| 220 | "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", | |
| 221 | "license": "MIT", | |
| 222 | "engines": { | |
| 223 | "node": ">=6.9.0" | |
| 224 | } | |
| 225 | }, | |
| 226 | "node_modules/@babel/helper-replace-supers": { | |
| 227 | "version": "7.27.1", | |
| 228 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", | |
| 229 | "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", | |
| 230 | "license": "MIT", | |
| 231 | "dependencies": { | |
| 232 | "@babel/helper-member-expression-to-functions": "^7.27.1", | |
| 233 | "@babel/helper-optimise-call-expression": "^7.27.1", | |
| 234 | "@babel/traverse": "^7.27.1" | |
| 235 | }, | |
| 236 | "engines": { | |
| 237 | "node": ">=6.9.0" | |
| 238 | }, | |
| 239 | "peerDependencies": { | |
| 240 | "@babel/core": "^7.0.0" | |
| 241 | } | |
| 242 | }, | |
| 243 | "node_modules/@babel/helper-skip-transparent-expression-wrappers": { | |
| 244 | "version": "7.27.1", | |
| 245 | "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", | |
| 246 | "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", | |
| 247 | "license": "MIT", | |
| 248 | "dependencies": { | |
| 249 | "@babel/traverse": "^7.27.1", | |
| 250 | "@babel/types": "^7.27.1" | |
| 251 | }, | |
| 252 | "engines": { | |
| 253 | "node": ">=6.9.0" | |
| 254 | } | |
| 255 | }, | |
| 256 | "node_modules/@babel/helper-string-parser": { | |
| 257 | "version": "7.27.1", | |
| 258 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", | |
| 259 | "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", | |
| 260 | "license": "MIT", | |
| 261 | "engines": { | |
| 262 | "node": ">=6.9.0" | |
| 263 | } | |
| 264 | }, | |
| 265 | 45 | "node_modules/@babel/helper-validator-identifier": { |
| 266 | 46 | "version": "7.27.1", |
| 267 | 47 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", |
| ... | ... | @@ -271,145 +51,26 @@ |
| 271 | 51 | "node": ">=6.9.0" |
| 272 | 52 | } |
| 273 | 53 | }, |
| 274 | "node_modules/@babel/helper-validator-option": { | |
| 275 | "version": "7.27.1", | |
| 276 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", | |
| 277 | "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", | |
| 278 | "license": "MIT", | |
| 279 | "engines": { | |
| 280 | "node": ">=6.9.0" | |
| 281 | } | |
| 282 | }, | |
| 283 | "node_modules/@babel/helpers": { | |
| 284 | "version": "7.28.4", | |
| 285 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", | |
| 286 | "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", | |
| 287 | "license": "MIT", | |
| 288 | "dependencies": { | |
| 289 | "@babel/template": "^7.27.2", | |
| 290 | "@babel/types": "^7.28.4" | |
| 291 | }, | |
| 292 | "engines": { | |
| 293 | "node": ">=6.9.0" | |
| 294 | } | |
| 295 | }, | |
| 296 | "node_modules/@babel/parser": { | |
| 297 | "version": "7.28.4", | |
| 298 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", | |
| 299 | "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", | |
| 300 | "license": "MIT", | |
| 301 | "dependencies": { | |
| 302 | "@babel/types": "^7.28.4" | |
| 303 | }, | |
| 304 | "bin": { | |
| 305 | "parser": "bin/babel-parser.js" | |
| 306 | }, | |
| 307 | "engines": { | |
| 308 | "node": ">=6.0.0" | |
| 309 | } | |
| 310 | }, | |
| 311 | "node_modules/@babel/plugin-syntax-typescript": { | |
| 312 | "version": "7.27.1", | |
| 313 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", | |
| 314 | "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", | |
| 315 | "license": "MIT", | |
| 316 | "dependencies": { | |
| 317 | "@babel/helper-plugin-utils": "^7.27.1" | |
| 318 | }, | |
| 319 | "engines": { | |
| 320 | "node": ">=6.9.0" | |
| 321 | }, | |
| 322 | "peerDependencies": { | |
| 323 | "@babel/core": "^7.0.0-0" | |
| 324 | } | |
| 325 | }, | |
| 326 | "node_modules/@babel/plugin-transform-modules-commonjs": { | |
| 327 | "version": "7.27.1", | |
| 328 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", | |
| 329 | "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", | |
| 330 | "license": "MIT", | |
| 331 | "dependencies": { | |
| 332 | "@babel/helper-module-transforms": "^7.27.1", | |
| 333 | "@babel/helper-plugin-utils": "^7.27.1" | |
| 334 | }, | |
| 335 | "engines": { | |
| 336 | "node": ">=6.9.0" | |
| 337 | }, | |
| 338 | "peerDependencies": { | |
| 339 | "@babel/core": "^7.0.0-0" | |
| 340 | } | |
| 341 | }, | |
| 342 | "node_modules/@babel/plugin-transform-typescript": { | |
| 343 | "version": "7.28.0", | |
| 344 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", | |
| 345 | "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", | |
| 346 | "license": "MIT", | |
| 347 | "dependencies": { | |
| 348 | "@babel/helper-annotate-as-pure": "^7.27.3", | |
| 349 | "@babel/helper-create-class-features-plugin": "^7.27.1", | |
| 350 | "@babel/helper-plugin-utils": "^7.27.1", | |
| 351 | "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", | |
| 352 | "@babel/plugin-syntax-typescript": "^7.27.1" | |
| 353 | }, | |
| 354 | "engines": { | |
| 355 | "node": ">=6.9.0" | |
| 356 | }, | |
| 54 | "node_modules/@clo/markodown": { | |
| 55 | "name": "@paperclover/markodown", | |
| 56 | "version": "1.0.0-rc.15", | |
| 57 | "resolved": "https://registry.npmjs.org/@paperclover/markodown/-/markodown-1.0.0-rc.15.tgz", | |
| 58 | "integrity": "sha512-c4aTliRZUQ9DwQx3nFaftTE5GCvjAvCl0EF3nton6ah9zDoRu69nEuFbKOpSlxYrWizWzkPQVBu9xZY6gr+ezQ==", | |
| 357 | 59 | "peerDependencies": { |
| 358 | "@babel/core": "^7.0.0-0" | |
| 359 | } | |
| 360 | }, | |
| 361 | "node_modules/@babel/runtime": { | |
| 362 | "version": "7.28.4", | |
| 363 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", | |
| 364 | "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", | |
| 365 | "license": "MIT", | |
| 366 | "engines": { | |
| 367 | "node": ">=6.9.0" | |
| 368 | } | |
| 369 | }, | |
| 370 | "node_modules/@babel/template": { | |
| 371 | "version": "7.27.2", | |
| 372 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", | |
| 373 | "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", | |
| 374 | "license": "MIT", | |
| 375 | "dependencies": { | |
| 376 | "@babel/code-frame": "^7.27.1", | |
| 377 | "@babel/parser": "^7.27.2", | |
| 378 | "@babel/types": "^7.27.1" | |
| 379 | }, | |
| 380 | "engines": { | |
| 381 | "node": ">=6.9.0" | |
| 382 | } | |
| 383 | }, | |
| 384 | "node_modules/@babel/traverse": { | |
| 385 | "version": "7.28.4", | |
| 386 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", | |
| 387 | "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", | |
| 388 | "license": "MIT", | |
| 389 | "dependencies": { | |
| 390 | "@babel/code-frame": "^7.27.1", | |
| 391 | "@babel/generator": "^7.28.3", | |
| 392 | "@babel/helper-globals": "^7.28.0", | |
| 393 | "@babel/parser": "^7.28.4", | |
| 394 | "@babel/template": "^7.27.2", | |
| 395 | "@babel/types": "^7.28.4", | |
| 396 | "debug": "^4.3.1" | |
| 397 | }, | |
| 398 | "engines": { | |
| 399 | "node": ">=6.9.0" | |
| 400 | } | |
| 401 | }, | |
| 402 | "node_modules/@babel/types": { | |
| 403 | "version": "7.28.4", | |
| 404 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", | |
| 405 | "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", | |
| 406 | "license": "MIT", | |
| 407 | "dependencies": { | |
| 408 | "@babel/helper-string-parser": "^7.27.1", | |
| 409 | "@babel/helper-validator-identifier": "^7.27.1" | |
| 60 | "@marko/compiler": "^5", | |
| 61 | "esbuild": "0.x", | |
| 62 | "rollup": "^4" | |
| 410 | 63 | }, |
| 411 | "engines": { | |
| 412 | "node": ">=6.9.0" | |
| 64 | "peerDependenciesMeta": { | |
| 65 | "@marko/compiler": { | |
| 66 | "optional": true | |
| 67 | }, | |
| 68 | "esbuild": { | |
| 69 | "optional": true | |
| 70 | }, | |
| 71 | "rollup": { | |
| 72 | "optional": true | |
| 73 | } | |
| 413 | 74 | } |
| 414 | 75 | }, |
| 415 | 76 | "node_modules/@codemirror/autocomplete": { |
| ... | ... | @@ -905,51 +566,12 @@ |
| 905 | 566 | "hono": "^4" |
| 906 | 567 | } |
| 907 | 568 | }, |
| 908 | "node_modules/@jridgewell/gen-mapping": { | |
| 909 | "version": "0.3.13", | |
| 910 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | |
| 911 | "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | |
| 912 | "license": "MIT", | |
| 913 | "dependencies": { | |
| 914 | "@jridgewell/sourcemap-codec": "^1.5.0", | |
| 915 | "@jridgewell/trace-mapping": "^0.3.24" | |
| 916 | } | |
| 917 | }, | |
| 918 | "node_modules/@jridgewell/remapping": { | |
| 919 | "version": "2.3.5", | |
| 920 | "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", | |
| 921 | "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", | |
| 922 | "license": "MIT", | |
| 923 | "dependencies": { | |
| 924 | "@jridgewell/gen-mapping": "^0.3.5", | |
| 925 | "@jridgewell/trace-mapping": "^0.3.24" | |
| 926 | } | |
| 927 | }, | |
| 928 | "node_modules/@jridgewell/resolve-uri": { | |
| 929 | "version": "3.1.2", | |
| 930 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 931 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 932 | "license": "MIT", | |
| 933 | "engines": { | |
| 934 | "node": ">=6.0.0" | |
| 935 | } | |
| 936 | }, | |
| 937 | 569 | "node_modules/@jridgewell/sourcemap-codec": { |
| 938 | "version": "1.5.4", | |
| 939 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", | |
| 940 | "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", | |
| 570 | "version": "1.5.5", | |
| 571 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 572 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 941 | 573 | "license": "MIT" |
| 942 | 574 | }, |
| 943 | "node_modules/@jridgewell/trace-mapping": { | |
| 944 | "version": "0.3.31", | |
| 945 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 946 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 947 | "license": "MIT", | |
| 948 | "dependencies": { | |
| 949 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 950 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 951 | } | |
| 952 | }, | |
| 953 | 575 | "node_modules/@lezer/common": { |
| 954 | 576 | "version": "1.2.3", |
| 955 | 577 | "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", |
| ... | ... | @@ -990,25 +612,16 @@ |
| 990 | 612 | "license": "MIT" |
| 991 | 613 | }, |
| 992 | 614 | "node_modules/@marko/compiler": { |
| 993 | "version": "5.39.40", | |
| 994 | "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.40.tgz", | |
| 995 | "integrity": "sha512-gQSdfJ5XOy01DtP7ULiqI1onp7Cix1QvnjM0KbfR2HMrWNI14V8V/2JguFAm3BzKEMZmmgvRLk0rvBzSm9FP6Q==", | |
| 615 | "version": "5.39.55", | |
| 616 | "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.55.tgz", | |
| 617 | "integrity": "sha512-QHukS/gMzxKWcF5xALVAH1swVAudCXC5DA2DDC5r+TzBiQ0v+uz7D8oNlwxO2xjTRPu7AUukcufHscbhytcyGg==", | |
| 996 | 618 | "license": "MIT", |
| 619 | "peer": true, | |
| 997 | 620 | "dependencies": { |
| 998 | "@babel/code-frame": "^7.27.1", | |
| 999 | "@babel/core": "^7.28.0", | |
| 1000 | "@babel/generator": "^7.28.0", | |
| 1001 | "@babel/parser": "^7.28.0", | |
| 1002 | "@babel/plugin-syntax-typescript": "^7.27.1", | |
| 1003 | "@babel/plugin-transform-modules-commonjs": "^7.27.1", | |
| 1004 | "@babel/plugin-transform-typescript": "^7.28.0", | |
| 1005 | "@babel/runtime": "^7.28.2", | |
| 1006 | "@babel/traverse": "^7.28.0", | |
| 1007 | "@babel/types": "^7.28.2", | |
| 1008 | 621 | "@luxass/strip-json-comments": "^1.4.0", |
| 1009 | 622 | "complain": "^1.6.1", |
| 1010 | 623 | "he": "^1.2.0", |
| 1011 | "htmljs-parser": "^5.7.3", | |
| 624 | "htmljs-parser": "^5.7.4", | |
| 1012 | 625 | "jsesc": "^3.1.0", |
| 1013 | 626 | "kleur": "^4.1.5", |
| 1014 | 627 | "lasso-package-root": "^1.0.1", |
| ... | ... | @@ -1023,40 +636,18 @@ |
| 1023 | 636 | "node": "18 || 20 || >=22" |
| 1024 | 637 | } |
| 1025 | 638 | }, |
| 1026 | "node_modules/@mdx-js/mdx": { | |
| 1027 | "version": "3.1.0", | |
| 1028 | "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", | |
| 1029 | "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", | |
| 639 | "node_modules/@marko/runtime-tags": { | |
| 640 | "version": "6.0.145", | |
| 641 | "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.145.tgz", | |
| 642 | "integrity": "sha512-3M65F25XylMRoitQZ7NmvSCk8IliBwhT8ay5AJaChJ55NvR9B2l2mnT0YcDvVQjqxKGUYU/xKfNmFYGcCj2RVA==", | |
| 1030 | 643 | "license": "MIT", |
| 1031 | 644 | "dependencies": { |
| 1032 | "@types/estree": "^1.0.0", | |
| 1033 | "@types/estree-jsx": "^1.0.0", | |
| 1034 | "@types/hast": "^3.0.0", | |
| 1035 | "@types/mdx": "^2.0.0", | |
| 1036 | "collapse-white-space": "^2.0.0", | |
| 1037 | "devlop": "^1.0.0", | |
| 1038 | "estree-util-is-identifier-name": "^3.0.0", | |
| 1039 | "estree-util-scope": "^1.0.0", | |
| 1040 | "estree-walker": "^3.0.0", | |
| 1041 | "hast-util-to-jsx-runtime": "^2.0.0", | |
| 1042 | "markdown-extensions": "^2.0.0", | |
| 1043 | "recma-build-jsx": "^1.0.0", | |
| 1044 | "recma-jsx": "^1.0.0", | |
| 1045 | "recma-stringify": "^1.0.0", | |
| 1046 | "rehype-recma": "^1.0.0", | |
| 1047 | "remark-mdx": "^3.0.0", | |
| 1048 | "remark-parse": "^11.0.0", | |
| 1049 | "remark-rehype": "^11.0.0", | |
| 1050 | "source-map": "^0.7.0", | |
| 1051 | "unified": "^11.0.0", | |
| 1052 | "unist-util-position-from-estree": "^2.0.0", | |
| 1053 | "unist-util-stringify-position": "^4.0.0", | |
| 1054 | "unist-util-visit": "^5.0.0", | |
| 1055 | "vfile": "^6.0.0" | |
| 645 | "@marko/compiler": "^5.39.53", | |
| 646 | "csstype": "^3.2.3", | |
| 647 | "magic-string": "^0.30.21" | |
| 1056 | 648 | }, |
| 1057 | "funding": { | |
| 1058 | "type": "opencollective", | |
| 1059 | "url": "https://opencollective.com/unified" | |
| 649 | "engines": { | |
| 650 | "node": "18 || 20 || >=22" | |
| 1060 | 651 | } |
| 1061 | 652 | }, |
| 1062 | 653 | "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { |
| ... | ... | @@ -1170,242 +761,35 @@ |
| 1170 | 761 | "node": ">=10" |
| 1171 | 762 | } |
| 1172 | 763 | }, |
| 1173 | "node_modules/@svta/cml-608": { | |
| 1174 | "version": "1.0.1", | |
| 1175 | "resolved": "https://registry.npmjs.org/@svta/cml-608/-/cml-608-1.0.1.tgz", | |
| 1176 | "integrity": "sha512-Y/Ier9VPUSOBnf0bJqdDyTlPrt4dDB+jk5mYHa1bnD2kcRl8qn7KkW3PRuj4w1aVN+BS2eHmsLxodt7P2hylUg==", | |
| 1177 | "license": "Apache-2.0", | |
| 1178 | "engines": { | |
| 1179 | "node": ">=20" | |
| 1180 | } | |
| 764 | "node_modules/@svta/common-media-library": { | |
| 765 | "version": "0.12.4", | |
| 766 | "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.12.4.tgz", | |
| 767 | "integrity": "sha512-9EuOoaNmz7JrfGwjsrD9SxF9otU5TNMnbLu1yU4BeLK0W5cDxVXXR58Z89q9u2AnHjIctscjMTYdlqQ1gojTuw==", | |
| 768 | "license": "Apache-2.0" | |
| 1181 | 769 | }, |
| 1182 | "node_modules/@svta/cml-cmcd": { | |
| 1183 | "version": "1.0.1", | |
| 1184 | "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-1.0.1.tgz", | |
| 1185 | "integrity": "sha512-eox305g+QUJgXqOLVrbgxeQHCgl90ewwQ9O2bIoo7m+hanR8Xswu5CknFnT5qqIbLOHfw80ug+raycoAFHTQ+w==", | |
| 1186 | "license": "Apache-2.0", | |
| 1187 | "engines": { | |
| 1188 | "node": ">=20" | |
| 1189 | }, | |
| 1190 | "peerDependencies": { | |
| 1191 | "@svta/cml-cta": "1.0.1", | |
| 1192 | "@svta/cml-structured-field-values": "1.0.1", | |
| 1193 | "@svta/cml-utils": "1.0.1" | |
| 770 | "node_modules/@tootallnate/quickjs-emscripten": { | |
| 771 | "version": "0.23.0", | |
| 772 | "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", | |
| 773 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", | |
| 774 | "license": "MIT" | |
| 775 | }, | |
| 776 | "node_modules/@types/node": { | |
| 777 | "version": "24.0.10", | |
| 778 | "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", | |
| 779 | "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", | |
| 780 | "license": "MIT", | |
| 781 | "dependencies": { | |
| 782 | "undici-types": "~7.8.0" | |
| 1194 | 783 | } |
| 1195 | 784 | }, |
| 1196 | "node_modules/@svta/cml-cmsd": { | |
| 1197 | "version": "1.0.1", | |
| 1198 | "resolved": "https://registry.npmjs.org/@svta/cml-cmsd/-/cml-cmsd-1.0.1.tgz", | |
| 1199 | "integrity": "sha512-+nIB8PuSfb/qw+xGaArPhNqPm84tBJUbe3H1DnPL5QUsjSUI7mUIUQwAtRV1ZdEu0+80g9i0op79woB0OIwr/g==", | |
| 1200 | "license": "Apache-2.0", | |
| 1201 | "engines": { | |
| 1202 | "node": ">=20" | |
| 1203 | }, | |
| 1204 | "peerDependencies": { | |
| 1205 | "@svta/cml-cta": "1.0.1", | |
| 1206 | "@svta/cml-structured-field-values": "1.0.1", | |
| 1207 | "@svta/cml-utils": "1.0.1" | |
| 1208 | } | |
| 1209 | }, | |
| 1210 | "node_modules/@svta/cml-cta": { | |
| 1211 | "version": "1.0.1", | |
| 1212 | "resolved": "https://registry.npmjs.org/@svta/cml-cta/-/cml-cta-1.0.1.tgz", | |
| 1213 | "integrity": "sha512-jcXqNIPv26bmFxIOFh8/c3+6WLH4qBjKpq9qTQcggDPoHuV1YBydMsJLOnYPDeK8rNMKcAkFLbnDRvyJthu5yw==", | |
| 1214 | "license": "Apache-2.0", | |
| 1215 | "peer": true, | |
| 1216 | "engines": { | |
| 1217 | "node": ">=20" | |
| 1218 | }, | |
| 1219 | "peerDependencies": { | |
| 1220 | "@svta/cml-structured-field-values": "1.0.1", | |
| 1221 | "@svta/cml-utils": "1.0.1" | |
| 1222 | } | |
| 1223 | }, | |
| 1224 | "node_modules/@svta/cml-dash": { | |
| 1225 | "version": "1.0.1", | |
| 1226 | "resolved": "https://registry.npmjs.org/@svta/cml-dash/-/cml-dash-1.0.1.tgz", | |
| 1227 | "integrity": "sha512-lYnD1I7FUbbQND+xICI+kcRaRXuT+whKk27R8m8me5VMVu2sMsAMc7Yui6l9sxw2cBKt8pSETPYRm/1+n4LZkw==", | |
| 1228 | "license": "Apache-2.0", | |
| 1229 | "engines": { | |
| 1230 | "node": ">=20" | |
| 1231 | }, | |
| 1232 | "peerDependencies": { | |
| 1233 | "@svta/cml-utils": "1.0.1" | |
| 1234 | } | |
| 1235 | }, | |
| 1236 | "node_modules/@svta/cml-id3": { | |
| 1237 | "version": "1.0.1", | |
| 1238 | "resolved": "https://registry.npmjs.org/@svta/cml-id3/-/cml-id3-1.0.1.tgz", | |
| 1239 | "integrity": "sha512-90fGlL1qRI88CcaB89k6NG6cC3kky4Eu2jwqU4HefqK+S5k2OASUxf8JXkGz+DsdaiY7sh51vGPYdolfBZS7ug==", | |
| 1240 | "license": "Apache-2.0", | |
| 1241 | "engines": { | |
| 1242 | "node": ">=20" | |
| 1243 | }, | |
| 1244 | "peerDependencies": { | |
| 1245 | "@svta/cml-utils": "1.0.1" | |
| 1246 | } | |
| 1247 | }, | |
| 1248 | "node_modules/@svta/cml-request": { | |
| 1249 | "version": "1.0.1", | |
| 1250 | "resolved": "https://registry.npmjs.org/@svta/cml-request/-/cml-request-1.0.1.tgz", | |
| 1251 | "integrity": "sha512-enL19BuXUjFkDDDF9jdNwUclMNPRsagnjGAetVC7xcmpDMpEx+ZLgsDip6BFNg5p6izSEk/OyujTWW1r8bDNiA==", | |
| 1252 | "license": "Apache-2.0", | |
| 1253 | "engines": { | |
| 1254 | "node": ">=20" | |
| 1255 | }, | |
| 1256 | "peerDependencies": { | |
| 1257 | "@svta/cml-utils": "1.0.1", | |
| 1258 | "@svta/cml-xml": "1.0.1" | |
| 1259 | } | |
| 1260 | }, | |
| 1261 | "node_modules/@svta/cml-structured-field-values": { | |
| 1262 | "version": "1.0.1", | |
| 1263 | "resolved": "https://registry.npmjs.org/@svta/cml-structured-field-values/-/cml-structured-field-values-1.0.1.tgz", | |
| 1264 | "integrity": "sha512-Kibciki59Pon3Pn/sl5uyrbJcSpZQDKqdCfDrokBvOdLoqqcd0oFrkEPsZBiuuIODX1CB80612xe8hopeFDyBA==", | |
| 1265 | "license": "Apache-2.0", | |
| 1266 | "peer": true, | |
| 1267 | "engines": { | |
| 1268 | "node": ">=20" | |
| 1269 | }, | |
| 1270 | "peerDependencies": { | |
| 1271 | "@svta/cml-utils": "1.0.1" | |
| 1272 | } | |
| 1273 | }, | |
| 1274 | "node_modules/@svta/cml-utils": { | |
| 1275 | "version": "1.0.1", | |
| 1276 | "resolved": "https://registry.npmjs.org/@svta/cml-utils/-/cml-utils-1.0.1.tgz", | |
| 1277 | "integrity": "sha512-kso3curTJfp00I1mKFoBliBApjn4aPE+wF8cPucf7TrSDVWZDeLLuF14ASmUE9m7rnrqTTK4878VvmXaXcCCfQ==", | |
| 1278 | "license": "Apache-2.0", | |
| 1279 | "peer": true, | |
| 1280 | "engines": { | |
| 1281 | "node": ">=20" | |
| 1282 | } | |
| 1283 | }, | |
| 1284 | "node_modules/@svta/cml-xml": { | |
| 1285 | "version": "1.0.1", | |
| 1286 | "resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz", | |
| 1287 | "integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==", | |
| 1288 | "license": "Apache-2.0", | |
| 1289 | "peer": true, | |
| 1290 | "engines": { | |
| 1291 | "node": ">=20" | |
| 1292 | }, | |
| 1293 | "peerDependencies": { | |
| 1294 | "@svta/cml-utils": "1.0.1" | |
| 1295 | } | |
| 1296 | }, | |
| 1297 | "node_modules/@tootallnate/quickjs-emscripten": { | |
| 1298 | "version": "0.23.0", | |
| 1299 | "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", | |
| 1300 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", | |
| 1301 | "license": "MIT" | |
| 1302 | }, | |
| 1303 | "node_modules/@types/debug": { | |
| 1304 | "version": "4.1.12", | |
| 1305 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", | |
| 1306 | "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", | |
| 1307 | "license": "MIT", | |
| 1308 | "dependencies": { | |
| 1309 | "@types/ms": "*" | |
| 1310 | } | |
| 1311 | }, | |
| 1312 | "node_modules/@types/estree": { | |
| 1313 | "version": "1.0.8", | |
| 1314 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", | |
| 1315 | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", | |
| 1316 | "license": "MIT" | |
| 1317 | }, | |
| 1318 | "node_modules/@types/estree-jsx": { | |
| 1319 | "version": "1.0.5", | |
| 1320 | "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", | |
| 1321 | "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", | |
| 1322 | "license": "MIT", | |
| 1323 | "dependencies": { | |
| 1324 | "@types/estree": "*" | |
| 1325 | } | |
| 1326 | }, | |
| 1327 | "node_modules/@types/hast": { | |
| 1328 | "version": "3.0.4", | |
| 1329 | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", | |
| 1330 | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", | |
| 1331 | "license": "MIT", | |
| 1332 | "dependencies": { | |
| 1333 | "@types/unist": "*" | |
| 1334 | } | |
| 1335 | }, | |
| 1336 | "node_modules/@types/mdast": { | |
| 1337 | "version": "4.0.4", | |
| 1338 | "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", | |
| 1339 | "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", | |
| 1340 | "license": "MIT", | |
| 1341 | "dependencies": { | |
| 1342 | "@types/unist": "*" | |
| 1343 | } | |
| 1344 | }, | |
| 1345 | "node_modules/@types/mdx": { | |
| 1346 | "version": "2.0.13", | |
| 1347 | "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", | |
| 1348 | "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", | |
| 1349 | "license": "MIT" | |
| 1350 | }, | |
| 1351 | "node_modules/@types/ms": { | |
| 1352 | "version": "2.1.0", | |
| 1353 | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", | |
| 1354 | "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", | |
| 1355 | "license": "MIT" | |
| 1356 | }, | |
| 1357 | "node_modules/@types/node": { | |
| 1358 | "version": "24.0.10", | |
| 1359 | "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", | |
| 1360 | "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", | |
| 1361 | "devOptional": true, | |
| 1362 | "license": "MIT", | |
| 1363 | "dependencies": { | |
| 1364 | "undici-types": "~7.8.0" | |
| 1365 | } | |
| 1366 | }, | |
| 1367 | "node_modules/@types/unist": { | |
| 1368 | "version": "3.0.3", | |
| 1369 | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", | |
| 1370 | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", | |
| 1371 | "license": "MIT" | |
| 1372 | }, | |
| 1373 | "node_modules/@types/yauzl": { | |
| 1374 | "version": "2.10.3", | |
| 1375 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", | |
| 1376 | "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", | |
| 1377 | "license": "MIT", | |
| 1378 | "optional": true, | |
| 1379 | "dependencies": { | |
| 1380 | "@types/node": "*" | |
| 1381 | } | |
| 1382 | }, | |
| 1383 | "node_modules/@ungap/structured-clone": { | |
| 1384 | "version": "1.3.0", | |
| 1385 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", | |
| 1386 | "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", | |
| 1387 | "license": "ISC" | |
| 1388 | }, | |
| 1389 | "node_modules/acorn": { | |
| 1390 | "version": "8.15.0", | |
| 1391 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", | |
| 1392 | "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", | |
| 1393 | "license": "MIT", | |
| 1394 | "peer": true, | |
| 1395 | "bin": { | |
| 1396 | "acorn": "bin/acorn" | |
| 1397 | }, | |
| 1398 | "engines": { | |
| 1399 | "node": ">=0.4.0" | |
| 1400 | } | |
| 1401 | }, | |
| 1402 | "node_modules/acorn-jsx": { | |
| 1403 | "version": "5.3.2", | |
| 1404 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", | |
| 1405 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", | |
| 1406 | "license": "MIT", | |
| 1407 | "peerDependencies": { | |
| 1408 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" | |
| 785 | "node_modules/@types/yauzl": { | |
| 786 | "version": "2.10.3", | |
| 787 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", | |
| 788 | "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", | |
| 789 | "license": "MIT", | |
| 790 | "optional": true, | |
| 791 | "dependencies": { | |
| 792 | "@types/node": "*" | |
| 1409 | 793 | } |
| 1410 | 794 | }, |
| 1411 | 795 | "node_modules/agent-base": { |
| ... | ... | @@ -1441,12 +825,33 @@ |
| 1441 | 825 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" |
| 1442 | 826 | } |
| 1443 | 827 | }, |
| 828 | "node_modules/app-module-path": { | |
| 829 | "version": "2.2.0", | |
| 830 | "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", | |
| 831 | "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", | |
| 832 | "license": "BSD-2-Clause" | |
| 833 | }, | |
| 834 | "node_modules/argly": { | |
| 835 | "version": "1.2.0", | |
| 836 | "resolved": "https://registry.npmjs.org/argly/-/argly-1.2.0.tgz", | |
| 837 | "integrity": "sha512-+F3InkcH2XOGK7Jf/ZQis4cwZ4wbfmpBKo5J+SAA9bglT1gVd0e9hroHWarU076pJrAfs8JKuRPwDqwPBOKHnw==", | |
| 838 | "license": "MIT" | |
| 839 | }, | |
| 1444 | 840 | "node_modules/argparse": { |
| 1445 | 841 | "version": "2.0.1", |
| 1446 | 842 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", |
| 1447 | 843 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", |
| 1448 | 844 | "license": "Python-2.0" |
| 1449 | 845 | }, |
| 846 | "node_modules/assertion-error": { | |
| 847 | "version": "1.1.0", | |
| 848 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", | |
| 849 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", | |
| 850 | "license": "MIT", | |
| 851 | "engines": { | |
| 852 | "node": "*" | |
| 853 | } | |
| 854 | }, | |
| 1450 | 855 | "node_modules/ast-types": { |
| 1451 | 856 | "version": "0.13.4", |
| 1452 | 857 | "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", |
| ... | ... | @@ -1459,15 +864,6 @@ |
| 1459 | 864 | "node": ">=4" |
| 1460 | 865 | } |
| 1461 | 866 | }, |
| 1462 | "node_modules/astring": { | |
| 1463 | "version": "1.9.0", | |
| 1464 | "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", | |
| 1465 | "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", | |
| 1466 | "license": "MIT", | |
| 1467 | "bin": { | |
| 1468 | "astring": "bin/astring" | |
| 1469 | } | |
| 1470 | }, | |
| 1471 | 867 | "node_modules/b4a": { |
| 1472 | 868 | "version": "1.7.3", |
| 1473 | 869 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", |
| ... | ... | @@ -1482,15 +878,11 @@ |
| 1482 | 878 | } |
| 1483 | 879 | } |
| 1484 | 880 | }, |
| 1485 | "node_modules/bail": { | |
| 1486 | "version": "2.0.2", | |
| 1487 | "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", | |
| 1488 | "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", | |
| 1489 | "license": "MIT", | |
| 1490 | "funding": { | |
| 1491 | "type": "github", | |
| 1492 | "url": "https://github.com/sponsors/wooorm" | |
| 1493 | } | |
| 881 | "node_modules/balanced-match": { | |
| 882 | "version": "1.0.2", | |
| 883 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | |
| 884 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", | |
| 885 | "license": "MIT" | |
| 1494 | 886 | }, |
| 1495 | 887 | "node_modules/bare-events": { |
| 1496 | 888 | "version": "2.7.0", |
| ... | ... | @@ -1576,9 +968,9 @@ |
| 1576 | 968 | } |
| 1577 | 969 | }, |
| 1578 | 970 | "node_modules/baseline-browser-mapping": { |
| 1579 | "version": "2.9.14", | |
| 1580 | "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", | |
| 1581 | "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", | |
| 971 | "version": "2.9.19", | |
| 972 | "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", | |
| 973 | "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", | |
| 1582 | 974 | "license": "Apache-2.0", |
| 1583 | 975 | "bin": { |
| 1584 | 976 | "baseline-browser-mapping": "dist/cli.js" |
| ... | ... | @@ -1632,10 +1024,25 @@ |
| 1632 | 1024 | "url": "https://github.com/sponsors/wooorm" |
| 1633 | 1025 | } |
| 1634 | 1026 | }, |
| 1027 | "node_modules/brace-expansion": { | |
| 1028 | "version": "2.0.2", | |
| 1029 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | |
| 1030 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | |
| 1031 | "license": "MIT", | |
| 1032 | "dependencies": { | |
| 1033 | "balanced-match": "^1.0.0" | |
| 1034 | } | |
| 1035 | }, | |
| 1036 | "node_modules/browser-refresh-client": { | |
| 1037 | "version": "1.1.4", | |
| 1038 | "resolved": "https://registry.npmjs.org/browser-refresh-client/-/browser-refresh-client-1.1.4.tgz", | |
| 1039 | "integrity": "sha512-FM/UzMFsG7wJ1ocxCSl6U7qGAIWASEk+tlvfJLP2Pd1JfS4kQ1r4d5f+nNmQI8fB8sXSD8+u/mWErEkAMxUu3w==", | |
| 1040 | "license": "MIT" | |
| 1041 | }, | |
| 1635 | 1042 | "node_modules/browserslist": { |
| 1636 | "version": "4.26.3", | |
| 1637 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", | |
| 1638 | "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", | |
| 1043 | "version": "4.28.1", | |
| 1044 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", | |
| 1045 | "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", | |
| 1639 | 1046 | "funding": [ |
| 1640 | 1047 | { |
| 1641 | 1048 | "type": "opencollective", |
| ... | ... | @@ -1653,11 +1060,11 @@ |
| 1653 | 1060 | "license": "MIT", |
| 1654 | 1061 | "peer": true, |
| 1655 | 1062 | "dependencies": { |
| 1656 | "baseline-browser-mapping": "^2.8.9", | |
| 1657 | "caniuse-lite": "^1.0.30001746", | |
| 1658 | "electron-to-chromium": "^1.5.227", | |
| 1659 | "node-releases": "^2.0.21", | |
| 1660 | "update-browserslist-db": "^1.1.3" | |
| 1063 | "baseline-browser-mapping": "^2.9.0", | |
| 1064 | "caniuse-lite": "^1.0.30001759", | |
| 1065 | "electron-to-chromium": "^1.5.263", | |
| 1066 | "node-releases": "^2.0.27", | |
| 1067 | "update-browserslist-db": "^1.2.0" | |
| 1661 | 1068 | }, |
| 1662 | 1069 | "bin": { |
| 1663 | 1070 | "browserslist": "cli.js" |
| ... | ... | @@ -1691,9 +1098,9 @@ |
| 1691 | 1098 | } |
| 1692 | 1099 | }, |
| 1693 | 1100 | "node_modules/caniuse-lite": { |
| 1694 | "version": "1.0.30001747", | |
| 1695 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz", | |
| 1696 | "integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==", | |
| 1101 | "version": "1.0.30001770", | |
| 1102 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", | |
| 1103 | "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", | |
| 1697 | 1104 | "funding": [ |
| 1698 | 1105 | { |
| 1699 | 1106 | "type": "opencollective", |
| ... | ... | @@ -1710,54 +1117,18 @@ |
| 1710 | 1117 | ], |
| 1711 | 1118 | "license": "CC-BY-4.0" |
| 1712 | 1119 | }, |
| 1713 | "node_modules/ccount": { | |
| 1714 | "version": "2.0.1", | |
| 1715 | "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", | |
| 1716 | "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", | |
| 1717 | "license": "MIT", | |
| 1718 | "funding": { | |
| 1719 | "type": "github", | |
| 1720 | "url": "https://github.com/sponsors/wooorm" | |
| 1721 | } | |
| 1722 | }, | |
| 1723 | "node_modules/character-entities": { | |
| 1724 | "version": "2.0.2", | |
| 1725 | "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", | |
| 1726 | "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", | |
| 1727 | "license": "MIT", | |
| 1728 | "funding": { | |
| 1729 | "type": "github", | |
| 1730 | "url": "https://github.com/sponsors/wooorm" | |
| 1731 | } | |
| 1732 | }, | |
| 1733 | "node_modules/character-entities-html4": { | |
| 1734 | "version": "2.1.0", | |
| 1735 | "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", | |
| 1736 | "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", | |
| 1737 | "license": "MIT", | |
| 1738 | "funding": { | |
| 1739 | "type": "github", | |
| 1740 | "url": "https://github.com/sponsors/wooorm" | |
| 1741 | } | |
| 1742 | }, | |
| 1743 | "node_modules/character-entities-legacy": { | |
| 1744 | "version": "3.0.0", | |
| 1745 | "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", | |
| 1746 | "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", | |
| 1747 | "license": "MIT", | |
| 1748 | "funding": { | |
| 1749 | "type": "github", | |
| 1750 | "url": "https://github.com/sponsors/wooorm" | |
| 1751 | } | |
| 1752 | }, | |
| 1753 | "node_modules/character-reference-invalid": { | |
| 1754 | "version": "2.0.1", | |
| 1755 | "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", | |
| 1756 | "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", | |
| 1120 | "node_modules/chai": { | |
| 1121 | "version": "3.5.0", | |
| 1122 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", | |
| 1123 | "integrity": "sha512-eRYY0vPS2a9zt5w5Z0aCeWbrXTEyvk7u/Xf71EzNObrjSCPgMm1Nku/D/u2tiqHBX5j40wWhj54YJLtgn8g55A==", | |
| 1757 | 1124 | "license": "MIT", |
| 1758 | "funding": { | |
| 1759 | "type": "github", | |
| 1760 | "url": "https://github.com/sponsors/wooorm" | |
| 1125 | "dependencies": { | |
| 1126 | "assertion-error": "^1.0.1", | |
| 1127 | "deep-eql": "^0.1.3", | |
| 1128 | "type-detect": "^1.0.0" | |
| 1129 | }, | |
| 1130 | "engines": { | |
| 1131 | "node": ">= 0.4.0" | |
| 1761 | 1132 | } |
| 1762 | 1133 | }, |
| 1763 | 1134 | "node_modules/chromium-bidi": { |
| ... | ... | @@ -1808,16 +1179,6 @@ |
| 1808 | 1179 | "@codemirror/view": "^6.0.0" |
| 1809 | 1180 | } |
| 1810 | 1181 | }, |
| 1811 | "node_modules/collapse-white-space": { | |
| 1812 | "version": "2.1.0", | |
| 1813 | "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", | |
| 1814 | "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", | |
| 1815 | "license": "MIT", | |
| 1816 | "funding": { | |
| 1817 | "type": "github", | |
| 1818 | "url": "https://github.com/sponsors/wooorm" | |
| 1819 | } | |
| 1820 | }, | |
| 1821 | 1182 | "node_modules/color-convert": { |
| 1822 | 1183 | "version": "2.0.1", |
| 1823 | 1184 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", |
| ... | ... | @@ -1836,16 +1197,6 @@ |
| 1836 | 1197 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |
| 1837 | 1198 | "license": "MIT" |
| 1838 | 1199 | }, |
| 1839 | "node_modules/comma-separated-tokens": { | |
| 1840 | "version": "2.0.3", | |
| 1841 | "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", | |
| 1842 | "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", | |
| 1843 | "license": "MIT", | |
| 1844 | "funding": { | |
| 1845 | "type": "github", | |
| 1846 | "url": "https://github.com/sponsors/wooorm" | |
| 1847 | } | |
| 1848 | }, | |
| 1849 | 1200 | "node_modules/complain": { |
| 1850 | 1201 | "version": "1.6.1", |
| 1851 | 1202 | "resolved": "https://registry.npmjs.org/complain/-/complain-1.6.1.tgz", |
| ... | ... | @@ -1855,12 +1206,6 @@ |
| 1855 | 1206 | "error-stack-parser": "^2.0.1" |
| 1856 | 1207 | } |
| 1857 | 1208 | }, |
| 1858 | "node_modules/convert-source-map": { | |
| 1859 | "version": "2.0.0", | |
| 1860 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 1861 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 1862 | "license": "MIT" | |
| 1863 | }, | |
| 1864 | 1209 | "node_modules/cosmiconfig": { |
| 1865 | 1210 | "version": "9.0.0", |
| 1866 | 1211 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", |
| ... | ... | @@ -1894,24 +1239,18 @@ |
| 1894 | 1239 | "license": "MIT" |
| 1895 | 1240 | }, |
| 1896 | 1241 | "node_modules/csstype": { |
| 1897 | "version": "3.1.3", | |
| 1898 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | |
| 1899 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", | |
| 1242 | "version": "3.2.3", | |
| 1243 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", | |
| 1244 | "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", | |
| 1900 | 1245 | "license": "MIT" |
| 1901 | 1246 | }, |
| 1902 | 1247 | "node_modules/dashjs": { |
| 1903 | "version": "5.1.1", | |
| 1904 | "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.1.1.tgz", | |
| 1905 | "integrity": "sha512-BzNXlUgzEjhuZ5M5hlSp1qIyQHZ7NpXAR0loP9DAAFVZj/ntL1DHeZ7qp/L3bvI4rq50X5indkAZQ3zEHWJoCA==", | |
| 1248 | "version": "5.0.3", | |
| 1249 | "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.0.3.tgz", | |
| 1250 | "integrity": "sha512-TXndNnCUjFjF2nYBxDVba+hWRpVkadkQ8flLp7kHkem+5+wZTfRShJCnVkPUosmjS0YPE9fVNLbYPJxHBeQZvA==", | |
| 1906 | 1251 | "license": "BSD-3-Clause", |
| 1907 | 1252 | "dependencies": { |
| 1908 | "@svta/cml-608": "1.0.1", | |
| 1909 | "@svta/cml-cmcd": "1.0.1", | |
| 1910 | "@svta/cml-cmsd": "1.0.1", | |
| 1911 | "@svta/cml-dash": "1.0.1", | |
| 1912 | "@svta/cml-id3": "1.0.1", | |
| 1913 | "@svta/cml-request": "1.0.1", | |
| 1914 | "@svta/cml-xml": "1.0.1", | |
| 1253 | "@svta/common-media-library": "^0.12.4", | |
| 1915 | 1254 | "bcp-47-match": "^2.0.3", |
| 1916 | 1255 | "bcp-47-normalize": "^2.3.0", |
| 1917 | 1256 | "codem-isoboxer": "0.3.10", |
| ... | ... | @@ -1949,17 +1288,25 @@ |
| 1949 | 1288 | } |
| 1950 | 1289 | } |
| 1951 | 1290 | }, |
| 1952 | "node_modules/decode-named-character-reference": { | |
| 1953 | "version": "1.2.0", | |
| 1954 | "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", | |
| 1955 | "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", | |
| 1291 | "node_modules/deep-eql": { | |
| 1292 | "version": "0.1.3", | |
| 1293 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", | |
| 1294 | "integrity": "sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==", | |
| 1956 | 1295 | "license": "MIT", |
| 1957 | 1296 | "dependencies": { |
| 1958 | "character-entities": "^2.0.0" | |
| 1297 | "type-detect": "0.1.1" | |
| 1959 | 1298 | }, |
| 1960 | "funding": { | |
| 1961 | "type": "github", | |
| 1962 | "url": "https://github.com/sponsors/wooorm" | |
| 1299 | "engines": { | |
| 1300 | "node": "*" | |
| 1301 | } | |
| 1302 | }, | |
| 1303 | "node_modules/deep-eql/node_modules/type-detect": { | |
| 1304 | "version": "0.1.1", | |
| 1305 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", | |
| 1306 | "integrity": "sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==", | |
| 1307 | "license": "MIT", | |
| 1308 | "engines": { | |
| 1309 | "node": "*" | |
| 1963 | 1310 | } |
| 1964 | 1311 | }, |
| 1965 | 1312 | "node_modules/degenerator": { |
| ... | ... | @@ -1976,15 +1323,6 @@ |
| 1976 | 1323 | "node": ">= 14" |
| 1977 | 1324 | } |
| 1978 | 1325 | }, |
| 1979 | "node_modules/dequal": { | |
| 1980 | "version": "2.0.3", | |
| 1981 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", | |
| 1982 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", | |
| 1983 | "license": "MIT", | |
| 1984 | "engines": { | |
| 1985 | "node": ">=6" | |
| 1986 | } | |
| 1987 | }, | |
| 1988 | 1326 | "node_modules/detect-libc": { |
| 1989 | 1327 | "version": "2.0.4", |
| 1990 | 1328 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", |
| ... | ... | @@ -2001,19 +1339,6 @@ |
| 2001 | 1339 | "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", |
| 2002 | 1340 | "license": "MIT" |
| 2003 | 1341 | }, |
| 2004 | "node_modules/devlop": { | |
| 2005 | "version": "1.1.0", | |
| 2006 | "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", | |
| 2007 | "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", | |
| 2008 | "license": "MIT", | |
| 2009 | "dependencies": { | |
| 2010 | "dequal": "^2.0.0" | |
| 2011 | }, | |
| 2012 | "funding": { | |
| 2013 | "type": "github", | |
| 2014 | "url": "https://github.com/sponsors/wooorm" | |
| 2015 | } | |
| 2016 | }, | |
| 2017 | 1342 | "node_modules/devtools-protocol": { |
| 2018 | 1343 | "version": "0.0.1508733", |
| 2019 | 1344 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz", |
| ... | ... | @@ -2022,9 +1347,9 @@ |
| 2022 | 1347 | "peer": true |
| 2023 | 1348 | }, |
| 2024 | 1349 | "node_modules/electron-to-chromium": { |
| 2025 | "version": "1.5.229", | |
| 2026 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.229.tgz", | |
| 2027 | "integrity": "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg==", | |
| 1350 | "version": "1.5.286", | |
| 1351 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", | |
| 1352 | "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", | |
| 2028 | 1353 | "license": "ISC" |
| 2029 | 1354 | }, |
| 2030 | 1355 | "node_modules/emoji-regex": { |
| ... | ... | @@ -2069,44 +1394,13 @@ |
| 2069 | 1394 | "stackframe": "^1.3.4" |
| 2070 | 1395 | } |
| 2071 | 1396 | }, |
| 2072 | "node_modules/esast-util-from-estree": { | |
| 2073 | "version": "2.0.0", | |
| 2074 | "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", | |
| 2075 | "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", | |
| 2076 | "license": "MIT", | |
| 2077 | "dependencies": { | |
| 2078 | "@types/estree-jsx": "^1.0.0", | |
| 2079 | "devlop": "^1.0.0", | |
| 2080 | "estree-util-visit": "^2.0.0", | |
| 2081 | "unist-util-position-from-estree": "^2.0.0" | |
| 2082 | }, | |
| 2083 | "funding": { | |
| 2084 | "type": "opencollective", | |
| 2085 | "url": "https://opencollective.com/unified" | |
| 2086 | } | |
| 2087 | }, | |
| 2088 | "node_modules/esast-util-from-js": { | |
| 2089 | "version": "2.0.1", | |
| 2090 | "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", | |
| 2091 | "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", | |
| 2092 | "license": "MIT", | |
| 2093 | "dependencies": { | |
| 2094 | "@types/estree-jsx": "^1.0.0", | |
| 2095 | "acorn": "^8.0.0", | |
| 2096 | "esast-util-from-estree": "^2.0.0", | |
| 2097 | "vfile-message": "^4.0.0" | |
| 2098 | }, | |
| 2099 | "funding": { | |
| 2100 | "type": "opencollective", | |
| 2101 | "url": "https://opencollective.com/unified" | |
| 2102 | } | |
| 2103 | }, | |
| 2104 | 1397 | "node_modules/esbuild": { |
| 2105 | 1398 | "version": "0.25.5", |
| 2106 | 1399 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", |
| 2107 | 1400 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", |
| 2108 | 1401 | "hasInstallScript": true, |
| 2109 | 1402 | "license": "MIT", |
| 1403 | "peer": true, | |
| 2110 | 1404 | "bin": { |
| 2111 | 1405 | "esbuild": "bin/esbuild" |
| 2112 | 1406 | }, |
| ... | ... | @@ -2203,116 +1497,22 @@ |
| 2203 | 1497 | "node": ">=4.0" |
| 2204 | 1498 | } |
| 2205 | 1499 | }, |
| 2206 | "node_modules/estree-util-attach-comments": { | |
| 2207 | "version": "3.0.0", | |
| 2208 | "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", | |
| 2209 | "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", | |
| 2210 | "license": "MIT", | |
| 2211 | "dependencies": { | |
| 2212 | "@types/estree": "^1.0.0" | |
| 2213 | }, | |
| 2214 | "funding": { | |
| 2215 | "type": "opencollective", | |
| 2216 | "url": "https://opencollective.com/unified" | |
| 1500 | "node_modules/esutils": { | |
| 1501 | "version": "2.0.3", | |
| 1502 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | |
| 1503 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", | |
| 1504 | "license": "BSD-2-Clause", | |
| 1505 | "engines": { | |
| 1506 | "node": ">=0.10.0" | |
| 2217 | 1507 | } |
| 2218 | 1508 | }, |
| 2219 | "node_modules/estree-util-build-jsx": { | |
| 2220 | "version": "3.0.1", | |
| 2221 | "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", | |
| 2222 | "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", | |
| 1509 | "node_modules/events-light": { | |
| 1510 | "version": "1.0.5", | |
| 1511 | "resolved": "https://registry.npmjs.org/events-light/-/events-light-1.0.5.tgz", | |
| 1512 | "integrity": "sha512-jF51LJzg5W+tkJgfZbjlbFCLcyVFEtOjU+xMCBylrXG13X5XHvfp6lNGfyBLF9u1mRTpUsMVYqSDukvpZff1mQ==", | |
| 2223 | 1513 | "license": "MIT", |
| 2224 | 1514 | "dependencies": { |
| 2225 | "@types/estree-jsx": "^1.0.0", | |
| 2226 | "devlop": "^1.0.0", | |
| 2227 | "estree-util-is-identifier-name": "^3.0.0", | |
| 2228 | "estree-walker": "^3.0.0" | |
| 2229 | }, | |
| 2230 | "funding": { | |
| 2231 | "type": "opencollective", | |
| 2232 | "url": "https://opencollective.com/unified" | |
| 2233 | } | |
| 2234 | }, | |
| 2235 | "node_modules/estree-util-is-identifier-name": { | |
| 2236 | "version": "3.0.0", | |
| 2237 | "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", | |
| 2238 | "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", | |
| 2239 | "license": "MIT", | |
| 2240 | "funding": { | |
| 2241 | "type": "opencollective", | |
| 2242 | "url": "https://opencollective.com/unified" | |
| 2243 | } | |
| 2244 | }, | |
| 2245 | "node_modules/estree-util-scope": { | |
| 2246 | "version": "1.0.0", | |
| 2247 | "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", | |
| 2248 | "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", | |
| 2249 | "license": "MIT", | |
| 2250 | "dependencies": { | |
| 2251 | "@types/estree": "^1.0.0", | |
| 2252 | "devlop": "^1.0.0" | |
| 2253 | }, | |
| 2254 | "funding": { | |
| 2255 | "type": "opencollective", | |
| 2256 | "url": "https://opencollective.com/unified" | |
| 2257 | } | |
| 2258 | }, | |
| 2259 | "node_modules/estree-util-to-js": { | |
| 2260 | "version": "2.0.0", | |
| 2261 | "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", | |
| 2262 | "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", | |
| 2263 | "license": "MIT", | |
| 2264 | "dependencies": { | |
| 2265 | "@types/estree-jsx": "^1.0.0", | |
| 2266 | "astring": "^1.8.0", | |
| 2267 | "source-map": "^0.7.0" | |
| 2268 | }, | |
| 2269 | "funding": { | |
| 2270 | "type": "opencollective", | |
| 2271 | "url": "https://opencollective.com/unified" | |
| 2272 | } | |
| 2273 | }, | |
| 2274 | "node_modules/estree-util-value-to-estree": { | |
| 2275 | "version": "3.4.1", | |
| 2276 | "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.1.tgz", | |
| 2277 | "integrity": "sha512-E4fEc8KLhDXnbyDa5XrbdT9PbgSMt0AGZPFUsGFok8N2Q7DTO+F6xAFJjIdw71EkidRg186I1mQCKzZ1ZbEsCw==", | |
| 2278 | "license": "MIT", | |
| 2279 | "dependencies": { | |
| 2280 | "@types/estree": "^1.0.0" | |
| 2281 | }, | |
| 2282 | "funding": { | |
| 2283 | "url": "https://github.com/sponsors/remcohaszing" | |
| 2284 | } | |
| 2285 | }, | |
| 2286 | "node_modules/estree-util-visit": { | |
| 2287 | "version": "2.0.0", | |
| 2288 | "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", | |
| 2289 | "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", | |
| 2290 | "license": "MIT", | |
| 2291 | "dependencies": { | |
| 2292 | "@types/estree-jsx": "^1.0.0", | |
| 2293 | "@types/unist": "^3.0.0" | |
| 2294 | }, | |
| 2295 | "funding": { | |
| 2296 | "type": "opencollective", | |
| 2297 | "url": "https://opencollective.com/unified" | |
| 2298 | } | |
| 2299 | }, | |
| 2300 | "node_modules/estree-walker": { | |
| 2301 | "version": "3.0.3", | |
| 2302 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", | |
| 2303 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", | |
| 2304 | "license": "MIT", | |
| 2305 | "dependencies": { | |
| 2306 | "@types/estree": "^1.0.0" | |
| 2307 | } | |
| 2308 | }, | |
| 2309 | "node_modules/esutils": { | |
| 2310 | "version": "2.0.3", | |
| 2311 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | |
| 2312 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", | |
| 2313 | "license": "BSD-2-Clause", | |
| 2314 | "engines": { | |
| 2315 | "node": ">=0.10.0" | |
| 1515 | "chai": "^3.5.0" | |
| 2316 | 1516 | } |
| 2317 | 1517 | }, |
| 2318 | 1518 | "node_modules/events-universal": { |
| ... | ... | @@ -2324,12 +1524,6 @@ |
| 2324 | 1524 | "bare-events": "^2.7.0" |
| 2325 | 1525 | } |
| 2326 | 1526 | }, |
| 2327 | "node_modules/extend": { | |
| 2328 | "version": "3.0.2", | |
| 2329 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", | |
| 2330 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", | |
| 2331 | "license": "MIT" | |
| 2332 | }, | |
| 2333 | 1527 | "node_modules/extract-zip": { |
| 2334 | 1528 | "version": "2.0.1", |
| 2335 | 1529 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", |
| ... | ... | @@ -2371,15 +1565,6 @@ |
| 2371 | 1565 | "pend": "~1.2.0" |
| 2372 | 1566 | } |
| 2373 | 1567 | }, |
| 2374 | "node_modules/gensync": { | |
| 2375 | "version": "1.0.0-beta.2", | |
| 2376 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", | |
| 2377 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", | |
| 2378 | "license": "MIT", | |
| 2379 | "engines": { | |
| 2380 | "node": ">=6.9.0" | |
| 2381 | } | |
| 2382 | }, | |
| 2383 | 1568 | "node_modules/get-caller-file": { |
| 2384 | 1569 | "version": "2.0.5", |
| 2385 | 1570 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", |
| ... | ... | @@ -2418,93 +1603,6 @@ |
| 2418 | 1603 | "node": ">= 14" |
| 2419 | 1604 | } |
| 2420 | 1605 | }, |
| 2421 | "node_modules/hast-util-properties-to-mdx-jsx-attributes": { | |
| 2422 | "version": "1.0.1", | |
| 2423 | "resolved": "https://registry.npmjs.org/hast-util-properties-to-mdx-jsx-attributes/-/hast-util-properties-to-mdx-jsx-attributes-1.0.1.tgz", | |
| 2424 | "integrity": "sha512-ZzxhjHZ+gyxaPIFp/nuRpVL4GIFoqzfH6vNgjaA3CuUAV6XCxYwAQfRczrZRkgL6msi6DdOl+/QEduOdzszvbg==", | |
| 2425 | "license": "MIT", | |
| 2426 | "dependencies": { | |
| 2427 | "@types/estree": "^1.0.0", | |
| 2428 | "@types/hast": "^3.0.0", | |
| 2429 | "comma-separated-tokens": "^2.0.0", | |
| 2430 | "estree-util-value-to-estree": "^3.0.0", | |
| 2431 | "mdast-util-mdx-jsx": "^3.0.0", | |
| 2432 | "property-information": "^7.0.0", | |
| 2433 | "space-separated-tokens": "^2.0.0", | |
| 2434 | "style-to-js": "^1.0.0" | |
| 2435 | }, | |
| 2436 | "funding": { | |
| 2437 | "url": "https://github.com/sponsors/remcohaszing" | |
| 2438 | } | |
| 2439 | }, | |
| 2440 | "node_modules/hast-util-to-estree": { | |
| 2441 | "version": "3.1.3", | |
| 2442 | "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", | |
| 2443 | "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", | |
| 2444 | "license": "MIT", | |
| 2445 | "dependencies": { | |
| 2446 | "@types/estree": "^1.0.0", | |
| 2447 | "@types/estree-jsx": "^1.0.0", | |
| 2448 | "@types/hast": "^3.0.0", | |
| 2449 | "comma-separated-tokens": "^2.0.0", | |
| 2450 | "devlop": "^1.0.0", | |
| 2451 | "estree-util-attach-comments": "^3.0.0", | |
| 2452 | "estree-util-is-identifier-name": "^3.0.0", | |
| 2453 | "hast-util-whitespace": "^3.0.0", | |
| 2454 | "mdast-util-mdx-expression": "^2.0.0", | |
| 2455 | "mdast-util-mdx-jsx": "^3.0.0", | |
| 2456 | "mdast-util-mdxjs-esm": "^2.0.0", | |
| 2457 | "property-information": "^7.0.0", | |
| 2458 | "space-separated-tokens": "^2.0.0", | |
| 2459 | "style-to-js": "^1.0.0", | |
| 2460 | "unist-util-position": "^5.0.0", | |
| 2461 | "zwitch": "^2.0.0" | |
| 2462 | }, | |
| 2463 | "funding": { | |
| 2464 | "type": "opencollective", | |
| 2465 | "url": "https://opencollective.com/unified" | |
| 2466 | } | |
| 2467 | }, | |
| 2468 | "node_modules/hast-util-to-jsx-runtime": { | |
| 2469 | "version": "2.3.6", | |
| 2470 | "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", | |
| 2471 | "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", | |
| 2472 | "license": "MIT", | |
| 2473 | "dependencies": { | |
| 2474 | "@types/estree": "^1.0.0", | |
| 2475 | "@types/hast": "^3.0.0", | |
| 2476 | "@types/unist": "^3.0.0", | |
| 2477 | "comma-separated-tokens": "^2.0.0", | |
| 2478 | "devlop": "^1.0.0", | |
| 2479 | "estree-util-is-identifier-name": "^3.0.0", | |
| 2480 | "hast-util-whitespace": "^3.0.0", | |
| 2481 | "mdast-util-mdx-expression": "^2.0.0", | |
| 2482 | "mdast-util-mdx-jsx": "^3.0.0", | |
| 2483 | "mdast-util-mdxjs-esm": "^2.0.0", | |
| 2484 | "property-information": "^7.0.0", | |
| 2485 | "space-separated-tokens": "^2.0.0", | |
| 2486 | "style-to-js": "^1.0.0", | |
| 2487 | "unist-util-position": "^5.0.0", | |
| 2488 | "vfile-message": "^4.0.0" | |
| 2489 | }, | |
| 2490 | "funding": { | |
| 2491 | "type": "opencollective", | |
| 2492 | "url": "https://opencollective.com/unified" | |
| 2493 | } | |
| 2494 | }, | |
| 2495 | "node_modules/hast-util-whitespace": { | |
| 2496 | "version": "3.0.0", | |
| 2497 | "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", | |
| 2498 | "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", | |
| 2499 | "license": "MIT", | |
| 2500 | "dependencies": { | |
| 2501 | "@types/hast": "^3.0.0" | |
| 2502 | }, | |
| 2503 | "funding": { | |
| 2504 | "type": "opencollective", | |
| 2505 | "url": "https://opencollective.com/unified" | |
| 2506 | } | |
| 2507 | }, | |
| 2508 | 1606 | "node_modules/he": { |
| 2509 | 1607 | "version": "1.2.0", |
| 2510 | 1608 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", |
| ... | ... | @@ -2521,9 +1619,9 @@ |
| 2521 | 1619 | "license": "Apache-2.0" |
| 2522 | 1620 | }, |
| 2523 | 1621 | "node_modules/hono": { |
| 2524 | "version": "4.11.7", | |
| 2525 | "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", | |
| 2526 | "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", | |
| 1622 | "version": "4.11.9", | |
| 1623 | "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", | |
| 1624 | "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", | |
| 2527 | 1625 | "license": "MIT", |
| 2528 | 1626 | "peer": true, |
| 2529 | 1627 | "engines": { |
| ... | ... | @@ -2547,9 +1645,9 @@ |
| 2547 | 1645 | "license": "MIT" |
| 2548 | 1646 | }, |
| 2549 | 1647 | "node_modules/htmljs-parser": { |
| 2550 | "version": "5.7.4", | |
| 2551 | "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.7.4.tgz", | |
| 2552 | "integrity": "sha512-PCGI7/Z10oBkpKKqbQ9Wgd0GZjSQ/dV7QlBjOmaqD1FkBsHWk730BW9mJCbnXfRbtZCdLh8M8IA8YeKPu9tGaQ==", | |
| 1648 | "version": "5.10.0", | |
| 1649 | "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.10.0.tgz", | |
| 1650 | "integrity": "sha512-5aAmTYhhiyBaBArw51R9c1m5BxqN3QBRpvhKRanWwTW1I59Y9YHvfGOBU12dfy7dPqgkwFKCUh0LX+ia34HHFw==", | |
| 2553 | 1651 | "license": "MIT" |
| 2554 | 1652 | }, |
| 2555 | 1653 | "node_modules/http-proxy-agent": { |
| ... | ... | @@ -2618,12 +1716,6 @@ |
| 2618 | 1716 | "sax": "1.2.1" |
| 2619 | 1717 | } |
| 2620 | 1718 | }, |
| 2621 | "node_modules/inline-style-parser": { | |
| 2622 | "version": "0.2.4", | |
| 2623 | "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", | |
| 2624 | "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", | |
| 2625 | "license": "MIT" | |
| 2626 | }, | |
| 2627 | 1719 | "node_modules/ip-address": { |
| 2628 | 1720 | "version": "10.0.1", |
| 2629 | 1721 | "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", |
| ... | ... | @@ -2682,28 +1774,6 @@ |
| 2682 | 1774 | "node": ">=8" |
| 2683 | 1775 | } |
| 2684 | 1776 | }, |
| 2685 | "node_modules/is-hexadecimal": { | |
| 2686 | "version": "2.0.1", | |
| 2687 | "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", | |
| 2688 | "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", | |
| 2689 | "license": "MIT", | |
| 2690 | "funding": { | |
| 2691 | "type": "github", | |
| 2692 | "url": "https://github.com/sponsors/wooorm" | |
| 2693 | } | |
| 2694 | }, | |
| 2695 | "node_modules/is-plain-obj": { | |
| 2696 | "version": "4.1.0", | |
| 2697 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", | |
| 2698 | "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", | |
| 2699 | "license": "MIT", | |
| 2700 | "engines": { | |
| 2701 | "node": ">=12" | |
| 2702 | }, | |
| 2703 | "funding": { | |
| 2704 | "url": "https://github.com/sponsors/sindresorhus" | |
| 2705 | } | |
| 2706 | }, | |
| 2707 | 1777 | "node_modules/js-tokens": { |
| 2708 | 1778 | "version": "4.0.0", |
| 2709 | 1779 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", |
| ... | ... | @@ -2740,18 +1810,6 @@ |
| 2740 | 1810 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", |
| 2741 | 1811 | "license": "MIT" |
| 2742 | 1812 | }, |
| 2743 | "node_modules/json5": { | |
| 2744 | "version": "2.2.3", | |
| 2745 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", | |
| 2746 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", | |
| 2747 | "license": "MIT", | |
| 2748 | "bin": { | |
| 2749 | "json5": "lib/cli.js" | |
| 2750 | }, | |
| 2751 | "engines": { | |
| 2752 | "node": ">=6" | |
| 2753 | } | |
| 2754 | }, | |
| 2755 | 1813 | "node_modules/kleur": { |
| 2756 | 1814 | "version": "4.1.5", |
| 2757 | 1815 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", |
| ... | ... | @@ -2798,834 +1856,73 @@ |
| 2798 | 1856 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", |
| 2799 | 1857 | "license": "MIT" |
| 2800 | 1858 | }, |
| 1859 | "node_modules/listener-tracker": { | |
| 1860 | "version": "2.0.0", | |
| 1861 | "resolved": "https://registry.npmjs.org/listener-tracker/-/listener-tracker-2.0.0.tgz", | |
| 1862 | "integrity": "sha512-U6NLzBRyrAsJs9AAjuBYifXtNYnAIDPIp81rNpxNoypXBR7qi/LhsuUWX5399zuTg1sBEQyOnWDYFrBQ28vk/w==", | |
| 1863 | "license": "Apache-2.0" | |
| 1864 | }, | |
| 2801 | 1865 | "node_modules/localforage": { |
| 2802 | 1866 | "version": "1.10.0", |
| 2803 | 1867 | "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", |
| 2804 | "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", | |
| 2805 | "license": "Apache-2.0", | |
| 2806 | "dependencies": { | |
| 2807 | "lie": "3.1.1" | |
| 2808 | } | |
| 2809 | }, | |
| 2810 | "node_modules/longest-streak": { | |
| 2811 | "version": "3.1.0", | |
| 2812 | "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", | |
| 2813 | "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", | |
| 2814 | "license": "MIT", | |
| 2815 | "funding": { | |
| 2816 | "type": "github", | |
| 2817 | "url": "https://github.com/sponsors/wooorm" | |
| 2818 | } | |
| 2819 | }, | |
| 2820 | "node_modules/lru-cache": { | |
| 2821 | "version": "5.1.1", | |
| 2822 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | |
| 2823 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", | |
| 2824 | "license": "ISC", | |
| 2825 | "dependencies": { | |
| 2826 | "yallist": "^3.0.2" | |
| 2827 | } | |
| 2828 | }, | |
| 2829 | "node_modules/magic-string": { | |
| 2830 | "version": "0.30.17", | |
| 2831 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", | |
| 2832 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", | |
| 2833 | "license": "MIT", | |
| 2834 | "dependencies": { | |
| 2835 | "@jridgewell/sourcemap-codec": "^1.5.0" | |
| 2836 | } | |
| 2837 | }, | |
| 2838 | "node_modules/markdown-extensions": { | |
| 2839 | "version": "2.0.0", | |
| 2840 | "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", | |
| 2841 | "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", | |
| 2842 | "license": "MIT", | |
| 2843 | "engines": { | |
| 2844 | "node": ">=16" | |
| 2845 | }, | |
| 2846 | "funding": { | |
| 2847 | "url": "https://github.com/sponsors/sindresorhus" | |
| 2848 | } | |
| 2849 | }, | |
| 2850 | "node_modules/marko": { | |
| 2851 | "version": "6.0.82", | |
| 2852 | "resolved": "https://registry.npmjs.org/marko/-/marko-6.0.82.tgz", | |
| 2853 | "integrity": "sha512-xnBm299wmqrK8++12dhGHBUonm2SZgQVPNhHZjfp3Wc7k4U/qPbKrc7H1DJgU7JrYv8sSdxkconisynDqBYC1g==", | |
| 2854 | "license": "MIT", | |
| 2855 | "dependencies": { | |
| 2856 | "@marko/compiler": "^5.39.40", | |
| 2857 | "csstype": "^3.1.3", | |
| 2858 | "magic-string": "^0.30.17" | |
| 2859 | }, | |
| 2860 | "engines": { | |
| 2861 | "node": "18 || 20 || >=22" | |
| 2862 | } | |
| 2863 | }, | |
| 2864 | "node_modules/mdast-util-from-markdown": { | |
| 2865 | "version": "2.0.2", | |
| 2866 | "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", | |
| 2867 | "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", | |
| 2868 | "license": "MIT", | |
| 2869 | "dependencies": { | |
| 2870 | "@types/mdast": "^4.0.0", | |
| 2871 | "@types/unist": "^3.0.0", | |
| 2872 | "decode-named-character-reference": "^1.0.0", | |
| 2873 | "devlop": "^1.0.0", | |
| 2874 | "mdast-util-to-string": "^4.0.0", | |
| 2875 | "micromark": "^4.0.0", | |
| 2876 | "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 2877 | "micromark-util-decode-string": "^2.0.0", | |
| 2878 | "micromark-util-normalize-identifier": "^2.0.0", | |
| 2879 | "micromark-util-symbol": "^2.0.0", | |
| 2880 | "micromark-util-types": "^2.0.0", | |
| 2881 | "unist-util-stringify-position": "^4.0.0" | |
| 2882 | }, | |
| 2883 | "funding": { | |
| 2884 | "type": "opencollective", | |
| 2885 | "url": "https://opencollective.com/unified" | |
| 2886 | } | |
| 2887 | }, | |
| 2888 | "node_modules/mdast-util-mdx": { | |
| 2889 | "version": "3.0.0", | |
| 2890 | "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", | |
| 2891 | "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", | |
| 2892 | "license": "MIT", | |
| 2893 | "dependencies": { | |
| 2894 | "mdast-util-from-markdown": "^2.0.0", | |
| 2895 | "mdast-util-mdx-expression": "^2.0.0", | |
| 2896 | "mdast-util-mdx-jsx": "^3.0.0", | |
| 2897 | "mdast-util-mdxjs-esm": "^2.0.0", | |
| 2898 | "mdast-util-to-markdown": "^2.0.0" | |
| 2899 | }, | |
| 2900 | "funding": { | |
| 2901 | "type": "opencollective", | |
| 2902 | "url": "https://opencollective.com/unified" | |
| 2903 | } | |
| 2904 | }, | |
| 2905 | "node_modules/mdast-util-mdx-expression": { | |
| 2906 | "version": "2.0.1", | |
| 2907 | "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", | |
| 2908 | "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", | |
| 2909 | "license": "MIT", | |
| 2910 | "dependencies": { | |
| 2911 | "@types/estree-jsx": "^1.0.0", | |
| 2912 | "@types/hast": "^3.0.0", | |
| 2913 | "@types/mdast": "^4.0.0", | |
| 2914 | "devlop": "^1.0.0", | |
| 2915 | "mdast-util-from-markdown": "^2.0.0", | |
| 2916 | "mdast-util-to-markdown": "^2.0.0" | |
| 2917 | }, | |
| 2918 | "funding": { | |
| 2919 | "type": "opencollective", | |
| 2920 | "url": "https://opencollective.com/unified" | |
| 2921 | } | |
| 2922 | }, | |
| 2923 | "node_modules/mdast-util-mdx-jsx": { | |
| 2924 | "version": "3.2.0", | |
| 2925 | "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", | |
| 2926 | "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", | |
| 2927 | "license": "MIT", | |
| 2928 | "dependencies": { | |
| 2929 | "@types/estree-jsx": "^1.0.0", | |
| 2930 | "@types/hast": "^3.0.0", | |
| 2931 | "@types/mdast": "^4.0.0", | |
| 2932 | "@types/unist": "^3.0.0", | |
| 2933 | "ccount": "^2.0.0", | |
| 2934 | "devlop": "^1.1.0", | |
| 2935 | "mdast-util-from-markdown": "^2.0.0", | |
| 2936 | "mdast-util-to-markdown": "^2.0.0", | |
| 2937 | "parse-entities": "^4.0.0", | |
| 2938 | "stringify-entities": "^4.0.0", | |
| 2939 | "unist-util-stringify-position": "^4.0.0", | |
| 2940 | "vfile-message": "^4.0.0" | |
| 2941 | }, | |
| 2942 | "funding": { | |
| 2943 | "type": "opencollective", | |
| 2944 | "url": "https://opencollective.com/unified" | |
| 2945 | } | |
| 2946 | }, | |
| 2947 | "node_modules/mdast-util-mdxjs-esm": { | |
| 2948 | "version": "2.0.1", | |
| 2949 | "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", | |
| 2950 | "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", | |
| 2951 | "license": "MIT", | |
| 2952 | "dependencies": { | |
| 2953 | "@types/estree-jsx": "^1.0.0", | |
| 2954 | "@types/hast": "^3.0.0", | |
| 2955 | "@types/mdast": "^4.0.0", | |
| 2956 | "devlop": "^1.0.0", | |
| 2957 | "mdast-util-from-markdown": "^2.0.0", | |
| 2958 | "mdast-util-to-markdown": "^2.0.0" | |
| 2959 | }, | |
| 2960 | "funding": { | |
| 2961 | "type": "opencollective", | |
| 2962 | "url": "https://opencollective.com/unified" | |
| 2963 | } | |
| 2964 | }, | |
| 2965 | "node_modules/mdast-util-phrasing": { | |
| 2966 | "version": "4.1.0", | |
| 2967 | "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", | |
| 2968 | "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", | |
| 2969 | "license": "MIT", | |
| 2970 | "dependencies": { | |
| 2971 | "@types/mdast": "^4.0.0", | |
| 2972 | "unist-util-is": "^6.0.0" | |
| 2973 | }, | |
| 2974 | "funding": { | |
| 2975 | "type": "opencollective", | |
| 2976 | "url": "https://opencollective.com/unified" | |
| 2977 | } | |
| 2978 | }, | |
| 2979 | "node_modules/mdast-util-to-hast": { | |
| 2980 | "version": "13.2.1", | |
| 2981 | "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", | |
| 2982 | "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", | |
| 2983 | "license": "MIT", | |
| 2984 | "dependencies": { | |
| 2985 | "@types/hast": "^3.0.0", | |
| 2986 | "@types/mdast": "^4.0.0", | |
| 2987 | "@ungap/structured-clone": "^1.0.0", | |
| 2988 | "devlop": "^1.0.0", | |
| 2989 | "micromark-util-sanitize-uri": "^2.0.0", | |
| 2990 | "trim-lines": "^3.0.0", | |
| 2991 | "unist-util-position": "^5.0.0", | |
| 2992 | "unist-util-visit": "^5.0.0", | |
| 2993 | "vfile": "^6.0.0" | |
| 2994 | }, | |
| 2995 | "funding": { | |
| 2996 | "type": "opencollective", | |
| 2997 | "url": "https://opencollective.com/unified" | |
| 2998 | } | |
| 2999 | }, | |
| 3000 | "node_modules/mdast-util-to-markdown": { | |
| 3001 | "version": "2.1.2", | |
| 3002 | "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", | |
| 3003 | "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", | |
| 3004 | "license": "MIT", | |
| 3005 | "dependencies": { | |
| 3006 | "@types/mdast": "^4.0.0", | |
| 3007 | "@types/unist": "^3.0.0", | |
| 3008 | "longest-streak": "^3.0.0", | |
| 3009 | "mdast-util-phrasing": "^4.0.0", | |
| 3010 | "mdast-util-to-string": "^4.0.0", | |
| 3011 | "micromark-util-classify-character": "^2.0.0", | |
| 3012 | "micromark-util-decode-string": "^2.0.0", | |
| 3013 | "unist-util-visit": "^5.0.0", | |
| 3014 | "zwitch": "^2.0.0" | |
| 3015 | }, | |
| 3016 | "funding": { | |
| 3017 | "type": "opencollective", | |
| 3018 | "url": "https://opencollective.com/unified" | |
| 3019 | } | |
| 3020 | }, | |
| 3021 | "node_modules/mdast-util-to-string": { | |
| 3022 | "version": "4.0.0", | |
| 3023 | "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", | |
| 3024 | "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", | |
| 3025 | "license": "MIT", | |
| 3026 | "dependencies": { | |
| 3027 | "@types/mdast": "^4.0.0" | |
| 3028 | }, | |
| 3029 | "funding": { | |
| 3030 | "type": "opencollective", | |
| 3031 | "url": "https://opencollective.com/unified" | |
| 3032 | } | |
| 3033 | }, | |
| 3034 | "node_modules/micromark": { | |
| 3035 | "version": "4.0.2", | |
| 3036 | "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", | |
| 3037 | "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", | |
| 3038 | "funding": [ | |
| 3039 | { | |
| 3040 | "type": "GitHub Sponsors", | |
| 3041 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3042 | }, | |
| 3043 | { | |
| 3044 | "type": "OpenCollective", | |
| 3045 | "url": "https://opencollective.com/unified" | |
| 3046 | } | |
| 3047 | ], | |
| 3048 | "license": "MIT", | |
| 3049 | "dependencies": { | |
| 3050 | "@types/debug": "^4.0.0", | |
| 3051 | "debug": "^4.0.0", | |
| 3052 | "decode-named-character-reference": "^1.0.0", | |
| 3053 | "devlop": "^1.0.0", | |
| 3054 | "micromark-core-commonmark": "^2.0.0", | |
| 3055 | "micromark-factory-space": "^2.0.0", | |
| 3056 | "micromark-util-character": "^2.0.0", | |
| 3057 | "micromark-util-chunked": "^2.0.0", | |
| 3058 | "micromark-util-combine-extensions": "^2.0.0", | |
| 3059 | "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 3060 | "micromark-util-encode": "^2.0.0", | |
| 3061 | "micromark-util-normalize-identifier": "^2.0.0", | |
| 3062 | "micromark-util-resolve-all": "^2.0.0", | |
| 3063 | "micromark-util-sanitize-uri": "^2.0.0", | |
| 3064 | "micromark-util-subtokenize": "^2.0.0", | |
| 3065 | "micromark-util-symbol": "^2.0.0", | |
| 3066 | "micromark-util-types": "^2.0.0" | |
| 3067 | } | |
| 3068 | }, | |
| 3069 | "node_modules/micromark-core-commonmark": { | |
| 3070 | "version": "2.0.3", | |
| 3071 | "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", | |
| 3072 | "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", | |
| 3073 | "funding": [ | |
| 3074 | { | |
| 3075 | "type": "GitHub Sponsors", | |
| 3076 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3077 | }, | |
| 3078 | { | |
| 3079 | "type": "OpenCollective", | |
| 3080 | "url": "https://opencollective.com/unified" | |
| 3081 | } | |
| 3082 | ], | |
| 3083 | "license": "MIT", | |
| 3084 | "dependencies": { | |
| 3085 | "decode-named-character-reference": "^1.0.0", | |
| 3086 | "devlop": "^1.0.0", | |
| 3087 | "micromark-factory-destination": "^2.0.0", | |
| 3088 | "micromark-factory-label": "^2.0.0", | |
| 3089 | "micromark-factory-space": "^2.0.0", | |
| 3090 | "micromark-factory-title": "^2.0.0", | |
| 3091 | "micromark-factory-whitespace": "^2.0.0", | |
| 3092 | "micromark-util-character": "^2.0.0", | |
| 3093 | "micromark-util-chunked": "^2.0.0", | |
| 3094 | "micromark-util-classify-character": "^2.0.0", | |
| 3095 | "micromark-util-html-tag-name": "^2.0.0", | |
| 3096 | "micromark-util-normalize-identifier": "^2.0.0", | |
| 3097 | "micromark-util-resolve-all": "^2.0.0", | |
| 3098 | "micromark-util-subtokenize": "^2.0.0", | |
| 3099 | "micromark-util-symbol": "^2.0.0", | |
| 3100 | "micromark-util-types": "^2.0.0" | |
| 3101 | } | |
| 3102 | }, | |
| 3103 | "node_modules/micromark-extension-mdx-expression": { | |
| 3104 | "version": "3.0.1", | |
| 3105 | "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", | |
| 3106 | "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", | |
| 3107 | "funding": [ | |
| 3108 | { | |
| 3109 | "type": "GitHub Sponsors", | |
| 3110 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3111 | }, | |
| 3112 | { | |
| 3113 | "type": "OpenCollective", | |
| 3114 | "url": "https://opencollective.com/unified" | |
| 3115 | } | |
| 3116 | ], | |
| 3117 | "license": "MIT", | |
| 3118 | "dependencies": { | |
| 3119 | "@types/estree": "^1.0.0", | |
| 3120 | "devlop": "^1.0.0", | |
| 3121 | "micromark-factory-mdx-expression": "^2.0.0", | |
| 3122 | "micromark-factory-space": "^2.0.0", | |
| 3123 | "micromark-util-character": "^2.0.0", | |
| 3124 | "micromark-util-events-to-acorn": "^2.0.0", | |
| 3125 | "micromark-util-symbol": "^2.0.0", | |
| 3126 | "micromark-util-types": "^2.0.0" | |
| 3127 | } | |
| 3128 | }, | |
| 3129 | "node_modules/micromark-extension-mdx-jsx": { | |
| 3130 | "version": "3.0.2", | |
| 3131 | "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", | |
| 3132 | "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", | |
| 3133 | "license": "MIT", | |
| 3134 | "dependencies": { | |
| 3135 | "@types/estree": "^1.0.0", | |
| 3136 | "devlop": "^1.0.0", | |
| 3137 | "estree-util-is-identifier-name": "^3.0.0", | |
| 3138 | "micromark-factory-mdx-expression": "^2.0.0", | |
| 3139 | "micromark-factory-space": "^2.0.0", | |
| 3140 | "micromark-util-character": "^2.0.0", | |
| 3141 | "micromark-util-events-to-acorn": "^2.0.0", | |
| 3142 | "micromark-util-symbol": "^2.0.0", | |
| 3143 | "micromark-util-types": "^2.0.0", | |
| 3144 | "vfile-message": "^4.0.0" | |
| 3145 | }, | |
| 3146 | "funding": { | |
| 3147 | "type": "opencollective", | |
| 3148 | "url": "https://opencollective.com/unified" | |
| 3149 | } | |
| 3150 | }, | |
| 3151 | "node_modules/micromark-extension-mdx-md": { | |
| 3152 | "version": "2.0.0", | |
| 3153 | "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", | |
| 3154 | "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", | |
| 3155 | "license": "MIT", | |
| 3156 | "dependencies": { | |
| 3157 | "micromark-util-types": "^2.0.0" | |
| 3158 | }, | |
| 3159 | "funding": { | |
| 3160 | "type": "opencollective", | |
| 3161 | "url": "https://opencollective.com/unified" | |
| 3162 | } | |
| 3163 | }, | |
| 3164 | "node_modules/micromark-extension-mdxjs": { | |
| 3165 | "version": "3.0.0", | |
| 3166 | "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", | |
| 3167 | "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", | |
| 3168 | "license": "MIT", | |
| 3169 | "dependencies": { | |
| 3170 | "acorn": "^8.0.0", | |
| 3171 | "acorn-jsx": "^5.0.0", | |
| 3172 | "micromark-extension-mdx-expression": "^3.0.0", | |
| 3173 | "micromark-extension-mdx-jsx": "^3.0.0", | |
| 3174 | "micromark-extension-mdx-md": "^2.0.0", | |
| 3175 | "micromark-extension-mdxjs-esm": "^3.0.0", | |
| 3176 | "micromark-util-combine-extensions": "^2.0.0", | |
| 3177 | "micromark-util-types": "^2.0.0" | |
| 3178 | }, | |
| 3179 | "funding": { | |
| 3180 | "type": "opencollective", | |
| 3181 | "url": "https://opencollective.com/unified" | |
| 3182 | } | |
| 3183 | }, | |
| 3184 | "node_modules/micromark-extension-mdxjs-esm": { | |
| 3185 | "version": "3.0.0", | |
| 3186 | "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", | |
| 3187 | "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", | |
| 3188 | "license": "MIT", | |
| 3189 | "dependencies": { | |
| 3190 | "@types/estree": "^1.0.0", | |
| 3191 | "devlop": "^1.0.0", | |
| 3192 | "micromark-core-commonmark": "^2.0.0", | |
| 3193 | "micromark-util-character": "^2.0.0", | |
| 3194 | "micromark-util-events-to-acorn": "^2.0.0", | |
| 3195 | "micromark-util-symbol": "^2.0.0", | |
| 3196 | "micromark-util-types": "^2.0.0", | |
| 3197 | "unist-util-position-from-estree": "^2.0.0", | |
| 3198 | "vfile-message": "^4.0.0" | |
| 3199 | }, | |
| 3200 | "funding": { | |
| 3201 | "type": "opencollective", | |
| 3202 | "url": "https://opencollective.com/unified" | |
| 3203 | } | |
| 3204 | }, | |
| 3205 | "node_modules/micromark-factory-destination": { | |
| 3206 | "version": "2.0.1", | |
| 3207 | "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", | |
| 3208 | "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", | |
| 3209 | "funding": [ | |
| 3210 | { | |
| 3211 | "type": "GitHub Sponsors", | |
| 3212 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3213 | }, | |
| 3214 | { | |
| 3215 | "type": "OpenCollective", | |
| 3216 | "url": "https://opencollective.com/unified" | |
| 3217 | } | |
| 3218 | ], | |
| 3219 | "license": "MIT", | |
| 3220 | "dependencies": { | |
| 3221 | "micromark-util-character": "^2.0.0", | |
| 3222 | "micromark-util-symbol": "^2.0.0", | |
| 3223 | "micromark-util-types": "^2.0.0" | |
| 3224 | } | |
| 3225 | }, | |
| 3226 | "node_modules/micromark-factory-label": { | |
| 3227 | "version": "2.0.1", | |
| 3228 | "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", | |
| 3229 | "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", | |
| 3230 | "funding": [ | |
| 3231 | { | |
| 3232 | "type": "GitHub Sponsors", | |
| 3233 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3234 | }, | |
| 3235 | { | |
| 3236 | "type": "OpenCollective", | |
| 3237 | "url": "https://opencollective.com/unified" | |
| 3238 | } | |
| 3239 | ], | |
| 3240 | "license": "MIT", | |
| 3241 | "dependencies": { | |
| 3242 | "devlop": "^1.0.0", | |
| 3243 | "micromark-util-character": "^2.0.0", | |
| 3244 | "micromark-util-symbol": "^2.0.0", | |
| 3245 | "micromark-util-types": "^2.0.0" | |
| 3246 | } | |
| 3247 | }, | |
| 3248 | "node_modules/micromark-factory-mdx-expression": { | |
| 3249 | "version": "2.0.3", | |
| 3250 | "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", | |
| 3251 | "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", | |
| 3252 | "funding": [ | |
| 3253 | { | |
| 3254 | "type": "GitHub Sponsors", | |
| 3255 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3256 | }, | |
| 3257 | { | |
| 3258 | "type": "OpenCollective", | |
| 3259 | "url": "https://opencollective.com/unified" | |
| 3260 | } | |
| 3261 | ], | |
| 3262 | "license": "MIT", | |
| 3263 | "dependencies": { | |
| 3264 | "@types/estree": "^1.0.0", | |
| 3265 | "devlop": "^1.0.0", | |
| 3266 | "micromark-factory-space": "^2.0.0", | |
| 3267 | "micromark-util-character": "^2.0.0", | |
| 3268 | "micromark-util-events-to-acorn": "^2.0.0", | |
| 3269 | "micromark-util-symbol": "^2.0.0", | |
| 3270 | "micromark-util-types": "^2.0.0", | |
| 3271 | "unist-util-position-from-estree": "^2.0.0", | |
| 3272 | "vfile-message": "^4.0.0" | |
| 3273 | } | |
| 3274 | }, | |
| 3275 | "node_modules/micromark-factory-space": { | |
| 3276 | "version": "2.0.1", | |
| 3277 | "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", | |
| 3278 | "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", | |
| 3279 | "funding": [ | |
| 3280 | { | |
| 3281 | "type": "GitHub Sponsors", | |
| 3282 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3283 | }, | |
| 3284 | { | |
| 3285 | "type": "OpenCollective", | |
| 3286 | "url": "https://opencollective.com/unified" | |
| 3287 | } | |
| 3288 | ], | |
| 3289 | "license": "MIT", | |
| 3290 | "dependencies": { | |
| 3291 | "micromark-util-character": "^2.0.0", | |
| 3292 | "micromark-util-types": "^2.0.0" | |
| 3293 | } | |
| 3294 | }, | |
| 3295 | "node_modules/micromark-factory-title": { | |
| 3296 | "version": "2.0.1", | |
| 3297 | "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", | |
| 3298 | "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", | |
| 3299 | "funding": [ | |
| 3300 | { | |
| 3301 | "type": "GitHub Sponsors", | |
| 3302 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3303 | }, | |
| 3304 | { | |
| 3305 | "type": "OpenCollective", | |
| 3306 | "url": "https://opencollective.com/unified" | |
| 3307 | } | |
| 3308 | ], | |
| 3309 | "license": "MIT", | |
| 3310 | "dependencies": { | |
| 3311 | "micromark-factory-space": "^2.0.0", | |
| 3312 | "micromark-util-character": "^2.0.0", | |
| 3313 | "micromark-util-symbol": "^2.0.0", | |
| 3314 | "micromark-util-types": "^2.0.0" | |
| 3315 | } | |
| 3316 | }, | |
| 3317 | "node_modules/micromark-factory-whitespace": { | |
| 3318 | "version": "2.0.1", | |
| 3319 | "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", | |
| 3320 | "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", | |
| 3321 | "funding": [ | |
| 3322 | { | |
| 3323 | "type": "GitHub Sponsors", | |
| 3324 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3325 | }, | |
| 3326 | { | |
| 3327 | "type": "OpenCollective", | |
| 3328 | "url": "https://opencollective.com/unified" | |
| 3329 | } | |
| 3330 | ], | |
| 3331 | "license": "MIT", | |
| 3332 | "dependencies": { | |
| 3333 | "micromark-factory-space": "^2.0.0", | |
| 3334 | "micromark-util-character": "^2.0.0", | |
| 3335 | "micromark-util-symbol": "^2.0.0", | |
| 3336 | "micromark-util-types": "^2.0.0" | |
| 3337 | } | |
| 3338 | }, | |
| 3339 | "node_modules/micromark-util-character": { | |
| 3340 | "version": "2.1.1", | |
| 3341 | "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", | |
| 3342 | "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", | |
| 3343 | "funding": [ | |
| 3344 | { | |
| 3345 | "type": "GitHub Sponsors", | |
| 3346 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3347 | }, | |
| 3348 | { | |
| 3349 | "type": "OpenCollective", | |
| 3350 | "url": "https://opencollective.com/unified" | |
| 3351 | } | |
| 3352 | ], | |
| 3353 | "license": "MIT", | |
| 3354 | "dependencies": { | |
| 3355 | "micromark-util-symbol": "^2.0.0", | |
| 3356 | "micromark-util-types": "^2.0.0" | |
| 3357 | } | |
| 3358 | }, | |
| 3359 | "node_modules/micromark-util-chunked": { | |
| 3360 | "version": "2.0.1", | |
| 3361 | "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", | |
| 3362 | "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", | |
| 3363 | "funding": [ | |
| 3364 | { | |
| 3365 | "type": "GitHub Sponsors", | |
| 3366 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3367 | }, | |
| 3368 | { | |
| 3369 | "type": "OpenCollective", | |
| 3370 | "url": "https://opencollective.com/unified" | |
| 3371 | } | |
| 3372 | ], | |
| 3373 | "license": "MIT", | |
| 3374 | "dependencies": { | |
| 3375 | "micromark-util-symbol": "^2.0.0" | |
| 3376 | } | |
| 3377 | }, | |
| 3378 | "node_modules/micromark-util-classify-character": { | |
| 3379 | "version": "2.0.1", | |
| 3380 | "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", | |
| 3381 | "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", | |
| 3382 | "funding": [ | |
| 3383 | { | |
| 3384 | "type": "GitHub Sponsors", | |
| 3385 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3386 | }, | |
| 3387 | { | |
| 3388 | "type": "OpenCollective", | |
| 3389 | "url": "https://opencollective.com/unified" | |
| 3390 | } | |
| 3391 | ], | |
| 3392 | "license": "MIT", | |
| 3393 | "dependencies": { | |
| 3394 | "micromark-util-character": "^2.0.0", | |
| 3395 | "micromark-util-symbol": "^2.0.0", | |
| 3396 | "micromark-util-types": "^2.0.0" | |
| 3397 | } | |
| 3398 | }, | |
| 3399 | "node_modules/micromark-util-combine-extensions": { | |
| 3400 | "version": "2.0.1", | |
| 3401 | "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", | |
| 3402 | "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", | |
| 3403 | "funding": [ | |
| 3404 | { | |
| 3405 | "type": "GitHub Sponsors", | |
| 3406 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3407 | }, | |
| 3408 | { | |
| 3409 | "type": "OpenCollective", | |
| 3410 | "url": "https://opencollective.com/unified" | |
| 3411 | } | |
| 3412 | ], | |
| 3413 | "license": "MIT", | |
| 3414 | "dependencies": { | |
| 3415 | "micromark-util-chunked": "^2.0.0", | |
| 3416 | "micromark-util-types": "^2.0.0" | |
| 3417 | } | |
| 3418 | }, | |
| 3419 | "node_modules/micromark-util-decode-numeric-character-reference": { | |
| 3420 | "version": "2.0.2", | |
| 3421 | "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", | |
| 3422 | "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", | |
| 3423 | "funding": [ | |
| 3424 | { | |
| 3425 | "type": "GitHub Sponsors", | |
| 3426 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3427 | }, | |
| 3428 | { | |
| 3429 | "type": "OpenCollective", | |
| 3430 | "url": "https://opencollective.com/unified" | |
| 3431 | } | |
| 3432 | ], | |
| 3433 | "license": "MIT", | |
| 3434 | "dependencies": { | |
| 3435 | "micromark-util-symbol": "^2.0.0" | |
| 3436 | } | |
| 3437 | }, | |
| 3438 | "node_modules/micromark-util-decode-string": { | |
| 3439 | "version": "2.0.1", | |
| 3440 | "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", | |
| 3441 | "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", | |
| 3442 | "funding": [ | |
| 3443 | { | |
| 3444 | "type": "GitHub Sponsors", | |
| 3445 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3446 | }, | |
| 3447 | { | |
| 3448 | "type": "OpenCollective", | |
| 3449 | "url": "https://opencollective.com/unified" | |
| 3450 | } | |
| 3451 | ], | |
| 3452 | "license": "MIT", | |
| 3453 | "dependencies": { | |
| 3454 | "decode-named-character-reference": "^1.0.0", | |
| 3455 | "micromark-util-character": "^2.0.0", | |
| 3456 | "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 3457 | "micromark-util-symbol": "^2.0.0" | |
| 3458 | } | |
| 3459 | }, | |
| 3460 | "node_modules/micromark-util-encode": { | |
| 3461 | "version": "2.0.1", | |
| 3462 | "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", | |
| 3463 | "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", | |
| 3464 | "funding": [ | |
| 3465 | { | |
| 3466 | "type": "GitHub Sponsors", | |
| 3467 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3468 | }, | |
| 3469 | { | |
| 3470 | "type": "OpenCollective", | |
| 3471 | "url": "https://opencollective.com/unified" | |
| 3472 | } | |
| 3473 | ], | |
| 3474 | "license": "MIT" | |
| 3475 | }, | |
| 3476 | "node_modules/micromark-util-events-to-acorn": { | |
| 3477 | "version": "2.0.3", | |
| 3478 | "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", | |
| 3479 | "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", | |
| 3480 | "funding": [ | |
| 3481 | { | |
| 3482 | "type": "GitHub Sponsors", | |
| 3483 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3484 | }, | |
| 3485 | { | |
| 3486 | "type": "OpenCollective", | |
| 3487 | "url": "https://opencollective.com/unified" | |
| 3488 | } | |
| 3489 | ], | |
| 3490 | "license": "MIT", | |
| 3491 | "dependencies": { | |
| 3492 | "@types/estree": "^1.0.0", | |
| 3493 | "@types/unist": "^3.0.0", | |
| 3494 | "devlop": "^1.0.0", | |
| 3495 | "estree-util-visit": "^2.0.0", | |
| 3496 | "micromark-util-symbol": "^2.0.0", | |
| 3497 | "micromark-util-types": "^2.0.0", | |
| 3498 | "vfile-message": "^4.0.0" | |
| 3499 | } | |
| 3500 | }, | |
| 3501 | "node_modules/micromark-util-html-tag-name": { | |
| 3502 | "version": "2.0.1", | |
| 3503 | "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", | |
| 3504 | "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", | |
| 3505 | "funding": [ | |
| 3506 | { | |
| 3507 | "type": "GitHub Sponsors", | |
| 3508 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3509 | }, | |
| 3510 | { | |
| 3511 | "type": "OpenCollective", | |
| 3512 | "url": "https://opencollective.com/unified" | |
| 3513 | } | |
| 3514 | ], | |
| 3515 | "license": "MIT" | |
| 3516 | }, | |
| 3517 | "node_modules/micromark-util-normalize-identifier": { | |
| 3518 | "version": "2.0.1", | |
| 3519 | "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", | |
| 3520 | "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", | |
| 3521 | "funding": [ | |
| 3522 | { | |
| 3523 | "type": "GitHub Sponsors", | |
| 3524 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3525 | }, | |
| 3526 | { | |
| 3527 | "type": "OpenCollective", | |
| 3528 | "url": "https://opencollective.com/unified" | |
| 3529 | } | |
| 3530 | ], | |
| 3531 | "license": "MIT", | |
| 3532 | "dependencies": { | |
| 3533 | "micromark-util-symbol": "^2.0.0" | |
| 3534 | } | |
| 3535 | }, | |
| 3536 | "node_modules/micromark-util-resolve-all": { | |
| 3537 | "version": "2.0.1", | |
| 3538 | "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", | |
| 3539 | "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", | |
| 3540 | "funding": [ | |
| 3541 | { | |
| 3542 | "type": "GitHub Sponsors", | |
| 3543 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3544 | }, | |
| 3545 | { | |
| 3546 | "type": "OpenCollective", | |
| 3547 | "url": "https://opencollective.com/unified" | |
| 3548 | } | |
| 3549 | ], | |
| 3550 | "license": "MIT", | |
| 1868 | "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", | |
| 1869 | "license": "Apache-2.0", | |
| 3551 | 1870 | "dependencies": { |
| 3552 | "micromark-util-types": "^2.0.0" | |
| 1871 | "lie": "3.1.1" | |
| 3553 | 1872 | } |
| 3554 | 1873 | }, |
| 3555 | "node_modules/micromark-util-sanitize-uri": { | |
| 3556 | "version": "2.0.1", | |
| 3557 | "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", | |
| 3558 | "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", | |
| 3559 | "funding": [ | |
| 3560 | { | |
| 3561 | "type": "GitHub Sponsors", | |
| 3562 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3563 | }, | |
| 3564 | { | |
| 3565 | "type": "OpenCollective", | |
| 3566 | "url": "https://opencollective.com/unified" | |
| 3567 | } | |
| 3568 | ], | |
| 1874 | "node_modules/magic-string": { | |
| 1875 | "version": "0.30.21", | |
| 1876 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", | |
| 1877 | "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", | |
| 3569 | 1878 | "license": "MIT", |
| 3570 | 1879 | "dependencies": { |
| 3571 | "micromark-util-character": "^2.0.0", | |
| 3572 | "micromark-util-encode": "^2.0.0", | |
| 3573 | "micromark-util-symbol": "^2.0.0" | |
| 1880 | "@jridgewell/sourcemap-codec": "^1.5.5" | |
| 3574 | 1881 | } |
| 3575 | 1882 | }, |
| 3576 | "node_modules/micromark-util-subtokenize": { | |
| 3577 | "version": "2.1.0", | |
| 3578 | "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", | |
| 3579 | "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", | |
| 3580 | "funding": [ | |
| 3581 | { | |
| 3582 | "type": "GitHub Sponsors", | |
| 3583 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3584 | }, | |
| 3585 | { | |
| 3586 | "type": "OpenCollective", | |
| 3587 | "url": "https://opencollective.com/unified" | |
| 3588 | } | |
| 3589 | ], | |
| 1883 | "node_modules/marko": { | |
| 1884 | "version": "5.38.24", | |
| 1885 | "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.24.tgz", | |
| 1886 | "integrity": "sha512-/F/ZiziJK8T+XMsbfF0/GkXBvqEAnVkZDWHw016Ey6qiFJcSpR/zE5AJ8cXLAqk9WTM4/gRo51wlm2XocpBajg==", | |
| 3590 | 1887 | "license": "MIT", |
| 3591 | 1888 | "dependencies": { |
| 3592 | "devlop": "^1.0.0", | |
| 3593 | "micromark-util-chunked": "^2.0.0", | |
| 3594 | "micromark-util-symbol": "^2.0.0", | |
| 3595 | "micromark-util-types": "^2.0.0" | |
| 1889 | "@marko/compiler": "^5.39.53", | |
| 1890 | "@marko/runtime-tags": "^6.0.145", | |
| 1891 | "app-module-path": "^2.2.0", | |
| 1892 | "argly": "^1.2.0", | |
| 1893 | "browser-refresh-client": "1.1.4", | |
| 1894 | "complain": "^1.6.1", | |
| 1895 | "csstype": "^3.2.3", | |
| 1896 | "events-light": "^1.0.5", | |
| 1897 | "listener-tracker": "^2.0.0", | |
| 1898 | "magic-string": "^0.30.21", | |
| 1899 | "minimatch": "^9.0.5", | |
| 1900 | "raptor-util": "^3.2.0", | |
| 1901 | "resolve-from": "^5.0.0", | |
| 1902 | "self-closing-tags": "^1.0.1", | |
| 1903 | "warp10": "^2.1.0" | |
| 1904 | }, | |
| 1905 | "bin": { | |
| 1906 | "markoc": "bin/markoc" | |
| 1907 | }, | |
| 1908 | "engines": { | |
| 1909 | "node": "18 || 20 || >=22" | |
| 3596 | 1910 | } |
| 3597 | 1911 | }, |
| 3598 | "node_modules/micromark-util-symbol": { | |
| 3599 | "version": "2.0.1", | |
| 3600 | "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", | |
| 3601 | "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", | |
| 3602 | "funding": [ | |
| 3603 | { | |
| 3604 | "type": "GitHub Sponsors", | |
| 3605 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3606 | }, | |
| 3607 | { | |
| 3608 | "type": "OpenCollective", | |
| 3609 | "url": "https://opencollective.com/unified" | |
| 3610 | } | |
| 3611 | ], | |
| 3612 | "license": "MIT" | |
| 3613 | }, | |
| 3614 | "node_modules/micromark-util-types": { | |
| 3615 | "version": "2.0.2", | |
| 3616 | "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", | |
| 3617 | "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", | |
| 3618 | "funding": [ | |
| 3619 | { | |
| 3620 | "type": "GitHub Sponsors", | |
| 3621 | "url": "https://github.com/sponsors/unifiedjs" | |
| 3622 | }, | |
| 3623 | { | |
| 3624 | "type": "OpenCollective", | |
| 3625 | "url": "https://opencollective.com/unified" | |
| 3626 | } | |
| 3627 | ], | |
| 3628 | "license": "MIT" | |
| 1912 | "node_modules/minimatch": { | |
| 1913 | "version": "9.0.5", | |
| 1914 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", | |
| 1915 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", | |
| 1916 | "license": "ISC", | |
| 1917 | "dependencies": { | |
| 1918 | "brace-expansion": "^2.0.1" | |
| 1919 | }, | |
| 1920 | "engines": { | |
| 1921 | "node": ">=16 || 14 >=14.17" | |
| 1922 | }, | |
| 1923 | "funding": { | |
| 1924 | "url": "https://github.com/sponsors/isaacs" | |
| 1925 | } | |
| 3629 | 1926 | }, |
| 3630 | 1927 | "node_modules/mitt": { |
| 3631 | 1928 | "version": "3.0.1", |
| ... | ... | @@ -3695,9 +1992,9 @@ |
| 3695 | 1992 | } |
| 3696 | 1993 | }, |
| 3697 | 1994 | "node_modules/node-releases": { |
| 3698 | "version": "2.0.21", | |
| 3699 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", | |
| 3700 | "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", | |
| 1995 | "version": "2.0.27", | |
| 1996 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", | |
| 1997 | "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", | |
| 3701 | 1998 | "license": "MIT" |
| 3702 | 1999 | }, |
| 3703 | 2000 | "node_modules/once": { |
| ... | ... | @@ -3753,31 +2050,6 @@ |
| 3753 | 2050 | "node": ">=6" |
| 3754 | 2051 | } |
| 3755 | 2052 | }, |
| 3756 | "node_modules/parse-entities": { | |
| 3757 | "version": "4.0.2", | |
| 3758 | "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", | |
| 3759 | "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", | |
| 3760 | "license": "MIT", | |
| 3761 | "dependencies": { | |
| 3762 | "@types/unist": "^2.0.0", | |
| 3763 | "character-entities-legacy": "^3.0.0", | |
| 3764 | "character-reference-invalid": "^2.0.0", | |
| 3765 | "decode-named-character-reference": "^1.0.0", | |
| 3766 | "is-alphanumerical": "^2.0.0", | |
| 3767 | "is-decimal": "^2.0.0", | |
| 3768 | "is-hexadecimal": "^2.0.0" | |
| 3769 | }, | |
| 3770 | "funding": { | |
| 3771 | "type": "github", | |
| 3772 | "url": "https://github.com/sponsors/wooorm" | |
| 3773 | } | |
| 3774 | }, | |
| 3775 | "node_modules/parse-entities/node_modules/@types/unist": { | |
| 3776 | "version": "2.0.11", | |
| 3777 | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", | |
| 3778 | "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", | |
| 3779 | "license": "MIT" | |
| 3780 | }, | |
| 3781 | 2053 | "node_modules/parse-json": { |
| 3782 | 2054 | "version": "5.2.0", |
| 3783 | 2055 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", |
| ... | ... | @@ -3823,16 +2095,6 @@ |
| 3823 | 2095 | "node": ">=0.4.0" |
| 3824 | 2096 | } |
| 3825 | 2097 | }, |
| 3826 | "node_modules/property-information": { | |
| 3827 | "version": "7.1.0", | |
| 3828 | "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", | |
| 3829 | "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", | |
| 3830 | "license": "MIT", | |
| 3831 | "funding": { | |
| 3832 | "type": "github", | |
| 3833 | "url": "https://github.com/sponsors/wooorm" | |
| 3834 | } | |
| 3835 | }, | |
| 3836 | 2098 | "node_modules/proxy-agent": { |
| 3837 | 2099 | "version": "6.5.0", |
| 3838 | 2100 | "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", |
| ... | ... | @@ -3934,156 +2196,12 @@ |
| 3934 | 2196 | "integrity": "sha512-uEDMMkBCJvjTqYMBnJNxn+neiS6a0rhybQNA9RaexGor1uvKjwyHA5VcbZMZEuqXhKUWbL+WNS7PhuZVZNB7pw==", |
| 3935 | 2197 | "license": "Apache-2.0" |
| 3936 | 2198 | }, |
| 3937 | "node_modules/recma-build-jsx": { | |
| 3938 | "version": "1.0.0", | |
| 3939 | "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", | |
| 3940 | "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", | |
| 3941 | "license": "MIT", | |
| 3942 | "dependencies": { | |
| 3943 | "@types/estree": "^1.0.0", | |
| 3944 | "estree-util-build-jsx": "^3.0.0", | |
| 3945 | "vfile": "^6.0.0" | |
| 3946 | }, | |
| 3947 | "funding": { | |
| 3948 | "type": "opencollective", | |
| 3949 | "url": "https://opencollective.com/unified" | |
| 3950 | } | |
| 3951 | }, | |
| 3952 | "node_modules/recma-jsx": { | |
| 3953 | "version": "1.0.0", | |
| 3954 | "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", | |
| 3955 | "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", | |
| 3956 | "license": "MIT", | |
| 3957 | "dependencies": { | |
| 3958 | "acorn-jsx": "^5.0.0", | |
| 3959 | "estree-util-to-js": "^2.0.0", | |
| 3960 | "recma-parse": "^1.0.0", | |
| 3961 | "recma-stringify": "^1.0.0", | |
| 3962 | "unified": "^11.0.0" | |
| 3963 | }, | |
| 3964 | "funding": { | |
| 3965 | "type": "opencollective", | |
| 3966 | "url": "https://opencollective.com/unified" | |
| 3967 | } | |
| 3968 | }, | |
| 3969 | "node_modules/recma-parse": { | |
| 3970 | "version": "1.0.0", | |
| 3971 | "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", | |
| 3972 | "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", | |
| 3973 | "license": "MIT", | |
| 3974 | "dependencies": { | |
| 3975 | "@types/estree": "^1.0.0", | |
| 3976 | "esast-util-from-js": "^2.0.0", | |
| 3977 | "unified": "^11.0.0", | |
| 3978 | "vfile": "^6.0.0" | |
| 3979 | }, | |
| 3980 | "funding": { | |
| 3981 | "type": "opencollective", | |
| 3982 | "url": "https://opencollective.com/unified" | |
| 3983 | } | |
| 3984 | }, | |
| 3985 | "node_modules/recma-stringify": { | |
| 3986 | "version": "1.0.0", | |
| 3987 | "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", | |
| 3988 | "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", | |
| 3989 | "license": "MIT", | |
| 3990 | "dependencies": { | |
| 3991 | "@types/estree": "^1.0.0", | |
| 3992 | "estree-util-to-js": "^2.0.0", | |
| 3993 | "unified": "^11.0.0", | |
| 3994 | "vfile": "^6.0.0" | |
| 3995 | }, | |
| 3996 | "funding": { | |
| 3997 | "type": "opencollective", | |
| 3998 | "url": "https://opencollective.com/unified" | |
| 3999 | } | |
| 4000 | }, | |
| 4001 | "node_modules/rehype-mdx-code-props": { | |
| 4002 | "version": "3.0.1", | |
| 4003 | "resolved": "https://registry.npmjs.org/rehype-mdx-code-props/-/rehype-mdx-code-props-3.0.1.tgz", | |
| 4004 | "integrity": "sha512-BWWKn0N6r7/qd7lbLgv5J8of7imz1l1PyCNoY7BH0AOR9JdJlQIfA9cKqTZVEb2h2GPKh473qrBajF0i01fq3A==", | |
| 4005 | "license": "MIT", | |
| 4006 | "dependencies": { | |
| 4007 | "@types/hast": "^3.0.0", | |
| 4008 | "hast-util-properties-to-mdx-jsx-attributes": "^1.0.0", | |
| 4009 | "mdast-util-from-markdown": "^2.0.0", | |
| 4010 | "mdast-util-mdx": "^3.0.0", | |
| 4011 | "micromark-extension-mdxjs": "^3.0.0", | |
| 4012 | "unified": "^11.0.0", | |
| 4013 | "unist-util-visit-parents": "^6.0.0" | |
| 4014 | }, | |
| 4015 | "funding": { | |
| 4016 | "url": "https://github.com/sponsors/remcohaszing" | |
| 4017 | } | |
| 4018 | }, | |
| 4019 | "node_modules/rehype-recma": { | |
| 4020 | "version": "1.0.0", | |
| 4021 | "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", | |
| 4022 | "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", | |
| 4023 | "license": "MIT", | |
| 4024 | "dependencies": { | |
| 4025 | "@types/estree": "^1.0.0", | |
| 4026 | "@types/hast": "^3.0.0", | |
| 4027 | "hast-util-to-estree": "^3.0.0" | |
| 4028 | }, | |
| 4029 | "funding": { | |
| 4030 | "type": "opencollective", | |
| 4031 | "url": "https://opencollective.com/unified" | |
| 4032 | } | |
| 4033 | }, | |
| 4034 | 2199 | "node_modules/relative-import-path": { |
| 4035 | 2200 | "version": "1.0.0", |
| 4036 | 2201 | "resolved": "https://registry.npmjs.org/relative-import-path/-/relative-import-path-1.0.0.tgz", |
| 4037 | 2202 | "integrity": "sha512-ZvbtoduKQmD4PZeJPfH6Ql21qUWhaMxiHkIsH+FUnZqKDwNIXBtGg5zRZyHWomiGYk8n5+KMBPK7Mi4D0XWfNg==", |
| 4038 | 2203 | "license": "MIT" |
| 4039 | 2204 | }, |
| 4040 | "node_modules/remark-mdx": { | |
| 4041 | "version": "3.1.0", | |
| 4042 | "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", | |
| 4043 | "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", | |
| 4044 | "license": "MIT", | |
| 4045 | "dependencies": { | |
| 4046 | "mdast-util-mdx": "^3.0.0", | |
| 4047 | "micromark-extension-mdxjs": "^3.0.0" | |
| 4048 | }, | |
| 4049 | "funding": { | |
| 4050 | "type": "opencollective", | |
| 4051 | "url": "https://opencollective.com/unified" | |
| 4052 | } | |
| 4053 | }, | |
| 4054 | "node_modules/remark-parse": { | |
| 4055 | "version": "11.0.0", | |
| 4056 | "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", | |
| 4057 | "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", | |
| 4058 | "license": "MIT", | |
| 4059 | "dependencies": { | |
| 4060 | "@types/mdast": "^4.0.0", | |
| 4061 | "mdast-util-from-markdown": "^2.0.0", | |
| 4062 | "micromark-util-types": "^2.0.0", | |
| 4063 | "unified": "^11.0.0" | |
| 4064 | }, | |
| 4065 | "funding": { | |
| 4066 | "type": "opencollective", | |
| 4067 | "url": "https://opencollective.com/unified" | |
| 4068 | } | |
| 4069 | }, | |
| 4070 | "node_modules/remark-rehype": { | |
| 4071 | "version": "11.1.2", | |
| 4072 | "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", | |
| 4073 | "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", | |
| 4074 | "license": "MIT", | |
| 4075 | "dependencies": { | |
| 4076 | "@types/hast": "^3.0.0", | |
| 4077 | "@types/mdast": "^4.0.0", | |
| 4078 | "mdast-util-to-hast": "^13.0.0", | |
| 4079 | "unified": "^11.0.0", | |
| 4080 | "vfile": "^6.0.0" | |
| 4081 | }, | |
| 4082 | "funding": { | |
| 4083 | "type": "opencollective", | |
| 4084 | "url": "https://opencollective.com/unified" | |
| 4085 | } | |
| 4086 | }, | |
| 4087 | 2205 | "node_modules/require-directory": { |
| 4088 | 2206 | "version": "2.1.1", |
| 4089 | 2207 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", |
| ... | ... | @@ -4117,15 +2235,6 @@ |
| 4117 | 2235 | "node": ">=0.12.0" |
| 4118 | 2236 | } |
| 4119 | 2237 | }, |
| 4120 | "node_modules/semver": { | |
| 4121 | "version": "6.3.1", | |
| 4122 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", | |
| 4123 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", | |
| 4124 | "license": "ISC", | |
| 4125 | "bin": { | |
| 4126 | "semver": "bin/semver.js" | |
| 4127 | } | |
| 4128 | }, | |
| 4129 | 2238 | "node_modules/smart-buffer": { |
| 4130 | 2239 | "version": "4.2.0", |
| 4131 | 2240 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", |
| ... | ... | @@ -4164,15 +2273,6 @@ |
| 4164 | 2273 | "node": ">= 14" |
| 4165 | 2274 | } |
| 4166 | 2275 | }, |
| 4167 | "node_modules/source-map": { | |
| 4168 | "version": "0.7.4", | |
| 4169 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", | |
| 4170 | "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", | |
| 4171 | "license": "BSD-3-Clause", | |
| 4172 | "engines": { | |
| 4173 | "node": ">= 8" | |
| 4174 | } | |
| 4175 | }, | |
| 4176 | 2276 | "node_modules/source-map-support": { |
| 4177 | 2277 | "version": "0.5.21", |
| 4178 | 2278 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", |
| ... | ... | @@ -4192,16 +2292,6 @@ |
| 4192 | 2292 | "node": ">=0.10.0" |
| 4193 | 2293 | } |
| 4194 | 2294 | }, |
| 4195 | "node_modules/space-separated-tokens": { | |
| 4196 | "version": "2.0.2", | |
| 4197 | "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", | |
| 4198 | "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", | |
| 4199 | "license": "MIT", | |
| 4200 | "funding": { | |
| 4201 | "type": "github", | |
| 4202 | "url": "https://github.com/sponsors/wooorm" | |
| 4203 | } | |
| 4204 | }, | |
| 4205 | 2295 | "node_modules/stackframe": { |
| 4206 | 2296 | "version": "1.3.4", |
| 4207 | 2297 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", |
| ... | ... | @@ -4233,20 +2323,6 @@ |
| 4233 | 2323 | "node": ">=8" |
| 4234 | 2324 | } |
| 4235 | 2325 | }, |
| 4236 | "node_modules/stringify-entities": { | |
| 4237 | "version": "4.0.4", | |
| 4238 | "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", | |
| 4239 | "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", | |
| 4240 | "license": "MIT", | |
| 4241 | "dependencies": { | |
| 4242 | "character-entities-html4": "^2.0.0", | |
| 4243 | "character-entities-legacy": "^3.0.0" | |
| 4244 | }, | |
| 4245 | "funding": { | |
| 4246 | "type": "github", | |
| 4247 | "url": "https://github.com/sponsors/wooorm" | |
| 4248 | } | |
| 4249 | }, | |
| 4250 | 2326 | "node_modules/strip-ansi": { |
| 4251 | 2327 | "version": "6.0.1", |
| 4252 | 2328 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| ... | ... | @@ -4265,24 +2341,6 @@ |
| 4265 | 2341 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", |
| 4266 | 2342 | "license": "MIT" |
| 4267 | 2343 | }, |
| 4268 | "node_modules/style-to-js": { | |
| 4269 | "version": "1.1.17", | |
| 4270 | "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", | |
| 4271 | "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", | |
| 4272 | "license": "MIT", | |
| 4273 | "dependencies": { | |
| 4274 | "style-to-object": "1.0.9" | |
| 4275 | } | |
| 4276 | }, | |
| 4277 | "node_modules/style-to-object": { | |
| 4278 | "version": "1.0.9", | |
| 4279 | "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", | |
| 4280 | "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", | |
| 4281 | "license": "MIT", | |
| 4282 | "dependencies": { | |
| 4283 | "inline-style-parser": "0.2.4" | |
| 4284 | } | |
| 4285 | }, | |
| 4286 | 2344 | "node_modules/tar-fs": { |
| 4287 | 2345 | "version": "3.1.1", |
| 4288 | 2346 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", |
| ... | ... | @@ -4317,32 +2375,21 @@ |
| 4317 | 2375 | "b4a": "^1.6.4" |
| 4318 | 2376 | } |
| 4319 | 2377 | }, |
| 4320 | "node_modules/trim-lines": { | |
| 4321 | "version": "3.0.1", | |
| 4322 | "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", | |
| 4323 | "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", | |
| 4324 | "license": "MIT", | |
| 4325 | "funding": { | |
| 4326 | "type": "github", | |
| 4327 | "url": "https://github.com/sponsors/wooorm" | |
| 4328 | } | |
| 4329 | }, | |
| 4330 | "node_modules/trough": { | |
| 4331 | "version": "2.2.0", | |
| 4332 | "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", | |
| 4333 | "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", | |
| 4334 | "license": "MIT", | |
| 4335 | "funding": { | |
| 4336 | "type": "github", | |
| 4337 | "url": "https://github.com/sponsors/wooorm" | |
| 4338 | } | |
| 4339 | }, | |
| 4340 | 2378 | "node_modules/tslib": { |
| 4341 | 2379 | "version": "2.8.1", |
| 4342 | 2380 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", |
| 4343 | 2381 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", |
| 4344 | 2382 | "license": "0BSD" |
| 4345 | 2383 | }, |
| 2384 | "node_modules/type-detect": { | |
| 2385 | "version": "1.0.0", | |
| 2386 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", | |
| 2387 | "integrity": "sha512-f9Uv6ezcpvCQjJU0Zqbg+65qdcszv3qUQsZfjdRbWiZ7AMenrX1u0lNk9EoWWX6e1F+NULyg27mtdeZ5WhpljA==", | |
| 2388 | "license": "MIT", | |
| 2389 | "engines": { | |
| 2390 | "node": "*" | |
| 2391 | } | |
| 2392 | }, | |
| 4346 | 2393 | "node_modules/typed-query-selector": { |
| 4347 | 2394 | "version": "2.12.0", |
| 4348 | 2395 | "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", |
| ... | ... | @@ -4353,7 +2400,6 @@ |
| 4353 | 2400 | "version": "5.8.3", |
| 4354 | 2401 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", |
| 4355 | 2402 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", |
| 4356 | "devOptional": true, | |
| 4357 | 2403 | "license": "Apache-2.0", |
| 4358 | 2404 | "peer": true, |
| 4359 | 2405 | "bin": { |
| ... | ... | @@ -4394,28 +2440,8 @@ |
| 4394 | 2440 | "version": "7.8.0", |
| 4395 | 2441 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", |
| 4396 | 2442 | "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", |
| 4397 | "devOptional": true, | |
| 4398 | 2443 | "license": "MIT" |
| 4399 | 2444 | }, |
| 4400 | "node_modules/unified": { | |
| 4401 | "version": "11.0.5", | |
| 4402 | "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", | |
| 4403 | "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", | |
| 4404 | "license": "MIT", | |
| 4405 | "dependencies": { | |
| 4406 | "@types/unist": "^3.0.0", | |
| 4407 | "bail": "^2.0.0", | |
| 4408 | "devlop": "^1.0.0", | |
| 4409 | "extend": "^3.0.0", | |
| 4410 | "is-plain-obj": "^4.0.0", | |
| 4411 | "trough": "^2.0.0", | |
| 4412 | "vfile": "^6.0.0" | |
| 4413 | }, | |
| 4414 | "funding": { | |
| 4415 | "type": "opencollective", | |
| 4416 | "url": "https://opencollective.com/unified" | |
| 4417 | } | |
| 4418 | }, | |
| 4419 | 2445 | "node_modules/unique-names-generator": { |
| 4420 | 2446 | "version": "4.7.1", |
| 4421 | 2447 | "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz", |
| ... | ... | @@ -4425,91 +2451,10 @@ |
| 4425 | 2451 | "node": ">=8" |
| 4426 | 2452 | } |
| 4427 | 2453 | }, |
| 4428 | "node_modules/unist-util-is": { | |
| 4429 | "version": "6.0.0", | |
| 4430 | "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", | |
| 4431 | "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", | |
| 4432 | "license": "MIT", | |
| 4433 | "dependencies": { | |
| 4434 | "@types/unist": "^3.0.0" | |
| 4435 | }, | |
| 4436 | "funding": { | |
| 4437 | "type": "opencollective", | |
| 4438 | "url": "https://opencollective.com/unified" | |
| 4439 | } | |
| 4440 | }, | |
| 4441 | "node_modules/unist-util-position": { | |
| 4442 | "version": "5.0.0", | |
| 4443 | "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", | |
| 4444 | "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", | |
| 4445 | "license": "MIT", | |
| 4446 | "dependencies": { | |
| 4447 | "@types/unist": "^3.0.0" | |
| 4448 | }, | |
| 4449 | "funding": { | |
| 4450 | "type": "opencollective", | |
| 4451 | "url": "https://opencollective.com/unified" | |
| 4452 | } | |
| 4453 | }, | |
| 4454 | "node_modules/unist-util-position-from-estree": { | |
| 4455 | "version": "2.0.0", | |
| 4456 | "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", | |
| 4457 | "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", | |
| 4458 | "license": "MIT", | |
| 4459 | "dependencies": { | |
| 4460 | "@types/unist": "^3.0.0" | |
| 4461 | }, | |
| 4462 | "funding": { | |
| 4463 | "type": "opencollective", | |
| 4464 | "url": "https://opencollective.com/unified" | |
| 4465 | } | |
| 4466 | }, | |
| 4467 | "node_modules/unist-util-stringify-position": { | |
| 4468 | "version": "4.0.0", | |
| 4469 | "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", | |
| 4470 | "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", | |
| 4471 | "license": "MIT", | |
| 4472 | "dependencies": { | |
| 4473 | "@types/unist": "^3.0.0" | |
| 4474 | }, | |
| 4475 | "funding": { | |
| 4476 | "type": "opencollective", | |
| 4477 | "url": "https://opencollective.com/unified" | |
| 4478 | } | |
| 4479 | }, | |
| 4480 | "node_modules/unist-util-visit": { | |
| 4481 | "version": "5.0.0", | |
| 4482 | "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", | |
| 4483 | "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", | |
| 4484 | "license": "MIT", | |
| 4485 | "dependencies": { | |
| 4486 | "@types/unist": "^3.0.0", | |
| 4487 | "unist-util-is": "^6.0.0", | |
| 4488 | "unist-util-visit-parents": "^6.0.0" | |
| 4489 | }, | |
| 4490 | "funding": { | |
| 4491 | "type": "opencollective", | |
| 4492 | "url": "https://opencollective.com/unified" | |
| 4493 | } | |
| 4494 | }, | |
| 4495 | "node_modules/unist-util-visit-parents": { | |
| 4496 | "version": "6.0.1", | |
| 4497 | "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", | |
| 4498 | "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", | |
| 4499 | "license": "MIT", | |
| 4500 | "dependencies": { | |
| 4501 | "@types/unist": "^3.0.0", | |
| 4502 | "unist-util-is": "^6.0.0" | |
| 4503 | }, | |
| 4504 | "funding": { | |
| 4505 | "type": "opencollective", | |
| 4506 | "url": "https://opencollective.com/unified" | |
| 4507 | } | |
| 4508 | }, | |
| 4509 | 2454 | "node_modules/update-browserslist-db": { |
| 4510 | "version": "1.1.3", | |
| 4511 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", | |
| 4512 | "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", | |
| 2455 | "version": "1.2.3", | |
| 2456 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", | |
| 2457 | "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", | |
| 4513 | 2458 | "funding": [ |
| 4514 | 2459 | { |
| 4515 | 2460 | "type": "opencollective", |
| ... | ... | @@ -4536,34 +2481,6 @@ |
| 4536 | 2481 | "browserslist": ">= 4.21.0" |
| 4537 | 2482 | } |
| 4538 | 2483 | }, |
| 4539 | "node_modules/vfile": { | |
| 4540 | "version": "6.0.3", | |
| 4541 | "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", | |
| 4542 | "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", | |
| 4543 | "license": "MIT", | |
| 4544 | "dependencies": { | |
| 4545 | "@types/unist": "^3.0.0", | |
| 4546 | "vfile-message": "^4.0.0" | |
| 4547 | }, | |
| 4548 | "funding": { | |
| 4549 | "type": "opencollective", | |
| 4550 | "url": "https://opencollective.com/unified" | |
| 4551 | } | |
| 4552 | }, | |
| 4553 | "node_modules/vfile-message": { | |
| 4554 | "version": "4.0.2", | |
| 4555 | "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", | |
| 4556 | "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", | |
| 4557 | "license": "MIT", | |
| 4558 | "dependencies": { | |
| 4559 | "@types/unist": "^3.0.0", | |
| 4560 | "unist-util-stringify-position": "^4.0.0" | |
| 4561 | }, | |
| 4562 | "funding": { | |
| 4563 | "type": "opencollective", | |
| 4564 | "url": "https://opencollective.com/unified" | |
| 4565 | } | |
| 4566 | }, | |
| 4567 | 2484 | "node_modules/vscode-oniguruma": { |
| 4568 | 2485 | "version": "2.0.1", |
| 4569 | 2486 | "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", |
| ... | ... | @@ -4582,6 +2499,12 @@ |
| 4582 | 2499 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", |
| 4583 | 2500 | "license": "MIT" |
| 4584 | 2501 | }, |
| 2502 | "node_modules/warp10": { | |
| 2503 | "version": "2.1.0", | |
| 2504 | "resolved": "https://registry.npmjs.org/warp10/-/warp10-2.1.0.tgz", | |
| 2505 | "integrity": "sha512-krhkqzJdUxAZv2Cx0Gz6dN1r7TTrG9RDewkDHBbJQIqbNTCdB5ZUHVh7VkA4DgrKW4ZXPPUQKCwmI/3btDse9A==", | |
| 2506 | "license": "MIT" | |
| 2507 | }, | |
| 4585 | 2508 | "node_modules/webdriver-bidi-protocol": { |
| 4586 | 2509 | "version": "0.3.6", |
| 4587 | 2510 | "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.6.tgz", |
| ... | ... | @@ -4641,12 +2564,6 @@ |
| 4641 | 2564 | "node": ">=10" |
| 4642 | 2565 | } |
| 4643 | 2566 | }, |
| 4644 | "node_modules/yallist": { | |
| 4645 | "version": "3.1.1", | |
| 4646 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", | |
| 4647 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", | |
| 4648 | "license": "ISC" | |
| 4649 | }, | |
| 4650 | 2567 | "node_modules/yargs": { |
| 4651 | 2568 | "version": "17.7.2", |
| 4652 | 2569 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", |
| ... | ... | @@ -4692,16 +2609,6 @@ |
| 4692 | 2609 | "funding": { |
| 4693 | 2610 | "url": "https://github.com/sponsors/colinhacks" |
| 4694 | 2611 | } |
| 4695 | }, | |
| 4696 | "node_modules/zwitch": { | |
| 4697 | "version": "2.0.4", | |
| 4698 | "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", | |
| 4699 | "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", | |
| 4700 | "license": "MIT", | |
| 4701 | "funding": { | |
| 4702 | "type": "github", | |
| 4703 | "url": "https://github.com/sponsors/wooorm" | |
| 4704 | } | |
| 4705 | 2612 | } |
| 4706 | 2613 | } |
| 4707 | 2614 | } |
package.json+10-19| ... | ... | @@ -1,47 +1,38 @@ |
| 1 | 1 | { |
| 2 | 2 | "type": "module", |
| 3 | 3 | "dependencies": { |
| 4 | "@clo/markodown": "npm:@paperclover/markodown@rc", | |
| 4 | 5 | "@hono/node-server": "1.14.3", |
| 5 | "@mdx-js/mdx": "3.1.0", | |
| 6 | "@marko/compiler": "^5.39.55", | |
| 7 | "@types/node": "24.0.10", | |
| 8 | "browserslist": "4.28.1", | |
| 6 | 9 | "codemirror": "6.0.1", |
| 7 | "dashjs": "^5.0.3", | |
| 8 | "devalue": "^5.3.2", | |
| 10 | "dashjs": "5.0.3", | |
| 11 | "devalue": "5.6.2", | |
| 9 | 12 | "esbuild": "0.25.5", |
| 10 | 13 | "hls.js": "1.6.5", |
| 11 | "hono": "^4.10.3", | |
| 14 | "hono": "4.11.9", | |
| 12 | 15 | "lib": "file:lib", |
| 13 | "marko": "6.0.82", | |
| 16 | "marko": "^5.38.24", | |
| 14 | 17 | "msgpackr": "1.11.5", |
| 15 | 18 | "puppeteer": "24.23.0", |
| 16 | "rehype-mdx-code-props": "^3.0.1", | |
| 17 | 19 | "source-map-support": "0.5.21", |
| 20 | "typescript": "5.8.3", | |
| 18 | 21 | "unique-names-generator": "4.7.1", |
| 19 | 22 | "vscode-oniguruma": "2.0.1", |
| 20 | 23 | "vscode-textmate": "9.2.0" |
| 21 | 24 | }, |
| 22 | "devDependencies": { | |
| 23 | "@types/node": "24.0.10", | |
| 24 | "baseline-browser-mapping": "^2.9.14", | |
| 25 | "typescript": "5.8.3" | |
| 26 | }, | |
| 27 | 25 | "imports": { |
| 28 | 26 | "#sitegen": "./framework/lib/sitegen.ts", |
| 29 | 27 | "#sitegen/*": "./framework/lib/*.ts", |
| 30 | 28 | "#jsx/jsx-runtime": "./lib/render/jsx-runtime.ts", |
| 31 | 29 | "#jsx/jsx-dev-runtime": "./lib/render/jsx-runtime.ts", |
| 32 | "#marko/runtime": "./framework/engine/marko-runtime.ts", | |
| 33 | "#marko/html": { | |
| 34 | "types": "marko/html", | |
| 35 | "production": "marko/production", | |
| 36 | "node": "marko/debug/html" | |
| 37 | }, | |
| 38 | 30 | "#hono": "hono", |
| 39 | 31 | "#hono/conninfo": { |
| 40 | 32 | "bun": "hono/bun", |
| 41 | 33 | "deno": "hono/deno", |
| 42 | 34 | "node": "@hono/node-server/conninfo", |
| 43 | 35 | "worker": "hono/cloudflare-workers" |
| 44 | }, | |
| 45 | "#mdx": "./framework/mdx-hack.tsx" | |
| 36 | } | |
| 46 | 37 | } |
| 47 | 38 | } |
src/blog/drafts/webdev/marko-intro.markodown+2-2| ... | ... | @@ -322,7 +322,7 @@ export const transitionDate = 1735639200000; |
| 322 | 322 | >${formatQuestionTimestamp(date)}</time> |
| 323 | 323 | </a> |
| 324 | 324 | |
| 325 | <CloverMarkdown ...{ text } /> | |
| 325 | <QuestionMarkdown=text/> | |
| 326 | 326 | </> |
| 327 | 327 | |
| 328 | 328 | // this singleton script will make all the '<time>' tags clickable. |
| ... | ... | @@ -330,7 +330,7 @@ client import "./clickable-links.client.ts"; |
| 330 | 330 | |
| 331 | 331 | import type { Question } from "@/q+a/models/Question.ts"; |
| 332 | 332 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; |
| 333 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; | |
| 333 | import { QuestionMarkdown } from "@/q+a/markdown.tsx"; | |
| 334 | 334 | ``` |
| 335 | 335 | |
| 336 | 336 | The integration is great, `client import` is quite a magical concept, and I've |
src/blog/pages/community-translations.mdo created+18| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | --- | |
| 2 | meta: | |
| 3 | title: clover's community translations | |
| 4 | --- | |
| 5 | ||
| 6 | <main> | |
| 7 | <nav>[back to the home page](/)</nav> | |
| 8 | ||
| 9 | # blog community translations | |
| 10 | ||
| 11 | clover blog posts are translated into other languages by my friends or other | |
| 12 | members of the (world's) community. if you natively speak a language that i | |
| 13 | don't have a blog page translated into, and would like to help spread my words | |
| 14 | to more readers, please get in touch. | |
| 15 | ||
| 16 | [`me@paperclover.net`](mailto:me@paperclover.net) | |
| 17 | ||
| 18 | </main> |
src/blog/pages/community-translations.mdx deleted-14| ... | ... | @@ -1,14 +0,0 @@ |
| 1 | export const meta = { title: "about clover's community translations" }; | |
| 2 | export const layout = { default: ({ content }) => <main>{content}</main> }; | |
| 3 | ||
| 4 | <a href="/">back to the home page</a> | |
| 5 | ||
| 6 | # blog community translations | |
| 7 | ||
| 8 | clover blog posts are translated into other languages by my friends or other | |
| 9 | members of the (world's) community. if you natively speak a language that i | |
| 10 | don't have a blog page translated into, and would like to help spread my words | |
| 11 | to more readers, please get in touch. | |
| 12 | ||
| 13 | <code><a href="mailto:me@paperclover.net">me@paperclover.net</a></code> | |
| 14 |
src/blog/pages/webdev/one-year-next-app-router/en.mdo created+742| ... | ... | @@ -0,0 +1,742 @@ |
| 1 | --- | |
| 2 | layout: "@/blog/tags/blog-layout.marko" | |
| 3 | meta: | |
| 4 | title: One Year with Next.js App Router — Why We're Moving On | |
| 5 | description: A critique of React Server Components and Next.js 15. | |
| 6 | keywords: ["webdev", "technical analysis", "opinion"] | |
| 7 | authors: ["clover caruso"] | |
| 8 | embed: | |
| 9 | thumbnail: /open-graph/next-js.png | |
| 10 | canonical: /blog/webdev/one-year-next-app-router | |
| 11 | date: "Oct 21st, 2025" | |
| 12 | --- | |
| 13 | ||
| 14 | As I've been using [Next.js] professionally on my employer's web app, I find the | |
| 15 | core design of their App Router and [React Server Components] (RSC) to be | |
| 16 | extremely frustrating. And it's not small bugs or that the API is confusing, | |
| 17 | but large disagreements about the fundamental design decisions that Vercel and | |
| 18 | the React team made when building it. | |
| 19 | ||
| 20 | The more webdev events I go to, the more I see people who dislike Next.js, but | |
| 21 | still get stuck using it. By the end of this article, I will share how me and | |
| 22 | my colleagues escaped this hell, seamlessly migrating our entire frontend to | |
| 23 | [TanStack Start]. | |
| 24 | ||
| 25 | [Next.js]: https://nextjs.org | |
| 26 | [React Server Components]: https://react.dev/reference/rsc/server-components | |
| 27 | ||
| 28 | <table-of-contents /> | |
| 29 | ||
| 30 | <h2#technical-review>A Technical Review: What are Server Components?</> | |
| 31 | ||
| 32 | The pitch of RSC is that components are put into two categories, | |
| 33 | <b.server>"server"</b> components and <b.client>"client"</b> | |
| 34 | components. Server components don't have `useState`, `useEffect`, but can be | |
| 35 | `async function`s and refer to backend tools like directly calling into a | |
| 36 | database. Client components are the existing | |
| 37 | model, where there is code on the backend to generate HTML text and frontend | |
| 38 | code to manage the DOM using `window.document.*`. | |
| 39 | ||
| 40 | > The first disaster: naming!! React is now using the wors | |
| 41 | > <b.server>"server"</b> and <b.client>"client"</b> to refer to | |
| 42 | > a very specific things, ignoring their existing definitions. This would be | |
| 43 | > fine, except <b.client>Client</b> components can run on the backend | |
| 44 | > too! In this article, I'll be using the terms <b>"backend"</b> and | |
| 45 | > <b>"frontend"</b> to describe the two execution environments that web apps | |
| 46 | > exist in: a Node.js process and a Web browser, respectively. | |
| 47 | ||
| 48 | This <b.server>Server</b>/<b.client>Client</b> component model | |
| 49 | is interesting. Since built-ins like `<Suspense />` get serialized across the | |
| 50 | network, data fetching can be very trivially modeled with async | |
| 51 | <b.server>server components</b>, and the fallback UI works as if it were | |
| 52 | client-side. | |
| 53 | ||
| 54 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 55 | // For this article, server components will be highlighted in red | |
| 56 | export default async function Page({ params }) { | |
| 57 | // Page params are given as a resolved promise | |
| 58 | const { username } = await params; | |
| 59 | ||
| 60 | // The components `UserInfo` and `UserPostList` will be run at the same | |
| 61 | // time. Once `UserInfo` is ready, the visitor will see the page with a | |
| 62 | // `PostListSkeleton` if the post list is not yet ready. | |
| 63 | return <main> | |
| 64 | <UserInfo username={username} /> | |
| 65 | ||
| 66 | <Suspense fallback={<PostListSkeleton />}> | |
| 67 | <UserPostList username={username} /> | |
| 68 | </Suspense> | |
| 69 | </main> | |
| 70 | } | |
| 71 | ||
| 72 | // Waterfalls are avoided by having multiple components, which | |
| 73 | // are all evaluated at the same time. | |
| 74 | ||
| 75 | async function UserInfo({ username }) { | |
| 76 | const user = await fetchUserInfo(username); | |
| 77 | return <> | |
| 78 | <h1>{user.displayName}</h1> | |
| 79 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 80 | </> | |
| 81 | } | |
| 82 | ||
| 83 | async function UserPostList({ username }) { | |
| 84 | const posts = await fetchUserPostList(username); | |
| 85 | return /* post list ui omitted for brevity */; | |
| 86 | } | |
| 87 | ``` | |
| 88 | ||
| 89 | If we ignore the 40kB gzipped bundle size of React itself, the above example | |
| 90 | has zero JavaScript for the UI and data fetching &mdash; it just streams the | |
| 91 | markup! For example, the imagined markdown parser within the `<Markdown />` | |
| 92 | component stays on the backend. When an interactive frontend is needed, <b.client>Client | |
| 93 | components</b> can be created by putting them in a file starting with `"use | |
| 94 | client"`. | |
| 95 | ||
| 96 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 97 | "use client"; // This comment marks the file for client-side bundling. | |
| 98 | ||
| 99 | export function CopyButton({ url }) { | |
| 100 | return <> | |
| 101 | <span>{url}</span> | |
| 102 | <button onClick={() => { | |
| 103 | const full = new URL(url, location.href); | |
| 104 | navigator.clipboard.writeText(full.href); | |
| 105 | // omitting error handling, success ui, styles | |
| 106 | }}>copy</button> | |
| 107 | </> | |
| 108 | } | |
| 109 | ``` | |
| 110 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 111 | export function Card() { | |
| 112 | return <article> | |
| 113 | <header> | |
| 114 | {/* Make the browser import the copy button */} | |
| 115 | <CopyButton url="/q+a/2506010139" /> | |
| 116 | </header> | |
| 117 | <p> | |
| 118 | {/* Process markdown on the backend */} | |
| 119 | <Markdown content=".........." /> | |
| 120 | </p> | |
| 121 | </article> | |
| 122 | } | |
| 123 | ``` | |
| 124 | ||
| 125 | <h2 id="real-world-pitfalls">Real-world Pitfalls of the App Router</> | |
| 126 | ||
| 127 | After quitting [Bun] as a runtime engineer (I implemented [Server Components | |
| 128 | bundling] and [a RSC template][bun-rsc] there), I joined a small company working on the | |
| 129 | front lines: a Next.js app with a Hono backend. The following notes are | |
| 130 | simplifications from the real world problems I've encountered when trying to | |
| 131 | maintain and develop new features. As a result of all of these, everyone's time | |
| 132 | is wasted either working around design flaws, or explaining to each other why | |
| 133 | what should be a non-issue is an immovable object. | |
| 134 | ||
| 135 | [Bun]: https://bun.com | |
| 136 | [Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 137 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 138 | ||
| 139 | <h3 id='optimistic-updates'>Optimistic Updates are Impossible</> | |
| 140 | ||
| 141 | The Next.js documentation for performing mutations [does not mention optimistic | |
| 142 | updates][nextjs-updating-data]; it appears this case was not thought about. | |
| 143 | Components rendered by the <b.server>React Server</b>, by design, can | |
| 144 | not be modified after mounting. Elements that could change need to be inside a | |
| 145 | client component, but data fetching cannot happen on the client components, | |
| 146 | even during SSR on the backend. This results in awkwardly small server | |
| 147 | components that only do data fetching and then have a client component that | |
| 148 | contains a mostly-static version of the page. | |
| 149 | ||
| 150 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 151 | ||
| 152 | export default async function Page() { | |
| 153 | const user = await fetchUserInfo(username); | |
| 154 | return <ProfileLayout> | |
| 155 | <UserProfile user={user} /> | |
| 156 | </ProfileLayout>; | |
| 157 | } | |
| 158 | ``` | |
| 159 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 160 | ||
| 161 | "use client"; // Must separate the client code into a second file! | |
| 162 | ||
| 163 | export function UserProfile({ user: initialUser }) { | |
| 164 | // There are many great state management libraries out there; | |
| 165 | // for simplicity, this example will use one state cell. | |
| 166 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 167 | ||
| 168 | async function onEdit(newUser) { | |
| 169 | optimisticUpdateUser(newUser); | |
| 170 | const resp = await fetch("...", { | |
| 171 | method: 'POST', | |
| 172 | body: JSON.stringify(newUser), | |
| 173 | ... // (headers, credentials, tracing, and more) | |
| 174 | }) | |
| 175 | if (!resp.ok) /* always remember to test for errors! */ | |
| 176 | } | |
| 177 | ||
| 178 | return <main>{/* user interface with editable fields... */}</main>: | |
| 179 | } | |
| 180 | ``` | |
| 181 | ||
| 182 | As more of the page needs interactivity, it gets messier trying to keep the | |
| 183 | static parts truly server-side. On the work app, nearly every piece of UI | |
| 184 | displays some dynamic data. A [`WebSocket`][ws] synchronizes data live as it | |
| 185 | updates (for example, a user card's online state along with their basic | |
| 186 | profile). Since these component setups are harder to understand and maintain | |
| 187 | for engineers, almost all of our pages are entirely `"use client"` with a | |
| 188 | `page.tsx` that defines the data fetching. | |
| 189 | ||
| 190 | A more concrete example of what this looks like in practice with the | |
| 191 | data-fetching library we use at work, [TanStack Query]. | |
| 192 | ||
| 193 | [TanStack Query]: https://github.com/tanstack/query#readme | |
| 194 | ||
| 195 | ```ts filename="src/queries/users.ts" | |
| 196 | // At work, there is a helper function `defineQuery` for type safety. | |
| 197 | // Fetchers are trivial and can run on the backend or the frontend. | |
| 198 | export const queryUserInfo = (username) => ({ | |
| 199 | queryKey: ['user', username], | |
| 200 | queryFn: async ({ ... }) => /* fetch data */ | |
| 201 | }); | |
| 202 | ``` | |
| 203 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 204 | export default async function Page({ params }) { | |
| 205 | const { username } = await params; | |
| 206 | ||
| 207 | // There's no global state in the React Server. Since layouts | |
| 208 | // are executed in parallel, the TanStack `QueryClient` has to | |
| 209 | // be reconstructed multiple times per route. | |
| 210 | const queryClient = new QueryClient(); | |
| 211 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 212 | ||
| 213 | // HydrationBoundary is a client component that passes JSON | |
| 214 | // data from the React server to the client component. | |
| 215 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 216 | <ClientPage /> | |
| 217 | </HydrationBoundary>; | |
| 218 | } | |
| 219 | ``` | |
| 220 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 221 | "use client"; | |
| 222 | export function ClientPage() { | |
| 223 | const { username } = useParams(); | |
| 224 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 225 | ||
| 226 | // ... some hooks | |
| 227 | ||
| 228 | return <main> | |
| 229 | {/* ... an interactive web page */} | |
| 230 | </main>; | |
| 231 | } | |
| 232 | ``` | |
| 233 | ||
| 234 | This example has to be three separate files because of the rules of server | |
| 235 | component bundling. (The client component needs `"use client"`, and server | |
| 236 | component files often can't be imported on the client due to server-only | |
| 237 | imports.). In the Pages router, this could've been a single file because of the | |
| 238 | tree-shaking that `getStaticProps` and `getServerSideProps` has. | |
| 239 | ||
| 240 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 241 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 242 | ||
| 243 | <h3 id='redundant-fetches'>Every Navigation is Another Fetch</> | |
| 244 | ||
| 245 | Since the App Router starts every page as a server component, with (ideally) | |
| 246 | small areas of interactivity, a navigation to a new page *has* to fetch the | |
| 247 | Next.js server, regardless of what data the client already has available! Even | |
| 248 | with a a `loading.tsx` file, opening `/`, navigating to `/other`, and then | |
| 249 | going back to `/` will show the loading state while it re-fetches the homepage. | |
| 250 | ||
| 251 | The only case this works is for **perfectly static content**, where instant | |
| 252 | navigations and prefetching work great. But **web apps are not static**, they | |
| 253 | have lots of dynamic content. Being logged in affects the homepage, which is | |
| 254 | infuriating because the client literally has everything needed to display the | |
| 255 | page instantly. It's not like the cookies changed. | |
| 256 | ||
| 257 | > **aside**: In further testing on a blank project, I observe cases where the | |
| 258 | > Next frontend code would pre-fetch routes, but **without any real contents**. | |
| 259 | > On the hello world example, this was a 1.8kB RSC payload that pointed to 2 | |
| 260 | > different JS chunks 4 separate times. This is just pure waste of our | |
| 261 | > bandwidth and egress, especially considering all of this information is | |
| 262 | > re-fetched when I actually click the link. | |
| 263 | > | |
| 264 | > ```json whitespace="pre-wrap" | |
| 265 | > 1:"$Sreact.fragment" | |
| 266 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 267 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 268 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 269 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 270 | > 7:"$Sreact.suspense" | |
| 271 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 272 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 273 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 274 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 275 | > ``` | |
| 276 | > | |
| 277 | > In review, I found there is actually some content in here: the loading state. | |
| 278 | > Do you see it? | |
| 279 | > | |
| 280 | > ```json | |
| 281 | > ["$","div","l",{"children":"loading..."}] | |
| 282 | > ``` | |
| 283 | > | |
| 284 | > It's still a lot of waste, since all of this data gets re-emitted in the | |
| 285 | > actual page RSC. | |
| 286 | ||
| 287 | The solution to this appears to be [`staleTime`][nextjs-stale], but it's marked | |
| 288 | experimental and "not recommended for production". The fact this is a | |
| 289 | non-default afterthought configuration option is embarrassing. Even if we used | |
| 290 | it, you cannot make multiple pages that refer to the same underlying data share | |
| 291 | any of it. | |
| 292 | ||
| 293 | One form of loading state that cannot be represented with the App Router is | |
| 294 | having a page such as a page like a git project's issue page, and clicking on a | |
| 295 | user name to navigate to their profile page. With `loading.tsx`, the entire | |
| 296 | page is a skeleton, but when modeling these queries with TanStack Query it is | |
| 297 | possible to show the username and avatar instantly while the user's bio and | |
| 298 | repositories are fetched in. Server components don't support this form of | |
| 299 | navigation because the data is only available in rendered components, so it | |
| 300 | must be re-fetched. | |
| 301 | ||
| 302 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 303 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 304 | ||
| 305 | In our Next.js site, we have this line of code on our server component data | |
| 306 | fetchers to make soft navigations faster by skipping the data fetch phase all | |
| 307 | together. | |
| 308 | ||
| 309 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 310 | export function serverSidePrefetchQueries(queries) { | |
| 311 | if ((await headers()).get("next-url")) { | |
| 312 | // This is a soft-navigation. SKIP the prefetching to make it faster. | |
| 313 | // The client might already have this data, and if not, they have the | |
| 314 | // loading state. Ideally, this server request wouldn't exist -- The | |
| 315 | // client side has nearly ALL the code since the app is written mostly | |
| 316 | // as client components. Kind of a design flaw of the App router TBH. | |
| 317 | return; | |
| 318 | } | |
| 319 | // ... data prefetching-logic ... | |
| 320 | } | |
| 321 | ``` | |
| 322 | ||
| 323 | In addition to this, `loading.tsx` should contain the `useQuery` calls so that | |
| 324 | while the network request for the empty RSC happens, the data is being fetched | |
| 325 | if it actually is needed. In fact, the `loading.tsx` state can just be the | |
| 326 | actual client component, and you'll see the client page. | |
| 327 | ||
| 328 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 329 | "use client"; | |
| 330 | export default function PageLoadingSkeleton() { | |
| 331 | return <ClientPage />; | |
| 332 | } | |
| 333 | ``` | |
| 334 | ||
| 335 | > At work, we just make our `loading.tsx` files contain the `useQuery` | |
| 336 | > calls and show a skeleton. This is because when Next.js loads the actual Server | |
| 337 | > Component, no matter what, the entire page re-mounts. No VDOM diffing here, | |
| 338 | > meaning all hooks (`useState`) will reset slightly after the request | |
| 339 | > completes. I tried to reproduce a simple case where I was *begging* Next.js to | |
| 340 | > just *update the existing DOM* and preserve state, but it just doesn't. | |
| 341 | > Thankfully, the time the blank RSC call takes is short enough. | |
| 342 | ||
| 343 | <h3 id='layout-restrictions'>Layouts are Artificially Restricted</> | |
| 344 | ||
| 345 | Layouts can perform data fetching, but they can't observe or alter the request | |
| 346 | in any way. This is done so that Next.js can fetch and cache layouts whenever they | |
| 347 | want. In every other framework, layouts are just regular components that have | |
| 348 | no feature difference compared to page components. | |
| 349 | ||
| 350 | Fetching layouts in isolation is a cute idea, but it ends up being silly | |
| 351 | because it also means that any data fetching has to be re-done per layout. You | |
| 352 | can't share a `QueryClient`; instead, you must rely on their [monkey-patched | |
| 353 | `fetch`][nextjs-fetch] to cache the same `GET` request like they promise. | |
| 354 | ||
| 355 | When a coworker asks me about why Next.js rejects some code, I've given up on | |
| 356 | explaining the technical intricacies and just say *"It's a Next.js Skill Issue, | |
| 357 | I'm going to blow it up soon don't worry."* These rules are too hard for normal | |
| 358 | developers to understand. | |
| 359 | ||
| 360 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 361 | ||
| 362 | <h3 id='rsc-payload'>You Still Download All the Content Twice</> | |
| 363 | ||
| 364 | Unlike the ["Islands Architecture"][islands], Server Components still have to | |
| 365 | be hydrated on the frontend to support `Suspense` and preserving client | |
| 366 | component state. When doing soft navigations, the "RSC Payload" (which is not | |
| 367 | HTML at all) is retrieved by `fetch`. On a fresh reload, HTML is needed for the | |
| 368 | [first paint], but the information about Client components and `Suspense` is | |
| 369 | not contained within that HTML. React's solution is to **send a second copy of | |
| 370 | the entire page's markup**. An example of what a Next.js production server | |
| 371 | would send in a dynamic page render would be something like this: | |
| 372 | ||
| 373 | [first paint]: https://web.dev/articles/fcp | |
| 374 | ||
| 375 | ```html filename="GET /user/clover" | |
| 376 | <!DOCTYPE html> | |
| 377 | <html> | |
| 378 | <head> | |
| 379 | {link and meta tags} | |
| 380 | </head> | |
| 381 | <body> | |
| 382 | {server side render} | |
| 383 | <script> | |
| 384 | // a bootstrap script that sets up global `__next_f` as | |
| 385 | // an array. once React loads, this `.push` function | |
| 386 | // gets overwritten to write new chunks directly to the | |
| 387 | // RSC decoder. this script has some dom helpers too | |
| 388 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 389 | </script> | |
| 390 | <script> | |
| 391 | // the RSC payload for the application shell. | |
| 392 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 393 | </script> | |
| 394 | ||
| 395 | <!-- | |
| 396 | the closing </body> is NOT written yet, since there is a | |
| 397 | suspense boundary not resolved. time passes, and only | |
| 398 | then is more data is written | |
| 399 | --> | |
| 400 | <div class="user-post-list"> | |
| 401 | {server side render of a Suspense boundary} | |
| 402 | </div> | |
| 403 | <script> | |
| 404 | // the RSC payload for the suspense boundary | |
| 405 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 406 | </script> | |
| 407 | ||
| 408 | <!-- HTML and script tags repeat until the entire page is done --> | |
| 409 | </body> | |
| 410 | </html> | |
| 411 | ``` | |
| 412 | ||
| 413 | This solution **doubles the size of the initial HTML payload**. Except it's | |
| 414 | worse, because the RSC payload includes JSON quoted in JS string literals, | |
| 415 | which is a is much less efficient format than HTML. While it seems to compress | |
| 416 | fine with brotli and render fast in the browser, this is wasteful. With the | |
| 417 | hydration pattern, at least the data locally could be re-used for interactivity | |
| 418 | and other pages. | |
| 419 | ||
| 420 | Even on pages that have little to no interactivity, you pay the cost. To use | |
| 421 | the Next.js documentation as an example, loading [its | |
| 422 | homepage](https://nextjs.org/docs) loads an page that is around 750kB (250kB of | |
| 423 | HTML and the 500kB of script tags), and content is in there twice. | |
| 424 | ||
| 425 | You can verify that by pressing <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> | |
| 426 | on Mac or <kbd>Ctrl</kbd> + <kbd>u</kbd> on other platforms. And then | |
| 427 | <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> to locate any string of the | |
| 428 | blog, such as "building full-stack web applications". It's there twice. And | |
| 429 | **there is no way around this**, since it's a fundamental piece of React Server | |
| 430 | Components. | |
| 431 | ||
| 432 | This RSC format certainly has more waste. But I really don't feel like digging into | |
| 433 | why the string `/_next/static/chunks/6192a3719cda7dcc.js` appears 27 separate | |
| 434 | times. What the hell, guys? Is your bandwidth free??? | |
| 435 | ||
| 436 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 437 | ||
| 438 | <h3 id='turbopack'>Turbopack Sucks</> | |
| 439 | ||
| 440 | This section is not constructive. | |
| 441 | ||
| 442 | - Turbopack isn't fast | |
| 443 | - Turbopack emits code that is hard to debug in a debugger (in development mode) | |
| 444 | - Turbopack throws bad error messages in many cases | |
| 445 | ||
| 446 | I wouldn't have given this point a section in the blog normally, but I want to | |
| 447 | point out three actual examples directly from the project. | |
| 448 | ||
| 449 | The first is a place where during some refactoring to satisfy the Server/Client | |
| 450 | component models, I accidentally made a Client component `async`. This one was | |
| 451 | quite annoying because it didn't say at all where the issue was, but only | |
| 452 | contained the <b.server>server</b> stack trace. | |
| 453 | ||
| 454 |  | |
| 455 | ||
| 456 | Another case of a terrible error message: | |
| 457 | ||
| 458 |  | |
| 459 | ||
| 460 | > After fixing the underlying issue in this second error (which I cannot recall), | |
| 461 | > the Dev server hung and had to be restarted to recover. | |
| 462 | ||
| 463 | The final one is the dozen times I place a debugger breakpoint and the | |
| 464 | variable name `hello` gets turned into | |
| 465 | `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` | |
| 466 | and other bullshit. | |
| 467 | ||
| 468 | Okay. This all sucks. What can we do? | |
| 469 | ||
| 470 | <h2 id='ditching-nextjs'>Seamlessly Ditching Next.js and Vercel at Work</> | |
| 471 | ||
| 472 | There are two types of web projects: | |
| 473 | ||
| 474 | - A web site with mostly static content. | |
| 475 | - A web app with majorly dynamic and interactive components. | |
| 476 | ||
| 477 | And Next.js is the wrong tool for both of these jobs. If you're in the first | |
| 478 | category with a static web site, go for [Astro] or [Fresh]. For everyone who | |
| 479 | needs the full power of React, this section is about how I replaced the vendor | |
| 480 | locked Next with [TanStack Start], incrementally and seamlessly. | |
| 481 | ||
| 482 | [Astro]: https://astro.build/ | |
| 483 | [Fresh]: https://fresh.deno.dev/ | |
| 484 | [TanStack Start]: https://tanstack.com/start/latest | |
| 485 | ||
| 486 | It started with this Vite config. | |
| 487 | ||
| 488 | ```ts filename="vite.config.ts" | |
| 489 | const config = defineConfig(({ mode }) => { | |
| 490 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 491 | return { | |
| 492 | // Use the Next.js default port 3000 | |
| 493 | server: { port: 3000 }, | |
| 494 | // Use the Next.js default env prefix "NEXT_PUBLIC_" | |
| 495 | define: Object.fromEntries(Object.entries(env).map( | |
| 496 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 497 | plugins: [ | |
| 498 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 499 | tailwindcss(), | |
| 500 | // For ease of understanding from coworkers, I started porting | |
| 501 | // the routes in `src/tanstack-routes`. When the migration was | |
| 502 | // done, it would go back to the default `src/routes`. | |
| 503 | tanstackStart({ | |
| 504 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 505 | }), | |
| 506 | viteReact(), | |
| 507 | ], | |
| 508 | resolve: { | |
| 509 | // The key to the incremental migration: redirect `next` elsewhere | |
| 510 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 511 | conditions: ["tanstack"], | |
| 512 | extensions: [ | |
| 513 | // Allow a file named like `utils/session.tanstack.ts` to | |
| 514 | // override `utils/session.ts` when imported. | |
| 515 | ".tanstack.tsx", ".tanstack.ts", | |
| 516 | // Default import extensions | |
| 517 | ".mjs", ".js", ".mts", ".ts", | |
| 518 | ".jsx", ".tsx", ".json", | |
| 519 | ], | |
| 520 | }, | |
| 521 | }; | |
| 522 | }); | |
| 523 | ``` | |
| 524 | ||
| 525 | Then, I looked for every usage of a Next.js API, and either removed it or made | |
| 526 | a stub for TanStack. For example, `src/tanstack-next/link.tsx` implements | |
| 527 | `next/link`: | |
| 528 | ||
| 529 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 530 | import { Link } from "@tanstack/react-router"; | |
| 531 | import type { LinkProps } from "next/link"; | |
| 532 | ||
| 533 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 534 | return <Link {...rest} to={href as unknown as any} />; | |
| 535 | } | |
| 536 | ``` | |
| 537 | ||
| 538 | > Some of these stubs can be extremely simple. Starting out, my implementation | |
| 539 | > of `useRouter` was just `return {}`, but later I had to add a couple methods | |
| 540 | > to the object. The code here doesn't have to be clean, because it is | |
| 541 | > temporary. | |
| 542 | ||
| 543 | Now, the new site can import nearly every client component by either stubbing | |
| 544 | out the Next.js APIs it needs, or by using the `.tanstack.ts` extension to | |
| 545 | re-implement logic on a file-by-file basis. And shortly after, I got the site's | |
| 546 | homepage to work in TanStack Start, and we merged the branch. | |
| 547 | ||
| 548 |  | |
| 549 | ||
| 550 | > This first PR only supported one of our pages, and was able to do it in a | |
| 551 | > thousand lines of added code, and 40 lines deleted. I had previous patches to | |
| 552 | > remove the few uses of `next/image` and `next/font`. | |
| 553 | ||
| 554 | What was left was porting every other route over. The one thing we lose in | |
| 555 | migrating from Next.js to any other framework is the ability to `await` | |
| 556 | data-fetching functions in the UI. In practice, moving every route into a | |
| 557 | `loader` function made it much more clear what happened when a page was SSR'd. | |
| 558 | For pages that had multiple fetches, these could be combined into a single, | |
| 559 | special API call that would return all of the relevant data for that page. | |
| 560 | ||
| 561 | To re-iterate in bold font: <strong style='color:var(--secondary)'>The | |
| 562 | migration path from Server Components is to just simplify your code &mdash; RSC | |
| 563 | inherently drives you down a chaotic road of things you do not need</strong>. | |
| 564 | Nearly every complex part of our site got easier to understand for all | |
| 565 | engineers. The exception to this was having everyone get used to the new file | |
| 566 | system routing conventions. With enough examples, we all got the hang of it. | |
| 567 | ||
| 568 | With the incremental migration in place, new code did not break the existing | |
| 569 | deployment. TanStack slowly took over the codebase, and we eventually deleted | |
| 570 | all of the Next.js stubs and gained all of the beautiful [type-safety features] | |
| 571 | that the TanStack Router provides. At the end, the site performed faster from | |
| 572 | every angle: Development Mode, Production page load times, Soft navigations, | |
| 573 | and at a lower price than our Next depoyment with Vercel. | |
| 574 | ||
| 575 | [type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 576 | ||
| 577 | We're not the only ones seeing the change. While I try and keep myself off of | |
| 578 | social media, someone sent me [the results of Brian Anglin's work at | |
| 579 | Superwall][superwall-twitter], showing incredible CPU reductions on TanStack | |
| 580 | Start. I also recall ChatGPT switching from Next.js to Remix (random online | |
| 581 | chatter: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]) a year ago. | |
| 582 | ||
| 583 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 584 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 585 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 586 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 587 | ||
| 588 | <h3 id='next-metadata'>`next/metadata` is Great</> | |
| 589 | ||
| 590 | In my opinion, this is one of the only good APIs Next.js has, and was the one | |
| 591 | place in our code where moving to TanStack made things harder to do. Instead of | |
| 592 | worsening the code, I just ported their metadata API into a regular function, | |
| 593 | so everyone can use it. Originally, I had a 1:1 port on NPM, but earlier this | |
| 594 | year I simplified it's API into one short and understandable | |
| 595 | file. As of this blog post, I have added a TanStack-compatible | |
| 596 | `meta.toTags` API, which can be installed from [JSR][lib-jsr], [NPM][lib-npm], | |
| 597 | or simply copied into your project. | |
| 598 | ||
| 599 | > **notice**: Due to time constraints with writing this article, the library | |
| 600 | > has not yet been updated. I'll probably get around to it by the ~~end of this | |
| 601 | > week (Oct 24th)~~ some time soon... As a placeholder, I'm able to share the | |
| 602 | > version that is used at work to my website: | |
| 603 | > [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts). | |
| 604 | ||
| 605 | ```tsx | |
| 606 | // once in your project | |
| 607 | import * as meta from "@clo/lib/meta.ts"; | |
| 608 | ||
| 609 | export const defineHead = meta.toTags.bind(null, { | |
| 610 | // site-wide options | |
| 611 | base: new URL("https://paperclover.net"), | |
| 612 | titleTemplate: (title) => [title, "paper clover"] | |
| 613 | .filter(Boolean).join(' | '), | |
| 614 | // ... | |
| 615 | }); | |
| 616 | ||
| 617 | // for each page... | |
| 618 | export const Route = createFileRoute("/blog")({ | |
| 619 | head: () => | |
| 620 | defineHead({ | |
| 621 | title: "clover's blog", // templated with `titleTemplate` | |
| 622 | description: "a catgirl meows about her technology viewpoints", | |
| 623 | canonical: "/blog", // joined with `base` | |
| 624 | ||
| 625 | // When specified, configures Open Graph and Twitter embed, | |
| 626 | // using the page title and description as the default. | |
| 627 | // The defaults are good, but it supports more options. | |
| 628 | embed: { | |
| 629 | image: "/img/blog.webp", | |
| 630 | }, | |
| 631 | ||
| 632 | // Every exotic meta tag is done with a JSX fragment. This | |
| 633 | // doesn't render React, it just loops through the tags. | |
| 634 | // My goal was to cover the most common 99% of uses. | |
| 635 | extra: <> | |
| 636 | <meta name="site-verification" content="waffles" />, | |
| 637 | </>, | |
| 638 | }), | |
| 639 | ||
| 640 | component: Page, | |
| 641 | }); | |
| 642 | ||
| 643 | function Page() { | |
| 644 | ... | |
| 645 | } | |
| 646 | ``` | |
| 647 | ||
| 648 | My version wasn't concerned with covering the entire space of Next.js's metadata | |
| 649 | object, but instead uses inline JSX to fill that gap. | |
| 650 | ||
| 651 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 652 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 653 | ||
| 654 | <h3 id='vercel-og'>`next/og` is Good Too</> | |
| 655 | ||
| 656 | No strong opinions. I just want to remind everyone that the `@vercel/og` package exists. | |
| 657 | ||
| 658 | <h2 id='experience-feels-like-the-usual'>My Experience Feels like the Usual</> | |
| 659 | ||
| 660 | At the Next.js Conf 2024, everyone there was raving about Server Components. I | |
| 661 | forget exactly who I talked to, but the big people were all in on this. I, | |
| 662 | having implemented the bundler end of RSC, saw a couple of the problems in the | |
| 663 | format. With Next 15 "stabilizing" the App Router last year, many companies are | |
| 664 | building their products on it, realizing these pitfalls first-hand. | |
| 665 | ||
| 666 | I came into the Next.js game late, only starting in June with version 15. | |
| 667 | But everyone I've talked to at events sympathize with my notes. All the people | |
| 668 | I talked to on the subject at Bun's 1.3 Party agreed with me. Even some people | |
| 669 | at Vercel told me they don't like how Next.js is to actually use. | |
| 670 | ||
| 671 | I hope as TanStack Start stabilizes, it becomes the Next.js replacement everyone | |
| 672 | wants. | |
| 673 | ||
| 674 | <h2 id='prefer-respectful-tools'>Prefer Tools that Respect You</> | |
| 675 | ||
| 676 | A lot of in the JavaScript ecosystem is a mess. That mess is why web | |
| 677 | development gets made fun of. There were a lot of times I thought that working | |
| 678 | with the web was an unrecoverable mess, but the mess was actually just the | |
| 679 | commonly-used libraries I surrounded myself with. When that is peeled back, | |
| 680 | modern web development technologies are awesome. | |
| 681 | ||
| 682 | I've been making this website from scratch without any framework since late | |
| 683 | 2024, by writing systems like my own [TUI progress widget][progress], [static | |
| 684 | file proxy][file-cache], incremental build system, and many more components. | |
| 685 | Working on this code has produced some of my best coding sessions (by | |
| 686 | happiness) in years. The viewers of *[paper clover]* get a better quality | |
| 687 | website; the mini-libraries I create get [extracted for public use][lib], | |
| 688 | everyone wins. | |
| 689 | ||
| 690 | This level of from-scratch is too much for most people, especially at the | |
| 691 | workplace. I say that at the minimum, we should only give our attention and | |
| 692 | money to high quality tools that respect us. And Next.js and the company behind | |
| 693 | it, Vercel, are not that. | |
| 694 | ||
| 695 | If you use Next.js, and feel that the experience doesn't remind you of respect | |
| 696 | too, consider whether you and your colleagues want to continue supporting their | |
| 697 | [serverless empire]. The Vite ecosystem seems pretty decent to build on right | |
| 698 | now, but I still have little experience in using their tools at scale in | |
| 699 | production. The [Vite+ launch from Void0][vite-plus] seems interesting, but | |
| 700 | only time will tell if these venture-funded tools will respect us (end-users | |
| 701 | and developers) long term. | |
| 702 | ||
| 703 | Next.js Conf 2025, as of writing, is [tomorrow][next-conf]. Instead of | |
| 704 | purchasing a $800 ticket, I decided to put that money [toward the TanStack | |
| 705 | team][tanstack-donate] for [respecting and improving the web development | |
| 706 | ecosystem][tanstack-ethos]. | |
| 707 | ||
| 708 | [paper clover]: https://paperclover.net/ | |
| 709 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 710 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 711 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 712 | ||
| 713 | [next-conf]: https://nextjs.org/conf | |
| 714 | [vite-plus]: https://viteplus.dev/ | |
| 715 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 716 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 717 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 718 | ||
| 719 | ## What the Future Holds | |
| 720 | ||
| 721 | Slowly, I've been replacing many pieces of software that disrespect me with | |
| 722 | better alternatives. Some examples of this are GitHub, Visual Studio Code, | |
| 723 | DaVinci Resolve, Discord, Google Drive/Workspace, along many more. I plan to | |
| 724 | write more on this blog about the technical things I do (that progress library, | |
| 725 | the purpose of my own site generator, learnings from my current job), including | |
| 726 | some of my past projects at Bun (details on HMR, the crash reporter, and the | |
| 727 | crazy system for bundling built-in modules). If it interests you, please | |
| 728 | subscribe to the email list: | |
| 729 | ||
| 730 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually) | |
| 731 | ||
| 732 | [back to top](#top) &mdash; [ask a question about this article](/q+a) | |
| 733 | ||
| 734 | <br /> | |
| 735 | <br /> | |
| 736 | <br /> | |
| 737 | <br /> | |
| 738 | <br /> | |
| 739 | <br /> | |
| 740 | <footer> | |
| 741 | 2025 (c) paper clover | |
| 742 | </footer> |
src/blog/pages/webdev/one-year-next-app-router/en.mdx deleted-836| ... | ... | @@ -1,836 +0,0 @@ |
| 1 | import Heading from "@/blog/tags/heading.tsx"; | |
| 2 | import TableOfContents from "@/blog/tags/table-of-contents.tsx"; | |
| 3 | import { Layout } from "@/blog/tags/layout.tsx"; | |
| 4 | export { theme } from "@/blog/tags/layout.tsx"; | |
| 5 | ||
| 6 | export const meta = { | |
| 7 | title: "One Year with Next.js App Router — Why We're Moving On", | |
| 8 | description: "A critique of React Server Components and Next.js 15.", | |
| 9 | keywords: ["webdev", "technical analysis", "opinion"], | |
| 10 | authors: ["clover caruso"], | |
| 11 | embed: { | |
| 12 | thumbnail: "/open-graph/next-js.png" | |
| 13 | }, | |
| 14 | twitter: { | |
| 15 | image: "https://paperclover.net/open-graph/next-js.png" | |
| 16 | }, | |
| 17 | canonical: "/blog/webdev/one-year-next-app-router" | |
| 18 | }; | |
| 19 | ||
| 20 | <Layout | |
| 21 | meta={meta} | |
| 22 | date={'Oct 21st, 2025'} | |
| 23 | slug="webdev/one-year-next-app-router" | |
| 24 | tags={meta.keywords} | |
| 25 | > | |
| 26 | ||
| 27 | As I've been using [Next.js] professionally on my employer's web app, I find the | |
| 28 | core design of their App Router and [React Server Components] (RSC) to be | |
| 29 | extremely frustrating. And it's not small bugs or that the API is confusing, | |
| 30 | but large disagreements about the fundamental design decisions that Vercel and | |
| 31 | the React team made when building it. | |
| 32 | ||
| 33 | The more webdev events I go to, the more I see people who dislike Next.js, but | |
| 34 | still get stuck using it. By the end of this article, I will share how me and | |
| 35 | my colleagues escaped this hell, seamlessly migrating our entire frontend to | |
| 36 | [TanStack Start]. | |
| 37 | ||
| 38 | [Next.js]: https://nextjs.org | |
| 39 | [React Server Components]: https://react.dev/reference/rsc/server-components | |
| 40 | ||
| 41 | <TableOfContents> | |
| 42 | ||
| 43 | - [A Technical Review: What are Server Components?][§1] | |
| 44 | - [Real-world Pitfalls of the App Router][§2] | |
| 45 | - [Optimistic Updates are Impossible][§2.1] | |
| 46 | - [Every Navigation is Another Fetch][§2.2] | |
| 47 | - [Layouts are Artificially Restricted][§2.3] | |
| 48 | - [You Still Download All the Content Twice][§2.4] | |
| 49 | - [Turbopack Sucks][§2.5] | |
| 50 | - [Seamlessly Ditching Next.js and Vercel at Work][§3] | |
| 51 | - [`next/metadata` is Great][§3.1] | |
| 52 | - [`next/og` is Good Too][§3.2] | |
| 53 | - [My Experience Feels Like the Usual][§4] | |
| 54 | - [Prefer Tools that Respect You][§5] | |
| 55 | ||
| 56 | </TableOfContents> | |
| 57 | ||
| 58 | [§1]: #technical-review | |
| 59 | ||
| 60 | <Heading | |
| 61 | level='h2' | |
| 62 | slug='technical-review' | |
| 63 | >A Technical Review: What are Server Components?</Heading> | |
| 64 | ||
| 65 | The pitch of RSC is that components are put into two categories, | |
| 66 | <b class='server'>"server"</b> components and <b class='client'>"client"</b> | |
| 67 | components. Server components don't have `useState`, `useEffect`, but can be | |
| 68 | `async function`s and refer to backend tools like directly calling into a | |
| 69 | database. Client components are the existing | |
| 70 | model, where there is code on the backend to generate HTML text and frontend | |
| 71 | code to manage the DOM using `window.document.*`. | |
| 72 | ||
| 73 | > The first disaster: naming!! React is now using the words | |
| 74 | > <b class='server'>"server"</b> and <b class='client'>"client"</b> to refer to | |
| 75 | > a very specific things, ignoring their existing definitions. This would be | |
| 76 | > fine, except <b class='client'>Client</b> components can run on the backend | |
| 77 | > too! In this article, I'll be using the terms <b>"backend"</b> and | |
| 78 | > <b>"frontend"</b> to describe the two execution environments that web apps | |
| 79 | > exist in: a Node.js process and a Web browser, respectively. | |
| 80 | ||
| 81 | This <b class='server'>Server</b>/<b class='client'>Client</b> component model | |
| 82 | is interesting. Since built-ins like `<Suspense />` get serialized across the | |
| 83 | network, data fetching can be very trivially modeled with async <b | |
| 84 | class='server'>server components</b>, and the fallback UI works as if it were | |
| 85 | client-side. | |
| 86 | ||
| 87 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 88 | // For this article, server components will be highlighted in red | |
| 89 | export default async function Page({ params }) { | |
| 90 | // Page params are given as a resolved promise | |
| 91 | const { username } = await params; | |
| 92 | ||
| 93 | // The components `UserInfo` and `UserPostList` will be run at the same | |
| 94 | // time. Once `UserInfo` is ready, the visitor will see the page with a | |
| 95 | // `PostListSkeleton` if the post list is not yet ready. | |
| 96 | return <main> | |
| 97 | <UserInfo username={username} /> | |
| 98 | ||
| 99 | <Suspense fallback={<PostListSkeleton />}> | |
| 100 | <UserPostList username={username} /> | |
| 101 | </Suspense> | |
| 102 | </main> | |
| 103 | } | |
| 104 | ||
| 105 | // Waterfalls are avoided by having multiple components, which | |
| 106 | // are all evaluated at the same time. | |
| 107 | ||
| 108 | async function UserInfo({ username }) { | |
| 109 | const user = await fetchUserInfo(username); | |
| 110 | return <> | |
| 111 | <h1>{user.displayName}</h1> | |
| 112 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 113 | </> | |
| 114 | } | |
| 115 | ||
| 116 | async function UserPostList({ username }) { | |
| 117 | const posts = await fetchUserPostList(username); | |
| 118 | return /* post list ui omitted for brevity */; | |
| 119 | } | |
| 120 | ``` | |
| 121 | ||
| 122 | If we ignore the 40kB gzipped bundle size of React itself, the above example | |
| 123 | has zero JavaScript for the UI and data fetching &mdash; it just streams the | |
| 124 | markup! For example, the imagined markdown parser within the `<Markdown />` | |
| 125 | component stays on the backend. When an interactive frontend is needed, <b class='client'>Client | |
| 126 | components</b> can be created by putting them in a file starting with `"use | |
| 127 | client"`. | |
| 128 | ||
| 129 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 130 | "use client"; // This comment marks the file for client-side bundling. | |
| 131 | ||
| 132 | export function CopyButton({ url }) { | |
| 133 | return <> | |
| 134 | <span>{url}</span> | |
| 135 | <button onClick={() => { | |
| 136 | const full = new URL(url, location.href); | |
| 137 | navigator.clipboard.writeText(full.href); | |
| 138 | // omitting error handling, success ui, styles | |
| 139 | }}>copy</button> | |
| 140 | </> | |
| 141 | } | |
| 142 | ``` | |
| 143 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 144 | export function Card() { | |
| 145 | return <article> | |
| 146 | <header> | |
| 147 | {/* Make the browser import the copy button */} | |
| 148 | <CopyButton url="/q+a/2506010139" /> | |
| 149 | </header> | |
| 150 | <p> | |
| 151 | {/* Process markdown on the backend */} | |
| 152 | <Markdown content=".........." /> | |
| 153 | </p> | |
| 154 | </article> | |
| 155 | } | |
| 156 | ``` | |
| 157 | ||
| 158 | [§2]: #real-world-pitfalls | |
| 159 | ||
| 160 | <Heading | |
| 161 | level='h2' | |
| 162 | slug='real-world-pitfalls' | |
| 163 | >Real-world Pitfalls of the App Router</Heading> | |
| 164 | ||
| 165 | After quitting [Bun] as a runtime engineer (I implemented [Server Components | |
| 166 | bundling] and [a RSC template][bun-rsc] there), I joined a small company working on the | |
| 167 | front lines: a Next.js app with a Hono backend. The following notes are | |
| 168 | simplifications from the real world problems I've encountered when trying to | |
| 169 | maintain and develop new features. As a result of all of these, everyone's time | |
| 170 | is wasted either working around design flaws, or explaining to each other why | |
| 171 | what should be a non-issue is an immovable object. | |
| 172 | ||
| 173 | [Bun]: https://bun.com | |
| 174 | [Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 175 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 176 | ||
| 177 | [§2.1]: #optimistic-updates | |
| 178 | ||
| 179 | <Heading | |
| 180 | level='h3' | |
| 181 | slug='optimistic-updates' | |
| 182 | >Optimistic Updates are Impossible</Heading> | |
| 183 | ||
| 184 | The Next.js documentation for performing mutations [does not mention optimistic | |
| 185 | updates][nextjs-updating-data]; it appears this case was not thought about. | |
| 186 | Components rendered by the <b class='server'>React Server</b>, by design, can | |
| 187 | not be modified after mounting. Elements that could change need to be inside a | |
| 188 | client component, but data fetching cannot happen on the client components, | |
| 189 | even during SSR on the backend. This results in awkwardly small server | |
| 190 | components that only do data fetching and then have a client component that | |
| 191 | contains a mostly-static version of the page. | |
| 192 | ||
| 193 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 194 | ||
| 195 | export default async function Page() { | |
| 196 | const user = await fetchUserInfo(username); | |
| 197 | return <ProfileLayout> | |
| 198 | <UserProfile user={user} /> | |
| 199 | </ProfileLayout>; | |
| 200 | } | |
| 201 | ``` | |
| 202 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 203 | ||
| 204 | "use client"; // Must separate the client code into a second file! | |
| 205 | ||
| 206 | export function UserProfile({ user: initialUser }) { | |
| 207 | // There are many great state management libraries out there; | |
| 208 | // for simplicity, this example will use one state cell. | |
| 209 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 210 | ||
| 211 | async function onEdit(newUser) { | |
| 212 | optimisticUpdateUser(newUser); | |
| 213 | const resp = await fetch("...", { | |
| 214 | method: 'POST', | |
| 215 | body: JSON.stringify(newUser), | |
| 216 | ... // (headers, credentials, tracing, and more) | |
| 217 | }) | |
| 218 | if (!resp.ok) /* always remember to test for errors! */ | |
| 219 | } | |
| 220 | ||
| 221 | return <main>{/* user interface with editable fields... */}</main>: | |
| 222 | } | |
| 223 | ``` | |
| 224 | ||
| 225 | As more of the page needs interactivity, it gets messier trying to keep the | |
| 226 | static parts truly server-side. On the work app, nearly every piece of UI | |
| 227 | displays some dynamic data. A [`WebSocket`][ws] synchronizes data live as it | |
| 228 | updates (for example, a user card's online state along with their basic | |
| 229 | profile). Since these component setups are harder to understand and maintain | |
| 230 | for engineers, almost all of our pages are entirely `"use client"` with a | |
| 231 | `page.tsx` that defines the data fetching. | |
| 232 | ||
| 233 | A more concrete example of what this looks like in practice with the | |
| 234 | data-fetching library we use at work, [TanStack Query]. | |
| 235 | ||
| 236 | [TanStack Query]: https://github.com/tanstack/query#readme | |
| 237 | ||
| 238 | ```ts filename="src/queries/users.ts" | |
| 239 | // At work, there is a helper function `defineQuery` for type safety. | |
| 240 | // Fetchers are trivial and can run on the backend or the frontend. | |
| 241 | export const queryUserInfo = (username) => ({ | |
| 242 | queryKey: ['user', username], | |
| 243 | queryFn: async ({ ... }) => /* fetch data */ | |
| 244 | }); | |
| 245 | ``` | |
| 246 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 247 | export default async function Page({ params }) { | |
| 248 | const { username } = await params; | |
| 249 | ||
| 250 | // There's no global state in the React Server. Since layouts | |
| 251 | // are executed in parallel, the TanStack `QueryClient` has to | |
| 252 | // be reconstructed multiple times per route. | |
| 253 | const queryClient = new QueryClient(); | |
| 254 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 255 | ||
| 256 | // HydrationBoundary is a client component that passes JSON | |
| 257 | // data from the React server to the client component. | |
| 258 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 259 | <ClientPage /> | |
| 260 | </HydrationBoundary>; | |
| 261 | } | |
| 262 | ``` | |
| 263 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 264 | "use client"; | |
| 265 | export function ClientPage() { | |
| 266 | const { username } = useParams(); | |
| 267 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 268 | ||
| 269 | // ... some hooks | |
| 270 | ||
| 271 | return <main> | |
| 272 | {/* ... an interactive web page */} | |
| 273 | </main>; | |
| 274 | } | |
| 275 | ``` | |
| 276 | ||
| 277 | This example has to be three separate files because of the rules of server | |
| 278 | component bundling. (The client component needs `"use client"`, and server | |
| 279 | component files often can't be imported on the client due to server-only | |
| 280 | imports.). In the Pages router, this could've been a single file because of the | |
| 281 | tree-shaking that `getStaticProps` and `getServerSideProps` has. | |
| 282 | ||
| 283 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 284 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 285 | ||
| 286 | ||
| 287 | [§2.2]: #redundant-fetches | |
| 288 | ||
| 289 | <Heading | |
| 290 | level='h3' | |
| 291 | slug='redundant-fetches' | |
| 292 | >Every Navigation is Another Fetch</Heading> | |
| 293 | ||
| 294 | Since the App Router starts every page as a server component, with (ideally) | |
| 295 | small areas of interactivity, a navigation to a new page *has* to fetch the | |
| 296 | Next.js server, regardless of what data the client already has available! Even | |
| 297 | with a a `loading.tsx` file, opening `/`, navigating to `/other`, and then | |
| 298 | going back to `/` will show the loading state while it re-fetches the homepage. | |
| 299 | ||
| 300 | The only case this works is for **perfectly static content**, where instant | |
| 301 | navigations and prefetching work great. But **web apps are not static**, they | |
| 302 | have lots of dynamic content. Being logged in affects the homepage, which is | |
| 303 | infuriating because the client literally has everything needed to display the | |
| 304 | page instantly. It's not like the cookies changed. | |
| 305 | ||
| 306 | > **aside**: In further testing on a blank project, I observe cases where the | |
| 307 | > Next frontend code would pre-fetch routes, but **without any real contents**. | |
| 308 | > On the hello world example, this was a 1.8kB RSC payload that pointed to 2 | |
| 309 | > different JS chunks 4 separate times. This is just pure waste of our | |
| 310 | > bandwidth and egress, especially considering all of this information is | |
| 311 | > re-fetched when I actually click the link. | |
| 312 | > | |
| 313 | > ```json whitespace="pre-wrap" | |
| 314 | > 1:"$Sreact.fragment" | |
| 315 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 316 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 317 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 318 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 319 | > 7:"$Sreact.suspense" | |
| 320 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 321 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 322 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 323 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 324 | > ``` | |
| 325 | > | |
| 326 | > In review, I found there is actually some content in here: the loading state. | |
| 327 | > Do you see it? | |
| 328 | > | |
| 329 | > ```json | |
| 330 | > ["$","div","l",{"children":"loading..."}] | |
| 331 | > ``` | |
| 332 | > | |
| 333 | > It's still a lot of waste, since all of this data gets re-emitted in the | |
| 334 | > actual page RSC. | |
| 335 | ||
| 336 | The solution to this appears to be [`staleTime`][nextjs-stale], but it's marked | |
| 337 | experimental and "not recommended for production". The fact this is a | |
| 338 | non-default afterthought configuration option is embarrassing. Even if we used | |
| 339 | it, you cannot make multiple pages that refer to the same underlying data share | |
| 340 | any of it. | |
| 341 | ||
| 342 | One form of loading state that cannot be represented with the App Router is | |
| 343 | having a page such as a page like a git project's issue page, and clicking on a | |
| 344 | user name to navigate to their profile page. With `loading.tsx`, the entire | |
| 345 | page is a skeleton, but when modeling these queries with TanStack Query it is | |
| 346 | possible to show the username and avatar instantly while the user's bio and | |
| 347 | repositories are fetched in. Server components don't support this form of | |
| 348 | navigation because the data is only available in rendered components, so it | |
| 349 | must be re-fetched. | |
| 350 | ||
| 351 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 352 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 353 | ||
| 354 | In our Next.js site, we have this line of code on our server component data | |
| 355 | fetchers to make soft navigations faster by skipping the data fetch phase all | |
| 356 | together. | |
| 357 | ||
| 358 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 359 | export function serverSidePrefetchQueries(queries) { | |
| 360 | if ((await headers()).get("next-url")) { | |
| 361 | // This is a soft-navigation. SKIP the prefetching to make it faster. | |
| 362 | // The client might already have this data, and if not, they have the | |
| 363 | // loading state. Ideally, this server request wouldn't exist -- The | |
| 364 | // client side has nearly ALL the code since the app is written mostly | |
| 365 | // as client components. Kind of a design flaw of the App router TBH. | |
| 366 | return; | |
| 367 | } | |
| 368 | // ... data prefetching-logic ... | |
| 369 | } | |
| 370 | ``` | |
| 371 | ||
| 372 | In addition to this, `loading.tsx` should contain the `useQuery` calls so that | |
| 373 | while the network request for the empty RSC happens, the data is being fetched | |
| 374 | if it actually is needed. In fact, the `loading.tsx` state can just be the | |
| 375 | actual client component, and you'll see the client page. | |
| 376 | ||
| 377 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 378 | "use client"; | |
| 379 | export default function PageLoadingSkeleton() { | |
| 380 | return <ClientPage />; | |
| 381 | } | |
| 382 | ``` | |
| 383 | ||
| 384 | > At work, we just make our `loading.tsx` files contain the `useQuery` | |
| 385 | > calls and show a skeleton. This is because when Next.js loads the actual Server | |
| 386 | > Component, no matter what, the entire page re-mounts. No VDOM diffing here, | |
| 387 | > meaning all hooks (`useState`) will reset slightly after the request | |
| 388 | > completes. I tried to reproduce a simple case where I was *begging* Next.js to | |
| 389 | > just *update the existing DOM* and preserve state, but it just doesn't. | |
| 390 | > Thankfully, the time the blank RSC call takes is short enough. | |
| 391 | ||
| 392 | [§2.3]: #layout-restrictions | |
| 393 | ||
| 394 | <Heading | |
| 395 | level='h3' | |
| 396 | slug='layout-restrictions' | |
| 397 | >Layouts are Artificially Restricted</Heading> | |
| 398 | ||
| 399 | Layouts can perform data fetching, but they can't observe or alter the request | |
| 400 | in any way. This is done so that Next.js can fetch and cache layouts whenever they | |
| 401 | want. In every other framework, layouts are just regular components that have | |
| 402 | no feature difference compared to page components. | |
| 403 | ||
| 404 | Fetching layouts in isolation is a cute idea, but it ends up being silly | |
| 405 | because it also means that any data fetching has to be re-done per layout. You | |
| 406 | can't share a `QueryClient`; instead, you must rely on their [monkey-patched | |
| 407 | `fetch`][nextjs-fetch] to cache the same `GET` request like they promise. | |
| 408 | ||
| 409 | When a coworker asks me about why Next.js rejects some code, I've given up on | |
| 410 | explaining the technical intricacies and just say *"It's a Next.js Skill Issue, | |
| 411 | I'm going to blow it up soon don't worry."* These rules are too hard for normal | |
| 412 | developers to understand. | |
| 413 | ||
| 414 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 415 | ||
| 416 | [§2.4]: #rsc-payload | |
| 417 | ||
| 418 | <Heading | |
| 419 | level='h3' | |
| 420 | slug='rsc-payload' | |
| 421 | >You Still Download All the Content Twice</Heading> | |
| 422 | ||
| 423 | Unlike the ["Islands Architecture"][islands], Server Components still have to | |
| 424 | be hydrated on the frontend to support `Suspense` and preserving client | |
| 425 | component state. When doing soft navigations, the "RSC Payload" (which is not | |
| 426 | HTML at all) is retrieved by `fetch`. On a fresh reload, HTML is needed for the | |
| 427 | [first paint], but the information about Client components and `Suspense` is | |
| 428 | not contained within that HTML. React's solution is to **send a second copy of | |
| 429 | the entire page's markup**. An example of what a Next.js production server | |
| 430 | would send in a dynamic page render would be something like this: | |
| 431 | ||
| 432 | [first paint]: https://web.dev/articles/fcp | |
| 433 | ||
| 434 | ```html filename="GET /user/clover" | |
| 435 | <!DOCTYPE html> | |
| 436 | <html> | |
| 437 | <head> | |
| 438 | {link and meta tags} | |
| 439 | </head> | |
| 440 | <body> | |
| 441 | {server side render} | |
| 442 | <script> | |
| 443 | // a bootstrap script that sets up global `__next_f` as | |
| 444 | // an array. once React loads, this `.push` function | |
| 445 | // gets overwritten to write new chunks directly to the | |
| 446 | // RSC decoder. this script has some dom helpers too | |
| 447 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 448 | </script> | |
| 449 | <script> | |
| 450 | // the RSC payload for the application shell. | |
| 451 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 452 | </script> | |
| 453 | ||
| 454 | <!-- | |
| 455 | the closing </body> is NOT written yet, since there is a | |
| 456 | suspense boundary not resolved. time passes, and only | |
| 457 | then is more data is written | |
| 458 | --> | |
| 459 | <div class="user-post-list"> | |
| 460 | {server side render of a Suspense boundary} | |
| 461 | </div> | |
| 462 | <script> | |
| 463 | // the RSC payload for the suspense boundary | |
| 464 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 465 | </script> | |
| 466 | ||
| 467 | <!-- HTML and script tags repeat until the entire page is done --> | |
| 468 | </body> | |
| 469 | </html> | |
| 470 | ``` | |
| 471 | ||
| 472 | This solution **doubles the size of the initial HTML payload**. Except it's | |
| 473 | worse, because the RSC payload includes JSON quoted in JS string literals, | |
| 474 | which is a is much less efficient format than HTML. While it seems to compress | |
| 475 | fine with brotli and render fast in the browser, this is wasteful. With the | |
| 476 | hydration pattern, at least the data locally could be re-used for interactivity | |
| 477 | and other pages. | |
| 478 | ||
| 479 | Even on pages that have little to no interactivity, you pay the cost. To use | |
| 480 | the Next.js documentation as an example, loading [its | |
| 481 | homepage](https://nextjs.org/docs) loads an page that is around 750kB (250kB of | |
| 482 | HTML and the 500kB of script tags), and content is in there twice. | |
| 483 | ||
| 484 | You can verify that by pressing <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> | |
| 485 | on Mac or <kbd>Ctrl</kbd> + <kbd>u</kbd> on other platforms. And then | |
| 486 | <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> to locate any string of the | |
| 487 | blog, such as "building full-stack web applications". It's there twice. And | |
| 488 | **there is no way around this**, since it's a fundamental piece of React Server | |
| 489 | Components. | |
| 490 | ||
| 491 | This RSC format certainly has more waste. But I really don't feel like digging into | |
| 492 | why the string `/_next/static/chunks/6192a3719cda7dcc.js` appears 27 separate | |
| 493 | times. What the hell, guys? Is your bandwidth free??? | |
| 494 | ||
| 495 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 496 | ||
| 497 | [§2.5]: #turbopack | |
| 498 | ||
| 499 | <Heading | |
| 500 | level='h3' | |
| 501 | slug='turbopack' | |
| 502 | >Turbopack Sucks</Heading> | |
| 503 | ||
| 504 | This section is not constructive. | |
| 505 | ||
| 506 | - Turbopack isn't fast | |
| 507 | - Turbopack emits code that is hard to debug in a debugger (in development mode) | |
| 508 | - Turbopack throws bad error messages in many cases | |
| 509 | ||
| 510 | I wouldn't have given this point a section in the blog normally, but I want to | |
| 511 | point out three actual examples directly from the project. | |
| 512 | ||
| 513 | The first is a place where during some refactoring to satisfy the Server/Client | |
| 514 | component models, I accidentally made a Client component `async`. This one was | |
| 515 | quite annoying because it didn't say at all where the issue was, but only | |
| 516 | contained the <b class='server'>server</b> stack trace. | |
| 517 | ||
| 518 |  | |
| 519 | ||
| 520 | Another case of a terrible error message: | |
| 521 | ||
| 522 |  | |
| 523 | ||
| 524 | > After fixing the underlying issue in this second error (which I cannot recall), | |
| 525 | > the Dev server hung and had to be restarted to recover. | |
| 526 | ||
| 527 | The final one is the dozen times I place a debugger breakpoint and the | |
| 528 | variable name `hello` gets turned into | |
| 529 | `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` | |
| 530 | and other bullshit. | |
| 531 | ||
| 532 | Okay. This all sucks. What can we do? | |
| 533 | ||
| 534 | [§3]: #ditching-nextjs | |
| 535 | ||
| 536 | <Heading | |
| 537 | level='h2' | |
| 538 | slug='ditching-nextjs' | |
| 539 | >Seamlessly Ditching Next.js and Vercel at Work</Heading> | |
| 540 | ||
| 541 | There are two types of web projects: | |
| 542 | ||
| 543 | - A web site with mostly static content. | |
| 544 | - A web app with majorly dynamic and interactive components. | |
| 545 | ||
| 546 | And Next.js is the wrong tool for both of these jobs. If you're in the first | |
| 547 | category with a static web site, go for [Astro] or [Fresh]. For everyone who | |
| 548 | needs the full power of React, this section is about how I replaced the vendor | |
| 549 | locked Next with [TanStack Start], incrementally and seamlessly. | |
| 550 | ||
| 551 | [Astro]: https://astro.build/ | |
| 552 | [Fresh]: https://fresh.deno.dev/ | |
| 553 | [TanStack Start]: https://tanstack.com/start/latest | |
| 554 | ||
| 555 | It started with this Vite config. | |
| 556 | ||
| 557 | ```ts filename="vite.config.ts" | |
| 558 | const config = defineConfig(({ mode }) => { | |
| 559 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 560 | return { | |
| 561 | // Use the Next.js default port 3000 | |
| 562 | server: { port: 3000 }, | |
| 563 | // Use the Next.js default env prefix "NEXT_PUBLIC_" | |
| 564 | define: Object.fromEntries(Object.entries(env).map( | |
| 565 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 566 | plugins: [ | |
| 567 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 568 | tailwindcss(), | |
| 569 | // For ease of understanding from coworkers, I started porting | |
| 570 | // the routes in `src/tanstack-routes`. When the migration was | |
| 571 | // done, it would go back to the default `src/routes`. | |
| 572 | tanstackStart({ | |
| 573 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 574 | }), | |
| 575 | viteReact(), | |
| 576 | ], | |
| 577 | resolve: { | |
| 578 | // The key to the incremental migration: redirect `next` elsewhere | |
| 579 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 580 | conditions: ["tanstack"], | |
| 581 | extensions: [ | |
| 582 | // Allow a file named like `utils/session.tanstack.ts` to | |
| 583 | // override `utils/session.ts` when imported. | |
| 584 | ".tanstack.tsx", ".tanstack.ts", | |
| 585 | // Default import extensions | |
| 586 | ".mjs", ".js", ".mts", ".ts", | |
| 587 | ".jsx", ".tsx", ".json", | |
| 588 | ], | |
| 589 | }, | |
| 590 | }; | |
| 591 | }); | |
| 592 | ``` | |
| 593 | ||
| 594 | Then, I looked for every usage of a Next.js API, and either removed it or made | |
| 595 | a stub for TanStack. For example, `src/tanstack-next/link.tsx` implements | |
| 596 | `next/link`: | |
| 597 | ||
| 598 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 599 | import { Link } from "@tanstack/react-router"; | |
| 600 | import type { LinkProps } from "next/link"; | |
| 601 | ||
| 602 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 603 | return <Link {...rest} to={href as unknown as any} />; | |
| 604 | } | |
| 605 | ``` | |
| 606 | ||
| 607 | > Some of these stubs can be extremely simple. Starting out, my implementation | |
| 608 | > of `useRouter` was just `return {}`, but later I had to add a couple methods | |
| 609 | > to the object. The code here doesn't have to be clean, because it is | |
| 610 | > temporary. | |
| 611 | ||
| 612 | Now, the new site can import nearly every client component by either stubbing | |
| 613 | out the Next.js APIs it needs, or by using the `.tanstack.ts` extension to | |
| 614 | re-implement logic on a file-by-file basis. And shortly after, I got the site's | |
| 615 | homepage to work in TanStack Start, and we merged the branch. | |
| 616 | ||
| 617 |  | |
| 618 | ||
| 619 | > This first PR only supported one of our pages, and was able to do it in a | |
| 620 | > thousand lines of added code, and 40 lines deleted. I had previous patches to | |
| 621 | > remove the few uses of `next/image` and `next/font`. | |
| 622 | ||
| 623 | What was left was porting every other route over. The one thing we lose in | |
| 624 | migrating from Next.js to any other framework is the ability to `await` | |
| 625 | data-fetching functions in the UI. In practice, moving every route into a | |
| 626 | `loader` function made it much more clear what happened when a page was SSR'd. | |
| 627 | For pages that had multiple fetches, these could be combined into a single, | |
| 628 | special API call that would return all of the relevant data for that page. | |
| 629 | ||
| 630 | To re-iterate in bold font: <strong style='color:var(--secondary)'>The | |
| 631 | migration path from Server Components is to just simplify your code &mdash; RSC | |
| 632 | inherently drives you down a chaotic road of things you do not need</strong>. | |
| 633 | Nearly every complex part of our site got easier to understand for all | |
| 634 | engineers. The exception to this was having everyone get used to the new file | |
| 635 | system routing conventions. With enough examples, we all got the hang of it. | |
| 636 | ||
| 637 | With the incremental migration in place, new code did not break the existing | |
| 638 | deployment. TanStack slowly took over the codebase, and we eventually deleted | |
| 639 | all of the Next.js stubs and gained all of the beautiful [type-safety features] | |
| 640 | that the TanStack Router provides. At the end, the site performed faster from | |
| 641 | every angle: Development Mode, Production page load times, Soft navigations, | |
| 642 | and at a lower price than our Next depoyment with Vercel. | |
| 643 | ||
| 644 | [type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 645 | ||
| 646 | We're not the only ones seeing the change. While I try and keep myself off of | |
| 647 | social media, someone sent me [the results of Brian Anglin's work at | |
| 648 | Superwall][superwall-twitter], showing incredible CPU reductions on TanStack | |
| 649 | Start. I also recall ChatGPT switching from Next.js to Remix (random online | |
| 650 | chatter: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]) a year ago. | |
| 651 | ||
| 652 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 653 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 654 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 655 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 656 | ||
| 657 | [§3.1]: #next-metadata | |
| 658 | ||
| 659 | <Heading | |
| 660 | level='h3' | |
| 661 | slug='next-metadata' | |
| 662 | ><code>next/metadata</code> is Great</Heading> | |
| 663 | ||
| 664 | In my opinion, this is one of the only good APIs Next.js has, and was the one | |
| 665 | place in our code where moving to TanStack made things harder to do. Instead of | |
| 666 | worsening the code, I just ported their metadata API into a regular function, | |
| 667 | so everyone can use it. Originally, I had a 1:1 port on NPM, but earlier this | |
| 668 | year I simplified it's API into one short and understandable | |
| 669 | file. As of this blog post, I have added a TanStack-compatible | |
| 670 | `meta.toTags` API, which can be installed from [JSR][lib-jsr], [NPM][lib-npm], | |
| 671 | or simply copied into your project. | |
| 672 | ||
| 673 | > **notice**: Due to time constraints with writing this article, the library | |
| 674 | > has not yet been updated. I'll probably get around to it by the ~~end of this | |
| 675 | > week (Oct 24th)~~ some time soon... As a placeholder, I'm able to share the | |
| 676 | > version that is used at work to my website: | |
| 677 | > [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts). | |
| 678 | ||
| 679 | ```tsx | |
| 680 | // once in your project | |
| 681 | import * as meta from "@clo/lib/meta.ts"; | |
| 682 | ||
| 683 | export const defineHead = meta.toTags.bind(null, { | |
| 684 | // site-wide options | |
| 685 | base: new URL("https://paperclover.net"), | |
| 686 | titleTemplate: (title) => [title, "paper clover"] | |
| 687 | .filter(Boolean).join(' | '), | |
| 688 | // ... | |
| 689 | }); | |
| 690 | ||
| 691 | // for each page... | |
| 692 | export const Route = createFileRoute("/blog")({ | |
| 693 | head: () => | |
| 694 | defineHead({ | |
| 695 | title: "clover's blog", // templated with `titleTemplate` | |
| 696 | description: "a catgirl meows about her technology viewpoints", | |
| 697 | canonical: "/blog", // joined with `base` | |
| 698 | ||
| 699 | // When specified, configures Open Graph and Twitter embed, | |
| 700 | // using the page title and description as the default. | |
| 701 | // The defaults are good, but it supports more options. | |
| 702 | embed: { | |
| 703 | image: "/img/blog.webp", | |
| 704 | }, | |
| 705 | ||
| 706 | // Every exotic meta tag is done with a JSX fragment. This | |
| 707 | // doesn't render React, it just loops through the tags. | |
| 708 | // My goal was to cover the most common 99% of uses. | |
| 709 | extra: <> | |
| 710 | <meta name="site-verification" content="waffles" />, | |
| 711 | </>, | |
| 712 | }), | |
| 713 | ||
| 714 | component: Page, | |
| 715 | }); | |
| 716 | ||
| 717 | function Page() { | |
| 718 | ... | |
| 719 | } | |
| 720 | ``` | |
| 721 | ||
| 722 | My version wasn't concerned with covering the entire space of Next.js's metadata | |
| 723 | object, but instead uses inline JSX to fill that gap. | |
| 724 | ||
| 725 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 726 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 727 | ||
| 728 | [§3.2]: #vercel-og | |
| 729 | ||
| 730 | <Heading | |
| 731 | level='h3' | |
| 732 | slug='ditching-nextjs' | |
| 733 | ><code>next/og</code> is Good Too</Heading> | |
| 734 | ||
| 735 | No strong opinions. I just want to remind everyone that the `@vercel/og` package exists. | |
| 736 | ||
| 737 | [§4]: #experience-feels-like-the-usual | |
| 738 | ||
| 739 | <Heading | |
| 740 | level='h2' | |
| 741 | slug='experience-feels-like-the-usual' | |
| 742 | >My Experience Feels like the Usual</Heading> | |
| 743 | ||
| 744 | At the Next.js Conf 2024, everyone there was raving about Server Components. I | |
| 745 | forget exactly who I talked to, but the big people were all in on this. I, | |
| 746 | having implemented the bundler end of RSC, saw a couple of the problems in the | |
| 747 | format. With Next 15 "stabilizing" the App Router last year, many companies are | |
| 748 | building their products on it, realizing these pitfalls first-hand. | |
| 749 | ||
| 750 | I came into the Next.js game late, only starting in June with version 15. | |
| 751 | But everyone I've talked to at events sympathize with my notes. All the people | |
| 752 | I talked to on the subject at Bun's 1.3 Party agreed with me. Even some people | |
| 753 | at Vercel told me they don't like how Next.js is to actually use. | |
| 754 | ||
| 755 | I hope as TanStack Start stabilizes, it becomes the Next.js replacement everyone | |
| 756 | wants. | |
| 757 | ||
| 758 | [§5]: #prefer-respectful-tools | |
| 759 | ||
| 760 | <Heading | |
| 761 | level='h2' | |
| 762 | slug='prefer-respectful-tools' | |
| 763 | >Prefer Tools that Respect You</Heading> | |
| 764 | ||
| 765 | A lot of in the JavaScript ecosystem is a mess. That mess is why web | |
| 766 | development gets made fun of. There were a lot of times I thought that working | |
| 767 | with the web was an unrecoverable mess, but the mess was actually just the | |
| 768 | commonly-used libraries I surrounded myself with. When that is peeled back, | |
| 769 | modern web development technologies are awesome. | |
| 770 | ||
| 771 | I've been making this website from scratch without any framework since late | |
| 772 | 2024, by writing systems like my own [TUI progress widget][progress], [static | |
| 773 | file proxy][file-cache], incremental build system, and many more components. | |
| 774 | Working on this code has produced some of my best coding sessions (by | |
| 775 | happiness) in years. The viewers of *[paper clover]* get a better quality | |
| 776 | website; the mini-libraries I create get [extracted for public use][lib], | |
| 777 | everyone wins. | |
| 778 | ||
| 779 | This level of from-scratch is too much for most people, especially at the | |
| 780 | workplace. I say that at the minimum, we should only give our attention and | |
| 781 | money to high quality tools that respect us. And Next.js and the company behind | |
| 782 | it, Vercel, are not that. | |
| 783 | ||
| 784 | If you use Next.js, and feel that the experience doesn't remind you of respect | |
| 785 | too, consider whether you and your colleagues want to continue supporting their | |
| 786 | [serverless empire]. The Vite ecosystem seems pretty decent to build on right | |
| 787 | now, but I still have little experience in using their tools at scale in | |
| 788 | production. The [Vite+ launch from Void0][vite-plus] seems interesting, but | |
| 789 | only time will tell if these venture-funded tools will respect us (end-users | |
| 790 | and developers) long term. | |
| 791 | ||
| 792 | Next.js Conf 2025, as of writing, is [tomorrow][next-conf]. Instead of | |
| 793 | purchasing a $800 ticket, I decided to put that money [toward the TanStack | |
| 794 | team][tanstack-donate] for [respecting and improving the web development | |
| 795 | ecosystem][tanstack-ethos]. | |
| 796 | ||
| 797 | [paper clover]: https://paperclover.net/ | |
| 798 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 799 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 800 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 801 | ||
| 802 | [next-conf]: https://nextjs.org/conf | |
| 803 | [vite-plus]: https://viteplus.dev/ | |
| 804 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 805 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 806 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 807 | ||
| 808 | ## What the Future Holds | |
| 809 | ||
| 810 | Slowly, I've been replacing many pieces of software that disrespect me with | |
| 811 | better alternatives. Some examples of this are GitHub, Visual Studio Code, | |
| 812 | DaVinci Resolve, Discord, Google Drive/Workspace, along many more. I plan to | |
| 813 | write more on this blog about the technical things I do (that progress library, | |
| 814 | the purpose of my own site generator, learnings from my current job), including | |
| 815 | some of my past projects at Bun (details on HMR, the crash reporter, and the | |
| 816 | crazy system for bundling built-in modules). If it interests you, please | |
| 817 | subscribe to the email list: | |
| 818 | ||
| 819 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually) | |
| 820 | ||
| 821 | [back to top](#top) &mdash; [ask a question about this article](/q+a) | |
| 822 | ||
| 823 | <br /> | |
| 824 | <br /> | |
| 825 | <br /> | |
| 826 | <br /> | |
| 827 | <br /> | |
| 828 | <br /> | |
| 829 | <footer> | |
| 830 | 2025 (c) paper clover | |
| 831 | </footer> | |
| 832 | ||
| 833 | </Layout> | |
| 834 | ||
| 835 | <br /> | |
| 836 |
src/blog/pages/webdev/one-year-next-app-router/es.mdo created+773| ... | ... | @@ -0,0 +1,773 @@ |
| 1 | --- | |
| 2 | layout: "@/blog/tags/blog-layout.marko" | |
| 3 | meta: | |
| 4 | title: Un año con el enrutador de aplicación (App Router) de Next.js — por qué decidimos cambiar | |
| 5 | description: Una crítica de los componentes de servidor de React y Next.js 15. | |
| 6 | keywords: ["webdev", "technical analysis", "opinion"] | |
| 7 | authors: ["clover caruso", "trubiso"] | |
| 8 | embed: | |
| 9 | thumbnail: /open-graph/next-js.es.png | |
| 10 | canonical: /blog/webdev/one-year-next-app-router.es | |
| 11 | date: "Oct 21st, 2025" | |
| 12 | translation: | |
| 13 | lang: es | |
| 14 | author: "@trubiso" | |
| 15 | href: null | |
| 16 | date: Jan 13th, 2026 | |
| 17 | --- | |
| 18 | ||
| 19 | He estado usando [Next.js] profesionalmente para el desarrollo de aplicaciones | |
| 20 | web en mi trabajo y el diseño de su enrutador de aplicación (App Router) y los | |
| 21 | [componentes de servidor (React Server Components/RSC)][rsc] me parece | |
| 22 | extremadamente frustrante a nivel fundamental. No es por pequeños bugs o porque | |
| 23 | la API me parezca confusa, sino por grandes discrepancias en las decisiones de | |
| 24 | diseño fundamentales que tomaron Vercel y el equipo de React en su creación. | |
| 25 | ||
| 26 | Cuanto más asisto a eventos de desarrollo web, más veo a gente a quien no le | |
| 27 | gusta Next.js, pero que aún así debe usarlo. Al final del artículo, compartiré | |
| 28 | cómo escapamos de este infierno, migrando todo nuestro frontend a [TanStack | |
| 29 | Start] sin problemas. | |
| 30 | ||
| 31 | [Next.js]: https://nextjs.org | |
| 32 | [rsc]: https://react.dev/reference/rsc/server-components | |
| 33 | ||
| 34 | <table-of-contents /> | |
| 35 | ||
| 36 | <h2#technical-review>Un repaso técnico: ¿qué son los componentes de servidor?</> | |
| 37 | ||
| 38 | El punto clave de RSC es que los componentes se dividen en dos categorías, | |
| 39 | componentes de <b class='server'>"servidor"</b> y componentes de <b class='client'>"cliente"</b>. Los componentes de servidor pueden usar ni | |
| 40 | `useState` ni `useEffect`, pero pueden hacer uso de funciones `async` y emplear | |
| 41 | herramientas de backend, permitiendo por ejemplo llamar directamente a una base | |
| 42 | de datos. Los componentes de cliente constituyen el modelo tradicional, en el | |
| 43 | que hay código en el backend para generar HTML y en el frontend para administrar | |
| 44 | el DOM usando `window.document.*`. | |
| 45 | ||
| 46 | > Ahí va el primer desastre: ¡la nomenclatura! React usa las palabras <b class='server'>"servidor"</b> y <b class='client'>"cliente"</b> para referirse a cosas muy específicas, ignorando sus definiciones existentes. No habría ningún problema, si no fuera por que ¡los componentes de <b class='client'>cliente</b> también pueden correr en el backend! En este artículo, utilizaré los términos <b>"backend"</b> y <b>"frontend"</b> para describir los ámbitos de ejecución en los que existen las aplicaciones web: un proceso de Node.js y un navegador web, respectivamente. | |
| 47 | ||
| 48 | Este modelo de componentes de <b class='server'>servidor</b> y de <b class='client'>cliente</b> es curioso. Dado que las funciones integradas, como | |
| 49 | `<Suspense />`, se serializan a través de la red, la obtención de datos se puede | |
| 50 | modelar de manera trivial con <b class='server'>componentes de servidor</b> | |
| 51 | asíncronos, y la interfaz de usuario alternativa funciona como si fuera del lado | |
| 52 | del cliente. | |
| 53 | ||
| 54 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 55 | // En este artículo, los componentes de servidor están resaltados en rojo. | |
| 56 | export default async function Page({ params }) { | |
| 57 | // Los parámetros de la página se pasan como una Promise resuelta. | |
| 58 | const { username } = await params; | |
| 59 | ||
| 60 | // Los componentes `UserInfo` y `UserPostList` serán corridos a la vez. En | |
| 61 | // cuanto `UserInfo` esté listo, el visitante verá la página con un | |
| 62 | // `PostListSkeleton` si la lista de publicaciones no está lista. | |
| 63 | return <main> | |
| 64 | <UserInfo username={username} /> | |
| 65 | ||
| 66 | <Suspense fallback={<PostListSkeleton />}> | |
| 67 | <UserPostList username={username} /> | |
| 68 | </Suspense> | |
| 69 | </main> | |
| 70 | } | |
| 71 | ||
| 72 | // Evitamos waterfalls teniendo varios componentes evaluados a la vez. | |
| 73 | ||
| 74 | async function UserInfo({ username }) { | |
| 75 | const user = await fetchUserInfo(username); | |
| 76 | return <> | |
| 77 | <h1>{user.displayName}</h1> | |
| 78 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 79 | </> | |
| 80 | } | |
| 81 | ||
| 82 | async function UserPostList({ username }) { | |
| 83 | const posts = await fetchUserPostList(username); | |
| 84 | return /* interfaz de usuario de la lista de publicaciones omitida por brevedad */; | |
| 85 | } | |
| 86 | ``` | |
| 87 | ||
| 88 | El ejemplo anterior no usa nada de JavaScript (sin contar el tamaño del empaquetado | |
| 89 | _gzipped_ de 40kB del propio React) para la interfaz de usuario ni para la | |
| 90 | obtención de datos &mdash; ¡sólo envía el HTML! Por ejemplo, el _parser_ (analizador) | |
| 91 | imaginario de Markdown dentro del componente `<Markdown />` se queda en el | |
| 92 | backend. En aquellos casos en los que se necesita un frontend interactivo, | |
| 93 | simplemente se puede crear un <b class='client'>componente de cliente</b> | |
| 94 | poniéndolo en un archivo empezando con `"use client"`. | |
| 95 | ||
| 96 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 97 | "use client"; // Este comentario marca el archivo para el empaquetado del lado del cliente. | |
| 98 | ||
| 99 | export function CopyButton({ url }) { | |
| 100 | return <> | |
| 101 | <span>{url}</span> | |
| 102 | <button onClick={() => { | |
| 103 | const full = new URL(url, location.href); | |
| 104 | navigator.clipboard.writeText(full.href); | |
| 105 | // omitiendo todo el manejo de errores, la interfaz de éxito, los estilos... | |
| 106 | }}>copy</button> | |
| 107 | </> | |
| 108 | } | |
| 109 | ``` | |
| 110 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 111 | export function Card() { | |
| 112 | return <article> | |
| 113 | <header> | |
| 114 | {/* Hace que el navegador importe el botón de copiar */} | |
| 115 | <CopyButton url="/q+a/2506010139" /> | |
| 116 | </header> | |
| 117 | <p> | |
| 118 | {/* Procesa el Markdown en el backend */} | |
| 119 | <Markdown content=".........." /> | |
| 120 | </p> | |
| 121 | </article> | |
| 122 | } | |
| 123 | ``` | |
| 124 | ||
| 125 | <h2#real-world-pitfalls>Los inconvenientes del enrutador de aplicación</> | |
| 126 | ||
| 127 | Tras dejar [Bun] como ingeniero de runtime (donde implementé el [empaquetado de | |
| 128 | componentes de servidor][Server Components bundling] y [una plantilla de | |
| 129 | RSC][bun-rsc]), me uní a una pequeña compañía, trabajando en la línea de fuego: | |
| 130 | una aplicación de Next.js con un backend de Hono. Lo siguiente es una versión | |
| 131 | simplificada de los problemas con los que me enfrenté tratando de mantener y | |
| 132 | desarrollar características nuevas. Como resultado de todos ellos, perdimos | |
| 133 | todos el tiempo intentando eludir fallos de diseño o explicándonos por qué algo | |
| 134 | que no debería ser un problema para empezar se ha convertido en un obstáculo | |
| 135 | inamovible. | |
| 136 | ||
| 137 | [Bun]: https://bun.com | |
| 138 | [Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 139 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 140 | ||
| 141 | <h3#optimistic-updates>Las actualizaciones optimistas son imposibles</> | |
| 142 | ||
| 143 | La documentación de Next.js respecto a la mutación [no menciona las | |
| 144 | actualizaciones optimistas][nextjs-updating-data], parece ser que no pensaron en | |
| 145 | este caso. Los componentes renderizados por el <b class='server'>servidor de | |
| 146 | React</b> no pueden ser modificados tras ser montados por diseño. Los elementos | |
| 147 | que puedan tener cambios deben estar dentro de un componente de cliente, pero no | |
| 148 | puede estos componentes no pueden dar lugar a obtención de datos, incluso | |
| 149 | durante el renderizado del lado del servidor (SSR) en el backend. Esto deriva en | |
| 150 | componentes de servidor incómodamente diminutos que sólo se encargan de obtener | |
| 151 | ciertos datos y tienen un componente homólogo de cliente que contiene una | |
| 152 | versión prácticamente estática de la página. | |
| 153 | ||
| 154 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 155 | ||
| 156 | export default async function Page() { | |
| 157 | const user = await fetchUserInfo(username); | |
| 158 | return <ProfileLayout> | |
| 159 | <UserProfile user={user} /> | |
| 160 | </ProfileLayout>; | |
| 161 | } | |
| 162 | ``` | |
| 163 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 164 | ||
| 165 | "use client"; // Toca mover el código de cliente a otro archivo! | |
| 166 | ||
| 167 | export function UserProfile({ user: initialUser }) { | |
| 168 | // Existen muchas librerías de gestión de estado excelentes; | |
| 169 | // para simplificar, usaremos una celda de estado. | |
| 170 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 171 | ||
| 172 | async function onEdit(newUser) { | |
| 173 | optimisticUpdateUser(newUser); | |
| 174 | const resp = await fetch("...", { | |
| 175 | method: 'POST', | |
| 176 | body: JSON.stringify(newUser), | |
| 177 | ... // (encabezados, credenciales, trazado y más) | |
| 178 | }) | |
| 179 | if (!resp.ok) /* ¡recuerda siempre comprobar si se ha dado algún error! */ | |
| 180 | } | |
| 181 | ||
| 182 | return <main>{/* interfaz de usuario con campos editables... */}</main>: | |
| 183 | } | |
| 184 | ``` | |
| 185 | ||
| 186 | A medida que más partes de la página necesitan interactividad, se vuelve más | |
| 187 | lioso mantener las partes estáticas puramente del lado del servidor. En la | |
| 188 | aplicación de trabajo, casi cada componente de la interfaz de usuario muestra | |
| 189 | datos dinámicos. Un [`WebSocket`][ws] sincroniza los datos en tiempo real a | |
| 190 | medida que se actualizan (por ejemplo, el estado en línea y el perfil básico de | |
| 191 | una tarjeta de usuario). Como estas configuraciones de componentes son más | |
| 192 | difíciles de entender y mantener para los ingenieros, casi todas nuestras | |
| 193 | páginas están marcadas como `"use client"` con un `page.tsx` que define la | |
| 194 | obtención de datos necesaria. | |
| 195 | ||
| 196 | Un ejemplo más concreto de cómo queda esto en la práctica con la librería de | |
| 197 | obtención de datos que usamos en el trabajo, [TanStack Query]. | |
| 198 | ||
| 199 | [TanStack Query]: https://github.com/tanstack/query#readme | |
| 200 | ||
| 201 | ```ts filename="src/queries/users.ts" | |
| 202 | // Tenemos una función auxiliar `defineQuery` para la seguridad de tipos en el trabajo. | |
| 203 | // Los obtenedores de datos son triviales y pueden correr tanto en el backend como en el frontend. | |
| 204 | export const queryUserInfo = (username) => ({ | |
| 205 | queryKey: ['user', username], | |
| 206 | queryFn: async ({ ... }) => /* obtener datos */ | |
| 207 | }); | |
| 208 | ``` | |
| 209 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 210 | export default async function Page({ params }) { | |
| 211 | const { username } = await params; | |
| 212 | ||
| 213 | // No hay estado global en el servidor de React. Como los layouts | |
| 214 | // se ejecutan en paralelo, toca reconstruir el `QueryClient` de | |
| 215 | // TanStack varias veces por ruta. | |
| 216 | const queryClient = new QueryClient(); | |
| 217 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 218 | ||
| 219 | // HydrationBoundary es un componente de cliente que pasa datos | |
| 220 | // JSON del servidor de React al componente de cliente. | |
| 221 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 222 | <ClientPage /> | |
| 223 | </HydrationBoundary>; | |
| 224 | } | |
| 225 | ``` | |
| 226 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 227 | "use client"; | |
| 228 | export function ClientPage() { | |
| 229 | const { username } = useParams(); | |
| 230 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 231 | ||
| 232 | // ... algunos hooks | |
| 233 | ||
| 234 | return <main> | |
| 235 | {/* ... una página web interactiva */} | |
| 236 | </main>; | |
| 237 | } | |
| 238 | ``` | |
| 239 | ||
| 240 | Este ejemplo necesita tres archivos por las reglas del empaquetado de los | |
| 241 | componentes de servidor (el componente de cliente necesita `"use client"` y los | |
| 242 | archivos de componentes de servidor no suelen poderse importar en el cliente | |
| 243 | debido a las importaciones exclusivas de servidor). En el enrutador de páginas | |
| 244 | (Pages Router), podría haberlo hecho todo en un solo archivo por el tree-shaking | |
| 245 | que poseen `getStaticProps` y `getServerSideProps`. | |
| 246 | ||
| 247 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 248 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 249 | ||
| 250 | <h3#redundant-fetches>Cada navegación supone una petición</h3> | |
| 251 | ||
| 252 | Como el enrutador de aplicación inicia cada página como componente de servidor, | |
| 253 | con áreas pequeñas de interactividad (idealmente), navegar a una nueva página | |
| 254 | ¡*fuerza* una petición al servidor de Next.js, independientemente de los datos | |
| 255 | que el cliente ya tenga disponibles! Incluso con un archivo `loading.tsx`, al | |
| 256 | abrir `/`, navegar a `/other/ y luego volver a `/`, se mostrará el estado de | |
| 257 | carga mientras vuelve a obtener los datos de la página de inicio. | |
| 258 | ||
| 259 | Para lo único para lo que esto funciona bien es para el **contenido | |
| 260 | perfectamente estático**, donde las navegaciones instantáneas y la precarga | |
| 261 | (prefetching) funcionan genial. Pero **las aplicaciones web no son estáticas**, | |
| 262 | tienen mucho contenido dinámico. Haber iniciado sesión afecta a la página de | |
| 263 | inicio, cosa que es irritante porque el cliente literalmente ya tiene todo lo | |
| 264 | que necesita para mostrar la página instantáneamente. Ni siquiera han cambiado | |
| 265 | las cookies. | |
| 266 | ||
| 267 | > **nota**: Probándolo más en un proyecto en blanco, he observado casos en los | |
| 268 | > que el código de frontend de Next precarga rutas **sin ningún contenido | |
| 269 | > real**. En el ejemplo de hello world, era una carga de 1.8kB de RSC que | |
| 270 | > apuntaba a 2 fragmentos de JS diferentes 4 veces distintas. Esto es un desperdicio | |
| 271 | > puro de nuestro ancho de banda y egreso, especialmente considerando que toda | |
| 272 | > esta información se vuelve a obtener cuando de verdad hago clic en el enlace. | |
| 273 | > | |
| 274 | > ```json whitespace="pre-wrap" | |
| 275 | > 1:"$Sreact.fragment" | |
| 276 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 277 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 278 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 279 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 280 | > 7:"$Sreact.suspense" | |
| 281 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 282 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 283 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 284 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 285 | > ``` | |
| 286 | > | |
| 287 | > Revisándolo, me di cuenta de que en realidad hay algo de contenido: el estado | |
| 288 | > de carga. ¿Lo ves? | |
| 289 | > | |
| 290 | > ```json | |
| 291 | > ["$","div","l",{"children":"loading..."}] | |
| 292 | > ``` | |
| 293 | > | |
| 294 | > Sigue siendo un desperdicio grande, ya que todos estos datos se vuelven a | |
| 295 | > emitir en el RSC de la propia página. | |
| 296 | ||
| 297 | Parece ser que la solución a esto es [`staleTime`][nextjs-stale], pero está | |
| 298 | marcado como experimental y "no recomendado para producción". Es una vergüenza | |
| 299 | el hecho de que esto sea una opción que no está por defecto y que parece haber | |
| 300 | sido una idea de último momento. Incluso usándola, no es posible hacer que | |
| 301 | varias páginas que hacen referencia a los mismos datos los compartan. | |
| 302 | ||
| 303 | Un ejemplo de un estado de carga irrepresentable con el enrutador de aplicación | |
| 304 | es el de ciertas páginas, como una página de propuestas (issues) en un proyecto | |
| 305 | de git, al hacer clic en un nombre de usuario para ir a su página de perfil. Con | |
| 306 | `loading.tsx`, la página entera es un esqueleto, pero al modelar estas consultas | |
| 307 | con TanStack Query es posible mostrar el nombre de usuario y avatar | |
| 308 | instantáneamente mientras cargan la biografía del usuario y sus repositorios. | |
| 309 | Los componentes de servidor son incompatibles con esta tipo de navegación porque | |
| 310 | los datos sólo están disponibles en componentes renderizados, con lo cual deben | |
| 311 | ser obtenidos una vez más. | |
| 312 | ||
| 313 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 314 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 315 | ||
| 316 | En nuestro sitio Next.js, tenemos esta línea de código en nuestros obtenedores | |
| 317 | de datos en los componentes de servidor para hacer que las navegaciones suaves | |
| 318 | vayan más rápido, saltándose la fase de obtención de datos por completo. | |
| 319 | ||
| 320 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 321 | export function serverSidePrefetchQueries(queries) { | |
| 322 | if ((await headers()).get("next-url")) { | |
| 323 | // Se trata de una navegación suave. Nos SALTAMOS la precarga para | |
| 324 | 	// aumentar la velocidad. Puede que el cliente ya tenga estos datos y, | |
| 325 | 	// de lo contrario, tienen el estado de carga. Idealmente, no existiría | |
| 326 | 	// esta petición -- el lado de cliente ya tiene casi TODO el código ya | |
| 327 | 	// que la aplicación está escrita principalmente usando componentes de | |
| 328 | 	// cliente. Un fallo de diseño de parte del enrutador de aplicación, la | |
| 329 | 	// verdad. | |
| 330 | return; | |
| 331 | } | |
| 332 | // ... lógica de precarga de datos ... | |
| 333 | } | |
| 334 | ``` | |
| 335 | ||
| 336 | Aparte, `loading.tsx` debería contener las llamadas a `useQuery` para que, | |
| 337 | mientras se realiza la petición para el RSC vacío, se obtengan los datos si de | |
| 338 | verdad se necesitan. De hecho, el estado `loading.tsx` puede ser directamente el | |
| 339 | componente de cliente y se mostrará la página de cliente. | |
| 340 | ||
| 341 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 342 | "use client"; | |
| 343 | export default function PageLoadingSkeleton() { | |
| 344 | return <ClientPage />; | |
| 345 | } | |
| 346 | ``` | |
| 347 | ||
| 348 | > En el trabajo, simplemente hacemos que nuestros archivos `loading.tsx` | |
| 349 | > contengan las llamadas a `useQuery` y muestren un esqueleto. Esto se debe a | |
| 350 | > que cuando Next.js carga el componente de servidor en sí, remonta la página | |
| 351 | > entera sí o sí. No se da ningún tipo de VDOM diffing, así que todos los hooks | |
| 352 | > (`useState`) se reiniciarán un poco después de que la petición se complete. | |
| 353 | > Intenté reproducir un caso simple en el que estaba *suplicándole* a Next.js | |
| 354 | > que tan solo *actualizara el DOM existente* y preservara el estado, pero | |
| 355 | > directamente no lo hace. Por suerte, el tiempo que tarda la llamada RSC en | |
| 356 | > blanco es suficientemente corto. | |
| 357 | ||
| 358 | <h3#layout-restrictions>Los layouts tienen restricciones artificiales</h3> | |
| 359 | ||
| 360 | Los layouts pueden obtener datos, pero no pueden observar o alterar la petición | |
| 361 | de ninguna manera. Está hecho así para que Next.js pueda obtener y cachear los | |
| 362 | layouts cada vez que quiera. En cualquier otro framework, los layouts son | |
| 363 | componentes normales sin diferencia de otros componentes en la página. | |
| 364 | ||
| 365 | Obtener los layouts por sí mismos es una idea chula, pero acaba siendo estúpida, | |
| 366 | pues toda obtención de datos debe rehacerse para cada layout. No puedes | |
| 367 | compartir un `QueryClient`, en su lugar debes usar su | |
| 368 | [`fetch` _monkey-patched_][nextjs-fetch] para cachear la misma petición `GET` | |
| 369 | tal y como prometen. | |
| 370 | ||
| 371 | Cuando me pregunta un compañero de trabajo por qué Next.js rechaza código, ya ni | |
| 372 | intento explicar los entresijos, directamente digo *"Es un problema de habilidad | |
| 373 | de Next.js, no te preocupes, que le voy a dar fuego pronto"*. Estas reglas son | |
| 374 | demasiado difíciles para que un desarrollador promedio las entienda. | |
| 375 | ||
| 376 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 377 | ||
| 378 | <h3#rsc-payload>Te llevas todo el contenido dos veces de todas formas</h3> | |
| 379 | ||
| 380 | A diferencia de la ["arquitectura de islas"][islands], los componentes de | |
| 381 | servidor deben ser hidratados en el frontend para poder emplear `Suspense` y | |
| 382 | mantener el estado de los componentes de cliente. Al hacer navegaciones suaves, | |
| 383 | se obtiene mediante `fetch` la "carga de RSC" (que no es HTML en absoluto). | |
| 384 | Al hacer una recarga fresca de la página, se necesita HTML para | |
| 385 | [pintar por primera vez][first paint], pero la información sobre los componentes | |
| 386 | de cliente y `Suspense` no está en dicho HTML. La solución de React es **enviar | |
| 387 | una segunda copia del _markup_ de la página entera**. Un ejemplo de lo que | |
| 388 | enviaría un servidor de producción de Next.js en un renderizado dinámico de una | |
| 389 | página sería algo así: | |
| 390 | ||
| 391 | [first paint]: https://web.dev/articles/fcp | |
| 392 | ||
| 393 | ```html filename="GET /user/clover" | |
| 394 | <!DOCTYPE html> | |
| 395 | <html> | |
| 396 | <head> | |
| 397 | {etiquetas link y meta} | |
| 398 | </head> | |
| 399 | <body> | |
| 400 | {renderizado del lado del servidor} | |
| 401 | <script> | |
| 402 | // un script de inicialización que inicia `__next_f` | |
| 403 | // como un arreglo. en cuanto carga React, la función | |
| 404 | // `.push` se modifica para escribir nuevos fragmentos | |
| 405 | // al decodificador de RSC directamente. tiene algunos | |
| 406 | // auxiliares de DOM también | |
| 407 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 408 | </script> | |
| 409 | <script> | |
| 410 | // la carga de RSC para el shell de la aplicación. | |
| 411 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 412 | </script> | |
| 413 | ||
| 414 | <!-- | |
| 415 | NO está escrita todavía la etiqueta </body>, ya que hay una | |
| 416 | frontera de Suspense sin resolver. con el paso del tiempo, | |
| 417 | se escriben más datos. | |
| 418 | --> | |
| 419 | <div class="user-post-list"> | |
| 420 | {renderizado del lado del servidor de una frontera de Suspense} | |
| 421 | </div> | |
| 422 | <script> | |
| 423 | // la carga de RSC para la frontera de Suspense | |
| 424 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 425 | </script> | |
| 426 | ||
| 427 | <!-- se repiten etiquetas HTML y script hasta que la página acaba --> | |
| 428 | </body> | |
| 429 | </html> | |
| 430 | ``` | |
| 431 | ||
| 432 | Esta solución **duplica el tamaño de la carga inicial de HTML**. Aún peor, la | |
| 433 | carga de RSC incluye JSON en cadenas de texto de JS, un formato mucho menos | |
| 434 | eficiente que HTML. Aunque parece comprimirse bien con brotli y renderizarse | |
| 435 | rápido en el navegador, esto es un desperdicio. Con el patrón de hidratación, | |
| 436 | los datos se podrían reutilizar localmente, por lo menos, para la interactividad | |
| 437 | y otras páginas. | |
| 438 | ||
| 439 | Incluso en páginas con ínfima interactividad, pagas el precio. Como ejemplo, la | |
| 440 | documentación de Next.js, al cargar su [página de | |
| 441 | inicio](https://nextjs.org/docs), carga una página de alrededor de 750kB (250kB | |
| 442 | de HTML y 500kB de etiquetas script), y el contenido aparece dos veces. | |
| 443 | ||
| 444 | Puedes comprobarlo pulsando <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> en | |
| 445 | Mac o <kbd>Ctrl</kbd> + <kbd>u</kbd> en otras plataformas y después | |
| 446 | <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> para ubicar cualquier cadena | |
| 447 | en el blog, como "construyendo aplicaciones web full-stack". Aparece dos veces. | |
| 448 | Y **es inevitable**, ya que es una parte fundamental de los componentes de | |
| 449 | servidor de React. | |
| 450 | ||
| 451 | El formato de RSC definitivamente tiene más fallas, pero no tengo ganas de | |
| 452 | ponerme a investigar por qué la cadena `/_next/static/chunks/6192a3719cda7dcc.js` | |
| 453 | aparece 27 veces. ¿Qué diablos? ¿¿¿Dais el ancho de banda por gratis??? | |
| 454 | ||
| 455 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 456 | ||
| 457 | <h3#turbopack>Turbopack da asco</h3> | |
| 458 | ||
| 459 | Esta sección no es constructiva. | |
| 460 | ||
| 461 | - Turbopack no es ágil | |
| 462 | - Turbopack emite código difícil de depurar (en modo de desarrollo) | |
| 463 | - Turbopack produce mensajes de error malos en muchos casos | |
| 464 | ||
| 465 | Normalmente no le habría dado una sección en el blog a este punto, pero quiero | |
| 466 | mostrar tres ejemplos de verdad, directamente extraídos del proyecto. | |
| 467 | ||
| 468 | El primero es un lugar donde, al refactorizar código para satisfacer los modelos | |
| 469 | de componente de servidor/cliente, hice asíncrono un componente de cliente. Era | |
| 470 | irritante porque no indicaba dónde estaba el error, sólo contenía el stack trace | |
| 471 | del <b class='server'>servidor</b>. | |
| 472 | ||
| 473 |  | |
| 474 | ||
| 475 | Otro ejemplo de un error horrible: | |
| 476 | ||
| 477 |  | |
| 478 | ||
| 479 | > Tras arreglar el problema detrás de este segundo error (que ni siquiera recuerdo), | |
| 480 | > el servidor de desarrollo se quedó colgado y tuve que reiniciarlo para que se recuperara. | |
| 481 | ||
| 482 | El último caso es el millón de veces que he puesto un _breakpoint_ (punto de | |
| 483 | interrupción) en el depurador y la variable `hola` se convierte en | |
| 484 | `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hola"]` | |
| 485 | y más mierda. | |
| 486 | ||
| 487 | Vale. Todo esto da asco. ¿Qué podemos hacer? | |
| 488 | ||
| 489 | <h2#ditching-nextjs>Dejando atrás Next.js y Vercel en el trabajo</h2> | |
| 490 | ||
| 491 | Hay dos tipos de proyectos web: | |
| 492 | ||
| 493 | - Una página web con contenido mayoritariamente estático. | |
| 494 | - Una aplicación web con componentes mayormente dinámicos e interactivos. | |
| 495 | ||
| 496 | Y Next.js es la herramienta equivocada para ambos. Si quieres una página web | |
| 497 | estática, usa [Astro] o [Fresh]. Para quienes necesiten la potencia de React, | |
| 498 | esta sección trata cómo pasé de estar atado a Next a [TanStack Start], de forma | |
| 499 | incremental y sin baches. | |
| 500 | ||
| 501 | [Astro]: https://astro.build/ | |
| 502 | [Fresh]: https://fresh.deno.dev/ | |
| 503 | [TanStack Start]: https://tanstack.com/start/latest | |
| 504 | ||
| 505 | Todo empezó con esta configuración de Vite. | |
| 506 | ||
| 507 | ```ts filename="vite.config.ts" | |
| 508 | const config = defineConfig(({ mode }) => { | |
| 509 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 510 | return { | |
| 511 | // Usa el puerto predeterminado de Next.js, 3000 | |
| 512 | server: { port: 3000 }, | |
| 513 | // Usa el prefijo de variables de entorno predeterminado de Next.js, "NEXT_PUBLIC_" | |
| 514 | define: Object.fromEntries(Object.entries(env).map( | |
| 515 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 516 | plugins: [ | |
| 517 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 518 | tailwindcss(), | |
| 519 | // Para que mis compañeros de trabajo lo entendieran bien, empecé | |
| 520 | // a portar las rutas en `src/tanstack-routes`; en cuanto acabara, | |
| 521 | // volvería a cambiarlo a `src/routes`, como estaba por defecto. | |
| 522 | tanstackStart({ | |
| 523 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 524 | }), | |
| 525 | viteReact(), | |
| 526 | ], | |
| 527 | resolve: { | |
| 528 | // La clave para la migración incremental: redirigir `next` a otro lugar | |
| 529 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 530 | conditions: ["tanstack"], | |
| 531 | extensions: [ | |
| 532 | // Permitir que un archivo como `utils/session.tanstack.ts` | |
| 533 | // sobreescriba a `utils/session.ts` al ser importado. | |
| 534 | ".tanstack.tsx", ".tanstack.ts", | |
| 535 | // Extensiones de importación predeterminadas | |
| 536 | ".mjs", ".js", ".mts", ".ts", | |
| 537 | ".jsx", ".tsx", ".json", | |
| 538 | ], | |
| 539 | }, | |
| 540 | }; | |
| 541 | }); | |
| 542 | ``` | |
| 543 | ||
| 544 | Después, me puse a buscar cada uso de una API de Next.js, para o eliminarlo o | |
| 545 | crear un talón (_stub_) para TanStack. Por ejemplo, `src/tanstack-next/link.tsx` | |
| 546 | implementa `next/link`: | |
| 547 | ||
| 548 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 549 | import { Link } from "@tanstack/react-router"; | |
| 550 | import type { LinkProps } from "next/link"; | |
| 551 | ||
| 552 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 553 | return <Link {...rest} to={href as unknown as any} />; | |
| 554 | } | |
| 555 | ``` | |
| 556 | ||
| 557 | > Algunos de estos talones pueden ser extremadamente simples. Al principio, mi | |
| 558 | > implementación de `useRouter` era `return {}`. Más tarde, ya tuve que añadir | |
| 559 | > algunos métodos al objeto. El código no tiene por qué ser limpio, ya que es | |
| 560 | > temporal. | |
| 561 | ||
| 562 | De ahí, el sitio nuevo puede importar casi cada componente de cliente o creando | |
| 563 | talones para las APIs de Next.js que necesite o usando la extensión `.tanstack.ts` | |
| 564 | para reimplementar la lógica archivo por archivo. Poco después, conseguí que | |
| 565 | la página principal del sitio funcionara en TanStack Start e hicimos _merge_. | |
| 566 | ||
| 567 |  | |
| 568 | ||
| 569 | > En esta primera PR (solicitud de incorporación de cambios) sólo funcionaba una | |
| 570 | > de nuestras páginas, y conseguí que funcionara con unas mil líneas de código | |
| 571 | > añadido y 40 líneas eliminadas. Algunos parches previos eliminaban los pocos | |
| 572 | > usos de `next/image` y `next/font`. | |
| 573 | ||
| 574 | Lo que quedaba era portar el resto de rutas. Lo único que perdemos al migrar de | |
| 575 | Next.js a cualquier otro framework es el poder usar `await` con funciones de | |
| 576 | obtención de datos en la interfaz de usuario. En la práctica, mover cada ruta | |
| 577 | a una función `loader` esclareció qué era lo que pasaba al renderizar cada | |
| 578 | página desde el servidor (SSR). Para páginas con varias llamadas de obtención de | |
| 579 | datos, estas podían combinarse en una llamada API especial que devolviera todos | |
| 580 | los datos relevantes a ellas. | |
| 581 | ||
| 582 | Para reiterar, en negrita: <strong style='color:var(--secondary)'>El camino de | |
| 583 | migración de componentes de servidor es simplificar tu código &mdash; RSC | |
| 584 | intrínsecamente te lleva por un camino caótico repleto de cosas | |
| 585 | innecesarias</strong>. Casi todas las partes complejas de nuestro sitio se | |
| 586 | volvieron más fáciles de entender para todos nuestros ingenieros. La única | |
| 587 | excepción fue acostumbrar a todos a las nuevas convenciones para el enrutado | |
| 588 | del sistema de archivos. Con suficientes ejemplos, todos lo acabamos pillando. | |
| 589 | ||
| 590 | Con la migración incremental ya operativa, no se rompía el _deployment_ existente | |
| 591 | al añadir código nuevo. TanStack fue apoderándose del código y, con el tiempo, | |
| 592 | fuimos eliminando todos los talones de Next.js y ganamos todas las maravillosas | |
| 593 | [características de seguridad de tipos][type-safety features] que ofrece el | |
| 594 | enrutador de TanStack (TanStack Router). Al final, el sitio rendía más rápido en | |
| 595 | todos los aspectos: el modo de desarrollo, tiempos de carga en producción, | |
| 596 | navegaciones suaves, y todo a un menor coste que nuestro _deployment_ de Next con | |
| 597 | Vercel. | |
| 598 | ||
| 599 | [type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 600 | ||
| 601 | No somos los únicos sintiendo el cambio. Aunque intento evitar las redes | |
| 602 | sociales, alguien me envió [los resultados del trabajo de Brian Anglin en | |
| 603 | Superwall][superwall-twitter], mostrando reducciones de CPU increíbles usando | |
| 604 | TanStack Start. También recuerdo el cambio de ChatGPT de Next.js a Remix hace un | |
| 605 | año (conversaciones relevantes: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]). | |
| 606 | ||
| 607 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 608 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 609 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 610 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 611 | ||
| 612 | <h3#next-metadata><code>next/metadata</code> es maravilloso</h3> | |
| 613 | ||
| 614 | Bajo mi punto de vista, esta es una de las pocas APIs buenas que tiene Next.js, | |
| 615 | y fue el único lugar en nuestro código en el que el cambio a TanStack resultó en | |
| 616 | mayor dificultad. En vez de empeorar el código, porté su API de metadatos a una | |
| 617 | función normal y corriente, para que todos la pudieran usar. Solía tener un | |
| 618 | puerto 1:1 en NPM, pero a comienzos del año simplifiqué su API a un archivo | |
| 619 | pequeño y comprensible. En el momento de escribir esta publicación, he añadido | |
| 620 | una API `meta.toTags` compatible con TanStack que puede instalarse desde | |
| 621 | [JSR][lib-jsr] o [NPM][lib-npm], o simplemente la puedes copiar a tu proyecto. | |
| 622 | ||
| 623 | > **aviso**: Por limitaciones de tiempo para escribir este artículo, la librería | |
| 624 | > todavía no está actualizada. Probablemente lo haré ~~para el final de esta | |
| 625 | > semana (24 de octubre)~~ pronto... Por ahora, puedo compartir la versión que | |
| 626 | > uso en el trabajo: | |
| 627 | > [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts). | |
| 628 | ||
| 629 | ```tsx | |
| 630 | // una vez en tu proyecto | |
| 631 | import * as meta from "@clo/lib/meta.ts"; | |
| 632 | ||
| 633 | export const defineHead = meta.toTags.bind(null, { | |
| 634 | // opciones para todo el sitio | |
| 635 | base: new URL("https://paperclover.net"), | |
| 636 | titleTemplate: (title) => [title, "paper clover"] | |
| 637 | .filter(Boolean).join(' | '), | |
| 638 | // ... | |
| 639 | }); | |
| 640 | ||
| 641 | // para cada página... | |
| 642 | export const Route = createFileRoute("/blog")({ | |
| 643 | head: () => | |
| 644 | defineHead({ | |
| 645 | title: "blog de clover", // usando la plantilla `titleTemplate` | |
| 646 | description: "una gatita maúlla sobre sus opiniones tecnológicas", | |
| 647 | canonical: "/blog", // yuxtapuesto a `base` | |
| 648 | ||
| 649 | // Cuando se especifica, configura el embed (embebido) | |
| 650 | // de Open Graph y Twitter, usando el título y la | |
| 651 | // descripción de la página por defecto. | |
| 652 | // La configuración predeterminada está bien, | |
| 653 | // pero se pueden especificar más opciones. | |
| 654 | embed: { | |
| 655 | image: "/img/blog.webp", | |
| 656 | }, | |
| 657 | ||
| 658 | // Todas las etiquetas meta exóticas se hacen con un fragmento | |
| 659 | // JSX. No se renderiza React, sólo se itera sobre las etiquetas. | |
| 660 | // Mi objetivo era cubrir el 99% de los casos comunes. | |
| 661 | extra: <> | |
| 662 | <meta name="site-verification" content="waffles" />, | |
| 663 | </>, | |
| 664 | }), | |
| 665 | ||
| 666 | component: Page, | |
| 667 | }); | |
| 668 | ||
| 669 | function Page() { | |
| 670 | ... | |
| 671 | } | |
| 672 | ``` | |
| 673 | ||
| 674 | Mi versión no se preocupaba con cubrir todas las posibilidades del objeto de | |
| 675 | metadatos de Next.js; usa JSX en línea (_inline_) para llenar ese hueco. | |
| 676 | ||
| 677 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 678 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 679 | ||
| 680 | <h3#vercel-og><code>next/og</code> está bien también</h3> | |
| 681 | ||
| 682 | No tengo una opinión fuerte al respecto. Sólo quiero recordar a todos que existe | |
| 683 | el paquete `@vercel/og`. | |
| 684 | ||
| 685 | <h2#experience-feels-like-the-usual>Mi experiencia parece ser la típica</h2> | |
| 686 | ||
| 687 | En la Next.js Conf 2024, todos hablaban maravillas de los componentes de | |
| 688 | servidor. No recuerdo exactamente con quién fue que hablé, pero todos los | |
| 689 | grandes nombres estaban completamente a favor de ellos. Habiendo implementado el | |
| 690 | empaquetador de RSC, yo vi algunos de los problemas en el formato. Ahora, viendo | |
| 691 | que Next 15 "estabilizó" el enrutador de aplicaciones el año pasado, muchas | |
| 692 | compañías están construyendo productos con él, dándose cuenta de estos | |
| 693 | inconvenientes de primera mano. | |
| 694 | ||
| 695 | Llegué tarde al mundo de Next.js, pues empecé en junio con la versión 15. Pero | |
| 696 | todos con los que he hablado están de acuerdo con mis observaciones. Todos con | |
| 697 | los que hablé en la 1.3 Party de Bun estaban de acuerdo conmigo. Incluso gente | |
| 698 | de Vercel me ha dicho que no les gusta cómo es usar Next.js. | |
| 699 | ||
| 700 | Espero que, al estabilizarse TanStack Start, se vuelva el nuevo sustituto de | |
| 701 | Next.js que todos quieran. | |
| 702 | ||
| 703 | <h2#prefer-respectful-tools>Opta por herramientas que te respeten</h2> | |
| 704 | ||
| 705 | El ecosistema de JavaScript es un desastre y ese desastre es por el que la gente | |
| 706 | se burla del desarrollo web. Muchas veces he pensado que el desastre de trabajar | |
| 707 | en la web no tenía solución, pero el desastre eran en verdad las librerías | |
| 708 | comúnmente usadas de las que me rodeaba. Al quitar esa capa, las tecnologías de | |
| 709 | desarrollo web modernas son maravillosas. | |
| 710 | ||
| 711 | Llevo desarrollando esta página web desde cero, sin framework, desde finales de | |
| 712 | 2024, implementando sistemas como mi propio [widget de progreso para | |
| 713 | aplicaciones de terminal][progress], [proxy para archivos | |
| 714 | estáticos][file-cache], sistema de compilación incremental, y muchos más | |
| 715 | componentes. Trabajar en este código ha llevado a las mejores sesiones de código | |
| 716 | (en términos de felicidad) en años. Los visitantes de *[paper clover]* se llevan | |
| 717 | una página web de mejor calidad; las minilibrerías que he creado las [extraigo | |
| 718 | para uso público][lib], todos salen ganando. | |
| 719 | ||
| 720 | Este nivel de "desde cero" es demasiado para la mayoría, especialmente en el | |
| 721 | trabajo. Diría que, como mínimo, deberíamos dedicar atención y dinero sólo a | |
| 722 | herramientas de alta calidad que nos respeten. Y Next.js y la empresa detrás de | |
| 723 | ello, Vercel, no caen dentro de ese criterio. | |
| 724 | ||
| 725 | Si usas Next.js y sientes que tu experiencia tampoco te recuerda al respeto, | |
| 726 | reflexiona si tus compañeros de trabajo y tú queréis seguir apoyando su [imperio | |
| 727 | serverless][serverless empire]. El ecosistema de Vite parece bastante decente | |
| 728 | para desarrollar ahora mismo, pero no tengo mucha experiencia todavía en usar | |
| 729 | sus herramientas a gran escala en producción. El [lanzamiento de Vite+ de | |
| 730 | Void0][vite-plus] parece interesante, pero sólo el tiempo dirá si estas | |
| 731 | herramientas, financiadas por capital, nos respetarán (a los usuarios y a los | |
| 732 | desarrolladores) a largo plazo. | |
| 733 | ||
| 734 | La Next.js Conf 2025, en el momento de escribir, es [mañana][next-conf]. En vez | |
| 735 | de comprarme una entrada de $800, decidí destinar ese dinero al [equipo de | |
| 736 | TanStack][tanstack-donate] por [respetar y mejorar el ecosistema de desarrollo | |
| 737 | web][tanstack-ethos]. | |
| 738 | ||
| 739 | [paper clover]: https://paperclover.net/ | |
| 740 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 741 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 742 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 743 | ||
| 744 | [next-conf]: https://nextjs.org/conf | |
| 745 | [vite-plus]: https://viteplus.dev/ | |
| 746 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 747 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 748 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 749 | ||
| 750 | ## Lo que aguarda el futuro | |
| 751 | ||
| 752 | Paulatinamente, he estado reemplazando mucho software que me falta al respeto | |
| 753 | con mejores alternativas. Algunos ejemplos son GitHub, Visual Studio Code, | |
| 754 | DaVinci Resolve, Discord, Google Drive/Workspace, entre muchos otros. Pienso | |
| 755 | escribir más en este blog sobre mis proyectos técnicos (la librería de progreso, | |
| 756 | el propósito de mi generador de sitio propio, aprendizajes de mi trabajo | |
| 757 | actual), incluyendo algunos de mis proyectos pasados en Bun (detalles sobre HMR, | |
| 758 | el sistema de reporte de errores y la locura de sistema para empaquetar módulos | |
| 759 | integrados). Si te interesa, por favor suscríbete a la lista de correos: | |
| 760 | ||
| 761 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">haz clic aquí para enviar un correo a <code>subscribe@paperclover.net</code>, pidiendo ser añadido a la lista de correos.</a> (llevo esta lista de correos a mano) | |
| 762 | ||
| 763 | [volver al inicio](#top) &mdash; [pregúntame algo sobre este artículo](/q+a) | |
| 764 | ||
| 765 | <br /> | |
| 766 | <br /> | |
| 767 | <br /> | |
| 768 | <br /> | |
| 769 | <br /> | |
| 770 | <br /> | |
| 771 | <footer> | |
| 772 | 2025 (c) paper clover | |
| 773 | </footer> |
src/blog/pages/webdev/one-year-next-app-router/es.mdx deleted-868| ... | ... | @@ -1,868 +0,0 @@ |
| 1 | import Heading from "@/blog/tags/heading.tsx"; | |
| 2 | import TableOfContents from "@/blog/tags/table-of-contents.tsx"; | |
| 3 | import { Layout } from "@/blog/tags/layout.tsx"; | |
| 4 | export { theme } from "@/blog/tags/layout.tsx"; | |
| 5 | ||
| 6 | export const meta = { | |
| 7 | title: "Un año con el enrutador de aplicación (App Router) de Next.js — por qué decidimos cambiar", | |
| 8 | description: "Una crítica de los componentes de servidor de React y Next.js 15.", | |
| 9 | keywords: ["webdev", "technical analysis", "opinion"], | |
| 10 | authors: ["clover caruso"], | |
| 11 | embed: { | |
| 12 | thumbnail: "/open-graph/next-js.es.png" | |
| 13 | }, | |
| 14 | twitter: { | |
| 15 | image: "https://paperclover.net/open-graph/next-js.png" | |
| 16 | }, | |
| 17 | canonical: "/blog/webdev/one-year-next-app-router.es" | |
| 18 | }; | |
| 19 | ||
| 20 | <Layout | |
| 21 | meta={meta} | |
| 22 | date={'Oct 21st, 2025'} | |
| 23 | slug="webdev/one-year-next-app-router" | |
| 24 | tags={meta.keywords} | |
| 25 | translation={{ | |
| 26 | lang: "es", | |
| 27 | author: "@trubiso", | |
| 28 | href: null, | |
| 29 | date: "Jan 13th, 2026" | |
| 30 | }} | |
| 31 | > | |
| 32 | ||
| 33 | He estado usando [Next.js] profesionalmente para el desarrollo de aplicaciones | |
| 34 | web en mi trabajo y el diseño de su enrutador de aplicación (App Router) y los | |
| 35 | [componentes de servidor (React Server Components/RSC)][rsc] me parece | |
| 36 | extremadamente frustrante a nivel fundamental. No es por pequeños bugs o porque | |
| 37 | la API me parezca confusa, sino por grandes discrepancias en las decisiones de | |
| 38 | diseño fundamentales que tomaron Vercel y el equipo de React en su creación. | |
| 39 | ||
| 40 | Cuanto más asisto a eventos de desarrollo web, más veo a gente a quien no le | |
| 41 | gusta Next.js, pero que aún así debe usarlo. Al final del artículo, compartiré | |
| 42 | cómo escapamos de este infierno, migrando todo nuestro frontend a [TanStack | |
| 43 | Start] sin problemas. | |
| 44 | ||
| 45 | [Next.js]: https://nextjs.org | |
| 46 | [rsc]: https://react.dev/reference/rsc/server-components | |
| 47 | ||
| 48 | <TableOfContents> | |
| 49 | ||
| 50 | - [Un repaso técnico: ¿qué son los componentes de servidor?][§1] | |
| 51 | - [Los inconvenientes del enrutador de aplicaciones][§2] | |
| 52 | - [Las actualizaciones optimistas son imposibles][§2.1] | |
| 53 | - [Cada navegación supone una petición][§2.2] | |
| 54 | - [Los layouts tienen restricciones artificiales][§2.3] | |
| 55 | - [Te llevas todo el contenido dos veces de todas formas][§2.4] | |
| 56 | - [Turbopack da asco][§2.5] | |
| 57 | - [Dejando atrás Next.js y Vercel en el trabajo][§3] | |
| 58 | - [`next/metadata` es maravilloso][§3.1] | |
| 59 | - [`next/og` está bien también][§3.2] | |
| 60 | - [Mi experiencia parece ser la típica][§4] | |
| 61 | - [Opta por herramientas que te respeten][§5] | |
| 62 | ||
| 63 | </TableOfContents> | |
| 64 | ||
| 65 | [§1]: #technical-review | |
| 66 | ||
| 67 | <Heading | |
| 68 | level='h2' | |
| 69 | slug='technical-review' | |
| 70 | >Un repaso técnico: ¿qué son los componentes de servidor?</Heading> | |
| 71 | ||
| 72 | El punto clave de RSC es que los componentes se dividen en dos categorías, | |
| 73 | componentes de <b class='server'>"servidor"</b> y componentes de <b class='client'>"cliente"</b>. Los componentes de servidor pueden usar ni | |
| 74 | `useState` ni `useEffect`, pero pueden hacer uso de funciones `async` y emplear | |
| 75 | herramientas de backend, permitiendo por ejemplo llamar directamente a una base | |
| 76 | de datos. Los componentes de cliente constituyen el modelo tradicional, en el | |
| 77 | que hay código en el backend para generar HTML y en el frontend para administrar | |
| 78 | el DOM usando `window.document.*`. | |
| 79 | ||
| 80 | > Ahí va el primer desastre: ¡la nomenclatura! React usa las palabras <b class='server'>"servidor"</b> y <b class='client'>"cliente"</b> para referirse a cosas muy específicas, ignorando sus definiciones existentes. No habría ningún problema, si no fuera por que ¡los componentes de <b class='client'>cliente</b> también pueden correr en el backend! En este artículo, utilizaré los términos <b>"backend"</b> y <b>"frontend"</b> para describir los ámbitos de ejecución en los que existen las aplicaciones web: un proceso de Node.js y un navegador web, respectivamente. | |
| 81 | ||
| 82 | Este modelo de componentes de <b class='server'>servidor</b> y de <b class='client'>cliente</b> es curioso. Dado que las funciones integradas, como | |
| 83 | `<Suspense />`, se serializan a través de la red, la obtención de datos se puede | |
| 84 | modelar de manera trivial con <b class='server'>componentes de servidor</b> | |
| 85 | asíncronos, y la interfaz de usuario alternativa funciona como si fuera del lado | |
| 86 | del cliente. | |
| 87 | ||
| 88 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 89 | // En este artículo, los componentes de servidor están resaltados en rojo. | |
| 90 | export default async function Page({ params }) { | |
| 91 | // Los parámetros de la página se pasan como una Promise resuelta. | |
| 92 | const { username } = await params; | |
| 93 | ||
| 94 | // Los componentes `UserInfo` y `UserPostList` serán corridos a la vez. En | |
| 95 | // cuanto `UserInfo` esté listo, el visitante verá la página con un | |
| 96 | // `PostListSkeleton` si la lista de publicaciones no está lista. | |
| 97 | return <main> | |
| 98 | <UserInfo username={username} /> | |
| 99 | ||
| 100 | <Suspense fallback={<PostListSkeleton />}> | |
| 101 | <UserPostList username={username} /> | |
| 102 | </Suspense> | |
| 103 | </main> | |
| 104 | } | |
| 105 | ||
| 106 | // Evitamos waterfalls teniendo varios componentes evaluados a la vez. | |
| 107 | ||
| 108 | async function UserInfo({ username }) { | |
| 109 | const user = await fetchUserInfo(username); | |
| 110 | return <> | |
| 111 | <h1>{user.displayName}</h1> | |
| 112 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 113 | </> | |
| 114 | } | |
| 115 | ||
| 116 | async function UserPostList({ username }) { | |
| 117 | const posts = await fetchUserPostList(username); | |
| 118 | return /* interfaz de usuario de la lista de publicaciones omitida por brevedad */; | |
| 119 | } | |
| 120 | ``` | |
| 121 | ||
| 122 | El ejemplo anterior no usa nada de JavaScript (sin contar el tamaño del empaquetado | |
| 123 | _gzipped_ de 40kB del propio React) para la interfaz de usuario ni para la | |
| 124 | obtención de datos &mdash; ¡sólo envía el HTML! Por ejemplo, el _parser_ (analizador) | |
| 125 | imaginario de Markdown dentro del componente `<Markdown />` se queda en el | |
| 126 | backend. En aquellos casos en los que se necesita un frontend interactivo, | |
| 127 | simplemente se puede crear un <b class='client'>componente de cliente</b> | |
| 128 | poniéndolo en un archivo empezando con `"use client"`. | |
| 129 | ||
| 130 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 131 | "use client"; // Este comentario marca el archivo para el empaquetado del lado del cliente. | |
| 132 | ||
| 133 | export function CopyButton({ url }) { | |
| 134 | return <> | |
| 135 | <span>{url}</span> | |
| 136 | <button onClick={() => { | |
| 137 | const full = new URL(url, location.href); | |
| 138 | navigator.clipboard.writeText(full.href); | |
| 139 | // omitiendo todo el manejo de errores, la interfaz de éxito, los estilos... | |
| 140 | }}>copy</button> | |
| 141 | </> | |
| 142 | } | |
| 143 | ``` | |
| 144 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 145 | export function Card() { | |
| 146 | return <article> | |
| 147 | <header> | |
| 148 | {/* Hace que el navegador importe el botón de copiar */} | |
| 149 | <CopyButton url="/q+a/2506010139" /> | |
| 150 | </header> | |
| 151 | <p> | |
| 152 | {/* Procesa el Markdown en el backend */} | |
| 153 | <Markdown content=".........." /> | |
| 154 | </p> | |
| 155 | </article> | |
| 156 | } | |
| 157 | ``` | |
| 158 | ||
| 159 | [§2]: #real-world-pitfalls | |
| 160 | ||
| 161 | <Heading | |
| 162 | level='h2' | |
| 163 | slug='real-world-pitfalls' | |
| 164 | >Los inconvenientes del enrutador de aplicación</Heading> | |
| 165 | ||
| 166 | Tras dejar [Bun] como ingeniero de runtime (donde implementé el [empaquetado de | |
| 167 | componentes de servidor][Server Components bundling] y [una plantilla de | |
| 168 | RSC][bun-rsc]), me uní a una pequeña compañía, trabajando en la línea de fuego: | |
| 169 | una aplicación de Next.js con un backend de Hono. Lo siguiente es una versión | |
| 170 | simplificada de los problemas con los que me enfrenté tratando de mantener y | |
| 171 | desarrollar características nuevas. Como resultado de todos ellos, perdimos | |
| 172 | todos el tiempo intentando eludir fallos de diseño o explicándonos por qué algo | |
| 173 | que no debería ser un problema para empezar se ha convertido en un obstáculo | |
| 174 | inamovible. | |
| 175 | ||
| 176 | [Bun]: https://bun.com | |
| 177 | [Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 178 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 179 | ||
| 180 | [§2.1]: #optimistic-updates | |
| 181 | ||
| 182 | <Heading | |
| 183 | level='h3' | |
| 184 | slug='optimistic-updates' | |
| 185 | >Las actualizaciones optimistas son imposibles</Heading> | |
| 186 | ||
| 187 | La documentación de Next.js respecto a la mutación [no menciona las | |
| 188 | actualizaciones optimistas][nextjs-updating-data], parece ser que no pensaron en | |
| 189 | este caso. Los componentes renderizados por el <b class='server'>servidor de | |
| 190 | React</b> no pueden ser modificados tras ser montados por diseño. Los elementos | |
| 191 | que puedan tener cambios deben estar dentro de un componente de cliente, pero no | |
| 192 | puede estos componentes no pueden dar lugar a obtención de datos, incluso | |
| 193 | durante el renderizado del lado del servidor (SSR) en el backend. Esto deriva en | |
| 194 | componentes de servidor incómodamente diminutos que sólo se encargan de obtener | |
| 195 | ciertos datos y tienen un componente homólogo de cliente que contiene una | |
| 196 | versión prácticamente estática de la página. | |
| 197 | ||
| 198 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 199 | ||
| 200 | export default async function Page() { | |
| 201 | const user = await fetchUserInfo(username); | |
| 202 | return <ProfileLayout> | |
| 203 | <UserProfile user={user} /> | |
| 204 | </ProfileLayout>; | |
| 205 | } | |
| 206 | ``` | |
| 207 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 208 | ||
| 209 | "use client"; // Toca mover el código de cliente a otro archivo! | |
| 210 | ||
| 211 | export function UserProfile({ user: initialUser }) { | |
| 212 | // Existen muchas librerías de gestión de estado excelentes; | |
| 213 | // para simplificar, usaremos una celda de estado. | |
| 214 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 215 | ||
| 216 | async function onEdit(newUser) { | |
| 217 | optimisticUpdateUser(newUser); | |
| 218 | const resp = await fetch("...", { | |
| 219 | method: 'POST', | |
| 220 | body: JSON.stringify(newUser), | |
| 221 | ... // (encabezados, credenciales, trazado y más) | |
| 222 | }) | |
| 223 | if (!resp.ok) /* ¡recuerda siempre comprobar si se ha dado algún error! */ | |
| 224 | } | |
| 225 | ||
| 226 | return <main>{/* interfaz de usuario con campos editables... */}</main>: | |
| 227 | } | |
| 228 | ``` | |
| 229 | ||
| 230 | A medida que más partes de la página necesitan interactividad, se vuelve más | |
| 231 | lioso mantener las partes estáticas puramente del lado del servidor. En la | |
| 232 | aplicación de trabajo, casi cada componente de la interfaz de usuario muestra | |
| 233 | datos dinámicos. Un [`WebSocket`][ws] sincroniza los datos en tiempo real a | |
| 234 | medida que se actualizan (por ejemplo, el estado en línea y el perfil básico de | |
| 235 | una tarjeta de usuario). Como estas configuraciones de componentes son más | |
| 236 | difíciles de entender y mantener para los ingenieros, casi todas nuestras | |
| 237 | páginas están marcadas como `"use client"` con un `page.tsx` que define la | |
| 238 | obtención de datos necesaria. | |
| 239 | ||
| 240 | Un ejemplo más concreto de cómo queda esto en la práctica con la librería de | |
| 241 | obtención de datos que usamos en el trabajo, [TanStack Query]. | |
| 242 | ||
| 243 | [TanStack Query]: https://github.com/tanstack/query#readme | |
| 244 | ||
| 245 | ```ts filename="src/queries/users.ts" | |
| 246 | // Tenemos una función auxiliar `defineQuery` para la seguridad de tipos en el trabajo. | |
| 247 | // Los obtenedores de datos son triviales y pueden correr tanto en el backend como en el frontend. | |
| 248 | export const queryUserInfo = (username) => ({ | |
| 249 | queryKey: ['user', username], | |
| 250 | queryFn: async ({ ... }) => /* obtener datos */ | |
| 251 | }); | |
| 252 | ``` | |
| 253 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 254 | export default async function Page({ params }) { | |
| 255 | const { username } = await params; | |
| 256 | ||
| 257 | // No hay estado global en el servidor de React. Como los layouts | |
| 258 | // se ejecutan en paralelo, toca reconstruir el `QueryClient` de | |
| 259 | // TanStack varias veces por ruta. | |
| 260 | const queryClient = new QueryClient(); | |
| 261 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 262 | ||
| 263 | // HydrationBoundary es un componente de cliente que pasa datos | |
| 264 | // JSON del servidor de React al componente de cliente. | |
| 265 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 266 | <ClientPage /> | |
| 267 | </HydrationBoundary>; | |
| 268 | } | |
| 269 | ``` | |
| 270 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 271 | "use client"; | |
| 272 | export function ClientPage() { | |
| 273 | const { username } = useParams(); | |
| 274 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 275 | ||
| 276 | // ... algunos hooks | |
| 277 | ||
| 278 | return <main> | |
| 279 | {/* ... una página web interactiva */} | |
| 280 | </main>; | |
| 281 | } | |
| 282 | ``` | |
| 283 | ||
| 284 | Este ejemplo necesita tres archivos por las reglas del empaquetado de los | |
| 285 | componentes de servidor (el componente de cliente necesita `"use client"` y los | |
| 286 | archivos de componentes de servidor no suelen poderse importar en el cliente | |
| 287 | debido a las importaciones exclusivas de servidor). En el enrutador de páginas | |
| 288 | (Pages Router), podría haberlo hecho todo en un solo archivo por el tree-shaking | |
| 289 | que poseen `getStaticProps` y `getServerSideProps`. | |
| 290 | ||
| 291 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 292 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 293 | ||
| 294 | ||
| 295 | [§2.2]: #redundant-fetches | |
| 296 | ||
| 297 | <Heading | |
| 298 | level='h3' | |
| 299 | slug='redundant-fetches' | |
| 300 | >Cada navegación supone una petición</Heading> | |
| 301 | ||
| 302 | Como el enrutador de aplicación inicia cada página como componente de servidor, | |
| 303 | con áreas pequeñas de interactividad (idealmente), navegar a una nueva página | |
| 304 | ¡*fuerza* una petición al servidor de Next.js, independientemente de los datos | |
| 305 | que el cliente ya tenga disponibles! Incluso con un archivo `loading.tsx`, al | |
| 306 | abrir `/`, navegar a `/other/ y luego volver a `/`, se mostrará el estado de | |
| 307 | carga mientras vuelve a obtener los datos de la página de inicio. | |
| 308 | ||
| 309 | Para lo único para lo que esto funciona bien es para el **contenido | |
| 310 | perfectamente estático**, donde las navegaciones instantáneas y la precarga | |
| 311 | (prefetching) funcionan genial. Pero **las aplicaciones web no son estáticas**, | |
| 312 | tienen mucho contenido dinámico. Haber iniciado sesión afecta a la página de | |
| 313 | inicio, cosa que es irritante porque el cliente literalmente ya tiene todo lo | |
| 314 | que necesita para mostrar la página instantáneamente. Ni siquiera han cambiado | |
| 315 | las cookies. | |
| 316 | ||
| 317 | > **nota**: Probándolo más en un proyecto en blanco, he observado casos en los | |
| 318 | > que el código de frontend de Next precarga rutas **sin ningún contenido | |
| 319 | > real**. En el ejemplo de hello world, era una carga de 1.8kB de RSC que | |
| 320 | > apuntaba a 2 fragmentos de JS diferentes 4 veces distintas. Esto es un desperdicio | |
| 321 | > puro de nuestro ancho de banda y egreso, especialmente considerando que toda | |
| 322 | > esta información se vuelve a obtener cuando de verdad hago clic en el enlace. | |
| 323 | > | |
| 324 | > ```json whitespace="pre-wrap" | |
| 325 | > 1:"$Sreact.fragment" | |
| 326 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 327 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 328 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 329 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 330 | > 7:"$Sreact.suspense" | |
| 331 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 332 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 333 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 334 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 335 | > ``` | |
| 336 | > | |
| 337 | > Revisándolo, me di cuenta de que en realidad hay algo de contenido: el estado | |
| 338 | > de carga. ¿Lo ves? | |
| 339 | > | |
| 340 | > ```json | |
| 341 | > ["$","div","l",{"children":"loading..."}] | |
| 342 | > ``` | |
| 343 | > | |
| 344 | > Sigue siendo un desperdicio grande, ya que todos estos datos se vuelven a | |
| 345 | > emitir en el RSC de la propia página. | |
| 346 | ||
| 347 | Parece ser que la solución a esto es [`staleTime`][nextjs-stale], pero está | |
| 348 | marcado como experimental y "no recomendado para producción". Es una vergüenza | |
| 349 | el hecho de que esto sea una opción que no está por defecto y que parece haber | |
| 350 | sido una idea de último momento. Incluso usándola, no es posible hacer que | |
| 351 | varias páginas que hacen referencia a los mismos datos los compartan. | |
| 352 | ||
| 353 | Un ejemplo de un estado de carga irrepresentable con el enrutador de aplicación | |
| 354 | es el de ciertas páginas, como una página de propuestas (issues) en un proyecto | |
| 355 | de git, al hacer clic en un nombre de usuario para ir a su página de perfil. Con | |
| 356 | `loading.tsx`, la página entera es un esqueleto, pero al modelar estas consultas | |
| 357 | con TanStack Query es posible mostrar el nombre de usuario y avatar | |
| 358 | instantáneamente mientras cargan la biografía del usuario y sus repositorios. | |
| 359 | Los componentes de servidor son incompatibles con esta tipo de navegación porque | |
| 360 | los datos sólo están disponibles en componentes renderizados, con lo cual deben | |
| 361 | ser obtenidos una vez más. | |
| 362 | ||
| 363 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 364 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 365 | ||
| 366 | En nuestro sitio Next.js, tenemos esta línea de código en nuestros obtenedores | |
| 367 | de datos en los componentes de servidor para hacer que las navegaciones suaves | |
| 368 | vayan más rápido, saltándose la fase de obtención de datos por completo. | |
| 369 | ||
| 370 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 371 | export function serverSidePrefetchQueries(queries) { | |
| 372 | if ((await headers()).get("next-url")) { | |
| 373 | // Se trata de una navegación suave. Nos SALTAMOS la precarga para | |
| 374 | 	// aumentar la velocidad. Puede que el cliente ya tenga estos datos y, | |
| 375 | 	// de lo contrario, tienen el estado de carga. Idealmente, no existiría | |
| 376 | 	// esta petición -- el lado de cliente ya tiene casi TODO el código ya | |
| 377 | 	// que la aplicación está escrita principalmente usando componentes de | |
| 378 | 	// cliente. Un fallo de diseño de parte del enrutador de aplicación, la | |
| 379 | 	// verdad. | |
| 380 | return; | |
| 381 | } | |
| 382 | // ... lógica de precarga de datos ... | |
| 383 | } | |
| 384 | ``` | |
| 385 | ||
| 386 | Aparte, `loading.tsx` debería contener las llamadas a `useQuery` para que, | |
| 387 | mientras se realiza la petición para el RSC vacío, se obtengan los datos si de | |
| 388 | verdad se necesitan. De hecho, el estado `loading.tsx` puede ser directamente el | |
| 389 | componente de cliente y se mostrará la página de cliente. | |
| 390 | ||
| 391 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 392 | "use client"; | |
| 393 | export default function PageLoadingSkeleton() { | |
| 394 | return <ClientPage />; | |
| 395 | } | |
| 396 | ``` | |
| 397 | ||
| 398 | > En el trabajo, simplemente hacemos que nuestros archivos `loading.tsx` | |
| 399 | > contengan las llamadas a `useQuery` y muestren un esqueleto. Esto se debe a | |
| 400 | > que cuando Next.js carga el componente de servidor en sí, remonta la página | |
| 401 | > entera sí o sí. No se da ningún tipo de VDOM diffing, así que todos los hooks | |
| 402 | > (`useState`) se reiniciarán un poco después de que la petición se complete. | |
| 403 | > Intenté reproducir un caso simple en el que estaba *suplicándole* a Next.js | |
| 404 | > que tan solo *actualizara el DOM existente* y preservara el estado, pero | |
| 405 | > directamente no lo hace. Por suerte, el tiempo que tarda la llamada RSC en | |
| 406 | > blanco es suficientemente corto. | |
| 407 | ||
| 408 | [§2.3]: #layout-restrictions | |
| 409 | ||
| 410 | <Heading | |
| 411 | level='h3' | |
| 412 | slug='layout-restrictions' | |
| 413 | >Los layouts tienen restricciones artificiales</Heading> | |
| 414 | ||
| 415 | Los layouts pueden obtener datos, pero no pueden observar o alterar la petición | |
| 416 | de ninguna manera. Está hecho así para que Next.js pueda obtener y cachear los | |
| 417 | layouts cada vez que quiera. En cualquier otro framework, los layouts son | |
| 418 | componentes normales sin diferencia de otros componentes en la página. | |
| 419 | ||
| 420 | Obtener los layouts por sí mismos es una idea chula, pero acaba siendo estúpida, | |
| 421 | pues toda obtención de datos debe rehacerse para cada layout. No puedes | |
| 422 | compartir un `QueryClient`, en su lugar debes usar su | |
| 423 | [`fetch` _monkey-patched_][nextjs-fetch] para cachear la misma petición `GET` | |
| 424 | tal y como prometen. | |
| 425 | ||
| 426 | Cuando me pregunta un compañero de trabajo por qué Next.js rechaza código, ya ni | |
| 427 | intento explicar los entresijos, directamente digo *"Es un problema de habilidad | |
| 428 | de Next.js, no te preocupes, que le voy a dar fuego pronto"*. Estas reglas son | |
| 429 | demasiado difíciles para que un desarrollador promedio las entienda. | |
| 430 | ||
| 431 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 432 | ||
| 433 | [§2.4]: #rsc-payload | |
| 434 | ||
| 435 | <Heading | |
| 436 | level='h3' | |
| 437 | slug='rsc-payload' | |
| 438 | >Te llevas todo el contenido dos veces de todas formas</Heading> | |
| 439 | ||
| 440 | A diferencia de la ["arquitectura de islas"][islands], los componentes de | |
| 441 | servidor deben ser hidratados en el frontend para poder emplear `Suspense` y | |
| 442 | mantener el estado de los componentes de cliente. Al hacer navegaciones suaves, | |
| 443 | se obtiene mediante `fetch` la "carga de RSC" (que no es HTML en absoluto). | |
| 444 | Al hacer una recarga fresca de la página, se necesita HTML para | |
| 445 | [pintar por primera vez][first paint], pero la información sobre los componentes | |
| 446 | de cliente y `Suspense` no está en dicho HTML. La solución de React es **enviar | |
| 447 | una segunda copia del _markup_ de la página entera**. Un ejemplo de lo que | |
| 448 | enviaría un servidor de producción de Next.js en un renderizado dinámico de una | |
| 449 | página sería algo así: | |
| 450 | ||
| 451 | [first paint]: https://web.dev/articles/fcp | |
| 452 | ||
| 453 | ```html filename="GET /user/clover" | |
| 454 | <!DOCTYPE html> | |
| 455 | <html> | |
| 456 | <head> | |
| 457 | {etiquetas link y meta} | |
| 458 | </head> | |
| 459 | <body> | |
| 460 | {renderizado del lado del servidor} | |
| 461 | <script> | |
| 462 | // un script de inicialización que inicia `__next_f` | |
| 463 | // como un arreglo. en cuanto carga React, la función | |
| 464 | // `.push` se modifica para escribir nuevos fragmentos | |
| 465 | // al decodificador de RSC directamente. tiene algunos | |
| 466 | // auxiliares de DOM también | |
| 467 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 468 | </script> | |
| 469 | <script> | |
| 470 | // la carga de RSC para el shell de la aplicación. | |
| 471 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 472 | </script> | |
| 473 | ||
| 474 | <!-- | |
| 475 | NO está escrita todavía la etiqueta </body>, ya que hay una | |
| 476 | frontera de Suspense sin resolver. con el paso del tiempo, | |
| 477 | se escriben más datos. | |
| 478 | --> | |
| 479 | <div class="user-post-list"> | |
| 480 | {renderizado del lado del servidor de una frontera de Suspense} | |
| 481 | </div> | |
| 482 | <script> | |
| 483 | // la carga de RSC para la frontera de Suspense | |
| 484 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 485 | </script> | |
| 486 | ||
| 487 | <!-- se repiten etiquetas HTML y script hasta que la página acaba --> | |
| 488 | </body> | |
| 489 | </html> | |
| 490 | ``` | |
| 491 | ||
| 492 | Esta solución **duplica el tamaño de la carga inicial de HTML**. Aún peor, la | |
| 493 | carga de RSC incluye JSON en cadenas de texto de JS, un formato mucho menos | |
| 494 | eficiente que HTML. Aunque parece comprimirse bien con brotli y renderizarse | |
| 495 | rápido en el navegador, esto es un desperdicio. Con el patrón de hidratación, | |
| 496 | los datos se podrían reutilizar localmente, por lo menos, para la interactividad | |
| 497 | y otras páginas. | |
| 498 | ||
| 499 | Incluso en páginas con ínfima interactividad, pagas el precio. Como ejemplo, la | |
| 500 | documentación de Next.js, al cargar su [página de | |
| 501 | inicio](https://nextjs.org/docs), carga una página de alrededor de 750kB (250kB | |
| 502 | de HTML y 500kB de etiquetas script), y el contenido aparece dos veces. | |
| 503 | ||
| 504 | Puedes comprobarlo pulsando <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> en | |
| 505 | Mac o <kbd>Ctrl</kbd> + <kbd>u</kbd> en otras plataformas y después | |
| 506 | <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> para ubicar cualquier cadena | |
| 507 | en el blog, como "construyendo aplicaciones web full-stack". Aparece dos veces. | |
| 508 | Y **es inevitable**, ya que es una parte fundamental de los componentes de | |
| 509 | servidor de React. | |
| 510 | ||
| 511 | El formato de RSC definitivamente tiene más fallas, pero no tengo ganas de | |
| 512 | ponerme a investigar por qué la cadena `/_next/static/chunks/6192a3719cda7dcc.js` | |
| 513 | aparece 27 veces. ¿Qué diablos? ¿¿¿Dais el ancho de banda por gratis??? | |
| 514 | ||
| 515 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 516 | ||
| 517 | [§2.5]: #turbopack | |
| 518 | ||
| 519 | <Heading | |
| 520 | level='h3' | |
| 521 | slug='turbopack' | |
| 522 | >Turbopack da asco</Heading> | |
| 523 | ||
| 524 | Esta sección no es constructiva. | |
| 525 | ||
| 526 | - Turbopack no es ágil | |
| 527 | - Turbopack emite código difícil de depurar (en modo de desarrollo) | |
| 528 | - Turbopack produce mensajes de error malos en muchos casos | |
| 529 | ||
| 530 | Normalmente no le habría dado una sección en el blog a este punto, pero quiero | |
| 531 | mostrar tres ejemplos de verdad, directamente extraídos del proyecto. | |
| 532 | ||
| 533 | El primero es un lugar donde, al refactorizar código para satisfacer los modelos | |
| 534 | de componente de servidor/cliente, hice asíncrono un componente de cliente. Era | |
| 535 | irritante porque no indicaba dónde estaba el error, sólo contenía el stack trace | |
| 536 | del <b class='server'>servidor</b>. | |
| 537 | ||
| 538 |  | |
| 539 | ||
| 540 | Otro ejemplo de un error horrible: | |
| 541 | ||
| 542 |  | |
| 543 | ||
| 544 | > Tras arreglar el problema detrás de este segundo error (que ni siquiera recuerdo), | |
| 545 | > el servidor de desarrollo se quedó colgado y tuve que reiniciarlo para que se recuperara. | |
| 546 | ||
| 547 | El último caso es el millón de veces que he puesto un _breakpoint_ (punto de | |
| 548 | interrupción) en el depurador y la variable `hola` se convierte en | |
| 549 | `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hola"]` | |
| 550 | y más mierda. | |
| 551 | ||
| 552 | Vale. Todo esto da asco. ¿Qué podemos hacer? | |
| 553 | ||
| 554 | [§3]: #ditching-nextjs | |
| 555 | ||
| 556 | <Heading | |
| 557 | level='h2' | |
| 558 | slug='ditching-nextjs' | |
| 559 | >Dejando atrás Next.js y Vercel en el trabajo</Heading> | |
| 560 | ||
| 561 | Hay dos tipos de proyectos web: | |
| 562 | ||
| 563 | - Una página web con contenido mayoritariamente estático. | |
| 564 | - Una aplicación web con componentes mayormente dinámicos e interactivos. | |
| 565 | ||
| 566 | Y Next.js es la herramienta equivocada para ambos. Si quieres una página web | |
| 567 | estática, usa [Astro] o [Fresh]. Para quienes necesiten la potencia de React, | |
| 568 | esta sección trata cómo pasé de estar atado a Next a [TanStack Start], de forma | |
| 569 | incremental y sin baches. | |
| 570 | ||
| 571 | [Astro]: https://astro.build/ | |
| 572 | [Fresh]: https://fresh.deno.dev/ | |
| 573 | [TanStack Start]: https://tanstack.com/start/latest | |
| 574 | ||
| 575 | Todo empezó con esta configuración de Vite. | |
| 576 | ||
| 577 | ```ts filename="vite.config.ts" | |
| 578 | const config = defineConfig(({ mode }) => { | |
| 579 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 580 | return { | |
| 581 | // Usa el puerto predeterminado de Next.js, 3000 | |
| 582 | server: { port: 3000 }, | |
| 583 | // Usa el prefijo de variables de entorno predeterminado de Next.js, "NEXT_PUBLIC_" | |
| 584 | define: Object.fromEntries(Object.entries(env).map( | |
| 585 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 586 | plugins: [ | |
| 587 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 588 | tailwindcss(), | |
| 589 | // Para que mis compañeros de trabajo lo entendieran bien, empecé | |
| 590 | // a portar las rutas en `src/tanstack-routes`; en cuanto acabara, | |
| 591 | // volvería a cambiarlo a `src/routes`, como estaba por defecto. | |
| 592 | tanstackStart({ | |
| 593 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 594 | }), | |
| 595 | viteReact(), | |
| 596 | ], | |
| 597 | resolve: { | |
| 598 | // La clave para la migración incremental: redirigir `next` a otro lugar | |
| 599 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 600 | conditions: ["tanstack"], | |
| 601 | extensions: [ | |
| 602 | // Permitir que un archivo como `utils/session.tanstack.ts` | |
| 603 | // sobreescriba a `utils/session.ts` al ser importado. | |
| 604 | ".tanstack.tsx", ".tanstack.ts", | |
| 605 | // Extensiones de importación predeterminadas | |
| 606 | ".mjs", ".js", ".mts", ".ts", | |
| 607 | ".jsx", ".tsx", ".json", | |
| 608 | ], | |
| 609 | }, | |
| 610 | }; | |
| 611 | }); | |
| 612 | ``` | |
| 613 | ||
| 614 | Después, me puse a buscar cada uso de una API de Next.js, para o eliminarlo o | |
| 615 | crear un talón (_stub_) para TanStack. Por ejemplo, `src/tanstack-next/link.tsx` | |
| 616 | implementa `next/link`: | |
| 617 | ||
| 618 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 619 | import { Link } from "@tanstack/react-router"; | |
| 620 | import type { LinkProps } from "next/link"; | |
| 621 | ||
| 622 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 623 | return <Link {...rest} to={href as unknown as any} />; | |
| 624 | } | |
| 625 | ``` | |
| 626 | ||
| 627 | > Algunos de estos talones pueden ser extremadamente simples. Al principio, mi | |
| 628 | > implementación de `useRouter` era `return {}`. Más tarde, ya tuve que añadir | |
| 629 | > algunos métodos al objeto. El código no tiene por qué ser limpio, ya que es | |
| 630 | > temporal. | |
| 631 | ||
| 632 | De ahí, el sitio nuevo puede importar casi cada componente de cliente o creando | |
| 633 | talones para las APIs de Next.js que necesite o usando la extensión `.tanstack.ts` | |
| 634 | para reimplementar la lógica archivo por archivo. Poco después, conseguí que | |
| 635 | la página principal del sitio funcionara en TanStack Start e hicimos _merge_. | |
| 636 | ||
| 637 |  | |
| 638 | ||
| 639 | > En esta primera PR (solicitud de incorporación de cambios) sólo funcionaba una | |
| 640 | > de nuestras páginas, y conseguí que funcionara con unas mil líneas de código | |
| 641 | > añadido y 40 líneas eliminadas. Algunos parches previos eliminaban los pocos | |
| 642 | > usos de `next/image` y `next/font`. | |
| 643 | ||
| 644 | Lo que quedaba era portar el resto de rutas. Lo único que perdemos al migrar de | |
| 645 | Next.js a cualquier otro framework es el poder usar `await` con funciones de | |
| 646 | obtención de datos en la interfaz de usuario. En la práctica, mover cada ruta | |
| 647 | a una función `loader` esclareció qué era lo que pasaba al renderizar cada | |
| 648 | página desde el servidor (SSR). Para páginas con varias llamadas de obtención de | |
| 649 | datos, estas podían combinarse en una llamada API especial que devolviera todos | |
| 650 | los datos relevantes a ellas. | |
| 651 | ||
| 652 | Para reiterar, en negrita: <strong style='color:var(--secondary)'>El camino de | |
| 653 | migración de componentes de servidor es simplificar tu código &mdash; RSC | |
| 654 | intrínsecamente te lleva por un camino caótico repleto de cosas | |
| 655 | innecesarias</strong>. Casi todas las partes complejas de nuestro sitio se | |
| 656 | volvieron más fáciles de entender para todos nuestros ingenieros. La única | |
| 657 | excepción fue acostumbrar a todos a las nuevas convenciones para el enrutado | |
| 658 | del sistema de archivos. Con suficientes ejemplos, todos lo acabamos pillando. | |
| 659 | ||
| 660 | Con la migración incremental ya operativa, no se rompía el _deployment_ existente | |
| 661 | al añadir código nuevo. TanStack fue apoderándose del código y, con el tiempo, | |
| 662 | fuimos eliminando todos los talones de Next.js y ganamos todas las maravillosas | |
| 663 | [características de seguridad de tipos][type-safety features] que ofrece el | |
| 664 | enrutador de TanStack (TanStack Router). Al final, el sitio rendía más rápido en | |
| 665 | todos los aspectos: el modo de desarrollo, tiempos de carga en producción, | |
| 666 | navegaciones suaves, y todo a un menor coste que nuestro _deployment_ de Next con | |
| 667 | Vercel. | |
| 668 | ||
| 669 | [type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 670 | ||
| 671 | No somos los únicos sintiendo el cambio. Aunque intento evitar las redes | |
| 672 | sociales, alguien me envió [los resultados del trabajo de Brian Anglin en | |
| 673 | Superwall][superwall-twitter], mostrando reducciones de CPU increíbles usando | |
| 674 | TanStack Start. También recuerdo el cambio de ChatGPT de Next.js a Remix hace un | |
| 675 | año (conversaciones relevantes: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]). | |
| 676 | ||
| 677 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 678 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 679 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 680 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 681 | ||
| 682 | [§3.1]: #next-metadata | |
| 683 | ||
| 684 | <Heading | |
| 685 | level='h3' | |
| 686 | slug='next-metadata' | |
| 687 | ><code>next/metadata</code> es maravilloso</Heading> | |
| 688 | ||
| 689 | Bajo mi punto de vista, esta es una de las pocas APIs buenas que tiene Next.js, | |
| 690 | y fue el único lugar en nuestro código en el que el cambio a TanStack resultó en | |
| 691 | mayor dificultad. En vez de empeorar el código, porté su API de metadatos a una | |
| 692 | función normal y corriente, para que todos la pudieran usar. Solía tener un | |
| 693 | puerto 1:1 en NPM, pero a comienzos del año simplifiqué su API a un archivo | |
| 694 | pequeño y comprensible. En el momento de escribir esta publicación, he añadido | |
| 695 | una API `meta.toTags` compatible con TanStack que puede instalarse desde | |
| 696 | [JSR][lib-jsr] o [NPM][lib-npm], o simplemente la puedes copiar a tu proyecto. | |
| 697 | ||
| 698 | > **aviso**: Por limitaciones de tiempo para escribir este artículo, la librería | |
| 699 | > todavía no está actualizada. Probablemente lo haré ~~para el final de esta | |
| 700 | > semana (24 de octubre)~~ pronto... Por ahora, puedo compartir la versión que | |
| 701 | > uso en el trabajo: | |
| 702 | > [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts). | |
| 703 | ||
| 704 | ```tsx | |
| 705 | // una vez en tu proyecto | |
| 706 | import * as meta from "@clo/lib/meta.ts"; | |
| 707 | ||
| 708 | export const defineHead = meta.toTags.bind(null, { | |
| 709 | // opciones para todo el sitio | |
| 710 | base: new URL("https://paperclover.net"), | |
| 711 | titleTemplate: (title) => [title, "paper clover"] | |
| 712 | .filter(Boolean).join(' | '), | |
| 713 | // ... | |
| 714 | }); | |
| 715 | ||
| 716 | // para cada página... | |
| 717 | export const Route = createFileRoute("/blog")({ | |
| 718 | head: () => | |
| 719 | defineHead({ | |
| 720 | title: "blog de clover", // usando la plantilla `titleTemplate` | |
| 721 | description: "una gatita maúlla sobre sus opiniones tecnológicas", | |
| 722 | canonical: "/blog", // yuxtapuesto a `base` | |
| 723 | ||
| 724 | // Cuando se especifica, configura el embed (embebido) | |
| 725 | // de Open Graph y Twitter, usando el título y la | |
| 726 | // descripción de la página por defecto. | |
| 727 | // La configuración predeterminada está bien, | |
| 728 | // pero se pueden especificar más opciones. | |
| 729 | embed: { | |
| 730 | image: "/img/blog.webp", | |
| 731 | }, | |
| 732 | ||
| 733 | // Todas las etiquetas meta exóticas se hacen con un fragmento | |
| 734 | // JSX. No se renderiza React, sólo se itera sobre las etiquetas. | |
| 735 | // Mi objetivo era cubrir el 99% de los casos comunes. | |
| 736 | extra: <> | |
| 737 | <meta name="site-verification" content="waffles" />, | |
| 738 | </>, | |
| 739 | }), | |
| 740 | ||
| 741 | component: Page, | |
| 742 | }); | |
| 743 | ||
| 744 | function Page() { | |
| 745 | ... | |
| 746 | } | |
| 747 | ``` | |
| 748 | ||
| 749 | Mi versión no se preocupaba con cubrir todas las posibilidades del objeto de | |
| 750 | metadatos de Next.js; usa JSX en línea (_inline_) para llenar ese hueco. | |
| 751 | ||
| 752 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 753 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 754 | ||
| 755 | [§3.2]: #vercel-og | |
| 756 | ||
| 757 | <Heading | |
| 758 | level='h3' | |
| 759 | slug='ditching-nextjs' | |
| 760 | ><code>next/og</code> está bien también</Heading> | |
| 761 | ||
| 762 | No tengo una opinión fuerte al respecto. Sólo quiero recordar a todos que existe | |
| 763 | el paquete `@vercel/og`. | |
| 764 | ||
| 765 | [§4]: #experience-feels-like-the-usual | |
| 766 | ||
| 767 | <Heading | |
| 768 | level='h2' | |
| 769 | slug='experience-feels-like-the-usual' | |
| 770 | >Mi experiencia parece ser la típica</Heading> | |
| 771 | ||
| 772 | En la Next.js Conf 2024, todos hablaban maravillas de los componentes de | |
| 773 | servidor. No recuerdo exactamente con quién fue que hablé, pero todos los | |
| 774 | grandes nombres estaban completamente a favor de ellos. Habiendo implementado el | |
| 775 | empaquetador de RSC, yo vi algunos de los problemas en el formato. Ahora, viendo | |
| 776 | que Next 15 "estabilizó" el enrutador de aplicaciones el año pasado, muchas | |
| 777 | compañías están construyendo productos con él, dándose cuenta de estos | |
| 778 | inconvenientes de primera mano. | |
| 779 | ||
| 780 | Llegué tarde al mundo de Next.js, pues empecé en junio con la versión 15. Pero | |
| 781 | todos con los que he hablado están de acuerdo con mis observaciones. Todos con | |
| 782 | los que hablé en la 1.3 Party de Bun estaban de acuerdo conmigo. Incluso gente | |
| 783 | de Vercel me ha dicho que no les gusta cómo es usar Next.js. | |
| 784 | ||
| 785 | Espero que, al estabilizarse TanStack Start, se vuelva el nuevo sustituto de | |
| 786 | Next.js que todos quieran. | |
| 787 | ||
| 788 | [§5]: #prefer-respectful-tools | |
| 789 | ||
| 790 | <Heading | |
| 791 | level='h2' | |
| 792 | slug='prefer-respectful-tools' | |
| 793 | >Opta por herramientas que te respeten</Heading> | |
| 794 | ||
| 795 | El ecosistema de JavaScript es un desastre y ese desastre es por el que la gente | |
| 796 | se burla del desarrollo web. Muchas veces he pensado que el desastre de trabajar | |
| 797 | en la web no tenía solución, pero el desastre eran en verdad las librerías | |
| 798 | comúnmente usadas de las que me rodeaba. Al quitar esa capa, las tecnologías de | |
| 799 | desarrollo web modernas son maravillosas. | |
| 800 | ||
| 801 | Llevo desarrollando esta página web desde cero, sin framework, desde finales de | |
| 802 | 2024, implementando sistemas como mi propio [widget de progreso para | |
| 803 | aplicaciones de terminal][progress], [proxy para archivos | |
| 804 | estáticos][file-cache], sistema de compilación incremental, y muchos más | |
| 805 | componentes. Trabajar en este código ha llevado a las mejores sesiones de código | |
| 806 | (en términos de felicidad) en años. Los visitantes de *[paper clover]* se llevan | |
| 807 | una página web de mejor calidad; las minilibrerías que he creado las [extraigo | |
| 808 | para uso público][lib], todos salen ganando. | |
| 809 | ||
| 810 | Este nivel de "desde cero" es demasiado para la mayoría, especialmente en el | |
| 811 | trabajo. Diría que, como mínimo, deberíamos dedicar atención y dinero sólo a | |
| 812 | herramientas de alta calidad que nos respeten. Y Next.js y la empresa detrás de | |
| 813 | ello, Vercel, no caen dentro de ese criterio. | |
| 814 | ||
| 815 | Si usas Next.js y sientes que tu experiencia tampoco te recuerda al respeto, | |
| 816 | reflexiona si tus compañeros de trabajo y tú queréis seguir apoyando su [imperio | |
| 817 | serverless][serverless empire]. El ecosistema de Vite parece bastante decente | |
| 818 | para desarrollar ahora mismo, pero no tengo mucha experiencia todavía en usar | |
| 819 | sus herramientas a gran escala en producción. El [lanzamiento de Vite+ de | |
| 820 | Void0][vite-plus] parece interesante, pero sólo el tiempo dirá si estas | |
| 821 | herramientas, financiadas por capital, nos respetarán (a los usuarios y a los | |
| 822 | desarrolladores) a largo plazo. | |
| 823 | ||
| 824 | La Next.js Conf 2025, en el momento de escribir, es [mañana][next-conf]. En vez | |
| 825 | de comprarme una entrada de $800, decidí destinar ese dinero al [equipo de | |
| 826 | TanStack][tanstack-donate] por [respetar y mejorar el ecosistema de desarrollo | |
| 827 | web][tanstack-ethos]. | |
| 828 | ||
| 829 | [paper clover]: https://paperclover.net/ | |
| 830 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 831 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 832 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 833 | ||
| 834 | [next-conf]: https://nextjs.org/conf | |
| 835 | [vite-plus]: https://viteplus.dev/ | |
| 836 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 837 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 838 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 839 | ||
| 840 | ## Lo que aguarda el futuro | |
| 841 | ||
| 842 | Paulatinamente, he estado reemplazando mucho software que me falta al respeto | |
| 843 | con mejores alternativas. Algunos ejemplos son GitHub, Visual Studio Code, | |
| 844 | DaVinci Resolve, Discord, Google Drive/Workspace, entre muchos otros. Pienso | |
| 845 | escribir más en este blog sobre mis proyectos técnicos (la librería de progreso, | |
| 846 | el propósito de mi generador de sitio propio, aprendizajes de mi trabajo | |
| 847 | actual), incluyendo algunos de mis proyectos pasados en Bun (detalles sobre HMR, | |
| 848 | el sistema de reporte de errores y la locura de sistema para empaquetar módulos | |
| 849 | integrados). Si te interesa, por favor suscríbete a la lista de correos: | |
| 850 | ||
| 851 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">haz clic aquí para enviar un correo a <code>subscribe@paperclover.net</code>, pidiendo ser añadido a la lista de correos.</a> (llevo esta lista de correos a mano) | |
| 852 | ||
| 853 | [volver al inicio](#top) &mdash; [pregúntame algo sobre este artículo](/q+a) | |
| 854 | ||
| 855 | <br /> | |
| 856 | <br /> | |
| 857 | <br /> | |
| 858 | <br /> | |
| 859 | <br /> | |
| 860 | <br /> | |
| 861 | <footer> | |
| 862 | 2025 (c) paper clover | |
| 863 | </footer> | |
| 864 | ||
| 865 | </Layout> | |
| 866 | ||
| 867 | <br /> | |
| 868 |
src/blog/pages/webdev/one-year-next-app-router/ko.mdo created+541| ... | ... | @@ -0,0 +1,541 @@ |
| 1 | --- | |
| 2 | layout: "@/blog/tags/blog-layout.marko" | |
| 3 | meta: | |
| 4 | title: Next.js 앱 라우터와 함께한 1년 — 우리가 떠나기로 한 이유 | |
| 5 | description: 리액트 서버 컴포넌트와 Next.js 15에 대한 비판 | |
| 6 | keywords: ["webdev", "technical analysis", "opinion"] | |
| 7 | authors: ["clover caruso", "Chanhee Kim"] | |
| 8 | embed: | |
| 9 | thumbnail: "/open-graph/next-js.ko.png" | |
| 10 | canonical: "/blog/webdev/one-year-next-app-router.ko" | |
| 11 | date: "Oct 21st, 2025" | |
| 12 | translation: | |
| 13 | lang: "ko" | |
| 14 | author: "Chanhee Kim" | |
| 15 | href: "https:/\/substack.com/@chanheekim377573" | |
| 16 | date: "Dec 8th, 2025" | |
| 17 | --- | |
| 18 | ||
| 19 | 직장에서 웹 앱 개발에 Next.js를 전문적으로 사용해오면서, 앱 라우터와 [리액트 서버 컴포넌트(React Server Components, RSC)][rsc]의 핵심 설계가 매우 답답하게 느껴졌습니다. 사소한 버그나 API의 혼란스러움이 아니라, Vercel과 리액트 팀이 이를 구축할 때 내린 근본적인 설계 결정에 대한 큰 이견이 있기 때문입니다. | |
| 20 | ||
| 21 | 웹 개발 행사에 갈때마다 Next.js를 싫어함에도 계속 사용해야 하는 사람들을 더 많이 보게 됩니다. 이 글의 마지막에는 저와 동료들이 어떻게 이 지옥에서 탈출하여 전체 프론트엔드를 [TanStack Start]로 원활하게 마이그레이션했는지 공유하겠습니다. | |
| 22 | ||
| 23 | [TanStack Start]: https://tanstack.com/start/latest | |
| 24 | [rsc]: https://react.dev/reference/rsc/server-components | |
| 25 | ||
| 26 | <table-of-contents /> | |
| 27 | ||
| 28 | <h2#technical-review>기술 리뷰: 서버 컴포넌트란 무엇인가요?</h2> | |
| 29 | ||
| 30 | RSC의 핵심은 컴포넌트를 <b class='server'>"서버"</b> 컴포넌트와 <b class='client'>"클라이언트"</b> 컴포넌트 두 가지 범주로 분류한다는 점입니다. 서버 컴포넌트는 `useState`나 `useEffect`를 사용하지 않지만, `async function`일 수 있으며 데이터베이스에 직접 호출하는 등 백엔드 도구를 참조할 수 있습니다. 클라이언트 컴포넌트는 기존 모델로, 백엔드에서 HTML 텍스트를 생성하는 코드와 `window.document.*`를 사용하여 DOM을 관리하는 프론트엔드 코드가 존재합니다. | |
| 31 | ||
| 32 | > 첫 번째 재앙: 명명법!! 리액트는 이제 기존 정의를 무시하고 <b class='server'>"서버"</b>와 <b class='client'>"클라이언트"</b>라는 단어를 매우 특정한 개념을 가리키는 데 사용하고 있습니다. <b class='client'>클라이언트</b> 컴포넌트도 백엔드에서 실행될 수 있다는 점을 제외하면 괜찮을 텐데요! 이 글에서는 웹 앱이 존재하는 두 가지 실행 환경, 즉 Node.js 프로세스와 웹 브라우저를 각각 설명하기 위해 <b>"백엔드"</b>와 <b>"프론트엔드"</b>라는 용어를 사용할 것입니다. | |
| 33 | ||
| 34 | 이 <b class='server'>"서버"</b>/<b class='client'>"클라이언트"</b> 컴포넌트 모델은 흥미롭습니다. `<Suspense />` 같은 내장 컴포넌트가 네트워크를 통해 직렬화되기 때문에, 비동기 <b class='server'>서버 컴포넌트</b>로 데이터 가져오기를 아주 간단하게 모델링할 수 있으며, 폴백 UI는 마치 클라이언트 측에서 작동하는 것처럼 동작합니다. | |
| 35 | ||
| 36 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 37 | // 이 글에서 서버 컴포넌트는 빨간색으로 강조 표시됩니다. | |
| 38 | export default async function Page({ params }) { | |
| 39 | // Page 매개변수는 해결된 프로미스로 제공됩니다 | |
| 40 | const { username } = await params; | |
| 41 | ||
| 42 | // `UserInfo` 및 `UserPostList` 컴포넌트는 동시에 실행됩니다. | |
| 43 | // `UserInfo`가 준비되면, 방문자는 게시물 목록이 아직 준비되지 않은 경우 | |
| 44 | // `PostListSkeleton`이 포함된 페이지를 보게 됩니다. | |
| 45 | return <main> | |
| 46 | <UserInfo username={username} /> | |
| 47 | ||
| 48 | <Suspense fallback={<PostListSkeleton />}> | |
| 49 | <UserPostList username={username} /> | |
| 50 | </Suspense> | |
| 51 | </main> | |
| 52 | } | |
| 53 | ||
| 54 | // 워터폴은 여러 컴포넌트를 동시에 평가함으로써 방지됩니다. | |
| 55 | ||
| 56 | async function UserInfo({ username }) { | |
| 57 | const user = await fetchUserInfo(username); | |
| 58 | return <> | |
| 59 | <h1>{user.displayName}</h1> | |
| 60 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 61 | </> | |
| 62 | } | |
| 63 | ||
| 64 | async function UserPostList({ username }) { | |
| 65 | const posts = await fetchUserPostList(username); | |
| 66 | return /* post list ui omitted for brevity */; | |
| 67 | } | |
| 68 | ``` | |
| 69 | ||
| 70 | 리액트 자체의 40kB gzip 압축 번들을 제외하면, 위 예시는 UI와 데이터 페칭을 위한 자바스크립트가 전혀 없습니다. 단순히 마크업을 스트리밍할 뿐이죠! 예를 들어, `<Markdown />` 컴포넌트 내부의 가상 마크다운 파서는 백엔드에 그대로 남아 있습니다. 인터랙티브한 프론트엔드가 필요할 때는, "use client"로 시작하는 파일에 컴포넌트를 배치하여 <b class='client'>클라이언트 컴포넌트</b>를 만들 수 있습니다. | |
| 71 | ||
| 72 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 73 | "use client"; // 이 주석은 파일이 클라이언트 사이드로 번들링 되도록 마킹합니다. | |
| 74 | ||
| 75 | export function CopyButton({ url }) { | |
| 76 | return <> | |
| 77 | <span>{url}</span> | |
| 78 | <button onClick={() => { | |
| 79 | const full = new URL(url, location.href); | |
| 80 | navigator.clipboard.writeText(full.href); | |
| 81 | // 에러 처리나 성공시 보여주는 ui는 제외했습니다 | |
| 82 | }}>copy</button> | |
| 83 | </> | |
| 84 | } | |
| 85 | ``` | |
| 86 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 87 | export function Card() { | |
| 88 | return <article> | |
| 89 | <header> | |
| 90 | {/* 브라우저가 CopyButton을 import 하도록 합니다 */} | |
| 91 | <CopyButton url="/q+a/2506010139" /> | |
| 92 | </header> | |
| 93 | <p> | |
| 94 | {/* 마크다운 처리는 백엔드에서 수행됩니다 */} | |
| 95 | <Markdown content=".........." /> | |
| 96 | </p> | |
| 97 | </article> | |
| 98 | } | |
| 99 | ``` | |
| 100 | ||
| 101 | ||
| 102 | <h2#real-world-pitfalls>실제로 발생하는 앱 라우터의 문제점들</h2> | |
| 103 | ||
| 104 | 런타임 엔지니어로 근무하던 [Bun]을 그만둔 후([서버 컴포넌트 번들링]과 [RSC 템플릿][bun-rsc]을 구현했습니다), 저는 최전선에서 일하는 소규모 회사에 합류했습니다. Hono 백엔드를 가진 Next.js 애플리케이션이었습니다. 다음 내용들은 실제 현장에서 유지보수 및 신규 기능 개발 시 마주친 문제들을 단순화한 것입니다. 이 모든 것들의 결과로 인해, 모두가 설계상의 결함을 우회하거나, 당연히 해결되어야 할 문제가 왜 해결 불가능한 장애물이 되었는지 서로 설명하는 데 시간을 낭비하게 되었습니다. | |
| 105 | ||
| 106 | [Bun]: https://bun.com | |
| 107 | [서버 컴포넌트 번들링]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 108 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 109 | ||
| 110 | ||
| 111 | <h3#optimistic-updates>낙관적 업데이트는 불가능합니다</h3> | |
| 112 | ||
| 113 | Next.js 문서에는 변경 수행 시 [낙관적 업데이트에 대한 언급이 없습니다][nextjs-updating-data]. 이 경우를 고려하지 않은 것으로 보입니다. <b class='server'>리액트 서버</b>에서 렌더링되는 컴포넌트는 설계상 마운팅 후 수정할 수 없습니다. 변경될 수 있는 요소는 클라이언트 컴포넌트 내에 있어야 하지만, 백엔드에서 SSR(서버 측 렌더링) 중에도 클라이언트 컴포넌트에서 데이터 가져오기가 불가능합니다. 이로 인해 데이터 가져오기만 수행하는 어색하게 작은 서버 컴포넌트와, 대부분 정적인 버전의 페이지를 포함하는 클라이언트 컴포넌트가 분리되어 있는 구조가 됩니다. | |
| 114 | ||
| 115 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 116 | ||
| 117 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 118 | export default async function Page() { | |
| 119 | const user = await fetchUserInfo(username); | |
| 120 | return <ProfileLayout> | |
| 121 | <UserProfile user={user} /> | |
| 122 | </ProfileLayout>; | |
| 123 | } | |
| 124 | ``` | |
| 125 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 126 | "use client"; // 클라이언트 코드를 반드시 두 번째 파일로 분리해야 합니다! | |
| 127 | ||
| 128 | export function UserProfile({ user: initialUser }) { | |
| 129 | // 훌륭한 상태 관리 라이브러리들이 많이 존재합니다. | |
| 130 | // 단순화를 위해 이 예제에서는 하나의 상태 셀을 사용하겠습니다. | |
| 131 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 132 | ||
| 133 | async function onEdit(newUser) { | |
| 134 | optimisticUpdateUser(newUser); | |
| 135 | const resp = await fetch("...", { | |
| 136 | method: 'POST', | |
| 137 | body: JSON.stringify(newUser), | |
| 138 | ... // (헤더, 자격 증명, 추적 등) | |
| 139 | }) | |
| 140 | if (!resp.ok) /* 항상 오류 검사를 잊지 마세요! */ | |
| 141 | } | |
| 142 | ||
| 143 | return <main>{/* 편집 가능한 필드가 있는 사용자 인터페이스... */}</main>: | |
| 144 | } | |
| 145 | ``` | |
| 146 | ||
| 147 | 페이지의 상호작용 요소가 늘어날수록 정적 부분을 서버 측에서 완전히 처리하기 복잡해집니다. 업무용 앱에서는 거의 모든 UI 요소가 동적 데이터를 표시합니다. [`WebSocket`][ws]은 데이터가 업데이트될 때 실시간으로 동기화합니다(예: 사용자 카드의 온라인 상태와 기본 프로필 정보). 이러한 컴포넌트 설정은 엔지니어가 이해하고 유지하기 어렵기 때문에, 거의 모든 페이지가 데이터 가져오기를 정의하는 `page.tsx`를 통해 완전히 `"use client` 방식으로 구현됩니다. | |
| 148 | ||
| 149 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 150 | ||
| 151 | 직장에서 사용하는 데이터 페칭 라이브러리인 [TanStack Query](https://github.com/tanstack/query#readme)를 통해 실제 적용 사례를 보다 구체적으로 살펴보겠습니다. | |
| 152 | ||
| 153 | ```ts filename="src/queries/users.ts" | |
| 154 | // 작업 시 타입 안전성을 위해 `defineQuery` 헬퍼 함수가 사용됩니다. | |
| 155 | // 페처는 단순하며 백엔드나 프론트엔드에서 실행될 수 있습니다. | |
| 156 | export const queryUserInfo = (username) => ({ | |
| 157 | queryKey: ['user', username], | |
| 158 | queryFn: async ({ ... }) => /* fetch data */ | |
| 159 | }); | |
| 160 | ``` | |
| 161 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 162 | export default async function Page({ params }) { | |
| 163 | const { username } = await params; | |
| 164 | ||
| 165 | // 리액트 서버에는 글로벌 상태가 없습니다. | |
| 166 | // 레이아웃이 병렬로 실행되기 때문에 TanStack `QueryClient`는 경로 마다 여러 번 재구성되어야 합니다. | |
| 167 | const queryClient = new QueryClient(); | |
| 168 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 169 | ||
| 170 | // HydrationBoundary는 리액트 서버에서 클라이언트 컴포넌트로 | |
| 171 | // JSON 데이터를 전달하는 클라이언트 컴포넌트입니다. | |
| 172 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 173 | <ClientPage /> | |
| 174 | </HydrationBoundary>; | |
| 175 | } | |
| 176 | ``` | |
| 177 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 178 | "use client"; | |
| 179 | export function ClientPage() { | |
| 180 | const { username } = useParams(); | |
| 181 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 182 | ||
| 183 | // ... 이외의 다른 훅들 | |
| 184 | ||
| 185 | return <main> | |
| 186 | {/* ... 인터랙티브 웹 페이지 */} | |
| 187 | </main>; | |
| 188 | } | |
| 189 | ``` | |
| 190 | ||
| 191 | 이 예제는 서버 컴포넌트 번들링 규칙 때문에 반드시 세 개의 별도 파일로 구성되어야 합니다. (클라이언트 컴포넌트는 `"use client"`가 필요하며, 서버 전용 임포트 때문에 서버 컴포넌트 파일은 클라이언트에서 종종 임포트할 수 없습니다.) Pages 라우터에서는 `getStaticProps`와 `getServerSideProps`가 트리 셰이킹을 지원하기 때문에 단일 파일로 구현할 수 있었습니다. | |
| 192 | ||
| 193 | ||
| 194 | <h3#redundant-fetches>모든 탐색(navigation)은 또 다른 페치 요청입니다</h3> | |
| 195 | ||
| 196 | 앱 라우터는 모든 페이지를 서버 컴포넌트로 시작하며 이상적으로는 상호작용 영역이 작기 때문에, 새 페이지로 이동할 때는 클라이언트가 이미 보유한 데이터와 무관하게 Next.js 서버를 다시 호출해야 합니다! `loading.tsx` 파일이 있더라도, `/`를 열고 `/other`로 이동한 후 다시 `/`로 돌아오면 홈페이지를 재로딩하는 동안 로딩 상태가 표시됩니다. | |
| 197 | ||
| 198 | 이 방법이 통하는 유일한 경우는 <b>완벽히 정적인 콘텐츠</b>일 때로, 이때는 즉각적인 탐색과 사전 로딩이 훌륭하게 작동합니다. 하지만 <b>웹 애플리케이션은 정적이지 않고</b> 다량의 동적 콘텐츠를 포함합니다. 클라이언트가 페이지를 즉시 표시하는 데 필요한 모든 것을 이미 가지고 있음에도 불구하고, 로그인 상태가 홈페이지를 변경시키는 것은 정말 짜증나는 일입니다. 쿠키가 변경된 것도 아닌데 말이죠. | |
| 199 | ||
| 200 | > 참고: 빈 프로젝트에서 추가 테스트를 진행한 결과, Next 프론트엔드 코드가 실제 콘텐츠 없이 경로를 미리 가져오는 사례를 관찰했습니다. 'Hello World' 예제에서는 1.8kB 크기의 RSC 페이로드가 2개의 서로 다른 JS 청크를 4번에 걸쳐 가리켰습니다. 이는 순전히 대역폭과 아웃바운드 트래픽을 낭비하는 행위입니다. 특히 링크를 실제로 클릭할 때 이 모든 정보를 다시 가져온다는 점을 고려하면 더욱 그렇습니다. | |
| 201 | > | |
| 202 | > ```json whitespace="pre-wrap" | |
| 203 | > 1:"$Sreact.fragment" | |
| 204 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 205 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 206 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 207 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 208 | > 7:"$Sreact.suspense" | |
| 209 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 210 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 211 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 212 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 213 | > ``` | |
| 214 | > | |
| 215 | > 검토해 보니 여기에 실제로 일부 내용이 있더군요. 로딩 상태입니다. 보이시나요? | |
| 216 | > | |
| 217 | > ```json | |
| 218 | > ["$","div","l",{"children":"loading..."}] | |
| 219 | > ``` | |
| 220 | > | |
| 221 | > 이 모든 데이터가 실제 페이지 RSC에서 다시 전송되기 때문에 여전히 큰 낭비입니다. | |
| 222 | ||
| 223 | 이 문제의 해결책으로 보이는 [`staleTime`][nextjs-stale]은 실험적 기능으로 분류되어 "실제 운영 환경에서는 권장되지 않는다"고 명시되어 있습니다. 이 기능이 비기본 설정의 추가 구성 옵션으로 처리되고 있다는 사실 자체가 당혹스럽습니다. 설령 이를 사용한다고 해도, 동일한 기본 데이터를 참조하는 여러 페이지 간에 데이터를 공유하는 것은 불가능합니다. | |
| 224 | ||
| 225 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 226 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 227 | ||
| 228 | 앱 라우터로는 표현할 수 없는 로딩 상태의 한 형태는, 예를 들어 Git 프로젝트의 이슈 페이지와 같은 페이지에서 사용자 이름을 클릭해 해당 프로필 페이지로 이동하는 경우입니다. `loading.tsx`를 사용하면 전체 페이지가 스켈레톤 형태로 표시되지만, TanStack Query로 이러한 쿼리를 모델링하면 사용자 정보와 저장소를 불러오는 동안 사용자 이름과 아바타를 즉시 표시할 수 있습니다. 서버 컴포넌트는 렌더링된 컴포넌트에서만 데이터가 사용 가능하기 때문에 이 형태의 탐색을 지원하지 않습니다. 따라서 데이터를 다시 가져와야 합니다. | |
| 229 | ||
| 230 | 우리 Next.js 사이트의 서버 컴포넌트 데이터 페처에는 데이터 페치 단계를 완전히 건너뛰어 소프트 네비게이션을 더 빠르게 만들기 위한 코드 라인이 있습니다. | |
| 231 | ||
| 232 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 233 | export function serverSidePrefetchQueries(queries) { | |
| 234 | if ((await headers()).get("next-url")) { | |
| 235 | // 이건 소프트 네비게이션입니다. 더 빠르게 하려면 프리페칭을 건너뛰세요. | |
| 236 | // 클라이언트가 이미 이 데이터를 가지고 있을 수 있으며, 그렇지 않더라도 로딩 상태를 가지고 있습니다. | |
| 237 | // 이상적으로는 이 서버 요청은 없어야 합니다. 앱의 대부분이 클라이언트 컴포넌트로 작성되어 | |
| 238 | // 거의 모든 코드가 클라이언트 측에 있기 때문이죠. 솔직히 말해서 앱 라우터의 설계상의 결함이라고 할 수 있죠. | |
| 239 | return; | |
| 240 | } | |
| 241 | // ... 데이터 프리 페칭 로직 ... | |
| 242 | } | |
| 243 | ``` | |
| 244 | ||
| 245 | 또한 `loading.tsx`에는 `useQuery` 호출이 포함되어야 합니다. 이렇게 하면 빈 RSC에 대한 네트워크 요청이 발생하는 동안 실제로 필요한 경우 데이터를 가져올 수 있습니다. 실제로 `loading.tsx`의 상태는 실제 클라이언트 컴포넌트 자체일 수 있으며, 그러면 클라이언트 페이지가 표시됩니다. | |
| 246 | ||
| 247 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 248 | "use client"; | |
| 249 | export default function PageLoadingSkeleton() { | |
| 250 | return <ClientPage />; | |
| 251 | } | |
| 252 | ``` | |
| 253 | ||
| 254 | > 업무에서는 단순히 `loading.tsx` 파일에 `useQuery` 호출을 포함하고 스켈레톤만 표시하도록 만듭니다. Next.js가 실제 서버 컴포넌트를 로드할 때면 어쨌든 페이지 전체가 재마운트되기 때문입니다. 여기서는 VDOM 비교가 발생하지 않으므로, 요청 완료 후 모든 훅(useState)이 약간의 지연후에 초기화됩니다. 기존 DOM만 업데이트하고 상태를 유지하도록 Next.js에 간청하는 간단한 사례를 재현해 보려 했지만, 그렇게 되지 않았습니다. 다행히 빈 RSC 호출에 소요되는 시간은 충분히 짧습니다. | |
| 255 | ||
| 256 | ||
| 257 | <h3#layout-restrictions>레이아웃은 인위적으로 제한됩니다</h3> | |
| 258 | ||
| 259 | 레이아웃은 데이터를 가져올 수는 있지만, 요청을 어떤 방식으로든 관찰하거나 변경할 수 없습니다. 이는 Next.js가 원하는 때에 레이아웃을 가져오고 캐시할 수 있도록 하기 위함입니다. 다른 모든 프레임워크에서는 레이아웃이 단순히 일반 컴포넌트일 뿐이며 페이지 컴포넌트와 기능상 차이는 없습니다. | |
| 260 | ||
| 261 | 레이아웃을 개별적으로 로드하는 아이디어는 매력적이지만 이는 결국 모든 데이터 로드 작업이 레이아웃마다 재실행되어야 한다는 의미라서 어리석은 선택이 됩니다. `QueryClient`를 공유할 수 없습니다. 대신, 그들이 약속한 것처럼 동일한 `GET` 요청을 캐싱하려면 [몽키 패치된 `fetch` 메서드][nextjs-fetch]에 의존해야 합니다. | |
| 262 | ||
| 263 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 264 | ||
| 265 | 동료가 Next.js가 왜 특정 코드를 거부하는지 묻는다면, 기술적 복잡성을 설명하는 건 포기하고 그냥 "Next.js 기술 문제야, 곧 해결할 테니까 걱정 마"라고 말하곤 합니다. 이 규칙들은 일반 개발자들이 이해하기엔 너무 까다롭습니다. | |
| 266 | ||
| 267 | ||
| 268 | <h3#rsc-payload>여전히 모든 콘텐츠를 두 번 다운로드합니다</h3> | |
| 269 | ||
| 270 | ["아일랜드 아키텍처"][islands]와 달리 서버 컴포넌트는 `Suspense` 지원 및 클라이언트 컴포넌트 상태 보존을 위해 프론트엔드에서 여전히 하이드레이션되어야 합니다. 소프트 네비게이션 시 "RSC 페이로드"(HTML이 전혀 아님)는 `fetch`로 가져옵니다. 새로 고침 시 첫 번째 페인트에는 HTML이 필요하지만, 클라이언트 컴포넌트와 `Suspense` 관련 정보는 해당 HTML에 포함되어 있지 않습니다. 리액트의 해결책은 <b>전체 페이지 마크업의 두 번째 사본을 전송하는 것</b>입니다. Next.js 프로덕션 서버가 동적 페이지 렌더링 시 전송하는 예시는 다음과 같습니다. | |
| 271 | ||
| 272 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 273 | [first paint]: https://web.dev/articles/fcp | |
| 274 | ||
| 275 | ```html filename="GET /user/clover" | |
| 276 | <!DOCTYPE html> | |
| 277 | <html> | |
| 278 | <head> | |
| 279 | {link and meta tags} | |
| 280 | </head> | |
| 281 | <body> | |
| 282 | {server side render} | |
| 283 | <script> | |
| 284 | // 글로벌 `__next_f`를 배열로 설정하는 부트스트랩 스크립트. | |
| 285 | // 리액트가 로드되면 이 `.push` 함수가 재정의되어 | |
| 286 | // 새로운 청크를 RSC 디코더에 직접 기록합니다. | |
| 287 | // 이 스크립트에는 DOM 헬퍼도 포함되어 있습니다. | |
| 288 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 289 | </script> | |
| 290 | <script> | |
| 291 | // 애플리케이션 셸용 RSC 페이로드. | |
| 292 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 293 | </script> | |
| 294 | ||
| 295 | <!-- | |
| 296 | 닫는 태그 </body>는 아직 작성되지 않았습니다. | |
| 297 | 해결되지 않은 서스펜스 경계가 존재하기 때문입니다. | |
| 298 | 시간이 흐른 후에야 추가 데이터가 작성됩니다. | |
| 299 | --> | |
| 300 | <div class="user-post-list"> | |
| 301 | {server side render of a Suspense boundary} | |
| 302 | </div> | |
| 303 | <script> | |
| 304 | // 서스펜스 경계에 대한 RSC 페이로드 | |
| 305 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 306 | </script> | |
| 307 | ||
| 308 | <!-- HTML 및 스크립트 태그는 페이지 전체가 완료될 때까지 반복됩니다 --> | |
| 309 | </body> | |
| 310 | </html> | |
| 311 | ``` | |
| 312 | ||
| 313 | 이 솔루션은 <b>초기 HTML 페이로드의 크기를 두 배로 늘립니다</b>. 하지만 더 나쁜 점은 RSC 페이로드에 JS 문자열 리터럴로 감싸져 있는 JSON이 포함되어 있다는 것입니다. 이는 HTML보다 훨씬 비효율적인 형식입니다. 브로틀리(brotli)로 잘 압축되고 브라우저에서 빠르게 렌더링되는 것처럼 보이지만, 이는 낭비입니다. 하이드레이션 패턴을 사용하면 최소한 로컬 데이터는 상호작용 및 다른 페이지에서 재사용될 수 있습니다. | |
| 314 | ||
| 315 | 상호작용이 거의 없거나 전혀 없는 페이지에서도 비용을 지불하게 됩니다. [Next.js 문서](https://nextjs.org/docs)를 예로 들면, 홈페이지 로딩 시 약 750kB(HTML 250kB와 스크립트 태그 500kB)의 페이지가 로드되며, 콘텐츠가 두 번 포함됩니다. | |
| 316 | ||
| 317 | Mac에서는 <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>를, 다른 플랫폼에서는 <kbd>Ctrl</kbd> + <kbd>u</kbd>를 눌러 확인할 수 있습니다. 그런 다음 <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd>를 눌러 블로그의 특정 문자열(예: "풀스택 웹 애플리케이션 구축")을 찾아보세요. 두 번 등장합니다. 이는 리액트 서버 컴포넌트의 핵심 요소이므로 <b>피할 수 없습니다</b>. | |
| 318 | ||
| 319 | 이런 RSC 형식은 분명히 더 많은 낭비를 낳습니다. 하지만 정말로 `/_next/static/chunks/6192a3719cda7dcc.js`라는 문자열이 27번이나 따로따로 나타나는 이유를 캐내고 싶진 않네요. 뭐야, 너희들. 대역폭이 공짜냐??? | |
| 320 | ||
| 321 | ||
| 322 | <h3#turbopack>터보팩은 구립니다</h3> | |
| 323 | ||
| 324 | 이 섹션은 건설적이지 않습니다. | |
| 325 | ||
| 326 | - 터보팩은 빠르지 않습니다 | |
| 327 | - 터보팩은 디버거에서 디버깅하기 어려운 코드를 생성합니다(개발 모드에서) | |
| 328 | - 터보팩은 많은 경우에 불명확한 오류 메시지를 출력합니다 | |
| 329 | ||
| 330 | 평소라면 이 점을 블로그에 별도 섹션으로 다루지 않았겠지만, 프로젝트에서 직접 가져온 세 가지 실제 사례를 지적하고자 합니다. | |
| 331 | ||
| 332 | 첫 번째는 서버/클라이언트 컴포넌트 모델을 충족시키기 위한 리팩토링 과정에서 실수로 클라이언트 컴포넌트를 `async`로 만든 경우입니다. 이 문제는 문제가 발생한 위치를 전혀 알려주지 않고 <b class='server'>서버</b> 스택 트레이스만 포함하고 있어서 상당히 성가셨습니다. | |
| 333 | ||
| 334 |  | |
| 335 | ||
| 336 | 끔찍한 오류 메시지의 또 다른 사례입니다. | |
| 337 | ||
| 338 |  | |
| 339 | ||
| 340 | > 이 두 번째 오류의 근본적인 문제(기억나지 않음)를 수정한 후, 개발 서버가 멈춰서 복구하기 위해 재시작해야 했습니다. | |
| 341 | ||
| 342 | 마지막으로, 디버거 중단점을 수십 번 설정할 때마다 변수 이름 `hello`가 `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` 같은 거지같은 이름으로 바뀌는 경우입니다. | |
| 343 | ||
| 344 | 네. 이 모든게 구립니다. 어쩌면 좋을까요? | |
| 345 | ||
| 346 | ||
| 347 | <h2#ditching-nextjs>업무에서 Next.js와 Vercel을 매끄럽게 대체하기</h2> | |
| 348 | ||
| 349 | 웹 프로젝트에는 두 가지 유형이 있습니다. | |
| 350 | ||
| 351 | - 주로 정적 콘텐츠로 구성된 웹사이트 | |
| 352 | - 주로 동적이고 상호작용적인 컴포넌트를 가진 웹 애플리케이션 | |
| 353 | ||
| 354 | Next.js는 이 두 가지 작업 모두에 적합하지 않은 도구입니다. 정적 웹사이트를 만드는 첫 번째 유형에 해당한다면 [Astro]나 [Fresh]를 선택하세요. 리액트의 모든 기능을 필요로 하는 분들을 위해, 이 섹션에서는 벤더에 종속된 Next를 [TanStack Start]로 점진적으로 매끄럽게 교체한 방법을 설명합니다. | |
| 355 | ||
| 356 | [Astro]: https://astro.build/ | |
| 357 | [Fresh]: https://fresh.deno.dev/ | |
| 358 | ||
| 359 | 이 Vite 설정부터 시작했습니다. | |
| 360 | ||
| 361 | ```ts filename="vite.config.ts" | |
| 362 | const config = defineConfig(({ mode }) => { | |
| 363 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 364 | return { | |
| 365 | // Next.js 기본 포트 3000을 사용하세요 | |
| 366 | server: { port: 3000 }, | |
| 367 | // Next.js의 기본 환경 접두사 "NEXT_PUBLIC_"을 사용하십시오. | |
| 368 | define: Object.fromEntries(Object.entries(env).map( | |
| 369 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 370 | plugins: [ | |
| 371 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 372 | tailwindcss(), | |
| 373 | // 동료들이 이해하기 쉽도록 `src/tanstack-routes`에 있는 라우트를 | |
| 374 | // 포팅하기 시작했습니다. 마이그레이션이 완료되면 | |
| 375 | // 기본 `src/routes`로 되돌아갈 예정입니다. | |
| 376 | tanstackStart({ | |
| 377 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 378 | }), | |
| 379 | viteReact(), | |
| 380 | ], | |
| 381 | resolve: { | |
| 382 | // 증분 마이그레이션의 핵심: `next`를 다른 곳으로 리디렉션 | |
| 383 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 384 | conditions: ["tanstack"], | |
| 385 | extensions: [ | |
| 386 | // `utils/session.tanstack.ts`와 같은 이름의 파일이 | |
| 387 | // `utils/session.ts`를 가져올 때 덮어쓸 수 있도록 허용합니다. | |
| 388 | ".tanstack.tsx", ".tanstack.ts", | |
| 389 | // 기본 임포트 확장자 | |
| 390 | ".mjs", ".js", ".mts", ".ts", | |
| 391 | ".jsx", ".tsx", ".json", | |
| 392 | ], | |
| 393 | }, | |
| 394 | }; | |
| 395 | }); | |
| 396 | ``` | |
| 397 | ||
| 398 | 그런 다음 Next.js API의 모든 사용처를 찾아 제거하거나 TanStack용 스텁을 만들었습니다. 예를 들어, `src/tanstack-next/link.tsx`는 `next/link`를 구현합니다. | |
| 399 | ||
| 400 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 401 | import { Link } from "@tanstack/react-router"; | |
| 402 | import type { LinkProps } from "next/link"; | |
| 403 | ||
| 404 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 405 | return <Link {...rest} to={href as unknown as any} />; | |
| 406 | } | |
| 407 | ``` | |
| 408 | ||
| 409 | > 이러한 스텁 중 일부는 매우 간단할 수 있습니다. 처음 시작했을 때, 제가 구현한 `useRouter`는 단순히 `return {}`였지만, 나중에 객체에 몇 가지 메서드를 추가해야 했습니다. 여기 코드 자체는 임시적인 것이므로 깔끔할 필요가 없습니다. | |
| 410 | ||
| 411 | 이제 새 사이트는 필요한 Next.js API를 스터빙하거나 `.tanstack.ts` 확장자를 사용해 파일별로 로직을 재구현함으로써 거의 모든 클라이언트 컴포넌트를 가져올 수 있습니다. 그리고 얼마 지나지 않아 TanStack Start에서 사이트 홈페이지를 작동시키는 데 성공했고, 해당 브랜치를 병합했습니다. | |
| 412 | ||
| 413 |  | |
| 414 | ||
| 415 | > 이 첫 번째 PR은 우리 페이지 중 하나만을 지원했으며, 추가된 코드 천 줄과 삭제된 코드 40줄로 이를 수행할 수 있었습니다. 저는 이전 패치를 통해 `next/image` 및 `next/font`의 몇 가지 사용 사례를 제거한 적이 있습니다. | |
| 416 | ||
| 417 | 남은 작업은 다른 모든 경로를 포팅하는 것이었습니다. Next.js에서 다른 프레임워크로 마이그레이션할 때 잃게 되는 한 가지는 UI에서 데이터 페칭 함수를 `await`할 수 있는 기능입니다. 실제로 모든 경로를 `loader` 함수로 이동시키면 페이지가 서버 측 렌더링(SSR)될 때 어떤 일이 발생하는지 훨씬 명확해졌습니다. 여러 번의 데이터 가져오기가 필요한 페이지의 경우, 해당 페이지에 필요한 모든 관련 데이터를 반환하는 단일 특수 API 호출로 통합할 수 있었습니다. | |
| 418 | ||
| 419 | 다시 한번 강조하자면, <strong style='color:var(--secondary)'>서버 컴포넌트에서의 마이그레이션 경로는 단순히 코드를 단순화하는 것입니다. RSC는 본질적으로 필요 없는 것들로 가득한 혼란스러운 길로 이끌어갑니다.</strong> 우리 사이트의 거의 모든 복잡한 부분이 모든 엔지니어에게 이해하기 쉬워졌습니다. 유일한 예외는 모두가 새로운 파일 시스템 라우팅 규칙에 익숙해져야 했던 점입니다. 충분한 예시를 통해 우리 모두 그 요령을 터득했습니다. | |
| 420 | ||
| 421 | 증분 마이그레이션을 통해 신규 코드가 기존 배포를 방해하지 않았습니다. TanStack이 점진적으로 코드베이스를 인수하면서, 결국 모든 Next.js 스텁을 삭제하고 TanStack 라우터가 제공하는 모든 우수한 [타입 안전성 기능]을 확보했습니다. 최종적으로 사이트는 모든 측면에서 더 빠른 성능을 보였습니다: 개발 모드, 프로덕션 페이지 로딩 시간, 소프트 네비게이션, 그리고 Vercel을 사용한 Next 배포보다 저렴한 비용으로 구현되었습니다. | |
| 422 | ||
| 423 | [타입 안전성 기능]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 424 | ||
| 425 | 변화를 목격하는 건 우리만이 아닙니다. 저는 소셜 미디어를 멀리하려 노력하지만, 누군가 [브라이언 앵글린(Brian Anglin)이 Superwall에서 진행한 작업 결과][superwall-twitter]를 보내왔는데, TanStack Start에서 CPU 사용량이 엄청나게 감소한 걸 보여주더군요. 또한 1년 전 ChatGPT가 Next.js에서 Remix로 전환한 일도 기억납니다(관련 온라인 대화: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]). | |
| 426 | ||
| 427 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 428 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 429 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 430 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 431 | ||
| 432 | ||
| 433 | <h3#next-metadata><code>next/metadata</code>는 훌륭합니다</h3> | |
| 434 | ||
| 435 | 제 생각에 이건 Next.js가 가진 몇 안 되는 좋은 API 중 하나이며, TanStack으로 전환하면서 코드에서 유일하게 작업이 더 어려워진 부분이었습니다. 코드를 악화시키기보다는, 그냥 그들의 메타데이터 API를 일반 함수로 포팅해서 누구나 사용할 수 있게 했습니다. 원래는 NPM에 1:1 포팅 버전을 올렸지만, 올해 초에 API를 간결하고 이해하기 쉬운 하나의 파일로 단순화했습니다. 이 블로그 글 작성 시점 기준으로, TanStack 호환 `meta.toTags` API를 추가했으며, [JSR][lib-jsr]이나 [NPM][lib-npm]에서 설치하거나 단순히 프로젝트에 복사해서 사용할 수 있습니다. | |
| 436 | ||
| 437 | > 공지: 본 글 작성에 시간이 부족하여 라이브러리는 아직 업데이트되지 않았습니다. 아마도 ~~이번 주 말(10월 24일)~~ 가까운 시일 내에 처리할 예정입니다... 임시로 업무용으로 사용 중인 버전을 제 웹사이트에 공유합니다. [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts) | |
| 438 | ||
| 439 | ```tsx | |
| 440 | // 프로젝트에 한 번만 | |
| 441 | import * as meta from "@clo/lib/meta.ts"; | |
| 442 | ||
| 443 | export const defineHead = meta.toTags.bind(null, { | |
| 444 | // 사이트 전체 옵션 | |
| 445 | base: new URL("https://paperclover.net"), | |
| 446 | titleTemplate: (title) => [title, "paper clover"] | |
| 447 | .filter(Boolean).join(' | '), | |
| 448 | // ... | |
| 449 | }); | |
| 450 | ||
| 451 | // 각 페이지마다... | |
| 452 | export const Route = createFileRoute("/blog")({ | |
| 453 | head: () => | |
| 454 | defineHead({ | |
| 455 | title: "clover's blog", // `titleTemplate`으로 템플릿 처리됨 | |
| 456 | description: "a catgirl meows about her technology viewpoints", | |
| 457 | canonical: "/blog", // `base`와 결합됨 | |
| 458 | ||
| 459 | // 지정 시, 페이지 제목과 설명을 기본값으로 사용하여 | |
| 460 | // Open Graph 및 Twitter 임베드를 구성합니다. | |
| 461 | // 기본값도 괜찮지만, 더 많은 옵션을 지원합니다. | |
| 462 | embed: { | |
| 463 | image: "/img/blog.webp", | |
| 464 | }, | |
| 465 | ||
| 466 | // 모든 특수 메타 태그는 JSX 프래그먼트로 처리됩니다. | |
| 467 | // 이는 리액트 렌더링하지 않고 단순히 태그를 순회합니다. | |
| 468 | // 제 목표는 가장 흔한 99%의 사용 사례를 커버하는 것이었습니다. | |
| 469 | extra: <> | |
| 470 | <meta name="site-verification" content="waffles" />, | |
| 471 | </>, | |
| 472 | }), | |
| 473 | ||
| 474 | component: Page, | |
| 475 | }); | |
| 476 | ||
| 477 | function Page() { | |
| 478 | ... | |
| 479 | } | |
| 480 | ``` | |
| 481 | ||
| 482 | 제 버전은 Next.js 메타데이터 객체의 전체 영역을 커버하는 데 초점을 두지 않았으며, 대신 인라인 JSX를 사용하여 그 공백을 메웠습니다. | |
| 483 | ||
| 484 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 485 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 486 | ||
| 487 | <h3#vercel-og><code>next/og</code> 또한 좋습니다</h3> | |
| 488 | ||
| 489 | 특별한 의견은 없습니다. 그냥 `@vercel/og` 패키지가 있다는 점을 모두에게 상기시키고 싶을 뿐입니다. | |
| 490 | ||
| 491 | <h2#experience-feels-like-the-usual>제 경험은 일반적인 것 같아요</h2> | |
| 492 | ||
| 493 | Next.js Conf 2024에서 참석자 모두가 서버 컴포넌트(Server Components)에 열광했습니다. 정확히 누구와 이야기했는지는 기억나지 않지만, 주요 인사들은 모두 이 기술에 주목하고 있었습니다. RSC의 번들러 부분을 구현한 저는 이 포맷의 몇 가지 문제점을 목격했습니다. Next 15가 지난해 App Router를 "안정화"하면서 많은 기업들이 이를 기반으로 제품을 구축하고 있으며, 이러한 문제점들을 직접 경험하고 있습니다. | |
| 494 | ||
| 495 | 저는 Next.js를 늦게 접하게 되어 6월에야 버전 15로 시작했습니다. 하지만 행사에서 만난 모든 분들이 제 의견에 공감해 주셨습니다. Bun의 1.3 파티에서 이 주제로 이야기한 분들도 모두 동의하셨죠. 심지어 Vercel의 몇몇 분들조차 Next.js의 실제 사용 방식이 마음에 들지 않는다고 말씀하셨습니다. | |
| 496 | ||
| 497 | TanStack Start가 안정화되면서 모두가 원하는 Next.js 대체 솔루션이 되길 바랍니다. | |
| 498 | ||
| 499 | ||
| 500 | <h2#prefer-respectful-tools>사용자를 존중하는 도구를 선택하세요</h2> | |
| 501 | ||
| 502 | 자바스크립트 생태계는 상당 부분 엉망이고, 그 때문인지 웹 개발은 종종 조롱의 대상이 됩니다.. 저는 웹 작업을 회복 불가능한 난장판이라고 생각했던 적이 수없이 많았지만, 그 혼란은 사실 제가 둘러싸고 있던 흔히 쓰이는 라이브러리들 때문이었습니다. 그 껍질을 벗겨내면 현대 웹 개발 기술은 정말 대단합니다. | |
| 503 | ||
| 504 | 2024년 말부터 프레임워크 없이 이 웹사이트를 처음부터 직접 제작해 왔습니다. 자체 개발한 [TUI 진행률 위젯][progress], [정적 파일 프록시][file-cache], 증분 빌드 시스템 등 다양한 컴포넌트를 직접 구현하며 작업했습니다. 지난 수년간 가장 행복한 코딩 경험이었습니다. *[페이퍼 클로버]* 방문자들은 더 나은 품질의 웹사이트를 이용하게 되고, 제가 만든 미니 라이브러리는 [공개적으로 활용될 수 있게 추출됩니다][lib]. 덕분에 모두가 이득을 보게 되었죠. | |
| 505 | ||
| 506 | 이 정도로 완전히 처음부터 시작하는 방식은 대부분의 사람들에게, 특히 직장에서는 부담스럽습니다. 최소한 우리를 존중하는 고품질 도구에만 우리의 관심과 돈을 쏟아야 한다고 생각합니다. 그리고 Next.js와 그 배후 기업인 Vercel은 그렇지 않습니다. | |
| 507 | ||
| 508 | Next.js를 사용하면서 개발자로서 존중받는다는 느낌이 들지 않는다면, 과연 여러분과 동료들이 그들의 [서버리스 제국]을 계속 지지하고 싶은지 고민해 보세요. 현재 Vite 생태계는 구축하기에 꽤 괜찮아 보이지만, 아직 대규모 프로덕션 환경에서 그들의 도구를 사용해 본 경험은 거의 없습니다. [Void0의 Vite+ 출시 소식][vite-plus]은 흥미롭지만, 이러한 벤처 수준의 지원 도구가 장기적으로 우리(최종 사용자와 개발자)를 존중할지는 시간이 지나야 알 수 있을 것입니다. | |
| 509 | ||
| 510 | Next.js Conf 2025는 글을 쓰는 시점 기준으로 [내일][next-conf]입니다. 800달러짜리 티켓을 구매하는 대신, 웹 개발 생태계를 [존중하고 개선하는][tanstack-ethos] [TanStack 팀에 그 돈을 기부하기로 결정했습니다][tanstack-donate]. | |
| 511 | ||
| 512 | [페이퍼 클로버]: https://paperclover.net/ | |
| 513 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 514 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 515 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 516 | ||
| 517 | [next-conf]: https://nextjs.org/conf | |
| 518 | [vite-plus]: https://viteplus.dev/ | |
| 519 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 520 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 521 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 522 | ||
| 523 | ## 앞으로의 계획 | |
| 524 | ||
| 525 | 점진적으로, 저는 저를 존중하지 않는 많은 소프트웨어들을 더 나은 대안으로 교체해 왔습니다. 그 예로는 GitHub, Visual Studio Code, DaVinci Resolve, Discord, Google Drive/Workspace 등이 있으며, 그 외에도 많습니다. 이 블로그에서는 제가 진행 중인 기술적 작업들(프로그레스 라이브러리, 자체 사이트 생성기의 목적, 현재 직장에서의 배움)과 Bun에서의 과거 프로젝트들(HMR, 크래시 리포터, 내장 모듈 번들링을 위한 독특한 시스템에 대한 세부 사항)에 대해 더 많이 다루려 합니다. 관심이 있으시다면 이메일 리스트에 가입해 주세요. | |
| 526 | ||
| 527 | <hr /> | |
| 528 | ||
| 529 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually) | |
| 530 | ||
| 531 | [↑](#top) &mdash; [ask a question about this article (English)](/q+a) | |
| 532 | ||
| 533 | <br /> | |
| 534 | <br /> | |
| 535 | <br /> | |
| 536 | <br /> | |
| 537 | <br /> | |
| 538 | <br /> | |
| 539 | <footer> | |
| 540 | 2025 (c) paper clover | |
| 541 | </footer> |
src/blog/pages/webdev/one-year-next-app-router/ko.mdx deleted-624| ... | ... | @@ -1,624 +0,0 @@ |
| 1 | import Heading from "@/blog/tags/heading.tsx"; | |
| 2 | import TableOfContents from "@/blog/tags/table-of-contents.tsx"; | |
| 3 | import { Layout } from "@/blog/tags/layout.tsx"; | |
| 4 | export { theme } from "@/blog/tags/layout.tsx"; | |
| 5 | ||
| 6 | export const meta = { | |
| 7 | title: "Next.js 앱 라우터와 함께한 1년 — 우리가 떠나기로 한 이유", | |
| 8 | description: "리액트 서버 컴포넌트와 Next.js 15에 대한 비판", | |
| 9 | keywords: ["webdev", "technical analysis", "opinion"], | |
| 10 | authors: ["clover caruso", "Chanhee Kim"], | |
| 11 | embed: { | |
| 12 | thumbnail: "/open-graph/next-js.ko.png" | |
| 13 | }, | |
| 14 | twitter: { | |
| 15 | image: "https://paperclover.net/open-graph/next-js.ko.png" | |
| 16 | }, | |
| 17 | canonical: "/blog/webdev/one-year-next-app-router.ko" | |
| 18 | }; | |
| 19 | ||
| 20 | <Layout | |
| 21 | meta={meta} | |
| 22 | date={'Oct 21st, 2025'} | |
| 23 | slug="webdev/one-year-next-app-router" | |
| 24 | translation={{ | |
| 25 | lang: "ko", | |
| 26 | author: "Chanhee Kim", | |
| 27 | href: "https://substack.com/@chanheekim377573", | |
| 28 | date: "Dec 8th, 2025" | |
| 29 | }} | |
| 30 | > | |
| 31 | ||
| 32 | 직장에서 웹 앱 개발에 Next.js를 전문적으로 사용해오면서, 앱 라우터와 [리액트 서버 컴포넌트(React Server Components, RSC)][rsc]의 핵심 설계가 매우 답답하게 느껴졌습니다. 사소한 버그나 API의 혼란스러움이 아니라, Vercel과 리액트 팀이 이를 구축할 때 내린 근본적인 설계 결정에 대한 큰 이견이 있기 때문입니다. | |
| 33 | ||
| 34 | 웹 개발 행사에 갈때마다 Next.js를 싫어함에도 계속 사용해야 하는 사람들을 더 많이 보게 됩니다. 이 글의 마지막에는 저와 동료들이 어떻게 이 지옥에서 탈출하여 전체 프론트엔드를 [TanStack Start]로 원활하게 마이그레이션했는지 공유하겠습니다. | |
| 35 | ||
| 36 | [TanStack Start]: https://tanstack.com/start/latest | |
| 37 | [rsc]: https://react.dev/reference/rsc/server-components | |
| 38 | ||
| 39 | <TableOfContents> | |
| 40 | ||
| 41 | - [기술 리뷰: 서버 컴포넌트란 무엇인가요?][§1] | |
| 42 | - [실제로 발생하는 앱 라우터의 문제점들][§2] | |
| 43 | - [낙관적 업데이트는 불가능합니다][§2.1] | |
| 44 | - [모든 탐색은 또 다른 페치 요청입니다][§2.2] | |
| 45 | - [레이아웃은 인위적으로 제한됩니다][§2.3] | |
| 46 | - [여전히 모든 콘텐츠를 두 번 다운로드합니다][§2.4] | |
| 47 | - [터보팩은 구립니다][§2.5] | |
| 48 | - [업무에서 Next.js와 Vercel을 매끄럽게 대체하기][§3] | |
| 49 | - [`next/metadata`는 훌륭합니다][§3.1] | |
| 50 | - [`next/og` 또한 좋습니다][§3.2] | |
| 51 | - [제 경험은 일반적인 것 같아요][§4] | |
| 52 | - [사용자를 존중하는 도구를 선택하세요][§5] | |
| 53 | ||
| 54 | </TableOfContents> | |
| 55 | ||
| 56 | [§1]: #technical-review | |
| 57 | ||
| 58 | <Heading | |
| 59 | level='h2' | |
| 60 | slug='technical-review' | |
| 61 | >기술 리뷰: 서버 컴포넌트란 무엇인가요?</Heading> | |
| 62 | ||
| 63 | RSC의 핵심은 컴포넌트를 <b class='server'>"서버"</b> 컴포넌트와 <b class='client'>"클라이언트"</b> 컴포넌트 두 가지 범주로 분류한다는 점입니다. 서버 컴포넌트는 `useState`나 `useEffect`를 사용하지 않지만, `async function`일 수 있으며 데이터베이스에 직접 호출하는 등 백엔드 도구를 참조할 수 있습니다. 클라이언트 컴포넌트는 기존 모델로, 백엔드에서 HTML 텍스트를 생성하는 코드와 `window.document.*`를 사용하여 DOM을 관리하는 프론트엔드 코드가 존재합니다. | |
| 64 | ||
| 65 | > 첫 번째 재앙: 명명법!! 리액트는 이제 기존 정의를 무시하고 <b class='server'>"서버"</b>와 <b class='client'>"클라이언트"</b>라는 단어를 매우 특정한 개념을 가리키는 데 사용하고 있습니다. <b class='client'>클라이언트</b> 컴포넌트도 백엔드에서 실행될 수 있다는 점을 제외하면 괜찮을 텐데요! 이 글에서는 웹 앱이 존재하는 두 가지 실행 환경, 즉 Node.js 프로세스와 웹 브라우저를 각각 설명하기 위해 <b>"백엔드"</b>와 <b>"프론트엔드"</b>라는 용어를 사용할 것입니다. | |
| 66 | ||
| 67 | 이 <b class='server'>"서버"</b>/<b class='client'>"클라이언트"</b> 컴포넌트 모델은 흥미롭습니다. `<Suspense />` 같은 내장 컴포넌트가 네트워크를 통해 직렬화되기 때문에, 비동기 <b class='server'>서버 컴포넌트</b>로 데이터 가져오기를 아주 간단하게 모델링할 수 있으며, 폴백 UI는 마치 클라이언트 측에서 작동하는 것처럼 동작합니다. | |
| 68 | ||
| 69 | ```tsx filename="src/app/[username]/page.tsx" tint="server" | |
| 70 | // 이 글에서 서버 컴포넌트는 빨간색으로 강조 표시됩니다. | |
| 71 | export default async function Page({ params }) { | |
| 72 | // Page 매개변수는 해결된 프로미스로 제공됩니다 | |
| 73 | const { username } = await params; | |
| 74 | ||
| 75 | // `UserInfo` 및 `UserPostList` 컴포넌트는 동시에 실행됩니다. | |
| 76 | // `UserInfo`가 준비되면, 방문자는 게시물 목록이 아직 준비되지 않은 경우 | |
| 77 | // `PostListSkeleton`이 포함된 페이지를 보게 됩니다. | |
| 78 | return <main> | |
| 79 | <UserInfo username={username} /> | |
| 80 | ||
| 81 | <Suspense fallback={<PostListSkeleton />}> | |
| 82 | <UserPostList username={username} /> | |
| 83 | </Suspense> | |
| 84 | </main> | |
| 85 | } | |
| 86 | ||
| 87 | // 워터폴은 여러 컴포넌트를 동시에 평가함으로써 방지됩니다. | |
| 88 | ||
| 89 | async function UserInfo({ username }) { | |
| 90 | const user = await fetchUserInfo(username); | |
| 91 | return <> | |
| 92 | <h1>{user.displayName}</h1> | |
| 93 | {user.bio ? <Markdown content={user.bio} /> : ""} | |
| 94 | </> | |
| 95 | } | |
| 96 | ||
| 97 | async function UserPostList({ username }) { | |
| 98 | const posts = await fetchUserPostList(username); | |
| 99 | return /* post list ui omitted for brevity */; | |
| 100 | } | |
| 101 | ``` | |
| 102 | ||
| 103 | 리액트 자체의 40kB gzip 압축 번들을 제외하면, 위 예시는 UI와 데이터 페칭을 위한 자바스크립트가 전혀 없습니다. 단순히 마크업을 스트리밍할 뿐이죠! 예를 들어, `<Markdown />` 컴포넌트 내부의 가상 마크다운 파서는 백엔드에 그대로 남아 있습니다. 인터랙티브한 프론트엔드가 필요할 때는, "use client"로 시작하는 파일에 컴포넌트를 배치하여 <b class='client'>클라이언트 컴포넌트</b>를 만들 수 있습니다. | |
| 104 | ||
| 105 | ```tsx filename="src/components/CopyButton.tsx" tint="client" | |
| 106 | "use client"; // 이 주석은 파일이 클라이언트 사이드로 번들링 되도록 마킹합니다. | |
| 107 | ||
| 108 | export function CopyButton({ url }) { | |
| 109 | return <> | |
| 110 | <span>{url}</span> | |
| 111 | <button onClick={() => { | |
| 112 | const full = new URL(url, location.href); | |
| 113 | navigator.clipboard.writeText(full.href); | |
| 114 | // 에러 처리나 성공시 보여주는 ui는 제외했습니다 | |
| 115 | }}>copy</button> | |
| 116 | </> | |
| 117 | } | |
| 118 | ``` | |
| 119 | ```tsx filename="src/app/q+a/Card.tsx" tint="server" | |
| 120 | export function Card() { | |
| 121 | return <article> | |
| 122 | <header> | |
| 123 | {/* 브라우저가 CopyButton을 import 하도록 합니다 */} | |
| 124 | <CopyButton url="/q+a/2506010139" /> | |
| 125 | </header> | |
| 126 | <p> | |
| 127 | {/* 마크다운 처리는 백엔드에서 수행됩니다 */} | |
| 128 | <Markdown content=".........." /> | |
| 129 | </p> | |
| 130 | </article> | |
| 131 | } | |
| 132 | ``` | |
| 133 | ||
| 134 | [§2]: #real-world-pitfalls | |
| 135 | ||
| 136 | <Heading | |
| 137 | level='h2' | |
| 138 | slug='real-world-pitfalls' | |
| 139 | >실제로 발생하는 앱 라우터의 문제점들</Heading> | |
| 140 | ||
| 141 | 런타임 엔지니어로 근무하던 [Bun]을 그만둔 후([서버 컴포넌트 번들링]과 [RSC 템플릿][bun-rsc]을 구현했습니다), 저는 최전선에서 일하는 소규모 회사에 합류했습니다. Hono 백엔드를 가진 Next.js 애플리케이션이었습니다. 다음 내용들은 실제 현장에서 유지보수 및 신규 기능 개발 시 마주친 문제들을 단순화한 것입니다. 이 모든 것들의 결과로 인해, 모두가 설계상의 결함을 우회하거나, 당연히 해결되어야 할 문제가 왜 해결 불가능한 장애물이 되었는지 서로 설명하는 데 시간을 낭비하게 되었습니다. | |
| 142 | ||
| 143 | [Bun]: https://bun.com | |
| 144 | [서버 컴포넌트 번들링]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts | |
| 145 | [bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react | |
| 146 | ||
| 147 | [§2.1]: #optimistic-updates | |
| 148 | ||
| 149 | <Heading | |
| 150 | level='h3' | |
| 151 | slug='optimistic-updates' | |
| 152 | >낙관적 업데이트는 불가능합니다</Heading> | |
| 153 | ||
| 154 | Next.js 문서에는 변경 수행 시 [낙관적 업데이트에 대한 언급이 없습니다][nextjs-updating-data]. 이 경우를 고려하지 않은 것으로 보입니다. <b class='server'>리액트 서버</b>에서 렌더링되는 컴포넌트는 설계상 마운팅 후 수정할 수 없습니다. 변경될 수 있는 요소는 클라이언트 컴포넌트 내에 있어야 하지만, 백엔드에서 SSR(서버 측 렌더링) 중에도 클라이언트 컴포넌트에서 데이터 가져오기가 불가능합니다. 이로 인해 데이터 가져오기만 수행하는 어색하게 작은 서버 컴포넌트와, 대부분 정적인 버전의 페이지를 포함하는 클라이언트 컴포넌트가 분리되어 있는 구조가 됩니다. | |
| 155 | ||
| 156 | [nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data | |
| 157 | ||
| 158 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 159 | export default async function Page() { | |
| 160 | const user = await fetchUserInfo(username); | |
| 161 | return <ProfileLayout> | |
| 162 | <UserProfile user={user} /> | |
| 163 | </ProfileLayout>; | |
| 164 | } | |
| 165 | ``` | |
| 166 | ```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client" | |
| 167 | "use client"; // 클라이언트 코드를 반드시 두 번째 파일로 분리해야 합니다! | |
| 168 | ||
| 169 | export function UserProfile({ user: initialUser }) { | |
| 170 | // 훌륭한 상태 관리 라이브러리들이 많이 존재합니다. | |
| 171 | // 단순화를 위해 이 예제에서는 하나의 상태 셀을 사용하겠습니다. | |
| 172 | const [user, optimisticUpdateUser] = useState(initialUser); | |
| 173 | ||
| 174 | async function onEdit(newUser) { | |
| 175 | optimisticUpdateUser(newUser); | |
| 176 | const resp = await fetch("...", { | |
| 177 | method: 'POST', | |
| 178 | body: JSON.stringify(newUser), | |
| 179 | ... // (헤더, 자격 증명, 추적 등) | |
| 180 | }) | |
| 181 | if (!resp.ok) /* 항상 오류 검사를 잊지 마세요! */ | |
| 182 | } | |
| 183 | ||
| 184 | return <main>{/* 편집 가능한 필드가 있는 사용자 인터페이스... */}</main>: | |
| 185 | } | |
| 186 | ``` | |
| 187 | ||
| 188 | 페이지의 상호작용 요소가 늘어날수록 정적 부분을 서버 측에서 완전히 처리하기 복잡해집니다. 업무용 앱에서는 거의 모든 UI 요소가 동적 데이터를 표시합니다. [`WebSocket`][ws]은 데이터가 업데이트될 때 실시간으로 동기화합니다(예: 사용자 카드의 온라인 상태와 기본 프로필 정보). 이러한 컴포넌트 설정은 엔지니어가 이해하고 유지하기 어렵기 때문에, 거의 모든 페이지가 데이터 가져오기를 정의하는 `page.tsx`를 통해 완전히 `"use client` 방식으로 구현됩니다. | |
| 189 | ||
| 190 | [ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API | |
| 191 | ||
| 192 | 직장에서 사용하는 데이터 페칭 라이브러리인 [TanStack Query](https://github.com/tanstack/query#readme)를 통해 실제 적용 사례를 보다 구체적으로 살펴보겠습니다. | |
| 193 | ||
| 194 | ```ts filename="src/queries/users.ts" | |
| 195 | // 작업 시 타입 안전성을 위해 `defineQuery` 헬퍼 함수가 사용됩니다. | |
| 196 | // 페처는 단순하며 백엔드나 프론트엔드에서 실행될 수 있습니다. | |
| 197 | export const queryUserInfo = (username) => ({ | |
| 198 | queryKey: ['user', username], | |
| 199 | queryFn: async ({ ... }) => /* fetch data */ | |
| 200 | }); | |
| 201 | ``` | |
| 202 | ```tsx filename="src/app/user/[username]/page.tsx" tint="server" | |
| 203 | export default async function Page({ params }) { | |
| 204 | const { username } = await params; | |
| 205 | ||
| 206 | // 리액트 서버에는 글로벌 상태가 없습니다. | |
| 207 | // 레이아웃이 병렬로 실행되기 때문에 TanStack `QueryClient`는 경로 마다 여러 번 재구성되어야 합니다. | |
| 208 | const queryClient = new QueryClient(); | |
| 209 | await queryClient.ensureQueryData(queryUserInfo(username)); | |
| 210 | ||
| 211 | // HydrationBoundary는 리액트 서버에서 클라이언트 컴포넌트로 | |
| 212 | // JSON 데이터를 전달하는 클라이언트 컴포넌트입니다. | |
| 213 | return <HydrationBoundary state={dehydrate(queryClient)}> | |
| 214 | <ClientPage /> | |
| 215 | </HydrationBoundary>; | |
| 216 | } | |
| 217 | ``` | |
| 218 | ```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client" | |
| 219 | "use client"; | |
| 220 | export function ClientPage() { | |
| 221 | const { username } = useParams(); | |
| 222 | const { data: user } = useSuspenseQuery(queryUserInfo(username)); | |
| 223 | ||
| 224 | // ... 이외의 다른 훅들 | |
| 225 | ||
| 226 | return <main> | |
| 227 | {/* ... 인터랙티브 웹 페이지 */} | |
| 228 | </main>; | |
| 229 | } | |
| 230 | ``` | |
| 231 | ||
| 232 | 이 예제는 서버 컴포넌트 번들링 규칙 때문에 반드시 세 개의 별도 파일로 구성되어야 합니다. (클라이언트 컴포넌트는 `"use client"`가 필요하며, 서버 전용 임포트 때문에 서버 컴포넌트 파일은 클라이언트에서 종종 임포트할 수 없습니다.) Pages 라우터에서는 `getStaticProps`와 `getServerSideProps`가 트리 셰이킹을 지원하기 때문에 단일 파일로 구현할 수 있었습니다. | |
| 233 | ||
| 234 | [§2.2]: #redundant-fetches | |
| 235 | ||
| 236 | <Heading | |
| 237 | level='h3' | |
| 238 | slug='redundant-fetches' | |
| 239 | >모든 탐색(navigation)은 또 다른 페치 요청입니다</Heading> | |
| 240 | ||
| 241 | 앱 라우터는 모든 페이지를 서버 컴포넌트로 시작하며 이상적으로는 상호작용 영역이 작기 때문에, 새 페이지로 이동할 때는 클라이언트가 이미 보유한 데이터와 무관하게 Next.js 서버를 다시 호출해야 합니다! `loading.tsx` 파일이 있더라도, `/`를 열고 `/other`로 이동한 후 다시 `/`로 돌아오면 홈페이지를 재로딩하는 동안 로딩 상태가 표시됩니다. | |
| 242 | ||
| 243 | 이 방법이 통하는 유일한 경우는 <b>완벽히 정적인 콘텐츠</b>일 때로, 이때는 즉각적인 탐색과 사전 로딩이 훌륭하게 작동합니다. 하지만 <b>웹 애플리케이션은 정적이지 않고</b> 다량의 동적 콘텐츠를 포함합니다. 클라이언트가 페이지를 즉시 표시하는 데 필요한 모든 것을 이미 가지고 있음에도 불구하고, 로그인 상태가 홈페이지를 변경시키는 것은 정말 짜증나는 일입니다. 쿠키가 변경된 것도 아닌데 말이죠. | |
| 244 | ||
| 245 | > 참고: 빈 프로젝트에서 추가 테스트를 진행한 결과, Next 프론트엔드 코드가 실제 콘텐츠 없이 경로를 미리 가져오는 사례를 관찰했습니다. 'Hello World' 예제에서는 1.8kB 크기의 RSC 페이로드가 2개의 서로 다른 JS 청크를 4번에 걸쳐 가리켰습니다. 이는 순전히 대역폭과 아웃바운드 트래픽을 낭비하는 행위입니다. 특히 링크를 실제로 클릭할 때 이 모든 정보를 다시 가져온다는 점을 고려하면 더욱 그렇습니다. | |
| 246 | > | |
| 247 | > ```json whitespace="pre-wrap" | |
| 248 | > 1:"$Sreact.fragment" | |
| 249 | > 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 250 | > 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"] | |
| 251 | > 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"] | |
| 252 | > 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"] | |
| 253 | > 7:"$Sreact.suspense" | |
| 254 | > 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false} | |
| 255 | > 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] | |
| 256 | > 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"] | |
| 257 | > 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]] | |
| 258 | > ``` | |
| 259 | > | |
| 260 | > 검토해 보니 여기에 실제로 일부 내용이 있더군요. 로딩 상태입니다. 보이시나요? | |
| 261 | > | |
| 262 | > ```json | |
| 263 | > ["$","div","l",{"children":"loading..."}] | |
| 264 | > ``` | |
| 265 | > | |
| 266 | > 이 모든 데이터가 실제 페이지 RSC에서 다시 전송되기 때문에 여전히 큰 낭비입니다. | |
| 267 | ||
| 268 | 이 문제의 해결책으로 보이는 [`staleTime`][nextjs-stale]은 실험적 기능으로 분류되어 "실제 운영 환경에서는 권장되지 않는다"고 명시되어 있습니다. 이 기능이 비기본 설정의 추가 구성 옵션으로 처리되고 있다는 사실 자체가 당혹스럽습니다. 설령 이를 사용한다고 해도, 동일한 기본 데이터를 참조하는 여러 페이지 간에 데이터를 공유하는 것은 불가능합니다. | |
| 269 | ||
| 270 | [nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes | |
| 271 | [nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661 | |
| 272 | ||
| 273 | 앱 라우터로는 표현할 수 없는 로딩 상태의 한 형태는, 예를 들어 Git 프로젝트의 이슈 페이지와 같은 페이지에서 사용자 이름을 클릭해 해당 프로필 페이지로 이동하는 경우입니다. `loading.tsx`를 사용하면 전체 페이지가 스켈레톤 형태로 표시되지만, TanStack Query로 이러한 쿼리를 모델링하면 사용자 정보와 저장소를 불러오는 동안 사용자 이름과 아바타를 즉시 표시할 수 있습니다. 서버 컴포넌트는 렌더링된 컴포넌트에서만 데이터가 사용 가능하기 때문에 이 형태의 탐색을 지원하지 않습니다. 따라서 데이터를 다시 가져와야 합니다. | |
| 274 | ||
| 275 | 우리 Next.js 사이트의 서버 컴포넌트 데이터 페처에는 데이터 페치 단계를 완전히 건너뛰어 소프트 네비게이션을 더 빠르게 만들기 위한 코드 라인이 있습니다. | |
| 276 | ||
| 277 | ```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server" | |
| 278 | export function serverSidePrefetchQueries(queries) { | |
| 279 | if ((await headers()).get("next-url")) { | |
| 280 | // 이건 소프트 네비게이션입니다. 더 빠르게 하려면 프리페칭을 건너뛰세요. | |
| 281 | // 클라이언트가 이미 이 데이터를 가지고 있을 수 있으며, 그렇지 않더라도 로딩 상태를 가지고 있습니다. | |
| 282 | // 이상적으로는 이 서버 요청은 없어야 합니다. 앱의 대부분이 클라이언트 컴포넌트로 작성되어 | |
| 283 | // 거의 모든 코드가 클라이언트 측에 있기 때문이죠. 솔직히 말해서 앱 라우터의 설계상의 결함이라고 할 수 있죠. | |
| 284 | return; | |
| 285 | } | |
| 286 | // ... 데이터 프리 페칭 로직 ... | |
| 287 | } | |
| 288 | ``` | |
| 289 | ||
| 290 | 또한 `loading.tsx`에는 `useQuery` 호출이 포함되어야 합니다. 이렇게 하면 빈 RSC에 대한 네트워크 요청이 발생하는 동안 실제로 필요한 경우 데이터를 가져올 수 있습니다. 실제로 `loading.tsx`의 상태는 실제 클라이언트 컴포넌트 자체일 수 있으며, 그러면 클라이언트 페이지가 표시됩니다. | |
| 291 | ||
| 292 | ```tsx filename="src/app/user/[username]/loading.tsx" tint="client" | |
| 293 | "use client"; | |
| 294 | export default function PageLoadingSkeleton() { | |
| 295 | return <ClientPage />; | |
| 296 | } | |
| 297 | ``` | |
| 298 | ||
| 299 | > 업무에서는 단순히 `loading.tsx` 파일에 `useQuery` 호출을 포함하고 스켈레톤만 표시하도록 만듭니다. Next.js가 실제 서버 컴포넌트를 로드할 때면 어쨌든 페이지 전체가 재마운트되기 때문입니다. 여기서는 VDOM 비교가 발생하지 않으므로, 요청 완료 후 모든 훅(useState)이 약간의 지연후에 초기화됩니다. 기존 DOM만 업데이트하고 상태를 유지하도록 Next.js에 간청하는 간단한 사례를 재현해 보려 했지만, 그렇게 되지 않았습니다. 다행히 빈 RSC 호출에 소요되는 시간은 충분히 짧습니다. | |
| 300 | ||
| 301 | [§2.3]: #layout-restrictions | |
| 302 | ||
| 303 | <Heading | |
| 304 | level='h3' | |
| 305 | slug='layout-restrictions' | |
| 306 | >레이아웃은 인위적으로 제한됩니다</Heading> | |
| 307 | ||
| 308 | 레이아웃은 데이터를 가져올 수는 있지만, 요청을 어떤 방식으로든 관찰하거나 변경할 수 없습니다. 이는 Next.js가 원하는 때에 레이아웃을 가져오고 캐시할 수 있도록 하기 위함입니다. 다른 모든 프레임워크에서는 레이아웃이 단순히 일반 컴포넌트일 뿐이며 페이지 컴포넌트와 기능상 차이는 없습니다. | |
| 309 | ||
| 310 | 레이아웃을 개별적으로 로드하는 아이디어는 매력적이지만 이는 결국 모든 데이터 로드 작업이 레이아웃마다 재실행되어야 한다는 의미라서 어리석은 선택이 됩니다. `QueryClient`를 공유할 수 없습니다. 대신, 그들이 약속한 것처럼 동일한 `GET` 요청을 캐싱하려면 [몽키 패치된 `fetch` 메서드][nextjs-fetch]에 의존해야 합니다. | |
| 311 | ||
| 312 | [nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch | |
| 313 | ||
| 314 | 동료가 Next.js가 왜 특정 코드를 거부하는지 묻는다면, 기술적 복잡성을 설명하는 건 포기하고 그냥 "Next.js 기술 문제야, 곧 해결할 테니까 걱정 마"라고 말하곤 합니다. 이 규칙들은 일반 개발자들이 이해하기엔 너무 까다롭습니다. | |
| 315 | ||
| 316 | [§2.4]: #rsc-payload | |
| 317 | ||
| 318 | <Heading | |
| 319 | level='h3' | |
| 320 | slug='rsc-payload' | |
| 321 | >여전히 모든 콘텐츠를 두 번 다운로드합니다</Heading> | |
| 322 | ||
| 323 | ["아일랜드 아키텍처"][islands]와 달리 서버 컴포넌트는 `Suspense` 지원 및 클라이언트 컴포넌트 상태 보존을 위해 프론트엔드에서 여전히 하이드레이션되어야 합니다. 소프트 네비게이션 시 "RSC 페이로드"(HTML이 전혀 아님)는 `fetch`로 가져옵니다. 새로 고침 시 첫 번째 페인트에는 HTML이 필요하지만, 클라이언트 컴포넌트와 `Suspense` 관련 정보는 해당 HTML에 포함되어 있지 않습니다. 리액트의 해결책은 <b>전체 페이지 마크업의 두 번째 사본을 전송하는 것</b>입니다. Next.js 프로덕션 서버가 동적 페이지 렌더링 시 전송하는 예시는 다음과 같습니다. | |
| 324 | ||
| 325 | [islands]: https://www.patterns.dev/vanilla/islands-architecture/ | |
| 326 | [first paint]: https://web.dev/articles/fcp | |
| 327 | ||
| 328 | ```html filename="GET /user/clover" | |
| 329 | <!DOCTYPE html> | |
| 330 | <html> | |
| 331 | <head> | |
| 332 | {link and meta tags} | |
| 333 | </head> | |
| 334 | <body> | |
| 335 | {server side render} | |
| 336 | <script> | |
| 337 | // 글로벌 `__next_f`를 배열로 설정하는 부트스트랩 스크립트. | |
| 338 | // 리액트가 로드되면 이 `.push` 함수가 재정의되어 | |
| 339 | // 새로운 청크를 RSC 디코더에 직접 기록합니다. | |
| 340 | // 이 스크립트에는 DOM 헬퍼도 포함되어 있습니다. | |
| 341 | (self.__next_f=self.__next_f||[]).push([0]) | |
| 342 | </script> | |
| 343 | <script> | |
| 344 | // 애플리케이션 셸용 RSC 페이로드. | |
| 345 | self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"]) | |
| 346 | </script> | |
| 347 | ||
| 348 | <!-- | |
| 349 | 닫는 태그 </body>는 아직 작성되지 않았습니다. | |
| 350 | 해결되지 않은 서스펜스 경계가 존재하기 때문입니다. | |
| 351 | 시간이 흐른 후에야 추가 데이터가 작성됩니다. | |
| 352 | --> | |
| 353 | <div class="user-post-list"> | |
| 354 | {server side render of a Suspense boundary} | |
| 355 | </div> | |
| 356 | <script> | |
| 357 | // 서스펜스 경계에 대한 RSC 페이로드 | |
| 358 | self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"]) | |
| 359 | </script> | |
| 360 | ||
| 361 | <!-- HTML 및 스크립트 태그는 페이지 전체가 완료될 때까지 반복됩니다 --> | |
| 362 | </body> | |
| 363 | </html> | |
| 364 | ``` | |
| 365 | ||
| 366 | 이 솔루션은 <b>초기 HTML 페이로드의 크기를 두 배로 늘립니다</b>. 하지만 더 나쁜 점은 RSC 페이로드에 JS 문자열 리터럴로 감싸져 있는 JSON이 포함되어 있다는 것입니다. 이는 HTML보다 훨씬 비효율적인 형식입니다. 브로틀리(brotli)로 잘 압축되고 브라우저에서 빠르게 렌더링되는 것처럼 보이지만, 이는 낭비입니다. 하이드레이션 패턴을 사용하면 최소한 로컬 데이터는 상호작용 및 다른 페이지에서 재사용될 수 있습니다. | |
| 367 | ||
| 368 | 상호작용이 거의 없거나 전혀 없는 페이지에서도 비용을 지불하게 됩니다. [Next.js 문서](https://nextjs.org/docs)를 예로 들면, 홈페이지 로딩 시 약 750kB(HTML 250kB와 스크립트 태그 500kB)의 페이지가 로드되며, 콘텐츠가 두 번 포함됩니다. | |
| 369 | ||
| 370 | Mac에서는 <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>를, 다른 플랫폼에서는 <kbd>Ctrl</kbd> + <kbd>u</kbd>를 눌러 확인할 수 있습니다. 그런 다음 <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd>를 눌러 블로그의 특정 문자열(예: "풀스택 웹 애플리케이션 구축")을 찾아보세요. 두 번 등장합니다. 이는 리액트 서버 컴포넌트의 핵심 요소이므로 <b>피할 수 없습니다</b>. | |
| 371 | ||
| 372 | 이런 RSC 형식은 분명히 더 많은 낭비를 낳습니다. 하지만 정말로 `/_next/static/chunks/6192a3719cda7dcc.js`라는 문자열이 27번이나 따로따로 나타나는 이유를 캐내고 싶진 않네요. 뭐야, 너희들. 대역폭이 공짜냐??? | |
| 373 | ||
| 374 | [§2.5]: #turbopack | |
| 375 | ||
| 376 | <Heading | |
| 377 | level='h3' | |
| 378 | slug='turbopack' | |
| 379 | >터보팩은 구립니다</Heading> | |
| 380 | ||
| 381 | 이 섹션은 건설적이지 않습니다. | |
| 382 | ||
| 383 | - 터보팩은 빠르지 않습니다 | |
| 384 | - 터보팩은 디버거에서 디버깅하기 어려운 코드를 생성합니다(개발 모드에서) | |
| 385 | - 터보팩은 많은 경우에 불명확한 오류 메시지를 출력합니다 | |
| 386 | ||
| 387 | 평소라면 이 점을 블로그에 별도 섹션으로 다루지 않았겠지만, 프로젝트에서 직접 가져온 세 가지 실제 사례를 지적하고자 합니다. | |
| 388 | ||
| 389 | 첫 번째는 서버/클라이언트 컴포넌트 모델을 충족시키기 위한 리팩토링 과정에서 실수로 클라이언트 컴포넌트를 `async`로 만든 경우입니다. 이 문제는 문제가 발생한 위치를 전혀 알려주지 않고 <b class='server'>서버</b> 스택 트레이스만 포함하고 있어서 상당히 성가셨습니다. | |
| 390 | ||
| 391 |  | |
| 392 | ||
| 393 | 끔찍한 오류 메시지의 또 다른 사례입니다. | |
| 394 | ||
| 395 |  | |
| 396 | ||
| 397 | > 이 두 번째 오류의 근본적인 문제(기억나지 않음)를 수정한 후, 개발 서버가 멈춰서 복구하기 위해 재시작해야 했습니다. | |
| 398 | ||
| 399 | 마지막으로, 디버거 중단점을 수십 번 설정할 때마다 변수 이름 `hello`가 `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` 같은 거지같은 이름으로 바뀌는 경우입니다. | |
| 400 | ||
| 401 | 네. 이 모든게 구립니다. 어쩌면 좋을까요? | |
| 402 | ||
| 403 | [§3]: #ditching-nextjs | |
| 404 | ||
| 405 | <Heading | |
| 406 | level='h2' | |
| 407 | slug='ditching-nextjs' | |
| 408 | >업무에서 Next.js와 Vercel을 매끄럽게 대체하기</Heading> | |
| 409 | ||
| 410 | 웹 프로젝트에는 두 가지 유형이 있습니다. | |
| 411 | ||
| 412 | - 주로 정적 콘텐츠로 구성된 웹사이트 | |
| 413 | - 주로 동적이고 상호작용적인 컴포넌트를 가진 웹 애플리케이션 | |
| 414 | ||
| 415 | Next.js는 이 두 가지 작업 모두에 적합하지 않은 도구입니다. 정적 웹사이트를 만드는 첫 번째 유형에 해당한다면 [Astro]나 [Fresh]를 선택하세요. 리액트의 모든 기능을 필요로 하는 분들을 위해, 이 섹션에서는 벤더에 종속된 Next를 [TanStack Start]로 점진적으로 매끄럽게 교체한 방법을 설명합니다. | |
| 416 | ||
| 417 | [Astro]: https://astro.build/ | |
| 418 | [Fresh]: https://fresh.deno.dev/ | |
| 419 | ||
| 420 | 이 Vite 설정부터 시작했습니다. | |
| 421 | ||
| 422 | ```ts filename="vite.config.ts" | |
| 423 | const config = defineConfig(({ mode }) => { | |
| 424 | const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"); | |
| 425 | return { | |
| 426 | // Next.js 기본 포트 3000을 사용하세요 | |
| 427 | server: { port: 3000 }, | |
| 428 | // Next.js의 기본 환경 접두사 "NEXT_PUBLIC_"을 사용하십시오. | |
| 429 | define: Object.fromEntries(Object.entries(env).map( | |
| 430 | ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])), | |
| 431 | plugins: [ | |
| 432 | viteTsConfigPaths({ projects: ["./tsconfig.json"] }), | |
| 433 | tailwindcss(), | |
| 434 | // 동료들이 이해하기 쉽도록 `src/tanstack-routes`에 있는 라우트를 | |
| 435 | // 포팅하기 시작했습니다. 마이그레이션이 완료되면 | |
| 436 | // 기본 `src/routes`로 되돌아갈 예정입니다. | |
| 437 | tanstackStart({ | |
| 438 | router: { routesDirectory: "src/tanstack-routes" }, | |
| 439 | }), | |
| 440 | viteReact(), | |
| 441 | ], | |
| 442 | resolve: { | |
| 443 | // 증분 마이그레이션의 핵심: `next`를 다른 곳으로 리디렉션 | |
| 444 | alias: { next: path.resolve("./src/tanstack-next/") }, | |
| 445 | conditions: ["tanstack"], | |
| 446 | extensions: [ | |
| 447 | // `utils/session.tanstack.ts`와 같은 이름의 파일이 | |
| 448 | // `utils/session.ts`를 가져올 때 덮어쓸 수 있도록 허용합니다. | |
| 449 | ".tanstack.tsx", ".tanstack.ts", | |
| 450 | // 기본 임포트 확장자 | |
| 451 | ".mjs", ".js", ".mts", ".ts", | |
| 452 | ".jsx", ".tsx", ".json", | |
| 453 | ], | |
| 454 | }, | |
| 455 | }; | |
| 456 | }); | |
| 457 | ``` | |
| 458 | ||
| 459 | 그런 다음 Next.js API의 모든 사용처를 찾아 제거하거나 TanStack용 스텁을 만들었습니다. 예를 들어, `src/tanstack-next/link.tsx`는 `next/link`를 구현합니다. | |
| 460 | ||
| 461 | ```tsx filename="src/tanstack-next/link.tsx" | |
| 462 | import { Link } from "@tanstack/react-router"; | |
| 463 | import type { LinkProps } from "next/link"; | |
| 464 | ||
| 465 | export default function LinkAdapter({ href, ...rest }: LinkProps) { | |
| 466 | return <Link {...rest} to={href as unknown as any} />; | |
| 467 | } | |
| 468 | ``` | |
| 469 | ||
| 470 | > 이러한 스텁 중 일부는 매우 간단할 수 있습니다. 처음 시작했을 때, 제가 구현한 `useRouter`는 단순히 `return {}`였지만, 나중에 객체에 몇 가지 메서드를 추가해야 했습니다. 여기 코드 자체는 임시적인 것이므로 깔끔할 필요가 없습니다. | |
| 471 | ||
| 472 | 이제 새 사이트는 필요한 Next.js API를 스터빙하거나 `.tanstack.ts` 확장자를 사용해 파일별로 로직을 재구현함으로써 거의 모든 클라이언트 컴포넌트를 가져올 수 있습니다. 그리고 얼마 지나지 않아 TanStack Start에서 사이트 홈페이지를 작동시키는 데 성공했고, 해당 브랜치를 병합했습니다. | |
| 473 | ||
| 474 |  | |
| 475 | ||
| 476 | > 이 첫 번째 PR은 우리 페이지 중 하나만을 지원했으며, 추가된 코드 천 줄과 삭제된 코드 40줄로 이를 수행할 수 있었습니다. 저는 이전 패치를 통해 `next/image` 및 `next/font`의 몇 가지 사용 사례를 제거한 적이 있습니다. | |
| 477 | ||
| 478 | 남은 작업은 다른 모든 경로를 포팅하는 것이었습니다. Next.js에서 다른 프레임워크로 마이그레이션할 때 잃게 되는 한 가지는 UI에서 데이터 페칭 함수를 `await`할 수 있는 기능입니다. 실제로 모든 경로를 `loader` 함수로 이동시키면 페이지가 서버 측 렌더링(SSR)될 때 어떤 일이 발생하는지 훨씬 명확해졌습니다. 여러 번의 데이터 가져오기가 필요한 페이지의 경우, 해당 페이지에 필요한 모든 관련 데이터를 반환하는 단일 특수 API 호출로 통합할 수 있었습니다. | |
| 479 | ||
| 480 | 다시 한번 강조하자면, <strong style='color:var(--secondary)'>서버 컴포넌트에서의 마이그레이션 경로는 단순히 코드를 단순화하는 것입니다. RSC는 본질적으로 필요 없는 것들로 가득한 혼란스러운 길로 이끌어갑니다.</strong> 우리 사이트의 거의 모든 복잡한 부분이 모든 엔지니어에게 이해하기 쉬워졌습니다. 유일한 예외는 모두가 새로운 파일 시스템 라우팅 규칙에 익숙해져야 했던 점입니다. 충분한 예시를 통해 우리 모두 그 요령을 터득했습니다. | |
| 481 | ||
| 482 | 증분 마이그레이션을 통해 신규 코드가 기존 배포를 방해하지 않았습니다. TanStack이 점진적으로 코드베이스를 인수하면서, 결국 모든 Next.js 스텁을 삭제하고 TanStack 라우터가 제공하는 모든 우수한 [타입 안전성 기능]을 확보했습니다. 최종적으로 사이트는 모든 측면에서 더 빠른 성능을 보였습니다: 개발 모드, 프로덕션 페이지 로딩 시간, 소프트 네비게이션, 그리고 Vercel을 사용한 Next 배포보다 저렴한 비용으로 구현되었습니다. | |
| 483 | ||
| 484 | [타입 안전성 기능]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety | |
| 485 | ||
| 486 | 변화를 목격하는 건 우리만이 아닙니다. 저는 소셜 미디어를 멀리하려 노력하지만, 누군가 [브라이언 앵글린(Brian Anglin)이 Superwall에서 진행한 작업 결과][superwall-twitter]를 보내왔는데, TanStack Start에서 CPU 사용량이 엄청나게 감소한 걸 보여주더군요. 또한 1년 전 ChatGPT가 Next.js에서 Remix로 전환한 일도 기억납니다(관련 온라인 대화: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]). | |
| 487 | ||
| 488 | [superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m | |
| 489 | [chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233 | |
| 490 | [chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix | |
| 491 | [chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix | |
| 492 | ||
| 493 | [§3.1]: #next-metadata | |
| 494 | ||
| 495 | <Heading | |
| 496 | level='h3' | |
| 497 | slug='next-metadata' | |
| 498 | ><code>next/metadata</code>는 훌륭합니다</Heading> | |
| 499 | ||
| 500 | 제 생각에 이건 Next.js가 가진 몇 안 되는 좋은 API 중 하나이며, TanStack으로 전환하면서 코드에서 유일하게 작업이 더 어려워진 부분이었습니다. 코드를 악화시키기보다는, 그냥 그들의 메타데이터 API를 일반 함수로 포팅해서 누구나 사용할 수 있게 했습니다. 원래는 NPM에 1:1 포팅 버전을 올렸지만, 올해 초에 API를 간결하고 이해하기 쉬운 하나의 파일로 단순화했습니다. 이 블로그 글 작성 시점 기준으로, TanStack 호환 `meta.toTags` API를 추가했으며, [JSR][lib-jsr]이나 [NPM][lib-npm]에서 설치하거나 단순히 프로젝트에 복사해서 사용할 수 있습니다. | |
| 501 | ||
| 502 | > 공지: 본 글 작성에 시간이 부족하여 라이브러리는 아직 업데이트되지 않았습니다. 아마도 ~~이번 주 말(10월 24일)~~ 가까운 시일 내에 처리할 예정입니다... 임시로 업무용으로 사용 중인 버전을 제 웹사이트에 공유합니다. [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts) | |
| 503 | ||
| 504 | ```tsx | |
| 505 | // 프로젝트에 한 번만 | |
| 506 | import * as meta from "@clo/lib/meta.ts"; | |
| 507 | ||
| 508 | export const defineHead = meta.toTags.bind(null, { | |
| 509 | // 사이트 전체 옵션 | |
| 510 | base: new URL("https://paperclover.net"), | |
| 511 | titleTemplate: (title) => [title, "paper clover"] | |
| 512 | .filter(Boolean).join(' | '), | |
| 513 | // ... | |
| 514 | }); | |
| 515 | ||
| 516 | // 각 페이지마다... | |
| 517 | export const Route = createFileRoute("/blog")({ | |
| 518 | head: () => | |
| 519 | defineHead({ | |
| 520 | title: "clover's blog", // `titleTemplate`으로 템플릿 처리됨 | |
| 521 | description: "a catgirl meows about her technology viewpoints", | |
| 522 | canonical: "/blog", // `base`와 결합됨 | |
| 523 | ||
| 524 | // 지정 시, 페이지 제목과 설명을 기본값으로 사용하여 | |
| 525 | // Open Graph 및 Twitter 임베드를 구성합니다. | |
| 526 | // 기본값도 괜찮지만, 더 많은 옵션을 지원합니다. | |
| 527 | embed: { | |
| 528 | image: "/img/blog.webp", | |
| 529 | }, | |
| 530 | ||
| 531 | // 모든 특수 메타 태그는 JSX 프래그먼트로 처리됩니다. | |
| 532 | // 이는 리액트 렌더링하지 않고 단순히 태그를 순회합니다. | |
| 533 | // 제 목표는 가장 흔한 99%의 사용 사례를 커버하는 것이었습니다. | |
| 534 | extra: <> | |
| 535 | <meta name="site-verification" content="waffles" />, | |
| 536 | </>, | |
| 537 | }), | |
| 538 | ||
| 539 | component: Page, | |
| 540 | }); | |
| 541 | ||
| 542 | function Page() { | |
| 543 | ... | |
| 544 | } | |
| 545 | ``` | |
| 546 | ||
| 547 | 제 버전은 Next.js 메타데이터 객체의 전체 영역을 커버하는 데 초점을 두지 않았으며, 대신 인라인 JSX를 사용하여 그 공백을 메웠습니다. | |
| 548 | ||
| 549 | [lib-jsr]: https://jsr.io/@clo/lib | |
| 550 | [lib-npm]: https://npmjs.com/@paperclover/lib | |
| 551 | ||
| 552 | [§3.2]: #vercel-og | |
| 553 | ||
| 554 | <Heading | |
| 555 | level='h3' | |
| 556 | slug='ditching-nextjs' | |
| 557 | ><code>next/og</code> 또한 좋습니다</Heading> | |
| 558 | ||
| 559 | 특별한 의견은 없습니다. 그냥 `@vercel/og` 패키지가 있다는 점을 모두에게 상기시키고 싶을 뿐입니다. | |
| 560 | ||
| 561 | [§4]: #experience-feels-like-the-usual | |
| 562 | ||
| 563 | <Heading | |
| 564 | level='h2' | |
| 565 | slug='experience-feels-like-the-usual' | |
| 566 | >제 경험은 일반적인 것 같아요</Heading> | |
| 567 | ||
| 568 | Next.js Conf 2024에서 참석자 모두가 서버 컴포넌트(Server Components)에 열광했습니다. 정확히 누구와 이야기했는지는 기억나지 않지만, 주요 인사들은 모두 이 기술에 주목하고 있었습니다. RSC의 번들러 부분을 구현한 저는 이 포맷의 몇 가지 문제점을 목격했습니다. Next 15가 지난해 App Router를 "안정화"하면서 많은 기업들이 이를 기반으로 제품을 구축하고 있으며, 이러한 문제점들을 직접 경험하고 있습니다. | |
| 569 | ||
| 570 | 저는 Next.js를 늦게 접하게 되어 6월에야 버전 15로 시작했습니다. 하지만 행사에서 만난 모든 분들이 제 의견에 공감해 주셨습니다. Bun의 1.3 파티에서 이 주제로 이야기한 분들도 모두 동의하셨죠. 심지어 Vercel의 몇몇 분들조차 Next.js의 실제 사용 방식이 마음에 들지 않는다고 말씀하셨습니다. | |
| 571 | ||
| 572 | TanStack Start가 안정화되면서 모두가 원하는 Next.js 대체 솔루션이 되길 바랍니다. | |
| 573 | ||
| 574 | [§5]: #prefer-respectful-tools | |
| 575 | ||
| 576 | <Heading | |
| 577 | level='h2' | |
| 578 | slug='prefer-respectful-tools' | |
| 579 | >사용자를 존중하는 도구를 선택하세요</Heading> | |
| 580 | ||
| 581 | 자바스크립트 생태계는 상당 부분 엉망이고, 그 때문인지 웹 개발은 종종 조롱의 대상이 됩니다.. 저는 웹 작업을 회복 불가능한 난장판이라고 생각했던 적이 수없이 많았지만, 그 혼란은 사실 제가 둘러싸고 있던 흔히 쓰이는 라이브러리들 때문이었습니다. 그 껍질을 벗겨내면 현대 웹 개발 기술은 정말 대단합니다. | |
| 582 | ||
| 583 | 2024년 말부터 프레임워크 없이 이 웹사이트를 처음부터 직접 제작해 왔습니다. 자체 개발한 [TUI 진행률 위젯][progress], [정적 파일 프록시][file-cache], 증분 빌드 시스템 등 다양한 컴포넌트를 직접 구현하며 작업했습니다. 지난 수년간 가장 행복한 코딩 경험이었습니다. *[페이퍼 클로버]* 방문자들은 더 나은 품질의 웹사이트를 이용하게 되고, 제가 만든 미니 라이브러리는 [공개적으로 활용될 수 있게 추출됩니다][lib]. 덕분에 모두가 이득을 보게 되었죠. | |
| 584 | ||
| 585 | 이 정도로 완전히 처음부터 시작하는 방식은 대부분의 사람들에게, 특히 직장에서는 부담스럽습니다. 최소한 우리를 존중하는 고품질 도구에만 우리의 관심과 돈을 쏟아야 한다고 생각합니다. 그리고 Next.js와 그 배후 기업인 Vercel은 그렇지 않습니다. | |
| 586 | ||
| 587 | Next.js를 사용하면서 개발자로서 존중받는다는 느낌이 들지 않는다면, 과연 여러분과 동료들이 그들의 [서버리스 제국]을 계속 지지하고 싶은지 고민해 보세요. 현재 Vite 생태계는 구축하기에 꽤 괜찮아 보이지만, 아직 대규모 프로덕션 환경에서 그들의 도구를 사용해 본 경험은 거의 없습니다. [Void0의 Vite+ 출시 소식][vite-plus]은 흥미롭지만, 이러한 벤처 수준의 지원 도구가 장기적으로 우리(최종 사용자와 개발자)를 존중할지는 시간이 지나야 알 수 있을 것입니다. | |
| 588 | ||
| 589 | Next.js Conf 2025는 글을 쓰는 시점 기준으로 [내일][next-conf]입니다. 800달러짜리 티켓을 구매하는 대신, 웹 개발 생태계를 [존중하고 개선하는][tanstack-ethos] [TanStack 팀에 그 돈을 기부하기로 결정했습니다][tanstack-donate]. | |
| 590 | ||
| 591 | [페이퍼 클로버]: https://paperclover.net/ | |
| 592 | [lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme | |
| 593 | [progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts | |
| 594 | [file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts | |
| 595 | ||
| 596 | [next-conf]: https://nextjs.org/conf | |
| 597 | [vite-plus]: https://viteplus.dev/ | |
| 598 | [tanstack-ethos]: https://tanstack.com/ethos | |
| 599 | [tanstack-donate]: https://github.com/sponsors/tannerlinsley | |
| 600 | [serverless empire]: https://youtu.be/SCIfWhAheVw | |
| 601 | ||
| 602 | ## 앞으로의 계획 | |
| 603 | ||
| 604 | 점진적으로, 저는 저를 존중하지 않는 많은 소프트웨어들을 더 나은 대안으로 교체해 왔습니다. 그 예로는 GitHub, Visual Studio Code, DaVinci Resolve, Discord, Google Drive/Workspace 등이 있으며, 그 외에도 많습니다. 이 블로그에서는 제가 진행 중인 기술적 작업들(프로그레스 라이브러리, 자체 사이트 생성기의 목적, 현재 직장에서의 배움)과 Bun에서의 과거 프로젝트들(HMR, 크래시 리포터, 내장 모듈 번들링을 위한 독특한 시스템에 대한 세부 사항)에 대해 더 많이 다루려 합니다. 관심이 있으시다면 이메일 리스트에 가입해 주세요. | |
| 605 | ||
| 606 | <hr /> | |
| 607 | ||
| 608 | <a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually) | |
| 609 | ||
| 610 | [↑](#top) &mdash; [ask a question about this article (English)](/q+a) | |
| 611 | ||
| 612 | <br /> | |
| 613 | <br /> | |
| 614 | <br /> | |
| 615 | <br /> | |
| 616 | <br /> | |
| 617 | <br /> | |
| 618 | <footer> | |
| 619 | 2025 (c) paper clover | |
| 620 | </footer> | |
| 621 | ||
| 622 | </Layout> | |
| 623 | ||
| 624 | <br /> |
src/blog/tags/blog-layout.marko created+107| ... | ... | @@ -0,0 +1,107 @@ |
| 1 | import "../blog.css"; | |
| 2 | export const theme = { | |
| 3 | bg: "#271a30", | |
| 4 | fg: "#ffffff", | |
| 5 | primary: "#91ffc6", | |
| 6 | }; | |
| 7 | export const components = { | |
| 8 | heading: Heading, | |
| 9 | codeBlock: CodeBlock | |
| 10 | }; | |
| 11 | ||
| 12 | export interface Module { | |
| 13 | meta: { | |
| 14 | title: string; | |
| 15 | description: string; | |
| 16 | keywords: string[]; | |
| 17 | canonical: string; | |
| 18 | }; | |
| 19 | date: string; | |
| 20 | content: render.Node; | |
| 21 | translation?: { | |
| 22 | lang: string; | |
| 23 | author: string; | |
| 24 | href: string; | |
| 25 | date: string; | |
| 26 | }; | |
| 27 | } | |
| 28 | export interface Input { | |
| 29 | module: Module; | |
| 30 | } | |
| 31 | ||
| 32 | export interface Context { | |
| 33 | outline: markodown.Heading[]; | |
| 34 | } | |
| 35 | ||
| 36 | export const markoOnly = true; | |
| 37 | ||
| 38 | <const/module: Module = input.module /> | |
| 39 | <const/{ date, translation } = module /> | |
| 40 | <const/{ title, description, keywords, canonical } = module.meta /> | |
| 41 | <const/slug = canonical.replace(/^\/blog\/|\.[a-zA-Z_-]+$/g, '') /> | |
| 42 | ||
| 43 | <const/translations= | |
| 44 | Path.resolve(import.meta.dirname, "../pages/" + slug).readDirSync() | |
| 45 | .filter((x) => x.ext === ".mdo" && x.baseWithoutExt !== "en") | |
| 46 | .map((x) => x.baseWithoutExt) /> | |
| 47 | <const/t = UNWRAP(localization.languages[translation?.lang ?? "en"]) /> | |
| 48 | ||
| 49 | <const/_=(() => { | |
| 50 | $global.cloverOutline = input.outline; | |
| 51 | })() /> | |
| 52 | ||
| 53 | <main> | |
| 54 | <header> | |
| 55 | <a href="/">${t.return}</a> | |
| 56 | ||
| 57 | <h1 style={ 'max-width': "30ch" }>${title}</> | |
| 58 | <p class="description"><em>${description}</em></p> | |
| 59 | ||
| 60 | <div.meta> | |
| 61 | <a class="meta-author" href="/">${t.writtenByClover}</a> | |
| 62 | <span>•</span> | |
| 63 | <if=translation> | |
| 64 | <a.meta-translate href=translation.href> | |
| 65 | ${t.translatedBy(translation)} | |
| 66 | </a> | |
| 67 | <span>•</span> | |
| 68 | </> | |
| 69 | <span.meta-date>${date}</span> | |
| 70 | </div> | |
| 71 | <div.tag-list> | |
| 72 | <if=translations.length> | |
| 73 | <a | |
| 74 | class=["custom tag lang lang-original", { active: !translation }] | |
| 75 | href=`/blog/${slug}` | |
| 76 | > | |
| 77 | ${localization.languages.en.title} | |
| 78 | </a> | |
| 79 | <for|code| of=translations> | |
| 80 | <a | |
| 81 | class=["custom tag lang", { active: code === translation?.lang }] | |
| 82 | href=`/blog/${slug}.${code}` | |
| 83 | > | |
| 84 | ${UNWRAP(localization.languages[code]).title} | |
| 85 | </a> | |
| 86 | </> | |
| 87 | <a.custom.tag.square href="/blog/community-translations">${"+"}</a> | |
| 88 | <span.bar /> | |
| 89 | </> | |
| 90 | <for|tag| of=keywords> | |
| 91 | <div.tag>${tag}</div> | |
| 92 | </> | |
| 93 | </div> | |
| 94 | ||
| 95 | <hr /> | |
| 96 | </header> | |
| 97 | ||
| 98 | <${input.content} /> | |
| 99 | ||
| 100 | </main> | |
| 101 | ||
| 102 | import * as localization from "../localization.tsx"; | |
| 103 | import * as markodown from "@clo/markodown"; | |
| 104 | import Heading from "./heading.marko"; | |
| 105 | import CodeBlock from "./code-block.marko"; | |
| 106 | import { Path } from "#sitegen/path"; | |
| 107 | import { UNWRAP } from "lib/assert.ts"; |
src/blog/tags/code-block.marko created+15| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | <const/{ filename, language, tint, whitespace, content } = input /> | |
| 2 | ||
| 3 | <const/highlightedPromise = ["tsx", "jsx", "json", "html", "ts", "js"].includes(language) | |
| 4 | ? highlight.highlightCode(content.trim(), language) | |
| 5 | : content.trim() | |
| 6 | /> | |
| 7 | ||
| 8 | <figure class=["code", tint]> | |
| 9 | <if=filename><figcaption>${filename}</figcaption></> | |
| 10 | <await|highlighted|=highlightedPromise> | |
| 11 | <pre style=whitespace ? { "white-space": whitespace } : {}><code>$!{highlighted}</code></pre> | |
| 12 | </> | |
| 13 | </figure> | |
| 14 | ||
| 15 | import * as highlight from "@/file-viewer/highlight.ts"; |
src/blog/tags/heading.marko created+5| ... | ... | @@ -0,0 +1,5 @@ |
| 1 | <const/{ level, content, ...attrs } = input /> | |
| 2 | <${'h' + level} ...attrs> | |
| 3 | <a.return href="#toc">§</a> | |
| 4 | <${content} /> | |
| 5 | </> |
src/blog/tags/heading.tsx deleted-10| ... | ... | @@ -1,10 +0,0 @@ |
| 1 | export default function ({ level: Level, slug, content }) { | |
| 2 | return ( | |
| 3 | <> | |
| 4 | <Level id={slug}> | |
| 5 | <a class="return" href="#toc">§</a> | |
| 6 | {content} | |
| 7 | </Level> | |
| 8 | </> | |
| 9 | ); | |
| 10 | } |
src/blog/tags/layout.tsx deleted-106| ... | ... | @@ -1,106 +0,0 @@ |
| 1 | import "../blog.css"; | |
| 2 | import { Path } from "#sitegen/path"; | |
| 3 | ||
| 4 | export const theme = { | |
| 5 | bg: "#271a30", | |
| 6 | fg: "#ffffff", | |
| 7 | primary: "#91ffc6", | |
| 8 | }; | |
| 9 | ||
| 10 | interface LayoutProps { | |
| 11 | meta: { title: string; description: string; keywords: string[] }; | |
| 12 | date: string; | |
| 13 | slug: string; | |
| 14 | content: render.Node; | |
| 15 | translation?: { | |
| 16 | lang: string; | |
| 17 | ||
| 18 | author: string; | |
| 19 | href: string; | |
| 20 | ||
| 21 | date: string; | |
| 22 | }; | |
| 23 | } | |
| 24 | ||
| 25 | export async function Layout({ | |
| 26 | meta: { title, description, keywords }, | |
| 27 | date, | |
| 28 | slug, | |
| 29 | content, | |
| 30 | translation, | |
| 31 | }: LayoutProps) { | |
| 32 | const translations = | |
| 33 | (await Path.resolve(import.meta.dirname, "../pages/" + slug).readDir()) | |
| 34 | .filter((x) => x.ext === ".mdx" && x.baseWithoutExt !== "en") | |
| 35 | .map((x) => x.baseWithoutExt); | |
| 36 | const t = UNWRAP(localization.languages[translation?.lang ?? "en"]); | |
| 37 | return ( | |
| 38 | <> | |
| 39 | <main> | |
| 40 | <header> | |
| 41 | <a href="/">{t.return}</a> | |
| 42 | ||
| 43 | <h1 style="max-width: 30ch">{title}</h1> | |
| 44 | <p class="description"> | |
| 45 | <em>{description}</em> | |
| 46 | </p> | |
| 47 | <div class="meta"> | |
| 48 | <a class="meta-author" href="/">{t.writtenByClover}</a> | |
| 49 | <span>•</span> | |
| 50 | {translation | |
| 51 | ? ( | |
| 52 | <> | |
| 53 | <a href={translation.href} class="meta-translate"> | |
| 54 | {t.translatedBy(translation)} | |
| 55 | </a> | |
| 56 | <span>•</span> | |
| 57 | </> | |
| 58 | ) | |
| 59 | : ""} | |
| 60 | <span class="meta-date">{date}</span> | |
| 61 | </div> | |
| 62 | <div class="tag-list"> | |
| 63 | {translations.length > 0 | |
| 64 | ? ( | |
| 65 | <> | |
| 66 | <a | |
| 67 | href={`/blog/${slug}`} | |
| 68 | class={["custom", "tag", "lang", "lang-original", { | |
| 69 | active: !translation, | |
| 70 | }]} | |
| 71 | > | |
| 72 | {localization.languages.en.title} | |
| 73 | </a> | |
| 74 | {translations.map((code) => ( | |
| 75 | <a | |
| 76 | class={["custom", "tag", "lang", { | |
| 77 | active: code === translation?.lang, | |
| 78 | }]} | |
| 79 | href={`/blog/${slug}.${code}`} | |
| 80 | > | |
| 81 | {UNWRAP(localization.languages[code]).title} | |
| 82 | </a> | |
| 83 | ))} | |
| 84 | <a | |
| 85 | href="/blog/community-translations" | |
| 86 | class="custom tag square" | |
| 87 | > | |
| 88 | + | |
| 89 | </a> | |
| 90 | <span class="bar"></span> | |
| 91 | </> | |
| 92 | ) | |
| 93 | : ""} | |
| 94 | {keywords.map((tag) => <div class="tag">{tag}</div>)} | |
| 95 | </div> | |
| 96 | <hr /> | |
| 97 | </header> | |
| 98 | {content} | |
| 99 | </main> | |
| 100 | </> | |
| 101 | ); | |
| 102 | } | |
| 103 | ||
| 104 | import * as localization from "../localization.tsx"; | |
| 105 | import { UNWRAP } from "lib/assert.ts"; | |
| 106 | import * as render from "lib/render.ts"; |
src/blog/tags/table-of-contents.marko created+29| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | import "./table-of-contents.css"; | |
| 2 | ||
| 3 | export interface Node { | |
| 4 | heading: markodown.Heading; | |
| 5 | children: Node[]; | |
| 6 | } | |
| 7 | ||
| 8 | <const/outline = $global.cloverOutline /> | |
| 9 | <const/groups = markodown.outlineToTree(outline) /> | |
| 10 | ||
| 11 | <define/Recurse|input: markodown.HeadingTree|> | |
| 12 | <a href=`#${input.id}`><${input.content} /></a> | |
| 13 | <if=input.children.length> | |
| 14 | <ul> | |
| 15 | <for|item| of=input.children> | |
| 16 | <li><Recurse ...item /></li> | |
| 17 | </> | |
| 18 | </ul> | |
| 19 | </> | |
| 20 | </> | |
| 21 | ||
| 22 | <div#toc> | |
| 23 | <h2>Contents:</h2> | |
| 24 | <ul><for|item| of=groups> | |
| 25 | <li><Recurse ...item /></li> | |
| 26 | </></ul> | |
| 27 | </div> | |
| 28 | ||
| 29 | import * as markodown from "@clo/markodown"; |
src/blog/tags/table-of-contents.tsx deleted-10| ... | ... | @@ -1,10 +0,0 @@ |
| 1 | import "./table-of-contents.css"; | |
| 2 | ||
| 3 | export default function ({ content }) { | |
| 4 | return ( | |
| 5 | <div id="toc"> | |
| 6 | <h2>Contents:</h2> | |
| 7 | {content} | |
| 8 | </div> | |
| 9 | ); | |
| 10 | } |
src/file-viewer/highlight-grammar/mdx.plist deleted-9527| ... | ... | @@ -1,9527 +0,0 @@ |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| 3 | <plist version="1.0"> | |
| 4 | <dict> | |
| 5 | <key>fileTypes</key> | |
| 6 | <array> | |
| 7 | <string>mdx</string> | |
| 8 | </array> | |
| 9 | <key>name</key> | |
| 10 | <string>MDX</string> | |
| 11 | <key>patterns</key> | |
| 12 | <array> | |
| 13 | <dict> | |
| 14 | <key>include</key> | |
| 15 | <string>#markdown-frontmatter</string> | |
| 16 | </dict> | |
| 17 | <dict> | |
| 18 | <key>include</key> | |
| 19 | <string>#markdown-sections</string> | |
| 20 | </dict> | |
| 21 | </array> | |
| 22 | <key>repository</key> | |
| 23 | <dict> | |
| 24 | <key>markdown-frontmatter</key> | |
| 25 | <dict> | |
| 26 | <key>patterns</key> | |
| 27 | <array> | |
| 28 | <dict> | |
| 29 | <key>include</key> | |
| 30 | <string>#extension-toml</string> | |
| 31 | </dict> | |
| 32 | <dict> | |
| 33 | <key>include</key> | |
| 34 | <string>#extension-yaml</string> | |
| 35 | </dict> | |
| 36 | </array> | |
| 37 | </dict> | |
| 38 | <key>markdown-sections</key> | |
| 39 | <dict> | |
| 40 | <key>patterns</key> | |
| 41 | <array> | |
| 42 | <dict> | |
| 43 | <key>include</key> | |
| 44 | <string>#commonmark-block-quote</string> | |
| 45 | </dict> | |
| 46 | <dict> | |
| 47 | <key>include</key> | |
| 48 | <string>#commonmark-code-fenced</string> | |
| 49 | </dict> | |
| 50 | <dict> | |
| 51 | <key>include</key> | |
| 52 | <string>#extension-gfm-footnote-definition</string> | |
| 53 | </dict> | |
| 54 | <dict> | |
| 55 | <key>include</key> | |
| 56 | <string>#commonmark-definition</string> | |
| 57 | </dict> | |
| 58 | <dict> | |
| 59 | <key>include</key> | |
| 60 | <string>#commonmark-heading-atx</string> | |
| 61 | </dict> | |
| 62 | <dict> | |
| 63 | <key>include</key> | |
| 64 | <string>#commonmark-thematic-break</string> | |
| 65 | </dict> | |
| 66 | <dict> | |
| 67 | <key>include</key> | |
| 68 | <string>#commonmark-heading-setext</string> | |
| 69 | </dict> | |
| 70 | <dict> | |
| 71 | <key>include</key> | |
| 72 | <string>#commonmark-list-item</string> | |
| 73 | </dict> | |
| 74 | <dict> | |
| 75 | <key>include</key> | |
| 76 | <string>#extension-gfm-table</string> | |
| 77 | </dict> | |
| 78 | <dict> | |
| 79 | <key>include</key> | |
| 80 | <string>#extension-math-flow</string> | |
| 81 | </dict> | |
| 82 | <dict> | |
| 83 | <key>include</key> | |
| 84 | <string>#extension-mdx-esm</string> | |
| 85 | </dict> | |
| 86 | <dict> | |
| 87 | <key>include</key> | |
| 88 | <string>#extension-mdx-expression-flow</string> | |
| 89 | </dict> | |
| 90 | <dict> | |
| 91 | <key>include</key> | |
| 92 | <string>#extension-mdx-jsx-flow</string> | |
| 93 | </dict> | |
| 94 | <dict> | |
| 95 | <key>include</key> | |
| 96 | <string>#commonmark-paragraph</string> | |
| 97 | </dict> | |
| 98 | </array> | |
| 99 | </dict> | |
| 100 | <key>markdown-string</key> | |
| 101 | <dict> | |
| 102 | <key>patterns</key> | |
| 103 | <array> | |
| 104 | <dict> | |
| 105 | <key>include</key> | |
| 106 | <string>#commonmark-character-escape</string> | |
| 107 | </dict> | |
| 108 | <dict> | |
| 109 | <key>include</key> | |
| 110 | <string>#commonmark-character-reference</string> | |
| 111 | </dict> | |
| 112 | </array> | |
| 113 | </dict> | |
| 114 | <key>markdown-text</key> | |
| 115 | <dict> | |
| 116 | <key>patterns</key> | |
| 117 | <array> | |
| 118 | <dict> | |
| 119 | <key>include</key> | |
| 120 | <string>#commonmark-attention</string> | |
| 121 | </dict> | |
| 122 | <dict> | |
| 123 | <key>include</key> | |
| 124 | <string>#commonmark-character-escape</string> | |
| 125 | </dict> | |
| 126 | <dict> | |
| 127 | <key>include</key> | |
| 128 | <string>#commonmark-character-reference</string> | |
| 129 | </dict> | |
| 130 | <dict> | |
| 131 | <key>include</key> | |
| 132 | <string>#commonmark-code-text</string> | |
| 133 | </dict> | |
| 134 | <dict> | |
| 135 | <key>include</key> | |
| 136 | <string>#commonmark-hard-break-trailing</string> | |
| 137 | </dict> | |
| 138 | <dict> | |
| 139 | <key>include</key> | |
| 140 | <string>#commonmark-hard-break-escape</string> | |
| 141 | </dict> | |
| 142 | <dict> | |
| 143 | <key>include</key> | |
| 144 | <string>#commonmark-label-end</string> | |
| 145 | </dict> | |
| 146 | <dict> | |
| 147 | <key>include</key> | |
| 148 | <string>#extension-gfm-footnote-call</string> | |
| 149 | </dict> | |
| 150 | <dict> | |
| 151 | <key>include</key> | |
| 152 | <string>#commonmark-label-start</string> | |
| 153 | </dict> | |
| 154 | <dict> | |
| 155 | <key>include</key> | |
| 156 | <string>#extension-gfm-autolink-literal</string> | |
| 157 | </dict> | |
| 158 | <dict> | |
| 159 | <key>include</key> | |
| 160 | <string>#extension-gfm-strikethrough</string> | |
| 161 | </dict> | |
| 162 | <dict> | |
| 163 | <key>include</key> | |
| 164 | <string>#extension-github-gemoji</string> | |
| 165 | </dict> | |
| 166 | <dict> | |
| 167 | <key>include</key> | |
| 168 | <string>#extension-github-mention</string> | |
| 169 | </dict> | |
| 170 | <dict> | |
| 171 | <key>include</key> | |
| 172 | <string>#extension-github-reference</string> | |
| 173 | </dict> | |
| 174 | <dict> | |
| 175 | <key>include</key> | |
| 176 | <string>#extension-math-text</string> | |
| 177 | </dict> | |
| 178 | <dict> | |
| 179 | <key>include</key> | |
| 180 | <string>#extension-mdx-expression-text</string> | |
| 181 | </dict> | |
| 182 | <dict> | |
| 183 | <key>include</key> | |
| 184 | <string>#extension-mdx-jsx-text</string> | |
| 185 | </dict> | |
| 186 | </array> | |
| 187 | </dict> | |
| 188 | <key>commonmark-attention</key> | |
| 189 | <dict> | |
| 190 | <key>patterns</key> | |
| 191 | <array> | |
| 192 | <dict> | |
| 193 | <key>match</key> | |
| 194 | <string>(?&lt;=\S)\*{3,}|\*{3,}(?=\S)</string> | |
| 195 | <key>name</key> | |
| 196 | <string>string.other.strong.emphasis.asterisk.mdx</string> | |
| 197 | </dict> | |
| 198 | <dict> | |
| 199 | <key>match</key> | |
| 200 | <string>(?&lt;=[\p{L}\p{N}])_{3,}(?![\p{L}\p{N}])|(?&lt;=\p{P})_{3,}|(?&lt;![\p{L}\p{N}]|\p{P})_{3,}(?!\s)</string> | |
| 201 | <key>name</key> | |
| 202 | <string>string.other.strong.emphasis.underscore.mdx</string> | |
| 203 | </dict> | |
| 204 | <dict> | |
| 205 | <key>match</key> | |
| 206 | <string>(?&lt;=\S)\*{2}|\*{2}(?=\S)</string> | |
| 207 | <key>name</key> | |
| 208 | <string>string.other.strong.asterisk.mdx</string> | |
| 209 | </dict> | |
| 210 | <dict> | |
| 211 | <key>match</key> | |
| 212 | <string>(?&lt;=[\p{L}\p{N}])_{2}(?![\p{L}\p{N}])|(?&lt;=\p{P})_{2}|(?&lt;![\p{L}\p{N}]|\p{P})_{2}(?!\s)</string> | |
| 213 | <key>name</key> | |
| 214 | <string>string.other.strong.underscore.mdx</string> | |
| 215 | </dict> | |
| 216 | <dict> | |
| 217 | <key>match</key> | |
| 218 | <string>(?&lt;=\S)\*|\*(?=\S)</string> | |
| 219 | <key>name</key> | |
| 220 | <string>string.other.emphasis.asterisk.mdx</string> | |
| 221 | </dict> | |
| 222 | <dict> | |
| 223 | <key>match</key> | |
| 224 | <string>(?&lt;=[\p{L}\p{N}])_(?![\p{L}\p{N}])|(?&lt;=\p{P})_|(?&lt;![\p{L}\p{N}]|\p{P})_(?!\s)</string> | |
| 225 | <key>name</key> | |
| 226 | <string>string.other.emphasis.underscore.mdx</string> | |
| 227 | </dict> | |
| 228 | </array> | |
| 229 | </dict> | |
| 230 | <key>commonmark-block-quote</key> | |
| 231 | <dict> | |
| 232 | <key>begin</key> | |
| 233 | <string>(?:^|\G)[\t ]*(&gt;)[ ]?</string> | |
| 234 | <key>beginCaptures</key> | |
| 235 | <dict> | |
| 236 | <key>0</key> | |
| 237 | <dict> | |
| 238 | <key>name</key> | |
| 239 | <string>markup.quote.mdx</string> | |
| 240 | </dict> | |
| 241 | <key>1</key> | |
| 242 | <dict> | |
| 243 | <key>name</key> | |
| 244 | <string>punctuation.definition.quote.begin.mdx</string> | |
| 245 | </dict> | |
| 246 | </dict> | |
| 247 | <key>patterns</key> | |
| 248 | <array> | |
| 249 | <dict> | |
| 250 | <key>include</key> | |
| 251 | <string>#markdown-sections</string> | |
| 252 | </dict> | |
| 253 | </array> | |
| 254 | <key>name</key> | |
| 255 | <string>markup.quote.mdx</string> | |
| 256 | <key>while</key> | |
| 257 | <string>(&gt;)[ ]?</string> | |
| 258 | <key>whileCaptures</key> | |
| 259 | <dict> | |
| 260 | <key>0</key> | |
| 261 | <dict> | |
| 262 | <key>name</key> | |
| 263 | <string>markup.quote.mdx</string> | |
| 264 | </dict> | |
| 265 | <key>1</key> | |
| 266 | <dict> | |
| 267 | <key>name</key> | |
| 268 | <string>punctuation.definition.quote.begin.mdx</string> | |
| 269 | </dict> | |
| 270 | </dict> | |
| 271 | </dict> | |
| 272 | <key>commonmark-character-escape</key> | |
| 273 | <dict> | |
| 274 | <key>match</key> | |
| 275 | <string>\\(?:[!"#$%&amp;'()*+,\-.\/:;&lt;=&gt;?@\[\\\]^_`{|}~])</string> | |
| 276 | <key>name</key> | |
| 277 | <string>constant.language.character-escape.mdx</string> | |
| 278 | </dict> | |
| 279 | <key>commonmark-character-reference</key> | |
| 280 | <dict> | |
| 281 | <key>patterns</key> | |
| 282 | <array> | |
| 283 | <dict> | |
| 284 | <key>include</key> | |
| 285 | <string>#whatwg-html-data-character-reference-named-terminated</string> | |
| 286 | </dict> | |
| 287 | <dict> | |
| 288 | <key>match</key> | |
| 289 | <string>(&amp;)(#)([Xx])([0-9A-Fa-f]{1,6})(;)</string> | |
| 290 | <key>name</key> | |
| 291 | <string>constant.language.character-reference.numeric.hexadecimal.html</string> | |
| 292 | <key>captures</key> | |
| 293 | <dict> | |
| 294 | <key>1</key> | |
| 295 | <dict> | |
| 296 | <key>name</key> | |
| 297 | <string>punctuation.definition.character-reference.begin.html</string> | |
| 298 | </dict> | |
| 299 | <key>2</key> | |
| 300 | <dict> | |
| 301 | <key>name</key> | |
| 302 | <string>punctuation.definition.character-reference.numeric.html</string> | |
| 303 | </dict> | |
| 304 | <key>3</key> | |
| 305 | <dict> | |
| 306 | <key>name</key> | |
| 307 | <string>punctuation.definition.character-reference.numeric.hexadecimal.html</string> | |
| 308 | </dict> | |
| 309 | <key>4</key> | |
| 310 | <dict> | |
| 311 | <key>name</key> | |
| 312 | <string>constant.numeric.integer.hexadecimal.html</string> | |
| 313 | </dict> | |
| 314 | <key>5</key> | |
| 315 | <dict> | |
| 316 | <key>name</key> | |
| 317 | <string>punctuation.definition.character-reference.end.html</string> | |
| 318 | </dict> | |
| 319 | </dict> | |
| 320 | </dict> | |
| 321 | <dict> | |
| 322 | <key>match</key> | |
| 323 | <string>(&amp;)(#)([0-9]{1,7})(;)</string> | |
| 324 | <key>name</key> | |
| 325 | <string>constant.language.character-reference.numeric.decimal.html</string> | |
| 326 | <key>captures</key> | |
| 327 | <dict> | |
| 328 | <key>1</key> | |
| 329 | <dict> | |
| 330 | <key>name</key> | |
| 331 | <string>punctuation.definition.character-reference.begin.html</string> | |
| 332 | </dict> | |
| 333 | <key>2</key> | |
| 334 | <dict> | |
| 335 | <key>name</key> | |
| 336 | <string>punctuation.definition.character-reference.numeric.html</string> | |
| 337 | </dict> | |
| 338 | <key>3</key> | |
| 339 | <dict> | |
| 340 | <key>name</key> | |
| 341 | <string>constant.numeric.integer.decimal.html</string> | |
| 342 | </dict> | |
| 343 | <key>4</key> | |
| 344 | <dict> | |
| 345 | <key>name</key> | |
| 346 | <string>punctuation.definition.character-reference.end.html</string> | |
| 347 | </dict> | |
| 348 | </dict> | |
| 349 | </dict> | |
| 350 | </array> | |
| 351 | </dict> | |
| 352 | <key>commonmark-code-fenced</key> | |
| 353 | <dict> | |
| 354 | <key>patterns</key> | |
| 355 | <array> | |
| 356 | <dict> | |
| 357 | <key>include</key> | |
| 358 | <string>#commonmark-code-fenced-apib</string> | |
| 359 | </dict> | |
| 360 | <dict> | |
| 361 | <key>include</key> | |
| 362 | <string>#commonmark-code-fenced-asciidoc</string> | |
| 363 | </dict> | |
| 364 | <dict> | |
| 365 | <key>include</key> | |
| 366 | <string>#commonmark-code-fenced-c</string> | |
| 367 | </dict> | |
| 368 | <dict> | |
| 369 | <key>include</key> | |
| 370 | <string>#commonmark-code-fenced-clojure</string> | |
| 371 | </dict> | |
| 372 | <dict> | |
| 373 | <key>include</key> | |
| 374 | <string>#commonmark-code-fenced-coffee</string> | |
| 375 | </dict> | |
| 376 | <dict> | |
| 377 | <key>include</key> | |
| 378 | <string>#commonmark-code-fenced-console</string> | |
| 379 | </dict> | |
| 380 | <dict> | |
| 381 | <key>include</key> | |
| 382 | <string>#commonmark-code-fenced-cpp</string> | |
| 383 | </dict> | |
| 384 | <dict> | |
| 385 | <key>include</key> | |
| 386 | <string>#commonmark-code-fenced-cs</string> | |
| 387 | </dict> | |
| 388 | <dict> | |
| 389 | <key>include</key> | |
| 390 | <string>#commonmark-code-fenced-css</string> | |
| 391 | </dict> | |
| 392 | <dict> | |
| 393 | <key>include</key> | |
| 394 | <string>#commonmark-code-fenced-diff</string> | |
| 395 | </dict> | |
| 396 | <dict> | |
| 397 | <key>include</key> | |
| 398 | <string>#commonmark-code-fenced-dockerfile</string> | |
| 399 | </dict> | |
| 400 | <dict> | |
| 401 | <key>include</key> | |
| 402 | <string>#commonmark-code-fenced-elixir</string> | |
| 403 | </dict> | |
| 404 | <dict> | |
| 405 | <key>include</key> | |
| 406 | <string>#commonmark-code-fenced-elm</string> | |
| 407 | </dict> | |
| 408 | <dict> | |
| 409 | <key>include</key> | |
| 410 | <string>#commonmark-code-fenced-erlang</string> | |
| 411 | </dict> | |
| 412 | <dict> | |
| 413 | <key>include</key> | |
| 414 | <string>#commonmark-code-fenced-gitconfig</string> | |
| 415 | </dict> | |
| 416 | <dict> | |
| 417 | <key>include</key> | |
| 418 | <string>#commonmark-code-fenced-go</string> | |
| 419 | </dict> | |
| 420 | <dict> | |
| 421 | <key>include</key> | |
| 422 | <string>#commonmark-code-fenced-graphql</string> | |
| 423 | </dict> | |
| 424 | <dict> | |
| 425 | <key>include</key> | |
| 426 | <string>#commonmark-code-fenced-haskell</string> | |
| 427 | </dict> | |
| 428 | <dict> | |
| 429 | <key>include</key> | |
| 430 | <string>#commonmark-code-fenced-html</string> | |
| 431 | </dict> | |
| 432 | <dict> | |
| 433 | <key>include</key> | |
| 434 | <string>#commonmark-code-fenced-ini</string> | |
| 435 | </dict> | |
| 436 | <dict> | |
| 437 | <key>include</key> | |
| 438 | <string>#commonmark-code-fenced-java</string> | |
| 439 | </dict> | |
| 440 | <dict> | |
| 441 | <key>include</key> | |
| 442 | <string>#commonmark-code-fenced-js</string> | |
| 443 | </dict> | |
| 444 | <dict> | |
| 445 | <key>include</key> | |
| 446 | <string>#commonmark-code-fenced-json</string> | |
| 447 | </dict> | |
| 448 | <dict> | |
| 449 | <key>include</key> | |
| 450 | <string>#commonmark-code-fenced-julia</string> | |
| 451 | </dict> | |
| 452 | <dict> | |
| 453 | <key>include</key> | |
| 454 | <string>#commonmark-code-fenced-kotlin</string> | |
| 455 | </dict> | |
| 456 | <dict> | |
| 457 | <key>include</key> | |
| 458 | <string>#commonmark-code-fenced-less</string> | |
| 459 | </dict> | |
| 460 | <dict> | |
| 461 | <key>include</key> | |
| 462 | <string>#commonmark-code-fenced-less</string> | |
| 463 | </dict> | |
| 464 | <dict> | |
| 465 | <key>include</key> | |
| 466 | <string>#commonmark-code-fenced-lua</string> | |
| 467 | </dict> | |
| 468 | <dict> | |
| 469 | <key>include</key> | |
| 470 | <string>#commonmark-code-fenced-makefile</string> | |
| 471 | </dict> | |
| 472 | <dict> | |
| 473 | <key>include</key> | |
| 474 | <string>#commonmark-code-fenced-md</string> | |
| 475 | </dict> | |
| 476 | <dict> | |
| 477 | <key>include</key> | |
| 478 | <string>#commonmark-code-fenced-mdx</string> | |
| 479 | </dict> | |
| 480 | <dict> | |
| 481 | <key>include</key> | |
| 482 | <string>#commonmark-code-fenced-objc</string> | |
| 483 | </dict> | |
| 484 | <dict> | |
| 485 | <key>include</key> | |
| 486 | <string>#commonmark-code-fenced-perl</string> | |
| 487 | </dict> | |
| 488 | <dict> | |
| 489 | <key>include</key> | |
| 490 | <string>#commonmark-code-fenced-php</string> | |
| 491 | </dict> | |
| 492 | <dict> | |
| 493 | <key>include</key> | |
| 494 | <string>#commonmark-code-fenced-php</string> | |
| 495 | </dict> | |
| 496 | <dict> | |
| 497 | <key>include</key> | |
| 498 | <string>#commonmark-code-fenced-python</string> | |
| 499 | </dict> | |
| 500 | <dict> | |
| 501 | <key>include</key> | |
| 502 | <string>#commonmark-code-fenced-r</string> | |
| 503 | </dict> | |
| 504 | <dict> | |
| 505 | <key>include</key> | |
| 506 | <string>#commonmark-code-fenced-raku</string> | |
| 507 | </dict> | |
| 508 | <dict> | |
| 509 | <key>include</key> | |
| 510 | <string>#commonmark-code-fenced-ruby</string> | |
| 511 | </dict> | |
| 512 | <dict> | |
| 513 | <key>include</key> | |
| 514 | <string>#commonmark-code-fenced-rust</string> | |
| 515 | </dict> | |
| 516 | <dict> | |
| 517 | <key>include</key> | |
| 518 | <string>#commonmark-code-fenced-scala</string> | |
| 519 | </dict> | |
| 520 | <dict> | |
| 521 | <key>include</key> | |
| 522 | <string>#commonmark-code-fenced-scss</string> | |
| 523 | </dict> | |
| 524 | <dict> | |
| 525 | <key>include</key> | |
| 526 | <string>#commonmark-code-fenced-shell</string> | |
| 527 | </dict> | |
| 528 | <dict> | |
| 529 | <key>include</key> | |
| 530 | <string>#commonmark-code-fenced-shell-session</string> | |
| 531 | </dict> | |
| 532 | <dict> | |
| 533 | <key>include</key> | |
| 534 | <string>#commonmark-code-fenced-sql</string> | |
| 535 | </dict> | |
| 536 | <dict> | |
| 537 | <key>include</key> | |
| 538 | <string>#commonmark-code-fenced-svg</string> | |
| 539 | </dict> | |
| 540 | <dict> | |
| 541 | <key>include</key> | |
| 542 | <string>#commonmark-code-fenced-swift</string> | |
| 543 | </dict> | |
| 544 | <dict> | |
| 545 | <key>include</key> | |
| 546 | <string>#commonmark-code-fenced-toml</string> | |
| 547 | </dict> | |
| 548 | <dict> | |
| 549 | <key>include</key> | |
| 550 | <string>#commonmark-code-fenced-ts</string> | |
| 551 | </dict> | |
| 552 | <dict> | |
| 553 | <key>include</key> | |
| 554 | <string>#commonmark-code-fenced-tsx</string> | |
| 555 | </dict> | |
| 556 | <dict> | |
| 557 | <key>include</key> | |
| 558 | <string>#commonmark-code-fenced-vbnet</string> | |
| 559 | </dict> | |
| 560 | <dict> | |
| 561 | <key>include</key> | |
| 562 | <string>#commonmark-code-fenced-xml</string> | |
| 563 | </dict> | |
| 564 | <dict> | |
| 565 | <key>include</key> | |
| 566 | <string>#commonmark-code-fenced-yaml</string> | |
| 567 | </dict> | |
| 568 | <dict> | |
| 569 | <key>include</key> | |
| 570 | <string>#commonmark-code-fenced-unknown</string> | |
| 571 | </dict> | |
| 572 | </array> | |
| 573 | </dict> | |
| 574 | <key>commonmark-code-fenced-unknown</key> | |
| 575 | <dict> | |
| 576 | <key>patterns</key> | |
| 577 | <array> | |
| 578 | <dict> | |
| 579 | <key>begin</key> | |
| 580 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?:[^\t\n\r` ])+)(?:[\t ]+((?:[^\n\r`])+))?)?(?:[\t ]*$)</string> | |
| 581 | <key>beginCaptures</key> | |
| 582 | <dict> | |
| 583 | <key>1</key> | |
| 584 | <dict> | |
| 585 | <key>name</key> | |
| 586 | <string>string.other.begin.code.fenced.mdx</string> | |
| 587 | </dict> | |
| 588 | <key>2</key> | |
| 589 | <dict> | |
| 590 | <key>name</key> | |
| 591 | <string>entity.name.function.mdx</string> | |
| 592 | <key>patterns</key> | |
| 593 | <array> | |
| 594 | <dict> | |
| 595 | <key>include</key> | |
| 596 | <string>#markdown-string</string> | |
| 597 | </dict> | |
| 598 | </array> | |
| 599 | </dict> | |
| 600 | <key>3</key> | |
| 601 | <dict> | |
| 602 | <key>patterns</key> | |
| 603 | <array> | |
| 604 | <dict> | |
| 605 | <key>include</key> | |
| 606 | <string>#markdown-string</string> | |
| 607 | </dict> | |
| 608 | </array> | |
| 609 | </dict> | |
| 610 | </dict> | |
| 611 | <key>contentName</key> | |
| 612 | <string>markup.raw.code.fenced.mdx</string> | |
| 613 | <key>end</key> | |
| 614 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 615 | <key>endCaptures</key> | |
| 616 | <dict> | |
| 617 | <key>1</key> | |
| 618 | <dict> | |
| 619 | <key>name</key> | |
| 620 | <string>string.other.end.code.fenced.mdx</string> | |
| 621 | </dict> | |
| 622 | </dict> | |
| 623 | <key>name</key> | |
| 624 | <string>markup.code.other.mdx</string> | |
| 625 | </dict> | |
| 626 | <dict> | |
| 627 | <key>begin</key> | |
| 628 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?:[^\t\n\r ])+)(?:[\t ]+((?:[^\n\r])+))?)?(?:[\t ]*$)</string> | |
| 629 | <key>beginCaptures</key> | |
| 630 | <dict> | |
| 631 | <key>1</key> | |
| 632 | <dict> | |
| 633 | <key>name</key> | |
| 634 | <string>string.other.begin.code.fenced.mdx</string> | |
| 635 | </dict> | |
| 636 | <key>2</key> | |
| 637 | <dict> | |
| 638 | <key>name</key> | |
| 639 | <string>entity.name.function.mdx</string> | |
| 640 | <key>patterns</key> | |
| 641 | <array> | |
| 642 | <dict> | |
| 643 | <key>include</key> | |
| 644 | <string>#markdown-string</string> | |
| 645 | </dict> | |
| 646 | </array> | |
| 647 | </dict> | |
| 648 | <key>3</key> | |
| 649 | <dict> | |
| 650 | <key>patterns</key> | |
| 651 | <array> | |
| 652 | <dict> | |
| 653 | <key>include</key> | |
| 654 | <string>#markdown-string</string> | |
| 655 | </dict> | |
| 656 | </array> | |
| 657 | </dict> | |
| 658 | </dict> | |
| 659 | <key>contentName</key> | |
| 660 | <string>markup.raw.code.fenced.mdx</string> | |
| 661 | <key>end</key> | |
| 662 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 663 | <key>endCaptures</key> | |
| 664 | <dict> | |
| 665 | <key>1</key> | |
| 666 | <dict> | |
| 667 | <key>name</key> | |
| 668 | <string>string.other.end.code.fenced.mdx</string> | |
| 669 | </dict> | |
| 670 | </dict> | |
| 671 | <key>name</key> | |
| 672 | <string>markup.code.other.mdx</string> | |
| 673 | </dict> | |
| 674 | </array> | |
| 675 | </dict> | |
| 676 | <key>commonmark-code-text</key> | |
| 677 | <dict> | |
| 678 | <key>match</key> | |
| 679 | <string>(?&lt;!`)(`+)(?!`)(.+?)(?&lt;!`)(\1)(?!`)</string> | |
| 680 | <key>name</key> | |
| 681 | <string>markup.code.other.mdx</string> | |
| 682 | <key>captures</key> | |
| 683 | <dict> | |
| 684 | <key>1</key> | |
| 685 | <dict> | |
| 686 | <key>name</key> | |
| 687 | <string>string.other.begin.code.mdx</string> | |
| 688 | </dict> | |
| 689 | <key>2</key> | |
| 690 | <dict> | |
| 691 | <key>name</key> | |
| 692 | <string>markup.raw.code.mdx markup.inline.raw.code.mdx</string> | |
| 693 | </dict> | |
| 694 | <key>3</key> | |
| 695 | <dict> | |
| 696 | <key>name</key> | |
| 697 | <string>string.other.end.code.mdx</string> | |
| 698 | </dict> | |
| 699 | </dict> | |
| 700 | </dict> | |
| 701 | <key>commonmark-definition</key> | |
| 702 | <dict> | |
| 703 | <key>match</key> | |
| 704 | <string>(?:^|\G)[\t ]*(\[)((?:[^\[\\\]]|\\[\[\\\]]?)+?)(\])(:)[ \t]*(?:(&lt;)((?:[^\n&lt;\\&gt;]|\\[&lt;\\&gt;]?)*)(&gt;)|(\g&lt;destination_raw&gt;))(?:[\t ]+(?:(")((?:[^"\\]|\\["\\]?)*)(")|(')((?:[^'\\]|\\['\\]?)*)(')|(\()((?:[^\)\\]|\\[\)\\]?)*)(\))))?$(?&lt;destination_raw&gt;(?!\&lt;)(?:(?:[^\p{Cc}\ \\\(\)]|\\[\(\)\\]?)|\(\g&lt;destination_raw&gt;*\))+){0}</string> | |
| 705 | <key>name</key> | |
| 706 | <string>meta.link.reference.def.mdx</string> | |
| 707 | <key>captures</key> | |
| 708 | <dict> | |
| 709 | <key>1</key> | |
| 710 | <dict> | |
| 711 | <key>name</key> | |
| 712 | <string>string.other.begin.mdx</string> | |
| 713 | </dict> | |
| 714 | <key>2</key> | |
| 715 | <dict> | |
| 716 | <key>name</key> | |
| 717 | <string>entity.name.identifier.mdx</string> | |
| 718 | <key>patterns</key> | |
| 719 | <array> | |
| 720 | <dict> | |
| 721 | <key>include</key> | |
| 722 | <string>#markdown-string</string> | |
| 723 | </dict> | |
| 724 | </array> | |
| 725 | </dict> | |
| 726 | <key>3</key> | |
| 727 | <dict> | |
| 728 | <key>name</key> | |
| 729 | <string>string.other.end.mdx</string> | |
| 730 | </dict> | |
| 731 | <key>4</key> | |
| 732 | <dict> | |
| 733 | <key>name</key> | |
| 734 | <string>punctuation.separator.key-value.mdx</string> | |
| 735 | </dict> | |
| 736 | <key>5</key> | |
| 737 | <dict> | |
| 738 | <key>name</key> | |
| 739 | <string>string.other.begin.destination.mdx</string> | |
| 740 | </dict> | |
| 741 | <key>6</key> | |
| 742 | <dict> | |
| 743 | <key>name</key> | |
| 744 | <string>string.other.link.destination.mdx</string> | |
| 745 | <key>patterns</key> | |
| 746 | <array> | |
| 747 | <dict> | |
| 748 | <key>include</key> | |
| 749 | <string>#markdown-string</string> | |
| 750 | </dict> | |
| 751 | </array> | |
| 752 | </dict> | |
| 753 | <key>7</key> | |
| 754 | <dict> | |
| 755 | <key>name</key> | |
| 756 | <string>string.other.end.destination.mdx</string> | |
| 757 | </dict> | |
| 758 | <key>8</key> | |
| 759 | <dict> | |
| 760 | <key>name</key> | |
| 761 | <string>string.other.link.destination.mdx</string> | |
| 762 | <key>patterns</key> | |
| 763 | <array> | |
| 764 | <dict> | |
| 765 | <key>include</key> | |
| 766 | <string>#markdown-string</string> | |
| 767 | </dict> | |
| 768 | </array> | |
| 769 | </dict> | |
| 770 | <key>9</key> | |
| 771 | <dict> | |
| 772 | <key>name</key> | |
| 773 | <string>string.other.begin.mdx</string> | |
| 774 | </dict> | |
| 775 | <key>10</key> | |
| 776 | <dict> | |
| 777 | <key>name</key> | |
| 778 | <string>string.quoted.double.mdx</string> | |
| 779 | <key>patterns</key> | |
| 780 | <array> | |
| 781 | <dict> | |
| 782 | <key>include</key> | |
| 783 | <string>#markdown-string</string> | |
| 784 | </dict> | |
| 785 | </array> | |
| 786 | </dict> | |
| 787 | <key>11</key> | |
| 788 | <dict> | |
| 789 | <key>name</key> | |
| 790 | <string>string.other.end.mdx</string> | |
| 791 | </dict> | |
| 792 | <key>12</key> | |
| 793 | <dict> | |
| 794 | <key>name</key> | |
| 795 | <string>string.other.begin.mdx</string> | |
| 796 | </dict> | |
| 797 | <key>13</key> | |
| 798 | <dict> | |
| 799 | <key>name</key> | |
| 800 | <string>string.quoted.single.mdx</string> | |
| 801 | <key>patterns</key> | |
| 802 | <array> | |
| 803 | <dict> | |
| 804 | <key>include</key> | |
| 805 | <string>#markdown-string</string> | |
| 806 | </dict> | |
| 807 | </array> | |
| 808 | </dict> | |
| 809 | <key>14</key> | |
| 810 | <dict> | |
| 811 | <key>name</key> | |
| 812 | <string>string.other.end.mdx</string> | |
| 813 | </dict> | |
| 814 | <key>15</key> | |
| 815 | <dict> | |
| 816 | <key>name</key> | |
| 817 | <string>string.other.begin.mdx</string> | |
| 818 | </dict> | |
| 819 | <key>16</key> | |
| 820 | <dict> | |
| 821 | <key>name</key> | |
| 822 | <string>string.quoted.paren.mdx</string> | |
| 823 | <key>patterns</key> | |
| 824 | <array> | |
| 825 | <dict> | |
| 826 | <key>include</key> | |
| 827 | <string>#markdown-string</string> | |
| 828 | </dict> | |
| 829 | </array> | |
| 830 | </dict> | |
| 831 | <key>17</key> | |
| 832 | <dict> | |
| 833 | <key>name</key> | |
| 834 | <string>string.other.end.mdx</string> | |
| 835 | </dict> | |
| 836 | </dict> | |
| 837 | </dict> | |
| 838 | <key>commonmark-hard-break-escape</key> | |
| 839 | <dict> | |
| 840 | <key>match</key> | |
| 841 | <string>\\$</string> | |
| 842 | <key>name</key> | |
| 843 | <string>constant.language.character-escape.line-ending.mdx</string> | |
| 844 | </dict> | |
| 845 | <key>commonmark-hard-break-trailing</key> | |
| 846 | <dict> | |
| 847 | <key>match</key> | |
| 848 | <string>( ){2,}$</string> | |
| 849 | <key>name</key> | |
| 850 | <string>carriage-return constant.language.character-escape.line-ending.mdx</string> | |
| 851 | </dict> | |
| 852 | <key>commonmark-heading-atx</key> | |
| 853 | <dict> | |
| 854 | <key>patterns</key> | |
| 855 | <array> | |
| 856 | <dict> | |
| 857 | <key>match</key> | |
| 858 | <string>(?:^|\G)[\t ]*(#{1}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 859 | <key>name</key> | |
| 860 | <string>markup.heading.atx.1.mdx</string> | |
| 861 | <key>captures</key> | |
| 862 | <dict> | |
| 863 | <key>1</key> | |
| 864 | <dict> | |
| 865 | <key>name</key> | |
| 866 | <string>punctuation.definition.heading.mdx</string> | |
| 867 | </dict> | |
| 868 | <key>2</key> | |
| 869 | <dict> | |
| 870 | <key>name</key> | |
| 871 | <string>entity.name.section.mdx</string> | |
| 872 | <key>patterns</key> | |
| 873 | <array> | |
| 874 | <dict> | |
| 875 | <key>include</key> | |
| 876 | <string>#markdown-text</string> | |
| 877 | </dict> | |
| 878 | </array> | |
| 879 | </dict> | |
| 880 | <key>3</key> | |
| 881 | <dict> | |
| 882 | <key>name</key> | |
| 883 | <string>punctuation.definition.heading.mdx</string> | |
| 884 | </dict> | |
| 885 | </dict> | |
| 886 | </dict> | |
| 887 | <dict> | |
| 888 | <key>match</key> | |
| 889 | <string>(?:^|\G)[\t ]*(#{2}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 890 | <key>name</key> | |
| 891 | <string>markup.heading.atx.2.mdx</string> | |
| 892 | <key>captures</key> | |
| 893 | <dict> | |
| 894 | <key>1</key> | |
| 895 | <dict> | |
| 896 | <key>name</key> | |
| 897 | <string>punctuation.definition.heading.mdx</string> | |
| 898 | </dict> | |
| 899 | <key>2</key> | |
| 900 | <dict> | |
| 901 | <key>name</key> | |
| 902 | <string>entity.name.section.mdx</string> | |
| 903 | <key>patterns</key> | |
| 904 | <array> | |
| 905 | <dict> | |
| 906 | <key>include</key> | |
| 907 | <string>#markdown-text</string> | |
| 908 | </dict> | |
| 909 | </array> | |
| 910 | </dict> | |
| 911 | <key>3</key> | |
| 912 | <dict> | |
| 913 | <key>name</key> | |
| 914 | <string>punctuation.definition.heading.mdx</string> | |
| 915 | </dict> | |
| 916 | </dict> | |
| 917 | </dict> | |
| 918 | <dict> | |
| 919 | <key>match</key> | |
| 920 | <string>(?:^|\G)[\t ]*(#{3}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 921 | <key>name</key> | |
| 922 | <string>markup.heading.atx.3.mdx</string> | |
| 923 | <key>captures</key> | |
| 924 | <dict> | |
| 925 | <key>1</key> | |
| 926 | <dict> | |
| 927 | <key>name</key> | |
| 928 | <string>punctuation.definition.heading.mdx</string> | |
| 929 | </dict> | |
| 930 | <key>2</key> | |
| 931 | <dict> | |
| 932 | <key>name</key> | |
| 933 | <string>entity.name.section.mdx</string> | |
| 934 | <key>patterns</key> | |
| 935 | <array> | |
| 936 | <dict> | |
| 937 | <key>include</key> | |
| 938 | <string>#markdown-text</string> | |
| 939 | </dict> | |
| 940 | </array> | |
| 941 | </dict> | |
| 942 | <key>3</key> | |
| 943 | <dict> | |
| 944 | <key>name</key> | |
| 945 | <string>punctuation.definition.heading.mdx</string> | |
| 946 | </dict> | |
| 947 | </dict> | |
| 948 | </dict> | |
| 949 | <dict> | |
| 950 | <key>match</key> | |
| 951 | <string>(?:^|\G)[\t ]*(#{4}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 952 | <key>name</key> | |
| 953 | <string>markup.heading.atx.4.mdx</string> | |
| 954 | <key>captures</key> | |
| 955 | <dict> | |
| 956 | <key>1</key> | |
| 957 | <dict> | |
| 958 | <key>name</key> | |
| 959 | <string>punctuation.definition.heading.mdx</string> | |
| 960 | </dict> | |
| 961 | <key>2</key> | |
| 962 | <dict> | |
| 963 | <key>name</key> | |
| 964 | <string>entity.name.section.mdx</string> | |
| 965 | <key>patterns</key> | |
| 966 | <array> | |
| 967 | <dict> | |
| 968 | <key>include</key> | |
| 969 | <string>#markdown-text</string> | |
| 970 | </dict> | |
| 971 | </array> | |
| 972 | </dict> | |
| 973 | <key>3</key> | |
| 974 | <dict> | |
| 975 | <key>name</key> | |
| 976 | <string>punctuation.definition.heading.mdx</string> | |
| 977 | </dict> | |
| 978 | </dict> | |
| 979 | </dict> | |
| 980 | <dict> | |
| 981 | <key>match</key> | |
| 982 | <string>(?:^|\G)[\t ]*(#{5}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 983 | <key>name</key> | |
| 984 | <string>markup.heading.atx.5.mdx</string> | |
| 985 | <key>captures</key> | |
| 986 | <dict> | |
| 987 | <key>1</key> | |
| 988 | <dict> | |
| 989 | <key>name</key> | |
| 990 | <string>punctuation.definition.heading.mdx</string> | |
| 991 | </dict> | |
| 992 | <key>2</key> | |
| 993 | <dict> | |
| 994 | <key>name</key> | |
| 995 | <string>entity.name.section.mdx</string> | |
| 996 | <key>patterns</key> | |
| 997 | <array> | |
| 998 | <dict> | |
| 999 | <key>include</key> | |
| 1000 | <string>#markdown-text</string> | |
| 1001 | </dict> | |
| 1002 | </array> | |
| 1003 | </dict> | |
| 1004 | <key>3</key> | |
| 1005 | <dict> | |
| 1006 | <key>name</key> | |
| 1007 | <string>punctuation.definition.heading.mdx</string> | |
| 1008 | </dict> | |
| 1009 | </dict> | |
| 1010 | </dict> | |
| 1011 | <dict> | |
| 1012 | <key>match</key> | |
| 1013 | <string>(?:^|\G)[\t ]*(#{6}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string> | |
| 1014 | <key>name</key> | |
| 1015 | <string>markup.heading.atx.6.mdx</string> | |
| 1016 | <key>captures</key> | |
| 1017 | <dict> | |
| 1018 | <key>1</key> | |
| 1019 | <dict> | |
| 1020 | <key>name</key> | |
| 1021 | <string>punctuation.definition.heading.mdx</string> | |
| 1022 | </dict> | |
| 1023 | <key>2</key> | |
| 1024 | <dict> | |
| 1025 | <key>name</key> | |
| 1026 | <string>entity.name.section.mdx</string> | |
| 1027 | <key>patterns</key> | |
| 1028 | <array> | |
| 1029 | <dict> | |
| 1030 | <key>include</key> | |
| 1031 | <string>#markdown-text</string> | |
| 1032 | </dict> | |
| 1033 | </array> | |
| 1034 | </dict> | |
| 1035 | <key>3</key> | |
| 1036 | <dict> | |
| 1037 | <key>name</key> | |
| 1038 | <string>punctuation.definition.heading.mdx</string> | |
| 1039 | </dict> | |
| 1040 | </dict> | |
| 1041 | </dict> | |
| 1042 | </array> | |
| 1043 | </dict> | |
| 1044 | <key>commonmark-heading-setext</key> | |
| 1045 | <dict> | |
| 1046 | <key>patterns</key> | |
| 1047 | <array> | |
| 1048 | <dict> | |
| 1049 | <key>match</key> | |
| 1050 | <string>(?:^|\G)[\t ]*(={1,})[ \t]*$</string> | |
| 1051 | <key>name</key> | |
| 1052 | <string>markup.heading.setext.1.mdx</string> | |
| 1053 | </dict> | |
| 1054 | <dict> | |
| 1055 | <key>match</key> | |
| 1056 | <string>(?:^|\G)[\t ]*(-{1,})[ \t]*$</string> | |
| 1057 | <key>name</key> | |
| 1058 | <string>markup.heading.setext.2.mdx</string> | |
| 1059 | </dict> | |
| 1060 | </array> | |
| 1061 | </dict> | |
| 1062 | <key>commonmark-label-end</key> | |
| 1063 | <dict> | |
| 1064 | <key>patterns</key> | |
| 1065 | <array> | |
| 1066 | <dict> | |
| 1067 | <key>match</key> | |
| 1068 | <string>(\])(\()[\t ]*(?:(?:(&lt;)((?:[^\n&lt;\\&gt;]|\\[&lt;\\&gt;]?)*)(&gt;)|(\g&lt;destination_raw&gt;))(?:[\t ]+(?:(")((?:[^"\\]|\\["\\]?)*)(")|(')((?:[^'\\]|\\['\\]?)*)(')|(\()((?:[^\)\\]|\\[\)\\]?)*)(\))))?)?[\t ]*(\))(?&lt;destination_raw&gt;(?!\&lt;)(?:(?:[^\p{Cc}\ \\\(\)]|\\[\(\)\\]?)|\(\g&lt;destination_raw&gt;*\))+){0}</string> | |
| 1069 | <key>captures</key> | |
| 1070 | <dict> | |
| 1071 | <key>1</key> | |
| 1072 | <dict> | |
| 1073 | <key>name</key> | |
| 1074 | <string>string.other.end.mdx</string> | |
| 1075 | </dict> | |
| 1076 | <key>2</key> | |
| 1077 | <dict> | |
| 1078 | <key>name</key> | |
| 1079 | <string>string.other.begin.mdx</string> | |
| 1080 | </dict> | |
| 1081 | <key>3</key> | |
| 1082 | <dict> | |
| 1083 | <key>name</key> | |
| 1084 | <string>string.other.begin.destination.mdx</string> | |
| 1085 | </dict> | |
| 1086 | <key>4</key> | |
| 1087 | <dict> | |
| 1088 | <key>name</key> | |
| 1089 | <string>string.other.link.destination.mdx</string> | |
| 1090 | <key>patterns</key> | |
| 1091 | <array> | |
| 1092 | <dict> | |
| 1093 | <key>include</key> | |
| 1094 | <string>#markdown-string</string> | |
| 1095 | </dict> | |
| 1096 | </array> | |
| 1097 | </dict> | |
| 1098 | <key>5</key> | |
| 1099 | <dict> | |
| 1100 | <key>name</key> | |
| 1101 | <string>string.other.end.destination.mdx</string> | |
| 1102 | </dict> | |
| 1103 | <key>6</key> | |
| 1104 | <dict> | |
| 1105 | <key>name</key> | |
| 1106 | <string>string.other.link.destination.mdx</string> | |
| 1107 | <key>patterns</key> | |
| 1108 | <array> | |
| 1109 | <dict> | |
| 1110 | <key>include</key> | |
| 1111 | <string>#markdown-string</string> | |
| 1112 | </dict> | |
| 1113 | </array> | |
| 1114 | </dict> | |
| 1115 | <key>7</key> | |
| 1116 | <dict> | |
| 1117 | <key>name</key> | |
| 1118 | <string>string.other.begin.mdx</string> | |
| 1119 | </dict> | |
| 1120 | <key>8</key> | |
| 1121 | <dict> | |
| 1122 | <key>name</key> | |
| 1123 | <string>string.quoted.double.mdx</string> | |
| 1124 | <key>patterns</key> | |
| 1125 | <array> | |
| 1126 | <dict> | |
| 1127 | <key>include</key> | |
| 1128 | <string>#markdown-string</string> | |
| 1129 | </dict> | |
| 1130 | </array> | |
| 1131 | </dict> | |
| 1132 | <key>9</key> | |
| 1133 | <dict> | |
| 1134 | <key>name</key> | |
| 1135 | <string>string.other.end.mdx</string> | |
| 1136 | </dict> | |
| 1137 | <key>10</key> | |
| 1138 | <dict> | |
| 1139 | <key>name</key> | |
| 1140 | <string>string.other.begin.mdx</string> | |
| 1141 | </dict> | |
| 1142 | <key>11</key> | |
| 1143 | <dict> | |
| 1144 | <key>name</key> | |
| 1145 | <string>string.quoted.single.mdx</string> | |
| 1146 | <key>patterns</key> | |
| 1147 | <array> | |
| 1148 | <dict> | |
| 1149 | <key>include</key> | |
| 1150 | <string>#markdown-string</string> | |
| 1151 | </dict> | |
| 1152 | </array> | |
| 1153 | </dict> | |
| 1154 | <key>12</key> | |
| 1155 | <dict> | |
| 1156 | <key>name</key> | |
| 1157 | <string>string.other.end.mdx</string> | |
| 1158 | </dict> | |
| 1159 | <key>13</key> | |
| 1160 | <dict> | |
| 1161 | <key>name</key> | |
| 1162 | <string>string.other.begin.mdx</string> | |
| 1163 | </dict> | |
| 1164 | <key>14</key> | |
| 1165 | <dict> | |
| 1166 | <key>name</key> | |
| 1167 | <string>string.quoted.paren.mdx</string> | |
| 1168 | <key>patterns</key> | |
| 1169 | <array> | |
| 1170 | <dict> | |
| 1171 | <key>include</key> | |
| 1172 | <string>#markdown-string</string> | |
| 1173 | </dict> | |
| 1174 | </array> | |
| 1175 | </dict> | |
| 1176 | <key>15</key> | |
| 1177 | <dict> | |
| 1178 | <key>name</key> | |
| 1179 | <string>string.other.end.mdx</string> | |
| 1180 | </dict> | |
| 1181 | <key>16</key> | |
| 1182 | <dict> | |
| 1183 | <key>name</key> | |
| 1184 | <string>string.other.end.mdx</string> | |
| 1185 | </dict> | |
| 1186 | </dict> | |
| 1187 | </dict> | |
| 1188 | <dict> | |
| 1189 | <key>match</key> | |
| 1190 | <string>(\])(\[)((?:[^\[\\\]]|\\[\[\\\]]?)+?)(\])</string> | |
| 1191 | <key>captures</key> | |
| 1192 | <dict> | |
| 1193 | <key>1</key> | |
| 1194 | <dict> | |
| 1195 | <key>name</key> | |
| 1196 | <string>string.other.end.mdx</string> | |
| 1197 | </dict> | |
| 1198 | <key>2</key> | |
| 1199 | <dict> | |
| 1200 | <key>name</key> | |
| 1201 | <string>string.other.begin.mdx</string> | |
| 1202 | </dict> | |
| 1203 | <key>3</key> | |
| 1204 | <dict> | |
| 1205 | <key>name</key> | |
| 1206 | <string>entity.name.identifier.mdx</string> | |
| 1207 | <key>patterns</key> | |
| 1208 | <array> | |
| 1209 | <dict> | |
| 1210 | <key>include</key> | |
| 1211 | <string>#markdown-string</string> | |
| 1212 | </dict> | |
| 1213 | </array> | |
| 1214 | </dict> | |
| 1215 | <key>4</key> | |
| 1216 | <dict> | |
| 1217 | <key>name</key> | |
| 1218 | <string>string.other.end.mdx</string> | |
| 1219 | </dict> | |
| 1220 | </dict> | |
| 1221 | </dict> | |
| 1222 | <dict> | |
| 1223 | <key>match</key> | |
| 1224 | <string>(\])</string> | |
| 1225 | <key>captures</key> | |
| 1226 | <dict> | |
| 1227 | <key>1</key> | |
| 1228 | <dict> | |
| 1229 | <key>name</key> | |
| 1230 | <string>string.other.end.mdx</string> | |
| 1231 | </dict> | |
| 1232 | </dict> | |
| 1233 | </dict> | |
| 1234 | </array> | |
| 1235 | </dict> | |
| 1236 | <key>commonmark-label-start</key> | |
| 1237 | <dict> | |
| 1238 | <key>patterns</key> | |
| 1239 | <array> | |
| 1240 | <dict> | |
| 1241 | <key>match</key> | |
| 1242 | <string>\!\[(?!\^)</string> | |
| 1243 | <key>name</key> | |
| 1244 | <string>string.other.begin.image.mdx</string> | |
| 1245 | </dict> | |
| 1246 | <dict> | |
| 1247 | <key>match</key> | |
| 1248 | <string>\[</string> | |
| 1249 | <key>name</key> | |
| 1250 | <string>string.other.begin.link.mdx</string> | |
| 1251 | </dict> | |
| 1252 | </array> | |
| 1253 | </dict> | |
| 1254 | <key>commonmark-list-item</key> | |
| 1255 | <dict> | |
| 1256 | <key>patterns</key> | |
| 1257 | <array> | |
| 1258 | <dict> | |
| 1259 | <key>begin</key> | |
| 1260 | <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{4}(?![ ])|\t)(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1261 | <key>beginCaptures</key> | |
| 1262 | <dict> | |
| 1263 | <key>1</key> | |
| 1264 | <dict> | |
| 1265 | <key>name</key> | |
| 1266 | <string>variable.unordered.list.mdx</string> | |
| 1267 | </dict> | |
| 1268 | <key>2</key> | |
| 1269 | <dict> | |
| 1270 | <key>name</key> | |
| 1271 | <string>keyword.other.tasklist.mdx</string> | |
| 1272 | </dict> | |
| 1273 | </dict> | |
| 1274 | <key>patterns</key> | |
| 1275 | <array> | |
| 1276 | <dict> | |
| 1277 | <key>include</key> | |
| 1278 | <string>#markdown-sections</string> | |
| 1279 | </dict> | |
| 1280 | </array> | |
| 1281 | <key>while</key> | |
| 1282 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{1}</string> | |
| 1283 | </dict> | |
| 1284 | <dict> | |
| 1285 | <key>begin</key> | |
| 1286 | <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{3}(?![ ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1287 | <key>beginCaptures</key> | |
| 1288 | <dict> | |
| 1289 | <key>1</key> | |
| 1290 | <dict> | |
| 1291 | <key>name</key> | |
| 1292 | <string>variable.unordered.list.mdx</string> | |
| 1293 | </dict> | |
| 1294 | <key>2</key> | |
| 1295 | <dict> | |
| 1296 | <key>name</key> | |
| 1297 | <string>keyword.other.tasklist.mdx</string> | |
| 1298 | </dict> | |
| 1299 | </dict> | |
| 1300 | <key>patterns</key> | |
| 1301 | <array> | |
| 1302 | <dict> | |
| 1303 | <key>include</key> | |
| 1304 | <string>#markdown-sections</string> | |
| 1305 | </dict> | |
| 1306 | </array> | |
| 1307 | <key>while</key> | |
| 1308 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string> | |
| 1309 | </dict> | |
| 1310 | <dict> | |
| 1311 | <key>begin</key> | |
| 1312 | <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{2}(?![ ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1313 | <key>beginCaptures</key> | |
| 1314 | <dict> | |
| 1315 | <key>1</key> | |
| 1316 | <dict> | |
| 1317 | <key>name</key> | |
| 1318 | <string>variable.unordered.list.mdx</string> | |
| 1319 | </dict> | |
| 1320 | <key>2</key> | |
| 1321 | <dict> | |
| 1322 | <key>name</key> | |
| 1323 | <string>keyword.other.tasklist.mdx</string> | |
| 1324 | </dict> | |
| 1325 | </dict> | |
| 1326 | <key>patterns</key> | |
| 1327 | <array> | |
| 1328 | <dict> | |
| 1329 | <key>include</key> | |
| 1330 | <string>#markdown-sections</string> | |
| 1331 | </dict> | |
| 1332 | </array> | |
| 1333 | <key>while</key> | |
| 1334 | <string>^(?=[\t ]*$)|(?:^|\G)[ ]{3}</string> | |
| 1335 | </dict> | |
| 1336 | <dict> | |
| 1337 | <key>begin</key> | |
| 1338 | <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{1}|(?=\n))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1339 | <key>beginCaptures</key> | |
| 1340 | <dict> | |
| 1341 | <key>1</key> | |
| 1342 | <dict> | |
| 1343 | <key>name</key> | |
| 1344 | <string>variable.unordered.list.mdx</string> | |
| 1345 | </dict> | |
| 1346 | <key>2</key> | |
| 1347 | <dict> | |
| 1348 | <key>name</key> | |
| 1349 | <string>keyword.other.tasklist.mdx</string> | |
| 1350 | </dict> | |
| 1351 | </dict> | |
| 1352 | <key>patterns</key> | |
| 1353 | <array> | |
| 1354 | <dict> | |
| 1355 | <key>include</key> | |
| 1356 | <string>#markdown-sections</string> | |
| 1357 | </dict> | |
| 1358 | </array> | |
| 1359 | <key>while</key> | |
| 1360 | <string>^(?=[\t ]*$)|(?:^|\G)[ ]{2}</string> | |
| 1361 | </dict> | |
| 1362 | <dict> | |
| 1363 | <key>begin</key> | |
| 1364 | <string>(?:^|\G)[\t ]*([0-9]{9})((?:\.|\)))(?:[ ]{4}(?![ ])|\t(?![\t ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1365 | <key>beginCaptures</key> | |
| 1366 | <dict> | |
| 1367 | <key>1</key> | |
| 1368 | <dict> | |
| 1369 | <key>name</key> | |
| 1370 | <string>string.other.number.mdx</string> | |
| 1371 | </dict> | |
| 1372 | <key>2</key> | |
| 1373 | <dict> | |
| 1374 | <key>name</key> | |
| 1375 | <string>variable.ordered.list.mdx</string> | |
| 1376 | </dict> | |
| 1377 | <key>3</key> | |
| 1378 | <dict> | |
| 1379 | <key>name</key> | |
| 1380 | <string>keyword.other.tasklist.mdx</string> | |
| 1381 | </dict> | |
| 1382 | </dict> | |
| 1383 | <key>patterns</key> | |
| 1384 | <array> | |
| 1385 | <dict> | |
| 1386 | <key>include</key> | |
| 1387 | <string>#markdown-sections</string> | |
| 1388 | </dict> | |
| 1389 | </array> | |
| 1390 | <key>while</key> | |
| 1391 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}[ ]{2}</string> | |
| 1392 | </dict> | |
| 1393 | <dict> | |
| 1394 | <key>begin</key> | |
| 1395 | <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{8})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1396 | <key>beginCaptures</key> | |
| 1397 | <dict> | |
| 1398 | <key>1</key> | |
| 1399 | <dict> | |
| 1400 | <key>name</key> | |
| 1401 | <string>string.other.number.mdx</string> | |
| 1402 | </dict> | |
| 1403 | <key>2</key> | |
| 1404 | <dict> | |
| 1405 | <key>name</key> | |
| 1406 | <string>variable.ordered.list.mdx</string> | |
| 1407 | </dict> | |
| 1408 | <key>3</key> | |
| 1409 | <dict> | |
| 1410 | <key>name</key> | |
| 1411 | <string>string.other.number.mdx</string> | |
| 1412 | </dict> | |
| 1413 | <key>4</key> | |
| 1414 | <dict> | |
| 1415 | <key>name</key> | |
| 1416 | <string>variable.ordered.list.mdx</string> | |
| 1417 | </dict> | |
| 1418 | <key>5</key> | |
| 1419 | <dict> | |
| 1420 | <key>name</key> | |
| 1421 | <string>keyword.other.tasklist.mdx</string> | |
| 1422 | </dict> | |
| 1423 | </dict> | |
| 1424 | <key>patterns</key> | |
| 1425 | <array> | |
| 1426 | <dict> | |
| 1427 | <key>include</key> | |
| 1428 | <string>#markdown-sections</string> | |
| 1429 | </dict> | |
| 1430 | </array> | |
| 1431 | <key>while</key> | |
| 1432 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}[ ]{1}</string> | |
| 1433 | </dict> | |
| 1434 | <dict> | |
| 1435 | <key>begin</key> | |
| 1436 | <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{8})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{7})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1437 | <key>beginCaptures</key> | |
| 1438 | <dict> | |
| 1439 | <key>1</key> | |
| 1440 | <dict> | |
| 1441 | <key>name</key> | |
| 1442 | <string>string.other.number.mdx</string> | |
| 1443 | </dict> | |
| 1444 | <key>2</key> | |
| 1445 | <dict> | |
| 1446 | <key>name</key> | |
| 1447 | <string>variable.ordered.list.mdx</string> | |
| 1448 | </dict> | |
| 1449 | <key>3</key> | |
| 1450 | <dict> | |
| 1451 | <key>name</key> | |
| 1452 | <string>string.other.number.mdx</string> | |
| 1453 | </dict> | |
| 1454 | <key>4</key> | |
| 1455 | <dict> | |
| 1456 | <key>name</key> | |
| 1457 | <string>variable.ordered.list.mdx</string> | |
| 1458 | </dict> | |
| 1459 | <key>5</key> | |
| 1460 | <dict> | |
| 1461 | <key>name</key> | |
| 1462 | <string>string.other.number.mdx</string> | |
| 1463 | </dict> | |
| 1464 | <key>6</key> | |
| 1465 | <dict> | |
| 1466 | <key>name</key> | |
| 1467 | <string>variable.ordered.list.mdx</string> | |
| 1468 | </dict> | |
| 1469 | <key>7</key> | |
| 1470 | <dict> | |
| 1471 | <key>name</key> | |
| 1472 | <string>keyword.other.tasklist.mdx</string> | |
| 1473 | </dict> | |
| 1474 | </dict> | |
| 1475 | <key>patterns</key> | |
| 1476 | <array> | |
| 1477 | <dict> | |
| 1478 | <key>include</key> | |
| 1479 | <string>#markdown-sections</string> | |
| 1480 | </dict> | |
| 1481 | </array> | |
| 1482 | <key>while</key> | |
| 1483 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}</string> | |
| 1484 | </dict> | |
| 1485 | <dict> | |
| 1486 | <key>begin</key> | |
| 1487 | <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{8})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{7})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{6})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1488 | <key>beginCaptures</key> | |
| 1489 | <dict> | |
| 1490 | <key>1</key> | |
| 1491 | <dict> | |
| 1492 | <key>name</key> | |
| 1493 | <string>string.other.number.mdx</string> | |
| 1494 | </dict> | |
| 1495 | <key>2</key> | |
| 1496 | <dict> | |
| 1497 | <key>name</key> | |
| 1498 | <string>variable.ordered.list.mdx</string> | |
| 1499 | </dict> | |
| 1500 | <key>3</key> | |
| 1501 | <dict> | |
| 1502 | <key>name</key> | |
| 1503 | <string>string.other.number.mdx</string> | |
| 1504 | </dict> | |
| 1505 | <key>4</key> | |
| 1506 | <dict> | |
| 1507 | <key>name</key> | |
| 1508 | <string>variable.ordered.list.mdx</string> | |
| 1509 | </dict> | |
| 1510 | <key>5</key> | |
| 1511 | <dict> | |
| 1512 | <key>name</key> | |
| 1513 | <string>string.other.number.mdx</string> | |
| 1514 | </dict> | |
| 1515 | <key>6</key> | |
| 1516 | <dict> | |
| 1517 | <key>name</key> | |
| 1518 | <string>variable.ordered.list.mdx</string> | |
| 1519 | </dict> | |
| 1520 | <key>7</key> | |
| 1521 | <dict> | |
| 1522 | <key>name</key> | |
| 1523 | <string>string.other.number.mdx</string> | |
| 1524 | </dict> | |
| 1525 | <key>8</key> | |
| 1526 | <dict> | |
| 1527 | <key>name</key> | |
| 1528 | <string>variable.ordered.list.mdx</string> | |
| 1529 | </dict> | |
| 1530 | <key>9</key> | |
| 1531 | <dict> | |
| 1532 | <key>name</key> | |
| 1533 | <string>keyword.other.tasklist.mdx</string> | |
| 1534 | </dict> | |
| 1535 | </dict> | |
| 1536 | <key>patterns</key> | |
| 1537 | <array> | |
| 1538 | <dict> | |
| 1539 | <key>include</key> | |
| 1540 | <string>#markdown-sections</string> | |
| 1541 | </dict> | |
| 1542 | </array> | |
| 1543 | <key>while</key> | |
| 1544 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{3}</string> | |
| 1545 | </dict> | |
| 1546 | <dict> | |
| 1547 | <key>begin</key> | |
| 1548 | <string>(?:^|\G)[\t ]*(?:([0-9]{8})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{7})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{6})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{5})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1549 | <key>beginCaptures</key> | |
| 1550 | <dict> | |
| 1551 | <key>1</key> | |
| 1552 | <dict> | |
| 1553 | <key>name</key> | |
| 1554 | <string>string.other.number.mdx</string> | |
| 1555 | </dict> | |
| 1556 | <key>2</key> | |
| 1557 | <dict> | |
| 1558 | <key>name</key> | |
| 1559 | <string>variable.ordered.list.mdx</string> | |
| 1560 | </dict> | |
| 1561 | <key>3</key> | |
| 1562 | <dict> | |
| 1563 | <key>name</key> | |
| 1564 | <string>string.other.number.mdx</string> | |
| 1565 | </dict> | |
| 1566 | <key>4</key> | |
| 1567 | <dict> | |
| 1568 | <key>name</key> | |
| 1569 | <string>variable.ordered.list.mdx</string> | |
| 1570 | </dict> | |
| 1571 | <key>5</key> | |
| 1572 | <dict> | |
| 1573 | <key>name</key> | |
| 1574 | <string>string.other.number.mdx</string> | |
| 1575 | </dict> | |
| 1576 | <key>6</key> | |
| 1577 | <dict> | |
| 1578 | <key>name</key> | |
| 1579 | <string>variable.ordered.list.mdx</string> | |
| 1580 | </dict> | |
| 1581 | <key>7</key> | |
| 1582 | <dict> | |
| 1583 | <key>name</key> | |
| 1584 | <string>string.other.number.mdx</string> | |
| 1585 | </dict> | |
| 1586 | <key>8</key> | |
| 1587 | <dict> | |
| 1588 | <key>name</key> | |
| 1589 | <string>variable.ordered.list.mdx</string> | |
| 1590 | </dict> | |
| 1591 | <key>9</key> | |
| 1592 | <dict> | |
| 1593 | <key>name</key> | |
| 1594 | <string>keyword.other.tasklist.mdx</string> | |
| 1595 | </dict> | |
| 1596 | </dict> | |
| 1597 | <key>patterns</key> | |
| 1598 | <array> | |
| 1599 | <dict> | |
| 1600 | <key>include</key> | |
| 1601 | <string>#markdown-sections</string> | |
| 1602 | </dict> | |
| 1603 | </array> | |
| 1604 | <key>while</key> | |
| 1605 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{2}</string> | |
| 1606 | </dict> | |
| 1607 | <dict> | |
| 1608 | <key>begin</key> | |
| 1609 | <string>(?:^|\G)[\t ]*(?:([0-9]{7})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{6})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{5})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{4})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1610 | <key>beginCaptures</key> | |
| 1611 | <dict> | |
| 1612 | <key>1</key> | |
| 1613 | <dict> | |
| 1614 | <key>name</key> | |
| 1615 | <string>string.other.number.mdx</string> | |
| 1616 | </dict> | |
| 1617 | <key>2</key> | |
| 1618 | <dict> | |
| 1619 | <key>name</key> | |
| 1620 | <string>variable.ordered.list.mdx</string> | |
| 1621 | </dict> | |
| 1622 | <key>3</key> | |
| 1623 | <dict> | |
| 1624 | <key>name</key> | |
| 1625 | <string>string.other.number.mdx</string> | |
| 1626 | </dict> | |
| 1627 | <key>4</key> | |
| 1628 | <dict> | |
| 1629 | <key>name</key> | |
| 1630 | <string>variable.ordered.list.mdx</string> | |
| 1631 | </dict> | |
| 1632 | <key>5</key> | |
| 1633 | <dict> | |
| 1634 | <key>name</key> | |
| 1635 | <string>string.other.number.mdx</string> | |
| 1636 | </dict> | |
| 1637 | <key>6</key> | |
| 1638 | <dict> | |
| 1639 | <key>name</key> | |
| 1640 | <string>variable.ordered.list.mdx</string> | |
| 1641 | </dict> | |
| 1642 | <key>7</key> | |
| 1643 | <dict> | |
| 1644 | <key>name</key> | |
| 1645 | <string>string.other.number.mdx</string> | |
| 1646 | </dict> | |
| 1647 | <key>8</key> | |
| 1648 | <dict> | |
| 1649 | <key>name</key> | |
| 1650 | <string>variable.ordered.list.mdx</string> | |
| 1651 | </dict> | |
| 1652 | <key>9</key> | |
| 1653 | <dict> | |
| 1654 | <key>name</key> | |
| 1655 | <string>keyword.other.tasklist.mdx</string> | |
| 1656 | </dict> | |
| 1657 | </dict> | |
| 1658 | <key>patterns</key> | |
| 1659 | <array> | |
| 1660 | <dict> | |
| 1661 | <key>include</key> | |
| 1662 | <string>#markdown-sections</string> | |
| 1663 | </dict> | |
| 1664 | </array> | |
| 1665 | <key>while</key> | |
| 1666 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{1}</string> | |
| 1667 | </dict> | |
| 1668 | <dict> | |
| 1669 | <key>begin</key> | |
| 1670 | <string>(?:^|\G)[\t ]*(?:([0-9]{6})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{5})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{4})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{3})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1671 | <key>beginCaptures</key> | |
| 1672 | <dict> | |
| 1673 | <key>1</key> | |
| 1674 | <dict> | |
| 1675 | <key>name</key> | |
| 1676 | <string>string.other.number.mdx</string> | |
| 1677 | </dict> | |
| 1678 | <key>2</key> | |
| 1679 | <dict> | |
| 1680 | <key>name</key> | |
| 1681 | <string>variable.ordered.list.mdx</string> | |
| 1682 | </dict> | |
| 1683 | <key>3</key> | |
| 1684 | <dict> | |
| 1685 | <key>name</key> | |
| 1686 | <string>string.other.number.mdx</string> | |
| 1687 | </dict> | |
| 1688 | <key>4</key> | |
| 1689 | <dict> | |
| 1690 | <key>name</key> | |
| 1691 | <string>variable.ordered.list.mdx</string> | |
| 1692 | </dict> | |
| 1693 | <key>5</key> | |
| 1694 | <dict> | |
| 1695 | <key>name</key> | |
| 1696 | <string>string.other.number.mdx</string> | |
| 1697 | </dict> | |
| 1698 | <key>6</key> | |
| 1699 | <dict> | |
| 1700 | <key>name</key> | |
| 1701 | <string>variable.ordered.list.mdx</string> | |
| 1702 | </dict> | |
| 1703 | <key>7</key> | |
| 1704 | <dict> | |
| 1705 | <key>name</key> | |
| 1706 | <string>string.other.number.mdx</string> | |
| 1707 | </dict> | |
| 1708 | <key>8</key> | |
| 1709 | <dict> | |
| 1710 | <key>name</key> | |
| 1711 | <string>variable.ordered.list.mdx</string> | |
| 1712 | </dict> | |
| 1713 | <key>9</key> | |
| 1714 | <dict> | |
| 1715 | <key>name</key> | |
| 1716 | <string>keyword.other.tasklist.mdx</string> | |
| 1717 | </dict> | |
| 1718 | </dict> | |
| 1719 | <key>patterns</key> | |
| 1720 | <array> | |
| 1721 | <dict> | |
| 1722 | <key>include</key> | |
| 1723 | <string>#markdown-sections</string> | |
| 1724 | </dict> | |
| 1725 | </array> | |
| 1726 | <key>while</key> | |
| 1727 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}</string> | |
| 1728 | </dict> | |
| 1729 | <dict> | |
| 1730 | <key>begin</key> | |
| 1731 | <string>(?:^|\G)[\t ]*(?:([0-9]{5})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{4})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{3})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{2})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1732 | <key>beginCaptures</key> | |
| 1733 | <dict> | |
| 1734 | <key>1</key> | |
| 1735 | <dict> | |
| 1736 | <key>name</key> | |
| 1737 | <string>string.other.number.mdx</string> | |
| 1738 | </dict> | |
| 1739 | <key>2</key> | |
| 1740 | <dict> | |
| 1741 | <key>name</key> | |
| 1742 | <string>variable.ordered.list.mdx</string> | |
| 1743 | </dict> | |
| 1744 | <key>3</key> | |
| 1745 | <dict> | |
| 1746 | <key>name</key> | |
| 1747 | <string>string.other.number.mdx</string> | |
| 1748 | </dict> | |
| 1749 | <key>4</key> | |
| 1750 | <dict> | |
| 1751 | <key>name</key> | |
| 1752 | <string>variable.ordered.list.mdx</string> | |
| 1753 | </dict> | |
| 1754 | <key>5</key> | |
| 1755 | <dict> | |
| 1756 | <key>name</key> | |
| 1757 | <string>string.other.number.mdx</string> | |
| 1758 | </dict> | |
| 1759 | <key>6</key> | |
| 1760 | <dict> | |
| 1761 | <key>name</key> | |
| 1762 | <string>variable.ordered.list.mdx</string> | |
| 1763 | </dict> | |
| 1764 | <key>7</key> | |
| 1765 | <dict> | |
| 1766 | <key>name</key> | |
| 1767 | <string>string.other.number.mdx</string> | |
| 1768 | </dict> | |
| 1769 | <key>8</key> | |
| 1770 | <dict> | |
| 1771 | <key>name</key> | |
| 1772 | <string>variable.ordered.list.mdx</string> | |
| 1773 | </dict> | |
| 1774 | <key>9</key> | |
| 1775 | <dict> | |
| 1776 | <key>name</key> | |
| 1777 | <string>keyword.other.tasklist.mdx</string> | |
| 1778 | </dict> | |
| 1779 | </dict> | |
| 1780 | <key>patterns</key> | |
| 1781 | <array> | |
| 1782 | <dict> | |
| 1783 | <key>include</key> | |
| 1784 | <string>#markdown-sections</string> | |
| 1785 | </dict> | |
| 1786 | </array> | |
| 1787 | <key>while</key> | |
| 1788 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{3}</string> | |
| 1789 | </dict> | |
| 1790 | <dict> | |
| 1791 | <key>begin</key> | |
| 1792 | <string>(?:^|\G)[\t ]*(?:([0-9]{4})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{3})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{2})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{1})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1793 | <key>beginCaptures</key> | |
| 1794 | <dict> | |
| 1795 | <key>1</key> | |
| 1796 | <dict> | |
| 1797 | <key>name</key> | |
| 1798 | <string>string.other.number.mdx</string> | |
| 1799 | </dict> | |
| 1800 | <key>2</key> | |
| 1801 | <dict> | |
| 1802 | <key>name</key> | |
| 1803 | <string>variable.ordered.list.mdx</string> | |
| 1804 | </dict> | |
| 1805 | <key>3</key> | |
| 1806 | <dict> | |
| 1807 | <key>name</key> | |
| 1808 | <string>string.other.number.mdx</string> | |
| 1809 | </dict> | |
| 1810 | <key>4</key> | |
| 1811 | <dict> | |
| 1812 | <key>name</key> | |
| 1813 | <string>variable.ordered.list.mdx</string> | |
| 1814 | </dict> | |
| 1815 | <key>5</key> | |
| 1816 | <dict> | |
| 1817 | <key>name</key> | |
| 1818 | <string>string.other.number.mdx</string> | |
| 1819 | </dict> | |
| 1820 | <key>6</key> | |
| 1821 | <dict> | |
| 1822 | <key>name</key> | |
| 1823 | <string>variable.ordered.list.mdx</string> | |
| 1824 | </dict> | |
| 1825 | <key>7</key> | |
| 1826 | <dict> | |
| 1827 | <key>name</key> | |
| 1828 | <string>string.other.number.mdx</string> | |
| 1829 | </dict> | |
| 1830 | <key>8</key> | |
| 1831 | <dict> | |
| 1832 | <key>name</key> | |
| 1833 | <string>variable.ordered.list.mdx</string> | |
| 1834 | </dict> | |
| 1835 | <key>9</key> | |
| 1836 | <dict> | |
| 1837 | <key>name</key> | |
| 1838 | <string>keyword.other.tasklist.mdx</string> | |
| 1839 | </dict> | |
| 1840 | </dict> | |
| 1841 | <key>patterns</key> | |
| 1842 | <array> | |
| 1843 | <dict> | |
| 1844 | <key>include</key> | |
| 1845 | <string>#markdown-sections</string> | |
| 1846 | </dict> | |
| 1847 | </array> | |
| 1848 | <key>while</key> | |
| 1849 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{2}</string> | |
| 1850 | </dict> | |
| 1851 | <dict> | |
| 1852 | <key>begin</key> | |
| 1853 | <string>(?:^|\G)[\t ]*(?:([0-9]{3})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{2})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{1})((?:\.|\)))(?:[ ]{3}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1854 | <key>beginCaptures</key> | |
| 1855 | <dict> | |
| 1856 | <key>1</key> | |
| 1857 | <dict> | |
| 1858 | <key>name</key> | |
| 1859 | <string>string.other.number.mdx</string> | |
| 1860 | </dict> | |
| 1861 | <key>2</key> | |
| 1862 | <dict> | |
| 1863 | <key>name</key> | |
| 1864 | <string>variable.ordered.list.mdx</string> | |
| 1865 | </dict> | |
| 1866 | <key>3</key> | |
| 1867 | <dict> | |
| 1868 | <key>name</key> | |
| 1869 | <string>string.other.number.mdx</string> | |
| 1870 | </dict> | |
| 1871 | <key>4</key> | |
| 1872 | <dict> | |
| 1873 | <key>name</key> | |
| 1874 | <string>variable.ordered.list.mdx</string> | |
| 1875 | </dict> | |
| 1876 | <key>5</key> | |
| 1877 | <dict> | |
| 1878 | <key>name</key> | |
| 1879 | <string>string.other.number.mdx</string> | |
| 1880 | </dict> | |
| 1881 | <key>6</key> | |
| 1882 | <dict> | |
| 1883 | <key>name</key> | |
| 1884 | <string>variable.ordered.list.mdx</string> | |
| 1885 | </dict> | |
| 1886 | <key>7</key> | |
| 1887 | <dict> | |
| 1888 | <key>name</key> | |
| 1889 | <string>keyword.other.tasklist.mdx</string> | |
| 1890 | </dict> | |
| 1891 | </dict> | |
| 1892 | <key>patterns</key> | |
| 1893 | <array> | |
| 1894 | <dict> | |
| 1895 | <key>include</key> | |
| 1896 | <string>#markdown-sections</string> | |
| 1897 | </dict> | |
| 1898 | </array> | |
| 1899 | <key>while</key> | |
| 1900 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{1}</string> | |
| 1901 | </dict> | |
| 1902 | <dict> | |
| 1903 | <key>begin</key> | |
| 1904 | <string>(?:^|\G)[\t ]*(?:([0-9]{2})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9])((?:\.|\)))(?:[ ]{2}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1905 | <key>beginCaptures</key> | |
| 1906 | <dict> | |
| 1907 | <key>1</key> | |
| 1908 | <dict> | |
| 1909 | <key>name</key> | |
| 1910 | <string>string.other.number.mdx</string> | |
| 1911 | </dict> | |
| 1912 | <key>2</key> | |
| 1913 | <dict> | |
| 1914 | <key>name</key> | |
| 1915 | <string>variable.ordered.list.mdx</string> | |
| 1916 | </dict> | |
| 1917 | <key>3</key> | |
| 1918 | <dict> | |
| 1919 | <key>name</key> | |
| 1920 | <string>string.other.number.mdx</string> | |
| 1921 | </dict> | |
| 1922 | <key>4</key> | |
| 1923 | <dict> | |
| 1924 | <key>name</key> | |
| 1925 | <string>variable.ordered.list.mdx</string> | |
| 1926 | </dict> | |
| 1927 | <key>5</key> | |
| 1928 | <dict> | |
| 1929 | <key>name</key> | |
| 1930 | <string>keyword.other.tasklist.mdx</string> | |
| 1931 | </dict> | |
| 1932 | </dict> | |
| 1933 | <key>patterns</key> | |
| 1934 | <array> | |
| 1935 | <dict> | |
| 1936 | <key>include</key> | |
| 1937 | <string>#markdown-sections</string> | |
| 1938 | </dict> | |
| 1939 | </array> | |
| 1940 | <key>while</key> | |
| 1941 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string> | |
| 1942 | </dict> | |
| 1943 | <dict> | |
| 1944 | <key>begin</key> | |
| 1945 | <string>(?:^|\G)[\t ]*([0-9])((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string> | |
| 1946 | <key>beginCaptures</key> | |
| 1947 | <dict> | |
| 1948 | <key>1</key> | |
| 1949 | <dict> | |
| 1950 | <key>name</key> | |
| 1951 | <string>string.other.number.mdx</string> | |
| 1952 | </dict> | |
| 1953 | <key>2</key> | |
| 1954 | <dict> | |
| 1955 | <key>name</key> | |
| 1956 | <string>variable.ordered.list.mdx</string> | |
| 1957 | </dict> | |
| 1958 | <key>3</key> | |
| 1959 | <dict> | |
| 1960 | <key>name</key> | |
| 1961 | <string>keyword.other.tasklist.mdx</string> | |
| 1962 | </dict> | |
| 1963 | </dict> | |
| 1964 | <key>patterns</key> | |
| 1965 | <array> | |
| 1966 | <dict> | |
| 1967 | <key>include</key> | |
| 1968 | <string>#markdown-sections</string> | |
| 1969 | </dict> | |
| 1970 | </array> | |
| 1971 | <key>while</key> | |
| 1972 | <string>^(?=[\t ]*$)|(?:^|\G)[ ]{3}</string> | |
| 1973 | </dict> | |
| 1974 | </array> | |
| 1975 | </dict> | |
| 1976 | <key>commonmark-paragraph</key> | |
| 1977 | <dict> | |
| 1978 | <key>begin</key> | |
| 1979 | <string>(?![\t ]*$)</string> | |
| 1980 | <key>name</key> | |
| 1981 | <string>meta.paragraph.mdx</string> | |
| 1982 | <key>patterns</key> | |
| 1983 | <array> | |
| 1984 | <dict> | |
| 1985 | <key>include</key> | |
| 1986 | <string>#markdown-text</string> | |
| 1987 | </dict> | |
| 1988 | </array> | |
| 1989 | <key>while</key> | |
| 1990 | <string>(?:^|\G)(?:[ ]{4}|\t)</string> | |
| 1991 | </dict> | |
| 1992 | <key>commonmark-thematic-break</key> | |
| 1993 | <dict> | |
| 1994 | <key>match</key> | |
| 1995 | <string>(?:^|\G)[\t ]*([-*_])[ \t]*(?:\1[ \t]*){2,}$</string> | |
| 1996 | <key>name</key> | |
| 1997 | <string>meta.separator.mdx</string> | |
| 1998 | </dict> | |
| 1999 | <key>extension-gfm-autolink-literal</key> | |
| 2000 | <dict> | |
| 2001 | <key>patterns</key> | |
| 2002 | <array> | |
| 2003 | <dict> | |
| 2004 | <key>match</key> | |
| 2005 | <string>(?&lt;=^|[\t\n\r \(\*\_\[\]~])(?=(?i:www)\.[^\n\r])(?:(?:[\p{L}\p{N}]|-|[\._](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+\g&lt;path&gt;?)?(?&lt;path&gt;(?:(?:[^\t\n\r !"&amp;'\(\)\*,\.:;&lt;\?\]_~]|&amp;(?![A-Za-z]*;(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))|[!"'\)\*,\.:;\?_~](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))|\(\g&lt;path&gt;*\))+){0}</string> | |
| 2006 | <key>name</key> | |
| 2007 | <string>string.other.link.autolink.literal.www.mdx</string> | |
| 2008 | </dict> | |
| 2009 | <dict> | |
| 2010 | <key>match</key> | |
| 2011 | <string>(?&lt;=^|[^A-Za-z])(?i:https?://)(?=[\p{L}\p{N}])(?:(?:[\p{L}\p{N}]|-|[\._](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+\g&lt;path&gt;?)?(?&lt;path&gt;(?:(?:[^\t\n\r !"&amp;'\(\)\*,\.:;&lt;\?\]_~]|&amp;(?![A-Za-z]*;(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))|[!"'\)\*,\.:;\?_~](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))|\(\g&lt;path&gt;*\))+){0}</string> | |
| 2012 | <key>name</key> | |
| 2013 | <string>string.other.link.autolink.literal.http.mdx</string> | |
| 2014 | </dict> | |
| 2015 | <dict> | |
| 2016 | <key>match</key> | |
| 2017 | <string>(?&lt;=^|[^A-Za-z/])(?i:mailto:|xmpp:)?(?:[0-9A-Za-z+\-\._])+@(?:(?:[0-9A-Za-z]|[-_](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+(?:\.(?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))))+(?:[A-Za-z]|[-_](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+</string> | |
| 2018 | <key>name</key> | |
| 2019 | <string>string.other.link.autolink.literal.email.mdx</string> | |
| 2020 | </dict> | |
| 2021 | </array> | |
| 2022 | </dict> | |
| 2023 | <key>extension-gfm-footnote-call</key> | |
| 2024 | <dict> | |
| 2025 | <key>match</key> | |
| 2026 | <string>(\[)(\^)((?:[^\t\n\r \[\\\]]|\\[\[\\\]]?)+)(\])</string> | |
| 2027 | <key>captures</key> | |
| 2028 | <dict> | |
| 2029 | <key>1</key> | |
| 2030 | <dict> | |
| 2031 | <key>name</key> | |
| 2032 | <string>string.other.begin.link.mdx</string> | |
| 2033 | </dict> | |
| 2034 | <key>2</key> | |
| 2035 | <dict> | |
| 2036 | <key>name</key> | |
| 2037 | <string>string.other.begin.footnote.mdx</string> | |
| 2038 | </dict> | |
| 2039 | <key>3</key> | |
| 2040 | <dict> | |
| 2041 | <key>name</key> | |
| 2042 | <string>entity.name.identifier.mdx</string> | |
| 2043 | <key>patterns</key> | |
| 2044 | <array> | |
| 2045 | <dict> | |
| 2046 | <key>include</key> | |
| 2047 | <string>#markdown-string</string> | |
| 2048 | </dict> | |
| 2049 | </array> | |
| 2050 | </dict> | |
| 2051 | <key>4</key> | |
| 2052 | <dict> | |
| 2053 | <key>name</key> | |
| 2054 | <string>string.other.end.footnote.mdx</string> | |
| 2055 | </dict> | |
| 2056 | </dict> | |
| 2057 | </dict> | |
| 2058 | <key>extension-gfm-footnote-definition</key> | |
| 2059 | <dict> | |
| 2060 | <key>begin</key> | |
| 2061 | <string>(?:^|\G)[\t ]*(\[)(\^)((?:[^\t\n\r \[\\\]]|\\[\[\\\]]?)+)(\])(:)[\t ]*</string> | |
| 2062 | <key>beginCaptures</key> | |
| 2063 | <dict> | |
| 2064 | <key>1</key> | |
| 2065 | <dict> | |
| 2066 | <key>name</key> | |
| 2067 | <string>string.other.begin.link.mdx</string> | |
| 2068 | </dict> | |
| 2069 | <key>2</key> | |
| 2070 | <dict> | |
| 2071 | <key>name</key> | |
| 2072 | <string>string.other.begin.footnote.mdx</string> | |
| 2073 | </dict> | |
| 2074 | <key>3</key> | |
| 2075 | <dict> | |
| 2076 | <key>name</key> | |
| 2077 | <string>entity.name.identifier.mdx</string> | |
| 2078 | <key>patterns</key> | |
| 2079 | <array> | |
| 2080 | <dict> | |
| 2081 | <key>include</key> | |
| 2082 | <string>#markdown-string</string> | |
| 2083 | </dict> | |
| 2084 | </array> | |
| 2085 | </dict> | |
| 2086 | <key>4</key> | |
| 2087 | <dict> | |
| 2088 | <key>name</key> | |
| 2089 | <string>string.other.end.footnote.mdx</string> | |
| 2090 | </dict> | |
| 2091 | </dict> | |
| 2092 | <key>patterns</key> | |
| 2093 | <array> | |
| 2094 | <dict> | |
| 2095 | <key>include</key> | |
| 2096 | <string>#markdown-sections</string> | |
| 2097 | </dict> | |
| 2098 | </array> | |
| 2099 | <key>while</key> | |
| 2100 | <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string> | |
| 2101 | </dict> | |
| 2102 | <key>extension-gfm-strikethrough</key> | |
| 2103 | <dict> | |
| 2104 | <key>match</key> | |
| 2105 | <string>(?&lt;=\S)(?&lt;!~)~{1,2}(?!~)|(?&lt;!~)~{1,2}(?=\S)(?!~)</string> | |
| 2106 | <key>name</key> | |
| 2107 | <string>string.other.strikethrough.mdx</string> | |
| 2108 | </dict> | |
| 2109 | <key>extension-gfm-table</key> | |
| 2110 | <dict> | |
| 2111 | <key>begin</key> | |
| 2112 | <string>(?:^|\G)[\t ]*(?=\|[^\n\r]+\|[ \t]*$)</string> | |
| 2113 | <key>patterns</key> | |
| 2114 | <array> | |
| 2115 | <dict> | |
| 2116 | <key>match</key> | |
| 2117 | <string>(?&lt;=\||(?:^|\G))[\t ]*((?:[^\n\r\\\|]|\\[\\\|]?)+?)[\t ]*(?=\||$)</string> | |
| 2118 | <key>captures</key> | |
| 2119 | <dict> | |
| 2120 | <key>1</key> | |
| 2121 | <dict> | |
| 2122 | <key>patterns</key> | |
| 2123 | <array> | |
| 2124 | <dict> | |
| 2125 | <key>include</key> | |
| 2126 | <string>#markdown-text</string> | |
| 2127 | </dict> | |
| 2128 | </array> | |
| 2129 | </dict> | |
| 2130 | </dict> | |
| 2131 | </dict> | |
| 2132 | <dict> | |
| 2133 | <key>match</key> | |
| 2134 | <string>(?:\|)</string> | |
| 2135 | <key>name</key> | |
| 2136 | <string>markup.list.table-delimiter.mdx</string> | |
| 2137 | </dict> | |
| 2138 | </array> | |
| 2139 | <key>end</key> | |
| 2140 | <string>^(?=[\t ]*$)|$</string> | |
| 2141 | </dict> | |
| 2142 | <key>extension-github-gemoji</key> | |
| 2143 | <dict> | |
| 2144 | <key>match</key> | |
| 2145 | <string>(:)((?:(?:(?:hand_with_index_finger_and_thumb_cros|mailbox_clo|fist_rai|confu)s|r(?:aised_hand_with_fingers_splay|e(?:gister|l(?:iev|ax)))|disappointed_reliev|confound|(?:a(?:ston|ngu)i|flu)sh|unamus|hush)e|(?:chart_with_(?:down|up)wards_tre|large_orange_diamo|small_(?:orang|blu)e_diamo|large_blue_diamo|parasol_on_grou|loud_sou|rewi)n|(?:rightwards_pushing_h|hourglass_flowing_s|leftwards_(?:pushing_)?h|(?:raised_back_of|palm_(?:down|up)|call_me)_h|(?:(?:(?:clippert|ascensi)on|norfolk)_is|christmas_is|desert_is|bouvet_is|new_zea|thai|eng|fin|ire)l|rightwards_h|pinching_h|writing_h|s(?:w(?:itzer|azi)|cot)l|magic_w|ok_h|icel)an|s(?:un_behind_(?:large|small|rain)_clou|hallow_pan_of_foo|tar_of_davi|leeping_be|kateboar|a(?:tisfie|uropo)|hiel|oun|qui)|(?:ear_with_hearing_a|pouring_liqu)i|(?:identification_c|(?:arrow_(?:back|for)|fast_for)w|credit_c|woman_be|biohaz|man_be|l(?:eop|iz))ar|m(?:usical_key|ortar_)boar|(?:drop_of_bl|canned_f)oo|c(?:apital_abc|upi)|person_bal|(?:black_bi|(?:cust|plac)a)r|(?:clip|key)boar|mermai|pea_po|worrie|po(?:la|u)n|threa|dv)d|(?:(?:(?:face_with_open_eyes_and_hand_over|face_with_diagonal|open|no)_mou|h(?:and_over_mou|yacin)|mammo)t|running_shirt_with_sas|(?:(?:fishing_pole_and_|blow)fi|(?:tropical_f|petri_d)i|(?:paint|tooth)bru|banglade|jellyfi)s|(?:camera_fl|wavy_d)as|triump|menora|pouc|blus|watc|das|has)h|(?:s(?:o(?:(?:uth_georgia_south_sandwich|lomon)_island|ck)|miling_face_with_three_heart|t_kitts_nevi|weat_drop|agittariu|c(?:orpiu|issor)|ymbol|hort)|twisted_rightwards_arrow|(?:northern_mariana|heard_mcdonald|(?:british_virgi|us_virgi|pitcair|cayma)n|turks_caicos|us_outlying|(?:falk|a)land|marshall|c(?:anary|ocos)|faroe)_island|(?:face_holding_back_tea|(?:c(?:ard_index_divid|rossed_fing)|pinched_fing)e|night_with_sta)r|(?:two_(?:wo)?men_holding|people_holding|heart|open)_hand|(?:sunrise_over_mountai|(?:congratul|united_n)atio|jea)n|(?:caribbean_)?netherland|(?:f(?:lower_playing_car|ace_in_clou)|crossed_swor|prayer_bea)d|(?:money_with_win|nest_with_eg|crossed_fla|hotsprin)g|revolving_heart|(?:high_brightne|(?:expression|wire)le|(?:tumbler|wine)_gla|milk_gla|compa|dre)s|performing_art|earth_america|orthodox_cros|l(?:ow_brightnes|a(?:tin_cros|o)|ung)|no_pedestrian|c(?:ontrol_kno|lu)b|b(?:ookmark_tab|rick|ean)|nesting_doll|cook_island|(?:fleur_de_l|tenn)i|(?:o(?:ncoming_b|phiuch|ctop)|hi(?:ppopotam|bisc)|trolleyb|m(?:(?:rs|x)_cla|auriti|inib)|belar|cact|abac|(?:cyp|tau)r)u|medal_sport|(?:chopstic|firewor)k|rhinocero|(?:p(?:aw_prin|eanu)|footprin)t|two_heart|princes|(?:hondur|baham)a|barbado|aquariu|c(?:ustom|hain)|maraca|comoro|flag|wale|hug|vh)s|(?:(?:diamond_shape_with_a_dot_ins|playground_sl)id|(?:(?:first_quarter|last_quarter|full|new)_moon_with|(?:zipper|money)_mouth|dotted_line|upside_down|c(?:rying_c|owboy_h)at|(?:disguis|nauseat)ed|neutral|monocle|panda|tired|woozy|clown|nerd|zany|fox)_fac|s(?:t(?:uck_out_tongue_winking_ey|eam_locomotiv)|(?:lightly_(?:frown|smil)|neez|h(?:ush|ak))ing_fac|(?:tudio_micropho|(?:hinto_shr|lot_mach)i|ierra_leo|axopho)n|mall_airplan|un_with_fac|a(?:luting_fac|tellit|k)|haved_ic|y(?:nagogu|ring)|n(?:owfl)?ak|urinam|pong)|(?:black_(?:medium_)?small|white_(?:(?:medium_)?small|large)|(?:black|white)_medium|black_large|orange|purple|yellow|b(?:rown|lue)|red)_squar|(?:(?:perso|woma)n_with_|man_with_)?probing_can|(?:p(?:ut_litter_in_its_pl|outing_f)|frowning_f|cold_f|wind_f|hot_f)ac|(?:arrows_c(?:ounterc)?lockwi|computer_mou|derelict_hou|carousel_hor|c(?:ity_sunri|hee)|heartpul|briefca|racehor|pig_no|lacros)s|(?:(?:face_with_head_band|ideograph_advant|adhesive_band|under|pack)a|currency_exchan|l(?:eft_l)?ugga|woman_jud|name_bad|man_jud|jud)g|face_with_peeking_ey|(?:(?:e(?:uropean_post_off|ar_of_r)|post_off)i|information_sour|ambulan)c|artificial_satellit|(?:busts?_in_silhouet|(?:vulcan_sal|parach)u|m(?:usical_no|ayot)|ro(?:ller_ska|set)|timor_les|ice_ska)t|(?:(?:incoming|red)_envelo|s(?:ao_tome_princi|tethosco)|(?:micro|tele)sco|citysca)p|(?:(?:(?:convenience|department)_st|musical_sc)o|f(?:light_depar|ramed_pic)tu|love_you_gestu|heart_on_fi|japanese_og|cote_divoi|perseve|singapo)r|b(?:ullettrain_sid|eliz|on)|(?:(?:female_|male_)?dete|radioa)ctiv|(?:christmas|deciduous|evergreen|tanabata|palm)_tre|(?:vibration_mo|cape_ver)d|(?:fortune_cook|neckt|self)i|(?:fork_and_)?knif|athletic_sho|(?:p(?:lead|arty)|drool|curs|melt|yawn|ly)ing_fac|vomiting_fac|(?:(?:c(?:urling_st|ycl)|meat_on_b|repeat_|headst)o|(?:fire_eng|tanger|ukra)i|rice_sce|(?:micro|i)pho|champag|pho)n|(?:cricket|video)_gam|(?:boxing_glo|oli)v|(?:d(?:ragon|izzy)|monkey)_fac|(?:m(?:artin|ozamb)iq|fond)u|wind_chim|test_tub|flat_sho|m(?:a(?:ns_sho|t)|icrob|oos|ut)|(?:handsh|fish_c|moon_c|cupc)ak|nail_car|zimbabw|ho(?:neybe|l)|ice_cub|airplan|pensiv|c(?:a(?:n(?:dl|o)|k)|o(?:ffe|oki))|tongu|purs|f(?:lut|iv)|d(?:at|ov)|n(?:iu|os)|kit|rag|ax)e|(?:(?:british_indian_ocean_territo|(?:plate_with_cutl|batt)e|medal_milita|low_batte|hunga|wea)r|family_(?:woman_(?:woman_(?:girl|boy)|girl|boy)|man_(?:woman_(?:girl|boy)|man_(?:girl|boy)|girl|boy))_bo|person_feeding_bab|woman_feeding_bab|s(?:u(?:spension_railwa|nn)|t(?:atue_of_libert|_barthelem|rawberr))|(?:m(?:ountain_cable|ilky_)|aerial_tram)wa|articulated_lorr|man_feeding_bab|mountain_railwa|partly_sunn|(?:vatican_c|infin)it|(?:outbox_tr|inbox_tr|birthd|motorw|paragu|urugu|norw|x_r)a|butterfl|ring_buo|t(?:urke|roph)|angr|fogg)y|(?:(?:perso|woma)n_in_motorized_wheelchai|(?:(?:notebook_with_decorative_c|four_leaf_cl)ov|(?:index_pointing_at_the_vie|white_flo)w|(?:face_with_thermome|non\-potable_wa|woman_firefigh|desktop_compu|m(?:an_firefigh|otor_scoo)|(?:ro(?:ller_coa|o)|oy)s|potable_wa|kick_scoo|thermome|firefigh|helicop|ot)t|(?:woman_factory_wor|(?:woman_office|woman_health|health)_wor|man_(?:factory|office|health)_wor|(?:factory|office)_wor|rice_crac|black_jo|firecrac)k|telephone_receiv|(?:palms_up_toget|f(?:ire_extinguis|eat)|teac)h|(?:(?:open_)?file_fol|level_sli)d|police_offic|f(?:lying_sauc|arm)|woman_teach|roll_of_pap|(?:m(?:iddle_f|an_s)in|woman_sin|hambur|plun|dag)g|do_not_litt|wilted_flow|woman_farm|man_(?:teach|farm)|(?:bell_pe|hot_pe|fli)pp|l(?:o(?:udspeak|ve_lett|bst)|edg|add)|tokyo_tow|c(?:ucumb|lapp|anc)|b(?:e(?:ginn|av)|adg)|print|hamst)e|(?:perso|woma)n_in_manual_wheelchai|m(?:an(?:_in_motorized|(?:_in_man)?ual)|otorized)_wheelchai|(?:person_(?:white|curly|red)_|wheelc)hai|triangular_rule|(?:film_project|e(?:l_salv|cu)ad|elevat|tract|anch)o|s(?:traight_rul|pace_invad|crewdriv|nowboard|unflow|peak|wimm|ing|occ|how|urf|ki)e|r(?:ed_ca|unne|azo)|d(?:o(?:lla|o)|ee)|barbe)r|(?:(?:cloud_with_(?:lightning_and_)?ra|japanese_gobl|round_pushp|liechtenste|mandar|pengu|dolph|bahra|pushp|viol)i|(?:couple(?:_with_heart_wo|kiss_)man|construction_worker|(?:mountain_bik|bow|row)ing|lotus_position|(?:w(?:eight_lift|alk)|climb)ing|white_haired|curly_haired|raising_hand|super(?:villain|hero)|red_haired|basketball|s(?:(?:wimm|urf)ing|assy)|haircut|no_good|(?:vampir|massag)e|b(?:iking|ald)|zombie|fairy|mage|elf|ng)_(?:wo)?ma|(?:(?:couple_with_heart_man|isle_of)_m|(?:couplekiss_woman_|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_|frowning_|s(?:tanding|auna)_|po(?:uting_|lice)|running_|blonde_|o(?:lder|k)_)wom|(?:perso|woma)n_with_turb|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_m|f(?:olding_hand_f|rowning_m)|man_with_turb|(?:turkmen|afghan|pak)ist|s(?:tanding_m|(?:outh_s)?ud|auna_m)|po(?:uting_|lice)m|running_m|azerbaij|k(?:yrgyz|azakh)st|tajikist|uzbekist|o(?:lder_m|k_m|ce)|(?:orang|bh)ut|taiw|jord)a|s(?:mall_red_triangle_dow|(?:valbard_jan_may|int_maart|ev)e|afety_pi|top_sig|t_marti|(?:corpi|po|o)o|wede)|(?:heavy_(?:d(?:ivision|ollar)|equals|minus|plus)|no_entry|female|male)_sig|(?:arrow_(?:heading|double)_d|p(?:erson_with_cr|oint_d)|arrow_up_d|thumbsd)ow|(?:house_with_gard|l(?:ock_with_ink_p|eafy_gre)|dancing_(?:wo)?m|fountain_p|keycap_t|chick|ali|yem|od)e|(?:izakaya|jack_o)_lanter|(?:funeral_u|(?:po(?:stal_h|pc)|capric)o|unico)r|chess_paw|b(?:a(?:llo|c)o|eni|rai)|l(?:anter|io)|c(?:o(?:ff)?i|row)|melo|rame|oma|yar)n|(?:s(?:t(?:uck_out_tongue_closed_ey|_vincent_grenadin)|kull_and_crossbon|unglass|pad)|(?:french_souther|palestinia)n_territori|(?:face_with_spiral|kissing_smiling)_ey|united_arab_emirat|kissing_closed_ey|(?:clinking_|dark_sun|eye)glass|(?:no_mobile_|head)phon|womans_cloth|b(?:allet_sho|lueberri)|philippin|(?:no_bicyc|seychel)l|roll_ey|(?:cher|a)ri|p(?:ancak|isc)|maldiv|leav)es|(?:f(?:amily_(?:woman_(?:woman_)?|man_(?:woman_|man_)?)girl_gir|earfu)|(?:woman_playing_hand|m(?:an_playing_hand|irror_)|c(?:onfetti|rystal)_|volley|track|base|8)bal|(?:(?:m(?:ailbox_with_(?:no_)?m|onor)|cockt|e\-m)a|(?:person|bride|woman)_with_ve|man_with_ve|light_ra|braz|ema)i|(?:transgender|baby)_symbo|passport_contro|(?:arrow_(?:down|up)_sm|rice_b|footb)al|(?:dromedary_cam|ferris_whe|love_hot|high_he|pretz|falaf|isra)e|page_with_cur|me(?:dical_symbo|ta)|(?:n(?:ewspaper_ro|o_be)|bellhop_be)l|rugby_footbal|s(?:chool_satche|(?:peak|ee)_no_evi|oftbal|crol|anda|nai|hel)|(?:peace|atom)_symbo|hear_no_evi|cora|hote|bage|labe|rof|ow)l|(?:(?:negative_squared_cross|heavy_exclamation|part_alternation)_mar|(?:eight_spoked_)?asteris|(?:ballot_box_with_che|(?:(?:mantelpiece|alarm|timer)_c|un)lo|(?:ha(?:(?:mmer_and|ir)_p|tch(?:ing|ed)_ch)|baby_ch|joyst)i|railway_tra|lipsti|peaco)c|heavy_check_mar|white_check_mar|tr(?:opical_drin|uc)|national_par|pickup_truc|diving_mas|floppy_dis|s(?:tar_struc|hamroc|kun|har)|chipmun|denmar|duc|hoo|lin)k|(?:leftwards_arrow_with_h|arrow_right_h|(?:o(?:range|pen)|closed|blue)_b)ook|(?:woman_playing_water_pol|m(?:an(?:_(?:playing_water_pol|with_gua_pi_ma|in_tuxed)|g)|ontenegr|o(?:roc|na)c|e(?:xic|tr|m))|(?:perso|woma)n_in_tuxed|(?:trinidad_toba|vir)g|water_buffal|b(?:urkina_fas|a(?:mbo|nj)|ent)|puerto_ric|water_pol|flaming|kangaro|(?:mosqu|burr)it|(?:avoc|torn)ad|curaca|lesoth|potat|ko(?:sov|k)|tomat|d(?:ang|od)|yo_y|hoch|t(?:ac|og)|zer)o|(?:c(?:entral_african|zech)|dominican)_republic|(?:eight_pointed_black_s|six_pointed_s|qa)tar|(?:business_suit_levitat|(?:classical_buil|breast_fee)d|(?:woman_cartwhee|m(?:an_(?:cartwhee|jugg)|en_wrest)|women_wrest|woman_jugg|face_exha|cartwhee|wrest|dump)l|c(?:hildren_cross|amp)|woman_facepalm|woman_shrugg|man_(?:facepalm|shrugg)|people_hugg|(?:person_fe|woman_da|man_da)nc|fist_oncom|horse_rac|(?:no_smo|thin)k|laugh|s(?:eedl|mok)|park|w(?:arn|edd))ing|f(?:a(?:mily(?:_(?:woman_(?:woman_(?:girl|boy)|girl|boy)|man_(?:woman_(?:girl|boy)|man_(?:girl|boy)|girl|boy)))?|ctory)|o(?:u(?:ntain|r)|ot|g)|r(?:owning)?|i(?:re|s[ht])|ly|u)|(?:(?:(?:information_desk|handball|bearded)_|(?:frowning|ok)_|juggling_|mer)pers|(?:previous_track|p(?:lay_or_p)?ause|black_square|white_square|next_track|r(?:ecord|adio)|eject)_butt|(?:wa[nx]ing_(?:crescent|gibbous)_m|bowl_with_sp|crescent_m|racc)o|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_pers|s(?:t(?:_pierre_miquel|op_butt|ati)|tanding_pers|peech_ballo|auna_pers)|r(?:eminder_r)?ibb|thought_ballo|watermel|badmint|c(?:amero|ray)|le(?:ban|m)|oni|bis)on|(?:heavy_heart_exclama|building_construc|heart_decora|exclama)tion|(?:(?:triangular_flag_on_po|(?:(?:woman_)?technolog|m(?:ountain_bicycl|an_technolog)|bicycl)i|(?:wo)?man_scienti|(?:wo)?man_arti|s(?:afety_ve|cienti)|empty_ne)s|(?:vertical_)?traffic_ligh|(?:rescue_worker_helm|military_helm|nazar_amul|city_suns|wastebask|dropl|t(?:rump|oil)|bouqu|buck|magn|secr)e|one_piece_swimsui|(?:(?:arrow_(?:low|upp)er|point)_r|bridge_at_n|copyr|mag_r)igh|(?:bullettrain_fro|(?:potted_pl|croiss|e(?:ggpl|leph))a)n|s(?:t(?:ar_and_cresc|ud)en|cream_ca|mi(?:ley?|rk)_ca|(?:peed|ail)boa|hir)|(?:arrow_(?:low|upp)er|point)_lef|woman_astronau|r(?:o(?:tating_ligh|cke)|eceip)|heart_eyes_ca|man_astronau|(?:woman_stud|circus_t|man_stud|trid)en|(?:ringed_pla|file_cabi)ne|nut_and_bol|(?:older_)?adul|k(?:i(?:ssing_ca|wi_frui)|uwai|no)|(?:pouting_c|c(?:ut_of_m|old_sw)e|womans_h|montserr|(?:(?:motor_|row)b|lab_c)o|heartbe|toph)a|(?:woman_pil|honey_p|man_pil|[cp]arr|teap|rob)o|hiking_boo|arrow_lef|fist_righ|flashligh|f(?:ist_lef|ee)|black_ca|astronau|(?:c(?:hest|oco)|dough)nu|innocen|joy_ca|artis|(?:acce|egy)p|co(?:me|a)|pilo)t|(?:heavy_multiplication_|t\-re)x|(?:s(?:miling_face_with_te|piral_calend)|oncoming_police_c|chocolate_b|ra(?:ilway|cing)_c|police_c|polar_be|teddy_be|madagasc|blue_c|calend|myanm)ar|c(?:l(?:o(?:ud(?:_with_lightning)?|ck(?:1[0-2]?|[2-9]))|ap)?|o(?:uple(?:_with_heart|kiss)?|nstruction|mputer|ok|p|w)|a(?:r(?:d_index)?|mera)|r(?:icket|y)|h(?:art|ild))|(?:m(?:artial_arts_unifo|echanical_a)r|(?:cherry_)?blosso|b(?:aggage_clai|roo)|ice_?crea|facepal|mushroo|restroo|vietna|dru|yu)m|(?:woman_with_headscar|m(?:obile_phone_of|aple_lea)|fallen_lea|wol)f|(?:(?:closed_lock_with|old)_|field_hoc|ice_hoc|han|don)key|g(?:lobe_with_meridians|r(?:e(?:y_(?:exclama|ques)tion|e(?:n(?:_(?:square|circle|salad|apple|heart|book)|land)|ce)|y_heart|nada)|i(?:mac|nn)ing|apes)|u(?:inea_bissau|ernsey|am|n)|(?:(?:olfing|enie)_(?:wo)?|uards(?:wo)?)man|(?:inger_roo|oal_ne|hos)t|(?:uadeloup|ame_di|iraff|oos)e|ift_heart|i(?:braltar|rl)|(?:uatemal|(?:eorg|amb)i|orill|uyan|han)a|uide_dog|(?:oggl|lov)es|arlic|emini|uitar|abon|oat|ear|b)|construction_worker|(?:(?:envelope_with|bow_and)_ar|left_right_ar|raised_eyeb)row|(?:(?:oncoming_automob|crocod)i|right_anger_bubb|l(?:eft_speech_bubb|otion_bott|ady_beet)|congo_brazzavil|eye_speech_bubb|(?:large_blue|orange|purple|yellow|brown)_circ|(?:(?:european|japanese)_cas|baby_bot)t|b(?:alance_sca|eet)|s(?:ewing_need|weat_smi)|(?:black|white|red)_circ|(?:motor|re)cyc|pood|turt|tama|waff|musc|eag)le|first_quarter_moon|s(?:m(?:all_red_triangle|i(?:ley?|rk))|t(?:uck_out_tongue|ar)|hopping|leeping|p(?:arkle|ider)|unrise|nowman|chool|cream|k(?:ull|i)|weat|ix|a)|(?:(?:b(?:osnia_herzegovi|ana)|wallis_futu|(?:french_gui|botsw)a|argenti|st_hele)n|(?:(?:equatorial|papua_new)_guin|north_kor|eritr)e|t(?:ristan_da_cunh|ad)|(?:(?:(?:french_poly|indo)ne|tuni)s|(?:new_caledo|ma(?:urita|cedo)|lithua|(?:tanz|alb|rom)a|arme|esto)n|diego_garc|s(?:audi_arab|t_luc|lov(?:ak|en)|omal|erb)|e(?:arth_as|thiop)|m(?:icrone|alay)s|(?:austra|mongo)l|c(?:ambod|roat)|(?:bulga|alge)r|(?:colom|nami|zam)b|boliv|l(?:iber|atv))i|(?:wheel_of_dhar|cine|pana)m|(?:(?:(?:closed|beach|open)_)?umbrel|ceuta_melil|venezue|ang(?:uil|o)|koa)l|c(?:ongo_kinshas|anad|ub)|(?:western_saha|a(?:mpho|ndor)|zeb)r|american_samo|video_camer|m(?:o(?:vie_camer|ldov)|alt|eg)|(?:earth_af|costa_)ric|s(?:outh_afric|ri_lank|a(?:mo|nt))|bubble_te|(?:antarct|jama)ic|ni(?:caragu|geri|nj)|austri|pi(?:nat|zz)|arub|k(?:eny|aab)|indi|u7a7|l(?:lam|ib[ry])|dn)a|l(?:ast_quarter_moon|o(?:tus|ck)|ips|eo)|(?:hammer_and_wren|c(?:ockroa|hur)|facepun|wren|crut|pun)ch|s(?:nowman_with_snow|ignal_strength|weet_potato|miling_imp|p(?:ider_web|arkle[rs])|w(?:im_brief|an)|a(?:n(?:_marino|dwich)|lt)|topwatch|t(?:a(?:dium|r[2s])|ew)|l(?:e(?:epy|d)|oth)|hrimp|yria|carf|(?:hee|oa)p|ea[lt]|h(?:oe|i[pt])|o[bs])|(?:s(?:tuffed_flatbre|p(?:iral_notep|eaking_he))|(?:exploding_h|baguette_br|flatbr)e)ad|(?:arrow_(?:heading|double)_u|(?:p(?:lace_of_wor|assenger_)sh|film_str|tul)i|page_facing_u|biting_li|(?:billed_c|world_m)a|mouse_tra|(?:curly_lo|busst)o|thumbsu|lo(?:llip)?o|clam|im)p|(?:anatomical|light_blue|sparkling|kissing|mending|orange|purple|yellow|broken|b(?:rown|l(?:ack|ue))|pink)_heart|(?:(?:transgender|black)_fla|mechanical_le|(?:checkered|pirate)_fla|electric_plu|rainbow_fla|poultry_le|service_do|white_fla|luxembour|fried_eg|moneyba|h(?:edgeh|otd)o|shru)g|(?:cloud_with|mountain)_snow|(?:(?:antigua_barb|berm)u|(?:kh|ug)an|rwan)da|(?:3r|2n)d_place_medal|1(?:st_place_medal|234|00)|lotus_position|(?:w(?:eight_lift|alk)|climb)ing|(?:(?:cup_with_str|auto_ricksh)a|carpentry_sa|windo|jigsa)w|(?:(?:couch_and|diya)_la|f(?:ried_shri|uelpu))mp|(?:woman_mechan|man_mechan|alemb)ic|(?:european_un|accord|collis|reun)ion|(?:flight_arriv|hospit|portug|seneg|nep)al|card_file_box|(?:(?:oncoming_)?tax|m(?:o(?:unt_fuj|ya)|alaw)|s(?:paghett|ush|ar)|b(?:r(?:occol|une)|urund)|(?:djibou|kiriba)t|hait|fij)i|(?:shopping_c|white_he|bar_ch)art|d(?:isappointed|ominica|e(?:sert)?)|raising_hand|super(?:villain|hero)|b(?:e(?:verage_box|ers|d)|u(?:bbles|lb|g)|i(?:k(?:ini|e)|rd)|o(?:o(?:ks|t)|a[rt]|y)|read|a[cn]k)|ra(?:ised_hands|bbit2|t)|(?:hindu_tem|ap)ple|thong_sandal|a(?:r(?:row_(?:right|down|up)|t)|bc?|nt)?|r(?:a(?:i(?:sed_hand|nbow)|bbit|dio|m)|u(?:nning)?|epeat|i(?:ng|ce)|o(?:ck|se))|takeout_box|(?:flying_|mini)disc|(?:(?:interrob|yin_y)a|b(?:o(?:omera|wli)|angba)|(?:ping_p|hong_k)o|calli|mahjo)ng|b(?:a(?:llot_box|sket|th?|by)|o(?:o(?:k(?:mark)?|m)|w)|u(?:tter|s)|e(?:ll|er?|ar))?|heart_eyes|basketball|(?:paperclip|dancer|ticket)s|point_up_2|(?:wo)?man_cook|n(?:ew(?:spaper)?|o(?:tebook|_entry)|iger)|t(?:e(?:lephone|a)|o(?:oth|p)|r(?:oll)?|wo)|h(?:o(?:u(?:rglass|se)|rse)|a(?:mmer|nd)|eart)|paperclip|full_moon|(?:b(?:lack_ni|athtu|om)|her)b|(?:long|oil)_drum|pineapple|(?:clock(?:1[0-2]?|[2-9])3|u6e8)0|p(?:o(?:int_up|ut)|r(?:ince|ay)|i(?:ck|g)|en)|e(?:nvelope|ight|u(?:ro)?|gg|ar|ye|s)|m(?:o(?:u(?:ntain|se)|nkey|on)|echanic|a(?:ilbox|g|n)|irror)?|new_moon|d(?:iamonds|olls|art)|question|k(?:iss(?:ing)?|ey)|haircut|no_good|(?:vampir|massag)e|g(?:olf(?:ing)?|u(?:inea|ard)|e(?:nie|m)|ift|rin)|h(?:a(?:ndbag|msa)|ouses|earts|ut)|postbox|toolbox|(?:pencil|t(?:rain|iger)|whale|cat|dog)2|belgium|(?:volca|kimo)no|(?:vanuat|tuval|pala|naur|maca)u|tokelau|o(?:range|ne?|m|k)?|office|dancer|ticket|dragon|pencil|zombie|w(?:o(?:mens|rm|od)|ave|in[gk]|c)|m(?:o(?:sque|use2)|e(?:rman|ns)|a(?:li|sk))|jersey|tshirt|w(?:heel|oman)|dizzy|j(?:apan|oy)|t(?:rain|iger)|whale|fairy|a(?:nge[lr]|bcd|tm)|c(?:h(?:a(?:ir|d)|ile)|a(?:ndy|mel)|urry|rab|o(?:rn|ol|w2)|[dn])|p(?:ager|e(?:a(?:ch|r)|ru)|i(?:g2|ll|e)|oop)|n(?:otes|ine)|t(?:onga|hree|ent|ram|[mv])|f(?:erry|r(?:ies|ee|og)|ax)|u(?:7(?:533|981|121)|5(?:5b6|408|272)|6(?:307|70[89]))|mage|e(?:yes|nd)|i(?:ra[nq]|t)|cat|dog|elf|z(?:zz|ap)|yen|j(?:ar|p)|leg|id|u[kps]|ng|o[2x]|vs|kr|[\+\x2D]1|x|v)(:)</string> | |
| 2146 | <key>name</key> | |
| 2147 | <string>string.emoji.mdx</string> | |
| 2148 | <key>captures</key> | |
| 2149 | <dict> | |
| 2150 | <key>1</key> | |
| 2151 | <dict> | |
| 2152 | <key>name</key> | |
| 2153 | <string>punctuation.definition.gemoji.begin.mdx</string> | |
| 2154 | </dict> | |
| 2155 | <key>2</key> | |
| 2156 | <dict> | |
| 2157 | <key>name</key> | |
| 2158 | <string>keyword.control.gemoji.mdx</string> | |
| 2159 | </dict> | |
| 2160 | <key>3</key> | |
| 2161 | <dict> | |
| 2162 | <key>name</key> | |
| 2163 | <string>punctuation.definition.gemoji.end.mdx</string> | |
| 2164 | </dict> | |
| 2165 | </dict> | |
| 2166 | </dict> | |
| 2167 | <key>extension-github-mention</key> | |
| 2168 | <dict> | |
| 2169 | <key>match</key> | |
| 2170 | <string>(?&lt;![0-9A-Za-z_`])(@)((?:[0-9A-Za-z][0-9A-Za-z-]{0,38})(?:\/(?:[0-9A-Za-z][0-9A-Za-z-]{0,38}))?)(?![0-9A-Za-z_`])</string> | |
| 2171 | <key>name</key> | |
| 2172 | <string>string.mention.mdx</string> | |
| 2173 | <key>captures</key> | |
| 2174 | <dict> | |
| 2175 | <key>1</key> | |
| 2176 | <dict> | |
| 2177 | <key>name</key> | |
| 2178 | <string>punctuation.definition.mention.begin.mdx</string> | |
| 2179 | </dict> | |
| 2180 | <key>2</key> | |
| 2181 | <dict> | |
| 2182 | <key>name</key> | |
| 2183 | <string>string.other.link.mention.mdx</string> | |
| 2184 | </dict> | |
| 2185 | </dict> | |
| 2186 | </dict> | |
| 2187 | <key>extension-github-reference</key> | |
| 2188 | <dict> | |
| 2189 | <key>patterns</key> | |
| 2190 | <array> | |
| 2191 | <dict> | |
| 2192 | <key>match</key> | |
| 2193 | <string>(?&lt;![0-9A-Za-z_])(?:((?i:ghsa-|cve-))([A-Za-z0-9]+)|((?i:gh-|#))([0-9]+))(?![0-9A-Za-z_])</string> | |
| 2194 | <key>name</key> | |
| 2195 | <string>string.reference.mdx</string> | |
| 2196 | <key>captures</key> | |
| 2197 | <dict> | |
| 2198 | <key>1</key> | |
| 2199 | <dict> | |
| 2200 | <key>name</key> | |
| 2201 | <string>punctuation.definition.reference.begin.mdx</string> | |
| 2202 | </dict> | |
| 2203 | <key>2</key> | |
| 2204 | <dict> | |
| 2205 | <key>name</key> | |
| 2206 | <string>string.other.link.reference.security-advisory.mdx</string> | |
| 2207 | </dict> | |
| 2208 | <key>3</key> | |
| 2209 | <dict> | |
| 2210 | <key>name</key> | |
| 2211 | <string>punctuation.definition.reference.begin.mdx</string> | |
| 2212 | </dict> | |
| 2213 | <key>4</key> | |
| 2214 | <dict> | |
| 2215 | <key>name</key> | |
| 2216 | <string>string.other.link.reference.issue-or-pr.mdx</string> | |
| 2217 | </dict> | |
| 2218 | </dict> | |
| 2219 | </dict> | |
| 2220 | <dict> | |
| 2221 | <key>match</key> | |
| 2222 | <string>(?&lt;![^\t\n\r \(@\[\{])((?:[0-9A-Za-z][0-9A-Za-z-]{0,38})(?:\/(?:(?:\.git[0-9A-Za-z_-]|\.(?!git)|[0-9A-Za-z_-])+))?)(#)([0-9]+)(?![0-9A-Za-z_])</string> | |
| 2223 | <key>name</key> | |
| 2224 | <string>string.reference.mdx</string> | |
| 2225 | <key>captures</key> | |
| 2226 | <dict> | |
| 2227 | <key>1</key> | |
| 2228 | <dict> | |
| 2229 | <key>name</key> | |
| 2230 | <string>string.other.link.reference.user.mdx</string> | |
| 2231 | </dict> | |
| 2232 | <key>2</key> | |
| 2233 | <dict> | |
| 2234 | <key>name</key> | |
| 2235 | <string>punctuation.definition.reference.begin.mdx</string> | |
| 2236 | </dict> | |
| 2237 | <key>3</key> | |
| 2238 | <dict> | |
| 2239 | <key>name</key> | |
| 2240 | <string>string.other.link.reference.issue-or-pr.mdx</string> | |
| 2241 | </dict> | |
| 2242 | </dict> | |
| 2243 | </dict> | |
| 2244 | </array> | |
| 2245 | </dict> | |
| 2246 | <key>extension-math-flow</key> | |
| 2247 | <dict> | |
| 2248 | <key>begin</key> | |
| 2249 | <string>(?:^|\G)[\t ]*(\${2,})([^\n\r\$]*)$</string> | |
| 2250 | <key>beginCaptures</key> | |
| 2251 | <dict> | |
| 2252 | <key>1</key> | |
| 2253 | <dict> | |
| 2254 | <key>name</key> | |
| 2255 | <string>string.other.begin.math.flow.mdx</string> | |
| 2256 | </dict> | |
| 2257 | <key>2</key> | |
| 2258 | <dict> | |
| 2259 | <key>patterns</key> | |
| 2260 | <array> | |
| 2261 | <dict> | |
| 2262 | <key>include</key> | |
| 2263 | <string>#markdown-string</string> | |
| 2264 | </dict> | |
| 2265 | </array> | |
| 2266 | </dict> | |
| 2267 | </dict> | |
| 2268 | <key>contentName</key> | |
| 2269 | <string>markup.raw.math.flow.mdx</string> | |
| 2270 | <key>end</key> | |
| 2271 | <string>(\1)(?:[\t ]*$)</string> | |
| 2272 | <key>endCaptures</key> | |
| 2273 | <dict> | |
| 2274 | <key>1</key> | |
| 2275 | <dict> | |
| 2276 | <key>name</key> | |
| 2277 | <string>string.other.end.math.flow.mdx</string> | |
| 2278 | </dict> | |
| 2279 | </dict> | |
| 2280 | <key>name</key> | |
| 2281 | <string>markup.code.other.mdx</string> | |
| 2282 | </dict> | |
| 2283 | <key>extension-math-text</key> | |
| 2284 | <dict> | |
| 2285 | <key>match</key> | |
| 2286 | <string>(?&lt;!\$)(\${2,})(?!\$)(.+?)(?&lt;!\$)(\1)(?!\$)</string> | |
| 2287 | <key>captures</key> | |
| 2288 | <dict> | |
| 2289 | <key>1</key> | |
| 2290 | <dict> | |
| 2291 | <key>name</key> | |
| 2292 | <string>string.other.begin.math.mdx</string> | |
| 2293 | </dict> | |
| 2294 | <key>2</key> | |
| 2295 | <dict> | |
| 2296 | <key>name</key> | |
| 2297 | <string>markup.raw.math.mdx markup.inline.raw.math.mdx</string> | |
| 2298 | </dict> | |
| 2299 | <key>3</key> | |
| 2300 | <dict> | |
| 2301 | <key>name</key> | |
| 2302 | <string>string.other.end.math.mdx</string> | |
| 2303 | </dict> | |
| 2304 | </dict> | |
| 2305 | </dict> | |
| 2306 | <key>extension-mdx-esm</key> | |
| 2307 | <dict> | |
| 2308 | <key>name</key> | |
| 2309 | <string>meta.embedded.tsx</string> | |
| 2310 | <key>begin</key> | |
| 2311 | <string>(?:^|\G)(?=(?i:export|import)[ ])</string> | |
| 2312 | <key>end</key> | |
| 2313 | <string>^(?=[\t ]*$)|$</string> | |
| 2314 | <key>patterns</key> | |
| 2315 | <array> | |
| 2316 | <dict> | |
| 2317 | <key>include</key> | |
| 2318 | <string>source.tsx#statements</string> | |
| 2319 | </dict> | |
| 2320 | </array> | |
| 2321 | </dict> | |
| 2322 | <key>extension-mdx-expression-flow</key> | |
| 2323 | <dict> | |
| 2324 | <key>begin</key> | |
| 2325 | <string>(?:^|\G)[\t ]*(\{)(?!.*\}[\t ]*.)</string> | |
| 2326 | <key>beginCaptures</key> | |
| 2327 | <dict> | |
| 2328 | <key>1</key> | |
| 2329 | <dict> | |
| 2330 | <key>name</key> | |
| 2331 | <string>string.other.begin.expression.mdx.js</string> | |
| 2332 | </dict> | |
| 2333 | </dict> | |
| 2334 | <key>contentName</key> | |
| 2335 | <string>meta.embedded.tsx</string> | |
| 2336 | <key>end</key> | |
| 2337 | <string>(\})(?:[\t ]*$)</string> | |
| 2338 | <key>endCaptures</key> | |
| 2339 | <dict> | |
| 2340 | <key>1</key> | |
| 2341 | <dict> | |
| 2342 | <key>name</key> | |
| 2343 | <string>string.other.begin.expression.mdx.js</string> | |
| 2344 | </dict> | |
| 2345 | </dict> | |
| 2346 | <key>patterns</key> | |
| 2347 | <array> | |
| 2348 | <dict> | |
| 2349 | <key>include</key> | |
| 2350 | <string>source.tsx#expression</string> | |
| 2351 | </dict> | |
| 2352 | </array> | |
| 2353 | </dict> | |
| 2354 | <key>extension-mdx-expression-text</key> | |
| 2355 | <dict> | |
| 2356 | <key>begin</key> | |
| 2357 | <string>\{</string> | |
| 2358 | <key>beginCaptures</key> | |
| 2359 | <dict> | |
| 2360 | <key>0</key> | |
| 2361 | <dict> | |
| 2362 | <key>name</key> | |
| 2363 | <string>string.other.begin.expression.mdx.js</string> | |
| 2364 | </dict> | |
| 2365 | </dict> | |
| 2366 | <key>contentName</key> | |
| 2367 | <string>meta.embedded.tsx</string> | |
| 2368 | <key>end</key> | |
| 2369 | <string>\}</string> | |
| 2370 | <key>endCaptures</key> | |
| 2371 | <dict> | |
| 2372 | <key>0</key> | |
| 2373 | <dict> | |
| 2374 | <key>name</key> | |
| 2375 | <string>string.other.begin.expression.mdx.js</string> | |
| 2376 | </dict> | |
| 2377 | </dict> | |
| 2378 | <key>patterns</key> | |
| 2379 | <array> | |
| 2380 | <dict> | |
| 2381 | <key>include</key> | |
| 2382 | <string>source.tsx#expression</string> | |
| 2383 | </dict> | |
| 2384 | </array> | |
| 2385 | </dict> | |
| 2386 | <key>extension-mdx-jsx-flow</key> | |
| 2387 | <dict> | |
| 2388 | <key>begin</key> | |
| 2389 | <string>(?&lt;=^|\G|\&gt;)[\t ]*(&lt;)(?=(?![\t\n\r ]))(?:\s*(/))?(?:\s*(?:(?:((?:[_$[:alpha:]][-_$[:alnum:]]*))\s*(:)\s*((?:[_$[:alpha:]][-_$[:alnum:]]*)))|((?:(?:[_$[:alpha:]][_$[:alnum:]]*)(?:\s*\.\s*(?:[_$[:alpha:]][-_$[:alnum:]]*))+))|((?:[_$[:upper:]][_$[:alnum:]]*))|((?:[_$[:alpha:]][-_$[:alnum:]]*)))(?=[\s\/\&gt;\{]))?</string> | |
| 2390 | <key>beginCaptures</key> | |
| 2391 | <dict> | |
| 2392 | <key>1</key> | |
| 2393 | <dict> | |
| 2394 | <key>name</key> | |
| 2395 | <string>punctuation.definition.tag.end.jsx</string> | |
| 2396 | </dict> | |
| 2397 | <key>2</key> | |
| 2398 | <dict> | |
| 2399 | <key>name</key> | |
| 2400 | <string>punctuation.definition.tag.closing.jsx</string> | |
| 2401 | </dict> | |
| 2402 | <key>3</key> | |
| 2403 | <dict> | |
| 2404 | <key>name</key> | |
| 2405 | <string>entity.name.tag.namespace.jsx</string> | |
| 2406 | </dict> | |
| 2407 | <key>4</key> | |
| 2408 | <dict> | |
| 2409 | <key>name</key> | |
| 2410 | <string>punctuation.separator.namespace.jsx</string> | |
| 2411 | </dict> | |
| 2412 | <key>5</key> | |
| 2413 | <dict> | |
| 2414 | <key>name</key> | |
| 2415 | <string>entity.name.tag.local.jsx</string> | |
| 2416 | </dict> | |
| 2417 | <key>6</key> | |
| 2418 | <dict> | |
| 2419 | <key>name</key> | |
| 2420 | <string>support.class.component.jsx</string> | |
| 2421 | </dict> | |
| 2422 | <key>7</key> | |
| 2423 | <dict> | |
| 2424 | <key>name</key> | |
| 2425 | <string>support.class.component.jsx</string> | |
| 2426 | </dict> | |
| 2427 | <key>8</key> | |
| 2428 | <dict> | |
| 2429 | <key>name</key> | |
| 2430 | <string>entity.name.tag.jsx</string> | |
| 2431 | </dict> | |
| 2432 | </dict> | |
| 2433 | <key>patterns</key> | |
| 2434 | <array> | |
| 2435 | <dict> | |
| 2436 | <key>include</key> | |
| 2437 | <string>source.tsx#jsx-tag-attribute-name</string> | |
| 2438 | </dict> | |
| 2439 | <dict> | |
| 2440 | <key>include</key> | |
| 2441 | <string>source.tsx#jsx-tag-attribute-assignment</string> | |
| 2442 | </dict> | |
| 2443 | <dict> | |
| 2444 | <key>include</key> | |
| 2445 | <string>source.tsx#jsx-string-double-quoted</string> | |
| 2446 | </dict> | |
| 2447 | <dict> | |
| 2448 | <key>include</key> | |
| 2449 | <string>source.tsx#jsx-string-single-quoted</string> | |
| 2450 | </dict> | |
| 2451 | <dict> | |
| 2452 | <key>include</key> | |
| 2453 | <string>source.tsx#jsx-evaluated-code</string> | |
| 2454 | </dict> | |
| 2455 | <dict> | |
| 2456 | <key>include</key> | |
| 2457 | <string>source.tsx#jsx-tag-attributes-illegal</string> | |
| 2458 | </dict> | |
| 2459 | </array> | |
| 2460 | <key>end</key> | |
| 2461 | <string>(?:(\/)\s*)?(&gt;)</string> | |
| 2462 | <key>endCaptures</key> | |
| 2463 | <dict> | |
| 2464 | <key>1</key> | |
| 2465 | <dict> | |
| 2466 | <key>name</key> | |
| 2467 | <string>punctuation.definition.tag.self-closing.jsx</string> | |
| 2468 | </dict> | |
| 2469 | <key>2</key> | |
| 2470 | <dict> | |
| 2471 | <key>name</key> | |
| 2472 | <string>punctuation.definition.tag.end.jsx</string> | |
| 2473 | </dict> | |
| 2474 | </dict> | |
| 2475 | </dict> | |
| 2476 | <key>extension-mdx-jsx-text</key> | |
| 2477 | <dict> | |
| 2478 | <key>begin</key> | |
| 2479 | <string>(&lt;)(?=(?![\t\n\r ]))(?:\s*(/))?(?:\s*(?:(?:((?:[_$[:alpha:]][-_$[:alnum:]]*))\s*(:)\s*((?:[_$[:alpha:]][-_$[:alnum:]]*)))|((?:(?:[_$[:alpha:]][_$[:alnum:]]*)(?:\s*\.\s*(?:[_$[:alpha:]][-_$[:alnum:]]*))+))|((?:[_$[:upper:]][_$[:alnum:]]*))|((?:[_$[:alpha:]][-_$[:alnum:]]*)))(?=[\s\/\&gt;\{]))?</string> | |
| 2480 | <key>beginCaptures</key> | |
| 2481 | <dict> | |
| 2482 | <key>1</key> | |
| 2483 | <dict> | |
| 2484 | <key>name</key> | |
| 2485 | <string>punctuation.definition.tag.end.jsx</string> | |
| 2486 | </dict> | |
| 2487 | <key>2</key> | |
| 2488 | <dict> | |
| 2489 | <key>name</key> | |
| 2490 | <string>punctuation.definition.tag.closing.jsx</string> | |
| 2491 | </dict> | |
| 2492 | <key>3</key> | |
| 2493 | <dict> | |
| 2494 | <key>name</key> | |
| 2495 | <string>entity.name.tag.namespace.jsx</string> | |
| 2496 | </dict> | |
| 2497 | <key>4</key> | |
| 2498 | <dict> | |
| 2499 | <key>name</key> | |
| 2500 | <string>punctuation.separator.namespace.jsx</string> | |
| 2501 | </dict> | |
| 2502 | <key>5</key> | |
| 2503 | <dict> | |
| 2504 | <key>name</key> | |
| 2505 | <string>entity.name.tag.local.jsx</string> | |
| 2506 | </dict> | |
| 2507 | <key>6</key> | |
| 2508 | <dict> | |
| 2509 | <key>name</key> | |
| 2510 | <string>support.class.component.jsx</string> | |
| 2511 | </dict> | |
| 2512 | <key>7</key> | |
| 2513 | <dict> | |
| 2514 | <key>name</key> | |
| 2515 | <string>support.class.component.jsx</string> | |
| 2516 | </dict> | |
| 2517 | <key>8</key> | |
| 2518 | <dict> | |
| 2519 | <key>name</key> | |
| 2520 | <string>entity.name.tag.jsx</string> | |
| 2521 | </dict> | |
| 2522 | </dict> | |
| 2523 | <key>patterns</key> | |
| 2524 | <array> | |
| 2525 | <dict> | |
| 2526 | <key>include</key> | |
| 2527 | <string>source.tsx#jsx-tag-attribute-name</string> | |
| 2528 | </dict> | |
| 2529 | <dict> | |
| 2530 | <key>include</key> | |
| 2531 | <string>source.tsx#jsx-tag-attribute-assignment</string> | |
| 2532 | </dict> | |
| 2533 | <dict> | |
| 2534 | <key>include</key> | |
| 2535 | <string>source.tsx#jsx-string-double-quoted</string> | |
| 2536 | </dict> | |
| 2537 | <dict> | |
| 2538 | <key>include</key> | |
| 2539 | <string>source.tsx#jsx-string-single-quoted</string> | |
| 2540 | </dict> | |
| 2541 | <dict> | |
| 2542 | <key>include</key> | |
| 2543 | <string>source.tsx#jsx-evaluated-code</string> | |
| 2544 | </dict> | |
| 2545 | <dict> | |
| 2546 | <key>include</key> | |
| 2547 | <string>source.tsx#jsx-tag-attributes-illegal</string> | |
| 2548 | </dict> | |
| 2549 | </array> | |
| 2550 | <key>end</key> | |
| 2551 | <string>(?:(\/)\s*)?(&gt;)</string> | |
| 2552 | <key>endCaptures</key> | |
| 2553 | <dict> | |
| 2554 | <key>1</key> | |
| 2555 | <dict> | |
| 2556 | <key>name</key> | |
| 2557 | <string>punctuation.definition.tag.self-closing.jsx</string> | |
| 2558 | </dict> | |
| 2559 | <key>2</key> | |
| 2560 | <dict> | |
| 2561 | <key>name</key> | |
| 2562 | <string>punctuation.definition.tag.end.jsx</string> | |
| 2563 | </dict> | |
| 2564 | </dict> | |
| 2565 | </dict> | |
| 2566 | <key>extension-toml</key> | |
| 2567 | <dict> | |
| 2568 | <key>begin</key> | |
| 2569 | <string>\A\+{3}$</string> | |
| 2570 | <key>end</key> | |
| 2571 | <string>^\+{3}$</string> | |
| 2572 | <key>beginCaptures</key> | |
| 2573 | <dict> | |
| 2574 | <key>0</key> | |
| 2575 | <dict> | |
| 2576 | <key>name</key> | |
| 2577 | <string>string.other.begin.toml</string> | |
| 2578 | </dict> | |
| 2579 | </dict> | |
| 2580 | <key>endCaptures</key> | |
| 2581 | <dict> | |
| 2582 | <key>0</key> | |
| 2583 | <dict> | |
| 2584 | <key>name</key> | |
| 2585 | <string>string.other.end.toml</string> | |
| 2586 | </dict> | |
| 2587 | </dict> | |
| 2588 | <key>contentName</key> | |
| 2589 | <string>meta.embedded.toml</string> | |
| 2590 | <key>patterns</key> | |
| 2591 | <array> | |
| 2592 | <dict> | |
| 2593 | <key>include</key> | |
| 2594 | <string>source.toml</string> | |
| 2595 | </dict> | |
| 2596 | </array> | |
| 2597 | </dict> | |
| 2598 | <key>extension-yaml</key> | |
| 2599 | <dict> | |
| 2600 | <key>begin</key> | |
| 2601 | <string>\A-{3}$</string> | |
| 2602 | <key>end</key> | |
| 2603 | <string>^-{3}$</string> | |
| 2604 | <key>beginCaptures</key> | |
| 2605 | <dict> | |
| 2606 | <key>0</key> | |
| 2607 | <dict> | |
| 2608 | <key>name</key> | |
| 2609 | <string>string.other.begin.yaml</string> | |
| 2610 | </dict> | |
| 2611 | </dict> | |
| 2612 | <key>endCaptures</key> | |
| 2613 | <dict> | |
| 2614 | <key>0</key> | |
| 2615 | <dict> | |
| 2616 | <key>name</key> | |
| 2617 | <string>string.other.end.yaml</string> | |
| 2618 | </dict> | |
| 2619 | </dict> | |
| 2620 | <key>contentName</key> | |
| 2621 | <string>meta.embedded.yaml</string> | |
| 2622 | <key>patterns</key> | |
| 2623 | <array> | |
| 2624 | <dict> | |
| 2625 | <key>include</key> | |
| 2626 | <string>source.yaml</string> | |
| 2627 | </dict> | |
| 2628 | </array> | |
| 2629 | </dict> | |
| 2630 | <key>whatwg-html-data-character-reference-named-terminated</key> | |
| 2631 | <dict> | |
| 2632 | <key>match</key> | |
| 2633 | <string>(&amp;)((?:C(?:(?:o(?:unterClockwiseCo)?|lockwiseCo)ntourIntegra|cedi)|(?:(?:Not(?:S(?:quareSu(?:per|b)set|u(?:cceeds|(?:per|b)set))|Precedes|Greater|Tilde|Less)|Not(?:Righ|Lef)tTriangle|(?:Not(?:(?:Succeed|Precede|Les)s|Greater)|(?:Precede|Succeed)s|Less)Slant|SquareSu(?:per|b)set|(?:Not(?:Greater|Tilde)|Tilde|Less)Full|RightTriangle|LeftTriangle|Greater(?:Slant|Full)|Precedes|Succeeds|Superset|NotHump|Subset|Tilde|Hump)Equ|int(?:er)?c|DotEqu)a|DoubleContourIntegra|(?:n(?:short)?parall|shortparall|p(?:arall|rur))e|(?:rightarrowta|l(?:eftarrowta|ced|ata|Ata)|sced|rata|perm|rced|rAta|ced)i|Proportiona|smepars|e(?:qvpars|pars|xc|um)|Integra|suphso|rarr[pt]|n(?:pars|tg)|l(?:arr[pt]|cei)|Rarrt|(?:hybu|fora)l|ForAl|[GKLNR-Tcknt]cedi|rcei|iexc|gime|fras|[uy]um|oso|dso|ium|Ium)l|D(?:o(?:uble(?:(?:L(?:ong(?:Left)?R|eftR)ight|L(?:ongL)?eft|UpDown|Right|Up)Arrow|Do(?:wnArrow|t))|wn(?:ArrowUpA|TeeA|a)rrow)|iacriticalDot|strok|ashv|cy)|(?:(?:(?:N(?:(?:otN)?estedGreater|ot(?:Greater|Less))|Less(?:Equal)?)Great|GreaterGreat|l[lr]corn|mark|east)e|Not(?:Double)?VerticalBa|(?:Not(?:Righ|Lef)tTriangleB|(?:(?:Righ|Lef)tDown|Right(?:Up)?|Left(?:Up)?)VectorB|RightTriangleB|Left(?:Triangle|Arrow)B|RightArrowB|V(?:er(?:ticalB|b)|b)|UpArrowB|l(?:ur(?:ds|u)h|dr(?:us|d)h|trP|owb|H)|profal|r(?:ulu|dld)h|b(?:igst|rvb)|(?:wed|ve[er])b|s(?:wn|es)w|n(?:wne|ese|sp|hp)|gtlP|d(?:oll|uh|H)|(?:hor|ov)b|u(?:dh|H)|r(?:lh|H)|ohb|hb|St)a|D(?:o(?:wn(?:(?:Left(?:Right|Tee)|RightTee)Vecto|(?:(?:Righ|Lef)tVector|Arrow)Ba)|ubleVerticalBa)|a(?:gge|r)|sc|f)|(?:(?:(?:Righ|Lef)tDown|(?:Righ|Lef)tUp)Tee|(?:Righ|Lef)tUpDown)Vecto|VerticalSeparato|(?:Left(?:Right|Tee)|RightTee)Vecto|less(?:eqq?)?gt|e(?:qslantgt|sc)|(?:RightF|LeftF|[lr]f)loo|u(?:[lr]corne|ar)|timesba|(?:plusa|cirs|apa)ci|U(?:arroci|f)|(?:dzigr|s(?:u(?:pl|br)|imr|[lr])|zigr|angz|nvH|l(?:tl|B)|r[Br])ar|UnderBa|(?:plus|harr|top|mid|of)ci|O(?:verBa|sc|f)|dd?agge|s(?:olba|sc)|g(?:t(?:rar|ci)|sc|f)|c(?:opys|u(?:po|ep)|sc|f)|(?:n(?:(?:v[lr]|w|r)A|l[Aa]|h[Aa]|eA)|x[hlr][Aa]|u(?:ua|da|A)|s[ew]A|rla|o[lr]a|rba|rAa|l[Ablr]a|h(?:oa|A)|era|d(?:ua|A)|cra|vA)r|o(?:lci|sc|ro|pa)|ropa|roar|l(?:o(?:pa|ar)|sc|Ar)|i(?:ma|s)c|ltci|dd?ar|a(?:ma|s)c|R(?:Bar|sc|f)|I(?:mac|f)|(?:u(?:ma|s)|oma|ema|Oma|Ema|[wyz]s|qs|ks|fs|Zs|Ys|Xs|Ws|Vs|Us|Ss|Qs|Ns|Ms|Ks|Is|Gs|Fs|Cs|Bs)c|Umac|x(?:sc|f)|v(?:sc|f)|rsc|n(?:ld|f)|m(?:sc|ld|ac|f)|rAr|h(?:sc|f)|b(?:sc|f)|psc|P(?:sc|f)|L(?:sc|ar|f)|jsc|J(?:sc|f)|E(?:sc|f)|[HT]sc|[yz]f|wf|tf|qf|pf|kf|jf|Zf|Yf|Xf|Wf|Vf|Tf|Sf|Qf|Nf|Mf|Kf|Hf|Gf|Ff|Cf|Bf)r|(?:Diacritical(?:Double)?A|[EINOSYZaisz]a)cute|(?:(?:N(?:egative(?:VeryThin|Thi(?:ck|n))|onBreaking)|NegativeMedium|ZeroWidth|VeryThin|Medium|Thi(?:ck|n))Spac|Filled(?:Very)?SmallSquar|Empty(?:Very)?SmallSquar|(?:N(?:ot(?:Succeeds|Greater|Tilde|Less)T|t)|DiacriticalT|VerticalT|PrecedesT|SucceedsT|NotEqualT|GreaterT|TildeT|EqualT|LessT|at|Ut|It)ild|(?:(?:DiacriticalG|[EIOUaiu]g)ra|(?:u|U)?bre|(?:o|e)?gra)v|(?:doublebar|curly|big|x)wedg|H(?:orizontalLin|ilbertSpac)|Double(?:Righ|Lef)tTe|(?:(?:measured|uw)ang|exponentia|dwang|ssmi|fema)l|(?:Poincarepla|reali|pho|oli)n|(?:black)?lozeng|(?:VerticalL|(?:prof|imag)l)in|SmallCircl|(?:black|dot)squar|rmoustach|l(?:moustach|angl)|(?:b(?:ack)?pr|(?:tri|xo)t|[qt]pr)im|[Tt]herefor|(?:DownB|[Gag]b)rev|(?:infint|nv[lr]tr)i|b(?:arwedg|owti)|an(?:dslop|gl)|(?:cu(?:rly)?v|rthr|lthr|b(?:ig|ar)v|xv)e|n(?:s(?:qsu[bp]|ccu)|prcu)|orslop|NewLin|maltes|Becaus|rangl|incar|(?:otil|Otil|t(?:ra|il))d|[inu]tild|s(?:mil|imn)|(?:sc|pr)cu|Wedg|Prim|Brev)e|(?:CloseCurly(?:Double)?Quo|OpenCurly(?:Double)?Quo|[ry]?acu)te|(?:Reverse(?:Up)?|Up)Equilibrium|C(?:apitalDifferentialD|(?:oproduc|(?:ircleD|enterD|d)o)t|on(?:grue|i)nt|conint|upCap|o(?:lone|pf)|OPY|hi)|(?:(?:(?:left)?rightsquig|(?:longleftr|twoheadr|nleftr|nLeftr|longr|hookr|nR|Rr)ight|(?:twohead|hook)left|longleft|updown|Updown|nright|Right|nleft|nLeft|down|up|Up)a|L(?:(?:ong(?:left)?righ|(?:ong)?lef)ta|eft(?:(?:right)?a|RightA|TeeA))|RightTeeA|LongLeftA|UpTeeA)rrow|(?:(?:RightArrow|Short|Upper|Lower)Left|(?:L(?:eftArrow|o(?:wer|ng))|LongLeft|Short|Upper)Right|ShortUp)Arrow|(?:b(?:lacktriangle(?:righ|lef)|ulle|no)|RightDoubleBracke|RightAngleBracke|Left(?:Doub|Ang)leBracke|(?:vartriangle|downharpoon|c(?:ircl|urv)earrow|upharpoon|looparrow)righ|(?:vartriangle|downharpoon|c(?:ircl|urv)earrow|upharpoon|looparrow|mapsto)lef|(?:UnderBrack|OverBrack|emptys|targ|Sups)e|diamondsui|c(?:ircledas|lubsui|are)|(?:spade|heart)sui|(?:(?:c(?:enter|t)|lmi|ino)d|(?:Triple|mD)D|n(?:otin|e)d|(?:ncong|doteq|su[bp]e|e[gl]s)d|l(?:ess|t)d|isind|c(?:ong|up|ap)?d|b(?:igod|N)|t(?:(?:ri)?d|opb)|s(?:ub|im)d|midd|g(?:tr?)?d|Lmid|DotD|(?:xo|ut|z)d|e(?:s?d|rD|fD|DD)|dtd|Zd|Id|Gd|Ed)o|realpar|i(?:magpar|iin)|S(?:uchTha|qr)|su[bp]mul|(?:(?:lt|i)que|gtque|(?:mid|low)a|e(?:que|xi))s|Produc|s(?:updo|e[cx])|r(?:parg|ec)|lparl|vangr|hamil|(?:homt|[lr]fis|ufis|dfis)h|phmma|t(?:wix|in)|quo|o(?:do|as)|fla|eDo)t|(?:(?:Square)?Intersecti|(?:straight|back|var)epsil|SquareUni|expectati|upsil|epsil|Upsil|eq?col|Epsil|(?:omic|Omic|rca|lca|eca|Sca|[NRTt]ca|Lca|Eca|[Zdz]ca|Dca)r|scar|ncar|herc|ccar|Ccar|iog|Iog)on|Not(?:S(?:quareSu(?:per|b)set|u(?:cceeds|(?:per|b)set))|Precedes|Greater|Tilde|Less)?|(?:(?:(?:Not(?:Reverse)?|Reverse)E|comp|E)leme|NotCongrue|(?:n[gl]|l)eqsla|geqsla|q(?:uat)?i|perc|iiii|coni|cwi|awi|oi)nt|(?:(?:rightleftharpo|leftrightharpo|quaterni)on|(?:(?:N(?:ot(?:NestedLess|Greater|Less)|estedLess)L|(?:eqslant|gtr(?:eqq?)?)l|LessL)e|Greater(?:Equal)?Le|cro)s|(?:rightright|leftleft|upup)arrow|rightleftarrow|(?:(?:(?:righ|lef)tthree|divideon|b(?:igo|ox)|[lr]o)t|InvisibleT)ime|downdownarrow|(?:(?:smallset|tri|dot|box)m|PlusM)inu|(?:RoundImpli|complex|Impli|Otim)e|C(?:ircle(?:Time|Minu|Plu)|ayley|ros)|(?:rationa|mode)l|NotExist|(?:(?:UnionP|MinusP|(?:b(?:ig[ou]|ox)|tri|s(?:u[bp]|im)|dot|xu|mn)p)l|(?:xo|u)pl|o(?:min|pl)|ropl|lopl|epl)u|otimesa|integer|e(?:linter|qual)|setminu|rarrbf|larrb?f|olcros|rarrf|mstpo|lesge|gesle|Exist|[lr]time|strn|napo|fltn|ccap|apo)s|(?:b(?:(?:lack|ig)triangledow|etwee)|(?:righ|lef)tharpoondow|(?:triangle|mapsto)dow|(?:nv|i)infi|ssetm|plusm|lagra|d(?:[lr]cor|isi)|c(?:ompf|aro)|s?frow|(?:hyph|curr)e|kgree|thor|ogo|ye)n|Not(?:Righ|Lef)tTriangle|(?:Up(?:Arrow)?|Short)DownArrow|(?:(?:n(?:triangle(?:righ|lef)t|succ|prec)|(?:trianglerigh|trianglelef|sqsu[bp]se|ques)t|backsim)e|lvertneq|gvertneq|(?:suc|pre)cneq|a(?:pprox|symp)e|(?:succ|prec|vee)e|circe)q|(?:UnderParenthes|OverParenthes|xn)is|(?:(?:Righ|Lef)tDown|Right(?:Up)?|Left(?:Up)?)Vector|D(?:o(?:wn(?:RightVector|LeftVector|Arrow|Tee)|t)|el|D)|l(?:eftrightarrows|br(?:k(?:sl[du]|e)|ac[ek])|tri[ef]|s(?:im[eg]|qb|h)|hard|a(?:tes|ngd|p)|o[pz]f|rm|gE|fr|eg|cy)|(?:NotHumpDownHum|(?:righ|lef)tharpoonu|big(?:(?:triangle|sqc)u|c[au])|HumpDownHum|m(?:apstou|lc)|(?:capbr|xsq)cu|smash|rarr[al]|(?:weie|sha)r|larrl|velli|(?:thin|punc)s|h(?:elli|airs)|(?:u[lr]c|vp)ro|d[lr]cro|c(?:upc[au]|apc[au])|thka|scna|prn?a|oper|n(?:ums|va|cu|bs)|ens|xc[au]|Ma)p|l(?:eftrightarrow|e(?:ftarrow|s(?:dot)?)?|moust|a(?:rrb?|te?|ng)|t(?:ri)?|sim|par|oz|l|g)|n(?:triangle(?:righ|lef)t|succ|prec)|SquareSu(?:per|b)set|(?:I(?:nvisibleComm|ot)|(?:varthe|iio)t|varkapp|(?:vars|S)igm|(?:diga|mco)mm|Cedill|lambd|Lambd|delt|Thet|omeg|Omeg|Kapp|Delt|nabl|zet|to[es]|rdc|ldc|iot|Zet|Bet|Et)a|b(?:lacktriangle|arwed|u(?:mpe?|ll)|sol|o(?:x[HVhv]|t)|brk|ne)|(?:trianglerigh|trianglelef|sqsu[bp]se|ques)t|RightT(?:riangl|e)e|(?:(?:varsu[bp]setn|su(?:psetn?|bsetn?))eq|nsu[bp]seteq|colone|(?:wedg|sim)e|nsime|lneq|gneq)q|DifferentialD|(?:(?:fall|ris)ingdots|(?:suc|pre)ccurly|ddots)eq|A(?:pplyFunction|ssign|(?:tild|grav|brev)e|acute|o(?:gon|pf)|lpha|(?:mac|sc|f)r|c(?:irc|y)|ring|Elig|uml|nd|MP)|(?:varsu[bp]setn|su(?:psetn?|bsetn?))eq|L(?:eft(?:T(?:riangl|e)e|Arrow)|l)|G(?:reaterEqual|amma)|E(?:xponentialE|quilibrium|sim|cy|TH|NG)|(?:(?:RightCeil|LeftCeil|varnoth|ar|Ur)in|(?:b(?:ack)?co|uri)n|vzigza|roan|loan|ffli|amal|sun|rin|n(?:tl|an)|Ran|Lan)g|(?:thick|succn?|precn?|less|g(?:tr|n)|ln|n)approx|(?:s(?:traightph|em)|(?:rtril|xu|u[lr]|xd|v[lr])tr|varph|l[lr]tr|b(?:sem|eps)|Ph)i|(?:circledd|osl|n(?:v[Dd]|V[Dd]|d)|hsl|V(?:vd|D)|Osl|v[Dd]|md)ash|(?:(?:RuleDelay|imp|cuw)e|(?:n(?:s(?:hort)?)?|short|rn)mi|D(?:Dotrah|iamon)|(?:i(?:nt)?pr|peri)o|odsol|llhar|c(?:opro|irmi)|(?:capa|anda|pou)n|Barwe|napi|api)d|(?:cu(?:rlyeq(?:suc|pre)|es)|telre|[ou]dbla|Udbla|Odbla|radi|lesc|gesc|dbla)c|(?:circled|big|eq|[is]|c|x|a|S|[hw]|W|H|G|E|C)circ|rightarrow|R(?:ightArrow|arr|e)|Pr(?:oportion)?|(?:longmapst|varpropt|p(?:lustw|ropt)|varrh|numer|(?:rsa|lsa|sb)qu|m(?:icr|h)|[lr]aqu|bdqu|eur)o|UnderBrace|ImaginaryI|B(?:ernoullis|a(?:ckslash|rv)|umpeq|cy)|(?:(?:Laplace|Mellin|zee)tr|Fo(?:uriertr|p)|(?:profsu|ssta)r|ordero|origo|[ps]op|nop|mop|i(?:op|mo)|h(?:op|al)|f(?:op|no)|dop|bop|Rop|Pop|Nop|Lop|Iop|Hop|Dop|[GJKMOQSTV-Zgjkoqvwyz]op|Bop)f|nsu[bp]seteq|t(?:ri(?:angleq|e)|imesd|he(?:tav|re4)|au)|O(?:verBrace|r)|(?:(?:pitchfo|checkma|t(?:opfo|b)|rob|rbb|l[bo]b)r|intlarh|b(?:brktbr|l(?:oc|an))|perten|NoBrea|rarrh|s[ew]arh|n[ew]arh|l(?:arrh|hbl)|uhbl|Hace)k|(?:NotCupC|(?:mu(?:lti)?|x)m|cupbrc)ap|t(?:riangle|imes|heta|opf?)|Precedes|Succeeds|Superset|NotEqual|(?:n(?:atural|exist|les)|s(?:qc[au]p|mte)|prime)s|c(?:ir(?:cled[RS]|[Ee])|u(?:rarrm|larrp|darr[lr]|ps)|o(?:mmat|pf)|aps|hi)|b(?:sol(?:hsu)?b|ump(?:eq|E)|ox(?:box|[Vv][HLRhlr]|[Hh][DUdu]|[DUdu][LRlr])|e(?:rnou|t[ah])|lk(?:34|1[24])|cy)|(?:l(?:esdot|squ|dqu)o|rsquo|rdquo|ngt)r|a(?:n(?:g(?:msda[a-h]|st|e)|d[dv])|st|p[Ee]|mp|fr|c[Edy])|(?:g(?:esdoto|E)|[lr]haru)l|(?:angrtvb|lrhar|nis)d|(?:(?:th(?:ic)?k|succn?|p(?:r(?:ecn?|n)?|lus)|rarr|l(?:ess|arr)|su[bp]|par|scn|g(?:tr|n)|ne|sc|n[glv]|ln|eq?)si|thetasy|ccupss|alefsy|botto)m|trpezium|(?:hks[ew]|dr?bk|bk)arow|(?:(?:[lr]a|d|c)empty|b(?:nequi|empty)|plank|nequi|odi)v|(?:(?:sc|rp|n)pol|point|fpart)int|(?:c(?:irf|wco)|awco)nint|PartialD|n(?:s(?:u[bp](?:set)?|c)|rarr|ot(?:ni|in)?|warr|e(?:arr)?|a(?:tur|p)|vlt|p(?:re?|ar)|um?|l[et]|ge|i)|n(?:atural|exist|les)|d(?:i(?:am(?:ond)?|v(?:ide)?)|tri|ash|ot|d)|backsim|l(?:esdot|squ|dqu)o|g(?:esdoto|E)|U(?:p(?:Arrow|si)|nion|arr)|angrtvb|p(?:l(?:anckh|us(?:d[ou]|[be]))|ar(?:sl|t)|r(?:od|nE|E)|erp|iv|m)|n(?:ot(?:niv[a-c]|in(?:v[a-c]|E))|rarr[cw]|s(?:u[bp][Ee]|c[er])|part|v(?:le|g[et])|g(?:es|E)|c(?:ap|y)|apE|lE|iv|Ll|Gg)|m(?:inus(?:du|b)|ale|cy|p)|rbr(?:k(?:sl[du]|e)|ac[ek])|(?:suphsu|tris|rcu|lcu)b|supdsub|(?:s[ew]a|n[ew]a)rrow|(?:b(?:ecaus|sim)|n(?:[lr]tri|bump)|csu[bp])e|equivDD|u(?:rcorn|lcorn|psi)|timesb|s(?:u(?:p(?:set)?|b(?:set)?)|q(?:su[bp]|u)|i(?:gma|m)|olb?|dot|mt|fr|ce?)|p(?:l(?:anck|us)|r(?:op|ec?)?|ara?|i)|o(?:times|r(?:d(?:er)?)?)|m(?:i(?:nusd?|d)|a(?:p(?:sto)?|lt)|u)|rmoust|g(?:e(?:s(?:dot|l)?|q)?|sim|n(?:ap|e)|t|l|g)|(?:spade|heart)s|c(?:u(?:rarr|larr|p)|o(?:m(?:ma|p)|lon|py|ng)|lubs|heck|cups|irc?|ent|ap)|colone|a(?:p(?:prox)?|n(?:g(?:msd|rt)?|d)|symp|f|c)|S(?:quare|u[bp]|c)|Subset|b(?:ecaus|sim)|vsu[bp]n[Ee]|s(?:u(?:psu[bp]|b(?:su[bp]|n[Ee]|E)|pn[Ee]|p[1-3E]|m)|q(?:u(?:ar[ef]|f)|su[bp]e)|igma[fv]|etmn|dot[be]|par|mid|hc?y|c[Ey])|f(?:rac(?:78|5[68]|45|3[458]|2[35]|1[2-68])|fr)|e(?:m(?:sp1[34]|ptyv)|psiv|c(?:irc|y)|t[ah]|ng|ll|fr|e)|(?:kappa|isins|vBar|fork|rho|phi|n[GL]t)v|divonx|V(?:dashl|ee)|gammad|G(?:ammad|cy|[Tgt])|[Ldhlt]strok|[HT]strok|(?:c(?:ylct|hc)|(?:s(?:oft|hch)|hard|S(?:OFT|HCH)|jser|J(?:ser|uk)|HARD|tsh|TSH|juk|iuk|I(?:uk|[EO])|zh|yi|nj|lj|k[hj]|gj|dj|ZH|Y[AIU]|NJ|LJ|K[HJ]|GJ|D[JSZ])c|ubrc|Ubrc|(?:yu|i[eo]|dz|v|p|f)c|TSc|SHc|CHc|Vc|Pc|Mc|Fc)y|(?:(?:wre|jm)at|dalet|a(?:ngs|le)p|imat|[lr]ds)h|[CLRUceglnou]acute|ff?llig|(?:f(?:fi|[ij])|sz|oe|ij|ae|OE|IJ)lig|r(?:a(?:tio|rr|ng)|tri|par|eal)|s[ew]arr|s(?:qc[au]p|mte)|prime|rarrb|i(?:n(?:fin|t)?|sin|t|i|c)|e(?:quiv|m(?:pty|sp)|p(?:si|ar)|cir|l|g)|kappa|isins|ncong|doteq|(?:wedg|sim)e|nsime|rsquo|rdquo|[lr]haru|V(?:dash|ert)|Tilde|lrhar|gamma|Equal|UpTee|n(?:[lr]tri|bump)|C(?:olon|up|ap)|v(?:arpi|ert)|u(?:psih|ml)|vnsu[bp]|r(?:tri[ef]|e(?:als|g)|a(?:rr[cw]|ng[de]|ce)|sh|lm|x)|rhard|sim[gl]E|i(?:sin[Ev]|mage|f[fr]|cy)|harrw|(?:n[gl]|l)eqq|g(?:sim[el]|tcc|e(?:qq|l)|nE|l[Eaj]|gg|ap)|ocirc|starf|utrif|d(?:trif|i(?:ams|e)|ashv|sc[ry]|fr|eg)|[du]har[lr]|T(?:HORN|a[bu])|(?:TRAD|[gl]vn)E|odash|[EUaeu]o(?:gon|pf)|alpha|[IJOUYgjuy]c(?:irc|y)|v(?:arr|ee)|succ|sim[gl]|harr|ln(?:ap|e)|lesg|(?:n[gl]|l)eq|ocir|star|utri|vBar|fork|su[bp]e|nsim|lneq|gneq|csu[bp]|zwn?j|yacy|x(?:opf|i)|scnE|o(?:r(?:d[fm]|v)|mid|lt|hm|gt|fr|cy|S)|scap|rsqb|ropf|ltcc|tsc[ry]|QUOT|[EOUYao]uml|rho|phi|n[GL]t|e[gl]s|ngt|I(?:nt|m)|nis|rfr|rcy|lnE|lEg|ufr|S(?:um|cy)|R(?:sh|ho)|psi|Ps?i|[NRTt]cy|L(?:sh|cy|[Tt])|kcy|Kcy|Hat|REG|[Zdz]cy|wr|lE|wp|Xi|Nu|Mu)(;)</string> | |
| 2634 | <key>name</key> | |
| 2635 | <string>constant.language.character-reference.named.html</string> | |
| 2636 | <key>captures</key> | |
| 2637 | <dict> | |
| 2638 | <key>1</key> | |
| 2639 | <dict> | |
| 2640 | <key>name</key> | |
| 2641 | <string>punctuation.definition.character-reference.begin.html</string> | |
| 2642 | </dict> | |
| 2643 | <key>2</key> | |
| 2644 | <dict> | |
| 2645 | <key>name</key> | |
| 2646 | <string>keyword.control.character-reference.html</string> | |
| 2647 | </dict> | |
| 2648 | <key>3</key> | |
| 2649 | <dict> | |
| 2650 | <key>name</key> | |
| 2651 | <string>punctuation.definition.character-reference.end.html</string> | |
| 2652 | </dict> | |
| 2653 | </dict> | |
| 2654 | </dict> | |
| 2655 | <key>commonmark-code-fenced-apib</key> | |
| 2656 | <dict> | |
| 2657 | <key>patterns</key> | |
| 2658 | <array> | |
| 2659 | <dict> | |
| 2660 | <key>begin</key> | |
| 2661 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:api\x2dblueprint|(?:.*\.)?apib))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 2662 | <key>beginCaptures</key> | |
| 2663 | <dict> | |
| 2664 | <key>1</key> | |
| 2665 | <dict> | |
| 2666 | <key>name</key> | |
| 2667 | <string>string.other.begin.code.fenced.mdx</string> | |
| 2668 | </dict> | |
| 2669 | <key>2</key> | |
| 2670 | <dict> | |
| 2671 | <key>name</key> | |
| 2672 | <string>entity.name.function.mdx</string> | |
| 2673 | <key>patterns</key> | |
| 2674 | <array> | |
| 2675 | <dict> | |
| 2676 | <key>include</key> | |
| 2677 | <string>#markdown-string</string> | |
| 2678 | </dict> | |
| 2679 | </array> | |
| 2680 | </dict> | |
| 2681 | <key>3</key> | |
| 2682 | <dict> | |
| 2683 | <key>patterns</key> | |
| 2684 | <array> | |
| 2685 | <dict> | |
| 2686 | <key>include</key> | |
| 2687 | <string>#markdown-string</string> | |
| 2688 | </dict> | |
| 2689 | </array> | |
| 2690 | </dict> | |
| 2691 | </dict> | |
| 2692 | <key>end</key> | |
| 2693 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 2694 | <key>endCaptures</key> | |
| 2695 | <dict> | |
| 2696 | <key>1</key> | |
| 2697 | <dict> | |
| 2698 | <key>name</key> | |
| 2699 | <string>string.other.end.code.fenced.mdx</string> | |
| 2700 | </dict> | |
| 2701 | </dict> | |
| 2702 | <key>name</key> | |
| 2703 | <string>markup.code.apib.mdx</string> | |
| 2704 | <key>patterns</key> | |
| 2705 | <array> | |
| 2706 | <dict> | |
| 2707 | <key>begin</key> | |
| 2708 | <string>(^|\G)(\s*)(.*)</string> | |
| 2709 | <key>while</key> | |
| 2710 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 2711 | <key>contentName</key> | |
| 2712 | <string>meta.embedded.apib</string> | |
| 2713 | <key>patterns</key> | |
| 2714 | <array> | |
| 2715 | <dict> | |
| 2716 | <key>include</key> | |
| 2717 | <string>text.html.markdown.source.gfm.apib</string> | |
| 2718 | </dict> | |
| 2719 | </array> | |
| 2720 | </dict> | |
| 2721 | </array> | |
| 2722 | </dict> | |
| 2723 | <dict> | |
| 2724 | <key>begin</key> | |
| 2725 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:api\x2dblueprint|(?:.*\.)?apib))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 2726 | <key>beginCaptures</key> | |
| 2727 | <dict> | |
| 2728 | <key>1</key> | |
| 2729 | <dict> | |
| 2730 | <key>name</key> | |
| 2731 | <string>string.other.begin.code.fenced.mdx</string> | |
| 2732 | </dict> | |
| 2733 | <key>2</key> | |
| 2734 | <dict> | |
| 2735 | <key>name</key> | |
| 2736 | <string>entity.name.function.mdx</string> | |
| 2737 | <key>patterns</key> | |
| 2738 | <array> | |
| 2739 | <dict> | |
| 2740 | <key>include</key> | |
| 2741 | <string>#markdown-string</string> | |
| 2742 | </dict> | |
| 2743 | </array> | |
| 2744 | </dict> | |
| 2745 | <key>3</key> | |
| 2746 | <dict> | |
| 2747 | <key>patterns</key> | |
| 2748 | <array> | |
| 2749 | <dict> | |
| 2750 | <key>include</key> | |
| 2751 | <string>#markdown-string</string> | |
| 2752 | </dict> | |
| 2753 | </array> | |
| 2754 | </dict> | |
| 2755 | </dict> | |
| 2756 | <key>end</key> | |
| 2757 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 2758 | <key>endCaptures</key> | |
| 2759 | <dict> | |
| 2760 | <key>1</key> | |
| 2761 | <dict> | |
| 2762 | <key>name</key> | |
| 2763 | <string>string.other.end.code.fenced.mdx</string> | |
| 2764 | </dict> | |
| 2765 | </dict> | |
| 2766 | <key>name</key> | |
| 2767 | <string>markup.code.apib.mdx</string> | |
| 2768 | <key>patterns</key> | |
| 2769 | <array> | |
| 2770 | <dict> | |
| 2771 | <key>begin</key> | |
| 2772 | <string>(^|\G)(\s*)(.*)</string> | |
| 2773 | <key>while</key> | |
| 2774 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 2775 | <key>contentName</key> | |
| 2776 | <string>meta.embedded.apib</string> | |
| 2777 | <key>patterns</key> | |
| 2778 | <array> | |
| 2779 | <dict> | |
| 2780 | <key>include</key> | |
| 2781 | <string>text.html.markdown.source.gfm.apib</string> | |
| 2782 | </dict> | |
| 2783 | </array> | |
| 2784 | </dict> | |
| 2785 | </array> | |
| 2786 | </dict> | |
| 2787 | </array> | |
| 2788 | </dict> | |
| 2789 | <key>commonmark-code-fenced-asciidoc</key> | |
| 2790 | <dict> | |
| 2791 | <key>patterns</key> | |
| 2792 | <array> | |
| 2793 | <dict> | |
| 2794 | <key>begin</key> | |
| 2795 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:adoc|asciidoc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 2796 | <key>beginCaptures</key> | |
| 2797 | <dict> | |
| 2798 | <key>1</key> | |
| 2799 | <dict> | |
| 2800 | <key>name</key> | |
| 2801 | <string>string.other.begin.code.fenced.mdx</string> | |
| 2802 | </dict> | |
| 2803 | <key>2</key> | |
| 2804 | <dict> | |
| 2805 | <key>name</key> | |
| 2806 | <string>entity.name.function.mdx</string> | |
| 2807 | <key>patterns</key> | |
| 2808 | <array> | |
| 2809 | <dict> | |
| 2810 | <key>include</key> | |
| 2811 | <string>#markdown-string</string> | |
| 2812 | </dict> | |
| 2813 | </array> | |
| 2814 | </dict> | |
| 2815 | <key>3</key> | |
| 2816 | <dict> | |
| 2817 | <key>patterns</key> | |
| 2818 | <array> | |
| 2819 | <dict> | |
| 2820 | <key>include</key> | |
| 2821 | <string>#markdown-string</string> | |
| 2822 | </dict> | |
| 2823 | </array> | |
| 2824 | </dict> | |
| 2825 | </dict> | |
| 2826 | <key>end</key> | |
| 2827 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 2828 | <key>endCaptures</key> | |
| 2829 | <dict> | |
| 2830 | <key>1</key> | |
| 2831 | <dict> | |
| 2832 | <key>name</key> | |
| 2833 | <string>string.other.end.code.fenced.mdx</string> | |
| 2834 | </dict> | |
| 2835 | </dict> | |
| 2836 | <key>name</key> | |
| 2837 | <string>markup.code.asciidoc.mdx</string> | |
| 2838 | <key>patterns</key> | |
| 2839 | <array> | |
| 2840 | <dict> | |
| 2841 | <key>begin</key> | |
| 2842 | <string>(^|\G)(\s*)(.*)</string> | |
| 2843 | <key>while</key> | |
| 2844 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 2845 | <key>contentName</key> | |
| 2846 | <string>meta.embedded.asciidoc</string> | |
| 2847 | <key>patterns</key> | |
| 2848 | <array> | |
| 2849 | <dict> | |
| 2850 | <key>include</key> | |
| 2851 | <string>text.html.asciidoc</string> | |
| 2852 | </dict> | |
| 2853 | </array> | |
| 2854 | </dict> | |
| 2855 | </array> | |
| 2856 | </dict> | |
| 2857 | <dict> | |
| 2858 | <key>begin</key> | |
| 2859 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:adoc|asciidoc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 2860 | <key>beginCaptures</key> | |
| 2861 | <dict> | |
| 2862 | <key>1</key> | |
| 2863 | <dict> | |
| 2864 | <key>name</key> | |
| 2865 | <string>string.other.begin.code.fenced.mdx</string> | |
| 2866 | </dict> | |
| 2867 | <key>2</key> | |
| 2868 | <dict> | |
| 2869 | <key>name</key> | |
| 2870 | <string>entity.name.function.mdx</string> | |
| 2871 | <key>patterns</key> | |
| 2872 | <array> | |
| 2873 | <dict> | |
| 2874 | <key>include</key> | |
| 2875 | <string>#markdown-string</string> | |
| 2876 | </dict> | |
| 2877 | </array> | |
| 2878 | </dict> | |
| 2879 | <key>3</key> | |
| 2880 | <dict> | |
| 2881 | <key>patterns</key> | |
| 2882 | <array> | |
| 2883 | <dict> | |
| 2884 | <key>include</key> | |
| 2885 | <string>#markdown-string</string> | |
| 2886 | </dict> | |
| 2887 | </array> | |
| 2888 | </dict> | |
| 2889 | </dict> | |
| 2890 | <key>end</key> | |
| 2891 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 2892 | <key>endCaptures</key> | |
| 2893 | <dict> | |
| 2894 | <key>1</key> | |
| 2895 | <dict> | |
| 2896 | <key>name</key> | |
| 2897 | <string>string.other.end.code.fenced.mdx</string> | |
| 2898 | </dict> | |
| 2899 | </dict> | |
| 2900 | <key>name</key> | |
| 2901 | <string>markup.code.asciidoc.mdx</string> | |
| 2902 | <key>patterns</key> | |
| 2903 | <array> | |
| 2904 | <dict> | |
| 2905 | <key>begin</key> | |
| 2906 | <string>(^|\G)(\s*)(.*)</string> | |
| 2907 | <key>while</key> | |
| 2908 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 2909 | <key>contentName</key> | |
| 2910 | <string>meta.embedded.asciidoc</string> | |
| 2911 | <key>patterns</key> | |
| 2912 | <array> | |
| 2913 | <dict> | |
| 2914 | <key>include</key> | |
| 2915 | <string>text.html.asciidoc</string> | |
| 2916 | </dict> | |
| 2917 | </array> | |
| 2918 | </dict> | |
| 2919 | </array> | |
| 2920 | </dict> | |
| 2921 | </array> | |
| 2922 | </dict> | |
| 2923 | <key>commonmark-code-fenced-c</key> | |
| 2924 | <dict> | |
| 2925 | <key>patterns</key> | |
| 2926 | <array> | |
| 2927 | <dict> | |
| 2928 | <key>begin</key> | |
| 2929 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:dtrace|dtrace\x2dscript|oncrpc|rpc|rpcgen|unified\x2dparallel\x2dc|x\x2dbitmap|x\x2dpixmap|xdr|(?:.*\.)?(?:c|cats|h|idc|opencl|upc|xbm|xpm|xs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 2930 | <key>beginCaptures</key> | |
| 2931 | <dict> | |
| 2932 | <key>1</key> | |
| 2933 | <dict> | |
| 2934 | <key>name</key> | |
| 2935 | <string>string.other.begin.code.fenced.mdx</string> | |
| 2936 | </dict> | |
| 2937 | <key>2</key> | |
| 2938 | <dict> | |
| 2939 | <key>name</key> | |
| 2940 | <string>entity.name.function.mdx</string> | |
| 2941 | <key>patterns</key> | |
| 2942 | <array> | |
| 2943 | <dict> | |
| 2944 | <key>include</key> | |
| 2945 | <string>#markdown-string</string> | |
| 2946 | </dict> | |
| 2947 | </array> | |
| 2948 | </dict> | |
| 2949 | <key>3</key> | |
| 2950 | <dict> | |
| 2951 | <key>patterns</key> | |
| 2952 | <array> | |
| 2953 | <dict> | |
| 2954 | <key>include</key> | |
| 2955 | <string>#markdown-string</string> | |
| 2956 | </dict> | |
| 2957 | </array> | |
| 2958 | </dict> | |
| 2959 | </dict> | |
| 2960 | <key>end</key> | |
| 2961 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 2962 | <key>endCaptures</key> | |
| 2963 | <dict> | |
| 2964 | <key>1</key> | |
| 2965 | <dict> | |
| 2966 | <key>name</key> | |
| 2967 | <string>string.other.end.code.fenced.mdx</string> | |
| 2968 | </dict> | |
| 2969 | </dict> | |
| 2970 | <key>name</key> | |
| 2971 | <string>markup.code.c.mdx</string> | |
| 2972 | <key>patterns</key> | |
| 2973 | <array> | |
| 2974 | <dict> | |
| 2975 | <key>begin</key> | |
| 2976 | <string>(^|\G)(\s*)(.*)</string> | |
| 2977 | <key>while</key> | |
| 2978 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 2979 | <key>contentName</key> | |
| 2980 | <string>meta.embedded.c</string> | |
| 2981 | <key>patterns</key> | |
| 2982 | <array> | |
| 2983 | <dict> | |
| 2984 | <key>include</key> | |
| 2985 | <string>source.c</string> | |
| 2986 | </dict> | |
| 2987 | </array> | |
| 2988 | </dict> | |
| 2989 | </array> | |
| 2990 | </dict> | |
| 2991 | <dict> | |
| 2992 | <key>begin</key> | |
| 2993 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:dtrace|dtrace\x2dscript|oncrpc|rpc|rpcgen|unified\x2dparallel\x2dc|x\x2dbitmap|x\x2dpixmap|xdr|(?:.*\.)?(?:c|cats|h|idc|opencl|upc|xbm|xpm|xs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 2994 | <key>beginCaptures</key> | |
| 2995 | <dict> | |
| 2996 | <key>1</key> | |
| 2997 | <dict> | |
| 2998 | <key>name</key> | |
| 2999 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3000 | </dict> | |
| 3001 | <key>2</key> | |
| 3002 | <dict> | |
| 3003 | <key>name</key> | |
| 3004 | <string>entity.name.function.mdx</string> | |
| 3005 | <key>patterns</key> | |
| 3006 | <array> | |
| 3007 | <dict> | |
| 3008 | <key>include</key> | |
| 3009 | <string>#markdown-string</string> | |
| 3010 | </dict> | |
| 3011 | </array> | |
| 3012 | </dict> | |
| 3013 | <key>3</key> | |
| 3014 | <dict> | |
| 3015 | <key>patterns</key> | |
| 3016 | <array> | |
| 3017 | <dict> | |
| 3018 | <key>include</key> | |
| 3019 | <string>#markdown-string</string> | |
| 3020 | </dict> | |
| 3021 | </array> | |
| 3022 | </dict> | |
| 3023 | </dict> | |
| 3024 | <key>end</key> | |
| 3025 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3026 | <key>endCaptures</key> | |
| 3027 | <dict> | |
| 3028 | <key>1</key> | |
| 3029 | <dict> | |
| 3030 | <key>name</key> | |
| 3031 | <string>string.other.end.code.fenced.mdx</string> | |
| 3032 | </dict> | |
| 3033 | </dict> | |
| 3034 | <key>name</key> | |
| 3035 | <string>markup.code.c.mdx</string> | |
| 3036 | <key>patterns</key> | |
| 3037 | <array> | |
| 3038 | <dict> | |
| 3039 | <key>begin</key> | |
| 3040 | <string>(^|\G)(\s*)(.*)</string> | |
| 3041 | <key>while</key> | |
| 3042 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3043 | <key>contentName</key> | |
| 3044 | <string>meta.embedded.c</string> | |
| 3045 | <key>patterns</key> | |
| 3046 | <array> | |
| 3047 | <dict> | |
| 3048 | <key>include</key> | |
| 3049 | <string>source.c</string> | |
| 3050 | </dict> | |
| 3051 | </array> | |
| 3052 | </dict> | |
| 3053 | </array> | |
| 3054 | </dict> | |
| 3055 | </array> | |
| 3056 | </dict> | |
| 3057 | <key>commonmark-code-fenced-clojure</key> | |
| 3058 | <dict> | |
| 3059 | <key>patterns</key> | |
| 3060 | <array> | |
| 3061 | <dict> | |
| 3062 | <key>begin</key> | |
| 3063 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:clojure|rouge|(?:.*\.)?(?:boot|cl2|clj|cljc|cljs|cljs\.hl|cljscm|cljx|edn|hic|rg|wisp)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3064 | <key>beginCaptures</key> | |
| 3065 | <dict> | |
| 3066 | <key>1</key> | |
| 3067 | <dict> | |
| 3068 | <key>name</key> | |
| 3069 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3070 | </dict> | |
| 3071 | <key>2</key> | |
| 3072 | <dict> | |
| 3073 | <key>name</key> | |
| 3074 | <string>entity.name.function.mdx</string> | |
| 3075 | <key>patterns</key> | |
| 3076 | <array> | |
| 3077 | <dict> | |
| 3078 | <key>include</key> | |
| 3079 | <string>#markdown-string</string> | |
| 3080 | </dict> | |
| 3081 | </array> | |
| 3082 | </dict> | |
| 3083 | <key>3</key> | |
| 3084 | <dict> | |
| 3085 | <key>patterns</key> | |
| 3086 | <array> | |
| 3087 | <dict> | |
| 3088 | <key>include</key> | |
| 3089 | <string>#markdown-string</string> | |
| 3090 | </dict> | |
| 3091 | </array> | |
| 3092 | </dict> | |
| 3093 | </dict> | |
| 3094 | <key>end</key> | |
| 3095 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3096 | <key>endCaptures</key> | |
| 3097 | <dict> | |
| 3098 | <key>1</key> | |
| 3099 | <dict> | |
| 3100 | <key>name</key> | |
| 3101 | <string>string.other.end.code.fenced.mdx</string> | |
| 3102 | </dict> | |
| 3103 | </dict> | |
| 3104 | <key>name</key> | |
| 3105 | <string>markup.code.clojure.mdx</string> | |
| 3106 | <key>patterns</key> | |
| 3107 | <array> | |
| 3108 | <dict> | |
| 3109 | <key>begin</key> | |
| 3110 | <string>(^|\G)(\s*)(.*)</string> | |
| 3111 | <key>while</key> | |
| 3112 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3113 | <key>contentName</key> | |
| 3114 | <string>meta.embedded.clojure</string> | |
| 3115 | <key>patterns</key> | |
| 3116 | <array> | |
| 3117 | <dict> | |
| 3118 | <key>include</key> | |
| 3119 | <string>source.clojure</string> | |
| 3120 | </dict> | |
| 3121 | </array> | |
| 3122 | </dict> | |
| 3123 | </array> | |
| 3124 | </dict> | |
| 3125 | <dict> | |
| 3126 | <key>begin</key> | |
| 3127 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:clojure|rouge|(?:.*\.)?(?:boot|cl2|clj|cljc|cljs|cljs\.hl|cljscm|cljx|edn|hic|rg|wisp)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3128 | <key>beginCaptures</key> | |
| 3129 | <dict> | |
| 3130 | <key>1</key> | |
| 3131 | <dict> | |
| 3132 | <key>name</key> | |
| 3133 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3134 | </dict> | |
| 3135 | <key>2</key> | |
| 3136 | <dict> | |
| 3137 | <key>name</key> | |
| 3138 | <string>entity.name.function.mdx</string> | |
| 3139 | <key>patterns</key> | |
| 3140 | <array> | |
| 3141 | <dict> | |
| 3142 | <key>include</key> | |
| 3143 | <string>#markdown-string</string> | |
| 3144 | </dict> | |
| 3145 | </array> | |
| 3146 | </dict> | |
| 3147 | <key>3</key> | |
| 3148 | <dict> | |
| 3149 | <key>patterns</key> | |
| 3150 | <array> | |
| 3151 | <dict> | |
| 3152 | <key>include</key> | |
| 3153 | <string>#markdown-string</string> | |
| 3154 | </dict> | |
| 3155 | </array> | |
| 3156 | </dict> | |
| 3157 | </dict> | |
| 3158 | <key>end</key> | |
| 3159 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3160 | <key>endCaptures</key> | |
| 3161 | <dict> | |
| 3162 | <key>1</key> | |
| 3163 | <dict> | |
| 3164 | <key>name</key> | |
| 3165 | <string>string.other.end.code.fenced.mdx</string> | |
| 3166 | </dict> | |
| 3167 | </dict> | |
| 3168 | <key>name</key> | |
| 3169 | <string>markup.code.clojure.mdx</string> | |
| 3170 | <key>patterns</key> | |
| 3171 | <array> | |
| 3172 | <dict> | |
| 3173 | <key>begin</key> | |
| 3174 | <string>(^|\G)(\s*)(.*)</string> | |
| 3175 | <key>while</key> | |
| 3176 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3177 | <key>contentName</key> | |
| 3178 | <string>meta.embedded.clojure</string> | |
| 3179 | <key>patterns</key> | |
| 3180 | <array> | |
| 3181 | <dict> | |
| 3182 | <key>include</key> | |
| 3183 | <string>source.clojure</string> | |
| 3184 | </dict> | |
| 3185 | </array> | |
| 3186 | </dict> | |
| 3187 | </array> | |
| 3188 | </dict> | |
| 3189 | </array> | |
| 3190 | </dict> | |
| 3191 | <key>commonmark-code-fenced-coffee</key> | |
| 3192 | <dict> | |
| 3193 | <key>patterns</key> | |
| 3194 | <array> | |
| 3195 | <dict> | |
| 3196 | <key>begin</key> | |
| 3197 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:coffee\x2dscript|coffeescript|(?:.*\.)?(?:_coffee|cjsx|coffee|cson|em|emberscript|iced)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3198 | <key>beginCaptures</key> | |
| 3199 | <dict> | |
| 3200 | <key>1</key> | |
| 3201 | <dict> | |
| 3202 | <key>name</key> | |
| 3203 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3204 | </dict> | |
| 3205 | <key>2</key> | |
| 3206 | <dict> | |
| 3207 | <key>name</key> | |
| 3208 | <string>entity.name.function.mdx</string> | |
| 3209 | <key>patterns</key> | |
| 3210 | <array> | |
| 3211 | <dict> | |
| 3212 | <key>include</key> | |
| 3213 | <string>#markdown-string</string> | |
| 3214 | </dict> | |
| 3215 | </array> | |
| 3216 | </dict> | |
| 3217 | <key>3</key> | |
| 3218 | <dict> | |
| 3219 | <key>patterns</key> | |
| 3220 | <array> | |
| 3221 | <dict> | |
| 3222 | <key>include</key> | |
| 3223 | <string>#markdown-string</string> | |
| 3224 | </dict> | |
| 3225 | </array> | |
| 3226 | </dict> | |
| 3227 | </dict> | |
| 3228 | <key>end</key> | |
| 3229 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3230 | <key>endCaptures</key> | |
| 3231 | <dict> | |
| 3232 | <key>1</key> | |
| 3233 | <dict> | |
| 3234 | <key>name</key> | |
| 3235 | <string>string.other.end.code.fenced.mdx</string> | |
| 3236 | </dict> | |
| 3237 | </dict> | |
| 3238 | <key>name</key> | |
| 3239 | <string>markup.code.coffee.mdx</string> | |
| 3240 | <key>patterns</key> | |
| 3241 | <array> | |
| 3242 | <dict> | |
| 3243 | <key>begin</key> | |
| 3244 | <string>(^|\G)(\s*)(.*)</string> | |
| 3245 | <key>while</key> | |
| 3246 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3247 | <key>contentName</key> | |
| 3248 | <string>meta.embedded.coffee</string> | |
| 3249 | <key>patterns</key> | |
| 3250 | <array> | |
| 3251 | <dict> | |
| 3252 | <key>include</key> | |
| 3253 | <string>source.coffee</string> | |
| 3254 | </dict> | |
| 3255 | </array> | |
| 3256 | </dict> | |
| 3257 | </array> | |
| 3258 | </dict> | |
| 3259 | <dict> | |
| 3260 | <key>begin</key> | |
| 3261 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:coffee\x2dscript|coffeescript|(?:.*\.)?(?:_coffee|cjsx|coffee|cson|em|emberscript|iced)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3262 | <key>beginCaptures</key> | |
| 3263 | <dict> | |
| 3264 | <key>1</key> | |
| 3265 | <dict> | |
| 3266 | <key>name</key> | |
| 3267 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3268 | </dict> | |
| 3269 | <key>2</key> | |
| 3270 | <dict> | |
| 3271 | <key>name</key> | |
| 3272 | <string>entity.name.function.mdx</string> | |
| 3273 | <key>patterns</key> | |
| 3274 | <array> | |
| 3275 | <dict> | |
| 3276 | <key>include</key> | |
| 3277 | <string>#markdown-string</string> | |
| 3278 | </dict> | |
| 3279 | </array> | |
| 3280 | </dict> | |
| 3281 | <key>3</key> | |
| 3282 | <dict> | |
| 3283 | <key>patterns</key> | |
| 3284 | <array> | |
| 3285 | <dict> | |
| 3286 | <key>include</key> | |
| 3287 | <string>#markdown-string</string> | |
| 3288 | </dict> | |
| 3289 | </array> | |
| 3290 | </dict> | |
| 3291 | </dict> | |
| 3292 | <key>end</key> | |
| 3293 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3294 | <key>endCaptures</key> | |
| 3295 | <dict> | |
| 3296 | <key>1</key> | |
| 3297 | <dict> | |
| 3298 | <key>name</key> | |
| 3299 | <string>string.other.end.code.fenced.mdx</string> | |
| 3300 | </dict> | |
| 3301 | </dict> | |
| 3302 | <key>name</key> | |
| 3303 | <string>markup.code.coffee.mdx</string> | |
| 3304 | <key>patterns</key> | |
| 3305 | <array> | |
| 3306 | <dict> | |
| 3307 | <key>begin</key> | |
| 3308 | <string>(^|\G)(\s*)(.*)</string> | |
| 3309 | <key>while</key> | |
| 3310 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3311 | <key>contentName</key> | |
| 3312 | <string>meta.embedded.coffee</string> | |
| 3313 | <key>patterns</key> | |
| 3314 | <array> | |
| 3315 | <dict> | |
| 3316 | <key>include</key> | |
| 3317 | <string>source.coffee</string> | |
| 3318 | </dict> | |
| 3319 | </array> | |
| 3320 | </dict> | |
| 3321 | </array> | |
| 3322 | </dict> | |
| 3323 | </array> | |
| 3324 | </dict> | |
| 3325 | <key>commonmark-code-fenced-console</key> | |
| 3326 | <dict> | |
| 3327 | <key>patterns</key> | |
| 3328 | <array> | |
| 3329 | <dict> | |
| 3330 | <key>begin</key> | |
| 3331 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:pycon|python\x2dconsole))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3332 | <key>beginCaptures</key> | |
| 3333 | <dict> | |
| 3334 | <key>1</key> | |
| 3335 | <dict> | |
| 3336 | <key>name</key> | |
| 3337 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3338 | </dict> | |
| 3339 | <key>2</key> | |
| 3340 | <dict> | |
| 3341 | <key>name</key> | |
| 3342 | <string>entity.name.function.mdx</string> | |
| 3343 | <key>patterns</key> | |
| 3344 | <array> | |
| 3345 | <dict> | |
| 3346 | <key>include</key> | |
| 3347 | <string>#markdown-string</string> | |
| 3348 | </dict> | |
| 3349 | </array> | |
| 3350 | </dict> | |
| 3351 | <key>3</key> | |
| 3352 | <dict> | |
| 3353 | <key>patterns</key> | |
| 3354 | <array> | |
| 3355 | <dict> | |
| 3356 | <key>include</key> | |
| 3357 | <string>#markdown-string</string> | |
| 3358 | </dict> | |
| 3359 | </array> | |
| 3360 | </dict> | |
| 3361 | </dict> | |
| 3362 | <key>end</key> | |
| 3363 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3364 | <key>endCaptures</key> | |
| 3365 | <dict> | |
| 3366 | <key>1</key> | |
| 3367 | <dict> | |
| 3368 | <key>name</key> | |
| 3369 | <string>string.other.end.code.fenced.mdx</string> | |
| 3370 | </dict> | |
| 3371 | </dict> | |
| 3372 | <key>name</key> | |
| 3373 | <string>markup.code.console.mdx</string> | |
| 3374 | <key>patterns</key> | |
| 3375 | <array> | |
| 3376 | <dict> | |
| 3377 | <key>begin</key> | |
| 3378 | <string>(^|\G)(\s*)(.*)</string> | |
| 3379 | <key>while</key> | |
| 3380 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3381 | <key>contentName</key> | |
| 3382 | <string>meta.embedded.console</string> | |
| 3383 | <key>patterns</key> | |
| 3384 | <array> | |
| 3385 | <dict> | |
| 3386 | <key>include</key> | |
| 3387 | <string>text.python.console</string> | |
| 3388 | </dict> | |
| 3389 | </array> | |
| 3390 | </dict> | |
| 3391 | </array> | |
| 3392 | </dict> | |
| 3393 | <dict> | |
| 3394 | <key>begin</key> | |
| 3395 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:pycon|python\x2dconsole))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3396 | <key>beginCaptures</key> | |
| 3397 | <dict> | |
| 3398 | <key>1</key> | |
| 3399 | <dict> | |
| 3400 | <key>name</key> | |
| 3401 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3402 | </dict> | |
| 3403 | <key>2</key> | |
| 3404 | <dict> | |
| 3405 | <key>name</key> | |
| 3406 | <string>entity.name.function.mdx</string> | |
| 3407 | <key>patterns</key> | |
| 3408 | <array> | |
| 3409 | <dict> | |
| 3410 | <key>include</key> | |
| 3411 | <string>#markdown-string</string> | |
| 3412 | </dict> | |
| 3413 | </array> | |
| 3414 | </dict> | |
| 3415 | <key>3</key> | |
| 3416 | <dict> | |
| 3417 | <key>patterns</key> | |
| 3418 | <array> | |
| 3419 | <dict> | |
| 3420 | <key>include</key> | |
| 3421 | <string>#markdown-string</string> | |
| 3422 | </dict> | |
| 3423 | </array> | |
| 3424 | </dict> | |
| 3425 | </dict> | |
| 3426 | <key>end</key> | |
| 3427 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3428 | <key>endCaptures</key> | |
| 3429 | <dict> | |
| 3430 | <key>1</key> | |
| 3431 | <dict> | |
| 3432 | <key>name</key> | |
| 3433 | <string>string.other.end.code.fenced.mdx</string> | |
| 3434 | </dict> | |
| 3435 | </dict> | |
| 3436 | <key>name</key> | |
| 3437 | <string>markup.code.console.mdx</string> | |
| 3438 | <key>patterns</key> | |
| 3439 | <array> | |
| 3440 | <dict> | |
| 3441 | <key>begin</key> | |
| 3442 | <string>(^|\G)(\s*)(.*)</string> | |
| 3443 | <key>while</key> | |
| 3444 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3445 | <key>contentName</key> | |
| 3446 | <string>meta.embedded.console</string> | |
| 3447 | <key>patterns</key> | |
| 3448 | <array> | |
| 3449 | <dict> | |
| 3450 | <key>include</key> | |
| 3451 | <string>text.python.console</string> | |
| 3452 | </dict> | |
| 3453 | </array> | |
| 3454 | </dict> | |
| 3455 | </array> | |
| 3456 | </dict> | |
| 3457 | </array> | |
| 3458 | </dict> | |
| 3459 | <key>commonmark-code-fenced-cpp</key> | |
| 3460 | <dict> | |
| 3461 | <key>patterns</key> | |
| 3462 | <array> | |
| 3463 | <dict> | |
| 3464 | <key>begin</key> | |
| 3465 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:ags|ags\x2dscript|asymptote|c\+\+|edje\x2ddata\x2dcollection|game\x2dmaker\x2dlanguage|swig|(?:.*\.)?(?:asc|ash|asy|c\+\+|cc|cp|cpp|cppm|cxx|edc|gml|h\+\+|hh|hpp|hxx|inl|ino|ipp|ixx|metal|re|tcc|tpp|txx)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3466 | <key>beginCaptures</key> | |
| 3467 | <dict> | |
| 3468 | <key>1</key> | |
| 3469 | <dict> | |
| 3470 | <key>name</key> | |
| 3471 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3472 | </dict> | |
| 3473 | <key>2</key> | |
| 3474 | <dict> | |
| 3475 | <key>name</key> | |
| 3476 | <string>entity.name.function.mdx</string> | |
| 3477 | <key>patterns</key> | |
| 3478 | <array> | |
| 3479 | <dict> | |
| 3480 | <key>include</key> | |
| 3481 | <string>#markdown-string</string> | |
| 3482 | </dict> | |
| 3483 | </array> | |
| 3484 | </dict> | |
| 3485 | <key>3</key> | |
| 3486 | <dict> | |
| 3487 | <key>patterns</key> | |
| 3488 | <array> | |
| 3489 | <dict> | |
| 3490 | <key>include</key> | |
| 3491 | <string>#markdown-string</string> | |
| 3492 | </dict> | |
| 3493 | </array> | |
| 3494 | </dict> | |
| 3495 | </dict> | |
| 3496 | <key>end</key> | |
| 3497 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3498 | <key>endCaptures</key> | |
| 3499 | <dict> | |
| 3500 | <key>1</key> | |
| 3501 | <dict> | |
| 3502 | <key>name</key> | |
| 3503 | <string>string.other.end.code.fenced.mdx</string> | |
| 3504 | </dict> | |
| 3505 | </dict> | |
| 3506 | <key>name</key> | |
| 3507 | <string>markup.code.cpp.mdx</string> | |
| 3508 | <key>patterns</key> | |
| 3509 | <array> | |
| 3510 | <dict> | |
| 3511 | <key>begin</key> | |
| 3512 | <string>(^|\G)(\s*)(.*)</string> | |
| 3513 | <key>while</key> | |
| 3514 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3515 | <key>contentName</key> | |
| 3516 | <string>meta.embedded.cpp</string> | |
| 3517 | <key>patterns</key> | |
| 3518 | <array> | |
| 3519 | <dict> | |
| 3520 | <key>include</key> | |
| 3521 | <string>source.c++</string> | |
| 3522 | </dict> | |
| 3523 | <dict> | |
| 3524 | <key>include</key> | |
| 3525 | <string>source.cpp</string> | |
| 3526 | </dict> | |
| 3527 | </array> | |
| 3528 | </dict> | |
| 3529 | </array> | |
| 3530 | </dict> | |
| 3531 | <dict> | |
| 3532 | <key>begin</key> | |
| 3533 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:ags|ags\x2dscript|asymptote|c\+\+|edje\x2ddata\x2dcollection|game\x2dmaker\x2dlanguage|swig|(?:.*\.)?(?:asc|ash|asy|c\+\+|cc|cp|cpp|cppm|cxx|edc|gml|h\+\+|hh|hpp|hxx|inl|ino|ipp|ixx|metal|re|tcc|tpp|txx)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3534 | <key>beginCaptures</key> | |
| 3535 | <dict> | |
| 3536 | <key>1</key> | |
| 3537 | <dict> | |
| 3538 | <key>name</key> | |
| 3539 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3540 | </dict> | |
| 3541 | <key>2</key> | |
| 3542 | <dict> | |
| 3543 | <key>name</key> | |
| 3544 | <string>entity.name.function.mdx</string> | |
| 3545 | <key>patterns</key> | |
| 3546 | <array> | |
| 3547 | <dict> | |
| 3548 | <key>include</key> | |
| 3549 | <string>#markdown-string</string> | |
| 3550 | </dict> | |
| 3551 | </array> | |
| 3552 | </dict> | |
| 3553 | <key>3</key> | |
| 3554 | <dict> | |
| 3555 | <key>patterns</key> | |
| 3556 | <array> | |
| 3557 | <dict> | |
| 3558 | <key>include</key> | |
| 3559 | <string>#markdown-string</string> | |
| 3560 | </dict> | |
| 3561 | </array> | |
| 3562 | </dict> | |
| 3563 | </dict> | |
| 3564 | <key>end</key> | |
| 3565 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3566 | <key>endCaptures</key> | |
| 3567 | <dict> | |
| 3568 | <key>1</key> | |
| 3569 | <dict> | |
| 3570 | <key>name</key> | |
| 3571 | <string>string.other.end.code.fenced.mdx</string> | |
| 3572 | </dict> | |
| 3573 | </dict> | |
| 3574 | <key>name</key> | |
| 3575 | <string>markup.code.cpp.mdx</string> | |
| 3576 | <key>patterns</key> | |
| 3577 | <array> | |
| 3578 | <dict> | |
| 3579 | <key>begin</key> | |
| 3580 | <string>(^|\G)(\s*)(.*)</string> | |
| 3581 | <key>while</key> | |
| 3582 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3583 | <key>contentName</key> | |
| 3584 | <string>meta.embedded.cpp</string> | |
| 3585 | <key>patterns</key> | |
| 3586 | <array> | |
| 3587 | <dict> | |
| 3588 | <key>include</key> | |
| 3589 | <string>source.c++</string> | |
| 3590 | </dict> | |
| 3591 | <dict> | |
| 3592 | <key>include</key> | |
| 3593 | <string>source.cpp</string> | |
| 3594 | </dict> | |
| 3595 | </array> | |
| 3596 | </dict> | |
| 3597 | </array> | |
| 3598 | </dict> | |
| 3599 | </array> | |
| 3600 | </dict> | |
| 3601 | <key>commonmark-code-fenced-cs</key> | |
| 3602 | <dict> | |
| 3603 | <key>patterns</key> | |
| 3604 | <array> | |
| 3605 | <dict> | |
| 3606 | <key>begin</key> | |
| 3607 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:beef|c#|cakescript|csharp|(?:.*\.)?(?:bf|cake|cs|cs\.pp|csx|eq|linq|uno)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3608 | <key>beginCaptures</key> | |
| 3609 | <dict> | |
| 3610 | <key>1</key> | |
| 3611 | <dict> | |
| 3612 | <key>name</key> | |
| 3613 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3614 | </dict> | |
| 3615 | <key>2</key> | |
| 3616 | <dict> | |
| 3617 | <key>name</key> | |
| 3618 | <string>entity.name.function.mdx</string> | |
| 3619 | <key>patterns</key> | |
| 3620 | <array> | |
| 3621 | <dict> | |
| 3622 | <key>include</key> | |
| 3623 | <string>#markdown-string</string> | |
| 3624 | </dict> | |
| 3625 | </array> | |
| 3626 | </dict> | |
| 3627 | <key>3</key> | |
| 3628 | <dict> | |
| 3629 | <key>patterns</key> | |
| 3630 | <array> | |
| 3631 | <dict> | |
| 3632 | <key>include</key> | |
| 3633 | <string>#markdown-string</string> | |
| 3634 | </dict> | |
| 3635 | </array> | |
| 3636 | </dict> | |
| 3637 | </dict> | |
| 3638 | <key>end</key> | |
| 3639 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3640 | <key>endCaptures</key> | |
| 3641 | <dict> | |
| 3642 | <key>1</key> | |
| 3643 | <dict> | |
| 3644 | <key>name</key> | |
| 3645 | <string>string.other.end.code.fenced.mdx</string> | |
| 3646 | </dict> | |
| 3647 | </dict> | |
| 3648 | <key>name</key> | |
| 3649 | <string>markup.code.cs.mdx</string> | |
| 3650 | <key>patterns</key> | |
| 3651 | <array> | |
| 3652 | <dict> | |
| 3653 | <key>begin</key> | |
| 3654 | <string>(^|\G)(\s*)(.*)</string> | |
| 3655 | <key>while</key> | |
| 3656 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3657 | <key>contentName</key> | |
| 3658 | <string>meta.embedded.cs</string> | |
| 3659 | <key>patterns</key> | |
| 3660 | <array> | |
| 3661 | <dict> | |
| 3662 | <key>include</key> | |
| 3663 | <string>source.cs</string> | |
| 3664 | </dict> | |
| 3665 | </array> | |
| 3666 | </dict> | |
| 3667 | </array> | |
| 3668 | </dict> | |
| 3669 | <dict> | |
| 3670 | <key>begin</key> | |
| 3671 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:beef|c#|cakescript|csharp|(?:.*\.)?(?:bf|cake|cs|cs\.pp|csx|eq|linq|uno)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3672 | <key>beginCaptures</key> | |
| 3673 | <dict> | |
| 3674 | <key>1</key> | |
| 3675 | <dict> | |
| 3676 | <key>name</key> | |
| 3677 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3678 | </dict> | |
| 3679 | <key>2</key> | |
| 3680 | <dict> | |
| 3681 | <key>name</key> | |
| 3682 | <string>entity.name.function.mdx</string> | |
| 3683 | <key>patterns</key> | |
| 3684 | <array> | |
| 3685 | <dict> | |
| 3686 | <key>include</key> | |
| 3687 | <string>#markdown-string</string> | |
| 3688 | </dict> | |
| 3689 | </array> | |
| 3690 | </dict> | |
| 3691 | <key>3</key> | |
| 3692 | <dict> | |
| 3693 | <key>patterns</key> | |
| 3694 | <array> | |
| 3695 | <dict> | |
| 3696 | <key>include</key> | |
| 3697 | <string>#markdown-string</string> | |
| 3698 | </dict> | |
| 3699 | </array> | |
| 3700 | </dict> | |
| 3701 | </dict> | |
| 3702 | <key>end</key> | |
| 3703 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3704 | <key>endCaptures</key> | |
| 3705 | <dict> | |
| 3706 | <key>1</key> | |
| 3707 | <dict> | |
| 3708 | <key>name</key> | |
| 3709 | <string>string.other.end.code.fenced.mdx</string> | |
| 3710 | </dict> | |
| 3711 | </dict> | |
| 3712 | <key>name</key> | |
| 3713 | <string>markup.code.cs.mdx</string> | |
| 3714 | <key>patterns</key> | |
| 3715 | <array> | |
| 3716 | <dict> | |
| 3717 | <key>begin</key> | |
| 3718 | <string>(^|\G)(\s*)(.*)</string> | |
| 3719 | <key>while</key> | |
| 3720 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3721 | <key>contentName</key> | |
| 3722 | <string>meta.embedded.cs</string> | |
| 3723 | <key>patterns</key> | |
| 3724 | <array> | |
| 3725 | <dict> | |
| 3726 | <key>include</key> | |
| 3727 | <string>source.cs</string> | |
| 3728 | </dict> | |
| 3729 | </array> | |
| 3730 | </dict> | |
| 3731 | </array> | |
| 3732 | </dict> | |
| 3733 | </array> | |
| 3734 | </dict> | |
| 3735 | <key>commonmark-code-fenced-css</key> | |
| 3736 | <dict> | |
| 3737 | <key>patterns</key> | |
| 3738 | <array> | |
| 3739 | <dict> | |
| 3740 | <key>begin</key> | |
| 3741 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?css))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3742 | <key>beginCaptures</key> | |
| 3743 | <dict> | |
| 3744 | <key>1</key> | |
| 3745 | <dict> | |
| 3746 | <key>name</key> | |
| 3747 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3748 | </dict> | |
| 3749 | <key>2</key> | |
| 3750 | <dict> | |
| 3751 | <key>name</key> | |
| 3752 | <string>entity.name.function.mdx</string> | |
| 3753 | <key>patterns</key> | |
| 3754 | <array> | |
| 3755 | <dict> | |
| 3756 | <key>include</key> | |
| 3757 | <string>#markdown-string</string> | |
| 3758 | </dict> | |
| 3759 | </array> | |
| 3760 | </dict> | |
| 3761 | <key>3</key> | |
| 3762 | <dict> | |
| 3763 | <key>patterns</key> | |
| 3764 | <array> | |
| 3765 | <dict> | |
| 3766 | <key>include</key> | |
| 3767 | <string>#markdown-string</string> | |
| 3768 | </dict> | |
| 3769 | </array> | |
| 3770 | </dict> | |
| 3771 | </dict> | |
| 3772 | <key>end</key> | |
| 3773 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3774 | <key>endCaptures</key> | |
| 3775 | <dict> | |
| 3776 | <key>1</key> | |
| 3777 | <dict> | |
| 3778 | <key>name</key> | |
| 3779 | <string>string.other.end.code.fenced.mdx</string> | |
| 3780 | </dict> | |
| 3781 | </dict> | |
| 3782 | <key>name</key> | |
| 3783 | <string>markup.code.css.mdx</string> | |
| 3784 | <key>patterns</key> | |
| 3785 | <array> | |
| 3786 | <dict> | |
| 3787 | <key>begin</key> | |
| 3788 | <string>(^|\G)(\s*)(.*)</string> | |
| 3789 | <key>while</key> | |
| 3790 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3791 | <key>contentName</key> | |
| 3792 | <string>meta.embedded.css</string> | |
| 3793 | <key>patterns</key> | |
| 3794 | <array> | |
| 3795 | <dict> | |
| 3796 | <key>include</key> | |
| 3797 | <string>source.css</string> | |
| 3798 | </dict> | |
| 3799 | </array> | |
| 3800 | </dict> | |
| 3801 | </array> | |
| 3802 | </dict> | |
| 3803 | <dict> | |
| 3804 | <key>begin</key> | |
| 3805 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?css))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3806 | <key>beginCaptures</key> | |
| 3807 | <dict> | |
| 3808 | <key>1</key> | |
| 3809 | <dict> | |
| 3810 | <key>name</key> | |
| 3811 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3812 | </dict> | |
| 3813 | <key>2</key> | |
| 3814 | <dict> | |
| 3815 | <key>name</key> | |
| 3816 | <string>entity.name.function.mdx</string> | |
| 3817 | <key>patterns</key> | |
| 3818 | <array> | |
| 3819 | <dict> | |
| 3820 | <key>include</key> | |
| 3821 | <string>#markdown-string</string> | |
| 3822 | </dict> | |
| 3823 | </array> | |
| 3824 | </dict> | |
| 3825 | <key>3</key> | |
| 3826 | <dict> | |
| 3827 | <key>patterns</key> | |
| 3828 | <array> | |
| 3829 | <dict> | |
| 3830 | <key>include</key> | |
| 3831 | <string>#markdown-string</string> | |
| 3832 | </dict> | |
| 3833 | </array> | |
| 3834 | </dict> | |
| 3835 | </dict> | |
| 3836 | <key>end</key> | |
| 3837 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3838 | <key>endCaptures</key> | |
| 3839 | <dict> | |
| 3840 | <key>1</key> | |
| 3841 | <dict> | |
| 3842 | <key>name</key> | |
| 3843 | <string>string.other.end.code.fenced.mdx</string> | |
| 3844 | </dict> | |
| 3845 | </dict> | |
| 3846 | <key>name</key> | |
| 3847 | <string>markup.code.css.mdx</string> | |
| 3848 | <key>patterns</key> | |
| 3849 | <array> | |
| 3850 | <dict> | |
| 3851 | <key>begin</key> | |
| 3852 | <string>(^|\G)(\s*)(.*)</string> | |
| 3853 | <key>while</key> | |
| 3854 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3855 | <key>contentName</key> | |
| 3856 | <string>meta.embedded.css</string> | |
| 3857 | <key>patterns</key> | |
| 3858 | <array> | |
| 3859 | <dict> | |
| 3860 | <key>include</key> | |
| 3861 | <string>source.css</string> | |
| 3862 | </dict> | |
| 3863 | </array> | |
| 3864 | </dict> | |
| 3865 | </array> | |
| 3866 | </dict> | |
| 3867 | </array> | |
| 3868 | </dict> | |
| 3869 | <key>commonmark-code-fenced-diff</key> | |
| 3870 | <dict> | |
| 3871 | <key>patterns</key> | |
| 3872 | <array> | |
| 3873 | <dict> | |
| 3874 | <key>begin</key> | |
| 3875 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:udiff|(?:.*\.)?(?:diff|patch)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 3876 | <key>beginCaptures</key> | |
| 3877 | <dict> | |
| 3878 | <key>1</key> | |
| 3879 | <dict> | |
| 3880 | <key>name</key> | |
| 3881 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3882 | </dict> | |
| 3883 | <key>2</key> | |
| 3884 | <dict> | |
| 3885 | <key>name</key> | |
| 3886 | <string>entity.name.function.mdx</string> | |
| 3887 | <key>patterns</key> | |
| 3888 | <array> | |
| 3889 | <dict> | |
| 3890 | <key>include</key> | |
| 3891 | <string>#markdown-string</string> | |
| 3892 | </dict> | |
| 3893 | </array> | |
| 3894 | </dict> | |
| 3895 | <key>3</key> | |
| 3896 | <dict> | |
| 3897 | <key>patterns</key> | |
| 3898 | <array> | |
| 3899 | <dict> | |
| 3900 | <key>include</key> | |
| 3901 | <string>#markdown-string</string> | |
| 3902 | </dict> | |
| 3903 | </array> | |
| 3904 | </dict> | |
| 3905 | </dict> | |
| 3906 | <key>end</key> | |
| 3907 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3908 | <key>endCaptures</key> | |
| 3909 | <dict> | |
| 3910 | <key>1</key> | |
| 3911 | <dict> | |
| 3912 | <key>name</key> | |
| 3913 | <string>string.other.end.code.fenced.mdx</string> | |
| 3914 | </dict> | |
| 3915 | </dict> | |
| 3916 | <key>name</key> | |
| 3917 | <string>markup.code.diff.mdx</string> | |
| 3918 | <key>patterns</key> | |
| 3919 | <array> | |
| 3920 | <dict> | |
| 3921 | <key>begin</key> | |
| 3922 | <string>(^|\G)(\s*)(.*)</string> | |
| 3923 | <key>while</key> | |
| 3924 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3925 | <key>contentName</key> | |
| 3926 | <string>meta.embedded.diff</string> | |
| 3927 | <key>patterns</key> | |
| 3928 | <array> | |
| 3929 | <dict> | |
| 3930 | <key>include</key> | |
| 3931 | <string>source.diff</string> | |
| 3932 | </dict> | |
| 3933 | </array> | |
| 3934 | </dict> | |
| 3935 | </array> | |
| 3936 | </dict> | |
| 3937 | <dict> | |
| 3938 | <key>begin</key> | |
| 3939 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:udiff|(?:.*\.)?(?:diff|patch)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 3940 | <key>beginCaptures</key> | |
| 3941 | <dict> | |
| 3942 | <key>1</key> | |
| 3943 | <dict> | |
| 3944 | <key>name</key> | |
| 3945 | <string>string.other.begin.code.fenced.mdx</string> | |
| 3946 | </dict> | |
| 3947 | <key>2</key> | |
| 3948 | <dict> | |
| 3949 | <key>name</key> | |
| 3950 | <string>entity.name.function.mdx</string> | |
| 3951 | <key>patterns</key> | |
| 3952 | <array> | |
| 3953 | <dict> | |
| 3954 | <key>include</key> | |
| 3955 | <string>#markdown-string</string> | |
| 3956 | </dict> | |
| 3957 | </array> | |
| 3958 | </dict> | |
| 3959 | <key>3</key> | |
| 3960 | <dict> | |
| 3961 | <key>patterns</key> | |
| 3962 | <array> | |
| 3963 | <dict> | |
| 3964 | <key>include</key> | |
| 3965 | <string>#markdown-string</string> | |
| 3966 | </dict> | |
| 3967 | </array> | |
| 3968 | </dict> | |
| 3969 | </dict> | |
| 3970 | <key>end</key> | |
| 3971 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 3972 | <key>endCaptures</key> | |
| 3973 | <dict> | |
| 3974 | <key>1</key> | |
| 3975 | <dict> | |
| 3976 | <key>name</key> | |
| 3977 | <string>string.other.end.code.fenced.mdx</string> | |
| 3978 | </dict> | |
| 3979 | </dict> | |
| 3980 | <key>name</key> | |
| 3981 | <string>markup.code.diff.mdx</string> | |
| 3982 | <key>patterns</key> | |
| 3983 | <array> | |
| 3984 | <dict> | |
| 3985 | <key>begin</key> | |
| 3986 | <string>(^|\G)(\s*)(.*)</string> | |
| 3987 | <key>while</key> | |
| 3988 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 3989 | <key>contentName</key> | |
| 3990 | <string>meta.embedded.diff</string> | |
| 3991 | <key>patterns</key> | |
| 3992 | <array> | |
| 3993 | <dict> | |
| 3994 | <key>include</key> | |
| 3995 | <string>source.diff</string> | |
| 3996 | </dict> | |
| 3997 | </array> | |
| 3998 | </dict> | |
| 3999 | </array> | |
| 4000 | </dict> | |
| 4001 | </array> | |
| 4002 | </dict> | |
| 4003 | <key>commonmark-code-fenced-dockerfile</key> | |
| 4004 | <dict> | |
| 4005 | <key>patterns</key> | |
| 4006 | <array> | |
| 4007 | <dict> | |
| 4008 | <key>begin</key> | |
| 4009 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:containerfile|(?:.*\.)?dockerfile))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4010 | <key>beginCaptures</key> | |
| 4011 | <dict> | |
| 4012 | <key>1</key> | |
| 4013 | <dict> | |
| 4014 | <key>name</key> | |
| 4015 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4016 | </dict> | |
| 4017 | <key>2</key> | |
| 4018 | <dict> | |
| 4019 | <key>name</key> | |
| 4020 | <string>entity.name.function.mdx</string> | |
| 4021 | <key>patterns</key> | |
| 4022 | <array> | |
| 4023 | <dict> | |
| 4024 | <key>include</key> | |
| 4025 | <string>#markdown-string</string> | |
| 4026 | </dict> | |
| 4027 | </array> | |
| 4028 | </dict> | |
| 4029 | <key>3</key> | |
| 4030 | <dict> | |
| 4031 | <key>patterns</key> | |
| 4032 | <array> | |
| 4033 | <dict> | |
| 4034 | <key>include</key> | |
| 4035 | <string>#markdown-string</string> | |
| 4036 | </dict> | |
| 4037 | </array> | |
| 4038 | </dict> | |
| 4039 | </dict> | |
| 4040 | <key>end</key> | |
| 4041 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4042 | <key>endCaptures</key> | |
| 4043 | <dict> | |
| 4044 | <key>1</key> | |
| 4045 | <dict> | |
| 4046 | <key>name</key> | |
| 4047 | <string>string.other.end.code.fenced.mdx</string> | |
| 4048 | </dict> | |
| 4049 | </dict> | |
| 4050 | <key>name</key> | |
| 4051 | <string>markup.code.dockerfile.mdx</string> | |
| 4052 | <key>patterns</key> | |
| 4053 | <array> | |
| 4054 | <dict> | |
| 4055 | <key>begin</key> | |
| 4056 | <string>(^|\G)(\s*)(.*)</string> | |
| 4057 | <key>while</key> | |
| 4058 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4059 | <key>contentName</key> | |
| 4060 | <string>meta.embedded.dockerfile</string> | |
| 4061 | <key>patterns</key> | |
| 4062 | <array> | |
| 4063 | <dict> | |
| 4064 | <key>include</key> | |
| 4065 | <string>source.dockerfile</string> | |
| 4066 | </dict> | |
| 4067 | </array> | |
| 4068 | </dict> | |
| 4069 | </array> | |
| 4070 | </dict> | |
| 4071 | <dict> | |
| 4072 | <key>begin</key> | |
| 4073 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:containerfile|(?:.*\.)?dockerfile))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4074 | <key>beginCaptures</key> | |
| 4075 | <dict> | |
| 4076 | <key>1</key> | |
| 4077 | <dict> | |
| 4078 | <key>name</key> | |
| 4079 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4080 | </dict> | |
| 4081 | <key>2</key> | |
| 4082 | <dict> | |
| 4083 | <key>name</key> | |
| 4084 | <string>entity.name.function.mdx</string> | |
| 4085 | <key>patterns</key> | |
| 4086 | <array> | |
| 4087 | <dict> | |
| 4088 | <key>include</key> | |
| 4089 | <string>#markdown-string</string> | |
| 4090 | </dict> | |
| 4091 | </array> | |
| 4092 | </dict> | |
| 4093 | <key>3</key> | |
| 4094 | <dict> | |
| 4095 | <key>patterns</key> | |
| 4096 | <array> | |
| 4097 | <dict> | |
| 4098 | <key>include</key> | |
| 4099 | <string>#markdown-string</string> | |
| 4100 | </dict> | |
| 4101 | </array> | |
| 4102 | </dict> | |
| 4103 | </dict> | |
| 4104 | <key>end</key> | |
| 4105 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4106 | <key>endCaptures</key> | |
| 4107 | <dict> | |
| 4108 | <key>1</key> | |
| 4109 | <dict> | |
| 4110 | <key>name</key> | |
| 4111 | <string>string.other.end.code.fenced.mdx</string> | |
| 4112 | </dict> | |
| 4113 | </dict> | |
| 4114 | <key>name</key> | |
| 4115 | <string>markup.code.dockerfile.mdx</string> | |
| 4116 | <key>patterns</key> | |
| 4117 | <array> | |
| 4118 | <dict> | |
| 4119 | <key>begin</key> | |
| 4120 | <string>(^|\G)(\s*)(.*)</string> | |
| 4121 | <key>while</key> | |
| 4122 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4123 | <key>contentName</key> | |
| 4124 | <string>meta.embedded.dockerfile</string> | |
| 4125 | <key>patterns</key> | |
| 4126 | <array> | |
| 4127 | <dict> | |
| 4128 | <key>include</key> | |
| 4129 | <string>source.dockerfile</string> | |
| 4130 | </dict> | |
| 4131 | </array> | |
| 4132 | </dict> | |
| 4133 | </array> | |
| 4134 | </dict> | |
| 4135 | </array> | |
| 4136 | </dict> | |
| 4137 | <key>commonmark-code-fenced-elixir</key> | |
| 4138 | <dict> | |
| 4139 | <key>patterns</key> | |
| 4140 | <array> | |
| 4141 | <dict> | |
| 4142 | <key>begin</key> | |
| 4143 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:elixir|(?:.*\.)?(?:ex|exs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4144 | <key>beginCaptures</key> | |
| 4145 | <dict> | |
| 4146 | <key>1</key> | |
| 4147 | <dict> | |
| 4148 | <key>name</key> | |
| 4149 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4150 | </dict> | |
| 4151 | <key>2</key> | |
| 4152 | <dict> | |
| 4153 | <key>name</key> | |
| 4154 | <string>entity.name.function.mdx</string> | |
| 4155 | <key>patterns</key> | |
| 4156 | <array> | |
| 4157 | <dict> | |
| 4158 | <key>include</key> | |
| 4159 | <string>#markdown-string</string> | |
| 4160 | </dict> | |
| 4161 | </array> | |
| 4162 | </dict> | |
| 4163 | <key>3</key> | |
| 4164 | <dict> | |
| 4165 | <key>patterns</key> | |
| 4166 | <array> | |
| 4167 | <dict> | |
| 4168 | <key>include</key> | |
| 4169 | <string>#markdown-string</string> | |
| 4170 | </dict> | |
| 4171 | </array> | |
| 4172 | </dict> | |
| 4173 | </dict> | |
| 4174 | <key>end</key> | |
| 4175 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4176 | <key>endCaptures</key> | |
| 4177 | <dict> | |
| 4178 | <key>1</key> | |
| 4179 | <dict> | |
| 4180 | <key>name</key> | |
| 4181 | <string>string.other.end.code.fenced.mdx</string> | |
| 4182 | </dict> | |
| 4183 | </dict> | |
| 4184 | <key>name</key> | |
| 4185 | <string>markup.code.elixir.mdx</string> | |
| 4186 | <key>patterns</key> | |
| 4187 | <array> | |
| 4188 | <dict> | |
| 4189 | <key>begin</key> | |
| 4190 | <string>(^|\G)(\s*)(.*)</string> | |
| 4191 | <key>while</key> | |
| 4192 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4193 | <key>contentName</key> | |
| 4194 | <string>meta.embedded.elixir</string> | |
| 4195 | <key>patterns</key> | |
| 4196 | <array> | |
| 4197 | <dict> | |
| 4198 | <key>include</key> | |
| 4199 | <string>source.elixir</string> | |
| 4200 | </dict> | |
| 4201 | </array> | |
| 4202 | </dict> | |
| 4203 | </array> | |
| 4204 | </dict> | |
| 4205 | <dict> | |
| 4206 | <key>begin</key> | |
| 4207 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:elixir|(?:.*\.)?(?:ex|exs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4208 | <key>beginCaptures</key> | |
| 4209 | <dict> | |
| 4210 | <key>1</key> | |
| 4211 | <dict> | |
| 4212 | <key>name</key> | |
| 4213 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4214 | </dict> | |
| 4215 | <key>2</key> | |
| 4216 | <dict> | |
| 4217 | <key>name</key> | |
| 4218 | <string>entity.name.function.mdx</string> | |
| 4219 | <key>patterns</key> | |
| 4220 | <array> | |
| 4221 | <dict> | |
| 4222 | <key>include</key> | |
| 4223 | <string>#markdown-string</string> | |
| 4224 | </dict> | |
| 4225 | </array> | |
| 4226 | </dict> | |
| 4227 | <key>3</key> | |
| 4228 | <dict> | |
| 4229 | <key>patterns</key> | |
| 4230 | <array> | |
| 4231 | <dict> | |
| 4232 | <key>include</key> | |
| 4233 | <string>#markdown-string</string> | |
| 4234 | </dict> | |
| 4235 | </array> | |
| 4236 | </dict> | |
| 4237 | </dict> | |
| 4238 | <key>end</key> | |
| 4239 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4240 | <key>endCaptures</key> | |
| 4241 | <dict> | |
| 4242 | <key>1</key> | |
| 4243 | <dict> | |
| 4244 | <key>name</key> | |
| 4245 | <string>string.other.end.code.fenced.mdx</string> | |
| 4246 | </dict> | |
| 4247 | </dict> | |
| 4248 | <key>name</key> | |
| 4249 | <string>markup.code.elixir.mdx</string> | |
| 4250 | <key>patterns</key> | |
| 4251 | <array> | |
| 4252 | <dict> | |
| 4253 | <key>begin</key> | |
| 4254 | <string>(^|\G)(\s*)(.*)</string> | |
| 4255 | <key>while</key> | |
| 4256 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4257 | <key>contentName</key> | |
| 4258 | <string>meta.embedded.elixir</string> | |
| 4259 | <key>patterns</key> | |
| 4260 | <array> | |
| 4261 | <dict> | |
| 4262 | <key>include</key> | |
| 4263 | <string>source.elixir</string> | |
| 4264 | </dict> | |
| 4265 | </array> | |
| 4266 | </dict> | |
| 4267 | </array> | |
| 4268 | </dict> | |
| 4269 | </array> | |
| 4270 | </dict> | |
| 4271 | <key>commonmark-code-fenced-elm</key> | |
| 4272 | <dict> | |
| 4273 | <key>patterns</key> | |
| 4274 | <array> | |
| 4275 | <dict> | |
| 4276 | <key>begin</key> | |
| 4277 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?elm))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4278 | <key>beginCaptures</key> | |
| 4279 | <dict> | |
| 4280 | <key>1</key> | |
| 4281 | <dict> | |
| 4282 | <key>name</key> | |
| 4283 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4284 | </dict> | |
| 4285 | <key>2</key> | |
| 4286 | <dict> | |
| 4287 | <key>name</key> | |
| 4288 | <string>entity.name.function.mdx</string> | |
| 4289 | <key>patterns</key> | |
| 4290 | <array> | |
| 4291 | <dict> | |
| 4292 | <key>include</key> | |
| 4293 | <string>#markdown-string</string> | |
| 4294 | </dict> | |
| 4295 | </array> | |
| 4296 | </dict> | |
| 4297 | <key>3</key> | |
| 4298 | <dict> | |
| 4299 | <key>patterns</key> | |
| 4300 | <array> | |
| 4301 | <dict> | |
| 4302 | <key>include</key> | |
| 4303 | <string>#markdown-string</string> | |
| 4304 | </dict> | |
| 4305 | </array> | |
| 4306 | </dict> | |
| 4307 | </dict> | |
| 4308 | <key>end</key> | |
| 4309 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4310 | <key>endCaptures</key> | |
| 4311 | <dict> | |
| 4312 | <key>1</key> | |
| 4313 | <dict> | |
| 4314 | <key>name</key> | |
| 4315 | <string>string.other.end.code.fenced.mdx</string> | |
| 4316 | </dict> | |
| 4317 | </dict> | |
| 4318 | <key>name</key> | |
| 4319 | <string>markup.code.elm.mdx</string> | |
| 4320 | <key>patterns</key> | |
| 4321 | <array> | |
| 4322 | <dict> | |
| 4323 | <key>begin</key> | |
| 4324 | <string>(^|\G)(\s*)(.*)</string> | |
| 4325 | <key>while</key> | |
| 4326 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4327 | <key>contentName</key> | |
| 4328 | <string>meta.embedded.elm</string> | |
| 4329 | <key>patterns</key> | |
| 4330 | <array> | |
| 4331 | <dict> | |
| 4332 | <key>include</key> | |
| 4333 | <string>source.elm</string> | |
| 4334 | </dict> | |
| 4335 | </array> | |
| 4336 | </dict> | |
| 4337 | </array> | |
| 4338 | </dict> | |
| 4339 | <dict> | |
| 4340 | <key>begin</key> | |
| 4341 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?elm))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4342 | <key>beginCaptures</key> | |
| 4343 | <dict> | |
| 4344 | <key>1</key> | |
| 4345 | <dict> | |
| 4346 | <key>name</key> | |
| 4347 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4348 | </dict> | |
| 4349 | <key>2</key> | |
| 4350 | <dict> | |
| 4351 | <key>name</key> | |
| 4352 | <string>entity.name.function.mdx</string> | |
| 4353 | <key>patterns</key> | |
| 4354 | <array> | |
| 4355 | <dict> | |
| 4356 | <key>include</key> | |
| 4357 | <string>#markdown-string</string> | |
| 4358 | </dict> | |
| 4359 | </array> | |
| 4360 | </dict> | |
| 4361 | <key>3</key> | |
| 4362 | <dict> | |
| 4363 | <key>patterns</key> | |
| 4364 | <array> | |
| 4365 | <dict> | |
| 4366 | <key>include</key> | |
| 4367 | <string>#markdown-string</string> | |
| 4368 | </dict> | |
| 4369 | </array> | |
| 4370 | </dict> | |
| 4371 | </dict> | |
| 4372 | <key>end</key> | |
| 4373 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4374 | <key>endCaptures</key> | |
| 4375 | <dict> | |
| 4376 | <key>1</key> | |
| 4377 | <dict> | |
| 4378 | <key>name</key> | |
| 4379 | <string>string.other.end.code.fenced.mdx</string> | |
| 4380 | </dict> | |
| 4381 | </dict> | |
| 4382 | <key>name</key> | |
| 4383 | <string>markup.code.elm.mdx</string> | |
| 4384 | <key>patterns</key> | |
| 4385 | <array> | |
| 4386 | <dict> | |
| 4387 | <key>begin</key> | |
| 4388 | <string>(^|\G)(\s*)(.*)</string> | |
| 4389 | <key>while</key> | |
| 4390 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4391 | <key>contentName</key> | |
| 4392 | <string>meta.embedded.elm</string> | |
| 4393 | <key>patterns</key> | |
| 4394 | <array> | |
| 4395 | <dict> | |
| 4396 | <key>include</key> | |
| 4397 | <string>source.elm</string> | |
| 4398 | </dict> | |
| 4399 | </array> | |
| 4400 | </dict> | |
| 4401 | </array> | |
| 4402 | </dict> | |
| 4403 | </array> | |
| 4404 | </dict> | |
| 4405 | <key>commonmark-code-fenced-erlang</key> | |
| 4406 | <dict> | |
| 4407 | <key>patterns</key> | |
| 4408 | <array> | |
| 4409 | <dict> | |
| 4410 | <key>begin</key> | |
| 4411 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:erlang|(?:.*\.)?(?:app|app\.src|erl|es|escript|hrl|xrl|yrl)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4412 | <key>beginCaptures</key> | |
| 4413 | <dict> | |
| 4414 | <key>1</key> | |
| 4415 | <dict> | |
| 4416 | <key>name</key> | |
| 4417 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4418 | </dict> | |
| 4419 | <key>2</key> | |
| 4420 | <dict> | |
| 4421 | <key>name</key> | |
| 4422 | <string>entity.name.function.mdx</string> | |
| 4423 | <key>patterns</key> | |
| 4424 | <array> | |
| 4425 | <dict> | |
| 4426 | <key>include</key> | |
| 4427 | <string>#markdown-string</string> | |
| 4428 | </dict> | |
| 4429 | </array> | |
| 4430 | </dict> | |
| 4431 | <key>3</key> | |
| 4432 | <dict> | |
| 4433 | <key>patterns</key> | |
| 4434 | <array> | |
| 4435 | <dict> | |
| 4436 | <key>include</key> | |
| 4437 | <string>#markdown-string</string> | |
| 4438 | </dict> | |
| 4439 | </array> | |
| 4440 | </dict> | |
| 4441 | </dict> | |
| 4442 | <key>end</key> | |
| 4443 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4444 | <key>endCaptures</key> | |
| 4445 | <dict> | |
| 4446 | <key>1</key> | |
| 4447 | <dict> | |
| 4448 | <key>name</key> | |
| 4449 | <string>string.other.end.code.fenced.mdx</string> | |
| 4450 | </dict> | |
| 4451 | </dict> | |
| 4452 | <key>name</key> | |
| 4453 | <string>markup.code.erlang.mdx</string> | |
| 4454 | <key>patterns</key> | |
| 4455 | <array> | |
| 4456 | <dict> | |
| 4457 | <key>begin</key> | |
| 4458 | <string>(^|\G)(\s*)(.*)</string> | |
| 4459 | <key>while</key> | |
| 4460 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4461 | <key>contentName</key> | |
| 4462 | <string>meta.embedded.erlang</string> | |
| 4463 | <key>patterns</key> | |
| 4464 | <array> | |
| 4465 | <dict> | |
| 4466 | <key>include</key> | |
| 4467 | <string>source.erlang</string> | |
| 4468 | </dict> | |
| 4469 | </array> | |
| 4470 | </dict> | |
| 4471 | </array> | |
| 4472 | </dict> | |
| 4473 | <dict> | |
| 4474 | <key>begin</key> | |
| 4475 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:erlang|(?:.*\.)?(?:app|app\.src|erl|es|escript|hrl|xrl|yrl)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4476 | <key>beginCaptures</key> | |
| 4477 | <dict> | |
| 4478 | <key>1</key> | |
| 4479 | <dict> | |
| 4480 | <key>name</key> | |
| 4481 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4482 | </dict> | |
| 4483 | <key>2</key> | |
| 4484 | <dict> | |
| 4485 | <key>name</key> | |
| 4486 | <string>entity.name.function.mdx</string> | |
| 4487 | <key>patterns</key> | |
| 4488 | <array> | |
| 4489 | <dict> | |
| 4490 | <key>include</key> | |
| 4491 | <string>#markdown-string</string> | |
| 4492 | </dict> | |
| 4493 | </array> | |
| 4494 | </dict> | |
| 4495 | <key>3</key> | |
| 4496 | <dict> | |
| 4497 | <key>patterns</key> | |
| 4498 | <array> | |
| 4499 | <dict> | |
| 4500 | <key>include</key> | |
| 4501 | <string>#markdown-string</string> | |
| 4502 | </dict> | |
| 4503 | </array> | |
| 4504 | </dict> | |
| 4505 | </dict> | |
| 4506 | <key>end</key> | |
| 4507 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4508 | <key>endCaptures</key> | |
| 4509 | <dict> | |
| 4510 | <key>1</key> | |
| 4511 | <dict> | |
| 4512 | <key>name</key> | |
| 4513 | <string>string.other.end.code.fenced.mdx</string> | |
| 4514 | </dict> | |
| 4515 | </dict> | |
| 4516 | <key>name</key> | |
| 4517 | <string>markup.code.erlang.mdx</string> | |
| 4518 | <key>patterns</key> | |
| 4519 | <array> | |
| 4520 | <dict> | |
| 4521 | <key>begin</key> | |
| 4522 | <string>(^|\G)(\s*)(.*)</string> | |
| 4523 | <key>while</key> | |
| 4524 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4525 | <key>contentName</key> | |
| 4526 | <string>meta.embedded.erlang</string> | |
| 4527 | <key>patterns</key> | |
| 4528 | <array> | |
| 4529 | <dict> | |
| 4530 | <key>include</key> | |
| 4531 | <string>source.erlang</string> | |
| 4532 | </dict> | |
| 4533 | </array> | |
| 4534 | </dict> | |
| 4535 | </array> | |
| 4536 | </dict> | |
| 4537 | </array> | |
| 4538 | </dict> | |
| 4539 | <key>commonmark-code-fenced-gitconfig</key> | |
| 4540 | <dict> | |
| 4541 | <key>patterns</key> | |
| 4542 | <array> | |
| 4543 | <dict> | |
| 4544 | <key>begin</key> | |
| 4545 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:git\x2dconfig|gitmodules|(?:.*\.)?gitconfig))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4546 | <key>beginCaptures</key> | |
| 4547 | <dict> | |
| 4548 | <key>1</key> | |
| 4549 | <dict> | |
| 4550 | <key>name</key> | |
| 4551 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4552 | </dict> | |
| 4553 | <key>2</key> | |
| 4554 | <dict> | |
| 4555 | <key>name</key> | |
| 4556 | <string>entity.name.function.mdx</string> | |
| 4557 | <key>patterns</key> | |
| 4558 | <array> | |
| 4559 | <dict> | |
| 4560 | <key>include</key> | |
| 4561 | <string>#markdown-string</string> | |
| 4562 | </dict> | |
| 4563 | </array> | |
| 4564 | </dict> | |
| 4565 | <key>3</key> | |
| 4566 | <dict> | |
| 4567 | <key>patterns</key> | |
| 4568 | <array> | |
| 4569 | <dict> | |
| 4570 | <key>include</key> | |
| 4571 | <string>#markdown-string</string> | |
| 4572 | </dict> | |
| 4573 | </array> | |
| 4574 | </dict> | |
| 4575 | </dict> | |
| 4576 | <key>end</key> | |
| 4577 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4578 | <key>endCaptures</key> | |
| 4579 | <dict> | |
| 4580 | <key>1</key> | |
| 4581 | <dict> | |
| 4582 | <key>name</key> | |
| 4583 | <string>string.other.end.code.fenced.mdx</string> | |
| 4584 | </dict> | |
| 4585 | </dict> | |
| 4586 | <key>name</key> | |
| 4587 | <string>markup.code.gitconfig.mdx</string> | |
| 4588 | <key>patterns</key> | |
| 4589 | <array> | |
| 4590 | <dict> | |
| 4591 | <key>begin</key> | |
| 4592 | <string>(^|\G)(\s*)(.*)</string> | |
| 4593 | <key>while</key> | |
| 4594 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4595 | <key>contentName</key> | |
| 4596 | <string>meta.embedded.gitconfig</string> | |
| 4597 | <key>patterns</key> | |
| 4598 | <array> | |
| 4599 | <dict> | |
| 4600 | <key>include</key> | |
| 4601 | <string>source.gitconfig</string> | |
| 4602 | </dict> | |
| 4603 | </array> | |
| 4604 | </dict> | |
| 4605 | </array> | |
| 4606 | </dict> | |
| 4607 | <dict> | |
| 4608 | <key>begin</key> | |
| 4609 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:git\x2dconfig|gitmodules|(?:.*\.)?gitconfig))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4610 | <key>beginCaptures</key> | |
| 4611 | <dict> | |
| 4612 | <key>1</key> | |
| 4613 | <dict> | |
| 4614 | <key>name</key> | |
| 4615 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4616 | </dict> | |
| 4617 | <key>2</key> | |
| 4618 | <dict> | |
| 4619 | <key>name</key> | |
| 4620 | <string>entity.name.function.mdx</string> | |
| 4621 | <key>patterns</key> | |
| 4622 | <array> | |
| 4623 | <dict> | |
| 4624 | <key>include</key> | |
| 4625 | <string>#markdown-string</string> | |
| 4626 | </dict> | |
| 4627 | </array> | |
| 4628 | </dict> | |
| 4629 | <key>3</key> | |
| 4630 | <dict> | |
| 4631 | <key>patterns</key> | |
| 4632 | <array> | |
| 4633 | <dict> | |
| 4634 | <key>include</key> | |
| 4635 | <string>#markdown-string</string> | |
| 4636 | </dict> | |
| 4637 | </array> | |
| 4638 | </dict> | |
| 4639 | </dict> | |
| 4640 | <key>end</key> | |
| 4641 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4642 | <key>endCaptures</key> | |
| 4643 | <dict> | |
| 4644 | <key>1</key> | |
| 4645 | <dict> | |
| 4646 | <key>name</key> | |
| 4647 | <string>string.other.end.code.fenced.mdx</string> | |
| 4648 | </dict> | |
| 4649 | </dict> | |
| 4650 | <key>name</key> | |
| 4651 | <string>markup.code.gitconfig.mdx</string> | |
| 4652 | <key>patterns</key> | |
| 4653 | <array> | |
| 4654 | <dict> | |
| 4655 | <key>begin</key> | |
| 4656 | <string>(^|\G)(\s*)(.*)</string> | |
| 4657 | <key>while</key> | |
| 4658 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4659 | <key>contentName</key> | |
| 4660 | <string>meta.embedded.gitconfig</string> | |
| 4661 | <key>patterns</key> | |
| 4662 | <array> | |
| 4663 | <dict> | |
| 4664 | <key>include</key> | |
| 4665 | <string>source.gitconfig</string> | |
| 4666 | </dict> | |
| 4667 | </array> | |
| 4668 | </dict> | |
| 4669 | </array> | |
| 4670 | </dict> | |
| 4671 | </array> | |
| 4672 | </dict> | |
| 4673 | <key>commonmark-code-fenced-go</key> | |
| 4674 | <dict> | |
| 4675 | <key>patterns</key> | |
| 4676 | <array> | |
| 4677 | <dict> | |
| 4678 | <key>begin</key> | |
| 4679 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:golang|(?:.*\.)?go))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4680 | <key>beginCaptures</key> | |
| 4681 | <dict> | |
| 4682 | <key>1</key> | |
| 4683 | <dict> | |
| 4684 | <key>name</key> | |
| 4685 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4686 | </dict> | |
| 4687 | <key>2</key> | |
| 4688 | <dict> | |
| 4689 | <key>name</key> | |
| 4690 | <string>entity.name.function.mdx</string> | |
| 4691 | <key>patterns</key> | |
| 4692 | <array> | |
| 4693 | <dict> | |
| 4694 | <key>include</key> | |
| 4695 | <string>#markdown-string</string> | |
| 4696 | </dict> | |
| 4697 | </array> | |
| 4698 | </dict> | |
| 4699 | <key>3</key> | |
| 4700 | <dict> | |
| 4701 | <key>patterns</key> | |
| 4702 | <array> | |
| 4703 | <dict> | |
| 4704 | <key>include</key> | |
| 4705 | <string>#markdown-string</string> | |
| 4706 | </dict> | |
| 4707 | </array> | |
| 4708 | </dict> | |
| 4709 | </dict> | |
| 4710 | <key>end</key> | |
| 4711 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4712 | <key>endCaptures</key> | |
| 4713 | <dict> | |
| 4714 | <key>1</key> | |
| 4715 | <dict> | |
| 4716 | <key>name</key> | |
| 4717 | <string>string.other.end.code.fenced.mdx</string> | |
| 4718 | </dict> | |
| 4719 | </dict> | |
| 4720 | <key>name</key> | |
| 4721 | <string>markup.code.go.mdx</string> | |
| 4722 | <key>patterns</key> | |
| 4723 | <array> | |
| 4724 | <dict> | |
| 4725 | <key>begin</key> | |
| 4726 | <string>(^|\G)(\s*)(.*)</string> | |
| 4727 | <key>while</key> | |
| 4728 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4729 | <key>contentName</key> | |
| 4730 | <string>meta.embedded.go</string> | |
| 4731 | <key>patterns</key> | |
| 4732 | <array> | |
| 4733 | <dict> | |
| 4734 | <key>include</key> | |
| 4735 | <string>source.go</string> | |
| 4736 | </dict> | |
| 4737 | </array> | |
| 4738 | </dict> | |
| 4739 | </array> | |
| 4740 | </dict> | |
| 4741 | <dict> | |
| 4742 | <key>begin</key> | |
| 4743 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:golang|(?:.*\.)?go))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4744 | <key>beginCaptures</key> | |
| 4745 | <dict> | |
| 4746 | <key>1</key> | |
| 4747 | <dict> | |
| 4748 | <key>name</key> | |
| 4749 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4750 | </dict> | |
| 4751 | <key>2</key> | |
| 4752 | <dict> | |
| 4753 | <key>name</key> | |
| 4754 | <string>entity.name.function.mdx</string> | |
| 4755 | <key>patterns</key> | |
| 4756 | <array> | |
| 4757 | <dict> | |
| 4758 | <key>include</key> | |
| 4759 | <string>#markdown-string</string> | |
| 4760 | </dict> | |
| 4761 | </array> | |
| 4762 | </dict> | |
| 4763 | <key>3</key> | |
| 4764 | <dict> | |
| 4765 | <key>patterns</key> | |
| 4766 | <array> | |
| 4767 | <dict> | |
| 4768 | <key>include</key> | |
| 4769 | <string>#markdown-string</string> | |
| 4770 | </dict> | |
| 4771 | </array> | |
| 4772 | </dict> | |
| 4773 | </dict> | |
| 4774 | <key>end</key> | |
| 4775 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4776 | <key>endCaptures</key> | |
| 4777 | <dict> | |
| 4778 | <key>1</key> | |
| 4779 | <dict> | |
| 4780 | <key>name</key> | |
| 4781 | <string>string.other.end.code.fenced.mdx</string> | |
| 4782 | </dict> | |
| 4783 | </dict> | |
| 4784 | <key>name</key> | |
| 4785 | <string>markup.code.go.mdx</string> | |
| 4786 | <key>patterns</key> | |
| 4787 | <array> | |
| 4788 | <dict> | |
| 4789 | <key>begin</key> | |
| 4790 | <string>(^|\G)(\s*)(.*)</string> | |
| 4791 | <key>while</key> | |
| 4792 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4793 | <key>contentName</key> | |
| 4794 | <string>meta.embedded.go</string> | |
| 4795 | <key>patterns</key> | |
| 4796 | <array> | |
| 4797 | <dict> | |
| 4798 | <key>include</key> | |
| 4799 | <string>source.go</string> | |
| 4800 | </dict> | |
| 4801 | </array> | |
| 4802 | </dict> | |
| 4803 | </array> | |
| 4804 | </dict> | |
| 4805 | </array> | |
| 4806 | </dict> | |
| 4807 | <key>commonmark-code-fenced-graphql</key> | |
| 4808 | <dict> | |
| 4809 | <key>patterns</key> | |
| 4810 | <array> | |
| 4811 | <dict> | |
| 4812 | <key>begin</key> | |
| 4813 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:gql|graphql|graphqls)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4814 | <key>beginCaptures</key> | |
| 4815 | <dict> | |
| 4816 | <key>1</key> | |
| 4817 | <dict> | |
| 4818 | <key>name</key> | |
| 4819 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4820 | </dict> | |
| 4821 | <key>2</key> | |
| 4822 | <dict> | |
| 4823 | <key>name</key> | |
| 4824 | <string>entity.name.function.mdx</string> | |
| 4825 | <key>patterns</key> | |
| 4826 | <array> | |
| 4827 | <dict> | |
| 4828 | <key>include</key> | |
| 4829 | <string>#markdown-string</string> | |
| 4830 | </dict> | |
| 4831 | </array> | |
| 4832 | </dict> | |
| 4833 | <key>3</key> | |
| 4834 | <dict> | |
| 4835 | <key>patterns</key> | |
| 4836 | <array> | |
| 4837 | <dict> | |
| 4838 | <key>include</key> | |
| 4839 | <string>#markdown-string</string> | |
| 4840 | </dict> | |
| 4841 | </array> | |
| 4842 | </dict> | |
| 4843 | </dict> | |
| 4844 | <key>end</key> | |
| 4845 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4846 | <key>endCaptures</key> | |
| 4847 | <dict> | |
| 4848 | <key>1</key> | |
| 4849 | <dict> | |
| 4850 | <key>name</key> | |
| 4851 | <string>string.other.end.code.fenced.mdx</string> | |
| 4852 | </dict> | |
| 4853 | </dict> | |
| 4854 | <key>name</key> | |
| 4855 | <string>markup.code.graphql.mdx</string> | |
| 4856 | <key>patterns</key> | |
| 4857 | <array> | |
| 4858 | <dict> | |
| 4859 | <key>begin</key> | |
| 4860 | <string>(^|\G)(\s*)(.*)</string> | |
| 4861 | <key>while</key> | |
| 4862 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4863 | <key>contentName</key> | |
| 4864 | <string>meta.embedded.graphql</string> | |
| 4865 | <key>patterns</key> | |
| 4866 | <array> | |
| 4867 | <dict> | |
| 4868 | <key>include</key> | |
| 4869 | <string>source.graphql</string> | |
| 4870 | </dict> | |
| 4871 | </array> | |
| 4872 | </dict> | |
| 4873 | </array> | |
| 4874 | </dict> | |
| 4875 | <dict> | |
| 4876 | <key>begin</key> | |
| 4877 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:gql|graphql|graphqls)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 4878 | <key>beginCaptures</key> | |
| 4879 | <dict> | |
| 4880 | <key>1</key> | |
| 4881 | <dict> | |
| 4882 | <key>name</key> | |
| 4883 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4884 | </dict> | |
| 4885 | <key>2</key> | |
| 4886 | <dict> | |
| 4887 | <key>name</key> | |
| 4888 | <string>entity.name.function.mdx</string> | |
| 4889 | <key>patterns</key> | |
| 4890 | <array> | |
| 4891 | <dict> | |
| 4892 | <key>include</key> | |
| 4893 | <string>#markdown-string</string> | |
| 4894 | </dict> | |
| 4895 | </array> | |
| 4896 | </dict> | |
| 4897 | <key>3</key> | |
| 4898 | <dict> | |
| 4899 | <key>patterns</key> | |
| 4900 | <array> | |
| 4901 | <dict> | |
| 4902 | <key>include</key> | |
| 4903 | <string>#markdown-string</string> | |
| 4904 | </dict> | |
| 4905 | </array> | |
| 4906 | </dict> | |
| 4907 | </dict> | |
| 4908 | <key>end</key> | |
| 4909 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4910 | <key>endCaptures</key> | |
| 4911 | <dict> | |
| 4912 | <key>1</key> | |
| 4913 | <dict> | |
| 4914 | <key>name</key> | |
| 4915 | <string>string.other.end.code.fenced.mdx</string> | |
| 4916 | </dict> | |
| 4917 | </dict> | |
| 4918 | <key>name</key> | |
| 4919 | <string>markup.code.graphql.mdx</string> | |
| 4920 | <key>patterns</key> | |
| 4921 | <array> | |
| 4922 | <dict> | |
| 4923 | <key>begin</key> | |
| 4924 | <string>(^|\G)(\s*)(.*)</string> | |
| 4925 | <key>while</key> | |
| 4926 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4927 | <key>contentName</key> | |
| 4928 | <string>meta.embedded.graphql</string> | |
| 4929 | <key>patterns</key> | |
| 4930 | <array> | |
| 4931 | <dict> | |
| 4932 | <key>include</key> | |
| 4933 | <string>source.graphql</string> | |
| 4934 | </dict> | |
| 4935 | </array> | |
| 4936 | </dict> | |
| 4937 | </array> | |
| 4938 | </dict> | |
| 4939 | </array> | |
| 4940 | </dict> | |
| 4941 | <key>commonmark-code-fenced-haskell</key> | |
| 4942 | <dict> | |
| 4943 | <key>patterns</key> | |
| 4944 | <array> | |
| 4945 | <dict> | |
| 4946 | <key>begin</key> | |
| 4947 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:c2hs|c2hs\x2dhaskell|frege|haskell|(?:.*\.)?(?:chs|dhall|hs|hs\x2dboot|hsc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 4948 | <key>beginCaptures</key> | |
| 4949 | <dict> | |
| 4950 | <key>1</key> | |
| 4951 | <dict> | |
| 4952 | <key>name</key> | |
| 4953 | <string>string.other.begin.code.fenced.mdx</string> | |
| 4954 | </dict> | |
| 4955 | <key>2</key> | |
| 4956 | <dict> | |
| 4957 | <key>name</key> | |
| 4958 | <string>entity.name.function.mdx</string> | |
| 4959 | <key>patterns</key> | |
| 4960 | <array> | |
| 4961 | <dict> | |
| 4962 | <key>include</key> | |
| 4963 | <string>#markdown-string</string> | |
| 4964 | </dict> | |
| 4965 | </array> | |
| 4966 | </dict> | |
| 4967 | <key>3</key> | |
| 4968 | <dict> | |
| 4969 | <key>patterns</key> | |
| 4970 | <array> | |
| 4971 | <dict> | |
| 4972 | <key>include</key> | |
| 4973 | <string>#markdown-string</string> | |
| 4974 | </dict> | |
| 4975 | </array> | |
| 4976 | </dict> | |
| 4977 | </dict> | |
| 4978 | <key>end</key> | |
| 4979 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 4980 | <key>endCaptures</key> | |
| 4981 | <dict> | |
| 4982 | <key>1</key> | |
| 4983 | <dict> | |
| 4984 | <key>name</key> | |
| 4985 | <string>string.other.end.code.fenced.mdx</string> | |
| 4986 | </dict> | |
| 4987 | </dict> | |
| 4988 | <key>name</key> | |
| 4989 | <string>markup.code.haskell.mdx</string> | |
| 4990 | <key>patterns</key> | |
| 4991 | <array> | |
| 4992 | <dict> | |
| 4993 | <key>begin</key> | |
| 4994 | <string>(^|\G)(\s*)(.*)</string> | |
| 4995 | <key>while</key> | |
| 4996 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 4997 | <key>contentName</key> | |
| 4998 | <string>meta.embedded.haskell</string> | |
| 4999 | <key>patterns</key> | |
| 5000 | <array> | |
| 5001 | <dict> | |
| 5002 | <key>include</key> | |
| 5003 | <string>source.haskell</string> | |
| 5004 | </dict> | |
| 5005 | </array> | |
| 5006 | </dict> | |
| 5007 | </array> | |
| 5008 | </dict> | |
| 5009 | <dict> | |
| 5010 | <key>begin</key> | |
| 5011 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:c2hs|c2hs\x2dhaskell|frege|haskell|(?:.*\.)?(?:chs|dhall|hs|hs\x2dboot|hsc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5012 | <key>beginCaptures</key> | |
| 5013 | <dict> | |
| 5014 | <key>1</key> | |
| 5015 | <dict> | |
| 5016 | <key>name</key> | |
| 5017 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5018 | </dict> | |
| 5019 | <key>2</key> | |
| 5020 | <dict> | |
| 5021 | <key>name</key> | |
| 5022 | <string>entity.name.function.mdx</string> | |
| 5023 | <key>patterns</key> | |
| 5024 | <array> | |
| 5025 | <dict> | |
| 5026 | <key>include</key> | |
| 5027 | <string>#markdown-string</string> | |
| 5028 | </dict> | |
| 5029 | </array> | |
| 5030 | </dict> | |
| 5031 | <key>3</key> | |
| 5032 | <dict> | |
| 5033 | <key>patterns</key> | |
| 5034 | <array> | |
| 5035 | <dict> | |
| 5036 | <key>include</key> | |
| 5037 | <string>#markdown-string</string> | |
| 5038 | </dict> | |
| 5039 | </array> | |
| 5040 | </dict> | |
| 5041 | </dict> | |
| 5042 | <key>end</key> | |
| 5043 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5044 | <key>endCaptures</key> | |
| 5045 | <dict> | |
| 5046 | <key>1</key> | |
| 5047 | <dict> | |
| 5048 | <key>name</key> | |
| 5049 | <string>string.other.end.code.fenced.mdx</string> | |
| 5050 | </dict> | |
| 5051 | </dict> | |
| 5052 | <key>name</key> | |
| 5053 | <string>markup.code.haskell.mdx</string> | |
| 5054 | <key>patterns</key> | |
| 5055 | <array> | |
| 5056 | <dict> | |
| 5057 | <key>begin</key> | |
| 5058 | <string>(^|\G)(\s*)(.*)</string> | |
| 5059 | <key>while</key> | |
| 5060 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5061 | <key>contentName</key> | |
| 5062 | <string>meta.embedded.haskell</string> | |
| 5063 | <key>patterns</key> | |
| 5064 | <array> | |
| 5065 | <dict> | |
| 5066 | <key>include</key> | |
| 5067 | <string>source.haskell</string> | |
| 5068 | </dict> | |
| 5069 | </array> | |
| 5070 | </dict> | |
| 5071 | </array> | |
| 5072 | </dict> | |
| 5073 | </array> | |
| 5074 | </dict> | |
| 5075 | <key>commonmark-code-fenced-html</key> | |
| 5076 | <dict> | |
| 5077 | <key>patterns</key> | |
| 5078 | <array> | |
| 5079 | <dict> | |
| 5080 | <key>begin</key> | |
| 5081 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:html|(?:.*\.)?(?:hta|htm|html\.hl|kit|mtml|xht|xhtml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5082 | <key>beginCaptures</key> | |
| 5083 | <dict> | |
| 5084 | <key>1</key> | |
| 5085 | <dict> | |
| 5086 | <key>name</key> | |
| 5087 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5088 | </dict> | |
| 5089 | <key>2</key> | |
| 5090 | <dict> | |
| 5091 | <key>name</key> | |
| 5092 | <string>entity.name.function.mdx</string> | |
| 5093 | <key>patterns</key> | |
| 5094 | <array> | |
| 5095 | <dict> | |
| 5096 | <key>include</key> | |
| 5097 | <string>#markdown-string</string> | |
| 5098 | </dict> | |
| 5099 | </array> | |
| 5100 | </dict> | |
| 5101 | <key>3</key> | |
| 5102 | <dict> | |
| 5103 | <key>patterns</key> | |
| 5104 | <array> | |
| 5105 | <dict> | |
| 5106 | <key>include</key> | |
| 5107 | <string>#markdown-string</string> | |
| 5108 | </dict> | |
| 5109 | </array> | |
| 5110 | </dict> | |
| 5111 | </dict> | |
| 5112 | <key>end</key> | |
| 5113 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5114 | <key>endCaptures</key> | |
| 5115 | <dict> | |
| 5116 | <key>1</key> | |
| 5117 | <dict> | |
| 5118 | <key>name</key> | |
| 5119 | <string>string.other.end.code.fenced.mdx</string> | |
| 5120 | </dict> | |
| 5121 | </dict> | |
| 5122 | <key>name</key> | |
| 5123 | <string>markup.code.html.mdx</string> | |
| 5124 | <key>patterns</key> | |
| 5125 | <array> | |
| 5126 | <dict> | |
| 5127 | <key>begin</key> | |
| 5128 | <string>(^|\G)(\s*)(.*)</string> | |
| 5129 | <key>while</key> | |
| 5130 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5131 | <key>contentName</key> | |
| 5132 | <string>meta.embedded.html</string> | |
| 5133 | <key>patterns</key> | |
| 5134 | <array> | |
| 5135 | <dict> | |
| 5136 | <key>include</key> | |
| 5137 | <string>text.html.basic</string> | |
| 5138 | </dict> | |
| 5139 | </array> | |
| 5140 | </dict> | |
| 5141 | </array> | |
| 5142 | </dict> | |
| 5143 | <dict> | |
| 5144 | <key>begin</key> | |
| 5145 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:html|(?:.*\.)?(?:hta|htm|html\.hl|kit|mtml|xht|xhtml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5146 | <key>beginCaptures</key> | |
| 5147 | <dict> | |
| 5148 | <key>1</key> | |
| 5149 | <dict> | |
| 5150 | <key>name</key> | |
| 5151 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5152 | </dict> | |
| 5153 | <key>2</key> | |
| 5154 | <dict> | |
| 5155 | <key>name</key> | |
| 5156 | <string>entity.name.function.mdx</string> | |
| 5157 | <key>patterns</key> | |
| 5158 | <array> | |
| 5159 | <dict> | |
| 5160 | <key>include</key> | |
| 5161 | <string>#markdown-string</string> | |
| 5162 | </dict> | |
| 5163 | </array> | |
| 5164 | </dict> | |
| 5165 | <key>3</key> | |
| 5166 | <dict> | |
| 5167 | <key>patterns</key> | |
| 5168 | <array> | |
| 5169 | <dict> | |
| 5170 | <key>include</key> | |
| 5171 | <string>#markdown-string</string> | |
| 5172 | </dict> | |
| 5173 | </array> | |
| 5174 | </dict> | |
| 5175 | </dict> | |
| 5176 | <key>end</key> | |
| 5177 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5178 | <key>endCaptures</key> | |
| 5179 | <dict> | |
| 5180 | <key>1</key> | |
| 5181 | <dict> | |
| 5182 | <key>name</key> | |
| 5183 | <string>string.other.end.code.fenced.mdx</string> | |
| 5184 | </dict> | |
| 5185 | </dict> | |
| 5186 | <key>name</key> | |
| 5187 | <string>markup.code.html.mdx</string> | |
| 5188 | <key>patterns</key> | |
| 5189 | <array> | |
| 5190 | <dict> | |
| 5191 | <key>begin</key> | |
| 5192 | <string>(^|\G)(\s*)(.*)</string> | |
| 5193 | <key>while</key> | |
| 5194 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5195 | <key>contentName</key> | |
| 5196 | <string>meta.embedded.html</string> | |
| 5197 | <key>patterns</key> | |
| 5198 | <array> | |
| 5199 | <dict> | |
| 5200 | <key>include</key> | |
| 5201 | <string>text.html.basic</string> | |
| 5202 | </dict> | |
| 5203 | </array> | |
| 5204 | </dict> | |
| 5205 | </array> | |
| 5206 | </dict> | |
| 5207 | </array> | |
| 5208 | </dict> | |
| 5209 | <key>commonmark-code-fenced-ini</key> | |
| 5210 | <dict> | |
| 5211 | <key>patterns</key> | |
| 5212 | <array> | |
| 5213 | <dict> | |
| 5214 | <key>begin</key> | |
| 5215 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:altium|altium\x2ddesigner|dosini|(?:.*\.)?(?:cnf|dof|ini|lektorproject|outjob|pcbdoc|prefs|prjpcb|properties|schdoc|url)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5216 | <key>beginCaptures</key> | |
| 5217 | <dict> | |
| 5218 | <key>1</key> | |
| 5219 | <dict> | |
| 5220 | <key>name</key> | |
| 5221 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5222 | </dict> | |
| 5223 | <key>2</key> | |
| 5224 | <dict> | |
| 5225 | <key>name</key> | |
| 5226 | <string>entity.name.function.mdx</string> | |
| 5227 | <key>patterns</key> | |
| 5228 | <array> | |
| 5229 | <dict> | |
| 5230 | <key>include</key> | |
| 5231 | <string>#markdown-string</string> | |
| 5232 | </dict> | |
| 5233 | </array> | |
| 5234 | </dict> | |
| 5235 | <key>3</key> | |
| 5236 | <dict> | |
| 5237 | <key>patterns</key> | |
| 5238 | <array> | |
| 5239 | <dict> | |
| 5240 | <key>include</key> | |
| 5241 | <string>#markdown-string</string> | |
| 5242 | </dict> | |
| 5243 | </array> | |
| 5244 | </dict> | |
| 5245 | </dict> | |
| 5246 | <key>end</key> | |
| 5247 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5248 | <key>endCaptures</key> | |
| 5249 | <dict> | |
| 5250 | <key>1</key> | |
| 5251 | <dict> | |
| 5252 | <key>name</key> | |
| 5253 | <string>string.other.end.code.fenced.mdx</string> | |
| 5254 | </dict> | |
| 5255 | </dict> | |
| 5256 | <key>name</key> | |
| 5257 | <string>markup.code.ini.mdx</string> | |
| 5258 | <key>patterns</key> | |
| 5259 | <array> | |
| 5260 | <dict> | |
| 5261 | <key>begin</key> | |
| 5262 | <string>(^|\G)(\s*)(.*)</string> | |
| 5263 | <key>while</key> | |
| 5264 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5265 | <key>contentName</key> | |
| 5266 | <string>meta.embedded.ini</string> | |
| 5267 | <key>patterns</key> | |
| 5268 | <array> | |
| 5269 | <dict> | |
| 5270 | <key>include</key> | |
| 5271 | <string>source.ini</string> | |
| 5272 | </dict> | |
| 5273 | </array> | |
| 5274 | </dict> | |
| 5275 | </array> | |
| 5276 | </dict> | |
| 5277 | <dict> | |
| 5278 | <key>begin</key> | |
| 5279 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:altium|altium\x2ddesigner|dosini|(?:.*\.)?(?:cnf|dof|ini|lektorproject|outjob|pcbdoc|prefs|prjpcb|properties|schdoc|url)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5280 | <key>beginCaptures</key> | |
| 5281 | <dict> | |
| 5282 | <key>1</key> | |
| 5283 | <dict> | |
| 5284 | <key>name</key> | |
| 5285 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5286 | </dict> | |
| 5287 | <key>2</key> | |
| 5288 | <dict> | |
| 5289 | <key>name</key> | |
| 5290 | <string>entity.name.function.mdx</string> | |
| 5291 | <key>patterns</key> | |
| 5292 | <array> | |
| 5293 | <dict> | |
| 5294 | <key>include</key> | |
| 5295 | <string>#markdown-string</string> | |
| 5296 | </dict> | |
| 5297 | </array> | |
| 5298 | </dict> | |
| 5299 | <key>3</key> | |
| 5300 | <dict> | |
| 5301 | <key>patterns</key> | |
| 5302 | <array> | |
| 5303 | <dict> | |
| 5304 | <key>include</key> | |
| 5305 | <string>#markdown-string</string> | |
| 5306 | </dict> | |
| 5307 | </array> | |
| 5308 | </dict> | |
| 5309 | </dict> | |
| 5310 | <key>end</key> | |
| 5311 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5312 | <key>endCaptures</key> | |
| 5313 | <dict> | |
| 5314 | <key>1</key> | |
| 5315 | <dict> | |
| 5316 | <key>name</key> | |
| 5317 | <string>string.other.end.code.fenced.mdx</string> | |
| 5318 | </dict> | |
| 5319 | </dict> | |
| 5320 | <key>name</key> | |
| 5321 | <string>markup.code.ini.mdx</string> | |
| 5322 | <key>patterns</key> | |
| 5323 | <array> | |
| 5324 | <dict> | |
| 5325 | <key>begin</key> | |
| 5326 | <string>(^|\G)(\s*)(.*)</string> | |
| 5327 | <key>while</key> | |
| 5328 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5329 | <key>contentName</key> | |
| 5330 | <string>meta.embedded.ini</string> | |
| 5331 | <key>patterns</key> | |
| 5332 | <array> | |
| 5333 | <dict> | |
| 5334 | <key>include</key> | |
| 5335 | <string>source.ini</string> | |
| 5336 | </dict> | |
| 5337 | </array> | |
| 5338 | </dict> | |
| 5339 | </array> | |
| 5340 | </dict> | |
| 5341 | </array> | |
| 5342 | </dict> | |
| 5343 | <key>commonmark-code-fenced-java</key> | |
| 5344 | <dict> | |
| 5345 | <key>patterns</key> | |
| 5346 | <array> | |
| 5347 | <dict> | |
| 5348 | <key>begin</key> | |
| 5349 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:chuck|unrealscript|(?:.*\.)?(?:ck|jav|java|jsh|uc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5350 | <key>beginCaptures</key> | |
| 5351 | <dict> | |
| 5352 | <key>1</key> | |
| 5353 | <dict> | |
| 5354 | <key>name</key> | |
| 5355 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5356 | </dict> | |
| 5357 | <key>2</key> | |
| 5358 | <dict> | |
| 5359 | <key>name</key> | |
| 5360 | <string>entity.name.function.mdx</string> | |
| 5361 | <key>patterns</key> | |
| 5362 | <array> | |
| 5363 | <dict> | |
| 5364 | <key>include</key> | |
| 5365 | <string>#markdown-string</string> | |
| 5366 | </dict> | |
| 5367 | </array> | |
| 5368 | </dict> | |
| 5369 | <key>3</key> | |
| 5370 | <dict> | |
| 5371 | <key>patterns</key> | |
| 5372 | <array> | |
| 5373 | <dict> | |
| 5374 | <key>include</key> | |
| 5375 | <string>#markdown-string</string> | |
| 5376 | </dict> | |
| 5377 | </array> | |
| 5378 | </dict> | |
| 5379 | </dict> | |
| 5380 | <key>end</key> | |
| 5381 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5382 | <key>endCaptures</key> | |
| 5383 | <dict> | |
| 5384 | <key>1</key> | |
| 5385 | <dict> | |
| 5386 | <key>name</key> | |
| 5387 | <string>string.other.end.code.fenced.mdx</string> | |
| 5388 | </dict> | |
| 5389 | </dict> | |
| 5390 | <key>name</key> | |
| 5391 | <string>markup.code.java.mdx</string> | |
| 5392 | <key>patterns</key> | |
| 5393 | <array> | |
| 5394 | <dict> | |
| 5395 | <key>begin</key> | |
| 5396 | <string>(^|\G)(\s*)(.*)</string> | |
| 5397 | <key>while</key> | |
| 5398 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5399 | <key>contentName</key> | |
| 5400 | <string>meta.embedded.java</string> | |
| 5401 | <key>patterns</key> | |
| 5402 | <array> | |
| 5403 | <dict> | |
| 5404 | <key>include</key> | |
| 5405 | <string>source.java</string> | |
| 5406 | </dict> | |
| 5407 | </array> | |
| 5408 | </dict> | |
| 5409 | </array> | |
| 5410 | </dict> | |
| 5411 | <dict> | |
| 5412 | <key>begin</key> | |
| 5413 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:chuck|unrealscript|(?:.*\.)?(?:ck|jav|java|jsh|uc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5414 | <key>beginCaptures</key> | |
| 5415 | <dict> | |
| 5416 | <key>1</key> | |
| 5417 | <dict> | |
| 5418 | <key>name</key> | |
| 5419 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5420 | </dict> | |
| 5421 | <key>2</key> | |
| 5422 | <dict> | |
| 5423 | <key>name</key> | |
| 5424 | <string>entity.name.function.mdx</string> | |
| 5425 | <key>patterns</key> | |
| 5426 | <array> | |
| 5427 | <dict> | |
| 5428 | <key>include</key> | |
| 5429 | <string>#markdown-string</string> | |
| 5430 | </dict> | |
| 5431 | </array> | |
| 5432 | </dict> | |
| 5433 | <key>3</key> | |
| 5434 | <dict> | |
| 5435 | <key>patterns</key> | |
| 5436 | <array> | |
| 5437 | <dict> | |
| 5438 | <key>include</key> | |
| 5439 | <string>#markdown-string</string> | |
| 5440 | </dict> | |
| 5441 | </array> | |
| 5442 | </dict> | |
| 5443 | </dict> | |
| 5444 | <key>end</key> | |
| 5445 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5446 | <key>endCaptures</key> | |
| 5447 | <dict> | |
| 5448 | <key>1</key> | |
| 5449 | <dict> | |
| 5450 | <key>name</key> | |
| 5451 | <string>string.other.end.code.fenced.mdx</string> | |
| 5452 | </dict> | |
| 5453 | </dict> | |
| 5454 | <key>name</key> | |
| 5455 | <string>markup.code.java.mdx</string> | |
| 5456 | <key>patterns</key> | |
| 5457 | <array> | |
| 5458 | <dict> | |
| 5459 | <key>begin</key> | |
| 5460 | <string>(^|\G)(\s*)(.*)</string> | |
| 5461 | <key>while</key> | |
| 5462 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5463 | <key>contentName</key> | |
| 5464 | <string>meta.embedded.java</string> | |
| 5465 | <key>patterns</key> | |
| 5466 | <array> | |
| 5467 | <dict> | |
| 5468 | <key>include</key> | |
| 5469 | <string>source.java</string> | |
| 5470 | </dict> | |
| 5471 | </array> | |
| 5472 | </dict> | |
| 5473 | </array> | |
| 5474 | </dict> | |
| 5475 | </array> | |
| 5476 | </dict> | |
| 5477 | <key>commonmark-code-fenced-js</key> | |
| 5478 | <dict> | |
| 5479 | <key>patterns</key> | |
| 5480 | <array> | |
| 5481 | <dict> | |
| 5482 | <key>begin</key> | |
| 5483 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:cycript|javascript\+erb|json\x2dwith\x2dcomments|node|qt\x2dscript|(?:.*\.)?(?:_js|bones|cjs|code\x2dsnippets|code\x2dworkspace|cy|es6|jake|javascript|js|js\.erb|jsb|jscad|jsfl|jslib|jsm|json5|jsonc|jsonld|jspre|jss|jsx|mjs|njs|pac|sjs|ssjs|sublime\x2dbuild|sublime\x2dcolor\x2dscheme|sublime\x2dcommands|sublime\x2dcompletions|sublime\x2dkeymap|sublime\x2dmacro|sublime\x2dmenu|sublime\x2dmousemap|sublime\x2dproject|sublime\x2dsettings|sublime\x2dtheme|sublime\x2dworkspace|sublime_metrics|sublime_session|xsjs|xsjslib)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5484 | <key>beginCaptures</key> | |
| 5485 | <dict> | |
| 5486 | <key>1</key> | |
| 5487 | <dict> | |
| 5488 | <key>name</key> | |
| 5489 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5490 | </dict> | |
| 5491 | <key>2</key> | |
| 5492 | <dict> | |
| 5493 | <key>name</key> | |
| 5494 | <string>entity.name.function.mdx</string> | |
| 5495 | <key>patterns</key> | |
| 5496 | <array> | |
| 5497 | <dict> | |
| 5498 | <key>include</key> | |
| 5499 | <string>#markdown-string</string> | |
| 5500 | </dict> | |
| 5501 | </array> | |
| 5502 | </dict> | |
| 5503 | <key>3</key> | |
| 5504 | <dict> | |
| 5505 | <key>patterns</key> | |
| 5506 | <array> | |
| 5507 | <dict> | |
| 5508 | <key>include</key> | |
| 5509 | <string>#markdown-string</string> | |
| 5510 | </dict> | |
| 5511 | </array> | |
| 5512 | </dict> | |
| 5513 | </dict> | |
| 5514 | <key>end</key> | |
| 5515 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5516 | <key>endCaptures</key> | |
| 5517 | <dict> | |
| 5518 | <key>1</key> | |
| 5519 | <dict> | |
| 5520 | <key>name</key> | |
| 5521 | <string>string.other.end.code.fenced.mdx</string> | |
| 5522 | </dict> | |
| 5523 | </dict> | |
| 5524 | <key>name</key> | |
| 5525 | <string>markup.code.js.mdx</string> | |
| 5526 | <key>patterns</key> | |
| 5527 | <array> | |
| 5528 | <dict> | |
| 5529 | <key>begin</key> | |
| 5530 | <string>(^|\G)(\s*)(.*)</string> | |
| 5531 | <key>while</key> | |
| 5532 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5533 | <key>contentName</key> | |
| 5534 | <string>meta.embedded.js</string> | |
| 5535 | <key>patterns</key> | |
| 5536 | <array> | |
| 5537 | <dict> | |
| 5538 | <key>include</key> | |
| 5539 | <string>source.js</string> | |
| 5540 | </dict> | |
| 5541 | </array> | |
| 5542 | </dict> | |
| 5543 | </array> | |
| 5544 | </dict> | |
| 5545 | <dict> | |
| 5546 | <key>begin</key> | |
| 5547 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:cycript|javascript\+erb|json\x2dwith\x2dcomments|node|qt\x2dscript|(?:.*\.)?(?:_js|bones|cjs|code\x2dsnippets|code\x2dworkspace|cy|es6|jake|javascript|js|js\.erb|jsb|jscad|jsfl|jslib|jsm|json5|jsonc|jsonld|jspre|jss|jsx|mjs|njs|pac|sjs|ssjs|sublime\x2dbuild|sublime\x2dcolor\x2dscheme|sublime\x2dcommands|sublime\x2dcompletions|sublime\x2dkeymap|sublime\x2dmacro|sublime\x2dmenu|sublime\x2dmousemap|sublime\x2dproject|sublime\x2dsettings|sublime\x2dtheme|sublime\x2dworkspace|sublime_metrics|sublime_session|xsjs|xsjslib)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5548 | <key>beginCaptures</key> | |
| 5549 | <dict> | |
| 5550 | <key>1</key> | |
| 5551 | <dict> | |
| 5552 | <key>name</key> | |
| 5553 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5554 | </dict> | |
| 5555 | <key>2</key> | |
| 5556 | <dict> | |
| 5557 | <key>name</key> | |
| 5558 | <string>entity.name.function.mdx</string> | |
| 5559 | <key>patterns</key> | |
| 5560 | <array> | |
| 5561 | <dict> | |
| 5562 | <key>include</key> | |
| 5563 | <string>#markdown-string</string> | |
| 5564 | </dict> | |
| 5565 | </array> | |
| 5566 | </dict> | |
| 5567 | <key>3</key> | |
| 5568 | <dict> | |
| 5569 | <key>patterns</key> | |
| 5570 | <array> | |
| 5571 | <dict> | |
| 5572 | <key>include</key> | |
| 5573 | <string>#markdown-string</string> | |
| 5574 | </dict> | |
| 5575 | </array> | |
| 5576 | </dict> | |
| 5577 | </dict> | |
| 5578 | <key>end</key> | |
| 5579 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5580 | <key>endCaptures</key> | |
| 5581 | <dict> | |
| 5582 | <key>1</key> | |
| 5583 | <dict> | |
| 5584 | <key>name</key> | |
| 5585 | <string>string.other.end.code.fenced.mdx</string> | |
| 5586 | </dict> | |
| 5587 | </dict> | |
| 5588 | <key>name</key> | |
| 5589 | <string>markup.code.js.mdx</string> | |
| 5590 | <key>patterns</key> | |
| 5591 | <array> | |
| 5592 | <dict> | |
| 5593 | <key>begin</key> | |
| 5594 | <string>(^|\G)(\s*)(.*)</string> | |
| 5595 | <key>while</key> | |
| 5596 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5597 | <key>contentName</key> | |
| 5598 | <string>meta.embedded.js</string> | |
| 5599 | <key>patterns</key> | |
| 5600 | <array> | |
| 5601 | <dict> | |
| 5602 | <key>include</key> | |
| 5603 | <string>source.js</string> | |
| 5604 | </dict> | |
| 5605 | </array> | |
| 5606 | </dict> | |
| 5607 | </array> | |
| 5608 | </dict> | |
| 5609 | </array> | |
| 5610 | </dict> | |
| 5611 | <key>commonmark-code-fenced-json</key> | |
| 5612 | <dict> | |
| 5613 | <key>patterns</key> | |
| 5614 | <array> | |
| 5615 | <dict> | |
| 5616 | <key>begin</key> | |
| 5617 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:ecere\x2dprojects|ipython\x2dnotebook|jupyter\x2dnotebook|max|max/msp|maxmsp|oasv2\x2djson|oasv3\x2djson|(?:.*\.)?(?:4dform|4dproject|avsc|epj|geojson|gltf|har|ice|ipynb|json|json|json|json\x2dtmlanguage|jsonl|maxhelp|maxpat|maxproj|mcmeta|mxt|pat|sarif|tfstate|tfstate\.backup|topojson|webapp|webmanifest|yy|yyp)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5618 | <key>beginCaptures</key> | |
| 5619 | <dict> | |
| 5620 | <key>1</key> | |
| 5621 | <dict> | |
| 5622 | <key>name</key> | |
| 5623 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5624 | </dict> | |
| 5625 | <key>2</key> | |
| 5626 | <dict> | |
| 5627 | <key>name</key> | |
| 5628 | <string>entity.name.function.mdx</string> | |
| 5629 | <key>patterns</key> | |
| 5630 | <array> | |
| 5631 | <dict> | |
| 5632 | <key>include</key> | |
| 5633 | <string>#markdown-string</string> | |
| 5634 | </dict> | |
| 5635 | </array> | |
| 5636 | </dict> | |
| 5637 | <key>3</key> | |
| 5638 | <dict> | |
| 5639 | <key>patterns</key> | |
| 5640 | <array> | |
| 5641 | <dict> | |
| 5642 | <key>include</key> | |
| 5643 | <string>#markdown-string</string> | |
| 5644 | </dict> | |
| 5645 | </array> | |
| 5646 | </dict> | |
| 5647 | </dict> | |
| 5648 | <key>end</key> | |
| 5649 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5650 | <key>endCaptures</key> | |
| 5651 | <dict> | |
| 5652 | <key>1</key> | |
| 5653 | <dict> | |
| 5654 | <key>name</key> | |
| 5655 | <string>string.other.end.code.fenced.mdx</string> | |
| 5656 | </dict> | |
| 5657 | </dict> | |
| 5658 | <key>name</key> | |
| 5659 | <string>markup.code.json.mdx</string> | |
| 5660 | <key>patterns</key> | |
| 5661 | <array> | |
| 5662 | <dict> | |
| 5663 | <key>begin</key> | |
| 5664 | <string>(^|\G)(\s*)(.*)</string> | |
| 5665 | <key>while</key> | |
| 5666 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5667 | <key>contentName</key> | |
| 5668 | <string>meta.embedded.json</string> | |
| 5669 | <key>patterns</key> | |
| 5670 | <array> | |
| 5671 | <dict> | |
| 5672 | <key>include</key> | |
| 5673 | <string>source.json</string> | |
| 5674 | </dict> | |
| 5675 | </array> | |
| 5676 | </dict> | |
| 5677 | </array> | |
| 5678 | </dict> | |
| 5679 | <dict> | |
| 5680 | <key>begin</key> | |
| 5681 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:ecere\x2dprojects|ipython\x2dnotebook|jupyter\x2dnotebook|max|max/msp|maxmsp|oasv2\x2djson|oasv3\x2djson|(?:.*\.)?(?:4dform|4dproject|avsc|epj|geojson|gltf|har|ice|ipynb|json|json|json|json\x2dtmlanguage|jsonl|maxhelp|maxpat|maxproj|mcmeta|mxt|pat|sarif|tfstate|tfstate\.backup|topojson|webapp|webmanifest|yy|yyp)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5682 | <key>beginCaptures</key> | |
| 5683 | <dict> | |
| 5684 | <key>1</key> | |
| 5685 | <dict> | |
| 5686 | <key>name</key> | |
| 5687 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5688 | </dict> | |
| 5689 | <key>2</key> | |
| 5690 | <dict> | |
| 5691 | <key>name</key> | |
| 5692 | <string>entity.name.function.mdx</string> | |
| 5693 | <key>patterns</key> | |
| 5694 | <array> | |
| 5695 | <dict> | |
| 5696 | <key>include</key> | |
| 5697 | <string>#markdown-string</string> | |
| 5698 | </dict> | |
| 5699 | </array> | |
| 5700 | </dict> | |
| 5701 | <key>3</key> | |
| 5702 | <dict> | |
| 5703 | <key>patterns</key> | |
| 5704 | <array> | |
| 5705 | <dict> | |
| 5706 | <key>include</key> | |
| 5707 | <string>#markdown-string</string> | |
| 5708 | </dict> | |
| 5709 | </array> | |
| 5710 | </dict> | |
| 5711 | </dict> | |
| 5712 | <key>end</key> | |
| 5713 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5714 | <key>endCaptures</key> | |
| 5715 | <dict> | |
| 5716 | <key>1</key> | |
| 5717 | <dict> | |
| 5718 | <key>name</key> | |
| 5719 | <string>string.other.end.code.fenced.mdx</string> | |
| 5720 | </dict> | |
| 5721 | </dict> | |
| 5722 | <key>name</key> | |
| 5723 | <string>markup.code.json.mdx</string> | |
| 5724 | <key>patterns</key> | |
| 5725 | <array> | |
| 5726 | <dict> | |
| 5727 | <key>begin</key> | |
| 5728 | <string>(^|\G)(\s*)(.*)</string> | |
| 5729 | <key>while</key> | |
| 5730 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5731 | <key>contentName</key> | |
| 5732 | <string>meta.embedded.json</string> | |
| 5733 | <key>patterns</key> | |
| 5734 | <array> | |
| 5735 | <dict> | |
| 5736 | <key>include</key> | |
| 5737 | <string>source.json</string> | |
| 5738 | </dict> | |
| 5739 | </array> | |
| 5740 | </dict> | |
| 5741 | </array> | |
| 5742 | </dict> | |
| 5743 | </array> | |
| 5744 | </dict> | |
| 5745 | <key>commonmark-code-fenced-julia</key> | |
| 5746 | <dict> | |
| 5747 | <key>patterns</key> | |
| 5748 | <array> | |
| 5749 | <dict> | |
| 5750 | <key>begin</key> | |
| 5751 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:julia|(?:.*\.)?jl))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5752 | <key>beginCaptures</key> | |
| 5753 | <dict> | |
| 5754 | <key>1</key> | |
| 5755 | <dict> | |
| 5756 | <key>name</key> | |
| 5757 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5758 | </dict> | |
| 5759 | <key>2</key> | |
| 5760 | <dict> | |
| 5761 | <key>name</key> | |
| 5762 | <string>entity.name.function.mdx</string> | |
| 5763 | <key>patterns</key> | |
| 5764 | <array> | |
| 5765 | <dict> | |
| 5766 | <key>include</key> | |
| 5767 | <string>#markdown-string</string> | |
| 5768 | </dict> | |
| 5769 | </array> | |
| 5770 | </dict> | |
| 5771 | <key>3</key> | |
| 5772 | <dict> | |
| 5773 | <key>patterns</key> | |
| 5774 | <array> | |
| 5775 | <dict> | |
| 5776 | <key>include</key> | |
| 5777 | <string>#markdown-string</string> | |
| 5778 | </dict> | |
| 5779 | </array> | |
| 5780 | </dict> | |
| 5781 | </dict> | |
| 5782 | <key>end</key> | |
| 5783 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5784 | <key>endCaptures</key> | |
| 5785 | <dict> | |
| 5786 | <key>1</key> | |
| 5787 | <dict> | |
| 5788 | <key>name</key> | |
| 5789 | <string>string.other.end.code.fenced.mdx</string> | |
| 5790 | </dict> | |
| 5791 | </dict> | |
| 5792 | <key>name</key> | |
| 5793 | <string>markup.code.julia.mdx</string> | |
| 5794 | <key>patterns</key> | |
| 5795 | <array> | |
| 5796 | <dict> | |
| 5797 | <key>begin</key> | |
| 5798 | <string>(^|\G)(\s*)(.*)</string> | |
| 5799 | <key>while</key> | |
| 5800 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5801 | <key>contentName</key> | |
| 5802 | <string>meta.embedded.julia</string> | |
| 5803 | <key>patterns</key> | |
| 5804 | <array> | |
| 5805 | <dict> | |
| 5806 | <key>include</key> | |
| 5807 | <string>source.julia</string> | |
| 5808 | </dict> | |
| 5809 | </array> | |
| 5810 | </dict> | |
| 5811 | </array> | |
| 5812 | </dict> | |
| 5813 | <dict> | |
| 5814 | <key>begin</key> | |
| 5815 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:julia|(?:.*\.)?jl))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5816 | <key>beginCaptures</key> | |
| 5817 | <dict> | |
| 5818 | <key>1</key> | |
| 5819 | <dict> | |
| 5820 | <key>name</key> | |
| 5821 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5822 | </dict> | |
| 5823 | <key>2</key> | |
| 5824 | <dict> | |
| 5825 | <key>name</key> | |
| 5826 | <string>entity.name.function.mdx</string> | |
| 5827 | <key>patterns</key> | |
| 5828 | <array> | |
| 5829 | <dict> | |
| 5830 | <key>include</key> | |
| 5831 | <string>#markdown-string</string> | |
| 5832 | </dict> | |
| 5833 | </array> | |
| 5834 | </dict> | |
| 5835 | <key>3</key> | |
| 5836 | <dict> | |
| 5837 | <key>patterns</key> | |
| 5838 | <array> | |
| 5839 | <dict> | |
| 5840 | <key>include</key> | |
| 5841 | <string>#markdown-string</string> | |
| 5842 | </dict> | |
| 5843 | </array> | |
| 5844 | </dict> | |
| 5845 | </dict> | |
| 5846 | <key>end</key> | |
| 5847 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5848 | <key>endCaptures</key> | |
| 5849 | <dict> | |
| 5850 | <key>1</key> | |
| 5851 | <dict> | |
| 5852 | <key>name</key> | |
| 5853 | <string>string.other.end.code.fenced.mdx</string> | |
| 5854 | </dict> | |
| 5855 | </dict> | |
| 5856 | <key>name</key> | |
| 5857 | <string>markup.code.julia.mdx</string> | |
| 5858 | <key>patterns</key> | |
| 5859 | <array> | |
| 5860 | <dict> | |
| 5861 | <key>begin</key> | |
| 5862 | <string>(^|\G)(\s*)(.*)</string> | |
| 5863 | <key>while</key> | |
| 5864 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5865 | <key>contentName</key> | |
| 5866 | <string>meta.embedded.julia</string> | |
| 5867 | <key>patterns</key> | |
| 5868 | <array> | |
| 5869 | <dict> | |
| 5870 | <key>include</key> | |
| 5871 | <string>source.julia</string> | |
| 5872 | </dict> | |
| 5873 | </array> | |
| 5874 | </dict> | |
| 5875 | </array> | |
| 5876 | </dict> | |
| 5877 | </array> | |
| 5878 | </dict> | |
| 5879 | <key>commonmark-code-fenced-kotlin</key> | |
| 5880 | <dict> | |
| 5881 | <key>patterns</key> | |
| 5882 | <array> | |
| 5883 | <dict> | |
| 5884 | <key>begin</key> | |
| 5885 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:gradle\x2dkotlin\x2ddsl|kotlin|(?:.*\.)?(?:gradle\.kts|kt|ktm|kts)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 5886 | <key>beginCaptures</key> | |
| 5887 | <dict> | |
| 5888 | <key>1</key> | |
| 5889 | <dict> | |
| 5890 | <key>name</key> | |
| 5891 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5892 | </dict> | |
| 5893 | <key>2</key> | |
| 5894 | <dict> | |
| 5895 | <key>name</key> | |
| 5896 | <string>entity.name.function.mdx</string> | |
| 5897 | <key>patterns</key> | |
| 5898 | <array> | |
| 5899 | <dict> | |
| 5900 | <key>include</key> | |
| 5901 | <string>#markdown-string</string> | |
| 5902 | </dict> | |
| 5903 | </array> | |
| 5904 | </dict> | |
| 5905 | <key>3</key> | |
| 5906 | <dict> | |
| 5907 | <key>patterns</key> | |
| 5908 | <array> | |
| 5909 | <dict> | |
| 5910 | <key>include</key> | |
| 5911 | <string>#markdown-string</string> | |
| 5912 | </dict> | |
| 5913 | </array> | |
| 5914 | </dict> | |
| 5915 | </dict> | |
| 5916 | <key>end</key> | |
| 5917 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5918 | <key>endCaptures</key> | |
| 5919 | <dict> | |
| 5920 | <key>1</key> | |
| 5921 | <dict> | |
| 5922 | <key>name</key> | |
| 5923 | <string>string.other.end.code.fenced.mdx</string> | |
| 5924 | </dict> | |
| 5925 | </dict> | |
| 5926 | <key>name</key> | |
| 5927 | <string>markup.code.kotlin.mdx</string> | |
| 5928 | <key>patterns</key> | |
| 5929 | <array> | |
| 5930 | <dict> | |
| 5931 | <key>begin</key> | |
| 5932 | <string>(^|\G)(\s*)(.*)</string> | |
| 5933 | <key>while</key> | |
| 5934 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5935 | <key>contentName</key> | |
| 5936 | <string>meta.embedded.kotlin</string> | |
| 5937 | <key>patterns</key> | |
| 5938 | <array> | |
| 5939 | <dict> | |
| 5940 | <key>include</key> | |
| 5941 | <string>source.kotlin</string> | |
| 5942 | </dict> | |
| 5943 | </array> | |
| 5944 | </dict> | |
| 5945 | </array> | |
| 5946 | </dict> | |
| 5947 | <dict> | |
| 5948 | <key>begin</key> | |
| 5949 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:gradle\x2dkotlin\x2ddsl|kotlin|(?:.*\.)?(?:gradle\.kts|kt|ktm|kts)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 5950 | <key>beginCaptures</key> | |
| 5951 | <dict> | |
| 5952 | <key>1</key> | |
| 5953 | <dict> | |
| 5954 | <key>name</key> | |
| 5955 | <string>string.other.begin.code.fenced.mdx</string> | |
| 5956 | </dict> | |
| 5957 | <key>2</key> | |
| 5958 | <dict> | |
| 5959 | <key>name</key> | |
| 5960 | <string>entity.name.function.mdx</string> | |
| 5961 | <key>patterns</key> | |
| 5962 | <array> | |
| 5963 | <dict> | |
| 5964 | <key>include</key> | |
| 5965 | <string>#markdown-string</string> | |
| 5966 | </dict> | |
| 5967 | </array> | |
| 5968 | </dict> | |
| 5969 | <key>3</key> | |
| 5970 | <dict> | |
| 5971 | <key>patterns</key> | |
| 5972 | <array> | |
| 5973 | <dict> | |
| 5974 | <key>include</key> | |
| 5975 | <string>#markdown-string</string> | |
| 5976 | </dict> | |
| 5977 | </array> | |
| 5978 | </dict> | |
| 5979 | </dict> | |
| 5980 | <key>end</key> | |
| 5981 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 5982 | <key>endCaptures</key> | |
| 5983 | <dict> | |
| 5984 | <key>1</key> | |
| 5985 | <dict> | |
| 5986 | <key>name</key> | |
| 5987 | <string>string.other.end.code.fenced.mdx</string> | |
| 5988 | </dict> | |
| 5989 | </dict> | |
| 5990 | <key>name</key> | |
| 5991 | <string>markup.code.kotlin.mdx</string> | |
| 5992 | <key>patterns</key> | |
| 5993 | <array> | |
| 5994 | <dict> | |
| 5995 | <key>begin</key> | |
| 5996 | <string>(^|\G)(\s*)(.*)</string> | |
| 5997 | <key>while</key> | |
| 5998 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 5999 | <key>contentName</key> | |
| 6000 | <string>meta.embedded.kotlin</string> | |
| 6001 | <key>patterns</key> | |
| 6002 | <array> | |
| 6003 | <dict> | |
| 6004 | <key>include</key> | |
| 6005 | <string>source.kotlin</string> | |
| 6006 | </dict> | |
| 6007 | </array> | |
| 6008 | </dict> | |
| 6009 | </array> | |
| 6010 | </dict> | |
| 6011 | </array> | |
| 6012 | </dict> | |
| 6013 | <key>commonmark-code-fenced-less</key> | |
| 6014 | <dict> | |
| 6015 | <key>patterns</key> | |
| 6016 | <array> | |
| 6017 | <dict> | |
| 6018 | <key>begin</key> | |
| 6019 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:less\x2dcss|(?:.*\.)?less))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6020 | <key>beginCaptures</key> | |
| 6021 | <dict> | |
| 6022 | <key>1</key> | |
| 6023 | <dict> | |
| 6024 | <key>name</key> | |
| 6025 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6026 | </dict> | |
| 6027 | <key>2</key> | |
| 6028 | <dict> | |
| 6029 | <key>name</key> | |
| 6030 | <string>entity.name.function.mdx</string> | |
| 6031 | <key>patterns</key> | |
| 6032 | <array> | |
| 6033 | <dict> | |
| 6034 | <key>include</key> | |
| 6035 | <string>#markdown-string</string> | |
| 6036 | </dict> | |
| 6037 | </array> | |
| 6038 | </dict> | |
| 6039 | <key>3</key> | |
| 6040 | <dict> | |
| 6041 | <key>patterns</key> | |
| 6042 | <array> | |
| 6043 | <dict> | |
| 6044 | <key>include</key> | |
| 6045 | <string>#markdown-string</string> | |
| 6046 | </dict> | |
| 6047 | </array> | |
| 6048 | </dict> | |
| 6049 | </dict> | |
| 6050 | <key>end</key> | |
| 6051 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6052 | <key>endCaptures</key> | |
| 6053 | <dict> | |
| 6054 | <key>1</key> | |
| 6055 | <dict> | |
| 6056 | <key>name</key> | |
| 6057 | <string>string.other.end.code.fenced.mdx</string> | |
| 6058 | </dict> | |
| 6059 | </dict> | |
| 6060 | <key>name</key> | |
| 6061 | <string>markup.code.less.mdx</string> | |
| 6062 | <key>patterns</key> | |
| 6063 | <array> | |
| 6064 | <dict> | |
| 6065 | <key>begin</key> | |
| 6066 | <string>(^|\G)(\s*)(.*)</string> | |
| 6067 | <key>while</key> | |
| 6068 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6069 | <key>contentName</key> | |
| 6070 | <string>meta.embedded.less</string> | |
| 6071 | <key>patterns</key> | |
| 6072 | <array> | |
| 6073 | <dict> | |
| 6074 | <key>include</key> | |
| 6075 | <string>source.css.less</string> | |
| 6076 | </dict> | |
| 6077 | </array> | |
| 6078 | </dict> | |
| 6079 | </array> | |
| 6080 | </dict> | |
| 6081 | <dict> | |
| 6082 | <key>begin</key> | |
| 6083 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:less\x2dcss|(?:.*\.)?less))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6084 | <key>beginCaptures</key> | |
| 6085 | <dict> | |
| 6086 | <key>1</key> | |
| 6087 | <dict> | |
| 6088 | <key>name</key> | |
| 6089 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6090 | </dict> | |
| 6091 | <key>2</key> | |
| 6092 | <dict> | |
| 6093 | <key>name</key> | |
| 6094 | <string>entity.name.function.mdx</string> | |
| 6095 | <key>patterns</key> | |
| 6096 | <array> | |
| 6097 | <dict> | |
| 6098 | <key>include</key> | |
| 6099 | <string>#markdown-string</string> | |
| 6100 | </dict> | |
| 6101 | </array> | |
| 6102 | </dict> | |
| 6103 | <key>3</key> | |
| 6104 | <dict> | |
| 6105 | <key>patterns</key> | |
| 6106 | <array> | |
| 6107 | <dict> | |
| 6108 | <key>include</key> | |
| 6109 | <string>#markdown-string</string> | |
| 6110 | </dict> | |
| 6111 | </array> | |
| 6112 | </dict> | |
| 6113 | </dict> | |
| 6114 | <key>end</key> | |
| 6115 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6116 | <key>endCaptures</key> | |
| 6117 | <dict> | |
| 6118 | <key>1</key> | |
| 6119 | <dict> | |
| 6120 | <key>name</key> | |
| 6121 | <string>string.other.end.code.fenced.mdx</string> | |
| 6122 | </dict> | |
| 6123 | </dict> | |
| 6124 | <key>name</key> | |
| 6125 | <string>markup.code.less.mdx</string> | |
| 6126 | <key>patterns</key> | |
| 6127 | <array> | |
| 6128 | <dict> | |
| 6129 | <key>begin</key> | |
| 6130 | <string>(^|\G)(\s*)(.*)</string> | |
| 6131 | <key>while</key> | |
| 6132 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6133 | <key>contentName</key> | |
| 6134 | <string>meta.embedded.less</string> | |
| 6135 | <key>patterns</key> | |
| 6136 | <array> | |
| 6137 | <dict> | |
| 6138 | <key>include</key> | |
| 6139 | <string>source.css.less</string> | |
| 6140 | </dict> | |
| 6141 | </array> | |
| 6142 | </dict> | |
| 6143 | </array> | |
| 6144 | </dict> | |
| 6145 | </array> | |
| 6146 | </dict> | |
| 6147 | <key>commonmark-code-fenced-lua</key> | |
| 6148 | <dict> | |
| 6149 | <key>patterns</key> | |
| 6150 | <array> | |
| 6151 | <dict> | |
| 6152 | <key>begin</key> | |
| 6153 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:fcgi|lua|nse|p8|pd_lua|rbxs|rockspec|wlua)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6154 | <key>beginCaptures</key> | |
| 6155 | <dict> | |
| 6156 | <key>1</key> | |
| 6157 | <dict> | |
| 6158 | <key>name</key> | |
| 6159 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6160 | </dict> | |
| 6161 | <key>2</key> | |
| 6162 | <dict> | |
| 6163 | <key>name</key> | |
| 6164 | <string>entity.name.function.mdx</string> | |
| 6165 | <key>patterns</key> | |
| 6166 | <array> | |
| 6167 | <dict> | |
| 6168 | <key>include</key> | |
| 6169 | <string>#markdown-string</string> | |
| 6170 | </dict> | |
| 6171 | </array> | |
| 6172 | </dict> | |
| 6173 | <key>3</key> | |
| 6174 | <dict> | |
| 6175 | <key>patterns</key> | |
| 6176 | <array> | |
| 6177 | <dict> | |
| 6178 | <key>include</key> | |
| 6179 | <string>#markdown-string</string> | |
| 6180 | </dict> | |
| 6181 | </array> | |
| 6182 | </dict> | |
| 6183 | </dict> | |
| 6184 | <key>end</key> | |
| 6185 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6186 | <key>endCaptures</key> | |
| 6187 | <dict> | |
| 6188 | <key>1</key> | |
| 6189 | <dict> | |
| 6190 | <key>name</key> | |
| 6191 | <string>string.other.end.code.fenced.mdx</string> | |
| 6192 | </dict> | |
| 6193 | </dict> | |
| 6194 | <key>name</key> | |
| 6195 | <string>markup.code.lua.mdx</string> | |
| 6196 | <key>patterns</key> | |
| 6197 | <array> | |
| 6198 | <dict> | |
| 6199 | <key>begin</key> | |
| 6200 | <string>(^|\G)(\s*)(.*)</string> | |
| 6201 | <key>while</key> | |
| 6202 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6203 | <key>contentName</key> | |
| 6204 | <string>meta.embedded.lua</string> | |
| 6205 | <key>patterns</key> | |
| 6206 | <array> | |
| 6207 | <dict> | |
| 6208 | <key>include</key> | |
| 6209 | <string>source.lua</string> | |
| 6210 | </dict> | |
| 6211 | </array> | |
| 6212 | </dict> | |
| 6213 | </array> | |
| 6214 | </dict> | |
| 6215 | <dict> | |
| 6216 | <key>begin</key> | |
| 6217 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:fcgi|lua|nse|p8|pd_lua|rbxs|rockspec|wlua)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6218 | <key>beginCaptures</key> | |
| 6219 | <dict> | |
| 6220 | <key>1</key> | |
| 6221 | <dict> | |
| 6222 | <key>name</key> | |
| 6223 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6224 | </dict> | |
| 6225 | <key>2</key> | |
| 6226 | <dict> | |
| 6227 | <key>name</key> | |
| 6228 | <string>entity.name.function.mdx</string> | |
| 6229 | <key>patterns</key> | |
| 6230 | <array> | |
| 6231 | <dict> | |
| 6232 | <key>include</key> | |
| 6233 | <string>#markdown-string</string> | |
| 6234 | </dict> | |
| 6235 | </array> | |
| 6236 | </dict> | |
| 6237 | <key>3</key> | |
| 6238 | <dict> | |
| 6239 | <key>patterns</key> | |
| 6240 | <array> | |
| 6241 | <dict> | |
| 6242 | <key>include</key> | |
| 6243 | <string>#markdown-string</string> | |
| 6244 | </dict> | |
| 6245 | </array> | |
| 6246 | </dict> | |
| 6247 | </dict> | |
| 6248 | <key>end</key> | |
| 6249 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6250 | <key>endCaptures</key> | |
| 6251 | <dict> | |
| 6252 | <key>1</key> | |
| 6253 | <dict> | |
| 6254 | <key>name</key> | |
| 6255 | <string>string.other.end.code.fenced.mdx</string> | |
| 6256 | </dict> | |
| 6257 | </dict> | |
| 6258 | <key>name</key> | |
| 6259 | <string>markup.code.lua.mdx</string> | |
| 6260 | <key>patterns</key> | |
| 6261 | <array> | |
| 6262 | <dict> | |
| 6263 | <key>begin</key> | |
| 6264 | <string>(^|\G)(\s*)(.*)</string> | |
| 6265 | <key>while</key> | |
| 6266 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6267 | <key>contentName</key> | |
| 6268 | <string>meta.embedded.lua</string> | |
| 6269 | <key>patterns</key> | |
| 6270 | <array> | |
| 6271 | <dict> | |
| 6272 | <key>include</key> | |
| 6273 | <string>source.lua</string> | |
| 6274 | </dict> | |
| 6275 | </array> | |
| 6276 | </dict> | |
| 6277 | </array> | |
| 6278 | </dict> | |
| 6279 | </array> | |
| 6280 | </dict> | |
| 6281 | <key>commonmark-code-fenced-makefile</key> | |
| 6282 | <dict> | |
| 6283 | <key>patterns</key> | |
| 6284 | <array> | |
| 6285 | <dict> | |
| 6286 | <key>begin</key> | |
| 6287 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bsdmake|mf|(?:.*\.)?(?:mak|make|makefile|mk|mkfile)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6288 | <key>beginCaptures</key> | |
| 6289 | <dict> | |
| 6290 | <key>1</key> | |
| 6291 | <dict> | |
| 6292 | <key>name</key> | |
| 6293 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6294 | </dict> | |
| 6295 | <key>2</key> | |
| 6296 | <dict> | |
| 6297 | <key>name</key> | |
| 6298 | <string>entity.name.function.mdx</string> | |
| 6299 | <key>patterns</key> | |
| 6300 | <array> | |
| 6301 | <dict> | |
| 6302 | <key>include</key> | |
| 6303 | <string>#markdown-string</string> | |
| 6304 | </dict> | |
| 6305 | </array> | |
| 6306 | </dict> | |
| 6307 | <key>3</key> | |
| 6308 | <dict> | |
| 6309 | <key>patterns</key> | |
| 6310 | <array> | |
| 6311 | <dict> | |
| 6312 | <key>include</key> | |
| 6313 | <string>#markdown-string</string> | |
| 6314 | </dict> | |
| 6315 | </array> | |
| 6316 | </dict> | |
| 6317 | </dict> | |
| 6318 | <key>end</key> | |
| 6319 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6320 | <key>endCaptures</key> | |
| 6321 | <dict> | |
| 6322 | <key>1</key> | |
| 6323 | <dict> | |
| 6324 | <key>name</key> | |
| 6325 | <string>string.other.end.code.fenced.mdx</string> | |
| 6326 | </dict> | |
| 6327 | </dict> | |
| 6328 | <key>name</key> | |
| 6329 | <string>markup.code.makefile.mdx</string> | |
| 6330 | <key>patterns</key> | |
| 6331 | <array> | |
| 6332 | <dict> | |
| 6333 | <key>begin</key> | |
| 6334 | <string>(^|\G)(\s*)(.*)</string> | |
| 6335 | <key>while</key> | |
| 6336 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6337 | <key>contentName</key> | |
| 6338 | <string>meta.embedded.makefile</string> | |
| 6339 | <key>patterns</key> | |
| 6340 | <array> | |
| 6341 | <dict> | |
| 6342 | <key>include</key> | |
| 6343 | <string>source.makefile</string> | |
| 6344 | </dict> | |
| 6345 | </array> | |
| 6346 | </dict> | |
| 6347 | </array> | |
| 6348 | </dict> | |
| 6349 | <dict> | |
| 6350 | <key>begin</key> | |
| 6351 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bsdmake|mf|(?:.*\.)?(?:mak|make|makefile|mk|mkfile)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6352 | <key>beginCaptures</key> | |
| 6353 | <dict> | |
| 6354 | <key>1</key> | |
| 6355 | <dict> | |
| 6356 | <key>name</key> | |
| 6357 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6358 | </dict> | |
| 6359 | <key>2</key> | |
| 6360 | <dict> | |
| 6361 | <key>name</key> | |
| 6362 | <string>entity.name.function.mdx</string> | |
| 6363 | <key>patterns</key> | |
| 6364 | <array> | |
| 6365 | <dict> | |
| 6366 | <key>include</key> | |
| 6367 | <string>#markdown-string</string> | |
| 6368 | </dict> | |
| 6369 | </array> | |
| 6370 | </dict> | |
| 6371 | <key>3</key> | |
| 6372 | <dict> | |
| 6373 | <key>patterns</key> | |
| 6374 | <array> | |
| 6375 | <dict> | |
| 6376 | <key>include</key> | |
| 6377 | <string>#markdown-string</string> | |
| 6378 | </dict> | |
| 6379 | </array> | |
| 6380 | </dict> | |
| 6381 | </dict> | |
| 6382 | <key>end</key> | |
| 6383 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6384 | <key>endCaptures</key> | |
| 6385 | <dict> | |
| 6386 | <key>1</key> | |
| 6387 | <dict> | |
| 6388 | <key>name</key> | |
| 6389 | <string>string.other.end.code.fenced.mdx</string> | |
| 6390 | </dict> | |
| 6391 | </dict> | |
| 6392 | <key>name</key> | |
| 6393 | <string>markup.code.makefile.mdx</string> | |
| 6394 | <key>patterns</key> | |
| 6395 | <array> | |
| 6396 | <dict> | |
| 6397 | <key>begin</key> | |
| 6398 | <string>(^|\G)(\s*)(.*)</string> | |
| 6399 | <key>while</key> | |
| 6400 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6401 | <key>contentName</key> | |
| 6402 | <string>meta.embedded.makefile</string> | |
| 6403 | <key>patterns</key> | |
| 6404 | <array> | |
| 6405 | <dict> | |
| 6406 | <key>include</key> | |
| 6407 | <string>source.makefile</string> | |
| 6408 | </dict> | |
| 6409 | </array> | |
| 6410 | </dict> | |
| 6411 | </array> | |
| 6412 | </dict> | |
| 6413 | </array> | |
| 6414 | </dict> | |
| 6415 | <key>commonmark-code-fenced-md</key> | |
| 6416 | <dict> | |
| 6417 | <key>patterns</key> | |
| 6418 | <array> | |
| 6419 | <dict> | |
| 6420 | <key>begin</key> | |
| 6421 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:md|pandoc|rmarkdown|(?:.*\.)?(?:livemd|markdown|mdown|mdwn|mkd|mkdn|mkdown|qmd|rmd|ronn|scd|workbook)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6422 | <key>beginCaptures</key> | |
| 6423 | <dict> | |
| 6424 | <key>1</key> | |
| 6425 | <dict> | |
| 6426 | <key>name</key> | |
| 6427 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6428 | </dict> | |
| 6429 | <key>2</key> | |
| 6430 | <dict> | |
| 6431 | <key>name</key> | |
| 6432 | <string>entity.name.function.mdx</string> | |
| 6433 | <key>patterns</key> | |
| 6434 | <array> | |
| 6435 | <dict> | |
| 6436 | <key>include</key> | |
| 6437 | <string>#markdown-string</string> | |
| 6438 | </dict> | |
| 6439 | </array> | |
| 6440 | </dict> | |
| 6441 | <key>3</key> | |
| 6442 | <dict> | |
| 6443 | <key>patterns</key> | |
| 6444 | <array> | |
| 6445 | <dict> | |
| 6446 | <key>include</key> | |
| 6447 | <string>#markdown-string</string> | |
| 6448 | </dict> | |
| 6449 | </array> | |
| 6450 | </dict> | |
| 6451 | </dict> | |
| 6452 | <key>end</key> | |
| 6453 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6454 | <key>endCaptures</key> | |
| 6455 | <dict> | |
| 6456 | <key>1</key> | |
| 6457 | <dict> | |
| 6458 | <key>name</key> | |
| 6459 | <string>string.other.end.code.fenced.mdx</string> | |
| 6460 | </dict> | |
| 6461 | </dict> | |
| 6462 | <key>name</key> | |
| 6463 | <string>markup.code.md.mdx</string> | |
| 6464 | <key>patterns</key> | |
| 6465 | <array> | |
| 6466 | <dict> | |
| 6467 | <key>begin</key> | |
| 6468 | <string>(^|\G)(\s*)(.*)</string> | |
| 6469 | <key>while</key> | |
| 6470 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6471 | <key>contentName</key> | |
| 6472 | <string>meta.embedded.md</string> | |
| 6473 | <key>patterns</key> | |
| 6474 | <array> | |
| 6475 | <dict> | |
| 6476 | <key>include</key> | |
| 6477 | <string>text.md</string> | |
| 6478 | </dict> | |
| 6479 | <dict> | |
| 6480 | <key>include</key> | |
| 6481 | <string>source.gfm</string> | |
| 6482 | </dict> | |
| 6483 | <dict> | |
| 6484 | <key>include</key> | |
| 6485 | <string>text.html.markdown</string> | |
| 6486 | </dict> | |
| 6487 | </array> | |
| 6488 | </dict> | |
| 6489 | </array> | |
| 6490 | </dict> | |
| 6491 | <dict> | |
| 6492 | <key>begin</key> | |
| 6493 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:md|pandoc|rmarkdown|(?:.*\.)?(?:livemd|markdown|mdown|mdwn|mkd|mkdn|mkdown|qmd|rmd|ronn|scd|workbook)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6494 | <key>beginCaptures</key> | |
| 6495 | <dict> | |
| 6496 | <key>1</key> | |
| 6497 | <dict> | |
| 6498 | <key>name</key> | |
| 6499 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6500 | </dict> | |
| 6501 | <key>2</key> | |
| 6502 | <dict> | |
| 6503 | <key>name</key> | |
| 6504 | <string>entity.name.function.mdx</string> | |
| 6505 | <key>patterns</key> | |
| 6506 | <array> | |
| 6507 | <dict> | |
| 6508 | <key>include</key> | |
| 6509 | <string>#markdown-string</string> | |
| 6510 | </dict> | |
| 6511 | </array> | |
| 6512 | </dict> | |
| 6513 | <key>3</key> | |
| 6514 | <dict> | |
| 6515 | <key>patterns</key> | |
| 6516 | <array> | |
| 6517 | <dict> | |
| 6518 | <key>include</key> | |
| 6519 | <string>#markdown-string</string> | |
| 6520 | </dict> | |
| 6521 | </array> | |
| 6522 | </dict> | |
| 6523 | </dict> | |
| 6524 | <key>end</key> | |
| 6525 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6526 | <key>endCaptures</key> | |
| 6527 | <dict> | |
| 6528 | <key>1</key> | |
| 6529 | <dict> | |
| 6530 | <key>name</key> | |
| 6531 | <string>string.other.end.code.fenced.mdx</string> | |
| 6532 | </dict> | |
| 6533 | </dict> | |
| 6534 | <key>name</key> | |
| 6535 | <string>markup.code.md.mdx</string> | |
| 6536 | <key>patterns</key> | |
| 6537 | <array> | |
| 6538 | <dict> | |
| 6539 | <key>begin</key> | |
| 6540 | <string>(^|\G)(\s*)(.*)</string> | |
| 6541 | <key>while</key> | |
| 6542 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6543 | <key>contentName</key> | |
| 6544 | <string>meta.embedded.md</string> | |
| 6545 | <key>patterns</key> | |
| 6546 | <array> | |
| 6547 | <dict> | |
| 6548 | <key>include</key> | |
| 6549 | <string>text.md</string> | |
| 6550 | </dict> | |
| 6551 | <dict> | |
| 6552 | <key>include</key> | |
| 6553 | <string>source.gfm</string> | |
| 6554 | </dict> | |
| 6555 | <dict> | |
| 6556 | <key>include</key> | |
| 6557 | <string>text.html.markdown</string> | |
| 6558 | </dict> | |
| 6559 | </array> | |
| 6560 | </dict> | |
| 6561 | </array> | |
| 6562 | </dict> | |
| 6563 | </array> | |
| 6564 | </dict> | |
| 6565 | <key>commonmark-code-fenced-mdx</key> | |
| 6566 | <dict> | |
| 6567 | <key>patterns</key> | |
| 6568 | <array> | |
| 6569 | <dict> | |
| 6570 | <key>begin</key> | |
| 6571 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?mdx))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6572 | <key>beginCaptures</key> | |
| 6573 | <dict> | |
| 6574 | <key>1</key> | |
| 6575 | <dict> | |
| 6576 | <key>name</key> | |
| 6577 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6578 | </dict> | |
| 6579 | <key>2</key> | |
| 6580 | <dict> | |
| 6581 | <key>name</key> | |
| 6582 | <string>entity.name.function.mdx</string> | |
| 6583 | <key>patterns</key> | |
| 6584 | <array> | |
| 6585 | <dict> | |
| 6586 | <key>include</key> | |
| 6587 | <string>#markdown-string</string> | |
| 6588 | </dict> | |
| 6589 | </array> | |
| 6590 | </dict> | |
| 6591 | <key>3</key> | |
| 6592 | <dict> | |
| 6593 | <key>patterns</key> | |
| 6594 | <array> | |
| 6595 | <dict> | |
| 6596 | <key>include</key> | |
| 6597 | <string>#markdown-string</string> | |
| 6598 | </dict> | |
| 6599 | </array> | |
| 6600 | </dict> | |
| 6601 | </dict> | |
| 6602 | <key>end</key> | |
| 6603 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6604 | <key>endCaptures</key> | |
| 6605 | <dict> | |
| 6606 | <key>1</key> | |
| 6607 | <dict> | |
| 6608 | <key>name</key> | |
| 6609 | <string>string.other.end.code.fenced.mdx</string> | |
| 6610 | </dict> | |
| 6611 | </dict> | |
| 6612 | <key>name</key> | |
| 6613 | <string>markup.code.mdx.mdx</string> | |
| 6614 | <key>patterns</key> | |
| 6615 | <array> | |
| 6616 | <dict> | |
| 6617 | <key>begin</key> | |
| 6618 | <string>(^|\G)(\s*)(.*)</string> | |
| 6619 | <key>while</key> | |
| 6620 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6621 | <key>contentName</key> | |
| 6622 | <string>meta.embedded.mdx</string> | |
| 6623 | <key>patterns</key> | |
| 6624 | <array> | |
| 6625 | <dict> | |
| 6626 | <key>include</key> | |
| 6627 | <string>source.mdx</string> | |
| 6628 | </dict> | |
| 6629 | </array> | |
| 6630 | </dict> | |
| 6631 | </array> | |
| 6632 | </dict> | |
| 6633 | <dict> | |
| 6634 | <key>begin</key> | |
| 6635 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?mdx))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6636 | <key>beginCaptures</key> | |
| 6637 | <dict> | |
| 6638 | <key>1</key> | |
| 6639 | <dict> | |
| 6640 | <key>name</key> | |
| 6641 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6642 | </dict> | |
| 6643 | <key>2</key> | |
| 6644 | <dict> | |
| 6645 | <key>name</key> | |
| 6646 | <string>entity.name.function.mdx</string> | |
| 6647 | <key>patterns</key> | |
| 6648 | <array> | |
| 6649 | <dict> | |
| 6650 | <key>include</key> | |
| 6651 | <string>#markdown-string</string> | |
| 6652 | </dict> | |
| 6653 | </array> | |
| 6654 | </dict> | |
| 6655 | <key>3</key> | |
| 6656 | <dict> | |
| 6657 | <key>patterns</key> | |
| 6658 | <array> | |
| 6659 | <dict> | |
| 6660 | <key>include</key> | |
| 6661 | <string>#markdown-string</string> | |
| 6662 | </dict> | |
| 6663 | </array> | |
| 6664 | </dict> | |
| 6665 | </dict> | |
| 6666 | <key>end</key> | |
| 6667 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6668 | <key>endCaptures</key> | |
| 6669 | <dict> | |
| 6670 | <key>1</key> | |
| 6671 | <dict> | |
| 6672 | <key>name</key> | |
| 6673 | <string>string.other.end.code.fenced.mdx</string> | |
| 6674 | </dict> | |
| 6675 | </dict> | |
| 6676 | <key>name</key> | |
| 6677 | <string>markup.code.mdx.mdx</string> | |
| 6678 | <key>patterns</key> | |
| 6679 | <array> | |
| 6680 | <dict> | |
| 6681 | <key>begin</key> | |
| 6682 | <string>(^|\G)(\s*)(.*)</string> | |
| 6683 | <key>while</key> | |
| 6684 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6685 | <key>contentName</key> | |
| 6686 | <string>meta.embedded.mdx</string> | |
| 6687 | <key>patterns</key> | |
| 6688 | <array> | |
| 6689 | <dict> | |
| 6690 | <key>include</key> | |
| 6691 | <string>source.mdx</string> | |
| 6692 | </dict> | |
| 6693 | </array> | |
| 6694 | </dict> | |
| 6695 | </array> | |
| 6696 | </dict> | |
| 6697 | </array> | |
| 6698 | </dict> | |
| 6699 | <key>commonmark-code-fenced-objc</key> | |
| 6700 | <dict> | |
| 6701 | <key>patterns</key> | |
| 6702 | <array> | |
| 6703 | <dict> | |
| 6704 | <key>begin</key> | |
| 6705 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:obj\x2dc|objc|objective\x2dc|objectivec))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6706 | <key>beginCaptures</key> | |
| 6707 | <dict> | |
| 6708 | <key>1</key> | |
| 6709 | <dict> | |
| 6710 | <key>name</key> | |
| 6711 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6712 | </dict> | |
| 6713 | <key>2</key> | |
| 6714 | <dict> | |
| 6715 | <key>name</key> | |
| 6716 | <string>entity.name.function.mdx</string> | |
| 6717 | <key>patterns</key> | |
| 6718 | <array> | |
| 6719 | <dict> | |
| 6720 | <key>include</key> | |
| 6721 | <string>#markdown-string</string> | |
| 6722 | </dict> | |
| 6723 | </array> | |
| 6724 | </dict> | |
| 6725 | <key>3</key> | |
| 6726 | <dict> | |
| 6727 | <key>patterns</key> | |
| 6728 | <array> | |
| 6729 | <dict> | |
| 6730 | <key>include</key> | |
| 6731 | <string>#markdown-string</string> | |
| 6732 | </dict> | |
| 6733 | </array> | |
| 6734 | </dict> | |
| 6735 | </dict> | |
| 6736 | <key>end</key> | |
| 6737 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6738 | <key>endCaptures</key> | |
| 6739 | <dict> | |
| 6740 | <key>1</key> | |
| 6741 | <dict> | |
| 6742 | <key>name</key> | |
| 6743 | <string>string.other.end.code.fenced.mdx</string> | |
| 6744 | </dict> | |
| 6745 | </dict> | |
| 6746 | <key>name</key> | |
| 6747 | <string>markup.code.objc.mdx</string> | |
| 6748 | <key>patterns</key> | |
| 6749 | <array> | |
| 6750 | <dict> | |
| 6751 | <key>begin</key> | |
| 6752 | <string>(^|\G)(\s*)(.*)</string> | |
| 6753 | <key>while</key> | |
| 6754 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6755 | <key>contentName</key> | |
| 6756 | <string>meta.embedded.objc</string> | |
| 6757 | <key>patterns</key> | |
| 6758 | <array> | |
| 6759 | <dict> | |
| 6760 | <key>include</key> | |
| 6761 | <string>source.objc</string> | |
| 6762 | </dict> | |
| 6763 | </array> | |
| 6764 | </dict> | |
| 6765 | </array> | |
| 6766 | </dict> | |
| 6767 | <dict> | |
| 6768 | <key>begin</key> | |
| 6769 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:obj\x2dc|objc|objective\x2dc|objectivec))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6770 | <key>beginCaptures</key> | |
| 6771 | <dict> | |
| 6772 | <key>1</key> | |
| 6773 | <dict> | |
| 6774 | <key>name</key> | |
| 6775 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6776 | </dict> | |
| 6777 | <key>2</key> | |
| 6778 | <dict> | |
| 6779 | <key>name</key> | |
| 6780 | <string>entity.name.function.mdx</string> | |
| 6781 | <key>patterns</key> | |
| 6782 | <array> | |
| 6783 | <dict> | |
| 6784 | <key>include</key> | |
| 6785 | <string>#markdown-string</string> | |
| 6786 | </dict> | |
| 6787 | </array> | |
| 6788 | </dict> | |
| 6789 | <key>3</key> | |
| 6790 | <dict> | |
| 6791 | <key>patterns</key> | |
| 6792 | <array> | |
| 6793 | <dict> | |
| 6794 | <key>include</key> | |
| 6795 | <string>#markdown-string</string> | |
| 6796 | </dict> | |
| 6797 | </array> | |
| 6798 | </dict> | |
| 6799 | </dict> | |
| 6800 | <key>end</key> | |
| 6801 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6802 | <key>endCaptures</key> | |
| 6803 | <dict> | |
| 6804 | <key>1</key> | |
| 6805 | <dict> | |
| 6806 | <key>name</key> | |
| 6807 | <string>string.other.end.code.fenced.mdx</string> | |
| 6808 | </dict> | |
| 6809 | </dict> | |
| 6810 | <key>name</key> | |
| 6811 | <string>markup.code.objc.mdx</string> | |
| 6812 | <key>patterns</key> | |
| 6813 | <array> | |
| 6814 | <dict> | |
| 6815 | <key>begin</key> | |
| 6816 | <string>(^|\G)(\s*)(.*)</string> | |
| 6817 | <key>while</key> | |
| 6818 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6819 | <key>contentName</key> | |
| 6820 | <string>meta.embedded.objc</string> | |
| 6821 | <key>patterns</key> | |
| 6822 | <array> | |
| 6823 | <dict> | |
| 6824 | <key>include</key> | |
| 6825 | <string>source.objc</string> | |
| 6826 | </dict> | |
| 6827 | </array> | |
| 6828 | </dict> | |
| 6829 | </array> | |
| 6830 | </dict> | |
| 6831 | </array> | |
| 6832 | </dict> | |
| 6833 | <key>commonmark-code-fenced-perl</key> | |
| 6834 | <dict> | |
| 6835 | <key>patterns</key> | |
| 6836 | <array> | |
| 6837 | <dict> | |
| 6838 | <key>begin</key> | |
| 6839 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:cperl|(?:.*\.)?(?:cgi|perl|ph|pl|plx|pm|psgi|t)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6840 | <key>beginCaptures</key> | |
| 6841 | <dict> | |
| 6842 | <key>1</key> | |
| 6843 | <dict> | |
| 6844 | <key>name</key> | |
| 6845 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6846 | </dict> | |
| 6847 | <key>2</key> | |
| 6848 | <dict> | |
| 6849 | <key>name</key> | |
| 6850 | <string>entity.name.function.mdx</string> | |
| 6851 | <key>patterns</key> | |
| 6852 | <array> | |
| 6853 | <dict> | |
| 6854 | <key>include</key> | |
| 6855 | <string>#markdown-string</string> | |
| 6856 | </dict> | |
| 6857 | </array> | |
| 6858 | </dict> | |
| 6859 | <key>3</key> | |
| 6860 | <dict> | |
| 6861 | <key>patterns</key> | |
| 6862 | <array> | |
| 6863 | <dict> | |
| 6864 | <key>include</key> | |
| 6865 | <string>#markdown-string</string> | |
| 6866 | </dict> | |
| 6867 | </array> | |
| 6868 | </dict> | |
| 6869 | </dict> | |
| 6870 | <key>end</key> | |
| 6871 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6872 | <key>endCaptures</key> | |
| 6873 | <dict> | |
| 6874 | <key>1</key> | |
| 6875 | <dict> | |
| 6876 | <key>name</key> | |
| 6877 | <string>string.other.end.code.fenced.mdx</string> | |
| 6878 | </dict> | |
| 6879 | </dict> | |
| 6880 | <key>name</key> | |
| 6881 | <string>markup.code.perl.mdx</string> | |
| 6882 | <key>patterns</key> | |
| 6883 | <array> | |
| 6884 | <dict> | |
| 6885 | <key>begin</key> | |
| 6886 | <string>(^|\G)(\s*)(.*)</string> | |
| 6887 | <key>while</key> | |
| 6888 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6889 | <key>contentName</key> | |
| 6890 | <string>meta.embedded.perl</string> | |
| 6891 | <key>patterns</key> | |
| 6892 | <array> | |
| 6893 | <dict> | |
| 6894 | <key>include</key> | |
| 6895 | <string>source.perl</string> | |
| 6896 | </dict> | |
| 6897 | </array> | |
| 6898 | </dict> | |
| 6899 | </array> | |
| 6900 | </dict> | |
| 6901 | <dict> | |
| 6902 | <key>begin</key> | |
| 6903 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:cperl|(?:.*\.)?(?:cgi|perl|ph|pl|plx|pm|psgi|t)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 6904 | <key>beginCaptures</key> | |
| 6905 | <dict> | |
| 6906 | <key>1</key> | |
| 6907 | <dict> | |
| 6908 | <key>name</key> | |
| 6909 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6910 | </dict> | |
| 6911 | <key>2</key> | |
| 6912 | <dict> | |
| 6913 | <key>name</key> | |
| 6914 | <string>entity.name.function.mdx</string> | |
| 6915 | <key>patterns</key> | |
| 6916 | <array> | |
| 6917 | <dict> | |
| 6918 | <key>include</key> | |
| 6919 | <string>#markdown-string</string> | |
| 6920 | </dict> | |
| 6921 | </array> | |
| 6922 | </dict> | |
| 6923 | <key>3</key> | |
| 6924 | <dict> | |
| 6925 | <key>patterns</key> | |
| 6926 | <array> | |
| 6927 | <dict> | |
| 6928 | <key>include</key> | |
| 6929 | <string>#markdown-string</string> | |
| 6930 | </dict> | |
| 6931 | </array> | |
| 6932 | </dict> | |
| 6933 | </dict> | |
| 6934 | <key>end</key> | |
| 6935 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 6936 | <key>endCaptures</key> | |
| 6937 | <dict> | |
| 6938 | <key>1</key> | |
| 6939 | <dict> | |
| 6940 | <key>name</key> | |
| 6941 | <string>string.other.end.code.fenced.mdx</string> | |
| 6942 | </dict> | |
| 6943 | </dict> | |
| 6944 | <key>name</key> | |
| 6945 | <string>markup.code.perl.mdx</string> | |
| 6946 | <key>patterns</key> | |
| 6947 | <array> | |
| 6948 | <dict> | |
| 6949 | <key>begin</key> | |
| 6950 | <string>(^|\G)(\s*)(.*)</string> | |
| 6951 | <key>while</key> | |
| 6952 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 6953 | <key>contentName</key> | |
| 6954 | <string>meta.embedded.perl</string> | |
| 6955 | <key>patterns</key> | |
| 6956 | <array> | |
| 6957 | <dict> | |
| 6958 | <key>include</key> | |
| 6959 | <string>source.perl</string> | |
| 6960 | </dict> | |
| 6961 | </array> | |
| 6962 | </dict> | |
| 6963 | </array> | |
| 6964 | </dict> | |
| 6965 | </array> | |
| 6966 | </dict> | |
| 6967 | <key>commonmark-code-fenced-php</key> | |
| 6968 | <dict> | |
| 6969 | <key>patterns</key> | |
| 6970 | <array> | |
| 6971 | <dict> | |
| 6972 | <key>begin</key> | |
| 6973 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:html\+php|inc|php|(?:.*\.)?(?:aw|ctp|php3|php4|php5|phps|phpt|phtml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 6974 | <key>beginCaptures</key> | |
| 6975 | <dict> | |
| 6976 | <key>1</key> | |
| 6977 | <dict> | |
| 6978 | <key>name</key> | |
| 6979 | <string>string.other.begin.code.fenced.mdx</string> | |
| 6980 | </dict> | |
| 6981 | <key>2</key> | |
| 6982 | <dict> | |
| 6983 | <key>name</key> | |
| 6984 | <string>entity.name.function.mdx</string> | |
| 6985 | <key>patterns</key> | |
| 6986 | <array> | |
| 6987 | <dict> | |
| 6988 | <key>include</key> | |
| 6989 | <string>#markdown-string</string> | |
| 6990 | </dict> | |
| 6991 | </array> | |
| 6992 | </dict> | |
| 6993 | <key>3</key> | |
| 6994 | <dict> | |
| 6995 | <key>patterns</key> | |
| 6996 | <array> | |
| 6997 | <dict> | |
| 6998 | <key>include</key> | |
| 6999 | <string>#markdown-string</string> | |
| 7000 | </dict> | |
| 7001 | </array> | |
| 7002 | </dict> | |
| 7003 | </dict> | |
| 7004 | <key>end</key> | |
| 7005 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7006 | <key>endCaptures</key> | |
| 7007 | <dict> | |
| 7008 | <key>1</key> | |
| 7009 | <dict> | |
| 7010 | <key>name</key> | |
| 7011 | <string>string.other.end.code.fenced.mdx</string> | |
| 7012 | </dict> | |
| 7013 | </dict> | |
| 7014 | <key>name</key> | |
| 7015 | <string>markup.code.php.mdx</string> | |
| 7016 | <key>patterns</key> | |
| 7017 | <array> | |
| 7018 | <dict> | |
| 7019 | <key>begin</key> | |
| 7020 | <string>(^|\G)(\s*)(.*)</string> | |
| 7021 | <key>while</key> | |
| 7022 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7023 | <key>contentName</key> | |
| 7024 | <string>meta.embedded.php</string> | |
| 7025 | <key>patterns</key> | |
| 7026 | <array> | |
| 7027 | <dict> | |
| 7028 | <key>include</key> | |
| 7029 | <string>text.html.php</string> | |
| 7030 | </dict> | |
| 7031 | </array> | |
| 7032 | </dict> | |
| 7033 | </array> | |
| 7034 | </dict> | |
| 7035 | <dict> | |
| 7036 | <key>begin</key> | |
| 7037 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:html\+php|inc|php|(?:.*\.)?(?:aw|ctp|php3|php4|php5|phps|phpt|phtml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7038 | <key>beginCaptures</key> | |
| 7039 | <dict> | |
| 7040 | <key>1</key> | |
| 7041 | <dict> | |
| 7042 | <key>name</key> | |
| 7043 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7044 | </dict> | |
| 7045 | <key>2</key> | |
| 7046 | <dict> | |
| 7047 | <key>name</key> | |
| 7048 | <string>entity.name.function.mdx</string> | |
| 7049 | <key>patterns</key> | |
| 7050 | <array> | |
| 7051 | <dict> | |
| 7052 | <key>include</key> | |
| 7053 | <string>#markdown-string</string> | |
| 7054 | </dict> | |
| 7055 | </array> | |
| 7056 | </dict> | |
| 7057 | <key>3</key> | |
| 7058 | <dict> | |
| 7059 | <key>patterns</key> | |
| 7060 | <array> | |
| 7061 | <dict> | |
| 7062 | <key>include</key> | |
| 7063 | <string>#markdown-string</string> | |
| 7064 | </dict> | |
| 7065 | </array> | |
| 7066 | </dict> | |
| 7067 | </dict> | |
| 7068 | <key>end</key> | |
| 7069 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7070 | <key>endCaptures</key> | |
| 7071 | <dict> | |
| 7072 | <key>1</key> | |
| 7073 | <dict> | |
| 7074 | <key>name</key> | |
| 7075 | <string>string.other.end.code.fenced.mdx</string> | |
| 7076 | </dict> | |
| 7077 | </dict> | |
| 7078 | <key>name</key> | |
| 7079 | <string>markup.code.php.mdx</string> | |
| 7080 | <key>patterns</key> | |
| 7081 | <array> | |
| 7082 | <dict> | |
| 7083 | <key>begin</key> | |
| 7084 | <string>(^|\G)(\s*)(.*)</string> | |
| 7085 | <key>while</key> | |
| 7086 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7087 | <key>contentName</key> | |
| 7088 | <string>meta.embedded.php</string> | |
| 7089 | <key>patterns</key> | |
| 7090 | <array> | |
| 7091 | <dict> | |
| 7092 | <key>include</key> | |
| 7093 | <string>text.html.php</string> | |
| 7094 | </dict> | |
| 7095 | </array> | |
| 7096 | </dict> | |
| 7097 | </array> | |
| 7098 | </dict> | |
| 7099 | </array> | |
| 7100 | </dict> | |
| 7101 | <key>commonmark-code-fenced-python</key> | |
| 7102 | <dict> | |
| 7103 | <key>patterns</key> | |
| 7104 | <array> | |
| 7105 | <dict> | |
| 7106 | <key>begin</key> | |
| 7107 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bazel|easybuild|python|python3|rusthon|snakemake|starlark|xonsh|(?:.*\.)?(?:bzl|eb|gyp|gypi|lmi|py|py3|pyde|pyi|pyp|pyt|pyw|rpy|sage|sagews|smk|snakefile|spec|tac|wsgi|xpy|xsh)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7108 | <key>beginCaptures</key> | |
| 7109 | <dict> | |
| 7110 | <key>1</key> | |
| 7111 | <dict> | |
| 7112 | <key>name</key> | |
| 7113 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7114 | </dict> | |
| 7115 | <key>2</key> | |
| 7116 | <dict> | |
| 7117 | <key>name</key> | |
| 7118 | <string>entity.name.function.mdx</string> | |
| 7119 | <key>patterns</key> | |
| 7120 | <array> | |
| 7121 | <dict> | |
| 7122 | <key>include</key> | |
| 7123 | <string>#markdown-string</string> | |
| 7124 | </dict> | |
| 7125 | </array> | |
| 7126 | </dict> | |
| 7127 | <key>3</key> | |
| 7128 | <dict> | |
| 7129 | <key>patterns</key> | |
| 7130 | <array> | |
| 7131 | <dict> | |
| 7132 | <key>include</key> | |
| 7133 | <string>#markdown-string</string> | |
| 7134 | </dict> | |
| 7135 | </array> | |
| 7136 | </dict> | |
| 7137 | </dict> | |
| 7138 | <key>end</key> | |
| 7139 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7140 | <key>endCaptures</key> | |
| 7141 | <dict> | |
| 7142 | <key>1</key> | |
| 7143 | <dict> | |
| 7144 | <key>name</key> | |
| 7145 | <string>string.other.end.code.fenced.mdx</string> | |
| 7146 | </dict> | |
| 7147 | </dict> | |
| 7148 | <key>name</key> | |
| 7149 | <string>markup.code.python.mdx</string> | |
| 7150 | <key>patterns</key> | |
| 7151 | <array> | |
| 7152 | <dict> | |
| 7153 | <key>begin</key> | |
| 7154 | <string>(^|\G)(\s*)(.*)</string> | |
| 7155 | <key>while</key> | |
| 7156 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7157 | <key>contentName</key> | |
| 7158 | <string>meta.embedded.python</string> | |
| 7159 | <key>patterns</key> | |
| 7160 | <array> | |
| 7161 | <dict> | |
| 7162 | <key>include</key> | |
| 7163 | <string>source.python</string> | |
| 7164 | </dict> | |
| 7165 | </array> | |
| 7166 | </dict> | |
| 7167 | </array> | |
| 7168 | </dict> | |
| 7169 | <dict> | |
| 7170 | <key>begin</key> | |
| 7171 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bazel|easybuild|python|python3|rusthon|snakemake|starlark|xonsh|(?:.*\.)?(?:bzl|eb|gyp|gypi|lmi|py|py3|pyde|pyi|pyp|pyt|pyw|rpy|sage|sagews|smk|snakefile|spec|tac|wsgi|xpy|xsh)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7172 | <key>beginCaptures</key> | |
| 7173 | <dict> | |
| 7174 | <key>1</key> | |
| 7175 | <dict> | |
| 7176 | <key>name</key> | |
| 7177 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7178 | </dict> | |
| 7179 | <key>2</key> | |
| 7180 | <dict> | |
| 7181 | <key>name</key> | |
| 7182 | <string>entity.name.function.mdx</string> | |
| 7183 | <key>patterns</key> | |
| 7184 | <array> | |
| 7185 | <dict> | |
| 7186 | <key>include</key> | |
| 7187 | <string>#markdown-string</string> | |
| 7188 | </dict> | |
| 7189 | </array> | |
| 7190 | </dict> | |
| 7191 | <key>3</key> | |
| 7192 | <dict> | |
| 7193 | <key>patterns</key> | |
| 7194 | <array> | |
| 7195 | <dict> | |
| 7196 | <key>include</key> | |
| 7197 | <string>#markdown-string</string> | |
| 7198 | </dict> | |
| 7199 | </array> | |
| 7200 | </dict> | |
| 7201 | </dict> | |
| 7202 | <key>end</key> | |
| 7203 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7204 | <key>endCaptures</key> | |
| 7205 | <dict> | |
| 7206 | <key>1</key> | |
| 7207 | <dict> | |
| 7208 | <key>name</key> | |
| 7209 | <string>string.other.end.code.fenced.mdx</string> | |
| 7210 | </dict> | |
| 7211 | </dict> | |
| 7212 | <key>name</key> | |
| 7213 | <string>markup.code.python.mdx</string> | |
| 7214 | <key>patterns</key> | |
| 7215 | <array> | |
| 7216 | <dict> | |
| 7217 | <key>begin</key> | |
| 7218 | <string>(^|\G)(\s*)(.*)</string> | |
| 7219 | <key>while</key> | |
| 7220 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7221 | <key>contentName</key> | |
| 7222 | <string>meta.embedded.python</string> | |
| 7223 | <key>patterns</key> | |
| 7224 | <array> | |
| 7225 | <dict> | |
| 7226 | <key>include</key> | |
| 7227 | <string>source.python</string> | |
| 7228 | </dict> | |
| 7229 | </array> | |
| 7230 | </dict> | |
| 7231 | </array> | |
| 7232 | </dict> | |
| 7233 | </array> | |
| 7234 | </dict> | |
| 7235 | <key>commonmark-code-fenced-r</key> | |
| 7236 | <dict> | |
| 7237 | <key>patterns</key> | |
| 7238 | <array> | |
| 7239 | <dict> | |
| 7240 | <key>begin</key> | |
| 7241 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:rscript|splus|(?:.*\.)?(?:r|rd|rsx)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7242 | <key>beginCaptures</key> | |
| 7243 | <dict> | |
| 7244 | <key>1</key> | |
| 7245 | <dict> | |
| 7246 | <key>name</key> | |
| 7247 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7248 | </dict> | |
| 7249 | <key>2</key> | |
| 7250 | <dict> | |
| 7251 | <key>name</key> | |
| 7252 | <string>entity.name.function.mdx</string> | |
| 7253 | <key>patterns</key> | |
| 7254 | <array> | |
| 7255 | <dict> | |
| 7256 | <key>include</key> | |
| 7257 | <string>#markdown-string</string> | |
| 7258 | </dict> | |
| 7259 | </array> | |
| 7260 | </dict> | |
| 7261 | <key>3</key> | |
| 7262 | <dict> | |
| 7263 | <key>patterns</key> | |
| 7264 | <array> | |
| 7265 | <dict> | |
| 7266 | <key>include</key> | |
| 7267 | <string>#markdown-string</string> | |
| 7268 | </dict> | |
| 7269 | </array> | |
| 7270 | </dict> | |
| 7271 | </dict> | |
| 7272 | <key>end</key> | |
| 7273 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7274 | <key>endCaptures</key> | |
| 7275 | <dict> | |
| 7276 | <key>1</key> | |
| 7277 | <dict> | |
| 7278 | <key>name</key> | |
| 7279 | <string>string.other.end.code.fenced.mdx</string> | |
| 7280 | </dict> | |
| 7281 | </dict> | |
| 7282 | <key>name</key> | |
| 7283 | <string>markup.code.r.mdx</string> | |
| 7284 | <key>patterns</key> | |
| 7285 | <array> | |
| 7286 | <dict> | |
| 7287 | <key>begin</key> | |
| 7288 | <string>(^|\G)(\s*)(.*)</string> | |
| 7289 | <key>while</key> | |
| 7290 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7291 | <key>contentName</key> | |
| 7292 | <string>meta.embedded.r</string> | |
| 7293 | <key>patterns</key> | |
| 7294 | <array> | |
| 7295 | <dict> | |
| 7296 | <key>include</key> | |
| 7297 | <string>source.r</string> | |
| 7298 | </dict> | |
| 7299 | </array> | |
| 7300 | </dict> | |
| 7301 | </array> | |
| 7302 | </dict> | |
| 7303 | <dict> | |
| 7304 | <key>begin</key> | |
| 7305 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:rscript|splus|(?:.*\.)?(?:r|rd|rsx)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7306 | <key>beginCaptures</key> | |
| 7307 | <dict> | |
| 7308 | <key>1</key> | |
| 7309 | <dict> | |
| 7310 | <key>name</key> | |
| 7311 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7312 | </dict> | |
| 7313 | <key>2</key> | |
| 7314 | <dict> | |
| 7315 | <key>name</key> | |
| 7316 | <string>entity.name.function.mdx</string> | |
| 7317 | <key>patterns</key> | |
| 7318 | <array> | |
| 7319 | <dict> | |
| 7320 | <key>include</key> | |
| 7321 | <string>#markdown-string</string> | |
| 7322 | </dict> | |
| 7323 | </array> | |
| 7324 | </dict> | |
| 7325 | <key>3</key> | |
| 7326 | <dict> | |
| 7327 | <key>patterns</key> | |
| 7328 | <array> | |
| 7329 | <dict> | |
| 7330 | <key>include</key> | |
| 7331 | <string>#markdown-string</string> | |
| 7332 | </dict> | |
| 7333 | </array> | |
| 7334 | </dict> | |
| 7335 | </dict> | |
| 7336 | <key>end</key> | |
| 7337 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7338 | <key>endCaptures</key> | |
| 7339 | <dict> | |
| 7340 | <key>1</key> | |
| 7341 | <dict> | |
| 7342 | <key>name</key> | |
| 7343 | <string>string.other.end.code.fenced.mdx</string> | |
| 7344 | </dict> | |
| 7345 | </dict> | |
| 7346 | <key>name</key> | |
| 7347 | <string>markup.code.r.mdx</string> | |
| 7348 | <key>patterns</key> | |
| 7349 | <array> | |
| 7350 | <dict> | |
| 7351 | <key>begin</key> | |
| 7352 | <string>(^|\G)(\s*)(.*)</string> | |
| 7353 | <key>while</key> | |
| 7354 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7355 | <key>contentName</key> | |
| 7356 | <string>meta.embedded.r</string> | |
| 7357 | <key>patterns</key> | |
| 7358 | <array> | |
| 7359 | <dict> | |
| 7360 | <key>include</key> | |
| 7361 | <string>source.r</string> | |
| 7362 | </dict> | |
| 7363 | </array> | |
| 7364 | </dict> | |
| 7365 | </array> | |
| 7366 | </dict> | |
| 7367 | </array> | |
| 7368 | </dict> | |
| 7369 | <key>commonmark-code-fenced-raku</key> | |
| 7370 | <dict> | |
| 7371 | <key>patterns</key> | |
| 7372 | <array> | |
| 7373 | <dict> | |
| 7374 | <key>begin</key> | |
| 7375 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:perl\x2d6|perl6|pod\x2d6|(?:.*\.)?(?:6pl|6pm|nqp|p6|p6l|p6m|pl6|pm6|pod|pod6|raku|rakumod)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7376 | <key>beginCaptures</key> | |
| 7377 | <dict> | |
| 7378 | <key>1</key> | |
| 7379 | <dict> | |
| 7380 | <key>name</key> | |
| 7381 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7382 | </dict> | |
| 7383 | <key>2</key> | |
| 7384 | <dict> | |
| 7385 | <key>name</key> | |
| 7386 | <string>entity.name.function.mdx</string> | |
| 7387 | <key>patterns</key> | |
| 7388 | <array> | |
| 7389 | <dict> | |
| 7390 | <key>include</key> | |
| 7391 | <string>#markdown-string</string> | |
| 7392 | </dict> | |
| 7393 | </array> | |
| 7394 | </dict> | |
| 7395 | <key>3</key> | |
| 7396 | <dict> | |
| 7397 | <key>patterns</key> | |
| 7398 | <array> | |
| 7399 | <dict> | |
| 7400 | <key>include</key> | |
| 7401 | <string>#markdown-string</string> | |
| 7402 | </dict> | |
| 7403 | </array> | |
| 7404 | </dict> | |
| 7405 | </dict> | |
| 7406 | <key>end</key> | |
| 7407 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7408 | <key>endCaptures</key> | |
| 7409 | <dict> | |
| 7410 | <key>1</key> | |
| 7411 | <dict> | |
| 7412 | <key>name</key> | |
| 7413 | <string>string.other.end.code.fenced.mdx</string> | |
| 7414 | </dict> | |
| 7415 | </dict> | |
| 7416 | <key>name</key> | |
| 7417 | <string>markup.code.raku.mdx</string> | |
| 7418 | <key>patterns</key> | |
| 7419 | <array> | |
| 7420 | <dict> | |
| 7421 | <key>begin</key> | |
| 7422 | <string>(^|\G)(\s*)(.*)</string> | |
| 7423 | <key>while</key> | |
| 7424 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7425 | <key>contentName</key> | |
| 7426 | <string>meta.embedded.raku</string> | |
| 7427 | <key>patterns</key> | |
| 7428 | <array> | |
| 7429 | <dict> | |
| 7430 | <key>include</key> | |
| 7431 | <string>source.raku</string> | |
| 7432 | </dict> | |
| 7433 | </array> | |
| 7434 | </dict> | |
| 7435 | </array> | |
| 7436 | </dict> | |
| 7437 | <dict> | |
| 7438 | <key>begin</key> | |
| 7439 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:perl\x2d6|perl6|pod\x2d6|(?:.*\.)?(?:6pl|6pm|nqp|p6|p6l|p6m|pl6|pm6|pod|pod6|raku|rakumod)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7440 | <key>beginCaptures</key> | |
| 7441 | <dict> | |
| 7442 | <key>1</key> | |
| 7443 | <dict> | |
| 7444 | <key>name</key> | |
| 7445 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7446 | </dict> | |
| 7447 | <key>2</key> | |
| 7448 | <dict> | |
| 7449 | <key>name</key> | |
| 7450 | <string>entity.name.function.mdx</string> | |
| 7451 | <key>patterns</key> | |
| 7452 | <array> | |
| 7453 | <dict> | |
| 7454 | <key>include</key> | |
| 7455 | <string>#markdown-string</string> | |
| 7456 | </dict> | |
| 7457 | </array> | |
| 7458 | </dict> | |
| 7459 | <key>3</key> | |
| 7460 | <dict> | |
| 7461 | <key>patterns</key> | |
| 7462 | <array> | |
| 7463 | <dict> | |
| 7464 | <key>include</key> | |
| 7465 | <string>#markdown-string</string> | |
| 7466 | </dict> | |
| 7467 | </array> | |
| 7468 | </dict> | |
| 7469 | </dict> | |
| 7470 | <key>end</key> | |
| 7471 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7472 | <key>endCaptures</key> | |
| 7473 | <dict> | |
| 7474 | <key>1</key> | |
| 7475 | <dict> | |
| 7476 | <key>name</key> | |
| 7477 | <string>string.other.end.code.fenced.mdx</string> | |
| 7478 | </dict> | |
| 7479 | </dict> | |
| 7480 | <key>name</key> | |
| 7481 | <string>markup.code.raku.mdx</string> | |
| 7482 | <key>patterns</key> | |
| 7483 | <array> | |
| 7484 | <dict> | |
| 7485 | <key>begin</key> | |
| 7486 | <string>(^|\G)(\s*)(.*)</string> | |
| 7487 | <key>while</key> | |
| 7488 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7489 | <key>contentName</key> | |
| 7490 | <string>meta.embedded.raku</string> | |
| 7491 | <key>patterns</key> | |
| 7492 | <array> | |
| 7493 | <dict> | |
| 7494 | <key>include</key> | |
| 7495 | <string>source.raku</string> | |
| 7496 | </dict> | |
| 7497 | </array> | |
| 7498 | </dict> | |
| 7499 | </array> | |
| 7500 | </dict> | |
| 7501 | </array> | |
| 7502 | </dict> | |
| 7503 | <key>commonmark-code-fenced-ruby</key> | |
| 7504 | <dict> | |
| 7505 | <key>patterns</key> | |
| 7506 | <array> | |
| 7507 | <dict> | |
| 7508 | <key>begin</key> | |
| 7509 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:jruby|macruby|(?:.*\.)?(?:builder|druby|duby|eye|gemspec|god|jbuilder|mirah|mspec|pluginspec|podspec|prawn|rabl|rake|rb|rbi|rbuild|rbw|rbx|ru|ruby|thor|watchr)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7510 | <key>beginCaptures</key> | |
| 7511 | <dict> | |
| 7512 | <key>1</key> | |
| 7513 | <dict> | |
| 7514 | <key>name</key> | |
| 7515 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7516 | </dict> | |
| 7517 | <key>2</key> | |
| 7518 | <dict> | |
| 7519 | <key>name</key> | |
| 7520 | <string>entity.name.function.mdx</string> | |
| 7521 | <key>patterns</key> | |
| 7522 | <array> | |
| 7523 | <dict> | |
| 7524 | <key>include</key> | |
| 7525 | <string>#markdown-string</string> | |
| 7526 | </dict> | |
| 7527 | </array> | |
| 7528 | </dict> | |
| 7529 | <key>3</key> | |
| 7530 | <dict> | |
| 7531 | <key>patterns</key> | |
| 7532 | <array> | |
| 7533 | <dict> | |
| 7534 | <key>include</key> | |
| 7535 | <string>#markdown-string</string> | |
| 7536 | </dict> | |
| 7537 | </array> | |
| 7538 | </dict> | |
| 7539 | </dict> | |
| 7540 | <key>end</key> | |
| 7541 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7542 | <key>endCaptures</key> | |
| 7543 | <dict> | |
| 7544 | <key>1</key> | |
| 7545 | <dict> | |
| 7546 | <key>name</key> | |
| 7547 | <string>string.other.end.code.fenced.mdx</string> | |
| 7548 | </dict> | |
| 7549 | </dict> | |
| 7550 | <key>name</key> | |
| 7551 | <string>markup.code.ruby.mdx</string> | |
| 7552 | <key>patterns</key> | |
| 7553 | <array> | |
| 7554 | <dict> | |
| 7555 | <key>begin</key> | |
| 7556 | <string>(^|\G)(\s*)(.*)</string> | |
| 7557 | <key>while</key> | |
| 7558 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7559 | <key>contentName</key> | |
| 7560 | <string>meta.embedded.ruby</string> | |
| 7561 | <key>patterns</key> | |
| 7562 | <array> | |
| 7563 | <dict> | |
| 7564 | <key>include</key> | |
| 7565 | <string>source.ruby</string> | |
| 7566 | </dict> | |
| 7567 | </array> | |
| 7568 | </dict> | |
| 7569 | </array> | |
| 7570 | </dict> | |
| 7571 | <dict> | |
| 7572 | <key>begin</key> | |
| 7573 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:jruby|macruby|(?:.*\.)?(?:builder|druby|duby|eye|gemspec|god|jbuilder|mirah|mspec|pluginspec|podspec|prawn|rabl|rake|rb|rbi|rbuild|rbw|rbx|ru|ruby|thor|watchr)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7574 | <key>beginCaptures</key> | |
| 7575 | <dict> | |
| 7576 | <key>1</key> | |
| 7577 | <dict> | |
| 7578 | <key>name</key> | |
| 7579 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7580 | </dict> | |
| 7581 | <key>2</key> | |
| 7582 | <dict> | |
| 7583 | <key>name</key> | |
| 7584 | <string>entity.name.function.mdx</string> | |
| 7585 | <key>patterns</key> | |
| 7586 | <array> | |
| 7587 | <dict> | |
| 7588 | <key>include</key> | |
| 7589 | <string>#markdown-string</string> | |
| 7590 | </dict> | |
| 7591 | </array> | |
| 7592 | </dict> | |
| 7593 | <key>3</key> | |
| 7594 | <dict> | |
| 7595 | <key>patterns</key> | |
| 7596 | <array> | |
| 7597 | <dict> | |
| 7598 | <key>include</key> | |
| 7599 | <string>#markdown-string</string> | |
| 7600 | </dict> | |
| 7601 | </array> | |
| 7602 | </dict> | |
| 7603 | </dict> | |
| 7604 | <key>end</key> | |
| 7605 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7606 | <key>endCaptures</key> | |
| 7607 | <dict> | |
| 7608 | <key>1</key> | |
| 7609 | <dict> | |
| 7610 | <key>name</key> | |
| 7611 | <string>string.other.end.code.fenced.mdx</string> | |
| 7612 | </dict> | |
| 7613 | </dict> | |
| 7614 | <key>name</key> | |
| 7615 | <string>markup.code.ruby.mdx</string> | |
| 7616 | <key>patterns</key> | |
| 7617 | <array> | |
| 7618 | <dict> | |
| 7619 | <key>begin</key> | |
| 7620 | <string>(^|\G)(\s*)(.*)</string> | |
| 7621 | <key>while</key> | |
| 7622 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7623 | <key>contentName</key> | |
| 7624 | <string>meta.embedded.ruby</string> | |
| 7625 | <key>patterns</key> | |
| 7626 | <array> | |
| 7627 | <dict> | |
| 7628 | <key>include</key> | |
| 7629 | <string>source.ruby</string> | |
| 7630 | </dict> | |
| 7631 | </array> | |
| 7632 | </dict> | |
| 7633 | </array> | |
| 7634 | </dict> | |
| 7635 | </array> | |
| 7636 | </dict> | |
| 7637 | <key>commonmark-code-fenced-rust</key> | |
| 7638 | <dict> | |
| 7639 | <key>patterns</key> | |
| 7640 | <array> | |
| 7641 | <dict> | |
| 7642 | <key>begin</key> | |
| 7643 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:rust|(?:.*\.)?(?:rs|rs\.in)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7644 | <key>beginCaptures</key> | |
| 7645 | <dict> | |
| 7646 | <key>1</key> | |
| 7647 | <dict> | |
| 7648 | <key>name</key> | |
| 7649 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7650 | </dict> | |
| 7651 | <key>2</key> | |
| 7652 | <dict> | |
| 7653 | <key>name</key> | |
| 7654 | <string>entity.name.function.mdx</string> | |
| 7655 | <key>patterns</key> | |
| 7656 | <array> | |
| 7657 | <dict> | |
| 7658 | <key>include</key> | |
| 7659 | <string>#markdown-string</string> | |
| 7660 | </dict> | |
| 7661 | </array> | |
| 7662 | </dict> | |
| 7663 | <key>3</key> | |
| 7664 | <dict> | |
| 7665 | <key>patterns</key> | |
| 7666 | <array> | |
| 7667 | <dict> | |
| 7668 | <key>include</key> | |
| 7669 | <string>#markdown-string</string> | |
| 7670 | </dict> | |
| 7671 | </array> | |
| 7672 | </dict> | |
| 7673 | </dict> | |
| 7674 | <key>end</key> | |
| 7675 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7676 | <key>endCaptures</key> | |
| 7677 | <dict> | |
| 7678 | <key>1</key> | |
| 7679 | <dict> | |
| 7680 | <key>name</key> | |
| 7681 | <string>string.other.end.code.fenced.mdx</string> | |
| 7682 | </dict> | |
| 7683 | </dict> | |
| 7684 | <key>name</key> | |
| 7685 | <string>markup.code.rust.mdx</string> | |
| 7686 | <key>patterns</key> | |
| 7687 | <array> | |
| 7688 | <dict> | |
| 7689 | <key>begin</key> | |
| 7690 | <string>(^|\G)(\s*)(.*)</string> | |
| 7691 | <key>while</key> | |
| 7692 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7693 | <key>contentName</key> | |
| 7694 | <string>meta.embedded.rust</string> | |
| 7695 | <key>patterns</key> | |
| 7696 | <array> | |
| 7697 | <dict> | |
| 7698 | <key>include</key> | |
| 7699 | <string>source.rust</string> | |
| 7700 | </dict> | |
| 7701 | </array> | |
| 7702 | </dict> | |
| 7703 | </array> | |
| 7704 | </dict> | |
| 7705 | <dict> | |
| 7706 | <key>begin</key> | |
| 7707 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:rust|(?:.*\.)?(?:rs|rs\.in)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7708 | <key>beginCaptures</key> | |
| 7709 | <dict> | |
| 7710 | <key>1</key> | |
| 7711 | <dict> | |
| 7712 | <key>name</key> | |
| 7713 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7714 | </dict> | |
| 7715 | <key>2</key> | |
| 7716 | <dict> | |
| 7717 | <key>name</key> | |
| 7718 | <string>entity.name.function.mdx</string> | |
| 7719 | <key>patterns</key> | |
| 7720 | <array> | |
| 7721 | <dict> | |
| 7722 | <key>include</key> | |
| 7723 | <string>#markdown-string</string> | |
| 7724 | </dict> | |
| 7725 | </array> | |
| 7726 | </dict> | |
| 7727 | <key>3</key> | |
| 7728 | <dict> | |
| 7729 | <key>patterns</key> | |
| 7730 | <array> | |
| 7731 | <dict> | |
| 7732 | <key>include</key> | |
| 7733 | <string>#markdown-string</string> | |
| 7734 | </dict> | |
| 7735 | </array> | |
| 7736 | </dict> | |
| 7737 | </dict> | |
| 7738 | <key>end</key> | |
| 7739 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7740 | <key>endCaptures</key> | |
| 7741 | <dict> | |
| 7742 | <key>1</key> | |
| 7743 | <dict> | |
| 7744 | <key>name</key> | |
| 7745 | <string>string.other.end.code.fenced.mdx</string> | |
| 7746 | </dict> | |
| 7747 | </dict> | |
| 7748 | <key>name</key> | |
| 7749 | <string>markup.code.rust.mdx</string> | |
| 7750 | <key>patterns</key> | |
| 7751 | <array> | |
| 7752 | <dict> | |
| 7753 | <key>begin</key> | |
| 7754 | <string>(^|\G)(\s*)(.*)</string> | |
| 7755 | <key>while</key> | |
| 7756 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7757 | <key>contentName</key> | |
| 7758 | <string>meta.embedded.rust</string> | |
| 7759 | <key>patterns</key> | |
| 7760 | <array> | |
| 7761 | <dict> | |
| 7762 | <key>include</key> | |
| 7763 | <string>source.rust</string> | |
| 7764 | </dict> | |
| 7765 | </array> | |
| 7766 | </dict> | |
| 7767 | </array> | |
| 7768 | </dict> | |
| 7769 | </array> | |
| 7770 | </dict> | |
| 7771 | <key>commonmark-code-fenced-scala</key> | |
| 7772 | <dict> | |
| 7773 | <key>patterns</key> | |
| 7774 | <array> | |
| 7775 | <dict> | |
| 7776 | <key>begin</key> | |
| 7777 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:kojo|sbt|sc|scala)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7778 | <key>beginCaptures</key> | |
| 7779 | <dict> | |
| 7780 | <key>1</key> | |
| 7781 | <dict> | |
| 7782 | <key>name</key> | |
| 7783 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7784 | </dict> | |
| 7785 | <key>2</key> | |
| 7786 | <dict> | |
| 7787 | <key>name</key> | |
| 7788 | <string>entity.name.function.mdx</string> | |
| 7789 | <key>patterns</key> | |
| 7790 | <array> | |
| 7791 | <dict> | |
| 7792 | <key>include</key> | |
| 7793 | <string>#markdown-string</string> | |
| 7794 | </dict> | |
| 7795 | </array> | |
| 7796 | </dict> | |
| 7797 | <key>3</key> | |
| 7798 | <dict> | |
| 7799 | <key>patterns</key> | |
| 7800 | <array> | |
| 7801 | <dict> | |
| 7802 | <key>include</key> | |
| 7803 | <string>#markdown-string</string> | |
| 7804 | </dict> | |
| 7805 | </array> | |
| 7806 | </dict> | |
| 7807 | </dict> | |
| 7808 | <key>end</key> | |
| 7809 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7810 | <key>endCaptures</key> | |
| 7811 | <dict> | |
| 7812 | <key>1</key> | |
| 7813 | <dict> | |
| 7814 | <key>name</key> | |
| 7815 | <string>string.other.end.code.fenced.mdx</string> | |
| 7816 | </dict> | |
| 7817 | </dict> | |
| 7818 | <key>name</key> | |
| 7819 | <string>markup.code.scala.mdx</string> | |
| 7820 | <key>patterns</key> | |
| 7821 | <array> | |
| 7822 | <dict> | |
| 7823 | <key>begin</key> | |
| 7824 | <string>(^|\G)(\s*)(.*)</string> | |
| 7825 | <key>while</key> | |
| 7826 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7827 | <key>contentName</key> | |
| 7828 | <string>meta.embedded.scala</string> | |
| 7829 | <key>patterns</key> | |
| 7830 | <array> | |
| 7831 | <dict> | |
| 7832 | <key>include</key> | |
| 7833 | <string>source.scala</string> | |
| 7834 | </dict> | |
| 7835 | </array> | |
| 7836 | </dict> | |
| 7837 | </array> | |
| 7838 | </dict> | |
| 7839 | <dict> | |
| 7840 | <key>begin</key> | |
| 7841 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:kojo|sbt|sc|scala)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7842 | <key>beginCaptures</key> | |
| 7843 | <dict> | |
| 7844 | <key>1</key> | |
| 7845 | <dict> | |
| 7846 | <key>name</key> | |
| 7847 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7848 | </dict> | |
| 7849 | <key>2</key> | |
| 7850 | <dict> | |
| 7851 | <key>name</key> | |
| 7852 | <string>entity.name.function.mdx</string> | |
| 7853 | <key>patterns</key> | |
| 7854 | <array> | |
| 7855 | <dict> | |
| 7856 | <key>include</key> | |
| 7857 | <string>#markdown-string</string> | |
| 7858 | </dict> | |
| 7859 | </array> | |
| 7860 | </dict> | |
| 7861 | <key>3</key> | |
| 7862 | <dict> | |
| 7863 | <key>patterns</key> | |
| 7864 | <array> | |
| 7865 | <dict> | |
| 7866 | <key>include</key> | |
| 7867 | <string>#markdown-string</string> | |
| 7868 | </dict> | |
| 7869 | </array> | |
| 7870 | </dict> | |
| 7871 | </dict> | |
| 7872 | <key>end</key> | |
| 7873 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7874 | <key>endCaptures</key> | |
| 7875 | <dict> | |
| 7876 | <key>1</key> | |
| 7877 | <dict> | |
| 7878 | <key>name</key> | |
| 7879 | <string>string.other.end.code.fenced.mdx</string> | |
| 7880 | </dict> | |
| 7881 | </dict> | |
| 7882 | <key>name</key> | |
| 7883 | <string>markup.code.scala.mdx</string> | |
| 7884 | <key>patterns</key> | |
| 7885 | <array> | |
| 7886 | <dict> | |
| 7887 | <key>begin</key> | |
| 7888 | <string>(^|\G)(\s*)(.*)</string> | |
| 7889 | <key>while</key> | |
| 7890 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7891 | <key>contentName</key> | |
| 7892 | <string>meta.embedded.scala</string> | |
| 7893 | <key>patterns</key> | |
| 7894 | <array> | |
| 7895 | <dict> | |
| 7896 | <key>include</key> | |
| 7897 | <string>source.scala</string> | |
| 7898 | </dict> | |
| 7899 | </array> | |
| 7900 | </dict> | |
| 7901 | </array> | |
| 7902 | </dict> | |
| 7903 | </array> | |
| 7904 | </dict> | |
| 7905 | <key>commonmark-code-fenced-scss</key> | |
| 7906 | <dict> | |
| 7907 | <key>patterns</key> | |
| 7908 | <array> | |
| 7909 | <dict> | |
| 7910 | <key>begin</key> | |
| 7911 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?scss))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 7912 | <key>beginCaptures</key> | |
| 7913 | <dict> | |
| 7914 | <key>1</key> | |
| 7915 | <dict> | |
| 7916 | <key>name</key> | |
| 7917 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7918 | </dict> | |
| 7919 | <key>2</key> | |
| 7920 | <dict> | |
| 7921 | <key>name</key> | |
| 7922 | <string>entity.name.function.mdx</string> | |
| 7923 | <key>patterns</key> | |
| 7924 | <array> | |
| 7925 | <dict> | |
| 7926 | <key>include</key> | |
| 7927 | <string>#markdown-string</string> | |
| 7928 | </dict> | |
| 7929 | </array> | |
| 7930 | </dict> | |
| 7931 | <key>3</key> | |
| 7932 | <dict> | |
| 7933 | <key>patterns</key> | |
| 7934 | <array> | |
| 7935 | <dict> | |
| 7936 | <key>include</key> | |
| 7937 | <string>#markdown-string</string> | |
| 7938 | </dict> | |
| 7939 | </array> | |
| 7940 | </dict> | |
| 7941 | </dict> | |
| 7942 | <key>end</key> | |
| 7943 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 7944 | <key>endCaptures</key> | |
| 7945 | <dict> | |
| 7946 | <key>1</key> | |
| 7947 | <dict> | |
| 7948 | <key>name</key> | |
| 7949 | <string>string.other.end.code.fenced.mdx</string> | |
| 7950 | </dict> | |
| 7951 | </dict> | |
| 7952 | <key>name</key> | |
| 7953 | <string>markup.code.scss.mdx</string> | |
| 7954 | <key>patterns</key> | |
| 7955 | <array> | |
| 7956 | <dict> | |
| 7957 | <key>begin</key> | |
| 7958 | <string>(^|\G)(\s*)(.*)</string> | |
| 7959 | <key>while</key> | |
| 7960 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 7961 | <key>contentName</key> | |
| 7962 | <string>meta.embedded.scss</string> | |
| 7963 | <key>patterns</key> | |
| 7964 | <array> | |
| 7965 | <dict> | |
| 7966 | <key>include</key> | |
| 7967 | <string>source.css.scss</string> | |
| 7968 | </dict> | |
| 7969 | </array> | |
| 7970 | </dict> | |
| 7971 | </array> | |
| 7972 | </dict> | |
| 7973 | <dict> | |
| 7974 | <key>begin</key> | |
| 7975 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?scss))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 7976 | <key>beginCaptures</key> | |
| 7977 | <dict> | |
| 7978 | <key>1</key> | |
| 7979 | <dict> | |
| 7980 | <key>name</key> | |
| 7981 | <string>string.other.begin.code.fenced.mdx</string> | |
| 7982 | </dict> | |
| 7983 | <key>2</key> | |
| 7984 | <dict> | |
| 7985 | <key>name</key> | |
| 7986 | <string>entity.name.function.mdx</string> | |
| 7987 | <key>patterns</key> | |
| 7988 | <array> | |
| 7989 | <dict> | |
| 7990 | <key>include</key> | |
| 7991 | <string>#markdown-string</string> | |
| 7992 | </dict> | |
| 7993 | </array> | |
| 7994 | </dict> | |
| 7995 | <key>3</key> | |
| 7996 | <dict> | |
| 7997 | <key>patterns</key> | |
| 7998 | <array> | |
| 7999 | <dict> | |
| 8000 | <key>include</key> | |
| 8001 | <string>#markdown-string</string> | |
| 8002 | </dict> | |
| 8003 | </array> | |
| 8004 | </dict> | |
| 8005 | </dict> | |
| 8006 | <key>end</key> | |
| 8007 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8008 | <key>endCaptures</key> | |
| 8009 | <dict> | |
| 8010 | <key>1</key> | |
| 8011 | <dict> | |
| 8012 | <key>name</key> | |
| 8013 | <string>string.other.end.code.fenced.mdx</string> | |
| 8014 | </dict> | |
| 8015 | </dict> | |
| 8016 | <key>name</key> | |
| 8017 | <string>markup.code.scss.mdx</string> | |
| 8018 | <key>patterns</key> | |
| 8019 | <array> | |
| 8020 | <dict> | |
| 8021 | <key>begin</key> | |
| 8022 | <string>(^|\G)(\s*)(.*)</string> | |
| 8023 | <key>while</key> | |
| 8024 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8025 | <key>contentName</key> | |
| 8026 | <string>meta.embedded.scss</string> | |
| 8027 | <key>patterns</key> | |
| 8028 | <array> | |
| 8029 | <dict> | |
| 8030 | <key>include</key> | |
| 8031 | <string>source.css.scss</string> | |
| 8032 | </dict> | |
| 8033 | </array> | |
| 8034 | </dict> | |
| 8035 | </array> | |
| 8036 | </dict> | |
| 8037 | </array> | |
| 8038 | </dict> | |
| 8039 | <key>commonmark-code-fenced-shell</key> | |
| 8040 | <dict> | |
| 8041 | <key>patterns</key> | |
| 8042 | <array> | |
| 8043 | <dict> | |
| 8044 | <key>begin</key> | |
| 8045 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:abuild|alpine\x2dabuild|apkbuild|envrc|gentoo\x2debuild|gentoo\x2declass|openrc|openrc\x2drunscript|shell|shell\x2dscript|(?:.*\.)?(?:bash|bats|command|csh|ebuild|eclass|ksh|sh|sh\.in|tcsh|tmux|tool|zsh|zsh\x2dtheme)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8046 | <key>beginCaptures</key> | |
| 8047 | <dict> | |
| 8048 | <key>1</key> | |
| 8049 | <dict> | |
| 8050 | <key>name</key> | |
| 8051 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8052 | </dict> | |
| 8053 | <key>2</key> | |
| 8054 | <dict> | |
| 8055 | <key>name</key> | |
| 8056 | <string>entity.name.function.mdx</string> | |
| 8057 | <key>patterns</key> | |
| 8058 | <array> | |
| 8059 | <dict> | |
| 8060 | <key>include</key> | |
| 8061 | <string>#markdown-string</string> | |
| 8062 | </dict> | |
| 8063 | </array> | |
| 8064 | </dict> | |
| 8065 | <key>3</key> | |
| 8066 | <dict> | |
| 8067 | <key>patterns</key> | |
| 8068 | <array> | |
| 8069 | <dict> | |
| 8070 | <key>include</key> | |
| 8071 | <string>#markdown-string</string> | |
| 8072 | </dict> | |
| 8073 | </array> | |
| 8074 | </dict> | |
| 8075 | </dict> | |
| 8076 | <key>end</key> | |
| 8077 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8078 | <key>endCaptures</key> | |
| 8079 | <dict> | |
| 8080 | <key>1</key> | |
| 8081 | <dict> | |
| 8082 | <key>name</key> | |
| 8083 | <string>string.other.end.code.fenced.mdx</string> | |
| 8084 | </dict> | |
| 8085 | </dict> | |
| 8086 | <key>name</key> | |
| 8087 | <string>markup.code.shell.mdx</string> | |
| 8088 | <key>patterns</key> | |
| 8089 | <array> | |
| 8090 | <dict> | |
| 8091 | <key>begin</key> | |
| 8092 | <string>(^|\G)(\s*)(.*)</string> | |
| 8093 | <key>while</key> | |
| 8094 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8095 | <key>contentName</key> | |
| 8096 | <string>meta.embedded.shell</string> | |
| 8097 | <key>patterns</key> | |
| 8098 | <array> | |
| 8099 | <dict> | |
| 8100 | <key>include</key> | |
| 8101 | <string>source.shell</string> | |
| 8102 | </dict> | |
| 8103 | </array> | |
| 8104 | </dict> | |
| 8105 | </array> | |
| 8106 | </dict> | |
| 8107 | <dict> | |
| 8108 | <key>begin</key> | |
| 8109 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:abuild|alpine\x2dabuild|apkbuild|envrc|gentoo\x2debuild|gentoo\x2declass|openrc|openrc\x2drunscript|shell|shell\x2dscript|(?:.*\.)?(?:bash|bats|command|csh|ebuild|eclass|ksh|sh|sh\.in|tcsh|tmux|tool|zsh|zsh\x2dtheme)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8110 | <key>beginCaptures</key> | |
| 8111 | <dict> | |
| 8112 | <key>1</key> | |
| 8113 | <dict> | |
| 8114 | <key>name</key> | |
| 8115 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8116 | </dict> | |
| 8117 | <key>2</key> | |
| 8118 | <dict> | |
| 8119 | <key>name</key> | |
| 8120 | <string>entity.name.function.mdx</string> | |
| 8121 | <key>patterns</key> | |
| 8122 | <array> | |
| 8123 | <dict> | |
| 8124 | <key>include</key> | |
| 8125 | <string>#markdown-string</string> | |
| 8126 | </dict> | |
| 8127 | </array> | |
| 8128 | </dict> | |
| 8129 | <key>3</key> | |
| 8130 | <dict> | |
| 8131 | <key>patterns</key> | |
| 8132 | <array> | |
| 8133 | <dict> | |
| 8134 | <key>include</key> | |
| 8135 | <string>#markdown-string</string> | |
| 8136 | </dict> | |
| 8137 | </array> | |
| 8138 | </dict> | |
| 8139 | </dict> | |
| 8140 | <key>end</key> | |
| 8141 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8142 | <key>endCaptures</key> | |
| 8143 | <dict> | |
| 8144 | <key>1</key> | |
| 8145 | <dict> | |
| 8146 | <key>name</key> | |
| 8147 | <string>string.other.end.code.fenced.mdx</string> | |
| 8148 | </dict> | |
| 8149 | </dict> | |
| 8150 | <key>name</key> | |
| 8151 | <string>markup.code.shell.mdx</string> | |
| 8152 | <key>patterns</key> | |
| 8153 | <array> | |
| 8154 | <dict> | |
| 8155 | <key>begin</key> | |
| 8156 | <string>(^|\G)(\s*)(.*)</string> | |
| 8157 | <key>while</key> | |
| 8158 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8159 | <key>contentName</key> | |
| 8160 | <string>meta.embedded.shell</string> | |
| 8161 | <key>patterns</key> | |
| 8162 | <array> | |
| 8163 | <dict> | |
| 8164 | <key>include</key> | |
| 8165 | <string>source.shell</string> | |
| 8166 | </dict> | |
| 8167 | </array> | |
| 8168 | </dict> | |
| 8169 | </array> | |
| 8170 | </dict> | |
| 8171 | </array> | |
| 8172 | </dict> | |
| 8173 | <key>commonmark-code-fenced-shell-session</key> | |
| 8174 | <dict> | |
| 8175 | <key>patterns</key> | |
| 8176 | <array> | |
| 8177 | <dict> | |
| 8178 | <key>begin</key> | |
| 8179 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bash\x2dsession|console|shellsession|(?:.*\.)?sh\x2dsession))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8180 | <key>beginCaptures</key> | |
| 8181 | <dict> | |
| 8182 | <key>1</key> | |
| 8183 | <dict> | |
| 8184 | <key>name</key> | |
| 8185 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8186 | </dict> | |
| 8187 | <key>2</key> | |
| 8188 | <dict> | |
| 8189 | <key>name</key> | |
| 8190 | <string>entity.name.function.mdx</string> | |
| 8191 | <key>patterns</key> | |
| 8192 | <array> | |
| 8193 | <dict> | |
| 8194 | <key>include</key> | |
| 8195 | <string>#markdown-string</string> | |
| 8196 | </dict> | |
| 8197 | </array> | |
| 8198 | </dict> | |
| 8199 | <key>3</key> | |
| 8200 | <dict> | |
| 8201 | <key>patterns</key> | |
| 8202 | <array> | |
| 8203 | <dict> | |
| 8204 | <key>include</key> | |
| 8205 | <string>#markdown-string</string> | |
| 8206 | </dict> | |
| 8207 | </array> | |
| 8208 | </dict> | |
| 8209 | </dict> | |
| 8210 | <key>end</key> | |
| 8211 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8212 | <key>endCaptures</key> | |
| 8213 | <dict> | |
| 8214 | <key>1</key> | |
| 8215 | <dict> | |
| 8216 | <key>name</key> | |
| 8217 | <string>string.other.end.code.fenced.mdx</string> | |
| 8218 | </dict> | |
| 8219 | </dict> | |
| 8220 | <key>name</key> | |
| 8221 | <string>markup.code.shell-session.mdx</string> | |
| 8222 | <key>patterns</key> | |
| 8223 | <array> | |
| 8224 | <dict> | |
| 8225 | <key>begin</key> | |
| 8226 | <string>(^|\G)(\s*)(.*)</string> | |
| 8227 | <key>while</key> | |
| 8228 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8229 | <key>contentName</key> | |
| 8230 | <string>meta.embedded.shell-session</string> | |
| 8231 | <key>patterns</key> | |
| 8232 | <array> | |
| 8233 | <dict> | |
| 8234 | <key>include</key> | |
| 8235 | <string>text.shell-session</string> | |
| 8236 | </dict> | |
| 8237 | </array> | |
| 8238 | </dict> | |
| 8239 | </array> | |
| 8240 | </dict> | |
| 8241 | <dict> | |
| 8242 | <key>begin</key> | |
| 8243 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bash\x2dsession|console|shellsession|(?:.*\.)?sh\x2dsession))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8244 | <key>beginCaptures</key> | |
| 8245 | <dict> | |
| 8246 | <key>1</key> | |
| 8247 | <dict> | |
| 8248 | <key>name</key> | |
| 8249 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8250 | </dict> | |
| 8251 | <key>2</key> | |
| 8252 | <dict> | |
| 8253 | <key>name</key> | |
| 8254 | <string>entity.name.function.mdx</string> | |
| 8255 | <key>patterns</key> | |
| 8256 | <array> | |
| 8257 | <dict> | |
| 8258 | <key>include</key> | |
| 8259 | <string>#markdown-string</string> | |
| 8260 | </dict> | |
| 8261 | </array> | |
| 8262 | </dict> | |
| 8263 | <key>3</key> | |
| 8264 | <dict> | |
| 8265 | <key>patterns</key> | |
| 8266 | <array> | |
| 8267 | <dict> | |
| 8268 | <key>include</key> | |
| 8269 | <string>#markdown-string</string> | |
| 8270 | </dict> | |
| 8271 | </array> | |
| 8272 | </dict> | |
| 8273 | </dict> | |
| 8274 | <key>end</key> | |
| 8275 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8276 | <key>endCaptures</key> | |
| 8277 | <dict> | |
| 8278 | <key>1</key> | |
| 8279 | <dict> | |
| 8280 | <key>name</key> | |
| 8281 | <string>string.other.end.code.fenced.mdx</string> | |
| 8282 | </dict> | |
| 8283 | </dict> | |
| 8284 | <key>name</key> | |
| 8285 | <string>markup.code.shell-session.mdx</string> | |
| 8286 | <key>patterns</key> | |
| 8287 | <array> | |
| 8288 | <dict> | |
| 8289 | <key>begin</key> | |
| 8290 | <string>(^|\G)(\s*)(.*)</string> | |
| 8291 | <key>while</key> | |
| 8292 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8293 | <key>contentName</key> | |
| 8294 | <string>meta.embedded.shell-session</string> | |
| 8295 | <key>patterns</key> | |
| 8296 | <array> | |
| 8297 | <dict> | |
| 8298 | <key>include</key> | |
| 8299 | <string>text.shell-session</string> | |
| 8300 | </dict> | |
| 8301 | </array> | |
| 8302 | </dict> | |
| 8303 | </array> | |
| 8304 | </dict> | |
| 8305 | </array> | |
| 8306 | </dict> | |
| 8307 | <key>commonmark-code-fenced-sql</key> | |
| 8308 | <dict> | |
| 8309 | <key>patterns</key> | |
| 8310 | <array> | |
| 8311 | <dict> | |
| 8312 | <key>begin</key> | |
| 8313 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:plpgsql|sqlpl|(?:.*\.)?(?:cql|db2|ddl|mysql|pgsql|prc|sql|sql|sql|tab|udf|viw)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8314 | <key>beginCaptures</key> | |
| 8315 | <dict> | |
| 8316 | <key>1</key> | |
| 8317 | <dict> | |
| 8318 | <key>name</key> | |
| 8319 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8320 | </dict> | |
| 8321 | <key>2</key> | |
| 8322 | <dict> | |
| 8323 | <key>name</key> | |
| 8324 | <string>entity.name.function.mdx</string> | |
| 8325 | <key>patterns</key> | |
| 8326 | <array> | |
| 8327 | <dict> | |
| 8328 | <key>include</key> | |
| 8329 | <string>#markdown-string</string> | |
| 8330 | </dict> | |
| 8331 | </array> | |
| 8332 | </dict> | |
| 8333 | <key>3</key> | |
| 8334 | <dict> | |
| 8335 | <key>patterns</key> | |
| 8336 | <array> | |
| 8337 | <dict> | |
| 8338 | <key>include</key> | |
| 8339 | <string>#markdown-string</string> | |
| 8340 | </dict> | |
| 8341 | </array> | |
| 8342 | </dict> | |
| 8343 | </dict> | |
| 8344 | <key>end</key> | |
| 8345 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8346 | <key>endCaptures</key> | |
| 8347 | <dict> | |
| 8348 | <key>1</key> | |
| 8349 | <dict> | |
| 8350 | <key>name</key> | |
| 8351 | <string>string.other.end.code.fenced.mdx</string> | |
| 8352 | </dict> | |
| 8353 | </dict> | |
| 8354 | <key>name</key> | |
| 8355 | <string>markup.code.sql.mdx</string> | |
| 8356 | <key>patterns</key> | |
| 8357 | <array> | |
| 8358 | <dict> | |
| 8359 | <key>begin</key> | |
| 8360 | <string>(^|\G)(\s*)(.*)</string> | |
| 8361 | <key>while</key> | |
| 8362 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8363 | <key>contentName</key> | |
| 8364 | <string>meta.embedded.sql</string> | |
| 8365 | <key>patterns</key> | |
| 8366 | <array> | |
| 8367 | <dict> | |
| 8368 | <key>include</key> | |
| 8369 | <string>source.sql</string> | |
| 8370 | </dict> | |
| 8371 | </array> | |
| 8372 | </dict> | |
| 8373 | </array> | |
| 8374 | </dict> | |
| 8375 | <dict> | |
| 8376 | <key>begin</key> | |
| 8377 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:plpgsql|sqlpl|(?:.*\.)?(?:cql|db2|ddl|mysql|pgsql|prc|sql|sql|sql|tab|udf|viw)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8378 | <key>beginCaptures</key> | |
| 8379 | <dict> | |
| 8380 | <key>1</key> | |
| 8381 | <dict> | |
| 8382 | <key>name</key> | |
| 8383 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8384 | </dict> | |
| 8385 | <key>2</key> | |
| 8386 | <dict> | |
| 8387 | <key>name</key> | |
| 8388 | <string>entity.name.function.mdx</string> | |
| 8389 | <key>patterns</key> | |
| 8390 | <array> | |
| 8391 | <dict> | |
| 8392 | <key>include</key> | |
| 8393 | <string>#markdown-string</string> | |
| 8394 | </dict> | |
| 8395 | </array> | |
| 8396 | </dict> | |
| 8397 | <key>3</key> | |
| 8398 | <dict> | |
| 8399 | <key>patterns</key> | |
| 8400 | <array> | |
| 8401 | <dict> | |
| 8402 | <key>include</key> | |
| 8403 | <string>#markdown-string</string> | |
| 8404 | </dict> | |
| 8405 | </array> | |
| 8406 | </dict> | |
| 8407 | </dict> | |
| 8408 | <key>end</key> | |
| 8409 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8410 | <key>endCaptures</key> | |
| 8411 | <dict> | |
| 8412 | <key>1</key> | |
| 8413 | <dict> | |
| 8414 | <key>name</key> | |
| 8415 | <string>string.other.end.code.fenced.mdx</string> | |
| 8416 | </dict> | |
| 8417 | </dict> | |
| 8418 | <key>name</key> | |
| 8419 | <string>markup.code.sql.mdx</string> | |
| 8420 | <key>patterns</key> | |
| 8421 | <array> | |
| 8422 | <dict> | |
| 8423 | <key>begin</key> | |
| 8424 | <string>(^|\G)(\s*)(.*)</string> | |
| 8425 | <key>while</key> | |
| 8426 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8427 | <key>contentName</key> | |
| 8428 | <string>meta.embedded.sql</string> | |
| 8429 | <key>patterns</key> | |
| 8430 | <array> | |
| 8431 | <dict> | |
| 8432 | <key>include</key> | |
| 8433 | <string>source.sql</string> | |
| 8434 | </dict> | |
| 8435 | </array> | |
| 8436 | </dict> | |
| 8437 | </array> | |
| 8438 | </dict> | |
| 8439 | </array> | |
| 8440 | </dict> | |
| 8441 | <key>commonmark-code-fenced-svg</key> | |
| 8442 | <dict> | |
| 8443 | <key>patterns</key> | |
| 8444 | <array> | |
| 8445 | <dict> | |
| 8446 | <key>begin</key> | |
| 8447 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?svg))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8448 | <key>beginCaptures</key> | |
| 8449 | <dict> | |
| 8450 | <key>1</key> | |
| 8451 | <dict> | |
| 8452 | <key>name</key> | |
| 8453 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8454 | </dict> | |
| 8455 | <key>2</key> | |
| 8456 | <dict> | |
| 8457 | <key>name</key> | |
| 8458 | <string>entity.name.function.mdx</string> | |
| 8459 | <key>patterns</key> | |
| 8460 | <array> | |
| 8461 | <dict> | |
| 8462 | <key>include</key> | |
| 8463 | <string>#markdown-string</string> | |
| 8464 | </dict> | |
| 8465 | </array> | |
| 8466 | </dict> | |
| 8467 | <key>3</key> | |
| 8468 | <dict> | |
| 8469 | <key>patterns</key> | |
| 8470 | <array> | |
| 8471 | <dict> | |
| 8472 | <key>include</key> | |
| 8473 | <string>#markdown-string</string> | |
| 8474 | </dict> | |
| 8475 | </array> | |
| 8476 | </dict> | |
| 8477 | </dict> | |
| 8478 | <key>end</key> | |
| 8479 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8480 | <key>endCaptures</key> | |
| 8481 | <dict> | |
| 8482 | <key>1</key> | |
| 8483 | <dict> | |
| 8484 | <key>name</key> | |
| 8485 | <string>string.other.end.code.fenced.mdx</string> | |
| 8486 | </dict> | |
| 8487 | </dict> | |
| 8488 | <key>name</key> | |
| 8489 | <string>markup.code.svg.mdx</string> | |
| 8490 | <key>patterns</key> | |
| 8491 | <array> | |
| 8492 | <dict> | |
| 8493 | <key>begin</key> | |
| 8494 | <string>(^|\G)(\s*)(.*)</string> | |
| 8495 | <key>while</key> | |
| 8496 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8497 | <key>contentName</key> | |
| 8498 | <string>meta.embedded.svg</string> | |
| 8499 | <key>patterns</key> | |
| 8500 | <array> | |
| 8501 | <dict> | |
| 8502 | <key>include</key> | |
| 8503 | <string>text.xml.svg</string> | |
| 8504 | </dict> | |
| 8505 | <dict> | |
| 8506 | <key>include</key> | |
| 8507 | <string>text.xml</string> | |
| 8508 | </dict> | |
| 8509 | </array> | |
| 8510 | </dict> | |
| 8511 | </array> | |
| 8512 | </dict> | |
| 8513 | <dict> | |
| 8514 | <key>begin</key> | |
| 8515 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?svg))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8516 | <key>beginCaptures</key> | |
| 8517 | <dict> | |
| 8518 | <key>1</key> | |
| 8519 | <dict> | |
| 8520 | <key>name</key> | |
| 8521 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8522 | </dict> | |
| 8523 | <key>2</key> | |
| 8524 | <dict> | |
| 8525 | <key>name</key> | |
| 8526 | <string>entity.name.function.mdx</string> | |
| 8527 | <key>patterns</key> | |
| 8528 | <array> | |
| 8529 | <dict> | |
| 8530 | <key>include</key> | |
| 8531 | <string>#markdown-string</string> | |
| 8532 | </dict> | |
| 8533 | </array> | |
| 8534 | </dict> | |
| 8535 | <key>3</key> | |
| 8536 | <dict> | |
| 8537 | <key>patterns</key> | |
| 8538 | <array> | |
| 8539 | <dict> | |
| 8540 | <key>include</key> | |
| 8541 | <string>#markdown-string</string> | |
| 8542 | </dict> | |
| 8543 | </array> | |
| 8544 | </dict> | |
| 8545 | </dict> | |
| 8546 | <key>end</key> | |
| 8547 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8548 | <key>endCaptures</key> | |
| 8549 | <dict> | |
| 8550 | <key>1</key> | |
| 8551 | <dict> | |
| 8552 | <key>name</key> | |
| 8553 | <string>string.other.end.code.fenced.mdx</string> | |
| 8554 | </dict> | |
| 8555 | </dict> | |
| 8556 | <key>name</key> | |
| 8557 | <string>markup.code.svg.mdx</string> | |
| 8558 | <key>patterns</key> | |
| 8559 | <array> | |
| 8560 | <dict> | |
| 8561 | <key>begin</key> | |
| 8562 | <string>(^|\G)(\s*)(.*)</string> | |
| 8563 | <key>while</key> | |
| 8564 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8565 | <key>contentName</key> | |
| 8566 | <string>meta.embedded.svg</string> | |
| 8567 | <key>patterns</key> | |
| 8568 | <array> | |
| 8569 | <dict> | |
| 8570 | <key>include</key> | |
| 8571 | <string>text.xml.svg</string> | |
| 8572 | </dict> | |
| 8573 | <dict> | |
| 8574 | <key>include</key> | |
| 8575 | <string>text.xml</string> | |
| 8576 | </dict> | |
| 8577 | </array> | |
| 8578 | </dict> | |
| 8579 | </array> | |
| 8580 | </dict> | |
| 8581 | </array> | |
| 8582 | </dict> | |
| 8583 | <key>commonmark-code-fenced-swift</key> | |
| 8584 | <dict> | |
| 8585 | <key>patterns</key> | |
| 8586 | <array> | |
| 8587 | <dict> | |
| 8588 | <key>begin</key> | |
| 8589 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?swift))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8590 | <key>beginCaptures</key> | |
| 8591 | <dict> | |
| 8592 | <key>1</key> | |
| 8593 | <dict> | |
| 8594 | <key>name</key> | |
| 8595 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8596 | </dict> | |
| 8597 | <key>2</key> | |
| 8598 | <dict> | |
| 8599 | <key>name</key> | |
| 8600 | <string>entity.name.function.mdx</string> | |
| 8601 | <key>patterns</key> | |
| 8602 | <array> | |
| 8603 | <dict> | |
| 8604 | <key>include</key> | |
| 8605 | <string>#markdown-string</string> | |
| 8606 | </dict> | |
| 8607 | </array> | |
| 8608 | </dict> | |
| 8609 | <key>3</key> | |
| 8610 | <dict> | |
| 8611 | <key>patterns</key> | |
| 8612 | <array> | |
| 8613 | <dict> | |
| 8614 | <key>include</key> | |
| 8615 | <string>#markdown-string</string> | |
| 8616 | </dict> | |
| 8617 | </array> | |
| 8618 | </dict> | |
| 8619 | </dict> | |
| 8620 | <key>end</key> | |
| 8621 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8622 | <key>endCaptures</key> | |
| 8623 | <dict> | |
| 8624 | <key>1</key> | |
| 8625 | <dict> | |
| 8626 | <key>name</key> | |
| 8627 | <string>string.other.end.code.fenced.mdx</string> | |
| 8628 | </dict> | |
| 8629 | </dict> | |
| 8630 | <key>name</key> | |
| 8631 | <string>markup.code.swift.mdx</string> | |
| 8632 | <key>patterns</key> | |
| 8633 | <array> | |
| 8634 | <dict> | |
| 8635 | <key>begin</key> | |
| 8636 | <string>(^|\G)(\s*)(.*)</string> | |
| 8637 | <key>while</key> | |
| 8638 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8639 | <key>contentName</key> | |
| 8640 | <string>meta.embedded.swift</string> | |
| 8641 | <key>patterns</key> | |
| 8642 | <array> | |
| 8643 | <dict> | |
| 8644 | <key>include</key> | |
| 8645 | <string>source.swift</string> | |
| 8646 | </dict> | |
| 8647 | </array> | |
| 8648 | </dict> | |
| 8649 | </array> | |
| 8650 | </dict> | |
| 8651 | <dict> | |
| 8652 | <key>begin</key> | |
| 8653 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?swift))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8654 | <key>beginCaptures</key> | |
| 8655 | <dict> | |
| 8656 | <key>1</key> | |
| 8657 | <dict> | |
| 8658 | <key>name</key> | |
| 8659 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8660 | </dict> | |
| 8661 | <key>2</key> | |
| 8662 | <dict> | |
| 8663 | <key>name</key> | |
| 8664 | <string>entity.name.function.mdx</string> | |
| 8665 | <key>patterns</key> | |
| 8666 | <array> | |
| 8667 | <dict> | |
| 8668 | <key>include</key> | |
| 8669 | <string>#markdown-string</string> | |
| 8670 | </dict> | |
| 8671 | </array> | |
| 8672 | </dict> | |
| 8673 | <key>3</key> | |
| 8674 | <dict> | |
| 8675 | <key>patterns</key> | |
| 8676 | <array> | |
| 8677 | <dict> | |
| 8678 | <key>include</key> | |
| 8679 | <string>#markdown-string</string> | |
| 8680 | </dict> | |
| 8681 | </array> | |
| 8682 | </dict> | |
| 8683 | </dict> | |
| 8684 | <key>end</key> | |
| 8685 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8686 | <key>endCaptures</key> | |
| 8687 | <dict> | |
| 8688 | <key>1</key> | |
| 8689 | <dict> | |
| 8690 | <key>name</key> | |
| 8691 | <string>string.other.end.code.fenced.mdx</string> | |
| 8692 | </dict> | |
| 8693 | </dict> | |
| 8694 | <key>name</key> | |
| 8695 | <string>markup.code.swift.mdx</string> | |
| 8696 | <key>patterns</key> | |
| 8697 | <array> | |
| 8698 | <dict> | |
| 8699 | <key>begin</key> | |
| 8700 | <string>(^|\G)(\s*)(.*)</string> | |
| 8701 | <key>while</key> | |
| 8702 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8703 | <key>contentName</key> | |
| 8704 | <string>meta.embedded.swift</string> | |
| 8705 | <key>patterns</key> | |
| 8706 | <array> | |
| 8707 | <dict> | |
| 8708 | <key>include</key> | |
| 8709 | <string>source.swift</string> | |
| 8710 | </dict> | |
| 8711 | </array> | |
| 8712 | </dict> | |
| 8713 | </array> | |
| 8714 | </dict> | |
| 8715 | </array> | |
| 8716 | </dict> | |
| 8717 | <key>commonmark-code-fenced-toml</key> | |
| 8718 | <dict> | |
| 8719 | <key>patterns</key> | |
| 8720 | <array> | |
| 8721 | <dict> | |
| 8722 | <key>begin</key> | |
| 8723 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?toml))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8724 | <key>beginCaptures</key> | |
| 8725 | <dict> | |
| 8726 | <key>1</key> | |
| 8727 | <dict> | |
| 8728 | <key>name</key> | |
| 8729 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8730 | </dict> | |
| 8731 | <key>2</key> | |
| 8732 | <dict> | |
| 8733 | <key>name</key> | |
| 8734 | <string>entity.name.function.mdx</string> | |
| 8735 | <key>patterns</key> | |
| 8736 | <array> | |
| 8737 | <dict> | |
| 8738 | <key>include</key> | |
| 8739 | <string>#markdown-string</string> | |
| 8740 | </dict> | |
| 8741 | </array> | |
| 8742 | </dict> | |
| 8743 | <key>3</key> | |
| 8744 | <dict> | |
| 8745 | <key>patterns</key> | |
| 8746 | <array> | |
| 8747 | <dict> | |
| 8748 | <key>include</key> | |
| 8749 | <string>#markdown-string</string> | |
| 8750 | </dict> | |
| 8751 | </array> | |
| 8752 | </dict> | |
| 8753 | </dict> | |
| 8754 | <key>end</key> | |
| 8755 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8756 | <key>endCaptures</key> | |
| 8757 | <dict> | |
| 8758 | <key>1</key> | |
| 8759 | <dict> | |
| 8760 | <key>name</key> | |
| 8761 | <string>string.other.end.code.fenced.mdx</string> | |
| 8762 | </dict> | |
| 8763 | </dict> | |
| 8764 | <key>name</key> | |
| 8765 | <string>markup.code.toml.mdx</string> | |
| 8766 | <key>patterns</key> | |
| 8767 | <array> | |
| 8768 | <dict> | |
| 8769 | <key>begin</key> | |
| 8770 | <string>(^|\G)(\s*)(.*)</string> | |
| 8771 | <key>while</key> | |
| 8772 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8773 | <key>contentName</key> | |
| 8774 | <string>meta.embedded.toml</string> | |
| 8775 | <key>patterns</key> | |
| 8776 | <array> | |
| 8777 | <dict> | |
| 8778 | <key>include</key> | |
| 8779 | <string>source.toml</string> | |
| 8780 | </dict> | |
| 8781 | </array> | |
| 8782 | </dict> | |
| 8783 | </array> | |
| 8784 | </dict> | |
| 8785 | <dict> | |
| 8786 | <key>begin</key> | |
| 8787 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?toml))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8788 | <key>beginCaptures</key> | |
| 8789 | <dict> | |
| 8790 | <key>1</key> | |
| 8791 | <dict> | |
| 8792 | <key>name</key> | |
| 8793 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8794 | </dict> | |
| 8795 | <key>2</key> | |
| 8796 | <dict> | |
| 8797 | <key>name</key> | |
| 8798 | <string>entity.name.function.mdx</string> | |
| 8799 | <key>patterns</key> | |
| 8800 | <array> | |
| 8801 | <dict> | |
| 8802 | <key>include</key> | |
| 8803 | <string>#markdown-string</string> | |
| 8804 | </dict> | |
| 8805 | </array> | |
| 8806 | </dict> | |
| 8807 | <key>3</key> | |
| 8808 | <dict> | |
| 8809 | <key>patterns</key> | |
| 8810 | <array> | |
| 8811 | <dict> | |
| 8812 | <key>include</key> | |
| 8813 | <string>#markdown-string</string> | |
| 8814 | </dict> | |
| 8815 | </array> | |
| 8816 | </dict> | |
| 8817 | </dict> | |
| 8818 | <key>end</key> | |
| 8819 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8820 | <key>endCaptures</key> | |
| 8821 | <dict> | |
| 8822 | <key>1</key> | |
| 8823 | <dict> | |
| 8824 | <key>name</key> | |
| 8825 | <string>string.other.end.code.fenced.mdx</string> | |
| 8826 | </dict> | |
| 8827 | </dict> | |
| 8828 | <key>name</key> | |
| 8829 | <string>markup.code.toml.mdx</string> | |
| 8830 | <key>patterns</key> | |
| 8831 | <array> | |
| 8832 | <dict> | |
| 8833 | <key>begin</key> | |
| 8834 | <string>(^|\G)(\s*)(.*)</string> | |
| 8835 | <key>while</key> | |
| 8836 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8837 | <key>contentName</key> | |
| 8838 | <string>meta.embedded.toml</string> | |
| 8839 | <key>patterns</key> | |
| 8840 | <array> | |
| 8841 | <dict> | |
| 8842 | <key>include</key> | |
| 8843 | <string>source.toml</string> | |
| 8844 | </dict> | |
| 8845 | </array> | |
| 8846 | </dict> | |
| 8847 | </array> | |
| 8848 | </dict> | |
| 8849 | </array> | |
| 8850 | </dict> | |
| 8851 | <key>commonmark-code-fenced-ts</key> | |
| 8852 | <dict> | |
| 8853 | <key>patterns</key> | |
| 8854 | <array> | |
| 8855 | <dict> | |
| 8856 | <key>begin</key> | |
| 8857 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:typescript|(?:.*\.)?(?:cts|mts|ts)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8858 | <key>beginCaptures</key> | |
| 8859 | <dict> | |
| 8860 | <key>1</key> | |
| 8861 | <dict> | |
| 8862 | <key>name</key> | |
| 8863 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8864 | </dict> | |
| 8865 | <key>2</key> | |
| 8866 | <dict> | |
| 8867 | <key>name</key> | |
| 8868 | <string>entity.name.function.mdx</string> | |
| 8869 | <key>patterns</key> | |
| 8870 | <array> | |
| 8871 | <dict> | |
| 8872 | <key>include</key> | |
| 8873 | <string>#markdown-string</string> | |
| 8874 | </dict> | |
| 8875 | </array> | |
| 8876 | </dict> | |
| 8877 | <key>3</key> | |
| 8878 | <dict> | |
| 8879 | <key>patterns</key> | |
| 8880 | <array> | |
| 8881 | <dict> | |
| 8882 | <key>include</key> | |
| 8883 | <string>#markdown-string</string> | |
| 8884 | </dict> | |
| 8885 | </array> | |
| 8886 | </dict> | |
| 8887 | </dict> | |
| 8888 | <key>end</key> | |
| 8889 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8890 | <key>endCaptures</key> | |
| 8891 | <dict> | |
| 8892 | <key>1</key> | |
| 8893 | <dict> | |
| 8894 | <key>name</key> | |
| 8895 | <string>string.other.end.code.fenced.mdx</string> | |
| 8896 | </dict> | |
| 8897 | </dict> | |
| 8898 | <key>name</key> | |
| 8899 | <string>markup.code.ts.mdx</string> | |
| 8900 | <key>patterns</key> | |
| 8901 | <array> | |
| 8902 | <dict> | |
| 8903 | <key>begin</key> | |
| 8904 | <string>(^|\G)(\s*)(.*)</string> | |
| 8905 | <key>while</key> | |
| 8906 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8907 | <key>contentName</key> | |
| 8908 | <string>meta.embedded.ts</string> | |
| 8909 | <key>patterns</key> | |
| 8910 | <array> | |
| 8911 | <dict> | |
| 8912 | <key>include</key> | |
| 8913 | <string>source.ts</string> | |
| 8914 | </dict> | |
| 8915 | </array> | |
| 8916 | </dict> | |
| 8917 | </array> | |
| 8918 | </dict> | |
| 8919 | <dict> | |
| 8920 | <key>begin</key> | |
| 8921 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:typescript|(?:.*\.)?(?:cts|mts|ts)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 8922 | <key>beginCaptures</key> | |
| 8923 | <dict> | |
| 8924 | <key>1</key> | |
| 8925 | <dict> | |
| 8926 | <key>name</key> | |
| 8927 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8928 | </dict> | |
| 8929 | <key>2</key> | |
| 8930 | <dict> | |
| 8931 | <key>name</key> | |
| 8932 | <string>entity.name.function.mdx</string> | |
| 8933 | <key>patterns</key> | |
| 8934 | <array> | |
| 8935 | <dict> | |
| 8936 | <key>include</key> | |
| 8937 | <string>#markdown-string</string> | |
| 8938 | </dict> | |
| 8939 | </array> | |
| 8940 | </dict> | |
| 8941 | <key>3</key> | |
| 8942 | <dict> | |
| 8943 | <key>patterns</key> | |
| 8944 | <array> | |
| 8945 | <dict> | |
| 8946 | <key>include</key> | |
| 8947 | <string>#markdown-string</string> | |
| 8948 | </dict> | |
| 8949 | </array> | |
| 8950 | </dict> | |
| 8951 | </dict> | |
| 8952 | <key>end</key> | |
| 8953 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 8954 | <key>endCaptures</key> | |
| 8955 | <dict> | |
| 8956 | <key>1</key> | |
| 8957 | <dict> | |
| 8958 | <key>name</key> | |
| 8959 | <string>string.other.end.code.fenced.mdx</string> | |
| 8960 | </dict> | |
| 8961 | </dict> | |
| 8962 | <key>name</key> | |
| 8963 | <string>markup.code.ts.mdx</string> | |
| 8964 | <key>patterns</key> | |
| 8965 | <array> | |
| 8966 | <dict> | |
| 8967 | <key>begin</key> | |
| 8968 | <string>(^|\G)(\s*)(.*)</string> | |
| 8969 | <key>while</key> | |
| 8970 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 8971 | <key>contentName</key> | |
| 8972 | <string>meta.embedded.ts</string> | |
| 8973 | <key>patterns</key> | |
| 8974 | <array> | |
| 8975 | <dict> | |
| 8976 | <key>include</key> | |
| 8977 | <string>source.ts</string> | |
| 8978 | </dict> | |
| 8979 | </array> | |
| 8980 | </dict> | |
| 8981 | </array> | |
| 8982 | </dict> | |
| 8983 | </array> | |
| 8984 | </dict> | |
| 8985 | <key>commonmark-code-fenced-tsx</key> | |
| 8986 | <dict> | |
| 8987 | <key>patterns</key> | |
| 8988 | <array> | |
| 8989 | <dict> | |
| 8990 | <key>begin</key> | |
| 8991 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?tsx))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 8992 | <key>beginCaptures</key> | |
| 8993 | <dict> | |
| 8994 | <key>1</key> | |
| 8995 | <dict> | |
| 8996 | <key>name</key> | |
| 8997 | <string>string.other.begin.code.fenced.mdx</string> | |
| 8998 | </dict> | |
| 8999 | <key>2</key> | |
| 9000 | <dict> | |
| 9001 | <key>name</key> | |
| 9002 | <string>entity.name.function.mdx</string> | |
| 9003 | <key>patterns</key> | |
| 9004 | <array> | |
| 9005 | <dict> | |
| 9006 | <key>include</key> | |
| 9007 | <string>#markdown-string</string> | |
| 9008 | </dict> | |
| 9009 | </array> | |
| 9010 | </dict> | |
| 9011 | <key>3</key> | |
| 9012 | <dict> | |
| 9013 | <key>patterns</key> | |
| 9014 | <array> | |
| 9015 | <dict> | |
| 9016 | <key>include</key> | |
| 9017 | <string>#markdown-string</string> | |
| 9018 | </dict> | |
| 9019 | </array> | |
| 9020 | </dict> | |
| 9021 | </dict> | |
| 9022 | <key>end</key> | |
| 9023 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9024 | <key>endCaptures</key> | |
| 9025 | <dict> | |
| 9026 | <key>1</key> | |
| 9027 | <dict> | |
| 9028 | <key>name</key> | |
| 9029 | <string>string.other.end.code.fenced.mdx</string> | |
| 9030 | </dict> | |
| 9031 | </dict> | |
| 9032 | <key>name</key> | |
| 9033 | <string>markup.code.tsx.mdx</string> | |
| 9034 | <key>patterns</key> | |
| 9035 | <array> | |
| 9036 | <dict> | |
| 9037 | <key>begin</key> | |
| 9038 | <string>(^|\G)(\s*)(.*)</string> | |
| 9039 | <key>while</key> | |
| 9040 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9041 | <key>contentName</key> | |
| 9042 | <string>meta.embedded.tsx</string> | |
| 9043 | <key>patterns</key> | |
| 9044 | <array> | |
| 9045 | <dict> | |
| 9046 | <key>include</key> | |
| 9047 | <string>source.tsx</string> | |
| 9048 | </dict> | |
| 9049 | </array> | |
| 9050 | </dict> | |
| 9051 | </array> | |
| 9052 | </dict> | |
| 9053 | <dict> | |
| 9054 | <key>begin</key> | |
| 9055 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?tsx))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 9056 | <key>beginCaptures</key> | |
| 9057 | <dict> | |
| 9058 | <key>1</key> | |
| 9059 | <dict> | |
| 9060 | <key>name</key> | |
| 9061 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9062 | </dict> | |
| 9063 | <key>2</key> | |
| 9064 | <dict> | |
| 9065 | <key>name</key> | |
| 9066 | <string>entity.name.function.mdx</string> | |
| 9067 | <key>patterns</key> | |
| 9068 | <array> | |
| 9069 | <dict> | |
| 9070 | <key>include</key> | |
| 9071 | <string>#markdown-string</string> | |
| 9072 | </dict> | |
| 9073 | </array> | |
| 9074 | </dict> | |
| 9075 | <key>3</key> | |
| 9076 | <dict> | |
| 9077 | <key>patterns</key> | |
| 9078 | <array> | |
| 9079 | <dict> | |
| 9080 | <key>include</key> | |
| 9081 | <string>#markdown-string</string> | |
| 9082 | </dict> | |
| 9083 | </array> | |
| 9084 | </dict> | |
| 9085 | </dict> | |
| 9086 | <key>end</key> | |
| 9087 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9088 | <key>endCaptures</key> | |
| 9089 | <dict> | |
| 9090 | <key>1</key> | |
| 9091 | <dict> | |
| 9092 | <key>name</key> | |
| 9093 | <string>string.other.end.code.fenced.mdx</string> | |
| 9094 | </dict> | |
| 9095 | </dict> | |
| 9096 | <key>name</key> | |
| 9097 | <string>markup.code.tsx.mdx</string> | |
| 9098 | <key>patterns</key> | |
| 9099 | <array> | |
| 9100 | <dict> | |
| 9101 | <key>begin</key> | |
| 9102 | <string>(^|\G)(\s*)(.*)</string> | |
| 9103 | <key>while</key> | |
| 9104 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9105 | <key>contentName</key> | |
| 9106 | <string>meta.embedded.tsx</string> | |
| 9107 | <key>patterns</key> | |
| 9108 | <array> | |
| 9109 | <dict> | |
| 9110 | <key>include</key> | |
| 9111 | <string>source.tsx</string> | |
| 9112 | </dict> | |
| 9113 | </array> | |
| 9114 | </dict> | |
| 9115 | </array> | |
| 9116 | </dict> | |
| 9117 | </array> | |
| 9118 | </dict> | |
| 9119 | <key>commonmark-code-fenced-vbnet</key> | |
| 9120 | <dict> | |
| 9121 | <key>patterns</key> | |
| 9122 | <array> | |
| 9123 | <dict> | |
| 9124 | <key>begin</key> | |
| 9125 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:fb|freebasic|realbasic|vb\x2d\.net|vb\.net|vbnet|vbscript|visual\x2dbasic|visual\x2dbasic\x2d\.net|(?:.*\.)?(?:bi|rbbas|rbfrm|rbmnu|rbres|rbtbar|rbuistate|vb|vbhtml|vbs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 9126 | <key>beginCaptures</key> | |
| 9127 | <dict> | |
| 9128 | <key>1</key> | |
| 9129 | <dict> | |
| 9130 | <key>name</key> | |
| 9131 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9132 | </dict> | |
| 9133 | <key>2</key> | |
| 9134 | <dict> | |
| 9135 | <key>name</key> | |
| 9136 | <string>entity.name.function.mdx</string> | |
| 9137 | <key>patterns</key> | |
| 9138 | <array> | |
| 9139 | <dict> | |
| 9140 | <key>include</key> | |
| 9141 | <string>#markdown-string</string> | |
| 9142 | </dict> | |
| 9143 | </array> | |
| 9144 | </dict> | |
| 9145 | <key>3</key> | |
| 9146 | <dict> | |
| 9147 | <key>patterns</key> | |
| 9148 | <array> | |
| 9149 | <dict> | |
| 9150 | <key>include</key> | |
| 9151 | <string>#markdown-string</string> | |
| 9152 | </dict> | |
| 9153 | </array> | |
| 9154 | </dict> | |
| 9155 | </dict> | |
| 9156 | <key>end</key> | |
| 9157 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9158 | <key>endCaptures</key> | |
| 9159 | <dict> | |
| 9160 | <key>1</key> | |
| 9161 | <dict> | |
| 9162 | <key>name</key> | |
| 9163 | <string>string.other.end.code.fenced.mdx</string> | |
| 9164 | </dict> | |
| 9165 | </dict> | |
| 9166 | <key>name</key> | |
| 9167 | <string>markup.code.vbnet.mdx</string> | |
| 9168 | <key>patterns</key> | |
| 9169 | <array> | |
| 9170 | <dict> | |
| 9171 | <key>begin</key> | |
| 9172 | <string>(^|\G)(\s*)(.*)</string> | |
| 9173 | <key>while</key> | |
| 9174 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9175 | <key>contentName</key> | |
| 9176 | <string>meta.embedded.vbnet</string> | |
| 9177 | <key>patterns</key> | |
| 9178 | <array> | |
| 9179 | <dict> | |
| 9180 | <key>include</key> | |
| 9181 | <string>source.vbnet</string> | |
| 9182 | </dict> | |
| 9183 | </array> | |
| 9184 | </dict> | |
| 9185 | </array> | |
| 9186 | </dict> | |
| 9187 | <dict> | |
| 9188 | <key>begin</key> | |
| 9189 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:fb|freebasic|realbasic|vb\x2d\.net|vb\.net|vbnet|vbscript|visual\x2dbasic|visual\x2dbasic\x2d\.net|(?:.*\.)?(?:bi|rbbas|rbfrm|rbmnu|rbres|rbtbar|rbuistate|vb|vbhtml|vbs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 9190 | <key>beginCaptures</key> | |
| 9191 | <dict> | |
| 9192 | <key>1</key> | |
| 9193 | <dict> | |
| 9194 | <key>name</key> | |
| 9195 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9196 | </dict> | |
| 9197 | <key>2</key> | |
| 9198 | <dict> | |
| 9199 | <key>name</key> | |
| 9200 | <string>entity.name.function.mdx</string> | |
| 9201 | <key>patterns</key> | |
| 9202 | <array> | |
| 9203 | <dict> | |
| 9204 | <key>include</key> | |
| 9205 | <string>#markdown-string</string> | |
| 9206 | </dict> | |
| 9207 | </array> | |
| 9208 | </dict> | |
| 9209 | <key>3</key> | |
| 9210 | <dict> | |
| 9211 | <key>patterns</key> | |
| 9212 | <array> | |
| 9213 | <dict> | |
| 9214 | <key>include</key> | |
| 9215 | <string>#markdown-string</string> | |
| 9216 | </dict> | |
| 9217 | </array> | |
| 9218 | </dict> | |
| 9219 | </dict> | |
| 9220 | <key>end</key> | |
| 9221 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9222 | <key>endCaptures</key> | |
| 9223 | <dict> | |
| 9224 | <key>1</key> | |
| 9225 | <dict> | |
| 9226 | <key>name</key> | |
| 9227 | <string>string.other.end.code.fenced.mdx</string> | |
| 9228 | </dict> | |
| 9229 | </dict> | |
| 9230 | <key>name</key> | |
| 9231 | <string>markup.code.vbnet.mdx</string> | |
| 9232 | <key>patterns</key> | |
| 9233 | <array> | |
| 9234 | <dict> | |
| 9235 | <key>begin</key> | |
| 9236 | <string>(^|\G)(\s*)(.*)</string> | |
| 9237 | <key>while</key> | |
| 9238 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9239 | <key>contentName</key> | |
| 9240 | <string>meta.embedded.vbnet</string> | |
| 9241 | <key>patterns</key> | |
| 9242 | <array> | |
| 9243 | <dict> | |
| 9244 | <key>include</key> | |
| 9245 | <string>source.vbnet</string> | |
| 9246 | </dict> | |
| 9247 | </array> | |
| 9248 | </dict> | |
| 9249 | </array> | |
| 9250 | </dict> | |
| 9251 | </array> | |
| 9252 | </dict> | |
| 9253 | <key>commonmark-code-fenced-xml</key> | |
| 9254 | <dict> | |
| 9255 | <key>patterns</key> | |
| 9256 | <array> | |
| 9257 | <dict> | |
| 9258 | <key>begin</key> | |
| 9259 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:collada|eagle|labview|web\x2dontology\x2dlanguage|xpages|(?:.*\.)?(?:adml|admx|ant|axaml|axml|brd|builds|ccproj|ccxml|clixml|cproject|cscfg|csdef|csproj|ct|dae|depproj|dita|ditamap|ditaval|dll\.config|dotsettings|filters|fsproj|fxml|glade|gmx|grxml|hzp|iml|ivy|jelly|jsproj|kml|launch|lvclass|lvlib|lvproj|mdpolicy|mjml|mxml|natvis|ndproj|nproj|nuspec|odd|osm|owl|pkgproj|proj|props|ps1xml|psc1|pt|qhelp|rdf|resx|rss|sch|sch|scxml|sfproj|shproj|srdf|storyboard|sublime\x2dsnippet|targets|tml|ui|urdf|ux|vbproj|vcxproj|vsixmanifest|vssettings|vstemplate|vxml|wixproj|wsdl|wsf|wxi|wxl|wxs|x3d|xacro|xaml|xib|xlf|xliff|xmi|xml|xml\.dist|xmp|xpl|xproc|xproj|xsd|xsp\x2dconfig|xsp\.metadata|xspec|xul|zcml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 9260 | <key>beginCaptures</key> | |
| 9261 | <dict> | |
| 9262 | <key>1</key> | |
| 9263 | <dict> | |
| 9264 | <key>name</key> | |
| 9265 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9266 | </dict> | |
| 9267 | <key>2</key> | |
| 9268 | <dict> | |
| 9269 | <key>name</key> | |
| 9270 | <string>entity.name.function.mdx</string> | |
| 9271 | <key>patterns</key> | |
| 9272 | <array> | |
| 9273 | <dict> | |
| 9274 | <key>include</key> | |
| 9275 | <string>#markdown-string</string> | |
| 9276 | </dict> | |
| 9277 | </array> | |
| 9278 | </dict> | |
| 9279 | <key>3</key> | |
| 9280 | <dict> | |
| 9281 | <key>patterns</key> | |
| 9282 | <array> | |
| 9283 | <dict> | |
| 9284 | <key>include</key> | |
| 9285 | <string>#markdown-string</string> | |
| 9286 | </dict> | |
| 9287 | </array> | |
| 9288 | </dict> | |
| 9289 | </dict> | |
| 9290 | <key>end</key> | |
| 9291 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9292 | <key>endCaptures</key> | |
| 9293 | <dict> | |
| 9294 | <key>1</key> | |
| 9295 | <dict> | |
| 9296 | <key>name</key> | |
| 9297 | <string>string.other.end.code.fenced.mdx</string> | |
| 9298 | </dict> | |
| 9299 | </dict> | |
| 9300 | <key>name</key> | |
| 9301 | <string>markup.code.xml.mdx</string> | |
| 9302 | <key>patterns</key> | |
| 9303 | <array> | |
| 9304 | <dict> | |
| 9305 | <key>begin</key> | |
| 9306 | <string>(^|\G)(\s*)(.*)</string> | |
| 9307 | <key>while</key> | |
| 9308 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9309 | <key>contentName</key> | |
| 9310 | <string>meta.embedded.xml</string> | |
| 9311 | <key>patterns</key> | |
| 9312 | <array> | |
| 9313 | <dict> | |
| 9314 | <key>include</key> | |
| 9315 | <string>text.xml</string> | |
| 9316 | </dict> | |
| 9317 | </array> | |
| 9318 | </dict> | |
| 9319 | </array> | |
| 9320 | </dict> | |
| 9321 | <dict> | |
| 9322 | <key>begin</key> | |
| 9323 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:collada|eagle|labview|web\x2dontology\x2dlanguage|xpages|(?:.*\.)?(?:adml|admx|ant|axaml|axml|brd|builds|ccproj|ccxml|clixml|cproject|cscfg|csdef|csproj|ct|dae|depproj|dita|ditamap|ditaval|dll\.config|dotsettings|filters|fsproj|fxml|glade|gmx|grxml|hzp|iml|ivy|jelly|jsproj|kml|launch|lvclass|lvlib|lvproj|mdpolicy|mjml|mxml|natvis|ndproj|nproj|nuspec|odd|osm|owl|pkgproj|proj|props|ps1xml|psc1|pt|qhelp|rdf|resx|rss|sch|sch|scxml|sfproj|shproj|srdf|storyboard|sublime\x2dsnippet|targets|tml|ui|urdf|ux|vbproj|vcxproj|vsixmanifest|vssettings|vstemplate|vxml|wixproj|wsdl|wsf|wxi|wxl|wxs|x3d|xacro|xaml|xib|xlf|xliff|xmi|xml|xml\.dist|xmp|xpl|xproc|xproj|xsd|xsp\x2dconfig|xsp\.metadata|xspec|xul|zcml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 9324 | <key>beginCaptures</key> | |
| 9325 | <dict> | |
| 9326 | <key>1</key> | |
| 9327 | <dict> | |
| 9328 | <key>name</key> | |
| 9329 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9330 | </dict> | |
| 9331 | <key>2</key> | |
| 9332 | <dict> | |
| 9333 | <key>name</key> | |
| 9334 | <string>entity.name.function.mdx</string> | |
| 9335 | <key>patterns</key> | |
| 9336 | <array> | |
| 9337 | <dict> | |
| 9338 | <key>include</key> | |
| 9339 | <string>#markdown-string</string> | |
| 9340 | </dict> | |
| 9341 | </array> | |
| 9342 | </dict> | |
| 9343 | <key>3</key> | |
| 9344 | <dict> | |
| 9345 | <key>patterns</key> | |
| 9346 | <array> | |
| 9347 | <dict> | |
| 9348 | <key>include</key> | |
| 9349 | <string>#markdown-string</string> | |
| 9350 | </dict> | |
| 9351 | </array> | |
| 9352 | </dict> | |
| 9353 | </dict> | |
| 9354 | <key>end</key> | |
| 9355 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9356 | <key>endCaptures</key> | |
| 9357 | <dict> | |
| 9358 | <key>1</key> | |
| 9359 | <dict> | |
| 9360 | <key>name</key> | |
| 9361 | <string>string.other.end.code.fenced.mdx</string> | |
| 9362 | </dict> | |
| 9363 | </dict> | |
| 9364 | <key>name</key> | |
| 9365 | <string>markup.code.xml.mdx</string> | |
| 9366 | <key>patterns</key> | |
| 9367 | <array> | |
| 9368 | <dict> | |
| 9369 | <key>begin</key> | |
| 9370 | <string>(^|\G)(\s*)(.*)</string> | |
| 9371 | <key>while</key> | |
| 9372 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9373 | <key>contentName</key> | |
| 9374 | <string>meta.embedded.xml</string> | |
| 9375 | <key>patterns</key> | |
| 9376 | <array> | |
| 9377 | <dict> | |
| 9378 | <key>include</key> | |
| 9379 | <string>text.xml</string> | |
| 9380 | </dict> | |
| 9381 | </array> | |
| 9382 | </dict> | |
| 9383 | </array> | |
| 9384 | </dict> | |
| 9385 | </array> | |
| 9386 | </dict> | |
| 9387 | <key>commonmark-code-fenced-yaml</key> | |
| 9388 | <dict> | |
| 9389 | <key>patterns</key> | |
| 9390 | <array> | |
| 9391 | <dict> | |
| 9392 | <key>begin</key> | |
| 9393 | <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:jar\x2dmanifest|kaitai\x2dstruct|oasv2\x2dyaml|oasv3\x2dyaml|unity3d\x2dasset|yaml|yml|(?:.*\.)?(?:anim|asset|ksy|lkml|lookml|mat|meta|mir|prefab|raml|reek|rviz|sublime\x2dsyntax|syntax|unity|yaml\x2dtmlanguage|yaml\.sed|yml\.mysql)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string> | |
| 9394 | <key>beginCaptures</key> | |
| 9395 | <dict> | |
| 9396 | <key>1</key> | |
| 9397 | <dict> | |
| 9398 | <key>name</key> | |
| 9399 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9400 | </dict> | |
| 9401 | <key>2</key> | |
| 9402 | <dict> | |
| 9403 | <key>name</key> | |
| 9404 | <string>entity.name.function.mdx</string> | |
| 9405 | <key>patterns</key> | |
| 9406 | <array> | |
| 9407 | <dict> | |
| 9408 | <key>include</key> | |
| 9409 | <string>#markdown-string</string> | |
| 9410 | </dict> | |
| 9411 | </array> | |
| 9412 | </dict> | |
| 9413 | <key>3</key> | |
| 9414 | <dict> | |
| 9415 | <key>patterns</key> | |
| 9416 | <array> | |
| 9417 | <dict> | |
| 9418 | <key>include</key> | |
| 9419 | <string>#markdown-string</string> | |
| 9420 | </dict> | |
| 9421 | </array> | |
| 9422 | </dict> | |
| 9423 | </dict> | |
| 9424 | <key>end</key> | |
| 9425 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9426 | <key>endCaptures</key> | |
| 9427 | <dict> | |
| 9428 | <key>1</key> | |
| 9429 | <dict> | |
| 9430 | <key>name</key> | |
| 9431 | <string>string.other.end.code.fenced.mdx</string> | |
| 9432 | </dict> | |
| 9433 | </dict> | |
| 9434 | <key>name</key> | |
| 9435 | <string>markup.code.yaml.mdx</string> | |
| 9436 | <key>patterns</key> | |
| 9437 | <array> | |
| 9438 | <dict> | |
| 9439 | <key>begin</key> | |
| 9440 | <string>(^|\G)(\s*)(.*)</string> | |
| 9441 | <key>while</key> | |
| 9442 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9443 | <key>contentName</key> | |
| 9444 | <string>meta.embedded.yaml</string> | |
| 9445 | <key>patterns</key> | |
| 9446 | <array> | |
| 9447 | <dict> | |
| 9448 | <key>include</key> | |
| 9449 | <string>source.yaml</string> | |
| 9450 | </dict> | |
| 9451 | </array> | |
| 9452 | </dict> | |
| 9453 | </array> | |
| 9454 | </dict> | |
| 9455 | <dict> | |
| 9456 | <key>begin</key> | |
| 9457 | <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:jar\x2dmanifest|kaitai\x2dstruct|oasv2\x2dyaml|oasv3\x2dyaml|unity3d\x2dasset|yaml|yml|(?:.*\.)?(?:anim|asset|ksy|lkml|lookml|mat|meta|mir|prefab|raml|reek|rviz|sublime\x2dsyntax|syntax|unity|yaml\x2dtmlanguage|yaml\.sed|yml\.mysql)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string> | |
| 9458 | <key>beginCaptures</key> | |
| 9459 | <dict> | |
| 9460 | <key>1</key> | |
| 9461 | <dict> | |
| 9462 | <key>name</key> | |
| 9463 | <string>string.other.begin.code.fenced.mdx</string> | |
| 9464 | </dict> | |
| 9465 | <key>2</key> | |
| 9466 | <dict> | |
| 9467 | <key>name</key> | |
| 9468 | <string>entity.name.function.mdx</string> | |
| 9469 | <key>patterns</key> | |
| 9470 | <array> | |
| 9471 | <dict> | |
| 9472 | <key>include</key> | |
| 9473 | <string>#markdown-string</string> | |
| 9474 | </dict> | |
| 9475 | </array> | |
| 9476 | </dict> | |
| 9477 | <key>3</key> | |
| 9478 | <dict> | |
| 9479 | <key>patterns</key> | |
| 9480 | <array> | |
| 9481 | <dict> | |
| 9482 | <key>include</key> | |
| 9483 | <string>#markdown-string</string> | |
| 9484 | </dict> | |
| 9485 | </array> | |
| 9486 | </dict> | |
| 9487 | </dict> | |
| 9488 | <key>end</key> | |
| 9489 | <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string> | |
| 9490 | <key>endCaptures</key> | |
| 9491 | <dict> | |
| 9492 | <key>1</key> | |
| 9493 | <dict> | |
| 9494 | <key>name</key> | |
| 9495 | <string>string.other.end.code.fenced.mdx</string> | |
| 9496 | </dict> | |
| 9497 | </dict> | |
| 9498 | <key>name</key> | |
| 9499 | <string>markup.code.yaml.mdx</string> | |
| 9500 | <key>patterns</key> | |
| 9501 | <array> | |
| 9502 | <dict> | |
| 9503 | <key>begin</key> | |
| 9504 | <string>(^|\G)(\s*)(.*)</string> | |
| 9505 | <key>while</key> | |
| 9506 | <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string> | |
| 9507 | <key>contentName</key> | |
| 9508 | <string>meta.embedded.yaml</string> | |
| 9509 | <key>patterns</key> | |
| 9510 | <array> | |
| 9511 | <dict> | |
| 9512 | <key>include</key> | |
| 9513 | <string>source.yaml</string> | |
| 9514 | </dict> | |
| 9515 | </array> | |
| 9516 | </dict> | |
| 9517 | </array> | |
| 9518 | </dict> | |
| 9519 | </array> | |
| 9520 | </dict> | |
| 9521 | </dict> | |
| 9522 | <key>scopeName</key> | |
| 9523 | <string>source.mdx</string> | |
| 9524 | <key>uuid</key> | |
| 9525 | <string>fe65e2cd-7c73-4a27-8b5e-5902893626aa</string> | |
| 9526 | </dict> | |
| 9527 | </plist> | |
| \ No newline at end of file |
src/pages/admin/index.mdo created+14| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | --- | |
| 2 | meta: | |
| 3 | title: paper clover admin | |
| 4 | --- | |
| 5 | <main> | |
| 6 | ||
| 7 | # hi chloe | |
| 8 | ||
| 9 | remember that you're loved. | |
| 10 | ||
| 11 | - [question admin view](/admin/q+a) | |
| 12 | - [question inbox](/admin/q+a/inbox) | |
| 13 | ||
| 14 | </main> |
src/pages/admin/index.mdx deleted-9| ... | ... | @@ -1,9 +0,0 @@ |
| 1 | export default ({children}) => <main>{children}</main> | |
| 2 | export const meta = { title: 'paper clover admin' }; | |
| 3 | ||
| 4 | # hi chloe | |
| 5 | ||
| 6 | remember that you're loved. | |
| 7 | ||
| 8 | - [question admin view](/admin/q+a) | |
| 9 | - [question inbox](/admin/q+a/inbox) |
src/pages/license.mdo created+33| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | --- | |
| 2 | meta: | |
| 3 | title: paper clover's media license | |
| 4 | theme: | |
| 5 | fg: "#ffe" | |
| 6 | bg: "#434" | |
| 7 | h1: "#f5f" | |
| 8 | primary: "#f5f" | |
| 9 | --- | |
| 10 | <main> | |
| 11 | ||
| 12 | # paper clover's media license | |
| 13 | ||
| 14 | copyright 2018-${new Date().getFullYear()} paper clover | |
| 15 | ||
| 16 | all rights reserved | |
| 17 | ||
| 18 | <br/> | |
| 19 | ||
| 20 | this license applies to all "media files" on paperclover.net, which specifically | |
| 21 | refers to downloadable files available within the [paperclover.net/file](/file) directory. | |
| 22 | ||
| 23 | permission is given to download, view, or otherwise store a copy of any media | |
| 24 | file. however, media files must not be uploaded, published, or otherwise | |
| 25 | distributed over the internet or other electronical communication devices. | |
| 26 | all uses of media files must be used for non-commercial purposes. | |
| 27 | ||
| 28 | paperclover.net provides media files as-is, without any warranty or liability. | |
| 29 | ||
| 30 | the above rules are in place to preserve my sanity.<br/> | |
| 31 | thank you for respecting them. | |
| 32 | ||
| 33 | </main> |
src/pages/license.mdx deleted-31| ... | ... | @@ -1,31 +0,0 @@ |
| 1 | export const CurrentYear = () => new Date().getFullYear(); | |
| 2 | export default ({ content }) => <main>{content}</main>; | |
| 3 | export const theme = { | |
| 4 | fg: '#ffe', | |
| 5 | bg: '#434', | |
| 6 | h1: '#f5f', | |
| 7 | }; | |
| 8 | export const meta = { | |
| 9 | title: "paper clover's media license" | |
| 10 | }; | |
| 11 | ||
| 12 | # paper clover's media license | |
| 13 | ||
| 14 | copyright 2018-<CurrentYear/> paper clover | |
| 15 | ||
| 16 | all rights reserved | |
| 17 | ||
| 18 | <br/> | |
| 19 | ||
| 20 | this license applies to all "media files" on paperclover.net, which specifically | |
| 21 | refers to downloadable files available within the [paperclover.net/file](/file) directory. | |
| 22 | ||
| 23 | permission is given to download, view, or otherwise store a copy of any media | |
| 24 | file. however, media files must not be uploaded, published, or otherwise | |
| 25 | distributed over the internet or other electronical communication devices. | |
| 26 | all uses of media files must be used for non-commercial purposes. | |
| 27 | ||
| 28 | paperclover.net provides media files as-is, without any warranty or liability. | |
| 29 | ||
| 30 | the above rules are in place to preserve my sanity.<br/> | |
| 31 | thank you for respecting them. |
src/pages/log.mdo created+413| ... | ... | @@ -0,0 +1,413 @@ |
| 1 | --- | |
| 2 | meta: | |
| 3 | title: clover's log | |
| 4 | --- | |
| 5 | import './log.css'; | |
| 6 | static const repo = "${repo}"; | |
| 7 | ||
| 8 | [go back to the home page](/) | |
| 9 | ||
| 10 | [git]: https://git.paperclover.net/ | |
| 11 | [home infra]: https://git.paperclover.net/clo/home-infra | |
| 12 | [name paint bot]: https://git.paperclover.net/clo/discord-name-painter | |
| 13 | [next.js blog post]: /blog/webdev/one-year-next-app-router | |
| 14 | [progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts | |
| 15 | [this site]: https://paperclover.net/ | |
| 16 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker | |
| 17 | [ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector | |
| 18 | [evil inc]: https://evil.inc/ | |
| 19 | [history of japan reanimated]: /history-of-japan-reanimated | |
| 20 | [react mutation]: /history-of-japan-reanimated | |
| 21 | ||
| 22 | <main> | |
| 23 | ||
| 24 | # clover's log | |
| 25 | ||
| 26 | these are like mini blog posts, but more in generally just what the heck i'm up to. | |
| 27 | ||
| 28 | ## 2026-02-06 | |
| 29 | ||
| 30 | spent yesterday and today cathing up with this page and preparing to get | |
| 31 | writing heavy on the blog. i also fixed a bug on the video player where it | |
| 32 | wouldnt work on latest chrome on non-av1 accelerated hardware. | |
| 33 | ||
| 34 | there were two bugs: | |
| 35 | ||
| 36 | - chrome without av1 and version >=142 would try and play the hls stream | |
| 37 | natively. they made `video.canPlayType("application/x-mpegURL")` return | |
| 38 | "maybe", which is total propaganda because chrome does not actually support | |
| 39 | this type of stream. they support some insane subset that isn't real world | |
| 40 | use cases. | |
| 41 | - hls polyfill was never being used because of a bundling mistake, so those | |
| 42 | users would get the unoptimized original file. | |
| 43 | ||
| 44 | since i was testing windows 7, decided to also fix some IE9 bugs. the page | |
| 45 | works besides the video player and the fonts, which is all things i believe i | |
| 46 | could easily fix, so i opened new issues for those tasks. | |
| 47 | ||
| 48 | ## 2026-02-04 | |
| 49 | ||
| 50 | tags: [this site] | |
| 51 | ||
| 52 | i did a ton of random stuff to [this site], including the RSS feed and random | |
| 53 | bug fixes on the domain. trying to reduce the issue count on my forgejo. | |
| 54 | ||
| 55 | ## 2026-02-03 | |
| 56 | ||
| 57 | tags: [history of japan reanimated] | |
| 58 | ||
| 59 | immediately after the youtube premiere, i worked on my webpage of it. the live | |
| 60 | credits was a really fun touch that really completes things. i'm very happy | |
| 61 | with the final result. | |
| 62 | ||
| 63 | i also had to do a ton of work fixing the video player to properly show | |
| 64 | thumbnails on the video player. the biggest one was adding even more hell to | |
| 65 | the dash-av1 player. a issue that `dash.js` has is providing the first frame of | |
| 66 | media before the user presses "play." this doesn't happen with the hls or | |
| 67 | native players. so my workaround... delay attachment of the view. | |
| 68 | ||
| 69 | ``` | |
| 70 | player.initialize(null, dashFile, false); | |
| 71 | player.updateSettings({ | |
| 72 | streaming: { cacheInitSegments: true }, | |
| 73 | }); | |
| 74 | player.preload(); | |
| 75 | ||
| 76 | video.addEventListener("play", function play() { | |
| 77 | video.removeEventListener("play", play); | |
| 78 | ||
| 79 | enableSafariAirplay(); // <-- another important hack | |
| 80 | player.attachView(video); | |
| 81 | onCloverVideoInit?.(id, video); | |
| 82 | }); | |
| 83 | video.controls = true; | |
| 84 | ``` | |
| 85 | ||
| 86 | but on chrome you get cooked because a video without a source is not playable. | |
| 87 | so a second hack used to attach a fake source. | |
| 88 | ||
| 89 | ``` | |
| 90 | const duration = new Promise<number>((resolve) => { | |
| 91 | player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, (e) => { | |
| 92 | const duration = e.data.mediaPresentationDuration; | |
| 93 | resolve(duration); // seconds | |
| 94 | }); | |
| 95 | }); | |
| 96 | mediaSource = new MediaSource(); | |
| 97 | mediaSource.addEventListener("sourceopen", () => { | |
| 98 | duration.then((duration) => { | |
| 99 | mediaSource = mediaSource!; // typescript assertion | |
| 100 | mediaSource.duration = duration; | |
| 101 | const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp8"'); | |
| 102 | const oneFrame = Uint8Array.from( | |
| 103 | atob("GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAITEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggEjTbuMU6uEHFO7a1OsggH97AEAAAAAAABZ" + "A".repeat(119) + "VSalmsCrXsYMPQkBNgIxMYXZmNjIuMy4xMDBXQYxMYXZmNjIuMy4xMDBEiYhAXgAAAAAAABZUrmvIrgEAAAAAAAA/14EBc8WIDV7YG1KxA/CcgQAitZyDdW5kiIEAhoVWX1ZQOIOBASPjg4QCYloA4JCwgQG6gQGagQJVsIRVuYEBElTDZ/tzc59jwIBnyJlFo4dFTkNPREVSRIeMTGF2ZjYyLjMuMTAwc3PWY8CLY8WIDV7YG1KxA/BnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjExLjEwMCBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjEyMDAwMDAwMAAfQ7Z11eeBAKOigQAAgBACAJ0BKgEAAQALxwiFhYiFhIg/ggAMDWAA/ua1AKOVgQAoALEBAC8R/AAYABhYL/QAJAAAo5WBAFAAsQEALxH8ABgAGFgv9AAkAAAcU7trkbuPs4EAt4r3gQHxggGj8IED"), | |
| 104 | (x) => x.charCodeAt(0), | |
| 105 | ); | |
| 106 | sb.appendBuffer(oneFrame); | |
| 107 | }); | |
| 108 | }); | |
| 109 | objectUrl = URL.createObjectURL(mediaSource); | |
| 110 | ||
| 111 | mainSource = document.createElement("source"); | |
| 112 | mainSource.src = objectUrl; | |
| 113 | video.appendChild(mainSource); | |
| 114 | ``` | |
| 115 | ||
| 116 | the payload within contains a single webm vp8 frame, one pixel by one pixel. | |
| 117 | this is enough to get chrome to shut up. now the experience is nearly perfect, | |
| 118 | with browser support all the way to the ancient firefox version on my old | |
| 119 | laptop, to modern Apple Silicon Macs. | |
| 120 | ||
| 121 | ## 2026-01-31 | |
| 122 | ||
| 123 | tags: [history of japan reanimated] | |
| 124 | ||
| 125 | main section of animation for the project was already done, but i wanted to | |
| 126 | make some needed adjustments to my scene. so i had been doing those for a few | |
| 127 | days, as well as animating an extra 25 seconds for the outro section. it was | |
| 128 | very fun doing that process, since i actually grabbed an old macbook i had in | |
| 129 | storage to take the textedit screenshot. more details on this entire process | |
| 130 | will be in the project page. | |
| 131 | ||
| 132 | ## 2026-01-30 | |
| 133 | ||
| 134 | tags: [react mutation] | |
| 135 | ||
| 136 | at work, one of my coworkers was complaining about my helper functions for | |
| 137 | TanStack Query's mutation system. the conclusion was that the mutation system | |
| 138 | we were building on was flawed and not enjoyable to use. so i spent a few days | |
| 139 | making an alternative library. it's on the JSR: [@clo/react-mutation](https://jsr.io/@clo/react-mutation). | |
| 140 | ||
| 141 | this project was really fun because of how much the API surface changed as i | |
| 142 | started staging the library into our actual code. the experience has shaped how | |
| 143 | i want to go about my upcoming blog post for the better. | |
| 144 | ||
| 145 | ## 2026-01-25 | |
| 146 | ||
| 147 | tags: [ts lie detector], [todo tracker] | |
| 148 | ||
| 149 | added a trivial binary to the lie detector, `tsld-node`, which is like `ts-node` or `tsx` but it runs with the lie detector. | |
| 150 | ||
| 151 | for todo tracker, it's been vibe coded to a point where the sitegen repo gets | |
| 152 | through all commits, but there are still some issues with missing TODOs. i haven't really had time to prioritize this, even with an ai agent writing most of it, since even then i have to review the progress of it, so it's just not worth my time until other projects pull through. | |
| 153 | ||
| 154 | ## 2026-01-24 | |
| 155 | ||
| 156 | tags: [git], [home infra] | |
| 157 | ||
| 158 | i moved forgejo off of sqlite and onto postgres. the only motiviation behind this was to easily backdate the repositories i had imported: [name paint bot] and a scripting language compiler i wrote. i was more comfortable doing this on a real database server than just editing the file. | |
| 159 | ||
| 160 | the migration took a bit for me to figure out, but last year someone named Sven [did the same thing](https://sven-seeberg.de/wp/?p=1213), and found this `pgloader` command with the critical `data only` clause. | |
| 161 | ||
| 162 | ``` | |
| 163 | echo "LOAD DATABASE | |
| 164 | FROM sqlite:///root/forgejo.db | |
| 165 | INTO postgresql://forgejo:$POSTGRES_PASSWORD_FORGEJO@postgres/forgejo | |
| 166 | WITH data only, reset sequences, prefetch rows = 10000 | |
| 167 | SET work_mem TO '16MB', maintenance_work_mem TO '512MB';" > ./pgloader-command | |
| 168 | ||
| 169 | pgloader ./pgloader-command | |
| 170 | ``` | |
| 171 | ||
| 172 | ## 2026-01-18 | |
| 173 | ||
| 174 | tags: [git], [home infra] | |
| 175 | ||
| 176 | i finally setup system integrated ssh push, but with a twist. | |
| 177 | ||
| 178 | the first problem is having two ssh servers on the same machine, one for the | |
| 179 | host, and another for Forgejo. this means that one of them had to live on | |
| 180 | another port, so i chose to move the host. but it kind of just sucks to use | |
| 181 | this. the proper solution is to use a custom config to direct the `git` user to | |
| 182 | the right place. | |
| 183 | ||
| 184 | what made this harder is i actually had two git instances; the second one is | |
| 185 | for a temporary infrastructure i'm running for [evil inc] until the | |
| 186 | organization gets dedicated hardware (more about this in a future post). so | |
| 187 | even if i routed `git` specially, it still wouldnt know which git server to go | |
| 188 | to. | |
| 189 | ||
| 190 | **the solution**: write a custom "router" script that generates an | |
| 191 | `authorized_keys` file based on which git instances actually have a key, then | |
| 192 | the line within the `authorized_keys` forces a special wrapper which intercepts | |
| 193 | the targetted git repository, routing it to the git instance with it. | |
| 194 | ||
| 195 | the sshd config looks like | |
| 196 | ||
| 197 | ``` | |
| 198 | Match User git | |
| 199 | AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k | |
| 200 | AuthorizedKeysCommandUser git | |
| 201 | ``` | |
| 202 | ||
| 203 | it is convenient because `AuthorizedKeysCommand` is run on every connection. it produces a file with zero or one valid keys: | |
| 204 | ||
| 205 | ``` | |
| 206 | command="/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh --clover 1 --evil 2",no-port-forwarding,...,restrict ssh-ed25519 AAAAC3NzaC... | |
| 207 | ``` | |
| 208 | ||
| 209 | and then the route script does this shit: | |
| 210 | ||
| 211 | ``` | |
| 212 | if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then | |
| 213 | [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1 | |
| 214 | exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id" | |
| 215 | elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then | |
| 216 | [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1 | |
| 217 | exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id" | |
| 218 | else | |
| 219 | echo "repo not found" >&2 | |
| 220 | exit 1 | |
| 221 | fi | |
| 222 | ``` | |
| 223 | ||
| 224 | it works beautifully. [see the whole patch for more info](https://git.paperclover.net/clo/home-infra/commit/e402d6ef71dfc310caeeb4d3579bddd9d0710594) | |
| 225 | ||
| 226 | ## 2026-01-14 | |
| 227 | ||
| 228 | tags: [next.js blog post], [this site] | |
| 229 | ||
| 230 | general housekeeping. getting this activity page up. getting the spanish | |
| 231 | translation by my good friend trubiso up. things are looking really cozy. | |
| 232 | ||
| 233 | ## 2026-01-12 | |
| 234 | ||
| 235 | tags: [album] | |
| 236 | ||
| 237 | i started more music work. i've gotten better at lyric writing, phrasing this | |
| 238 | new song as a sort of "adventure". felt for one of the first times that i was | |
| 239 | doing worldbuilding in a song. the imagery is that good. | |
| 240 | ||
| 241 | ## 2026-01-11 | |
| 242 | ||
| 243 | tags: [progress.ts], [todo tracker] | |
| 244 | ||
| 245 | i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git | |
| 246 | commits describe the tech better than me reiterating. | |
| 247 | ||
| 248 | > ## feat(lib/progress): implement streaming wire protocol | |
| 249 | > resolves `#47` | |
| 250 | > | |
| 251 | > `encodeByteStream` converts these events into a `ReadableStream`. by | |
| 252 | > batching events together, the stream contents remain small, that way the | |
| 253 | > code that constructs progress nodes do not have to worry about calling | |
| 254 | > many setters at once, it gets debounced be the serializer. stream | |
| 255 | > backpressure causes larger time-gaps to be batched (smaller). this | |
| 256 | > enables servers to respond with rich progress. | |
| 257 | > | |
| 258 | > ``` | |
| 259 | > const root = new progress.Root(); | |
| 260 | > doActionWithProgress(root).then(root.end, root.error); | |
| 261 | > // streaming clients indicate a header | |
| 262 | > if (req.headers.get("Accept")?.includes(progress.contentType)) | |
| 263 | > return new Response(progress.encodeByteStream(root), { | |
| 264 | > headers: { 'Content-Type': progress.contentType }, | |
| 265 | > }); | |
| 266 | > // to support non-streaming clients | |
| 267 | > return Response.json(await root.asPromise()); | |
| 268 | > ``` | |
| 269 | > | |
| 270 | > and `decodeByteStream` on the client: | |
| 271 | > | |
| 272 | > ``` | |
| 273 | > const output = document.getElementById("output"); | |
| 274 | > const res = await fetch(...); | |
| 275 | > if (!res.ok) throw ...; | |
| 276 | > const root = new progress.Root(); | |
| 277 | > root.on("change", (active) => { | |
| 278 | > output.innerText = ansi.strip(progress.formatAnsi( | |
| 279 | > performance.now(), | |
| 280 | > active, | |
| 281 | > )); | |
| 282 | > }); | |
| 283 | > const result = await progress.decodeByteStream(res.body, root); | |
| 284 | > output.innerText = JSON.stringify(result); | |
| 285 | > ``` | |
| 286 | > | |
| 287 | > there is currently no document bindings, but i plan to. additionally, a | |
| 288 | > React hook is very trivial to implement for this -- but that is | |
| 289 | > unplanned for this repository. for transports that require JSON or | |
| 290 | > UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of | |
| 291 | > JSON objects which can be compressed at the developer's discretion. | |
| 292 | ||
| 293 | > ## feat(lib/progress): headless rendering + time estimation | |
| 294 | > node signaling is done by providing a `progress.Root` to every node, | |
| 295 | > dispatching events to it when the node changes. the root is connected to | |
| 296 | > an observer to construct a UI out of it. there are two apis planned: | |
| 297 | > | |
| 298 | > - `attachToScreen` binds a root to a TTY screen (via the log.Widget API). | |
| 299 | > the primary use of this is to implement the top level `progress.start`. | |
| 300 | > | |
| 301 | > - a serialization system that allows transmitting a `Root` over a wire. | |
| 302 | > this commit was going to include this but it is an unexpectedly large | |
| 303 | > component. | |
| 304 | > | |
| 305 | > - potentially a browser binding like `attachToDocument`. this will not | |
| 306 | > be added in this patch. | |
| 307 | > | |
| 308 | > additionally, resolves #33 by implementing `estimatedTime` | |
| 309 | ||
| 310 | i also did a large part of the work to create a "code todo tracking" tool. i | |
| 311 | would say it's about half done, since the second half is simply fixing all of | |
| 312 | the little bugs there are. most of this code is currently ai-generated, but | |
| 313 | with me manually coming in to write interfaces and the modular program | |
| 314 | architecture. then i synthesize the code and the tests. this was basically just | |
| 315 | going on ambiently while [progress.ts] was in progress. | |
| 316 | ||
| 317 | ## 2026-01-09 | |
| 318 | ||
| 319 | tags: [home infra] | |
| 320 | ||
| 321 | finished SSO sub-project. im happy with the setup i used to protect internal | |
| 322 | services, such as pgadmin and qbittorrent. it's a caddy snippet that i can | |
| 323 | re-use very easily. | |
| 324 | ||
| 325 | ``` | |
| 326 | (reverse_proxy_auth) { | |
| 327 | handle /snow.oauth2/* { | |
| 328 | 		reverse_proxy "http://forward-auth" { | |
| 329 | 			header_up X-Real-IP {remote_host} | |
| 330 | 			header_up X-Forwarded-Uri {uri} | |
| 331 | 		} | |
| 332 | 	} | |
| 333 | handle { | |
| 334 | forward_auth "http://forward-auth" { | |
| 335 | uri /snow.oauth2/auth | |
| 336 | header_up X-Real-IP {remote_host} | |
| 337 | @error status 401 | |
| 338 | handle_response @error { | |
| 339 | redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri} | |
| 340 | } | |
| 341 | @valid_group header X-Auth-Request-Groups *role:{args[1]}* | |
| 342 | handle_response @valid_group { | |
| 343 | method {method} | |
| 344 | rewrite {uri} | |
| 345 | reverse_proxy {args[0]} { | |
| 346 | header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2" | |
| 347 | {block} | |
| 348 | } | |
| 349 | } | |
| 350 | handle_response { | |
| 351 | rewrite /403.html | |
| 352 | file_server { | |
| 353 | status 403 | |
| 354 | root /etc/caddy | |
| 355 | } | |
| 356 | } | |
| 357 | } | |
| 358 | } | |
| 359 | } | |
| 360 | ||
| 361 | # usage | |
| 362 | pg.{$HOME_DOMAIN} { | |
| 363 | import reverse_proxy_auth "http://pgadmin" admin | |
| 364 | } | |
| 365 | qbt.{$HOME_DOMAIN} { | |
| 366 | import reverse_proxy_auth "http://qbittorrent" media-manage | |
| 367 | } | |
| 368 | ``` | |
| 369 | ||
| 370 | ||
| 371 | ## 2026-01-04 | |
| 372 | ||
| 373 | tags: [home infra] | |
| 374 | ||
| 375 | working on SSO for my internal services. for context, i have about 12 | |
| 376 | self-hosted services running, half of which i allow my friends to access. | |
| 377 | currently, this is done through manually creating an account on such service | |
| 378 | (jellyfin, forgejo), but many are done through a caddy rule. in the interest of | |
| 379 | making my password manager less confused (ip vs domain, subdomain etc), i'm | |
| 380 | slowly reducing this setup to a single sign in page. | |
| 381 | ||
| 382 | to do this, i am using https://keycloak.org, which supports openid connect | |
| 383 | (how i will configure forgejo and jellyfin), as well as a separate service to | |
| 384 | provide forward auth proxying (how i protect services like copyparty, | |
| 385 | syncthing, pgadmin, and many more). i tried authelia beforehand, but i really | |
| 386 | do not recommend them due to how hard it is to configure, passkeys being | |
| 387 | annoying to setup, and limited themes. i also dont recommend authentik, but i | |
| 388 | couldnt figure out how to even start using it after i installed it. | |
| 389 | ||
| 390 | keycloak is a bit stupid on config. as all the config lies in the postgres | |
| 391 | database, i can't use a config file to setup the primary realm. so instead, i | |
| 392 | have this huge python script to use the API to upsert the configuration in. | |
| 393 | this works pretty well, and means that for locally running the infrastructure | |
| 394 | for testing, i can get the config to be the same (useful if you brick | |
| 395 | keycloak, which is pretty easy to do). | |
| 396 | ||
| 397 | ## 2026-01-02 | |
| 398 | ||
| 399 | tags: [home infra], [git], [name paint bot] show | |
| 400 | ||
| 401 | i deleted all my github repositories except four: my "readme", a bug | |
| 402 | reproduction repo, the mirror for [ts lie detector], and a shared private repo | |
| 403 | with someone that is load bearing. in this process, i've moved all the projects | |
| 404 | to my [forgejo instance][git]. | |
| 405 | ||
| 406 | with this, [name paint bot], one of my few remaining projects that is still | |
| 407 | active, moves to that forgejo instance using their github migrator. some of my | |
| 408 | private projects, like my pet scripting language, were migrated as well. it | |
| 409 | feels more alive on my site because of the theming and per-repo icons. | |
| 410 | ||
| 411 | after a year of forgejo, i am really happy with how it treats me. | |
| 412 | ||
| 413 | </main> |
src/pages/log.mdx deleted-407| ... | ... | @@ -1,407 +0,0 @@ |
| 1 | import './log.css'; | |
| 2 | export default ({ content }) => <main>{content}</main> | |
| 3 | export const meta = { title: 'clover\'s log' }; | |
| 4 | ||
| 5 | [go back to the home page](/) | |
| 6 | ||
| 7 | [git]: https://git.paperclover.net/ | |
| 8 | [home infra]: https://git.paperclover.net/clo/home-infra | |
| 9 | [name paint bot]: https://git.paperclover.net/clo/discord-name-painter | |
| 10 | [next.js blog post]: /blog/webdev/one-year-next-app-router | |
| 11 | [progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts | |
| 12 | [this site]: https://paperclover.net/ | |
| 13 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker | |
| 14 | [ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector | |
| 15 | [evil inc]: https://evil.inc/ | |
| 16 | [history of japan reanimated]: /history-of-japan-reanimated | |
| 17 | [react mutation]: /history-of-japan-reanimated | |
| 18 | ||
| 19 | # clover's log | |
| 20 | ||
| 21 | these are like mini blog posts, but more in generally just what the heck i'm up to. | |
| 22 | ||
| 23 | ## 2026-02-06 | |
| 24 | ||
| 25 | spent yesterday and today cathing up with this page and preparing to get | |
| 26 | writing heavy on the blog. i also fixed a bug on the video player where it | |
| 27 | wouldnt work on latest chrome on non-av1 accelerated hardware. | |
| 28 | ||
| 29 | there were two bugs: | |
| 30 | ||
| 31 | - chrome without av1 and version >=142 would try and play the hls stream | |
| 32 | natively. they made `video.canPlayType("application/x-mpegURL")` return | |
| 33 | "maybe", which is total propaganda because chrome does not actually support | |
| 34 | this type of stream. they support some insane subset that isn't real world | |
| 35 | use cases. | |
| 36 | - hls polyfill was never being used because of a bundling mistake, so those | |
| 37 | users would get the unoptimized original file. | |
| 38 | ||
| 39 | since i was testing windows 7, decided to also fix some IE9 bugs. the page | |
| 40 | works besides the video player and the fonts, which is all things i believe i | |
| 41 | could easily fix, so i opened new issues for those tasks. | |
| 42 | ||
| 43 | ## 2026-02-04 | |
| 44 | ||
| 45 | tags: [this site] | |
| 46 | ||
| 47 | i did a ton of random stuff to [this site], including the RSS feed and random | |
| 48 | bug fixes on the domain. trying to reduce the issue count on my forgejo. | |
| 49 | ||
| 50 | ## 2026-02-03 | |
| 51 | ||
| 52 | tags: [history of japan reanimated] | |
| 53 | ||
| 54 | immediately after the youtube premiere, i worked on my webpage of it. the live | |
| 55 | credits was a really fun touch that really completes things. i'm very happy | |
| 56 | with the final result. | |
| 57 | ||
| 58 | i also had to do a ton of work fixing the video player to properly show | |
| 59 | thumbnails on the video player. the biggest one was adding even more hell to | |
| 60 | the dash-av1 player. a issue that `dash.js` has is providing the first frame of | |
| 61 | media before the user presses "play." this doesn't happen with the hls or | |
| 62 | native players. so my workaround... delay attachment of the view. | |
| 63 | ||
| 64 | ``` | |
| 65 | player.initialize(null, dashFile, false); | |
| 66 | player.updateSettings({ | |
| 67 | streaming: { cacheInitSegments: true }, | |
| 68 | }); | |
| 69 | player.preload(); | |
| 70 | ||
| 71 | video.addEventListener("play", function play() { | |
| 72 | video.removeEventListener("play", play); | |
| 73 | ||
| 74 | enableSafariAirplay(); // <-- another important hack | |
| 75 | player.attachView(video); | |
| 76 | onCloverVideoInit?.(id, video); | |
| 77 | }); | |
| 78 | video.controls = true; | |
| 79 | ``` | |
| 80 | ||
| 81 | but on chrome you get cooked because a video without a source is not playable. | |
| 82 | so a second hack used to attach a fake source. | |
| 83 | ||
| 84 | ``` | |
| 85 | const duration = new Promise<number>((resolve) => { | |
| 86 | player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, (e) => { | |
| 87 | const duration = e.data.mediaPresentationDuration; | |
| 88 | resolve(duration); // seconds | |
| 89 | }); | |
| 90 | }); | |
| 91 | mediaSource = new MediaSource(); | |
| 92 | mediaSource.addEventListener("sourceopen", () => { | |
| 93 | duration.then((duration) => { | |
| 94 | mediaSource = mediaSource!; // typescript assertion | |
| 95 | mediaSource.duration = duration; | |
| 96 | const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp8"'); | |
| 97 | const oneFrame = Uint8Array.from( | |
| 98 | atob("GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAITEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggEjTbuMU6uEHFO7a1OsggH97AEAAAAAAABZ" + "A".repeat(119) + "VSalmsCrXsYMPQkBNgIxMYXZmNjIuMy4xMDBXQYxMYXZmNjIuMy4xMDBEiYhAXgAAAAAAABZUrmvIrgEAAAAAAAA/14EBc8WIDV7YG1KxA/CcgQAitZyDdW5kiIEAhoVWX1ZQOIOBASPjg4QCYloA4JCwgQG6gQGagQJVsIRVuYEBElTDZ/tzc59jwIBnyJlFo4dFTkNPREVSRIeMTGF2ZjYyLjMuMTAwc3PWY8CLY8WIDV7YG1KxA/BnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjExLjEwMCBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjEyMDAwMDAwMAAfQ7Z11eeBAKOigQAAgBACAJ0BKgEAAQALxwiFhYiFhIg/ggAMDWAA/ua1AKOVgQAoALEBAC8R/AAYABhYL/QAJAAAo5WBAFAAsQEALxH8ABgAGFgv9AAkAAAcU7trkbuPs4EAt4r3gQHxggGj8IED"), | |
| 99 | (x) => x.charCodeAt(0), | |
| 100 | ); | |
| 101 | sb.appendBuffer(oneFrame); | |
| 102 | }); | |
| 103 | }); | |
| 104 | objectUrl = URL.createObjectURL(mediaSource); | |
| 105 | ||
| 106 | mainSource = document.createElement("source"); | |
| 107 | mainSource.src = objectUrl; | |
| 108 | video.appendChild(mainSource); | |
| 109 | ``` | |
| 110 | ||
| 111 | the payload within contains a single webm vp8 frame, one pixel by one pixel. | |
| 112 | this is enough to get chrome to shut up. now the experience is nearly perfect, | |
| 113 | with browser support all the way to the ancient firefox version on my old | |
| 114 | laptop, to modern Apple Silicon Macs. | |
| 115 | ||
| 116 | ## 2026-01-31 | |
| 117 | ||
| 118 | tags: [history of japan reanimated] | |
| 119 | ||
| 120 | main section of animation for the project was already done, but i wanted to | |
| 121 | make some needed adjustments to my scene. so i had been doing those for a few | |
| 122 | days, as well as animating an extra 25 seconds for the outro section. it was | |
| 123 | very fun doing that process, since i actually grabbed an old macbook i had in | |
| 124 | storage to take the textedit screenshot. more details on this entire process | |
| 125 | will be in the project page. | |
| 126 | ||
| 127 | ## 2026-01-30 | |
| 128 | ||
| 129 | tags: [react mutation] | |
| 130 | ||
| 131 | at work, one of my coworkers was complaining about my helper functions for | |
| 132 | TanStack Query's mutation system. the conclusion was that the mutation system | |
| 133 | we were building on was flawed and not enjoyable to use. so i spent a few days | |
| 134 | making an alternative library. it's on the JSR: [@clo/react-mutation](https://jsr.io/@clo/react-mutation). | |
| 135 | ||
| 136 | this project was really fun because of how much the API surface changed as i | |
| 137 | started staging the library into our actual code. the experience has shaped how | |
| 138 | i want to go about my upcoming blog post for the better. | |
| 139 | ||
| 140 | ## 2026-01-25 | |
| 141 | ||
| 142 | tags: [ts lie detector], [todo tracker] | |
| 143 | ||
| 144 | added a trivial binary to the lie detector, `tsld-node`, which is like `ts-node` or `tsx` but it runs with the lie detector. | |
| 145 | ||
| 146 | for todo tracker, it's been vibe coded to a point where the sitegen repo gets | |
| 147 | through all commits, but there are still some issues with missing TODOs. i haven't really had time to prioritize this, even with an ai agent writing most of it, since even then i have to review the progress of it, so it's just not worth my time until other projects pull through. | |
| 148 | ||
| 149 | ## 2026-01-24 | |
| 150 | ||
| 151 | tags: [git], [home infra] | |
| 152 | ||
| 153 | i moved forgejo off of sqlite and onto postgres. the only motiviation behind this was to easily backdate the repositories i had imported: [name paint bot] and a scripting language compiler i wrote. i was more comfortable doing this on a real database server than just editing the file. | |
| 154 | ||
| 155 | the migration took a bit for me to figure out, but last year someone named Sven [did the same thing](https://sven-seeberg.de/wp/?p=1213), and found this `pgloader` command with the critical `data only` clause. | |
| 156 | ||
| 157 | ``` | |
| 158 | echo "LOAD DATABASE | |
| 159 | FROM sqlite:///root/forgejo.db | |
| 160 | INTO postgresql://forgejo:$POSTGRES_PASSWORD_FORGEJO@postgres/forgejo | |
| 161 | WITH data only, reset sequences, prefetch rows = 10000 | |
| 162 | SET work_mem TO '16MB', maintenance_work_mem TO '512MB';" > ./pgloader-command | |
| 163 | ||
| 164 | pgloader ./pgloader-command | |
| 165 | ``` | |
| 166 | ||
| 167 | ## 2026-01-18 | |
| 168 | ||
| 169 | tags: [git], [home infra] | |
| 170 | ||
| 171 | i finally setup system integrated ssh push, but with a twist. | |
| 172 | ||
| 173 | the first problem is having two ssh servers on the same machine, one for the | |
| 174 | host, and another for Forgejo. this means that one of them had to live on | |
| 175 | another port, so i chose to move the host. but it kind of just sucks to use | |
| 176 | this. the proper solution is to use a custom config to direct the `git` user to | |
| 177 | the right place. | |
| 178 | ||
| 179 | what made this harder is i actually had two git instances; the second one is | |
| 180 | for a temporary infrastructure i'm running for [evil inc] until the | |
| 181 | organization gets dedicated hardware (more about this in a future post). so | |
| 182 | even if i routed `git` specially, it still wouldnt know which git server to go | |
| 183 | to. | |
| 184 | ||
| 185 | **the solution**: write a custom "router" script that generates an | |
| 186 | `authorized_keys` file based on which git instances actually have a key, then | |
| 187 | the line within the `authorized_keys` forces a special wrapper which intercepts | |
| 188 | the targetted git repository, routing it to the git instance with it. | |
| 189 | ||
| 190 | the sshd config looks like | |
| 191 | ||
| 192 | ``` | |
| 193 | Match User git | |
| 194 | AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k | |
| 195 | AuthorizedKeysCommandUser git | |
| 196 | ``` | |
| 197 | ||
| 198 | it is convenient because `AuthorizedKeysCommand` is run on every connection. it produces a file with zero or one valid keys: | |
| 199 | ||
| 200 | ``` | |
| 201 | command="/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh --clover 1 --evil 2",no-port-forwarding,...,restrict ssh-ed25519 AAAAC3NzaC... | |
| 202 | ``` | |
| 203 | ||
| 204 | and then the route script does this shit: | |
| 205 | ||
| 206 | ``` | |
| 207 | if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then | |
| 208 | [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1 | |
| 209 | exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id" | |
| 210 | elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then | |
| 211 | [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1 | |
| 212 | exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id" | |
| 213 | else | |
| 214 | echo "repo not found" >&2 | |
| 215 | exit 1 | |
| 216 | fi | |
| 217 | ``` | |
| 218 | ||
| 219 | it works beautifully. [see the whole patch for more info](https://git.paperclover.net/clo/home-infra/commit/e402d6ef71dfc310caeeb4d3579bddd9d0710594) | |
| 220 | ||
| 221 | ## 2026-01-14 | |
| 222 | ||
| 223 | tags: [next.js blog post], [this site] | |
| 224 | ||
| 225 | general housekeeping. getting this activity page up. getting the spanish | |
| 226 | translation by my good friend trubiso up. things are looking really cozy. | |
| 227 | ||
| 228 | ## 2026-01-12 | |
| 229 | ||
| 230 | tags: [album] | |
| 231 | ||
| 232 | i started more music work. i've gotten better at lyric writing, phrasing this | |
| 233 | new song as a sort of "adventure". felt for one of the first times that i was | |
| 234 | doing worldbuilding in a song. the imagery is that good. | |
| 235 | ||
| 236 | ## 2026-01-11 | |
| 237 | ||
| 238 | tags: [progress.ts], [todo tracker] | |
| 239 | ||
| 240 | i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git | |
| 241 | commits describe the tech better than me reiterating. | |
| 242 | ||
| 243 | > ## feat(lib/progress): implement streaming wire protocol | |
| 244 | > resolves `#47` | |
| 245 | > | |
| 246 | > `encodeByteStream` converts these events into a `ReadableStream`. by | |
| 247 | > batching events together, the stream contents remain small, that way the | |
| 248 | > code that constructs progress nodes do not have to worry about calling | |
| 249 | > many setters at once, it gets debounced be the serializer. stream | |
| 250 | > backpressure causes larger time-gaps to be batched (smaller). this | |
| 251 | > enables servers to respond with rich progress. | |
| 252 | > | |
| 253 | > ``` | |
| 254 | > const root = new progress.Root(); | |
| 255 | > doActionWithProgress(root).then(root.end, root.error); | |
| 256 | > // streaming clients indicate a header | |
| 257 | > if (req.headers.get("Accept")?.includes(progress.contentType)) | |
| 258 | > return new Response(progress.encodeByteStream(root), { | |
| 259 | > headers: { 'Content-Type': progress.contentType }, | |
| 260 | > }); | |
| 261 | > // to support non-streaming clients | |
| 262 | > return Response.json(await root.asPromise()); | |
| 263 | > ``` | |
| 264 | > | |
| 265 | > and `decodeByteStream` on the client: | |
| 266 | > | |
| 267 | > ``` | |
| 268 | > const output = document.getElementById("output"); | |
| 269 | > const res = await fetch(...); | |
| 270 | > if (!res.ok) throw ...; | |
| 271 | > const root = new progress.Root(); | |
| 272 | > root.on("change", (active) => { | |
| 273 | > output.innerText = ansi.strip(progress.formatAnsi( | |
| 274 | > performance.now(), | |
| 275 | > active, | |
| 276 | > )); | |
| 277 | > }); | |
| 278 | > const result = await progress.decodeByteStream(res.body, root); | |
| 279 | > output.innerText = JSON.stringify(result); | |
| 280 | > ``` | |
| 281 | > | |
| 282 | > there is currently no document bindings, but i plan to. additionally, a | |
| 283 | > React hook is very trivial to implement for this -- but that is | |
| 284 | > unplanned for this repository. for transports that require JSON or | |
| 285 | > UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of | |
| 286 | > JSON objects which can be compressed at the developer's discretion. | |
| 287 | ||
| 288 | > ## feat(lib/progress): headless rendering + time estimation | |
| 289 | > node signaling is done by providing a `progress.Root` to every node, | |
| 290 | > dispatching events to it when the node changes. the root is connected to | |
| 291 | > an observer to construct a UI out of it. there are two apis planned: | |
| 292 | > | |
| 293 | > - `attachToScreen` binds a root to a TTY screen (via the log.Widget API). | |
| 294 | > the primary use of this is to implement the top level `progress.start`. | |
| 295 | > | |
| 296 | > - a serialization system that allows transmitting a `Root` over a wire. | |
| 297 | > this commit was going to include this but it is an unexpectedly large | |
| 298 | > component. | |
| 299 | > | |
| 300 | > - potentially a browser binding like `attachToDocument`. this will not | |
| 301 | > be added in this patch. | |
| 302 | > | |
| 303 | > additionally, resolves #33 by implementing `estimatedTime` | |
| 304 | ||
| 305 | i also did a large part of the work to create a "code todo tracking" tool. i | |
| 306 | would say it's about half done, since the second half is simply fixing all of | |
| 307 | the little bugs there are. most of this code is currently ai-generated, but | |
| 308 | with me manually coming in to write interfaces and the modular program | |
| 309 | architecture. then i synthesize the code and the tests. this was basically just | |
| 310 | going on ambiently while [progress.ts] was in progress. | |
| 311 | ||
| 312 | ## 2026-01-09 | |
| 313 | ||
| 314 | tags: [home infra] | |
| 315 | ||
| 316 | finished SSO sub-project. im happy with the setup i used to protect internal | |
| 317 | services, such as pgadmin and qbittorrent. it's a caddy snippet that i can | |
| 318 | re-use very easily. | |
| 319 | ||
| 320 | ``` | |
| 321 | (reverse_proxy_auth) { | |
| 322 | handle /snow.oauth2/* { | |
| 323 | 		reverse_proxy "http://forward-auth" { | |
| 324 | 			header_up X-Real-IP {remote_host} | |
| 325 | 			header_up X-Forwarded-Uri {uri} | |
| 326 | 		} | |
| 327 | 	} | |
| 328 | handle { | |
| 329 | forward_auth "http://forward-auth" { | |
| 330 | uri /snow.oauth2/auth | |
| 331 | header_up X-Real-IP {remote_host} | |
| 332 | @error status 401 | |
| 333 | handle_response @error { | |
| 334 | redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri} | |
| 335 | } | |
| 336 | @valid_group header X-Auth-Request-Groups *role:{args[1]}* | |
| 337 | handle_response @valid_group { | |
| 338 | method {method} | |
| 339 | rewrite {uri} | |
| 340 | reverse_proxy {args[0]} { | |
| 341 | header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2" | |
| 342 | {block} | |
| 343 | } | |
| 344 | } | |
| 345 | handle_response { | |
| 346 | rewrite /403.html | |
| 347 | file_server { | |
| 348 | status 403 | |
| 349 | root /etc/caddy | |
| 350 | } | |
| 351 | } | |
| 352 | } | |
| 353 | } | |
| 354 | } | |
| 355 | ||
| 356 | # usage | |
| 357 | pg.{$HOME_DOMAIN} { | |
| 358 | import reverse_proxy_auth "http://pgadmin" admin | |
| 359 | } | |
| 360 | qbt.{$HOME_DOMAIN} { | |
| 361 | import reverse_proxy_auth "http://qbittorrent" media-manage | |
| 362 | } | |
| 363 | ``` | |
| 364 | ||
| 365 | ||
| 366 | ## 2026-01-04 | |
| 367 | ||
| 368 | tags: [home infra] | |
| 369 | ||
| 370 | working on SSO for my internal services. for context, i have about 12 | |
| 371 | self-hosted services running, half of which i allow my friends to access. | |
| 372 | currently, this is done through manually creating an account on such service | |
| 373 | (jellyfin, forgejo), but many are done through a caddy rule. in the interest of | |
| 374 | making my password manager less confused (ip vs domain, subdomain etc), i'm | |
| 375 | slowly reducing this setup to a single sign in page. | |
| 376 | ||
| 377 | to do this, i am using https://keycloak.org, which supports openid connect | |
| 378 | (how i will configure forgejo and jellyfin), as well as a separate service to | |
| 379 | provide forward auth proxying (how i protect services like copyparty, | |
| 380 | syncthing, pgadmin, and many more). i tried authelia beforehand, but i really | |
| 381 | do not recommend them due to how hard it is to configure, passkeys being | |
| 382 | annoying to setup, and limited themes. i also dont recommend authentik, but i | |
| 383 | couldnt figure out how to even start using it after i installed it. | |
| 384 | ||
| 385 | keycloak is a bit stupid on config. as all the config lies in the postgres | |
| 386 | database, i can't use a config file to setup the primary realm. so instead, i | |
| 387 | have this huge python script to use the API to upsert the configuration in. | |
| 388 | this works pretty well, and means that for locally running the infrastructure | |
| 389 | for testing, i can get the config to be the same (useful if you brick | |
| 390 | keycloak, which is pretty easy to do). | |
| 391 | ||
| 392 | ## 2026-01-02 | |
| 393 | ||
| 394 | tags: [home infra], [git], [name paint bot] show | |
| 395 | ||
| 396 | i deleted all my github repositories except four: my "readme", a bug | |
| 397 | reproduction repo, the mirror for [ts lie detector], and a shared private repo | |
| 398 | with someone that is load bearing. in this process, i've moved all the projects | |
| 399 | to my [forgejo instance][git]. | |
| 400 | ||
| 401 | with this, [name paint bot], one of my few remaining projects that is still | |
| 402 | active, moves to that forgejo instance using their github migrator. some of my | |
| 403 | private projects, like my pet scripting language, were migrated as well. it | |
| 404 | feels more alive on my site because of the theming and per-repo icons. | |
| 405 | ||
| 406 | after a year of forgejo, i am really happy with how it treats me. | |
| 407 |
src/q+a/clover-markdown.tsx deleted-271| ... | ... | @@ -1,271 +0,0 @@ |
| 1 | function assert(condition: any, message?: string): asserts condition { | |
| 2 | if (!condition) { | |
| 3 | throw new Error(message || "Assertion failed"); | |
| 4 | } | |
| 5 | } | |
| 6 | ||
| 7 | export const customRules = defaultRules.clone(); | |
| 8 | ||
| 9 | /** A question mention, formatted like `#112233445566` */ | |
| 10 | customRules.insertBefore("em", { | |
| 11 | name: "mentionQuestion", | |
| 12 | match: inlineRegex(/^#([0-9]{10})(?:[0-9]{2})?/), | |
| 13 | parse(capture) { | |
| 14 | return { | |
| 15 | id: capture[1], | |
| 16 | }; | |
| 17 | }, | |
| 18 | }); | |
| 19 | ||
| 20 | /** A question mention, formatted like `@pheonix-write` */ | |
| 21 | customRules.insertBefore("em", { | |
| 22 | name: "mentionArtifact", | |
| 23 | match: inlineRegex(/^@([a-z0-9-]+)/i), | |
| 24 | parse(capture) { | |
| 25 | return { | |
| 26 | id: capture[1], | |
| 27 | }; | |
| 28 | }, | |
| 29 | }); | |
| 30 | ||
| 31 | /** The censor bar used for past deadnaming: #name# and #username# */ | |
| 32 | customRules.insertBefore("em", { | |
| 33 | name: "deadname", | |
| 34 | match: inlineRegex(/^#(name|username)#/i), | |
| 35 | parse(capture) { | |
| 36 | return { | |
| 37 | kind: capture[1], | |
| 38 | }; | |
| 39 | }, | |
| 40 | }); | |
| 41 | ||
| 42 | /** html block. allows arbitrary html, but rn only as an answer. formatted like `@html content here` */ | |
| 43 | customRules.insertBefore("paragraph", { | |
| 44 | name: "html", | |
| 45 | match: blockRegex(/^@html ((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/), | |
| 46 | parse(capture) { | |
| 47 | return { | |
| 48 | data: capture[1], | |
| 49 | }; | |
| 50 | }, | |
| 51 | }); | |
| 52 | ||
| 53 | /** for server-rendered search, mark can highlight the seen word */ | |
| 54 | customRules.insertBefore("paragraph", { | |
| 55 | name: "mark", | |
| 56 | match: inlineRegex(/^<!!mark!!>(.*?)<!!mark!!>/), | |
| 57 | parse: parseCaptureInline, | |
| 58 | }); | |
| 59 | ||
| 60 | /** Adjust autolinking to use my own components if possible */ | |
| 61 | customRules.insertBefore("url", { | |
| 62 | name: "autolink_mention", | |
| 63 | match: inlineRegex( | |
| 64 | /^https?:\/\/(?:paperclover\.net|localhost:\d+)\/([^\s<]+[^<.,:;"')\]\s])/, | |
| 65 | ), | |
| 66 | parse(capture) { | |
| 67 | const qaMatch = capture[1].match(/^q\+a\/([0-9]{10}(?:[0-9]{2})?)/); | |
| 68 | if (qaMatch) { | |
| 69 | return { | |
| 70 | type: "mentionQuestion", | |
| 71 | id: qaMatch[1], | |
| 72 | }; | |
| 73 | } | |
| 74 | ||
| 75 | const fileMatch = capture[1].match(/^file\/([^#?]*)/); | |
| 76 | if (fileMatch) { | |
| 77 | // const file = MediaFile.getByPath(decodeURIComponent(fileMatch[1])); | |
| 78 | // if (file) { | |
| 79 | // return { | |
| 80 | // type: "file", | |
| 81 | // file, | |
| 82 | // }; | |
| 83 | // } | |
| 84 | } | |
| 85 | ||
| 86 | return { | |
| 87 | type: "link", | |
| 88 | content: [ | |
| 89 | { | |
| 90 | type: "text", | |
| 91 | content: capture[1], | |
| 92 | }, | |
| 93 | ], | |
| 94 | target: capture[1], | |
| 95 | title: undefined, | |
| 96 | }; | |
| 97 | }, | |
| 98 | }); | |
| 99 | ||
| 100 | export const questionRules = customRules.clone(); | |
| 101 | ||
| 102 | questionRules.remove("heading"); | |
| 103 | ||
| 104 | /** Input paragraphs. any paragraph prefixed with `i:` */ | |
| 105 | questionRules.insertBefore("paragraph", { | |
| 106 | name: "question", | |
| 107 | match: blockRegex(/^(?:q: [^\n]+(?:\n|$))+\s*/), | |
| 108 | parse(capture, parse, state) { | |
| 109 | const lines = capture[0] | |
| 110 | .trim() | |
| 111 | .split("\n") | |
| 112 | .map((line) => line.trim()) | |
| 113 | .filter((x) => x.length > 0) | |
| 114 | .map((x) => { | |
| 115 | assert(x.startsWith("q: ")); | |
| 116 | return x.slice(3); | |
| 117 | }); | |
| 118 | const content = lines | |
| 119 | .map((line, i) => { | |
| 120 | const parsed = parseInline(parse, line, state); | |
| 121 | if (i < lines.length - 1) { | |
| 122 | parsed.push({ type: "br" }); | |
| 123 | } | |
| 124 | return parsed; | |
| 125 | }) | |
| 126 | .flat(); | |
| 127 | return { | |
| 128 | content, | |
| 129 | }; | |
| 130 | }, | |
| 131 | }); | |
| 132 | ||
| 133 | const cloverParser = createParser(questionRules); | |
| 134 | ||
| 135 | const basicNodeTagNames: Record<string, string> = { | |
| 136 | mark: "mark", | |
| 137 | question: "q-", | |
| 138 | paragraph: "p", | |
| 139 | hr: "hr", | |
| 140 | blockQuote: "blockquote", | |
| 141 | em: "em", | |
| 142 | strong: "strong", | |
| 143 | u: "u", | |
| 144 | del: "del", | |
| 145 | inlineCode: "code", | |
| 146 | br: "br", | |
| 147 | }; | |
| 148 | ||
| 149 | function BasicNode(node: ASTNode, children: any) { | |
| 150 | const E = basicNodeTagNames[node.type]; | |
| 151 | return <E>{children}</E>; | |
| 152 | } | |
| 153 | ||
| 154 | function ListRenderer(node: ASTNode, children: any[]) { | |
| 155 | const T = node.ordered ? "ol" : "ul"; | |
| 156 | return ( | |
| 157 | <T> | |
| 158 | {children.map((child) => <li>{child}</li>)} | |
| 159 | </T> | |
| 160 | ); | |
| 161 | } | |
| 162 | ||
| 163 | function MarkdownLink(node: ASTNode, children: any[]) { | |
| 164 | return <a href={node.target}>{children}</a>; | |
| 165 | } | |
| 166 | ||
| 167 | function Deadname(node: ASTNode) { | |
| 168 | return <span class="dead">old {node.kind}</span>; | |
| 169 | } | |
| 170 | ||
| 171 | function QuestionRef(node: ASTNode) { | |
| 172 | if (node.id.length === 12) node.id = node.id.slice(0, 10); | |
| 173 | const timestamp = questionIdToTimestamp(node.id); | |
| 174 | if (!timestamp) { | |
| 175 | console.error(`Invalid question ref: ${node.id}`); | |
| 176 | return <span class="question-ref">[Invalid Question Ref: #{node.id}]</span>; | |
| 177 | } | |
| 178 | return ( | |
| 179 | <a class="custom question-ref" href={`/q+a/${node.id}`}> | |
| 180 | {formatQuestionTimestamp(timestamp)} | |
| 181 | </a> | |
| 182 | ); | |
| 183 | } | |
| 184 | ||
| 185 | function ArtifactRef(node: ASTNode) { | |
| 186 | const artifact = artifactMap[node.id]; | |
| 187 | if (!artifact) { | |
| 188 | return <span class="artifact">[Invalid Artifact Ref: @{node.id}]</span>; | |
| 189 | } | |
| 190 | const [title, url, type] = artifact; | |
| 191 | if (!url) { | |
| 192 | return <span>{title}</span>; | |
| 193 | } | |
| 194 | return ( | |
| 195 | <a class={`custom artifact artifact-${type}`} href={url}> | |
| 196 | {title} | |
| 197 | </a> | |
| 198 | ); | |
| 199 | } | |
| 200 | ||
| 201 | const br = <br />; | |
| 202 | const ssrFunctions: any = { | |
| 203 | // Default Renders | |
| 204 | // heading: HeadingRenderer, | |
| 205 | hr: BasicNode, | |
| 206 | mark: BasicNode, | |
| 207 | blockQuote: BasicNode, | |
| 208 | // codeBlock: MarkdownCodeBlock, | |
| 209 | list: ListRenderer, | |
| 210 | // table: TableRenderer, | |
| 211 | paragraph: BasicNode, | |
| 212 | // tableSeparator: ???, | |
| 213 | link: MarkdownLink, | |
| 214 | // image: MarkdownImage, | |
| 215 | em: BasicNode, | |
| 216 | strong: BasicNode, | |
| 217 | u: BasicNode, | |
| 218 | del: BasicNode, | |
| 219 | inlineCode: BasicNode, | |
| 220 | br: () => br, | |
| 221 | newline: () => br, | |
| 222 | deadname: Deadname, | |
| 223 | ||
| 224 | // Custom Elements | |
| 225 | question: BasicNode, | |
| 226 | html: (node: any) => render.raw(node.data), | |
| 227 | mentionQuestion: QuestionRef, | |
| 228 | mentionArtifact: ArtifactRef, | |
| 229 | }; | |
| 230 | ||
| 231 | function renderNode(node: string | ASTNode | ASTNode[]): any { | |
| 232 | if (typeof node === "string") { | |
| 233 | return node; | |
| 234 | } else if (Array.isArray(node)) { | |
| 235 | return node.map((item) => renderNode(item)); | |
| 236 | } else if (node.type === "text") { | |
| 237 | return node.content; | |
| 238 | } else { | |
| 239 | const render = ssrFunctions[node.type]; | |
| 240 | if (!render) { | |
| 241 | throw new Error(`No renderer for node type "${node.type}"`); | |
| 242 | } | |
| 243 | const content = node.content as any; | |
| 244 | return render(node, content ? renderNode(content) : undefined); | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | export namespace CloverMarkdown { | |
| 249 | export interface Props { | |
| 250 | text: string; | |
| 251 | inline?: boolean; | |
| 252 | } | |
| 253 | } | |
| 254 | ||
| 255 | export function CloverMarkdown(props: CloverMarkdown.Props) { | |
| 256 | const ast = cloverParser(props.text, { inline: !!props.inline }); | |
| 257 | return renderNode(ast); | |
| 258 | } | |
| 259 | ||
| 260 | import { artifactMap } from "./artifacts.ts"; | |
| 261 | import { formatQuestionTimestamp, questionIdToTimestamp } from "./format.ts"; | |
| 262 | import { | |
| 263 | type ASTNode, | |
| 264 | blockRegex, | |
| 265 | createParser, | |
| 266 | defaultRules, | |
| 267 | inlineRegex, | |
| 268 | parseCaptureInline, | |
| 269 | parseInline, | |
| 270 | } from "./simple-markdown.ts"; | |
| 271 | import * as render from "lib/render.ts"; |
src/q+a/header.client.ts deleted-12| ... | ... | @@ -1,12 +0,0 @@ |
| 1 | const h1 = document.querySelector("h1")!; | |
| 2 | const key = "net.paperclover.q+a.header"; | |
| 3 | const state = localStorage?.getItem(key); | |
| 4 | if (state === "detrevni ton") h1.classList.toggle("invert"); | |
| 5 | h1.addEventListener("click", () => { | |
| 6 | h1.classList.toggle("invert"); | |
| 7 | localStorage?.setItem( | |
| 8 | key, | |
| 9 | (localStorage.getItem?.(key) ?? "not inverted") | |
| 10 | .split("").reverse().join(""), | |
| 11 | ); | |
| 12 | }); |
src/q+a/layout.tsx deleted-29| ... | ... | @@ -1,29 +0,0 @@ |
| 1 | import "./questions.css"; | |
| 2 | ||
| 3 | export default function QuestionLayout({ content }: render.Node) { | |
| 4 | addScript("./header.client.ts"); | |
| 5 | return ( | |
| 6 | <main class="qa"> | |
| 7 | <h1> | |
| 8 | <span class="a" style="color:var(--a);--s:#19607d">answers</span> | |
| 9 | <span class="n" style="--s:#999;margin:0 5px">&</span> | |
| 10 | <span class="q" style="color:var(--b);--s:#994253">questions</span> | |
| 11 | </h1> | |
| 12 | {content} | |
| 13 | </main> | |
| 14 | ); | |
| 15 | } | |
| 16 | ||
| 17 | export const theme = { | |
| 18 | fg: "#f0f0f0", | |
| 19 | bg: "#1a1b1c", | |
| 20 | primary: "#f5a9b8", | |
| 21 | }; | |
| 22 | ||
| 23 | export const minimal = { | |
| 24 | default: ({ content }: { content: any }) => <main class="qa">{content}</main>, | |
| 25 | theme, | |
| 26 | }; | |
| 27 | ||
| 28 | import { addScript } from "#sitegen"; | |
| 29 | import * as render from "lib/render.ts"; |
src/q+a/markdown.tsx created+81| ... | ... | @@ -0,0 +1,81 @@ |
| 1 | function labelledRedaction(contents: string) { | |
| 2 | return ( | |
| 3 | <span class="dead"> | |
| 4 | {contents === "name" | |
| 5 | ? "old name" | |
| 6 | : contents === "username" | |
| 7 | ? "old username" | |
| 8 | : contents === "domain" | |
| 9 | ? "old domain" | |
| 10 | : null} | |
| 11 | </span> | |
| 12 | ); | |
| 13 | } | |
| 14 | ||
| 15 | function questionRef(id: string) { | |
| 16 | if (id.length === 12) id = id.slice(0, 10); | |
| 17 | const timestamp = questionIdToTimestamp(id); | |
| 18 | if (!timestamp) { | |
| 19 | console.error(`Invalid question ref: ${id}`); | |
| 20 | return <span class="question-ref">[Invalid Question Ref: #{id}]</span>; | |
| 21 | } | |
| 22 | return ( | |
| 23 | <a class="custom question-ref" href={`/q+a/${id}`}> | |
| 24 | {formatQuestionTimestamp(timestamp)} | |
| 25 | </a> | |
| 26 | ); | |
| 27 | } | |
| 28 | ||
| 29 | function artifactRef(id: string) { | |
| 30 | const artifact = artifactMap[id]; | |
| 31 | if (!artifact) { | |
| 32 | return <span class="artifact">[Invalid Artifact Ref: @{id}]</span>; | |
| 33 | } | |
| 34 | const [title, url, type] = artifact; | |
| 35 | if (!url) { | |
| 36 | return <span>{title}</span>; | |
| 37 | } | |
| 38 | return ( | |
| 39 | <a class={`custom artifact artifact-${type}`} href={url}> | |
| 40 | {title} | |
| 41 | </a> | |
| 42 | ); | |
| 43 | } | |
| 44 | ||
| 45 | /** classic esbuild trick: use an impossible to guess key for string magic */ | |
| 46 | const uniqueKey = crypto.randomUUID().replaceAll("-", ""); | |
| 47 | ||
| 48 | const renderers = { | |
| 49 | A: artifactRef, | |
| 50 | Q: questionRef, | |
| 51 | R: labelledRedaction, | |
| 52 | } as const; | |
| 53 | ||
| 54 | export function renderMarkdown(source: string) { | |
| 55 | const tagNames: markodown.CloverQuestionExtensions = { | |
| 56 | question: "q-", | |
| 57 | artifactRef: uniqueKey + "_A", | |
| 58 | questionRef: uniqueKey + "_Q", | |
| 59 | labelledRedaction: uniqueKey + "_R", | |
| 60 | }; | |
| 61 | const result = markodown.transform({ | |
| 62 | source, | |
| 63 | markdownOnly: true, | |
| 64 | cloverExtensions: tagNames, | |
| 65 | }); | |
| 66 | if (!result.success) { | |
| 67 | throw new Error(JSON.stringify(result.errors)); | |
| 68 | } | |
| 69 | return result.text | |
| 70 | .replace( | |
| 71 | new RegExp(`<${uniqueKey}_([AQR])>([^<]*)<\\/${uniqueKey}_[AQR]>`, "g"), | |
| 72 | (_, type: "A" | "Q" | "R", content: string) => { | |
| 73 | return render.sync(renderers[type](content)).text; | |
| 74 | }, | |
| 75 | ); | |
| 76 | } | |
| 77 | ||
| 78 | import * as markodown from "@clo/markodown"; | |
| 79 | import * as render from "lib/render.ts"; | |
| 80 | import { artifactMap } from "./artifacts.ts"; | |
| 81 | import { formatQuestionTimestamp, questionIdToTimestamp } from "./format.ts"; |
src/q+a/pages/admin.q+a.marko+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | export * as layout from "../layout.tsx"; | |
| 1 | export { theme } from "../tags/question-layout.marko"; | |
| 2 | 2 | export interface Input { |
| 3 | 3 | admin?: boolean; |
| 4 | 4 | } |
src/q+a/pages/q+a.404.marko+3-1| ... | ... | @@ -1,10 +1,12 @@ |
| 1 | export * as layout from "../layout.tsx"; | |
| 1 | export * as layout from "../tags/question-layout.marko"; | |
| 2 | 2 | export const meta: Metadata = { |
| 3 | 3 | title: "paper clover q+a", |
| 4 | 4 | }; |
| 5 | 5 | |
| 6 | <question-layout> | |
| 6 | 7 | <h2>sound the alarms</h2> |
| 7 | 8 | <p>this page doesn't exist</p> |
| 8 | 9 | <p> |
| 9 | 10 | <a href="/q+a">return to the questions list</a> |
| 10 | 11 | </p> |
| 12 | </question-layout> |
src/q+a/pages/q+a.marko+3-1| ... | ... | @@ -2,7 +2,7 @@ export interface Input { |
| 2 | 2 | admin?: boolean; |
| 3 | 3 | } |
| 4 | 4 | |
| 5 | export * as layout from "../layout.tsx"; | |
| 5 | export { theme } from "../tags/question-layout.marko"; | |
| 6 | 6 | export const regenerate = { tags: ["q+a"] }; |
| 7 | 7 | export const meta: Metadata = { |
| 8 | 8 | title: "paper clover q+a", |
| ... | ... | @@ -12,6 +12,7 @@ export const meta: Metadata = { |
| 12 | 12 | <const/{ admin = false } = input /> |
| 13 | 13 | <const/questions = [...Question.getAll()] /> |
| 14 | 14 | |
| 15 | <question-layout> | |
| 15 | 16 | <if=!admin> |
| 16 | 17 | <question-form /> |
| 17 | 18 | </> |
| ... | ... | @@ -21,5 +22,6 @@ export const meta: Metadata = { |
| 21 | 22 | <footer> |
| 22 | 23 | fun fact: clover has answered ${questions.length} questions |
| 23 | 24 | </footer> |
| 25 | </question-layout> | |
| 24 | 26 | |
| 25 | 27 | import { Question } from "@/q+a/models/Question.ts"; |
src/q+a/scripts/editor.client.tsx+11-14| ... | ... | @@ -1,6 +1,5 @@ |
| 1 | 1 | import { EditorState } from "@codemirror/state"; |
| 2 | 2 | import { basicSetup, EditorView } from "codemirror"; |
| 3 | import * as render from "lib/render.ts"; | |
| 4 | 3 | import type { ScriptPayload } from "@/q+a/views/editor.marko"; |
| 5 | 4 | import QuestionRender from "@/q+a/tags/question.marko"; |
| 6 | 5 | |
| ... | ... | @@ -10,19 +9,17 @@ const date = new Date(payload.date); |
| 10 | 9 | const main = document.getElementById("edit-grid")! as HTMLDivElement; |
| 11 | 10 | const preview = document.getElementById("preview")! as HTMLDivElement; |
| 12 | 11 | |
| 13 | function updatePreview(text: string) { | |
| 14 | preview.innerHTML = render.sync( | |
| 15 | <QuestionRender | |
| 16 | question={{ | |
| 17 | id: payload.id, | |
| 18 | qmid: payload.qmid, | |
| 19 | text: text, | |
| 20 | type: payload.type, | |
| 21 | date, | |
| 22 | }} | |
| 23 | editor | |
| 24 | />, | |
| 25 | ).text; | |
| 12 | async function updatePreview(text: string) { | |
| 13 | preview.innerHTML = await QuestionRender.render({ | |
| 14 | question: { | |
| 15 | id: payload.id, | |
| 16 | qmid: payload.qmid, | |
| 17 | text: text, | |
| 18 | type: payload.type, | |
| 19 | date, | |
| 20 | }, | |
| 21 | editor: true, | |
| 22 | }); | |
| 26 | 23 | } |
| 27 | 24 | updatePreview(payload.text); |
| 28 | 25 |
src/q+a/simple-markdown.ts deleted-1090| ... | ... | @@ -1,1090 +0,0 @@ |
| 1 | // @ts-nocheck | |
| 2 | // # Simple-Markdown Core | |
| 3 | // | |
| 4 | // This is a fork of Khan-academy's Simple-Markdown[1], initially forked in 2022 | |
| 5 | // to add Svelte support[2], and used for paper clover's q+a markdown flavor. | |
| 6 | // | |
| 7 | // 1: https://github.com/Khan/perseus/tree/main/packages/simple-markdown/src | |
| 8 | // 2: https://github.com/paperclover/svelte-simple-markdown | |
| 9 | export type Rules = Record<string, ParserRule>; | |
| 10 | export interface ParserRule { | |
| 11 | name: string; | |
| 12 | match: MatchFunction; | |
| 13 | parse: ParseFunction; | |
| 14 | quality?: QualityFunction; | |
| 15 | } | |
| 16 | ||
| 17 | export class RuleList extends Array<ParserRule> { | |
| 18 | constructor(input?: ArrayLike<ParserRule>) { | |
| 19 | super(); | |
| 20 | if (input) { | |
| 21 | this.push(...Array.from(input)); | |
| 22 | } | |
| 23 | } | |
| 24 | ||
| 25 | insertBefore(rule: string, newRule: ParserRule): void { | |
| 26 | const index = this.findIndex((r) => r.name === rule); | |
| 27 | if (index === -1) { | |
| 28 | throw new Error(`Rule ${rule} not found`); | |
| 29 | } | |
| 30 | this.splice(index, 0, newRule); | |
| 31 | } | |
| 32 | ||
| 33 | insertAfter(rule: string, newRule: ParserRule): void { | |
| 34 | const index = this.findIndex((r) => r.name === rule); | |
| 35 | if (index === -1) { | |
| 36 | throw new Error(`Rule ${rule} not found`); | |
| 37 | } | |
| 38 | this.splice(index + 1, 0, newRule); | |
| 39 | } | |
| 40 | ||
| 41 | toRuleObject(): Record<string, ParserRule> { | |
| 42 | const result: Record<string, ParserRule> = {}; | |
| 43 | this.forEach((rule) => { | |
| 44 | result[rule.name] = rule; | |
| 45 | }); | |
| 46 | return result; | |
| 47 | } | |
| 48 | ||
| 49 | add(rule: ParserRule): void { | |
| 50 | this.push(rule); | |
| 51 | } | |
| 52 | ||
| 53 | get(rule: string): ParserRule | undefined { | |
| 54 | return this.find((r) => r.name === rule); | |
| 55 | } | |
| 56 | ||
| 57 | remove(rule: string): void { | |
| 58 | const index = this.findIndex((r) => r.name === rule); | |
| 59 | if (index === -1) { | |
| 60 | throw new Error(`Rule ${rule} not found`); | |
| 61 | } | |
| 62 | this.splice(index, 1); | |
| 63 | } | |
| 64 | ||
| 65 | clone() { | |
| 66 | return new RuleList(this); | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Creates a parser for a given set of rules, with the precedence | |
| 72 | * specified as a list of rules. | |
| 73 | * | |
| 74 | * @param rules | |
| 75 | * an object containing | |
| 76 | * rule type -> {match, order, parse} objects | |
| 77 | * (lower order is higher precedence) | |
| 78 | * @param [defaultState] | |
| 79 | * | |
| 80 | * @returns | |
| 81 | * The resulting parse function, with the following parameters: | |
| 82 | * @source: the input source string to be parsed | |
| 83 | * @state: an optional object to be threaded through parse | |
| 84 | * calls. Allows clients to add stateful operations to | |
| 85 | * parsing, such as keeping track of how many levels deep | |
| 86 | * some nesting is. For an example use-case, see passage-ref | |
| 87 | * parsing in src/widgets/passage/passage-markdown.jsx | |
| 88 | */ | |
| 89 | export function createParser( | |
| 90 | ruleListInput: RuleList, | |
| 91 | defaultState: Partial<ParserState> = {}, | |
| 92 | ) { | |
| 93 | let rules = ruleListInput.toRuleObject(); | |
| 94 | let ruleList = Object.keys(rules); | |
| 95 | ||
| 96 | let latestState: ParserState; | |
| 97 | ||
| 98 | let nestedParse = function (source: string, state?: ParserState) { | |
| 99 | let result: ASTNode[] = []; | |
| 100 | state = state || latestState; | |
| 101 | latestState = state; | |
| 102 | ||
| 103 | while (source) { | |
| 104 | // store the best match, it's rule, and quality: | |
| 105 | let ruleType = null; | |
| 106 | let rule = null; | |
| 107 | let capture = null; | |
| 108 | let quality = NaN; // loop control variables: | |
| 109 | ||
| 110 | let i = 0; | |
| 111 | let currRuleType = ruleList[0]; | |
| 112 | ||
| 113 | let currRule = rules[currRuleType]; | |
| 114 | ||
| 115 | do { | |
| 116 | let currCapture = currRule.match(source, state); | |
| 117 | ||
| 118 | if (currCapture) { | |
| 119 | let currQuality = currRule.quality | |
| 120 | ? currRule.quality(currCapture, state) | |
| 121 | : 0; | |
| 122 | ||
| 123 | // This should always be true the first time because | |
| 124 | // the initial quality is NaN (that's why there's the | |
| 125 | // condition negation). | |
| 126 | if (!(currQuality <= quality)) { | |
| 127 | ruleType = currRuleType; | |
| 128 | rule = currRule; | |
| 129 | capture = currCapture; | |
| 130 | quality = currQuality; | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | // Move on to the next item. | |
| 135 | // Note that this makes `currRule` be the next item | |
| 136 | i++; | |
| 137 | currRuleType = ruleList[i]; | |
| 138 | currRule = rules[currRuleType]; | |
| 139 | } while ( | |
| 140 | // keep looping while we're still within the ruleList | |
| 141 | currRule && | |
| 142 | // if we don't have a match yet, continue | |
| 143 | (!capture || | |
| 144 | // or if we have a match, but the next rule is | |
| 145 | // at the same order, and has a quality measurement | |
| 146 | // functions, then this rule must have a quality | |
| 147 | // measurement function (since they are sorted before | |
| 148 | // those without), and we need to check if there is | |
| 149 | // a better quality match | |
| 150 | currRule.quality) | |
| 151 | ); | |
| 152 | ||
| 153 | if (!rule || !capture || !ruleType) { | |
| 154 | throw new Error( | |
| 155 | "Could not find a matching rule for the below " + | |
| 156 | "content. The rule with highest `order` should " + | |
| 157 | "always match content provided to it. Check " + | |
| 158 | "the definition of `match` for '" + | |
| 159 | ruleList[ruleList.length - 1] + | |
| 160 | "'. It seems to not match the following source:\n" + | |
| 161 | source, | |
| 162 | ); | |
| 163 | } | |
| 164 | ||
| 165 | if (capture.index) { | |
| 166 | // If present and non-zero, i.e. a non-^ regexp result: | |
| 167 | throw new Error( | |
| 168 | "`match` must return a capture starting at index 0 " + | |
| 169 | "(the current parse index). Did you forget a ^ at the " + | |
| 170 | "start of the RegExp?", | |
| 171 | ); | |
| 172 | } | |
| 173 | ||
| 174 | let parsed = rule.parse(capture, nestedParse, state); | |
| 175 | ||
| 176 | // We maintain the same object here so that rules can | |
| 177 | // store references to the objects they return and | |
| 178 | // modify them later. (oops sorry! but this adds a lot | |
| 179 | // of power--see reflinks.) | |
| 180 | ||
| 181 | // We also let rules override the default type of | |
| 182 | // their parsed node if they would like to, so that | |
| 183 | // there can be a single output function for all links, | |
| 184 | // even if there are several rules to parse them. | |
| 185 | if (!parsed.type) { | |
| 186 | parsed.type = ruleType; | |
| 187 | } | |
| 188 | ||
| 189 | // Collapse text nodes | |
| 190 | if ( | |
| 191 | parsed.type === "text" && result[result.length - 1]?.type === "text" | |
| 192 | ) { | |
| 193 | result[result.length - 1].content += parsed.content; | |
| 194 | } else { | |
| 195 | result.push(parsed as ASTNode); | |
| 196 | } | |
| 197 | ||
| 198 | state.prevCapture = capture; | |
| 199 | source = source.substring(state.prevCapture[0].length); | |
| 200 | } | |
| 201 | ||
| 202 | return result; | |
| 203 | }; | |
| 204 | ||
| 205 | let outerParse = function ( | |
| 206 | source: string, | |
| 207 | state: ParserState = { inline: false }, | |
| 208 | ) { | |
| 209 | latestState = populateInitialState(state, defaultState); | |
| 210 | ||
| 211 | if (!latestState.inline && !latestState.disableAutoBlockNewlines) { | |
| 212 | source = source + "\n\n"; | |
| 213 | } | |
| 214 | ||
| 215 | // We store the previous capture so that match functions can | |
| 216 | // use some limited amount of lookbehind. Lists use this to | |
| 217 | // ensure they don't match arbitrary '- ' or '* ' in inline | |
| 218 | // text (see the list rule for more information). This stores | |
| 219 | // the full regex capture object, if there is one. | |
| 220 | latestState.prevCapture = undefined; | |
| 221 | return nestedParse(preprocess(source), latestState); | |
| 222 | }; | |
| 223 | ||
| 224 | return outerParse; | |
| 225 | } | |
| 226 | ||
| 227 | type Multiple<T> = T | T[]; | |
| 228 | type Nullable<T> = T | null | undefined; | |
| 229 | ||
| 230 | export type MatchFunction = ( | |
| 231 | source: string, | |
| 232 | state: ParserState, | |
| 233 | ) => Nullable<RegExpMatchArray>; | |
| 234 | ||
| 235 | export type Parser = (source: string, state?: ParserState) => ASTNode[]; | |
| 236 | ||
| 237 | export type ParseFunction = ( | |
| 238 | source: RegExpMatchArray, | |
| 239 | nestedParse: Parser, | |
| 240 | state: ParserState, | |
| 241 | ) => TypeOptionalASTNode; | |
| 242 | ||
| 243 | export type QualityFunction = ( | |
| 244 | capture: RegExpMatchArray, | |
| 245 | state: ParserState, | |
| 246 | ) => number; | |
| 247 | ||
| 248 | export interface ParserState { | |
| 249 | inline: boolean; | |
| 250 | prevCapture?: RegExpMatchArray; | |
| 251 | [key: string]: any; | |
| 252 | } | |
| 253 | ||
| 254 | export interface ASTNode { | |
| 255 | type: string; | |
| 256 | content?: ASTNode[] | string; | |
| 257 | [key: string]: any; | |
| 258 | } | |
| 259 | ||
| 260 | export type TypeOptionalASTNode = Omit<ASTNode, "type"> & { type?: string }; | |
| 261 | ||
| 262 | export interface RefNode { | |
| 263 | type: string; | |
| 264 | content?: Multiple<ASTNode>; | |
| 265 | target?: string; | |
| 266 | title?: string; | |
| 267 | alt?: string; | |
| 268 | } | |
| 269 | ||
| 270 | /** Creates a match function for an inline scoped element from a regex */ | |
| 271 | export function inlineRegex(regex: RegExp): MatchFunction { | |
| 272 | return (source, state) => { | |
| 273 | if (state.inline) { | |
| 274 | return regex.exec(source); | |
| 275 | } else { | |
| 276 | return null; | |
| 277 | } | |
| 278 | }; | |
| 279 | } | |
| 280 | ||
| 281 | /** Creates a match function for a block scoped element from a regex */ | |
| 282 | export function blockRegex(regex: RegExp): MatchFunction { | |
| 283 | return (source, state) => { | |
| 284 | if (state.inline) { | |
| 285 | return null; | |
| 286 | } else { | |
| 287 | return regex.exec(source); | |
| 288 | } | |
| 289 | }; | |
| 290 | } | |
| 291 | ||
| 292 | /** Creates a match function from a regex, ignoring block/inline scope */ | |
| 293 | export function anyScopeRegex(regex: RegExp): MatchFunction { | |
| 294 | return (source) => { | |
| 295 | return regex.exec(source); | |
| 296 | }; | |
| 297 | } | |
| 298 | ||
| 299 | const UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g; | |
| 300 | export function unescapeUrl(rawUrlString: string) { | |
| 301 | return rawUrlString.replace(UNESCAPE_URL_R, "$1"); | |
| 302 | } | |
| 303 | ||
| 304 | /** | |
| 305 | * Parse some content with the parser `parse`, with state.inline | |
| 306 | * set to true. Useful for block elements; not generally necessary | |
| 307 | * to be used by inline elements (where state.inline is already true. | |
| 308 | */ | |
| 309 | export function parseInline( | |
| 310 | parse: Parser, | |
| 311 | content: string, | |
| 312 | state: ParserState, | |
| 313 | ) { | |
| 314 | const isCurrentlyInline = state.inline || false; | |
| 315 | state.inline = true; | |
| 316 | const result = parse(content, state); | |
| 317 | state.inline = isCurrentlyInline; | |
| 318 | return result; | |
| 319 | } | |
| 320 | ||
| 321 | export function parseBlock(parse: Parser, content: string, state: ParserState) { | |
| 322 | const isCurrentlyInline = state.inline || false; | |
| 323 | state.inline = false; | |
| 324 | const result = parse(content + "\n\n", state); | |
| 325 | state.inline = isCurrentlyInline; | |
| 326 | return result; | |
| 327 | } | |
| 328 | ||
| 329 | export function parseCaptureInline( | |
| 330 | capture: RegExpMatchArray, | |
| 331 | parse: Parser, | |
| 332 | state: ParserState, | |
| 333 | ) { | |
| 334 | return { | |
| 335 | content: parseInline(parse, capture[1], state), | |
| 336 | }; | |
| 337 | } | |
| 338 | ||
| 339 | export function ignoreCapture() { | |
| 340 | return {}; | |
| 341 | } | |
| 342 | ||
| 343 | export function sanitizeUrl(url?: string) { | |
| 344 | if (url == null) { | |
| 345 | return null; | |
| 346 | } | |
| 347 | try { | |
| 348 | const prot = new URL(url, "https://localhost").protocol; | |
| 349 | if ( | |
| 350 | prot.indexOf("javascript:") === 0 || | |
| 351 | prot.indexOf("vbscript:") === 0 || | |
| 352 | prot.indexOf("data:") === 0 | |
| 353 | ) { | |
| 354 | return null; | |
| 355 | } | |
| 356 | } catch (e) { | |
| 357 | // invalid URLs should throw a TypeError | |
| 358 | // see for instance: `new URL("");` | |
| 359 | return null; | |
| 360 | } | |
| 361 | return url; | |
| 362 | } | |
| 363 | ||
| 364 | const CR_NEWLINE_R = /\r\n?/g; | |
| 365 | const TAB_R = /\t/g; | |
| 366 | const FORMFEED_R = /\f/g; | |
| 367 | ||
| 368 | /** | |
| 369 | * Turn various whitespace into easy-to-process whitespace | |
| 370 | */ | |
| 371 | function preprocess(source: string) { | |
| 372 | return source.replace(CR_NEWLINE_R, "\n").replace(FORMFEED_R, "").replace( | |
| 373 | TAB_R, | |
| 374 | " ", | |
| 375 | ); | |
| 376 | } | |
| 377 | ||
| 378 | function populateInitialState( | |
| 379 | givenState: Partial<ParserState>, | |
| 380 | defaultState: Partial<ParserState>, | |
| 381 | ) { | |
| 382 | let state = givenState || {}; | |
| 383 | ||
| 384 | for (let prop in defaultState) { | |
| 385 | if (Object.prototype.hasOwnProperty.call(defaultState, prop)) { | |
| 386 | state[prop] = defaultState[prop]; | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | return state as ParserState; | |
| 391 | } | |
| 392 | ||
| 393 | // recognize a `*` `-`, `+`, `1.`, `2.`... list bullet | |
| 394 | const LIST_BULLET = "(?:[*+-]|\\d+\\.)"; | |
| 395 | ||
| 396 | // recognize the start of a list item: | |
| 397 | // leading space plus a bullet plus a space (` * `) | |
| 398 | const LIST_ITEM_PREFIX = "( *)(" + LIST_BULLET + ") +"; | |
| 399 | const LIST_ITEM_PREFIX_R = new RegExp("^" + LIST_ITEM_PREFIX); | |
| 400 | ||
| 401 | // recognize an individual list item: | |
| 402 | // * hi | |
| 403 | // this is part of the same item | |
| 404 | // | |
| 405 | // as is this, which is a new paragraph in the same item | |
| 406 | // | |
| 407 | // * but this is not part of the same item | |
| 408 | const LIST_ITEM_R = new RegExp( | |
| 409 | LIST_ITEM_PREFIX + "[^\\n]*(?:\\n" + "(?!\\1" + LIST_BULLET + | |
| 410 | " )[^\\n]*)*(\n|$)", | |
| 411 | "gm", | |
| 412 | ); | |
| 413 | const BLOCK_END_R = /\n{2,}$/; | |
| 414 | const INLINE_CODE_ESCAPE_BACKTICKS_R = /^ (?= *`)|(` *) $/g; | |
| 415 | ||
| 416 | // recognize the end of a paragraph block inside a list item: | |
| 417 | // two or more newlines at end end of the item | |
| 418 | const LIST_BLOCK_END_R = BLOCK_END_R; | |
| 419 | const LIST_ITEM_END_R = / *\n+$/; | |
| 420 | ||
| 421 | // check whether a list item has paragraphs: if it does, | |
| 422 | // we leave the newlines at the end | |
| 423 | const LIST_R = new RegExp( | |
| 424 | "^( *)(" + | |
| 425 | LIST_BULLET + | |
| 426 | ") " + | |
| 427 | "[\\s\\S]+?(?:\n{2,}(?! )" + | |
| 428 | "(?!\\1" + | |
| 429 | LIST_BULLET + | |
| 430 | " )\\n*" + | |
| 431 | // the \\s*$ here is so that we can parse the inside of nested | |
| 432 | // lists, where our content might end before we receive two `\n`s | |
| 433 | "|\\s*\n*$)", | |
| 434 | ); | |
| 435 | const LIST_LOOKBEHIND_R = /(?:^|\n)( *)$/; | |
| 436 | ||
| 437 | const TABLES = (function () { | |
| 438 | const TABLE_ROW_SEPARATOR_TRIM = /^ *\| *| *\| *$/g; | |
| 439 | const TABLE_CELL_END_TRIM = / *$/; | |
| 440 | const TABLE_RIGHT_ALIGN = /^ *-+: *$/; | |
| 441 | const TABLE_CENTER_ALIGN = /^ *:-+: *$/; | |
| 442 | const TABLE_LEFT_ALIGN = /^ *:-+ *$/; // TODO: This needs a real type | |
| 443 | ||
| 444 | const parseTableAlignCapture = (alignCapture: string) => { | |
| 445 | if (TABLE_RIGHT_ALIGN.test(alignCapture)) { | |
| 446 | return "right"; | |
| 447 | } else if (TABLE_CENTER_ALIGN.test(alignCapture)) { | |
| 448 | return "center"; | |
| 449 | } else if (TABLE_LEFT_ALIGN.test(alignCapture)) { | |
| 450 | return "left"; | |
| 451 | } else { | |
| 452 | return null; | |
| 453 | } | |
| 454 | }; | |
| 455 | ||
| 456 | const parseTableAlign = (source: string, trimEndSeparators: boolean) => { | |
| 457 | if (trimEndSeparators) { | |
| 458 | source = source.replace(TABLE_ROW_SEPARATOR_TRIM, ""); | |
| 459 | } | |
| 460 | ||
| 461 | const alignText = source.trim().split("|"); | |
| 462 | return alignText.map(parseTableAlignCapture); | |
| 463 | }; | |
| 464 | ||
| 465 | const parseTableRow = ( | |
| 466 | source: string, | |
| 467 | parse: Parser, | |
| 468 | state: ParserState, | |
| 469 | trimEndSeparators: boolean, | |
| 470 | ) => { | |
| 471 | const prevInTable = state.inTable; | |
| 472 | state.inTable = true; | |
| 473 | const tableRow = parse(source.trim(), state); | |
| 474 | state.inTable = prevInTable; | |
| 475 | const cells: ASTNode[][] = [[]]; | |
| 476 | tableRow.forEach(function (node, i) { | |
| 477 | if (node.type === "tableSeparator") { | |
| 478 | // Filter out empty table separators at the start/end: | |
| 479 | if (!trimEndSeparators || (i !== 0 && i !== tableRow.length - 1)) { | |
| 480 | // Split the current row: | |
| 481 | cells.push([]); | |
| 482 | } | |
| 483 | } else { | |
| 484 | if ( | |
| 485 | typeof node.content === "string" && | |
| 486 | (tableRow[i + 1] == null || tableRow[i + 1].type === "tableSeparator") | |
| 487 | ) { | |
| 488 | node.content = node.content.replace(TABLE_CELL_END_TRIM, ""); | |
| 489 | } | |
| 490 | ||
| 491 | cells[cells.length - 1].push(node); | |
| 492 | } | |
| 493 | }); | |
| 494 | return cells; | |
| 495 | }; | |
| 496 | ||
| 497 | /** | |
| 498 | * @param {string} source | |
| 499 | * @param {SimpleMarkdown.Parser} parse | |
| 500 | * @param {SimpleMarkdown.State} state | |
| 501 | * @param {boolean} trimEndSeparators | |
| 502 | * @returns {SimpleMarkdown.ASTNode[][]} | |
| 503 | */ | |
| 504 | const parseTableCells = function ( | |
| 505 | source: string, | |
| 506 | parse: Parser, | |
| 507 | state: ParserState, | |
| 508 | trimEndSeparators: boolean, | |
| 509 | ) { | |
| 510 | const rowsText = source.trim().split("\n"); | |
| 511 | return rowsText.map(function (rowText) { | |
| 512 | return parseTableRow(rowText, parse, state, trimEndSeparators); | |
| 513 | }); | |
| 514 | }; | |
| 515 | ||
| 516 | /** | |
| 517 | * @param {boolean} trimEndSeparators | |
| 518 | * @returns {SimpleMarkdown.SingleNodeParseFunction} | |
| 519 | */ | |
| 520 | const parseTable = function (trimEndSeparators: boolean) { | |
| 521 | return function ( | |
| 522 | capture: RegExpMatchArray, | |
| 523 | parse: Parser, | |
| 524 | state: ParserState, | |
| 525 | ) { | |
| 526 | state.inline = true; | |
| 527 | const header = parseTableRow(capture[1], parse, state, trimEndSeparators); | |
| 528 | const align = parseTableAlign(capture[2], trimEndSeparators); | |
| 529 | const cells = parseTableCells( | |
| 530 | capture[3], | |
| 531 | parse, | |
| 532 | state, | |
| 533 | trimEndSeparators, | |
| 534 | ); | |
| 535 | state.inline = false; | |
| 536 | return { | |
| 537 | type: "table", | |
| 538 | header: header, | |
| 539 | align: align, | |
| 540 | cells: cells, | |
| 541 | }; | |
| 542 | }; | |
| 543 | }; | |
| 544 | ||
| 545 | return { | |
| 546 | parseTable: parseTable(true), | |
| 547 | parseNpTable: parseTable(false), | |
| 548 | TABLE_REGEX: /^ *(\|.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/, | |
| 549 | NPTABLE_REGEX: | |
| 550 | /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, | |
| 551 | }; | |
| 552 | })(); | |
| 553 | ||
| 554 | const LINK_INSIDE = "(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*"; | |
| 555 | const LINK_HREF_AND_TITLE = | |
| 556 | "\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*"; | |
| 557 | const AUTOLINK_MAILTO_CHECK_R = /mailto:/i; | |
| 558 | ||
| 559 | function parseRef( | |
| 560 | capture: RegExpMatchArray, | |
| 561 | state: ParserState, | |
| 562 | refNode: RefNode, | |
| 563 | ) { | |
| 564 | const ref = (capture[2] || capture[1]).replace(/\s+/g, " ").toLowerCase(); | |
| 565 | ||
| 566 | // We store information about previously seen defs on | |
| 567 | // state._defs (_ to deconflict with client-defined | |
| 568 | // state). If the def for this reflink/refimage has | |
| 569 | // already been seen, we can use its target/source | |
| 570 | // and title here: | |
| 571 | if (state._defs && state._defs[ref]) { | |
| 572 | const def = state._defs[ref]; | |
| 573 | ||
| 574 | // `refNode` can be a link or an image. Both use | |
| 575 | // target and title properties. | |
| 576 | refNode.target = def.target; | |
| 577 | refNode.title = def.title; | |
| 578 | } | |
| 579 | ||
| 580 | // In case we haven't seen our def yet (or if someone | |
| 581 | // overwrites that def later on), we add this node | |
| 582 | // to the list of ref nodes for that def. Then, when | |
| 583 | // we find the def, we can modify this link/image AST | |
| 584 | // node :). | |
| 585 | // I'm sorry. | |
| 586 | state._refs = state._refs || {}; | |
| 587 | state._refs[ref] = state._refs[ref] || []; | |
| 588 | ||
| 589 | state._refs[ref].push(refNode); | |
| 590 | ||
| 591 | return refNode; | |
| 592 | } | |
| 593 | ||
| 594 | export const defaultRules = new RuleList(); | |
| 595 | { | |
| 596 | defaultRules.add({ | |
| 597 | name: "heading", | |
| 598 | match: blockRegex(/^ *(#{1,6})([^\n]+?)#* *(?:\n *)+\n/), | |
| 599 | parse: function (capture, parse, state) { | |
| 600 | return { | |
| 601 | level: capture[1].length, | |
| 602 | content: parseInline(parse, capture[2].trim(), state), | |
| 603 | }; | |
| 604 | }, | |
| 605 | }); | |
| 606 | ||
| 607 | defaultRules.add({ | |
| 608 | name: "nptable", | |
| 609 | match: blockRegex(TABLES.NPTABLE_REGEX), | |
| 610 | parse: TABLES.parseNpTable, | |
| 611 | }); | |
| 612 | ||
| 613 | defaultRules.add({ | |
| 614 | name: "lheading", | |
| 615 | match: blockRegex(/^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/), | |
| 616 | parse(capture, parse, state) { | |
| 617 | return { | |
| 618 | type: "heading", | |
| 619 | level: capture[2] === "=" ? 1 : 2, | |
| 620 | content: parseInline(parse, capture[1], state), | |
| 621 | }; | |
| 622 | }, | |
| 623 | }); | |
| 624 | ||
| 625 | defaultRules.add({ | |
| 626 | name: "hr", | |
| 627 | match: blockRegex(/^( *[-*_]){3,} *(?:\n *)+\n/), | |
| 628 | parse: ignoreCapture, | |
| 629 | }); | |
| 630 | ||
| 631 | defaultRules.add({ | |
| 632 | name: "codeBlock", | |
| 633 | match: blockRegex(/^(?: {4}[^\n]+\n*)+(?:\n *)+\n/), | |
| 634 | parse(capture) { | |
| 635 | const content = capture[0].replace(/^ {4}/gm, "").replace(/\n+$/, ""); | |
| 636 | return { | |
| 637 | lang: undefined, | |
| 638 | content: content, | |
| 639 | }; | |
| 640 | }, | |
| 641 | }); | |
| 642 | ||
| 643 | defaultRules.add({ | |
| 644 | name: "fence", | |
| 645 | match: blockRegex( | |
| 646 | /^ *(`{3,}|~{3,}) *(?:(\S+) *)?\n([\s\S]+?)\n?\1 *(?:\n *)+\n/, | |
| 647 | ), | |
| 648 | parse(capture) { | |
| 649 | return { | |
| 650 | type: "codeBlock", | |
| 651 | lang: capture[2] || undefined, | |
| 652 | content: capture[3], | |
| 653 | }; | |
| 654 | }, | |
| 655 | }); | |
| 656 | ||
| 657 | defaultRules.add({ | |
| 658 | name: "blockQuote", | |
| 659 | match: blockRegex(/^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/), | |
| 660 | parse(capture, parse, state) { | |
| 661 | const content = capture[0].replace(/^ *> ?/gm, ""); | |
| 662 | return { | |
| 663 | content: parse(content, state), | |
| 664 | }; | |
| 665 | }, | |
| 666 | }); | |
| 667 | ||
| 668 | defaultRules.add({ | |
| 669 | name: "list", | |
| 670 | ||
| 671 | match(source, state) { | |
| 672 | // We only want to break into a list if we are at the start of a | |
| 673 | // line. This is to avoid parsing "hi * there" with "* there" | |
| 674 | // becoming a part of a list. | |
| 675 | // You might wonder, "but that's inline, so of course it wouldn't | |
| 676 | // start a list?". You would be correct! Except that some of our | |
| 677 | // lists can be inline, because they might be inside another list, | |
| 678 | // in which case we can parse with inline scope, but need to allow | |
| 679 | // nested lists inside this inline scope. | |
| 680 | const prevCaptureStr = state.prevCapture == null | |
| 681 | ? "" | |
| 682 | : state.prevCapture[0]; | |
| 683 | const isStartOfLineCapture = LIST_LOOKBEHIND_R.exec(prevCaptureStr); | |
| 684 | const isListBlock = state._list || !state.inline; | |
| 685 | ||
| 686 | if (isStartOfLineCapture && isListBlock) { | |
| 687 | source = isStartOfLineCapture[1] + source; | |
| 688 | return LIST_R.exec(source); | |
| 689 | } else { | |
| 690 | return null; | |
| 691 | } | |
| 692 | }, | |
| 693 | parse(capture, parse, state) { | |
| 694 | const bullet = capture[2]; | |
| 695 | const ordered = bullet.length > 1; | |
| 696 | const start = ordered ? +bullet : undefined; | |
| 697 | // We know this will match here, because of how the regexes are defined | |
| 698 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
| 699 | const items = capture[0].replace(LIST_BLOCK_END_R, "\n").match( | |
| 700 | LIST_ITEM_R, | |
| 701 | )!; | |
| 702 | ||
| 703 | let lastItemWasAParagraph = false; | |
| 704 | const itemContent = items.map(function (item, i) { | |
| 705 | // We need to see how far indented this item is: | |
| 706 | const prefixCapture = LIST_ITEM_PREFIX_R.exec(item); | |
| 707 | const space = prefixCapture ? prefixCapture[0].length : 0; // And then we construct a regex to "unindent" the subsequent | |
| 708 | // lines of the items by that amount: | |
| 709 | ||
| 710 | const spaceRegex = new RegExp("^ {1," + space + "}", "gm"); // Before processing the item, we need a couple things | |
| 711 | ||
| 712 | const content = item // remove indents on trailing lines: | |
| 713 | .replace(spaceRegex, "") // remove the bullet: | |
| 714 | .replace(LIST_ITEM_PREFIX_R, ""); // I'm not sur4 why this is necessary again? | |
| 715 | // Handling "loose" lists, like: | |
| 716 | // | |
| 717 | // * this is wrapped in a paragraph | |
| 718 | // | |
| 719 | // * as is this | |
| 720 | // | |
| 721 | // * as is this | |
| 722 | ||
| 723 | const isLastItem = i === items.length - 1; | |
| 724 | const containsBlocks = content.indexOf("\n\n") !== -1; // Any element in a list is a block if it contains multiple | |
| 725 | // newlines. The last element in the list can also be a block | |
| 726 | // if the previous item in the list was a block (this is | |
| 727 | // because non-last items in the list can end with \n\n, but | |
| 728 | // the last item can't, so we just "inherit" this property | |
| 729 | // from our previous element). | |
| 730 | ||
| 731 | const thisItemIsAParagraph = containsBlocks || | |
| 732 | (isLastItem && lastItemWasAParagraph); | |
| 733 | lastItemWasAParagraph = thisItemIsAParagraph; // backup our state for restoration afterwards. We're going to | |
| 734 | // want to set state._list to true, and state.inline depending | |
| 735 | // on our list's looseness. | |
| 736 | ||
| 737 | const oldStateInline = state.inline; | |
| 738 | const oldStateList = state._list; | |
| 739 | state._list = true; // Parse inline if we're in a tight list, or block if we're in | |
| 740 | // a loose list. | |
| 741 | ||
| 742 | let adjustedContent; | |
| 743 | ||
| 744 | if (thisItemIsAParagraph) { | |
| 745 | state.inline = false; | |
| 746 | adjustedContent = content.replace(LIST_ITEM_END_R, "\n\n"); | |
| 747 | } else { | |
| 748 | state.inline = true; | |
| 749 | adjustedContent = content.replace(LIST_ITEM_END_R, ""); | |
| 750 | } | |
| 751 | ||
| 752 | const result = parse(adjustedContent, state); // Restore our state before returning | |
| 753 | ||
| 754 | state.inline = oldStateInline; | |
| 755 | state._list = oldStateList; | |
| 756 | return result; | |
| 757 | }); | |
| 758 | ||
| 759 | return { | |
| 760 | ordered: ordered, | |
| 761 | start: start, | |
| 762 | content: itemContent, | |
| 763 | }; | |
| 764 | }, | |
| 765 | }); | |
| 766 | ||
| 767 | defaultRules.add({ | |
| 768 | name: "def", | |
| 769 | // TODO: This will match without a blank line before the next | |
| 770 | // block element, which is inconsistent with most of the rest of | |
| 771 | // simple-markdown. | |
| 772 | match: blockRegex( | |
| 773 | /^ *\[([^\]]+)\]: *<?([^\s>]*)>?(?: +["(]([^\n]+)[")])? *\n(?: *\n)*/, | |
| 774 | ), | |
| 775 | parse(capture, parse, state) { | |
| 776 | const def = capture[1].replace(/\s+/g, " ").toLowerCase(); | |
| 777 | const target = capture[2]; | |
| 778 | const title = capture[3]; | |
| 779 | ||
| 780 | // Look for previous links/images using this def | |
| 781 | // If any links/images using this def have already been declared, | |
| 782 | // they will have added themselves to the state._refs[def] list | |
| 783 | // (_ to deconflict with client-defined state). We look through | |
| 784 | // that list of reflinks for this def, and modify those AST nodes | |
| 785 | // with our newly found information now. | |
| 786 | // Sorry :(. | |
| 787 | if (state._refs && state._refs[def]) { | |
| 788 | // `refNode` can be a link or an image | |
| 789 | state._refs[def].forEach((refNode: RefNode) => { | |
| 790 | refNode.target = target; | |
| 791 | refNode.title = title; | |
| 792 | }); | |
| 793 | } | |
| 794 | ||
| 795 | // Add this def to our map of defs for any future links/images | |
| 796 | // In case we haven't found any or all of the refs referring to | |
| 797 | // this def yet, we add our def to the table of known defs, so | |
| 798 | // that future reflinks can modify themselves appropriately with | |
| 799 | // this information. | |
| 800 | state._defs = state._defs || {}; | |
| 801 | state._defs[def] = { | |
| 802 | target: target, | |
| 803 | title: title, | |
| 804 | }; | |
| 805 | ||
| 806 | // return the relevant parsed information | |
| 807 | // for debugging only. | |
| 808 | return { | |
| 809 | def: def, | |
| 810 | target: target, | |
| 811 | title: title, | |
| 812 | }; | |
| 813 | }, | |
| 814 | }); | |
| 815 | ||
| 816 | defaultRules.add({ | |
| 817 | name: "table", | |
| 818 | match: blockRegex(TABLES.TABLE_REGEX), | |
| 819 | parse: TABLES.parseTable, | |
| 820 | }); | |
| 821 | ||
| 822 | defaultRules.add({ | |
| 823 | name: "newline", | |
| 824 | match: blockRegex(/^(?:\n *)*\n/), | |
| 825 | parse: ignoreCapture, | |
| 826 | }); | |
| 827 | ||
| 828 | defaultRules.add({ | |
| 829 | name: "paragraph", | |
| 830 | match: blockRegex(/^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/), | |
| 831 | parse: parseCaptureInline, | |
| 832 | }); | |
| 833 | ||
| 834 | defaultRules.add({ | |
| 835 | name: "escape", | |
| 836 | // We don't allow escaping numbers, letters, or spaces here so that | |
| 837 | // backslashes used in plain text still get rendered. But allowing | |
| 838 | // escaping anything else provides a very flexible escape mechanism, | |
| 839 | // regardless of how this grammar is extended. | |
| 840 | match: inlineRegex(/^\\([^0-9A-Za-z\s])/), | |
| 841 | parse(capture) { | |
| 842 | return { | |
| 843 | type: "text", | |
| 844 | content: capture[1], | |
| 845 | }; | |
| 846 | }, | |
| 847 | }); | |
| 848 | ||
| 849 | defaultRules.add({ | |
| 850 | name: "tableSeparator", | |
| 851 | ||
| 852 | match(source, state) { | |
| 853 | if (!state.inTable) { | |
| 854 | return null; | |
| 855 | } | |
| 856 | ||
| 857 | return /^ *\| */.exec(source); | |
| 858 | }, | |
| 859 | parse() { | |
| 860 | return { | |
| 861 | type: "tableSeparator", | |
| 862 | }; | |
| 863 | }, | |
| 864 | }); | |
| 865 | ||
| 866 | defaultRules.add({ | |
| 867 | name: "autolink", | |
| 868 | match: inlineRegex(/^<([^: >]+:\/[^ >]+)>/), | |
| 869 | parse(capture) { | |
| 870 | return { | |
| 871 | type: "link", | |
| 872 | content: [ | |
| 873 | { | |
| 874 | type: "text", | |
| 875 | content: capture[1], | |
| 876 | }, | |
| 877 | ], | |
| 878 | target: capture[1], | |
| 879 | }; | |
| 880 | }, | |
| 881 | }); | |
| 882 | ||
| 883 | defaultRules.add({ | |
| 884 | name: "mailto", | |
| 885 | match: inlineRegex(/^<([^ >]+@[^ >]+)>/), | |
| 886 | parse(capture) { | |
| 887 | const address = capture[1]; | |
| 888 | let target = capture[1]; // Check for a `mailto:` already existing in the link: | |
| 889 | ||
| 890 | if (!AUTOLINK_MAILTO_CHECK_R.test(target)) { | |
| 891 | target = "mailto:" + target; | |
| 892 | } | |
| 893 | ||
| 894 | return { | |
| 895 | type: "link", | |
| 896 | content: [ | |
| 897 | { | |
| 898 | type: "text", | |
| 899 | content: address, | |
| 900 | }, | |
| 901 | ], | |
| 902 | target: target, | |
| 903 | }; | |
| 904 | }, | |
| 905 | }); | |
| 906 | ||
| 907 | defaultRules.add({ | |
| 908 | name: "url", | |
| 909 | match: inlineRegex(/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/), | |
| 910 | parse(capture) { | |
| 911 | return { | |
| 912 | type: "link", | |
| 913 | content: [ | |
| 914 | { | |
| 915 | type: "text", | |
| 916 | content: capture[1], | |
| 917 | }, | |
| 918 | ], | |
| 919 | target: capture[1], | |
| 920 | title: undefined, | |
| 921 | }; | |
| 922 | }, | |
| 923 | }); | |
| 924 | ||
| 925 | defaultRules.add({ | |
| 926 | name: "link", | |
| 927 | match: inlineRegex( | |
| 928 | new RegExp( | |
| 929 | "^\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)", | |
| 930 | ), | |
| 931 | ), | |
| 932 | parse(capture, parse, state) { | |
| 933 | const link = { | |
| 934 | content: parse(capture[1], state), | |
| 935 | target: unescapeUrl(capture[2]), | |
| 936 | title: capture[3], | |
| 937 | }; | |
| 938 | return link; | |
| 939 | }, | |
| 940 | }); | |
| 941 | ||
| 942 | defaultRules.add({ | |
| 943 | name: "image", | |
| 944 | match: inlineRegex( | |
| 945 | new RegExp( | |
| 946 | "^!\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)", | |
| 947 | ), | |
| 948 | ), | |
| 949 | parse: function (capture) { | |
| 950 | const image = { | |
| 951 | alt: capture[1], | |
| 952 | target: unescapeUrl(capture[2]), | |
| 953 | title: capture[3], | |
| 954 | }; | |
| 955 | return image; | |
| 956 | }, | |
| 957 | }); | |
| 958 | ||
| 959 | defaultRules.add({ | |
| 960 | name: "reflink", | |
| 961 | match: inlineRegex( | |
| 962 | new RegExp( | |
| 963 | // The first [part] of the link | |
| 964 | "^\\[(" + | |
| 965 | LINK_INSIDE + | |
| 966 | ")\\]" + // The [ref] target of the link | |
| 967 | "\\s*\\[([^\\]]*)\\]", | |
| 968 | ), | |
| 969 | ), | |
| 970 | parse(capture, parse, state) { | |
| 971 | return parseRef(capture, state, { | |
| 972 | type: "link", | |
| 973 | content: parse(capture[1], state), | |
| 974 | }); | |
| 975 | }, | |
| 976 | }); | |
| 977 | ||
| 978 | defaultRules.add({ | |
| 979 | name: "refimage", | |
| 980 | match: inlineRegex( | |
| 981 | new RegExp( | |
| 982 | // The first [part] of the link | |
| 983 | "^!\\[(" + | |
| 984 | LINK_INSIDE + | |
| 985 | ")\\]" + // The [ref] target of the link | |
| 986 | "\\s*\\[([^\\]]*)\\]", | |
| 987 | ), | |
| 988 | ), | |
| 989 | parse(capture, parse, state) { | |
| 990 | return parseRef(capture, state, { | |
| 991 | type: "image", | |
| 992 | alt: capture[1], | |
| 993 | }); | |
| 994 | }, | |
| 995 | }); | |
| 996 | ||
| 997 | defaultRules.add({ | |
| 998 | name: "em", | |
| 999 | /* same as strong/u */ | |
| 1000 | match: inlineRegex( | |
| 1001 | new RegExp( | |
| 1002 | // only match _s surrounding words. | |
| 1003 | "^\\b_" + | |
| 1004 | "((?:__|\\\\[\\s\\S]|[^\\\\_])+?)_" + | |
| 1005 | "\\b" + // Or match *s: | |
| 1006 | "|" + // Only match *s that are followed by a non-space: | |
| 1007 | "^\\*(?=\\S)(" + // Match at least one of: | |
| 1008 | "(?:" + // - `**`: so that bolds inside italics don't close the | |
| 1009 | // italics | |
| 1010 | "\\*\\*|" + // - escape sequence: so escaped *s don't close us | |
| 1011 | "\\\\[\\s\\S]|" + // - whitespace: followed by a non-* (we don't | |
| 1012 | // want ' *' to close an italics--it might | |
| 1013 | // start a list) | |
| 1014 | "\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|" + // - non-whitespace, non-*, non-backslash characters | |
| 1015 | "[^\\s\\*\\\\]" + | |
| 1016 | ")+?" + // followed by a non-space, non-* then * | |
| 1017 | ")\\*(?!\\*)", | |
| 1018 | ), | |
| 1019 | ), | |
| 1020 | quality(capture) { | |
| 1021 | // precedence by length, `em` wins ties: | |
| 1022 | return capture[0].length + 0.2; | |
| 1023 | }, | |
| 1024 | parse(capture, parse, state) { | |
| 1025 | return { | |
| 1026 | content: parse(capture[2] || capture[1], state), | |
| 1027 | }; | |
| 1028 | }, | |
| 1029 | }); | |
| 1030 | ||
| 1031 | defaultRules.add({ | |
| 1032 | name: "strong", | |
| 1033 | /* same as em */ | |
| 1034 | match: inlineRegex(/^\*\*((?:\\[\s\S]|[^\\])+?)\*\*(?!\*)/), | |
| 1035 | quality(capture) { | |
| 1036 | // precedence by length, wins ties vs `u`: | |
| 1037 | return capture[0].length + 0.1; | |
| 1038 | }, | |
| 1039 | parse: parseCaptureInline, | |
| 1040 | }); | |
| 1041 | ||
| 1042 | defaultRules.add({ | |
| 1043 | name: "u", | |
| 1044 | /* same as em&strong; increment for next rule */ | |
| 1045 | match: inlineRegex(/^__((?:\\[\s\S]|[^\\])+?)__(?!_)/), | |
| 1046 | quality(capture) { | |
| 1047 | // precedence by length, loses all ties | |
| 1048 | return capture[0].length; | |
| 1049 | }, | |
| 1050 | parse: parseCaptureInline, | |
| 1051 | }); | |
| 1052 | ||
| 1053 | defaultRules.add({ | |
| 1054 | name: "del", | |
| 1055 | match: inlineRegex(/^~~(?=\S)((?:\\[\s\S]|~(?!~)|[^\s~\\]|\s(?!~~))+?)~~/), | |
| 1056 | parse: parseCaptureInline, | |
| 1057 | }); | |
| 1058 | ||
| 1059 | defaultRules.add({ | |
| 1060 | name: "inlineCode", | |
| 1061 | match: inlineRegex(/^(`+)([\s\S]*?[^`])\1(?!`)/), | |
| 1062 | parse(capture) { | |
| 1063 | return { | |
| 1064 | content: capture[2].replace(INLINE_CODE_ESCAPE_BACKTICKS_R, "$1"), | |
| 1065 | }; | |
| 1066 | }, | |
| 1067 | }); | |
| 1068 | ||
| 1069 | defaultRules.add({ | |
| 1070 | name: "br", | |
| 1071 | match: anyScopeRegex(/^ {2,}\n/), | |
| 1072 | parse: ignoreCapture, | |
| 1073 | }); | |
| 1074 | ||
| 1075 | defaultRules.add({ | |
| 1076 | name: "text", | |
| 1077 | // Here we look for anything followed by non-symbols, | |
| 1078 | // double newlines, or double-space-newlines | |
| 1079 | // We break on any symbol characters so that this grammar | |
| 1080 | // is easy to extend without needing to modify this regex | |
| 1081 | match: anyScopeRegex( | |
| 1082 | /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/, | |
| 1083 | ), | |
| 1084 | parse: function (capture) { | |
| 1085 | return { | |
| 1086 | content: capture[0], | |
| 1087 | }; | |
| 1088 | }, | |
| 1089 | }); | |
| 1090 | } |
src/q+a/tags/header.client.ts created+12| ... | ... | @@ -0,0 +1,12 @@ |
| 1 | const h1 = document.querySelector("h1")!; | |
| 2 | const key = "net.paperclover.q+a.header"; | |
| 3 | const state = localStorage?.getItem(key); | |
| 4 | if (state === "detrevni ton") h1.classList.toggle("invert"); | |
| 5 | h1.addEventListener("click", () => { | |
| 6 | h1.classList.toggle("invert"); | |
| 7 | localStorage?.setItem( | |
| 8 | key, | |
| 9 | (localStorage.getItem?.(key) ?? "not inverted") | |
| 10 | .split("").reverse().join(""), | |
| 11 | ); | |
| 12 | }); |
src/q+a/tags/question-layout.marko created+22| ... | ... | @@ -0,0 +1,22 @@ |
| 1 | import "../questions.css"; | |
| 2 | client import "./header.client.ts"; | |
| 3 | ||
| 4 | <main class="qa"> | |
| 5 | <if=!input.minimal> | |
| 6 | <h1> | |
| 7 | <span class="a" style="color:var(--a);--s:#19607d">answers</span> | |
| 8 | <span class="n" style="--s:#999;margin:0 5px">&</span> | |
| 9 | <span class="q" style="color:var(--b);--s:#994253">questions</span> | |
| 10 | </h1> | |
| 11 | </> | |
| 12 | <${input.content} /> | |
| 13 | </main> | |
| 14 | ||
| 15 | export const theme = { | |
| 16 | fg: "#f0f0f0", | |
| 17 | bg: "#1a1b1c", | |
| 18 | primary: "#f5a9b8", | |
| 19 | }; | |
| 20 | ||
| 21 | import { addScript } from "#sitegen"; | |
| 22 | import * as render from "lib/render.ts"; |
src/q+a/tags/question.marko+3-3| ... | ... | @@ -8,7 +8,7 @@ export interface Input { |
| 8 | 8 | } |
| 9 | 9 | |
| 10 | 10 | // 2024-12-31 05:00:00 EST |
| 11 | export const transitionDate = 1735639200000; | |
| 11 | export const transitionDate = 1735639200000; | |
| 12 | 12 | |
| 13 | 13 | <const/{ question, admin = false, editor = false } = input /> |
| 14 | 14 | <const/{ id, date } = question/> |
| ... | ... | @@ -29,7 +29,7 @@ export const transitionDate = 1735639200000; |
| 29 | 29 | >${formatQuestionTimestamp(date)}</time> |
| 30 | 30 | </a> |
| 31 | 31 | |
| 32 | <CloverMarkdown text=question.text /> | |
| 32 | $!{markdown.renderMarkdown(question.text)} | |
| 33 | 33 | </> |
| 34 | 34 | |
| 35 | 35 | // this singleton script will make all the '<time>' tags clickable. |
| ... | ... | @@ -39,5 +39,5 @@ export const transitionDate = 1735639200000; |
| 39 | 39 | |
| 40 | 40 | import type { Question } from "@/q+a/models/Question.ts"; |
| 41 | 41 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; |
| 42 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; | |
| 42 | import * as markdown from "@/q+a/markdown.tsx"; | |
| 43 | 43 | import { addScript as Script } from "#sitegen" |
src/q+a/views/backend-inbox.marko+3-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | export { minimal as layout } from "../layout.tsx"; | |
| 1 | export { theme } from "../tags/question-layout.marko"; | |
| 2 | 2 | import { PendingQuestion } from "@/q+a/models/PendingQuestion.ts"; |
| 3 | 3 | import { |
| 4 | 4 | formatQuestionISOTimestamp, |
| ... | ... | @@ -8,6 +8,7 @@ export const meta = { title: 'question answer inbox' }; |
| 8 | 8 | |
| 9 | 9 | <const/questions = PendingQuestion.getAll() /> |
| 10 | 10 | |
| 11 | <question-layout minimal> | |
| 11 | 12 | <h1>inbox</h1> |
| 12 | 13 | <for|q| of=questions> |
| 13 | 14 | <div |
| ... | ... | @@ -36,5 +37,6 @@ export const meta = { title: 'question answer inbox' }; |
| 36 | 37 | </p> |
| 37 | 38 | </div> |
| 38 | 39 | </> |
| 40 | </question-layout> | |
| 39 | 41 | |
| 40 | 42 | client import "./backend-inbox.client.ts"; |
src/q+a/views/editor.marko+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import "./editor.css"; |
| 2 | export { theme } from "@/q+a/layout.tsx"; | |
| 2 | export { theme } from "../tags/question-layout.marko"; | |
| 3 | 3 | |
| 4 | 4 | export const meta = { title: "question editor" }; |
| 5 | 5 |
src/q+a/views/fail.marko+3-2| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | import * as layout from "../layout.tsx"; | |
| 2 | export { layout }; | |
| 1 | import * as layout from "../tags/question-layout.marko"; | |
| 3 | 2 | export const theme = { |
| 4 | 3 | ...layout.theme, |
| 5 | 4 | primary: "#58ffee", |
| ... | ... | @@ -8,6 +7,7 @@ export const meta = { title: 'oh no' }; |
| 8 | 7 | |
| 9 | 8 | <const/{ error, content }=input/> |
| 10 | 9 | |
| 10 | <question-layout> | |
| 11 | 11 | <h2 style="color: red">:(</h2> |
| 12 | 12 | <p style="color: red"> |
| 13 | 13 | ${error} |
| ... | ... | @@ -25,3 +25,4 @@ export const meta = { title: 'oh no' }; |
| 25 | 25 | <p> |
| 26 | 26 | <a href="/q+a">return to the questions list</a> |
| 27 | 27 | </p> |
| 28 | </question-layout> |
src/q+a/views/permalink.marko+4-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | export * as layout from "@/q+a/layout.tsx"; | |
| 1 | export { theme } from "../tags/question-layout.marko"; | |
| 2 | 2 | export interface Input { |
| 3 | 3 | question: Question; |
| 4 | 4 | } |
| ... | ... | @@ -25,6 +25,8 @@ server export function meta({ context, question }) { |
| 25 | 25 | |
| 26 | 26 | <const/{ question }=input/> |
| 27 | 27 | <const/{ type }=question/> |
| 28 | ||
| 29 | <question-layout> | |
| 28 | 30 | <if=type==QuestionType.normal> |
| 29 | 31 | <p>this page is a permalink to the following question:</p> |
| 30 | 32 | <question ...{question} /> |
| ... | ... | @@ -48,6 +50,7 @@ server export function meta({ context, question }) { |
| 48 | 50 | </> |
| 49 | 51 | |
| 50 | 52 | <a href="/q+a">view more questions</a> |
| 53 | </question-layout> | |
| 51 | 54 | |
| 52 | 55 | import { Question, QuestionType } from '@/q+a/models/Question.ts'; |
| 53 | 56 | import { transitionDate } from "@/q+a/tags/question.marko"; |
src/q+a/views/success.marko+3-3| ... | ... | @@ -5,6 +5,7 @@ export const meta = { title: 'question submitted!!!' }; |
| 5 | 5 | |
| 6 | 6 | <const/{ permalink }=input/> |
| 7 | 7 | |
| 8 | <question-layout> | |
| 8 | 9 | <h2 style="color: #8c78ff; margin-bottom: 0">thank you</h2> |
| 9 | 10 | <p style="color: #8c78ff"> |
| 10 | 11 | your question has been submitted. |
| ... | ... | @@ -23,11 +24,10 @@ export const meta = { title: 'question submitted!!!' }; |
| 23 | 24 | <p> |
| 24 | 25 | <a href="/q+a">return to the questions list</a> |
| 25 | 26 | </p> |
| 27 | </question-layout> | |
| 26 | 28 | |
| 27 | import * as layout from "../layout.tsx"; | |
| 28 | export { layout }; | |
| 29 | import * as layout from "../tags/question-layout.marko"; | |
| 29 | 30 | export const theme = { |
| 30 | 31 | ...layout.theme, |
| 31 | 32 | primary: "#58ffee", |
| 32 | 33 | }; |
| 33 |
tsconfig.json+2-1| ... | ... | @@ -16,7 +16,8 @@ |
| 16 | 16 | "verbatimModuleSyntax": true, |
| 17 | 17 | "target": "esnext", |
| 18 | 18 | "noUncheckedIndexedAccess": true, |
| 19 | "resolveJsonModule": true | |
| 19 | "resolveJsonModule": true, | |
| 20 | "types": ["marko"] | |
| 20 | 21 | }, |
| 21 | 22 | "include": ["lib/**/*", "src/**/*", "framework/**/*"] |
| 22 | 23 | } |