diff --git a/packages/shikiji-compact/README.md b/packages/shikiji-compact/README.md new file mode 100644 index 000000000..a27292d9a --- /dev/null +++ b/packages/shikiji-compact/README.md @@ -0,0 +1,25 @@ +# shikiji-compact + +Compactible build of `shikiji` to align with `shiki`. + +> Work in progress. It's not 100% compatible yet, but feedback is welcome :) + +## Install + +```bash +npm i -D shikiji-compact +``` + +Or set the alias + +```json +{ + "dependencies": { + "shiki": "npm:shikiji-compact@0.6" + } +} +``` + +## License + +MIT diff --git a/packages/shikiji-compact/build.config.ts b/packages/shikiji-compact/build.config.ts new file mode 100644 index 000000000..8c047d79c --- /dev/null +++ b/packages/shikiji-compact/build.config.ts @@ -0,0 +1,15 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index.ts', + ], + declaration: true, + rollup: { + emitCJS: false, + }, + externals: [ + 'hast', + 'shikiji', + ], +}) diff --git a/packages/shikiji-compact/index.cjs b/packages/shikiji-compact/index.cjs new file mode 100644 index 000000000..793c7f75e --- /dev/null +++ b/packages/shikiji-compact/index.cjs @@ -0,0 +1,4 @@ +module.exports.getHighlighter = async (...args) => { + const { getHighlighter } = await import('./dist/index.mjs') + return getHighlighter(...args) +} diff --git a/packages/shikiji-compact/package.json b/packages/shikiji-compact/package.json new file mode 100644 index 000000000..5e263ee3e --- /dev/null +++ b/packages/shikiji-compact/package.json @@ -0,0 +1,42 @@ +{ + "name": "shikiji-compact", + "type": "module", + "version": "0.6.1", + "description": "Shikiji with shiki compactible API", + "author": "Anthony Fu ", + "license": "MIT", + "homepage": "https://github.com/antfu/shikiji#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/antfu/shikiji.git", + "directory": "packages/shikiji-compact" + }, + "bugs": "https://github.com/antfu/shikiji/issues", + "keywords": [ + "shiki", + "rehype" + ], + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.mts", + "require": "./index.cjs", + "default": "./dist/index.mjs" + } + }, + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "files": [ + "dist", + "index.cjs" + ], + "scripts": { + "build": "unbuild", + "dev": "unbuild --stub", + "prepublishOnly": "nr build" + }, + "dependencies": { + "shikiji": "workspace:*" + } +} diff --git a/packages/shikiji-compact/src/index.ts b/packages/shikiji-compact/src/index.ts new file mode 100644 index 000000000..86da0b03e --- /dev/null +++ b/packages/shikiji-compact/src/index.ts @@ -0,0 +1,77 @@ +import type { BuiltinLanguage, BuiltinTheme, BundledHighlighterOptions, CodeToHastOptions, CodeToThemedTokensOptions, LineOption, StringLiteralUnion, ThemedToken } from 'shikiji' +import { bundledLanguages, bundledThemes, getHighlighter as getShikiji } from 'shikiji' + +export const BUNDLED_LANGUAGES = bundledLanguages +export const BUNDLED_THEMES = bundledThemes + +export * from './stub' + +export interface AnsiToHtmlOptions { + theme?: StringLiteralUnion + lineOptions?: LineOption[] +} + +export interface HighlighterOptions extends BundledHighlighterOptions { + theme?: BuiltinTheme +} + +export async function getHighlighter(options: HighlighterOptions = {}) { + const themes = options.themes || [] + const langs = options.langs || [] + + if (options.theme) + themes.unshift(options.theme) + if (!themes.length) + themes.push('nord') + + if (!langs.length) + langs.push(...Object.keys(bundledLanguages) as BuiltinLanguage[]) + + const shikiji = await getShikiji({ + ...options, + themes, + langs, + }) + + const defaultTheme = shikiji.getLoadedThemes()[0] + + function codeToThemedTokens(code: string, options: CodeToThemedTokensOptions): ThemedToken[][] + function codeToThemedTokens(code: string, lang: BuiltinLanguage, theme?: BuiltinTheme): ThemedToken[][] + function codeToThemedTokens(code: string, lang: BuiltinLanguage | CodeToThemedTokensOptions, theme?: BuiltinTheme): ThemedToken[][] { + if (typeof lang === 'string') { + return shikiji.codeToThemedTokens(code, { + lang, + theme: (theme || defaultTheme) as BuiltinTheme, + }) + } + return shikiji.codeToThemedTokens(code, lang) + } + + function codeToHtml(code: string, options: Partial>): string + function codeToHtml(code: string, lang: BuiltinLanguage, theme?: BuiltinTheme): string + function codeToHtml(code: string, lang: any, theme?: BuiltinTheme): string { + if (typeof lang === 'string') { + return shikiji.codeToHtml(code, { + lang, + theme: (theme || defaultTheme), + }) + } + return shikiji.codeToHtml(code, { + ...lang, + theme: (lang.theme || defaultTheme), + }) + } + + return { + ...shikiji, + codeToThemedTokens, + codeToHtml, + ansiToHtml(code: string, options?: AnsiToHtmlOptions) { + return shikiji.codeToHtml(code, { + lang: 'ansi', + ...options, + theme: options?.theme || defaultTheme, + }) + }, + } +} diff --git a/packages/shikiji-compact/src/stub.ts b/packages/shikiji-compact/src/stub.ts new file mode 100644 index 000000000..9ed0b20e1 --- /dev/null +++ b/packages/shikiji-compact/src/stub.ts @@ -0,0 +1,17 @@ +const _warned = new Set() +function warnOnce(message: string) { + if (!_warned.has(message)) { + console.warn(`[shikiji-compact]: ${message}`) + _warned.add(message) + } +} +function stubFunction(name: string) { + return () => { + warnOnce(`\`${name}\` is a stub function in \`shikiji-compact\` and does nothing.`) + } +} + +export const setCDN = stubFunction('setCDN') +export const setOnigasmWASM = stubFunction('setOnigasmWASM') +export const setWasm = stubFunction('setWasm') +export const setColorReplacements = stubFunction('setColorReplacements') diff --git a/packages/shikiji-compact/test/types.test.ts b/packages/shikiji-compact/test/types.test.ts new file mode 100644 index 000000000..2295c2dec --- /dev/null +++ b/packages/shikiji-compact/test/types.test.ts @@ -0,0 +1,64 @@ +import { expect, expectTypeOf, test } from 'vitest' +import * as shiki from 'shiki' +import * as shikiji from '../src/index' + +test('run', async () => { + const s = await shiki.getHighlighter({ + theme: 'nord', + langs: ['javascript'], + }) + + const sj = await shikiji.getHighlighter({ + theme: 'nord', + langs: ['javascript'], + }) + + const group = [s, sj] + + expect(s.getLoadedThemes()).toEqual(sj.getLoadedThemes()) + expect(s.getLoadedLanguages()).toEqual(sj.getLoadedLanguages()) + + expectTypeOf(s.codeToHtml).toMatchTypeOf(sj.codeToHtml) + + expect(sj.codeToThemedTokens('const a = 1', 'javascript')) + .toEqual(s.codeToThemedTokens('const a = 1', 'javascript')) + + group.forEach((h) => { + h.codeToHtml('const a = 1', 'javascript') + h.codeToHtml('const a = 1', 'javascript', 'nord') + }) + + s.codeToHtml('const a = 1', { lang: 'javascript' }) + sj.codeToHtml('const a = 1', { lang: 'javascript' }) + + s.ansiToHtml('const a = 1', { theme: 'nord' }) + sj.ansiToHtml('const a = 1', { theme: 'nord' }) + + const shikiKeys = Object.keys(s) + const shikijiKeys = Object.keys(sj) + const keysDiff = shikiKeys.filter(k => !shikijiKeys.includes(k)) + + expect.soft(keysDiff).toMatchInlineSnapshot(` + [ + "ansiToThemedTokens", + "getTheme", + "getBackgroundColor", + "getForegroundColor", + "setColorReplacements", + ] + `) + + const shikiExports = Object.keys(shiki) + const shikijiExports = Object.keys(shikiji) + const exportsDiff = shikiExports.filter(k => !shikijiExports.includes(k)) + + expect.soft(exportsDiff).toMatchInlineSnapshot(` + [ + "FontStyle", + "default", + "loadTheme", + "renderToHtml", + "toShikiTheme", + ] + `) +})