Moves <!-- @include: --> expansion out of markdownToVue and the local
search plugin into an include plugin registered on the renderer, which
wraps renderAsync and expands includes whenever the env carries a file
path. createMarkdownRenderer users and createContentLoader with render
or excerpt enabled now get includes expanded too (previously the
directives silently came through unexpanded), and the expansion is
reachable from the public API without exporting the helper (#4838).
Region selection uses the region engine (all same-named regions
concatenate, matched across comment styles), frontmatter of markdown
files is stripped before locating regions and headings so both share
one coordinate space, and the region suffix is anchored to the end of
the include path, so paths containing # now work.
Files reached by a failed render are still reported, so that creating
a missing snippet or include recovers the page instead of requiring an
edit of the including file.
BREAKING CHANGES:
- a missing region or heading anchor now throws instead of silently
including the whole file, and out-of-bounds or inverted ranges throw
instead of clamping; markdown.include.silent logs a warning and
expands to nothing instead
- markdown.include is a new option; false disables include processing
- custom search _render functions receive the raw source (sync
md.render does not expand includes; use renderAsync)
- the include-expanded source is exposed as env.src
Co-authored-by: Miroma <its.miroma@proton.me>
Co-authored-by: Naloam <110604855+Naloam@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the single-regex raw path parsing with a structural parser
that peels [title], {meta} and #region off the end, so meta can no
longer be silently swallowed into the file path. Inside the braces,
everything after the language is now passed through to the fence info
verbatim, allowing multiple attributes and quoted values (e.g.
{ts twoslash key="a b"}). Extensions are derived from the file name,
so dotfiles resolve, region references may contain dots, and {1, 2}
style spacing is tolerated.
Region extraction uses the new region engine, so all same-named
regions are concatenated in document order, and which marker lines are
removed from the output is configurable through stripRegionMarkers.
BREAKING CHANGES:
- a missing snippet file or region now throws instead of rendering an
error message inside the code block (or importing the whole file, in
the region case); the silent option restores non-fatal behavior by
logging a warning and rendering nothing
- the language inferred from a file name now includes uppercase
extensions, which were previously matched as [a-z0-9]+ only, so a
snippet of e.g. Foo.TS is highlighted where it used to render
without a language; suffixes that are not alphanumeric, like .c++ or
.code-snippets, are still not inferred and need the language given
in the braces
- markdown.snippet now accepts an options object in addition to the
boolean toggle
- rawPathToToken, findRegion and dedent are no longer exported from
the plugin module
Co-authored-by: Miroma <its.miroma@proton.me>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds readTextFile/readTextFileSync (CRLF/CR -> LF) next to the raw
retrying readFile and switches the markdown-source readers to them
(content loader, dynamic route templates, local search, includes).
Raw reads stay in place where bytes must be preserved (serve, init
scaffolding).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shared engine for VS Code-style region extraction, used by the snippet
and include rewrites. Compared to findRegion in the snippet plugin, it
returns all same-named regions in document order and matches marker
styles per line instead of locking onto the first style found, so
same-named regions in mixed-comment files (e.g. Vue SFCs) merge. All
open regions are tracked, and an end marker without a name closes the
innermost region opened in its own comment style, so it neither gets
captured by a region of another language nested inside it nor closes
one that was left open there.
The marker regexes follow the folding.markers definitions VS Code ships
per language, which corrects two of the previous ones: REM is
case-insensitive in bat, and #pragma allows a space after the hash.
Those definitions disagree about whether the hash is required - the
marker makes it optional for js and markdown, while html, css, sql, bat
and f# require it - so related comment styles are merged into one regex
each, keeping the hash optional only where at least one of the merged
languages makes it optional. Since a markdown renderer cannot know the
language of an imported file, all styles are tried on every file, which
makes this a superset of what an editor folds. Note that a language
service can disagree with the marker its language ships: TypeScript
requires the hash the ts/js marker makes optional, while the Lua one
accepts it hash-less where we require it. Fixtures covering the marker
forms per language live in brc-dd/region-marker-fixtures.
Quoted Visual Basic region names are matched without their quotes, and
region markers declared as JSON keys are recognized as an extension,
plain JSON having no comments to put a marker in.
Co-authored-by: Miroma <its.miroma@proton.me>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
add maxSize of 64MB to the markdown compile result LRUCache, such that
large pages do not grow this cache unboundedly.
change the cache key from JSON.stringify({ src, ts, relativePath })
(which not only included the entire source of each file in each key, but
also instantiated an object and called JSON.stringify), to a simpler
string concatenation with the sha256 hash of src.
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
Based-on-patch-by: Calum H. (IMB11) <contact@cal.engineer>
previously, cli.ts was injecting onAfterConfigResolve solely to print
the version banner. Now, build() itself will log the version after
resolving the config, unless the caller has supplied a different
onAfterConfigResolve.
BREAKING CHANGE: callers of vitepress' build() will now notice the
version banner gets printed. To disable that, pass a noop function:
```ts
build(root, {
onAfterConfigResolve() {},
})
```
gray-matter memoizes every parsed result in a plain cache object keyed
by the full input string, except when it is passed an options object.
310f934938/index.js (L44-L47)
such cache is never bounded, or cleared, so on large sites memory usage
skyrockets.
with this change, we are always passing an empty options object on every
call to matter() to bypass its internal cache.
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
Original-patch-by: Calum H. (IMB11) <contact@cal.engineer>
bundle() and generateSitemap() are plain workers now; build() wraps
them in tasks alongside render. Keeps the MPA asset copies and the
MPA client build under the bundles spinner instead of running after
it resolves, and skips the sitemap spinner when sitemap generation
is off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expose the any-typed highlight escape hatch on the exported
MarkdownRenderer type and accept it in the preConfig/config hooks,
instead of special-casing only the internal singleton.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default codepoint sort matches the pages sort below and is not
locale/ICU dependent; route order is only consumed by exact-match
lookups, so ordering stays unobservable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the rendering pages body into a standalone render() invoked
via bind, taking the bundle output whole. The SSR entry import now
fails under the rendering spinner, and the hashmap.json write runs
inside the task alongside the other rendering outputs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoist the groups[].name body out of resolveViteConfig into a
module-level function partially applied with themeEntryRE via bind,
instead of re-creating a closure over bundle() locals per config.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the remaining sync writes and the tempDir removal in
build()/generateMetadataScript() with fs/promises, and switch
linkVue to the async packageDirectory lookup with an async unlink.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lets bundle() receive the build outputs from the spinner-wrapped
callback directly instead of smuggling them through outer let
bindings with a definite-assignment assertion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Store the promise of the extracted loadData() directly in the
per-id map rather than resolving a hand-made pending promise at the
end of the load hook. This also propagates loader errors to the
other build's pending consumer, which previously awaited forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse the duplicated listen branches and await the listening
event; serve() now also rejects on listen errors like EADDRINUSE
instead of crashing in the callback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Iterate the log parser with for-await instead of data/end/error
callbacks (spawn errors are forwarded by destroying the stream), and
await the single-file lookup with events.once, which rejects on
child error by itself.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pipeline() replaces the manual finish/error promise and also
surfaces SitemapStream errors, which pipe() never forwarded to the
write stream.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
once() also rejects if the child emits error first, so the manual
promise wiring is unnecessary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Content, include, route-template and search reads now run
concurrently, so large sites can hit EMFILE/ENFILE. Route all async
reads through a shared readFile util that retries with backoff.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expanding an include that is its own ancestor recursed forever.
Track the ancestor chain and leave such includes unexpanded; the
same file can still be included repeatedly as a sibling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve includes through replaceAsync and fs/promises so include
files are read without blocking the event loop while pages render
concurrently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the existsSync pre-check in favor of catching ENOENT from the
read itself, and skip indexing when a page renders to nothing (file
gone or excluded via search: false) instead of feeding empty html to
the section splitter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split the block parser and fence renderer into factories, extract
region extraction, and type the snippet token instead of ts-ignoring
its src field.
Reading the file now handles ENOENT/EISDIR explicitly: a missing
snippet previously threw from statSync before the 'path not found'
message could render, so that branch was unreachable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It is internal and its only caller passes every argument; apply the
fallbacks at the call site instead of hiding them in the signature
after the required siteConfig parameter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read the 404 page with fs/promises, replace the trimChar helper with
a regex, and flatten the if/else around listen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swap the remaining sync fs calls in renderPage for fs/promises,
switch head-script minification to the async minify API, and make
resolvePageImports async for the realpath call. Pages render under
buildConcurrency, so blocking calls here stall the whole batch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createContentLoader already maps files with bounded concurrency, but
the sync stat/read calls serialized the actual I/O.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A fixed ui-custom name still gives adblock filter lists a stable
string to match on. Emit the Carbon/BuySellAds chunks with just the
content hash instead. Changes the emitted filenames for those chunks
and, transitively, the hashes of chunks that import them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Now that dev updates actually reach the index, any changed markdown
file was being ingested — including srcExclude'd include partials and
dynamic route templates, which are not pages and produced dead search
results. Mirror the initial scan and only index files present in
siteConfig.pages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createContentLoader called md.renderAsync without a MarkdownEnv, so
the internal link plugin fell back to defaults and always emitted
.html links in rendered content and excerpts even with cleanUrls
enabled, and plugins reading env.path/relativePath broke. Build the
same env as a normal page render and forward it to both calls.
fixes#4331closes#5299
Co-authored-by: TowyTowy <towy@airreps.link>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-indexing only invalidated the bare @localSearchIndex module; the
per-locale modules that carry the data were still served from the
transform cache, and the browser module cache pinned them too. Version
the locale import in dev so every index update loads fresh data.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewrites were computed from the locally globbed pages only, so pages
injected via the config were present in siteConfig.pages but never
entered the rewrites map.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
importerMap values were srcDir-relative paths computed with
path.posix.relative, which garbles on Windows drive-letter case
mismatches, and the delete cleanup used a relative key against the
absolute-keyed map so it never removed anything. Store module ids
instead, derive the cache key with a win32-aware relative, and prune
both the include entry and page references after invalidating.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dead link check compares resolved links against source-space page
paths, so a link to the original location of a rewritten page passed
the check but 404s in the built site.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discovered config files are keyed by their source directory, but the
lookup walked the rewrite-applied relativePath, so directories covered
by rewrites silently lost their additional config. Resolve against
pageData.filePath on both server and client, which also makes dynamic
route pages inherit configs from their route template's directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
URLs were derived from the source path only, so loader-generated links
pointed at the pre-rewrite location and 404ed for rewritten pages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
indexFile expects a srcDir-relative page and joins it with srcDir, but
hotUpdate passed the absolute path, producing a doubled path that
failed the existence check — so edits never updated the local search
index. Regression from #3374.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The markdownToVue compile cache is keyed by the rewrite-applied
relativePath, but include-triggered invalidation passed the source
path, so pages with rewrites served stale content in dev when an
included file changed.
closes#5035
Co-authored-by: yyznm <yyz_nm@163.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>