Package Exports
- remark-code-region
- remark-code-region/docusaurus
- remark-code-region/shiki
- remark-code-region/starlight
Readme
remark-code-region
Every code block in your docs is extracted from a passing test.
Pull tested code straight from your test suite into your Docusaurus, Astro, or any remark-powered docs. Named regions, automatic test-line stripping, hard build failures on drift. Drop-in replacement for remark-code-import with full file= compatibility.
The problem
Code examples in documentation go stale. You update an API, the tests pass, but the docs still show the old usage. Users copy broken code. Nobody notices until someone files an issue.
The solution
Write your examples as real, executable tests. Mark regions with comments. Reference them from your docs. The plugin injects the tested code at build time.
┌──────────────────────────┐
│ tests/test_sdk.py │
│ │
│ # region: create_user │
│ user = sdk.create(...) │ <- Runs in CI
│ assert user.id │
│ # endregion │
└────────────┬─────────────┘
│
build time
│
┌────────────▼─────────────┐
│ docs/quickstart.md │
│ │
│ ```python │
│ user = sdk.create(...) │ <- Injected (asserts stripped)
│ ``` │
└──────────────────────────┘If the test breaks, CI fails. If the region moves, the build fails. Stale docs become impossible.
Quick start
Code fences without reference or file= are untouched -- migrate one block at a time.
Install:
npm install remark-code-regionConfigure (Docusaurus):
// docusaurus.config.js
const codeRegion = require('remark-code-region');
module.exports = {
presets: [['classic', {
docs: {
remarkPlugins: [codeRegion],
},
}]],
};Add region markers to your test file:
# tests/test_users.py
def test_create_user():
# region: create_user
from myapp import client
user = client.create_user(name="Alice", role="admin")
print(user)
# {"id": "usr_123", "name": "Alice", "role": "admin"}
# endregion: create_user
assert user["name"] == "Alice"
assert user["role"] == "admin"Reference it from your docs:
```python reference="tests/test_users.py#create_user"
```At build time, the code fence is replaced with:
from myapp import client
user = client.create_user(name="Alice", role="admin")
print(user)
# {"id": "usr_123", "name": "Alice", "role": "admin"}The assert lines are automatically stripped. The region markers are removed. Clean, tested code in your docs.
Two syntaxes
remark-code-region supports two directive syntaxes. Both go through the same pipeline.
reference= (recommended)
Paths resolve relative to rootDir (defaults to process.cwd()). The directive is stripped from meta after processing.
```python reference="tests/test_users.py#create_user"
```file= (remark-code-import compatible)
Paths resolve relative to the markdown file. Drop-in replacement for remark-code-import -- all existing file= references work unchanged.
```python file=./tests/test_users.py#create_user
```Use <rootDir> to resolve relative to rootDir instead:
```python file=<rootDir>/tests/test_users.py#create_user
```Both syntaxes support named regions (#region_name), line ranges (#L3-L6), and query flags (?noStrip).
Multi-region concatenation
Pull multiple regions from the same file into one code block:
```python reference="tests/test_users.py#imports,create_user"
```Regions are joined with a blank line separator (configurable via regionSeparator). Mix regions and line ranges:
```python reference="tests/test_users.py#imports,L25-L30,create_user"
```Whitespace around commas is trimmed. If any region is missing, the build fails.
Region markers
Use comment-style markers that match your language:
# region: name
code here
# endregion: name// region: name
code here
// endregion: nameSupported out of the box (no configuration needed):
| Comment style | Languages |
|---|---|
# region: / # endregion: |
Python, bash, Ruby, YAML, TOML |
// region: / // endregion: |
JavaScript, TypeScript, Rust, Go, Java, C, C++, Swift, Kotlin |
Adding more languages
For languages that use different comment syntax, add marker presets via the regionMarkers option. Built-in presets are available for CSS, HTML, and SQL:
const codeRegion = require('remark-code-region');
const { PRESET_MARKERS } = codeRegion;
remarkPlugins: [[codeRegion, {
regionMarkers: [
// Keep the defaults (# and // comments)
...codeRegion.DEFAULT_REGION_MARKERS,
// Add CSS: /* region: name */ ... /* endregion: name */
PRESET_MARKERS.css,
// Add HTML: <!-- region: name --> ... <!-- endregion: name -->
PRESET_MARKERS.html,
// Add SQL/Lua: -- region: name ... -- endregion: name
PRESET_MARKERS.sql,
],
}]],Custom markers
For any other comment style, define your own {start, end} regex pair. Group 1 must capture the region name:
regionMarkers: [
...codeRegion.DEFAULT_REGION_MARKERS,
{
// Erlang/Elixir: %% region: name
start: /^[ \t]*%%\s*region:\s*(\S+)\s*$/,
end: /^[ \t]*%%\s*endregion:\s*(\S+)\s*$/,
},
],Automatic test-line stripping
These patterns are removed from injected code by default:
| Preset | Pattern | Language |
|---|---|---|
python |
assert ... |
Python |
rust |
assert_eq!(), assert_ne!() |
Rust |
java |
assertEquals(), assertTrue(), etc. |
Java/JUnit |
js |
expect() |
JS (Jest/Vitest) |
cpp |
ASSERT_*, EXPECT_* |
C++ (gtest) |
go |
if err != nil { t.Fatal |
Go |
markers |
Lines ending with // test-only or # test-only |
Any |
To keep assertions visible in a specific block, append ?noStrip:
```python reference="tests/test_users.py#create_user?noStrip"
```Customizing strip patterns
Use PRESET_STRIP to compose exactly what you need:
const codeRegion = require('remark-code-region');
const { PRESET_STRIP } = codeRegion;
remarkPlugins: [[codeRegion, {
strip: [...PRESET_STRIP.python, ...PRESET_STRIP.js, ...PRESET_STRIP.markers],
}]],Available presets: python, rust, java, js, cpp, go, markers.
Or disable stripping entirely:
remarkPlugins: [[codeRegion, { strip: false }]],Assertion transmutation
Instead of stripping assertions, transform them into readable output comments that show expected values:
# Test file:
user = client.create_user(name="Alice", role="admin")
assert user["name"] == "Alice"
assert user["role"] == "admin"
assert user["id"]
# In your docs (with transmutation):
user = client.create_user(name="Alice", role="admin")
# user["name"] => "Alice"
# user["role"] => "admin"
# user["id"] => (truthy)Enable transmutation with composable per-language presets:
const codeRegion = require('remark-code-region');
const { PRESET_TRANSMUTE } = codeRegion;
remarkPlugins: [[codeRegion, {
transmute: [...PRESET_TRANSMUTE.python, ...PRESET_TRANSMUTE.js],
}]],Available presets: python, js, rust, java, go, cpp.
Each language transforms assertions into subject => value comments using the correct comment prefix (# for Python, // for JS, etc.):
| Language | Before | After |
|---|---|---|
| Python | assert user["name"] == "Alice" |
# user["name"] => "Alice" |
| JS | expect(sum(2, 3)).toBe(5); |
// sum(2, 3) => 5 |
| Rust | assert_eq!(rows.len(), 1); |
// rows.len() => 1 |
| Java | assertEquals("Alice", name); |
// name => "Alice" |
| Go | assert.Equal(t, 3, len(rows)) |
// len(rows) => 3 |
| C++ | ASSERT_EQ(x, 1); |
// x => 1 |
Inequality and negation are kept natural: assert x != 0 becomes # x != 0.
Transmutation is opt-in and runs before strip. Transmuted lines become comments and skip stripping. Unmatched lines fall through to strip as usual. Use ?noTransmute to disable per block.
Diff between two regions
Show a highlighted diff between two tested code states -- for migration guides, changelogs, and "what changed in v2" docs.
```python diff reference="tests/test_api.py#v1_handler" diff-reference="#v2_handler"
```The primary reference= is the "before", diff-reference= is the "after". Same-file shorthand (#v2_handler) inherits the file from the primary. Cross-file diffs work too.
Three output modes (set globally via diffFormat option or per-block with ?format=):
unified (default) -- standard diff format, lang set to "diff":
def handler(request):
- return Response(request.body)
+ validate(request)
+ return Response(request.body, status=200)inline-annotations -- Shiki [!code ++] / [!code --] markers, original language preserved:
return Response(request.body) // [!code --]
validate(request) // [!code ++]
return Response(request.body, status=200) // [!code ++]side-by-side -- emits two sibling code nodes with data-diff-role="before" / "after" for custom component rendering.
Both sides go through the full cleaning pipeline (strip, transmute, dedent) independently before diffing.
remarkPlugins: [[codeRegion, { diffFormat: 'inline-annotations' }]],Per-block override:
```python reference="file.py#v1?format=inline-annotations" diff-reference="#v2"
```The diff-file= syntax works alongside file= for remark-code-import compatible paths.
Step-by-step tutorial diffs
For tutorials where each step builds on the previous, use diff-step to show the current step's complete code with highlights on what changed:
First, create the basic app:
```python reference="tests/tutorial.py#step1"
```
Now add configuration:
```python reference="tests/tutorial.py#step2" diff-step="step1"
```
Finally, add authentication:
```python reference="tests/tutorial.py#step3" diff-step="step2"
```Step 1 renders as plain code. Step 2 renders with // [!code ++] on lines added since step 1. Step 3 shows what changed since step 2. Each block is a standalone code fence with its own prose context.
The reader sees complete, copyable code at every step -- with green/red highlights showing exactly what's new. Every step is a passing test.
diff-step defaults to inline-annotations (Shiki highlighting). Override with ?format=unified for +/- diff output.
diff-reference |
diff-step |
|
|---|---|---|
| Use case | Migration guide (before/after) | Tutorial (step-by-step) |
| Output | Diff only | Full current code with change annotations |
| Default format | unified (+/- prefixes) |
inline-annotations (Shiki highlights) |
Tab groups
Group consecutive code fences into tabs using the tab attribute. Each fence is processed independently (reference extraction, strip, transmute, diff — everything works), then consecutive tab fences are wrapped in a tabGroup AST node.
Multi-language tabs:
```python tab="Python" reference="tests/test_sdk.py#create_user"
```
```javascript tab="Node.js" reference="tests/test_sdk.js#create_user"
```Same-language version comparison:
```python tab="SDK v1" reference="tests/v1.py#create_user"
```
```python tab="SDK v2" reference="tests/v2.py#create_user"
```Inline code tabs (no reference needed):
```bash tab="npm"
npm install myapp
```
```bash tab="yarn"
yarn add myapp
```Bare tab (no value) derives the label from the language tag: ```python tab → label "Python".
Sync key
Use tab-group="id" to synchronize tab selection across multiple tab groups on a page (like Docusaurus groupId or Starlight syncKey). Clicking "Python" in one group switches all groups with the same sync key.
```python tab="Python" tab-group="lang" reference="tests/install.py#pip"To split adjacent tab groups without visible content between them, use an HTML comment (<!-- -->), which creates an invisible node that breaks the consecutive grouping.
AST output
The plugin emits tabGroup wrapper nodes with data.hName = 'div' and data.hProperties.class = 'code-tabs' (customizable via tabGroupClass option). Each child code node gets data.tabLabel and data.hProperties['data-tab'].
Without a companion plugin, this renders as a <div class="code-tabs"> wrapping stacked <pre><code> blocks. Add a companion plugin to transform these into native framework tabs:
Docusaurus:
const codeRegion = require('remark-code-region');
const codeRegionTabs = require('remark-code-region/docusaurus');
remarkPlugins: [codeRegion, codeRegionTabs],Astro Starlight:
import codeRegion from 'remark-code-region';
import codeRegionTabs from 'remark-code-region/starlight';
remarkPlugins: [codeRegion, codeRegionTabs],For other remark pipelines, the tabGroup AST nodes are well-structured for custom rehype plugins to consume directly.
Shiki annotations
Add ?highlight= or ?focus= to any reference to inject Shiki transformer annotations into the extracted code:
```python reference="tests/test_sdk.py#create_user?highlight=1,3-5"
```
```python reference="tests/test_sdk.py#create_user?focus=2,4-6"
```Line numbers are 1-based and refer to the final output (after strip/transmute/dedent). Ranges (3-5) and mixed specs (1,3-5,8) are supported.
Add the Shiki companion plugin to inject the annotations:
import codeRegion from 'remark-code-region';
import codeRegionShiki from 'remark-code-region/shiki';
remarkPlugins: [codeRegion, codeRegionShiki],Requires @shikijs/transformers (transformerNotationHighlight, transformerNotationFocus) configured in your Shiki setup.
Soft diff-step highlighting
By default, diff-step emits // [!code ++] / // [!code --] for green/red diff highlighting. The Shiki companion can convert these to softer // [!code highlight] annotations — emphasizing new lines without the diff color scheme:
remarkPlugins: [codeRegion, [codeRegionShiki, { diffStepStyle: 'highlight' }]],Lines marked // [!code --] are removed (since diff-step shows the current code state), and // [!code ++] becomes // [!code highlight].
Auto-dedent
Extracted regions are automatically dedented. Common leading whitespace is removed so that code nested inside test functions or classes renders flush-left in your docs.
def test_create_user():
# region: create_user
from myapp import client
user = client.create_user(name="Alice")
# endregion: create_user...renders as:
from myapp import client
user = client.create_user(name="Alice")Dedent is part of the configurable clean pipeline. To disable it, use clean: ['collapse', 'trim'] (omitting 'dedent').
Clean pipeline
The clean option controls the whitespace processing pipeline. Same API shape as strip: undefined uses defaults, false disables all, string[] for a custom list.
Available steps (run in array order):
| Step | What it does |
|---|---|
'dedent' |
Remove common leading whitespace |
'collapse' |
Collapse 3+ consecutive blank lines to 2 |
'trim' |
Trim leading and trailing whitespace |
'trimEnd' |
Trim trailing whitespace only |
const codeRegion = require('remark-code-region');
const { PRESET_CLEAN } = codeRegion;
// Default: ['dedent', 'collapse', 'trim']
remarkPlugins: [[codeRegion, { clean: [...PRESET_CLEAN.default] }]],
// remark-code-import compat: only strip trailing whitespace
remarkPlugins: [[codeRegion, { clean: [...PRESET_CLEAN.compat] }]],
// Custom: dedent and trim, but keep blank line runs intact
remarkPlugins: [[codeRegion, { clean: ['dedent', 'trim'] }]],
// Disable all cleaning
remarkPlugins: [[codeRegion, { clean: false }]],Options
| Option | Type | Default | Description |
|---|---|---|---|
rootDir |
string |
process.cwd() |
Base directory for resolving reference= paths. |
allowOutsideRoot |
boolean |
false |
Allow references to files outside rootDir. Disabled by default as a security boundary. |
regionMarkers |
{start, end}[] |
DEFAULT_REGION_MARKERS |
Region marker pairs. Each start/end is a RegExp where group 1 captures the region name. |
strip |
RegExp[] | false |
undefined (defaults) |
Patterns to strip from injected code. undefined uses built-in defaults. false disables. RegExp[] replaces defaults. |
transmute |
TransmuteRule[] | false |
undefined (disabled) |
Transform assertions into output comments. undefined/false disables. TransmuteRule[] enables with those rules. |
clean |
string[] | false |
['dedent', 'collapse', 'trim'] |
Whitespace cleaning steps. false disables. string[] for custom list. |
regionSeparator |
string |
'\n\n' |
String inserted between regions in multi-region concatenation. |
preserveFileMeta |
boolean |
false |
Keep file= in meta after processing (matches remark-code-import behavior). |
diffFormat |
string |
'unified' |
Output format for diff blocks: 'unified', 'inline-annotations', or 'side-by-side'. |
tabGroupClass |
string |
'code-tabs' |
CSS class for tab group wrapper <div>. |
Exports (for composing custom configurations):
| Export | What it is |
|---|---|
DEFAULT_REGION_MARKERS |
Default region marker pairs (# and // comments) |
PRESET_MARKERS |
Additional markers: .css, .html, .sql |
PRESET_STRIP |
Strip patterns by language: .python, .rust, .java, .js, .cpp, .go, .markers |
DEFAULT_STRIP_PATTERNS |
All built-in strip patterns (union of all PRESET_STRIP groups) |
PRESET_TRANSMUTE |
Transmute rules by language: .python, .js, .rust, .java, .go, .cpp |
DEFAULT_TRANSMUTE_RULES |
All transmute rules combined |
PRESET_CLEAN |
Clean step presets: .default, .compat |
DEFAULT_CLEAN |
Default clean steps (['dedent', 'collapse', 'trim']) |
COMMENT_PREFIX |
Language tag to comment prefix map (70+ languages) |
const codeRegion = require('remark-code-region');
remarkPlugins: [[codeRegion, {
rootDir: __dirname,
regionMarkers: [
...codeRegion.DEFAULT_REGION_MARKERS,
codeRegion.PRESET_MARKERS.css,
],
strip: [...codeRegion.PRESET_STRIP.python, ...codeRegion.PRESET_STRIP.js],
transmute: [...codeRegion.PRESET_TRANSMUTE.python],
clean: ['dedent', 'trim'],
}]],Hard failure on drift
If a referenced file is missing or a region doesn't exist, the build fails:
Error: remark-code-region: region 'create_user' not found in tests/test_users.pyIf a region is opened but never closed, the build fails:
Error: remark-code-region: region 'create_user' in tests/test_users.py was opened but never closedThis is intentional. Silent stale docs are worse than a build error.
MDX compatibility
Works inside MDX components (<Tabs>, admonitions) -- the plugin runs at the remark AST level, before MDX processing. Any code fence with a reference or file= attribute is resolved, regardless of where it sits in the markdown tree.
Framework support
Docusaurus
const codeRegion = require('remark-code-region');
const codeRegionTabs = require('remark-code-region/docusaurus'); // optional, for tab groups
module.exports = {
presets: [['classic', {
docs: { remarkPlugins: [codeRegion, codeRegionTabs] },
}]],
};Astro Starlight
import codeRegion from 'remark-code-region';
import codeRegionTabs from 'remark-code-region/starlight'; // optional, for tab groups
export default defineConfig({
markdown: { remarkPlugins: [codeRegion, codeRegionTabs] },
});Astro (non-Starlight)
import codeRegion from 'remark-code-region';
export default defineConfig({
markdown: { remarkPlugins: [codeRegion] },
});Next.js (MDX)
import codeRegion from 'remark-code-region';
const withMDX = createMDX({
options: { remarkPlugins: [codeRegion] },
});Raw remark/unified
import { remark } from 'remark';
import codeRegion from 'remark-code-region';
const result = await remark()
.use(codeRegion, { rootDir: '/path/to/repo' })
.process(markdown);Note: VitePress uses markdown-it (not remark) and is not compatible.
Migrating from remark-code-import
remark-code-region is a drop-in replacement. All file= syntax works unchanged:
- const remarkCodeImport = require('remark-code-import');
+ const remarkCodeImport = require('remark-code-region');remark-code-import dropped require() support in v1.0.0. If your Docusaurus config uses require(), you're stuck on an old version. remark-code-region supports both require() and import.
| Feature | remark-code-import | remark-code-region |
|---|---|---|
| Include from file | file=./path.js#L3-L6 |
reference="path.py#region_name" or file= |
| Named regions | No | Yes -- stable across edits |
| Line ranges | Yes | Yes (via file= syntax) |
| Multi-region | No | Yes (#imports,create_user) |
| Strip test lines | No | Auto-strips asserts, expects, test-only markers |
| Transmute assertions | No | Transforms assertions into => value comments |
| Diff two regions | No | diff-reference= with unified, inline-annotation, or side-by-side output |
| Tutorial step diffs | No | diff-step= shows current code with change highlights from previous step |
| Auto-dedent | Opt-in | On by default (configurable) |
| Composable clean pipeline | No | PRESET_CLEAN.default / .compat / custom |
| Security boundary | allowImportingFromOutside |
allowOutsideRoot (default: false) |
CJS require() support |
Dropped in v1.0 | Yes |
| Fail on missing file | Yes | Yes |
For exact byte-identical output during migration:
const codeRegion = require('remark-code-region');
const { PRESET_CLEAN } = codeRegion;
remarkPlugins: [[codeRegion, {
clean: [...PRESET_CLEAN.compat],
preserveFileMeta: true,
strip: false,
}]],License
MIT