Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/powershell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tanstack/highlight": minor
---

Add isolated PowerShell highlighting with pwsh and ps1 aliases and selective imports.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This is not an editor parser or a TextMate engine. It is a deliberately small do
- [Quick Start](docs/quick-start.md)
- [Comparison](docs/comparison.md)
- [Language Support](docs/language-support.md)
- [PowerShell Showcase](docs/guides/powershell.md)
- [Octane Integration](docs/guides/octane.md)
- [Guides](docs/guides/language-registration.md)
- [API Reference](docs/reference/index.md)
Expand Down Expand Up @@ -218,7 +219,8 @@ Local browser bundles, minified with esbuild and compressed independently. KB us
| Core + TSX | 10.11 KB | 4.26 KB | 3.90 KB |
| Octane MDX + TypeScript | 13.86 KB | 5.59 KB | 5.17 KB |
| Nine-language docs set | 16.04 KB | 6.20 KB | 5.66 KB |
| All 39 languages | 52.74 KB | 17.73 KB | 15.93 KB |
| PowerShell + core | 7.19 KB | 3.25 KB | 3.01 KB |
| All 40 languages | 55.38 KB | 18.47 KB | 16.65 KB |

The following comparison was measured before the 1.0 property-context correction. Re-run the comparison commands below for current timings.

Expand Down
1 change: 1 addition & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
{ "label": "SSR and Client Rendering", "to": "guides/ssr-and-client" },
{ "label": "React Integration", "to": "guides/react" },
{ "label": "Octane Integration", "to": "guides/octane" },
{ "label": "PowerShell Showcase", "to": "guides/powershell" },
{ "label": "Custom Languages", "to": "guides/custom-languages" },
{ "label": "Bundle Size and Performance", "to": "guides/performance" }
]
Expand Down
8 changes: 5 additions & 3 deletions docs/guides/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ CI measures selective browser bundles and highlighting performance on real docum

## Bundle profiles

`pnpm run size` builds twenty-six browser profiles with esbuild and measures minified, gzip, and Brotli bytes independently. It also checks that helper, adapter, and selective language imports retain only the requested modules.
`pnpm run size` builds twenty-seven browser profiles with esbuild and measures minified, gzip, and Brotli bytes independently. It also checks that helper, adapter, and selective language imports retain only the requested modules.

| Profile | Registered languages | Current gzip | CI budget |
| --- | --- | ---: | ---: |
| Core | None | 1.82 KB | 2.0 KB |
| TSX | TSX | 4.26 KB | 4.35 KB |
| Octane | TypeScript plus Octane MDX adapter | 5.59 KB | 5.7 KB |
| Docs | CSS, HTML, JS, JSON, JSX, Markdown, Shell, TS, TSX | 6.20 KB | 6.3 KB |
| All | All 39 definitions | 17.73 KB | 18.0 KB |
| All | All 40 definitions | 18.47 KB | 18.75 KB |

KB uses 1,000 bytes. Core helpers imported from the root tree-shake to the same engine size. The standalone theme helper is 695 gzip bytes.

PowerShell plus core measures 3,252 gzip bytes. Adding PowerShell and correcting short Swift macro declarations grows the all-language entry from 17,681 to 18,471 gzip bytes (+790), measured under identical Node 26.11.1 tooling. The Swift selective profile grows by 13 gzip bytes and stays within its existing budget. All 24 unrelated profiles and their budgets are unchanged; only the all-language budget increases.

Selective profiles are the primary metric. The all-language profile exists to prevent convenience-entry growth from becoming invisible.

## Runtime corpus

The committed corpus contains 334 real code fences sampled from TanStack documentation, with up to twenty samples per normalized language.

`pnpm run bench` measures tokenization, HTML, Markdown, HAST, line numbers, long decorated blocks, and dedicated C#, C++, CMake, Dart, Java, Kotlin, Lua, Perl, PHP, Ruby, Rust, and Swift samples. Each profile reports the median of three samples after two warmup passes, with a 1.2 second CI budget. The main highlighting profile processes at least 10,000 blocks.
`pnpm run bench` measures tokenization, HTML, Markdown, HAST, line numbers, long decorated blocks, and dedicated C#, C++, CMake, Dart, Java, Kotlin, Lua, Perl, PHP, PowerShell, Ruby, Rust, and Swift samples. Each profile reports the median of three samples after two warmup passes, with a 1.2 second CI budget. The main highlighting profile processes at least 10,000 blocks.

A local before-and-after review used the same minified bundle settings, fixtures, and benchmark harness on macOS arm64 with Node 24.15.0:

Expand Down
83 changes: 83 additions & 0 deletions docs/guides/powershell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: PowerShell Showcase
---

# PowerShell Showcase

Register PowerShell directly for report and automation examples. The `pwsh` and `ps1` aliases normalize to `powershell` when it is registered.

```ts
import { createHighlighter } from '@tanstack/highlight/core'
import { powershell } from '@tanstack/highlight/languages/powershell'

const highlighter = createHighlighter({ languages: [powershell] })
const result = highlighter.highlight('Get-Item $HOME', { lang: 'pwsh' })
```

## A report pipeline

An advanced function combines attributes, splatting, scoped variables, typed records, here-strings, null coalescing, and case-insensitive word operators. The API URL is a placeholder.

```powershell
#Requires -Version 7.4
<# Build typed records, then shape a pipeline for display.
Quotes and $variables in this comment stay quiet. #>
function Get-StationReport {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('coast', 'ridge')]
[string[]] $Station
)

begin {
$script:Endpoint = $env:STATION_API ?? 'https://example.com'
$headers = @{ Accept = 'application/json' }
$budget = 64MB
}
process {
foreach ($name in $Station) {
$request = @{
Uri = "$script:Endpoint/observations/$name"
Headers = $headers
ErrorAction = 'Stop'
}
try {
$reading = Invoke-RestMethod @request
[pscustomobject]@{
Station = $name
Celsius = [double] $reading.celsius
Online = $true
}
}
catch {
Write-Warning -Message "Station $name unavailable: $_"
}
}
}
end {
$banner = @"
Forecast ready
Budget: $budget
"@
$literal = @'
$HOME is literal; # this is text, not a comment.
'@
Write-Verbose -Message ($banner + $literal)
}
}

${report-title} = 'Today''s observatory'
Get-StationReport -Station coast, ridge |
Where-Object { $_.Online -and $_.Celsius -ge 0 } |
Sort-Object -Property Celsius -Descending |
Select-Object -First 5 -Property Station, Celsius
```

## Preview and scope

Run `pnpm run report:compare` and open `artifacts/shiki-comparison.html`. This showcase appears first in GitHub Light and Aurora X alongside Shiki's reference. Its canonical source is `test/showcases/Get-StationReport.ps1`.

Interpolation remains inside the string token. Full PowerShell command/argument disambiguation and symbol resolution are outside the lightweight tokenizer's scope.

Lexical rules follow PowerShell's [quoting rules](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules) and [language specification](https://learn.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-02).
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const highlighter = createHighlighter({
})
```

The root entry is useful for prototypes, server-only scripts, or sites where the roughly 18 KB gzip all-language build is acceptable:
The root entry is useful for prototypes, server-only scripts, or sites where the [all-language bundle size](guides/performance) is acceptable:

```ts
import { highlight } from '@tanstack/highlight'
Expand Down
1 change: 1 addition & 0 deletions docs/language-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Every language is an isolated definition imported from `@tanstack/highlight/lang
| Perl | `perl` | `pl` | Sigil and special variables, quote-like operators with nested delimiters, regex vs division, heredocs, POD blocks |
| PHP | `php` | - | PHP tags, attributes, quoted strings, heredoc/nowdoc, optional HTML delegation |
| Plaintext | `plaintext` | `text`, `txt`, `-->` | Escaping only |
| PowerShell | `powershell` | `pwsh`, `ps1` | Quoted and here-strings, nested expandable expressions, scoped/braced/splat variables, cmdlets, parameters, type literals, case-insensitive keywords and operators |
| Python | `python` | `py` | Triple strings, prefixes, decorators, comments |
| Ruby | `ruby` | `rb` | Interpolated strings with nested quotes, percent literals, heredocs, regex vs division, symbols and hash keys, block comments |
| Rust | `rust` | `rs` | Nested block comments, raw strings with hash counts, byte and C strings, lifetimes vs character literals, attributes, macros |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/default-entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Returns the canonical registered language name for a name or alias. Names are tr
function listLanguages(): Array<HighlightLanguage>
```

Returns the 39 canonical language names registered in `defaultHighlighter`.
Returns the 40 canonical language names registered in `defaultHighlighter`.

### `tokenize`

Expand Down
3 changes: 2 additions & 1 deletion docs/reference/languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const highlighter = createHighlighter({
| `perl` | `@tanstack/highlight/languages/perl` | `pl` |
| `php` | `@tanstack/highlight/languages/php` | None |
| `plaintext` | `@tanstack/highlight/languages/plaintext` | `text`, `txt`, `-->` |
| `powershell` | `@tanstack/highlight/languages/powershell` | `pwsh`, `ps1` |
| `python` | `@tanstack/highlight/languages/python` | `py` |
| `ruby` | `@tanstack/highlight/languages/ruby` | `rb` |
| `rust` | `@tanstack/highlight/languages/rust` | `rs` |
Expand All @@ -61,6 +62,6 @@ const highlighter = createHighlighter({
| `vue` | `@tanstack/highlight/languages/vue` | None |
| `yaml` | `@tanstack/highlight/languages/yaml` | `yml` |

`@tanstack/highlight/languages` re-exports `apache`, `cmake`, `cpp`, `csharp`, `css`, `dart`, `diff`, `dockerfile`, `ejs`, `env`, `go`, `html`, `http`, `java`, `js`, `json`, `jsx`, `kotlin`, `lua`, `markdown`, `mermaid`, `nginx`, `perl`, `php`, `plaintext`, `python`, `ruby`, `rust`, `scheme`, `shell`, `sql`, `svelte`, `swift`, `toml`, `ts`, `tsrx`, `tsx`, `vue`, and `yaml`. The barrel is convenient but individual subpaths make bundle intent explicit.
`@tanstack/highlight/languages` re-exports `apache`, `cmake`, `cpp`, `csharp`, `css`, `dart`, `diff`, `dockerfile`, `ejs`, `env`, `go`, `html`, `http`, `java`, `js`, `json`, `jsx`, `kotlin`, `lua`, `markdown`, `mermaid`, `nginx`, `perl`, `php`, `plaintext`, `powershell`, `python`, `ruby`, `rust`, `scheme`, `shell`, `sql`, `svelte`, `swift`, `toml`, `ts`, `tsrx`, `tsx`, `vue`, and `yaml`. The barrel is convenient but individual subpaths make bundle intent explicit.

See the [language support matrix](../language-support) for the context-aware behavior and current scope of each registration.
6 changes: 3 additions & 3 deletions docs/test-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ The suite protects the package's actual product boundary: valid code commonly pu

## Size Profiles

`pnpm run size` checks twenty-six independent browser profiles, including root helpers, language barrel imports, adapters, and themes. Each has minified, gzip, and Brotli budgets. The main highlighter profiles are:
`pnpm run size` checks twenty-seven independent browser profiles, including root helpers, language barrel imports, adapters, and themes. Each has minified, gzip, and Brotli budgets. The main highlighter profiles are:

| Profile | Languages | Gzip budget |
| --- | --- | ---: |
| Core | None | 2.0 KB |
| TSX | TSX | 4.35 KB |
| Octane | TypeScript plus Octane MDX adapter | 5.7 KB |
| Docs | CSS, HTML, JS, JSON, JSX, Markdown, Shell, TS, TSX | 6.3 KB |
| All | All 39 definitions | 18.0 KB |
| All | All 40 definitions | 18.75 KB |

The selective profiles are the primary product metric. The all-language profile protects the convenience entry from unbounded growth. Bundle graphs reject unexpected language or theme code. Package tests repeat isolation checks through public exports after building.

## Throughput

`pnpm run bench` measures highlighting, tokenization, Markdown, HAST, line numbers, long numbered blocks, long decorated blocks, and dedicated C#, C++, CMake, Dart, Java, Kotlin, Lua, Perl, PHP, Ruby, Rust, and Swift samples. Timings use the median of three samples after two warmup passes. Each profile has a 1.2 second CI budget; the main highlighting profile processes at least 10,000 blocks.
`pnpm run bench` measures highlighting, tokenization, Markdown, HAST, line numbers, long numbered blocks, long decorated blocks, and dedicated C#, C++, CMake, Dart, Java, Kotlin, Lua, Perl, PHP, PowerShell, Ruby, Rust, and Swift samples. Timings use the median of three samples after two warmup passes. Each profile has a 1.2 second CI budget; the main highlighting profile processes at least 10,000 blocks.

`pnpm run compare:sugar-high` compares the overlapping JS/TS/JSX/TSX use case. `pnpm run compare:shiki` compares all supported fixtures. These are directional measurements, not claims of equivalent grammar depth.

Expand Down
6 changes: 6 additions & 0 deletions scripts/bench.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ try {
outputBytes: htmlBytes,
targetBlocks: 10_000,
},
powershell: {
fixtures: [{ rawLang: 'powershell', code: fs.readFileSync('test/showcases/Get-StationReport.ps1', 'utf8') }],
run: (fixture) => highlight(fixture.code, { lang: fixture.rawLang }),
observe: (result) => result.html.length,
targetBlocks: 2_000,
},
cpp: {
fixtures: [{ rawLang: 'cpp', code: '#include <vector>\nconstexpr auto text = R"tag(// raw text)tag";\nint main() { std::vector<int> values{1, 2, 3}; return values.size(); }' }],
run: (fixture) => highlight(fixture.code, { lang: fixture.rawLang }),
Expand Down
19 changes: 17 additions & 2 deletions scripts/generate-visual-compare.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ function normalizeShikiLanguage(lang) {
'js-vue': 'javascript',
jsonc: 'json',
md: 'markdown',
pwsh: 'powershell',
ps1: 'powershell',
sh: 'bash',
shell: 'bash',
text: 'plaintext',
Expand All @@ -96,8 +98,14 @@ function normalizeShikiLanguage(lang) {
}

function selectFixtures(fixtures) {
const selected = []
const seen = new Set()
const selected = [{
lang: 'powershell',
rawLang: 'powershell',
file: 'test/showcases/Get-StationReport.ps1',
line: 1,
code: fs.readFileSync('test/showcases/Get-StationReport.ps1', 'utf8'),
}]
const seen = new Set(['powershell'])

for (const fixture of fixtures) {
if (seen.has(fixture.lang)) continue
Expand Down Expand Up @@ -208,6 +216,13 @@ function buildHtml(samples) {
background: #0d1117;
color: #e6edf3;
}
.dark .shiki, .dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
.missing-shiki {
border: 1px dashed var(--panel-border);
color: var(--muted);
Expand Down
3 changes: 3 additions & 0 deletions scripts/language-utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const supportedLanguages = [
'perl',
'php',
'plaintext',
'powershell',
'python',
'ruby',
'rust',
Expand Down Expand Up @@ -54,6 +55,8 @@ const aliases = {
kt: 'kotlin',
kts: 'kotlin',
pl: 'perl',
pwsh: 'powershell',
ps1: 'powershell',
rb: 'ruby',
rs: 'rust',
'-->': 'plaintext',
Expand Down
11 changes: 10 additions & 1 deletion scripts/measure-size.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,22 @@ const profiles = {
languages: ['swift'],
limits: { minified: 7_450, gzip: 3_550, brotli: 3_250 },
},
powershell: {
source: `
import { createHighlighter } from './src/core.ts'
import { powershell } from './src/languages/powershell.ts'
globalThis.highlighter = createHighlighter({ languages: [powershell] })
`,
languages: ['powershell'],
limits: { minified: 7_400, gzip: 3_400, brotli: 3_150 },
},
all: {
source: `
import { defaultHighlighter } from './src/index.ts'
globalThis.highlighter = defaultHighlighter
`,
languages: 'all',
limits: { minified: 53_150, gzip: 18_000, brotli: 16_200 },
limits: { minified: 55_800, gzip: 18_750, brotli: 16_900 },
},
reactAdapter: {
source: `export * from './src/react.ts'`,
Expand Down
5 changes: 5 additions & 0 deletions scripts/test-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ const isolatedBundles = [
source: `import { createHighlighter } from '@tanstack/highlight/core'; import { tsx } from '${entry}'; globalThis.highlighter = createHighlighter({ languages: [tsx] })`,
languages: ['tsx'],
})),
...['@tanstack/highlight/languages/powershell', '@tanstack/highlight/languages'].map((entry) => ({
name: `${entry} PowerShell`,
source: `import { createHighlighter } from '@tanstack/highlight/core'; import { powershell } from '${entry}'; globalThis.highlighter = createHighlighter({ languages: [powershell] })`,
languages: ['powershell'],
})),
...['react', 'markdown', 'remark', 'rehype', 'octane'].map((entry) => ({
name: `${entry} adapter`,
source: `export * from '@tanstack/highlight/${entry}'`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Import only the definitions the application registers.
| Perl | `perl` | `@tanstack/highlight/languages/perl` | `pl` |
| PHP | `php` | `@tanstack/highlight/languages/php` | - |
| Plaintext | `plaintext` | `@tanstack/highlight/languages/plaintext` | `text`, `txt`, `-->` |
| PowerShell | `powershell` | `@tanstack/highlight/languages/powershell` | `pwsh`, `ps1` |
| Python | `python` | `@tanstack/highlight/languages/python` | `py` |
| Ruby | `ruby` | `@tanstack/highlight/languages/ruby` | `rb` |
| Rust | `rust` | `@tanstack/highlight/languages/rust` | `rs` |
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { nginx } from './languages/nginx.js'
import { perl } from './languages/perl.js'
import { php } from './languages/php.js'
import { plaintext } from './languages/plaintext.js'
import { powershell } from './languages/powershell.js'
import { python } from './languages/python.js'
import { ruby } from './languages/ruby.js'
import { rust } from './languages/rust.js'
Expand Down Expand Up @@ -71,6 +72,7 @@ export type HighlightLanguage =
| 'perl'
| 'php'
| 'plaintext'
| 'powershell'
| 'python'
| 'ruby'
| 'rust'
Expand Down Expand Up @@ -152,6 +154,7 @@ export const allLanguages = [
perl,
php,
plaintext,
powershell,
python,
ruby,
rust,
Expand Down
1 change: 1 addition & 0 deletions src/languages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { nginx } from './nginx.js'
export { perl } from './perl.js'
export { php } from './php.js'
export { plaintext } from './plaintext.js'
export { powershell } from './powershell.js'
export { python } from './python.js'
export { ruby } from './ruby.js'
export { rust } from './rust.js'
Expand Down
Loading