Repository: shikijs/shiki
Pull request #773 (2024-09-09 17:37:27)

refactor: new `@shikijs/types` package, move engines for each seperated packages
This PR introduced three new packages separated from `@shikijs/core`

- `@shikijs/types`
- `@shikijs/engine-javascript`
- `@shikijs/engine-oniguruma`

Currently only refactoring the code and does not change the exports. In the future we might consider to ask user to explicit import from each packages.

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *shiki-next* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | ee0013ca4950da549968aaaab73f95887dcecf0c |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/shiki-next/deploys/66df325a5fc6db0008f1d292 |
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *shiki-matsu* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | ee0013ca4950da549968aaaab73f95887dcecf0c |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/shiki-matsu/deploys/66df325a7cf96f00088ced86 |
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/shikijs/shiki/pull/773?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) Report
Attention: Patch coverage is `85.94595%` with `26 lines` in your changes missing coverage. Please review.
> Project coverage is 94.25%. Comparing base [(`2d409cd`)](https://app.codecov.io/gh/shikijs/shiki/commit/2d409cd8e87a849ae9b2465ad64901a43bbce1e8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) to head [(`ab64f08`)](https://app.codecov.io/gh/shikijs/shiki/commit/ab64f0866e3e8c3e046fa0276da2a25156a864dc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).

| [Files with missing lines](https://app.codecov.io/gh/shikijs/shiki/pull/773?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) | Patch % | Lines |
|---|---|---|
| [packages/engine-javascript/src/index.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fengine-javascript%2Fsrc%2Findex.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvZW5naW5lLWphdmFzY3JpcHQvc3JjL2luZGV4LnRz) | 88.80% | [14 Missing :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |
| [packages/engine-oniguruma/src/wasm-inlined.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fengine-oniguruma%2Fsrc%2Fwasm-inlined.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvZW5naW5lLW9uaWd1cnVtYS9zcmMvd2FzbS1pbmxpbmVkLnRz) | 0.00% | [6 Missing and 1 partial :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |
| [packages/core/src/engines/oniguruma.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fcore%2Fsrc%2Fengines%2Foniguruma.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvY29yZS9zcmMvZW5naW5lcy9vbmlndXJ1bWEudHM=) | 77.77% | [2 Missing :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |
| [packages/core/src/textmate.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fcore%2Fsrc%2Ftextmate.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvY29yZS9zcmMvdGV4dG1hdGUudHM=) | 0.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |
| [packages/core/src/types.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fcore%2Fsrc%2Ftypes.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvY29yZS9zcmMvdHlwZXMudHM=) | 0.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |
| [packages/core/src/wasm-inlined.ts](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&filepath=packages%2Fcore%2Fsrc%2Fwasm-inlined.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvY29yZS9zcmMvd2FzbS1pbmxpbmVkLnRz) | 0.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/773?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #773      +/-   ##
==========================================
+ Coverage   92.32%   94.25%   +1.93%     
==========================================
  Files          70       71       +1     
  Lines        4558     4127     -431     
  Branches     1009      917      -92     
==========================================
- Hits         4208     3890     -318     
+ Misses        345      229     -116     
- Partials        5        8       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/shikijs/shiki/pull/773?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).


Patch:
diff --git a/.gitignore b/.gitignore
index 998cabbb..abd761d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 .idea
 *.log
 *.tgz
+.temp
 coverage
 dist
 lib-cov
diff --git a/docs/.vitepress/vite.config.ts b/docs/.vitepress/vite.config.ts
index 6f671458..721540f9 100644
--- a/docs/.vitepress/vite.config.ts
+++ b/docs/.vitepress/vite.config.ts
@@ -2,9 +2,15 @@ import { fileURLToPath } from 'node:url'
 import UnoCSS from 'unocss/vite'
 import Components from 'unplugin-vue-components/vite'
 import { defineConfig } from 'vite'
+import Tsconfig from 'vite-tsconfig-paths'
 
 export default defineConfig({
   plugins: [
+    Tsconfig({
+      projects: [
+        fileURLToPath(new URL('../../tsconfig.json', import.meta.url)),
+      ],
+    }),
     Components({
       dirs: [
         fileURLToPath(new URL('./components', import.meta.url)),
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 00000000..5c55f78c
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,7 @@
+{
+  "compilerOptions": {
+    "module": "esnext",
+    "moduleResolution": "Bundler",
+    "strict": true
+  }
+}
diff --git a/packages/core/build.config.ts b/packages/core/build.config.ts
new file mode 100644
index 00000000..1cb678a0
--- /dev/null
+++ b/packages/core/build.config.ts
@@ -0,0 +1,24 @@
+import { defineBuildConfig } from 'unbuild'
+
+export default defineBuildConfig({
+  entries: [
+    'src/index.ts',
+    'src/types.ts',
+    'src/textmate.ts',
+    'src/wasm-inlined.ts',
+  ],
+  declaration: true,
+  rollup: {
+    emitCJS: false,
+    dts: {
+      compilerOptions: {
+        paths: {},
+      },
+      respectExternal: true,
+    },
+    inlineDependencies: true,
+  },
+  externals: [
+    'hast',
+  ],
+})
diff --git a/packages/core/package.json b/packages/core/package.json
index 3f786bf9..4b47ac35 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -60,19 +60,17 @@
     "dist"
   ],
   "scripts": {
-    "build": "rimraf dist && rollup -c",
-    "dev": "rollup -cw",
+    "build": "unbuild",
+    "dev": "unbuild --stub",
     "prepublishOnly": "nr build",
     "test": "vitest"
   },
   "dependencies": {
+    "@shikijs/engine-javascript": "workspace:*",
+    "@shikijs/engine-oniguruma": "workspace:*",
+    "@shikijs/types": "workspace:*",
     "@shikijs/vscode-textmate": "catalog:",
     "@types/hast": "catalog:",
-    "oniguruma-to-js": "catalog:",
-    "regex": "catalog:"
-  },
-  "devDependencies": {
-    "hast-util-to-html": "catalog:",
-    "vscode-oniguruma": "catalog:"
+    "hast-util-to-html": "catalog:"
   }
 }
diff --git a/packages/core/src/constructors/bundle-factory.ts b/packages/core/src/constructors/bundle-factory.ts
index 666a7442..c2df4b99 100644
--- a/packages/core/src/constructors/bundle-factory.ts
+++ b/packages/core/src/constructors/bundle-factory.ts
@@ -1,13 +1,32 @@
+import { ShikiError } from '@shikijs/types'
+
+import type {
+  Awaitable,
+  BundledHighlighterOptions,
+  CodeToHastOptions,
+  CodeToTokensBaseOptions,
+  CodeToTokensOptions,
+  CodeToTokensWithThemesOptions,
+  CreatedBundledHighlighterOptions,
+  CreateHighlighterFactory,
+  GrammarState,
+  HighlighterCoreOptions,
+  HighlighterGeneric,
+  LanguageInput,
+  RegexEngine,
+  RequireKeys,
+  SpecialLanguage,
+  SpecialTheme,
+  ThemedToken,
+  ThemedTokenWithVariants,
+  ThemeInput,
+  TokensResult,
+} from '@shikijs/types'
 import type { Root } from 'hast'
-import { createWasmOnigEngine } from '../engines/wasm'
-import { ShikiError } from '../error'
+
+import { createWasmOnigEngine } from '../engines/oniguruma'
 import { isSpecialLang, isSpecialTheme } from '../utils'
 import { createHighlighterCore } from './highlighter'
-import type { Awaitable, BundledHighlighterOptions, CodeToHastOptions, CodeToTokensBaseOptions, CodeToTokensOptions, CodeToTokensWithThemesOptions, CreatedBundledHighlighterOptions, GrammarState, HighlighterCoreOptions, HighlighterGeneric, LanguageInput, RegexEngine, RequireKeys, SpecialLanguage, SpecialTheme, ThemedToken, ThemedTokenWithVariants, ThemeInput, TokensResult } from '../types'
-
-export type CreateHighlighterFactory<L extends string, T extends string> = (
-  options: BundledHighlighterOptions<L, T>
-) => Promise<HighlighterGeneric<L, T>>
 
 /**
  * Create a `createHighlighter` function with bundled themes, languages, and engine.
diff --git a/packages/core/src/constructors/highlighter.ts b/packages/core/src/constructors/highlighter.ts
index d32a1816..fd48d30d 100644
--- a/packages/core/src/constructors/highlighter.ts
+++ b/packages/core/src/constructors/highlighter.ts
@@ -1,12 +1,13 @@
+import type { HighlighterCore, HighlighterCoreOptions } from '@shikijs/types'
+
 import { codeToHast } from '../highlight/code-to-hast'
 import { codeToHtml } from '../highlight/code-to-html'
 import { codeToTokens } from '../highlight/code-to-tokens'
 import { codeToTokensBase, getLastGrammarState } from '../highlight/code-to-tokens-base'
 import { codeToTokensWithThemes } from '../highlight/code-to-tokens-themes'
 import { createShikiInternal } from './internal'
-import { createShikiInternalSync } from './internal-sync'
 
-import type { HighlighterCore, HighlighterCoreOptions } from '../types'
+import { createShikiInternalSync } from './internal-sync'
 
 /**
  * Create a Shiki core highlighter instance, with no languages or themes bundled.
diff --git a/packages/core/src/constructors/internal-sync.ts b/packages/core/src/constructors/internal-sync.ts
index 4f7e1d3b..f31560b6 100644
--- a/packages/core/src/constructors/internal-sync.ts
+++ b/packages/core/src/constructors/internal-sync.ts
@@ -1,9 +1,3 @@
-import { ShikiError } from '../error'
-import { resolveLangs, resolveThemes } from '../textmate/getters-resolve'
-import { normalizeTheme } from '../textmate/normalize-theme'
-import { Registry } from '../textmate/registry'
-import { Resolver } from '../textmate/resolver'
-
 import type {
   HighlighterCoreOptions,
   LanguageInput,
@@ -15,7 +9,13 @@ import type {
   ThemeInput,
   ThemeRegistrationAny,
   ThemeRegistrationResolved,
-} from '../types'
+} from '@shikijs/types'
+
+import { ShikiError } from '../../../types/src/error'
+import { resolveLangs, resolveThemes } from '../textmate/getters-resolve'
+import { normalizeTheme } from '../textmate/normalize-theme'
+import { Registry } from '../textmate/registry'
+import { Resolver } from '../textmate/resolver'
 
 let instancesCount = 0
 
diff --git a/packages/core/src/constructors/internal.ts b/packages/core/src/constructors/internal.ts
index 6e01e5ee..bb6df26a 100644
--- a/packages/core/src/constructors/internal.ts
+++ b/packages/core/src/constructors/internal.ts
@@ -1,20 +1,12 @@
-import { createWasmOnigEngine } from '../engines/wasm'
-import { resolveLangs, resolveThemes } from '../textmate/getters-resolve'
-import { createShikiInternalSync } from './internal-sync'
+import { createWasmOnigEngine } from '@shikijs/engine-oniguruma'
 import type {
   HighlighterCoreOptions,
-  LoadWasmOptions,
   ShikiInternal,
-} from '../types'
+} from '@shikijs/types'
 
-let _defaultWasmLoader: LoadWasmOptions | undefined
-/**
- * Set the default wasm loader for `loadWasm`.
- * @internal
- */
-export function setDefaultWasmLoader(_loader: LoadWasmOptions) {
-  _defaultWasmLoader = _loader
-}
+import { getDefaultWasmLoader } from '../engines/oniguruma'
+import { resolveLangs, resolveThemes } from '../textmate/getters-resolve'
+import { createShikiInternalSync } from './internal-sync'
 
 /**
  * Get the minimal shiki context for rendering.
@@ -27,7 +19,7 @@ export async function createShikiInternal(options: HighlighterCoreOptions = {}):
   ] = await Promise.all([
     resolveThemes(options.themes || []),
     resolveLangs(options.langs || []),
-    (options.engine || createWasmOnigEngine(options.loadWasm || _defaultWasmLoader)),
+    (options.engine || createWasmOnigEngine(options.loadWasm || getDefaultWasmLoader())),
   ] as const)
 
   return createShikiInternalSync({
diff --git a/packages/core/src/engines/javascript.ts b/packages/core/src/engines/javascript.ts
index 52ac2f56..8c2fbc9c 100644
--- a/packages/core/src/engines/javascript.ts
+++ b/packages/core/src/engines/javascript.ts
@@ -1,156 +1,1 @@
-import { onigurumaToRegexp } from 'oniguruma-to-js'
-import { rewrite } from 'regex'
-import type { JavaScriptRegexEngineOptions, PatternScanner, RegexEngine, RegexEngineString } from '../types'
-
-const MAX = 4294967295
-
-/**
- * The default RegExp constructor for JavaScript regex engine.
- */
-export function defaultJavaScriptRegexConstructor(pattern: string): RegExp {
-  pattern = pattern
-    // YAML specific handling; TODO: move to tm-grammars
-    .replaceAll('[^\\s[-?:,\\[\\]{}#&*!|>\'"%@`]]', '[^\\s\\-?:,\\[\\]{}#&*!|>\'"%@`]')
-
-  const rewritten = rewrite(pattern, {
-    flags: 'dgm',
-    unicodeSetsPlugin: null,
-    disable: {
-      n: true,
-      v: true,
-      x: true,
-    },
-  })
-
-  return onigurumaToRegexp(
-    rewritten.expression,
-    {
-      flags: 'dgm',
-      ignoreContiguousAnchors: true,
-    },
-  )
-}
-
-export class JavaScriptScanner implements PatternScanner {
-  regexps: (RegExp | null)[]
-
-  constructor(
-    public patterns: string[],
-    public cache: Map<string, RegExp | Error>,
-    public forgiving: boolean,
-    public regexConstructor: (pattern: string) => RegExp = defaultJavaScriptRegexConstructor,
-  ) {
-    this.regexps = patterns.map((p) => {
-      const cached = cache?.get(p)
-      if (cached) {
-        if (cached instanceof RegExp) {
-          return cached
-        }
-        if (forgiving)
-          return null
-        throw cached
-      }
-      try {
-        const regex = regexConstructor(p)
-        cache?.set(p, regex)
-        return regex
-      }
-      catch (e) {
-        cache?.set(p, e as Error)
-        if (forgiving)
-          return null
-        // console.error({ ...e })
-        throw e
-      }
-    })
-  }
-
-  findNextMatchSync(string: string | RegexEngineString, startPosition: number) {
-    const str = typeof string === 'string'
-      ? string
-      : string.content
-    const pending: [index: number, match: RegExpExecArray][] = []
-
-    function toResult(index: number, match: RegExpExecArray) {
-      return {
-        index,
-        captureIndices: match.indices!.map((indice) => {
-          if (indice == null) {
-            return {
-              end: MAX,
-              start: MAX,
-              length: 0,
-            }
-          }
-          return {
-            start: indice[0],
-            length: indice[1] - indice[0],
-            end: indice[1],
-          }
-        }),
-      }
-    }
-
-    for (let i = 0; i < this.regexps.length; i++) {
-      const regexp = this.regexps[i]
-      if (!regexp)
-        continue
-      try {
-        regexp.lastIndex = startPosition
-        const match = regexp.exec(str)
-        if (!match)
-          continue
-        // If the match is at the start position, return it immediately
-        if (match.index === startPosition) {
-          return toResult(i, match)
-        }
-        // Otherwise, store it for later
-        pending.push([i, match])
-      }
-      catch (e) {
-        if (this.forgiving)
-          continue
-        throw e
-      }
-    }
-
-    // Find the closest match to the start position
-    if (pending.length) {
-      const minIndex = Math.min(...pending.map(m => m[1].index))
-      for (const [i, match] of pending) {
-        if (match.index === minIndex) {
-          return toResult(i, match)
-        }
-      }
-    }
-
-    return null
-  }
-}
-
-/**
- * Use the modern JavaScript RegExp engine to implement the OnigScanner.
- *
- * As Oniguruma regex is more powerful than JavaScript regex, some patterns may not be supported.
- * Errors will be thrown when parsing TextMate grammars with unsupported patterns.
- * Set `forgiving` to `true` to ignore these errors and skip the unsupported patterns.
- *
- * @experimental
- */
-export function createJavaScriptRegexEngine(options: JavaScriptRegexEngineOptions = {}): RegexEngine {
-  const {
-    forgiving = false,
-    cache = new Map(),
-  } = options
-
-  return {
-    createScanner(patterns: string[]) {
-      return new JavaScriptScanner(patterns, cache, forgiving, options.regexConstructor)
-    },
-    createString(s: string) {
-      return {
-        content: s,
-      }
-    },
-  }
-}
+export * from '@shikijs/engine-javascript'
diff --git a/packages/core/src/engines/oniguruma.ts b/packages/core/src/engines/oniguruma.ts
new file mode 100644
index 00000000..3f001afd
--- /dev/null
+++ b/packages/core/src/engines/oniguruma.ts
@@ -0,0 +1,20 @@
+import type { LoadWasmOptions } from '@shikijs/types'
+
+export * from '@shikijs/engine-oniguruma'
+
+let _defaultWasmLoader: LoadWasmOptions | undefined
+
+/**
+ * Set the default wasm loader for `loadWasm`.
+ * @internal
+ */
+export function setDefaultWasmLoader(_loader: LoadWasmOptions) {
+  _defaultWasmLoader = _loader
+}
+
+/**
+ * @internal
+ */
+export function getDefaultWasmLoader() {
+  return _defaultWasmLoader
+}
diff --git a/packages/core/src/highlight/_get-transformers.ts b/packages/core/src/highlight/_get-transformers.ts
index e818d0f5..0c8cc2d0 100644
--- a/packages/core/src/highlight/_get-transformers.ts
+++ b/packages/core/src/highlight/_get-transformers.ts
@@ -1,5 +1,5 @@
+import type { ShikiTransformer, TransformerOptions } from '@shikijs/types'
 import { transformerDecorations } from '../transformer-decorations'
-import type { ShikiTransformer, TransformerOptions } from '../types'
 
 const builtInTransformers: ShikiTransformer[] = [
   /* @__PURE__ */ transformerDecorations(),
diff --git a/packages/core/src/highlight/code-to-hast.ts b/packages/core/src/highlight/code-to-hast.ts
index d1910844..3584a947 100644
--- a/packages/core/src/highlight/code-to-hast.ts
+++ b/packages/core/src/highlight/code-to-hast.ts
@@ -1,9 +1,4 @@
-import type { Element, Root, Text } from 'hast'
-
-import { FontStyle } from '../types'
-import { addClassToHast, getTokenStyleObject, stringifyTokenStyle } from '../utils'
-import { getTransformers } from './_get-transformers'
-import { codeToTokens } from './code-to-tokens'
+import { FontStyle } from '@shikijs/vscode-textmate'
 
 import type {
   CodeToHastOptions,
@@ -13,7 +8,12 @@ import type {
   ShikiTransformerContextCommon,
   ShikiTransformerContextSource,
   ThemedToken,
-} from '../types'
+} from '@shikijs/types'
+import type { Element, Root, Text } from 'hast'
+
+import { addClassToHast, getTokenStyleObject, stringifyTokenStyle } from '../utils'
+import { getTransformers } from './_get-transformers'
+import { codeToTokens } from './code-to-tokens'
 
 export function codeToHast(
   internal: ShikiInternal,
diff --git a/packages/core/src/highlight/code-to-html.ts b/packages/core/src/highlight/code-to-html.ts
index b780c3eb..c4caddef 100644
--- a/packages/core/src/highlight/code-to-html.ts
+++ b/packages/core/src/highlight/code-to-html.ts
@@ -1,8 +1,13 @@
 import { toHtml as hastToHtml } from 'hast-util-to-html'
+import type {
+  CodeToHastOptions,
+  ShikiInternal,
+  ShikiTransformerContextCommon,
+} from '@shikijs/types'
+
 import { getTransformers } from './_get-transformers'
 import { codeToHast } from './code-to-hast'
 import { codeToTokens } from './code-to-tokens'
-import type { CodeToHastOptions, ShikiInternal, ShikiTransformerContextCommon } from '../types'
 
 export { hastToHtml }
 
diff --git a/packages/core/src/highlight/code-to-tokens-ansi.ts b/packages/core/src/highlight/code-to-tokens-ansi.ts
index 80f7766a..1009e84e 100644
--- a/packages/core/src/highlight/code-to-tokens-ansi.ts
+++ b/packages/core/src/highlight/code-to-tokens-ansi.ts
@@ -1,7 +1,13 @@
+import { FontStyle } from '@shikijs/vscode-textmate'
 import { createAnsiSequenceParser, createColorPalette, namedColors } from 'ansi-sequence-parser'
-import { FontStyle } from '../types'
+
+import type {
+  ThemedToken,
+  ThemeRegistrationResolved,
+  TokenizeWithThemeOptions,
+} from '@shikijs/types'
+
 import { applyColorReplacements, resolveColorReplacements, splitLines } from '../utils'
-import type { ThemedToken, ThemeRegistrationResolved, TokenizeWithThemeOptions } from '../types'
 
 export function tokenizeAnsiWithTheme(
   theme: ThemeRegistrationResolved,
diff --git a/packages/core/src/highlight/code-to-tokens-base.ts b/packages/core/src/highlight/code-to-tokens-base.ts
index a134229f..22f721e2 100644
--- a/packages/core/src/highlight/code-to-tokens-base.ts
+++ b/packages/core/src/highlight/code-to-tokens-base.ts
@@ -1,13 +1,26 @@
 /* ---------------------------------------------------------
  * Copyright (C) Microsoft Corporation. All rights reserved.
  *-------------------------------------------------------- */
+import { ShikiError } from '@shikijs/types'
 import { EncodedTokenMetadata, INITIAL } from '@shikijs/vscode-textmate'
-import type { IGrammar, IRawThemeSetting, StateStack } from '@shikijs/vscode-textmate'
-import { ShikiError } from '../error'
+import type {
+  CodeToTokensBaseOptions,
+  ShikiInternal,
+  ThemedToken,
+  ThemedTokenScopeExplanation,
+  ThemeRegistrationResolved,
+  TokenizeWithThemeOptions,
+} from '@shikijs/types'
+import type {
+  FontStyle,
+  IGrammar,
+  IRawThemeSetting,
+  StateStack,
+} from '@shikijs/vscode-textmate'
+
 import { getGrammarStack, GrammarState } from '../textmate/grammar-state'
 import { applyColorReplacements, isNoneTheme, isPlainLang, resolveColorReplacements, splitLines } from '../utils'
 import { tokenizeAnsiWithTheme } from './code-to-tokens-ansi'
-import type { CodeToTokensBaseOptions, FontStyle, ShikiInternal, ThemedToken, ThemedTokenScopeExplanation, ThemeRegistrationResolved, TokenizeWithThemeOptions } from '../types'
 
 /**
  * Code to tokens, with a simple theme.
diff --git a/packages/core/src/highlight/code-to-tokens-themes.ts b/packages/core/src/highlight/code-to-tokens-themes.ts
index a4f50e68..53775bf1 100644
--- a/packages/core/src/highlight/code-to-tokens-themes.ts
+++ b/packages/core/src/highlight/code-to-tokens-themes.ts
@@ -1,10 +1,10 @@
-import { codeToTokensBase } from './code-to-tokens-base'
 import type {
   CodeToTokensWithThemesOptions,
   ShikiInternal,
   ThemedToken,
   ThemedTokenWithVariants,
-} from '../types'
+} from '@shikijs/types'
+import { codeToTokensBase } from './code-to-tokens-base'
 
 /**
  * Get tokens with multiple themes
diff --git a/packages/core/src/highlight/code-to-tokens.ts b/packages/core/src/highlight/code-to-tokens.ts
index 17cc5f9e..7065a961 100644
--- a/packages/core/src/highlight/code-to-tokens.ts
+++ b/packages/core/src/highlight/code-to-tokens.ts
@@ -1,8 +1,8 @@
-import { ShikiError } from '../error'
+import type { CodeToTokensOptions, ShikiInternal, ThemedToken, ThemedTokenWithVariants, TokensResult } from '@shikijs/types'
+import { ShikiError } from '../../../types/src/error'
 import { applyColorReplacements, getTokenStyleObject, resolveColorReplacements, stringifyTokenStyle } from '../utils'
 import { codeToTokensBase } from './code-to-tokens-base'
 import { codeToTokensWithThemes } from './code-to-tokens-themes'
-import type { CodeToTokensOptions, ShikiInternal, ThemedToken, ThemedTokenWithVariants, TokensResult } from '../types'
 
 /**
  * High-level code-to-tokens API.
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index df7a465d..62537b15 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -1,36 +1,39 @@
 // Types
 export * from './constructors/bundle-factory'
-
 // Constructors
 export * from './constructors/highlighter'
-export { createShikiInternal, getShikiInternal, setDefaultWasmLoader } from './constructors/internal'
+export { createShikiInternal, getShikiInternal } from './constructors/internal'
 export { createShikiInternalSync } from './constructors/internal-sync'
-export { createJavaScriptRegexEngine, defaultJavaScriptRegexConstructor } from './engines/javascript'
 
 // Engines
-export { createWasmOnigEngine, loadWasm } from './engines/wasm'
-export { ShikiError } from './error'
+export { createJavaScriptRegexEngine, defaultJavaScriptRegexConstructor } from './engines/javascript'
+export { createWasmOnigEngine, loadWasm, setDefaultWasmLoader } from './engines/oniguruma'
 
+// Low-level Highlighting
 export { codeToHast, tokensToHast } from './highlight/code-to-hast'
-
 export { codeToHtml, hastToHtml } from './highlight/code-to-html'
 export { codeToTokens } from './highlight/code-to-tokens'
 export { tokenizeAnsiWithTheme } from './highlight/code-to-tokens-ansi'
-// Low-level Highlighting
 export { codeToTokensBase, tokenizeWithTheme } from './highlight/code-to-tokens-base'
 export { codeToTokensWithThemes } from './highlight/code-to-tokens-themes'
-// TextMate Utilities
-export { normalizeTheme } from './textmate/normalize-theme'
 
+export { normalizeTheme } from './textmate/normalize-theme'
 export { transformerDecorations } from './transformer-decorations'
-export * from './types'
+
 // Utils and Misc
 export * from './utils'
 
+// Types
+export * from '@shikijs/types'
+
 // Deprecated
 export {
   /**
    * @deprecated Use `EncodedTokenMetadata` from `@shikijs/vscode-textmate` instead.
    */
   EncodedTokenMetadata as StackElementMetadata,
+  /**
+   * @deprecated Import `FontStyle` from `@shikijs/vscode-textmate` instead.
+   */
+  FontStyle,
 } from '@shikijs/vscode-textmate'
diff --git a/packages/core/src/textmate.ts b/packages/core/src/textmate.ts
new file mode 100644
index 00000000..947f904c
--- /dev/null
+++ b/packages/core/src/textmate.ts
@@ -0,0 +1,1 @@
+export * from '@shikijs/vscode-textmate'
diff --git a/packages/core/src/textmate/getters-resolve.ts b/packages/core/src/textmate/getters-resolve.ts
index 43946ed7..6b71f7ab 100644
--- a/packages/core/src/textmate/getters-resolve.ts
+++ b/packages/core/src/textmate/getters-resolve.ts
@@ -1,6 +1,14 @@
+import type {
+  LanguageInput,
+  LanguageRegistration,
+  SpecialLanguage,
+  SpecialTheme,
+  ThemeInput,
+  ThemeRegistrationResolved,
+} from '@shikijs/types'
+
 import { isSpecialLang, isSpecialTheme, normalizeGetter } from '../utils'
 import { normalizeTheme } from './normalize-theme'
-import type { LanguageInput, LanguageRegistration, SpecialLanguage, SpecialTheme, ThemeInput, ThemeRegistrationResolved } from '../types'
 
 /**
  * Resolve
diff --git a/packages/core/src/textmate/grammar-state.ts b/packages/core/src/textmate/grammar-state.ts
index 82efd0de..a644ac7f 100644
--- a/packages/core/src/textmate/grammar-state.ts
+++ b/packages/core/src/textmate/grammar-state.ts
@@ -1,13 +1,14 @@
 import { INITIAL } from '@shikijs/vscode-textmate'
+import type { GrammarState as GrammarStateInterface } from '@shikijs/types'
 import type { StateStack, StateStackImpl } from '@shikijs/vscode-textmate'
-import { ShikiError } from '../error'
+import { ShikiError } from '../../../types/src/error'
 
 /**
  * GrammarState is a special reference object that holds the state of a grammar.
  *
  * It's used to highlight code snippets that are part of the target language.
  */
-export class GrammarState {
+export class GrammarState implements GrammarStateInterface {
   /**
    * Static method to create a initial grammar state.
    */
@@ -53,7 +54,7 @@ function getScopes(stack: StateStackImpl) {
   return scopes
 }
 
-export function getGrammarStack(state: GrammarState) {
+export function getGrammarStack(state: GrammarState | GrammarStateInterface): StateStack {
   if (!(state instanceof GrammarState))
     throw new ShikiError('Invalid grammar state')
   // @ts-expect-error _stack is private
diff --git a/packages/core/src/textmate/normalize-theme.ts b/packages/core/src/textmate/normalize-theme.ts
index a88b1e1d..694487be 100644
--- a/packages/core/src/textmate/normalize-theme.ts
+++ b/packages/core/src/textmate/normalize-theme.ts
@@ -1,4 +1,4 @@
-import type { ThemeRegistrationAny, ThemeRegistrationResolved } from '../types'
+import type { ThemeRegistrationAny, ThemeRegistrationResolved } from '@shikijs/types'
 
 /**
  * https://github.com/microsoft/vscode/blob/f7f05dee53fb33fe023db2e06e30a89d3094488f/src/vs/platform/theme/common/colorRegistry.ts#L258-L268
diff --git a/packages/core/src/textmate/registry.ts b/packages/core/src/textmate/registry.ts
index e9f8f2f3..d1752b9b 100644
--- a/packages/core/src/textmate/registry.ts
+++ b/packages/core/src/textmate/registry.ts
@@ -1,8 +1,13 @@
 import { Registry as TextMateRegistry, Theme as TextMateTheme } from '@shikijs/vscode-textmate'
+import type {
+  Grammar,
+  LanguageRegistration,
+  ThemeRegistrationAny,
+  ThemeRegistrationResolved,
+} from '@shikijs/types'
 import type { IGrammarConfiguration, IRawTheme } from '@shikijs/vscode-textmate'
-import { ShikiError } from '../error'
+import { ShikiError } from '../../../types/src/error'
 import { normalizeTheme } from './normalize-theme'
-import type { Grammar, LanguageRegistration, ThemeRegistrationAny, ThemeRegistrationResolved } from '../types'
 import type { Resolver } from './resolver'
 
 export class Registry extends TextMateRegistry {
diff --git a/packages/core/src/textmate/resolver.ts b/packages/core/src/textmate/resolver.ts
index 2f57ba38..4c93f568 100644
--- a/packages/core/src/textmate/resolver.ts
+++ b/packages/core/src/textmate/resolver.ts
@@ -1,5 +1,5 @@
+import type { LanguageRegistration, RegexEngine } from '@shikijs/types'
 import type { IOnigLib, RegistryOptions } from '@shikijs/vscode-textmate'
-import type { LanguageRegistration, RegexEngine } from '../types'
 
 export class Resolver implements RegistryOptions {
   private readonly _langs = new Map<string, LanguageRegistration>()
diff --git a/packages/core/src/transformer-decorations.ts b/packages/core/src/transformer-decorations.ts
index caa91044..24b3ca8e 100644
--- a/packages/core/src/transformer-decorations.ts
+++ b/packages/core/src/transformer-decorations.ts
@@ -1,6 +1,3 @@
-import type { Element, ElementContent } from 'hast'
-import { ShikiError } from './error'
-import { addClassToHast, createPositionConverter, splitTokens } from './utils'
 import type {
   DecorationItem,
   DecorationTransformType,
@@ -10,7 +7,10 @@ import type {
   ShikiTransformer,
   ShikiTransformerContextMeta,
   ShikiTransformerContextSource,
-} from './types'
+} from '@shikijs/types'
+import type { Element, ElementContent } from 'hast'
+import { ShikiError } from '../../types/src/error'
+import { addClassToHast, createPositionConverter, splitTokens } from './utils'
 
 interface TransformerDecorationsInternalContext {
   decorations: ResolvedDecorationItem[]
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 679c9fc9..3d6c4901 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -1,1 +1,1 @@
-export * from './types/index'
+export * from '@shikijs/types'
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index ecb1ae50..ac6222ef 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -1,5 +1,5 @@
-import type { Element } from 'hast'
-import { FontStyle } from './types'
+import { FontStyle } from '@shikijs/vscode-textmate'
+
 import type {
   MaybeArray,
   MaybeGetter,
@@ -12,7 +12,8 @@ import type {
   ThemeRegistrationAny,
   TokenizeWithThemeOptions,
   TokenStyles,
-} from './types'
+} from '@shikijs/types'
+import type { Element } from 'hast'
 
 export function toArray<T>(x: MaybeArray<T>): T[] {
   return Array.isArray(x) ? x : [x]
diff --git a/packages/core/src/wasm-inlined.ts b/packages/core/src/wasm-inlined.ts
index 4e941315..d706b1ef 100644
--- a/packages/core/src/wasm-inlined.ts
+++ b/packages/core/src/wasm-inlined.ts
@@ -1,11 +1,1 @@
-// @ts-expect-error this will be compiled to ArrayBuffer
-import binary from 'vscode-oniguruma/release/onig.wasm'
-import type { WebAssemblyInstantiator } from './types'
-
-export const wasmBinary = binary as ArrayBuffer
-
-export const getWasmInstance: WebAssemblyInstantiator = async (info) => {
-  return WebAssembly.instantiate(wasmBinary, info).then(wasm => wasm.instance.exports)
-}
-
-export default getWasmInstance
+export { default } from '@shikijs/engine-oniguruma/wasm-inlined'
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
deleted file mode 100644
index a3eb24a9..00000000
--- a/packages/core/tsconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "esnext",
-    "lib": ["esnext", "DOM"],
-    "rootDir": ".",
-    "module": "esnext",
-    "moduleResolution": "Bundler",
-    "resolveJsonModule": true,
-    "allowJs": true,
-    "strict": true,
-    "strictNullChecks": true,
-    "noEmit": true,
-    "preserveValueImports": false,
-    "esModuleInterop": true,
-    "skipDefaultLibCheck": true,
-    "skipLibCheck": true
-  }
-}
diff --git a/packages/engine-javascript/README.md b/packages/engine-javascript/README.md
new file mode 100644
index 00000000..99ed6e42
--- /dev/null
+++ b/packages/engine-javascript/README.md
@@ -0,0 +1,9 @@
+# @shikijs/engine-javascript
+
+Engine for Shiki using JavaScript's native RegExp (experimental).
+
+[Documentation](https://shiki.style/guide/regex-engines)
+
+## License
+
+MIT
diff --git a/packages/engine-javascript/build.config.ts b/packages/engine-javascript/build.config.ts
new file mode 100644
index 00000000..6afc6c96
--- /dev/null
+++ b/packages/engine-javascript/build.config.ts
@@ -0,0 +1,19 @@
+import { defineBuildConfig } from 'unbuild'
+
+export default defineBuildConfig({
+  entries: [
+    'src/index.ts',
+  ],
+  declaration: true,
+  rollup: {
+    emitCJS: false,
+    dts: {
+      compilerOptions: {
+        paths: {},
+      },
+    },
+  },
+  externals: [
+    'hast',
+  ],
+})
diff --git a/packages/engine-javascript/package.json b/packages/engine-javascript/package.json
new file mode 100644
index 00000000..48769d96
--- /dev/null
+++ b/packages/engine-javascript/package.json
@@ -0,0 +1,42 @@
+{
+  "name": "@shikijs/engine-javascript",
+  "type": "module",
+  "version": "1.16.3",
+  "description": "Engine for Shiki using JavaScript's native RegExp",
+  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
+  "license": "MIT",
+  "homepage": "https://github.com/shikijs/shiki#readme",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/shikijs/shiki.git",
+    "directory": "packages/transformers"
+  },
+  "bugs": "https://github.com/shikijs/shiki/issues",
+  "keywords": [
+    "shiki",
+    "@shikijs/transformers"
+  ],
+  "sideEffects": false,
+  "exports": {
+    ".": {
+      "types": "./dist/index.d.mts",
+      "default": "./dist/index.mjs"
+    }
+  },
+  "main": "./dist/index.mjs",
+  "module": "./dist/index.mjs",
+  "types": "./dist/index.d.mts",
+  "files": [
+    "dist"
+  ],
+  "scripts": {
+    "build": "unbuild",
+    "dev": "unbuild --stub",
+    "prepublishOnly": "nr build"
+  },
+  "dependencies": {
+    "@shikijs/types": "workspace:*",
+    "oniguruma-to-js": "catalog:",
+    "regex": "catalog:"
+  }
+}
diff --git a/packages/engine-javascript/src/index.ts b/packages/engine-javascript/src/index.ts
new file mode 100644
index 00000000..13537f4d
--- /dev/null
+++ b/packages/engine-javascript/src/index.ts
@@ -0,0 +1,179 @@
+import { onigurumaToRegexp } from 'oniguruma-to-js'
+import { rewrite } from 'regex'
+import type {
+  PatternScanner,
+  RegexEngine,
+  RegexEngineString,
+} from '@shikijs/types'
+
+export interface JavaScriptRegexEngineOptions {
+  /**
+   * Whether to allow invalid regex patterns.
+   */
+  forgiving?: boolean
+
+  /**
+   * Cache for regex patterns.
+   */
+  cache?: Map<string, RegExp | Error>
+
+  /**
+   * Custom pattern to RegExp constructor.
+   *
+   * By default `oniguruma-to-js` is used.
+   */
+  regexConstructor?: (pattern: string) => RegExp
+}
+
+const MAX = 4294967295
+
+/**
+ * The default RegExp constructor for JavaScript regex engine.
+ */
+export function defaultJavaScriptRegexConstructor(pattern: string): RegExp {
+  pattern = pattern
+    // YAML specific handling; TODO: move to tm-grammars
+    .replaceAll('[^\\s[-?:,\\[\\]{}#&*!|>\'"%@`]]', '[^\\s\\-?:,\\[\\]{}#&*!|>\'"%@`]')
+
+  const rewritten = rewrite(pattern, {
+    flags: 'dgm',
+    unicodeSetsPlugin: null,
+    disable: {
+      n: true,
+      v: true,
+      x: true,
+    },
+  })
+
+  return onigurumaToRegexp(
+    rewritten.expression,
+    {
+      flags: 'dgm',
+      ignoreContiguousAnchors: true,
+    },
+  )
+}
+
+export class JavaScriptScanner implements PatternScanner {
+  regexps: (RegExp | null)[]
+
+  constructor(
+    public patterns: string[],
+    public cache: Map<string, RegExp | Error>,
+    public forgiving: boolean,
+    public regexConstructor: (pattern: string) => RegExp = defaultJavaScriptRegexConstructor,
+  ) {
+    this.regexps = patterns.map((p) => {
+      const cached = cache?.get(p)
+      if (cached) {
+        if (cached instanceof RegExp) {
+          return cached
+        }
+        if (forgiving)
+          return null
+        throw cached
+      }
+      try {
+        const regex = regexConstructor(p)
+        cache?.set(p, regex)
+        return regex
+      }
+      catch (e) {
+        cache?.set(p, e as Error)
+        if (forgiving)
+          return null
+        // console.error({ ...e })
+        throw e
+      }
+    })
+  }
+
+  findNextMatchSync(string: string | RegexEngineString, startPosition: number) {
+    const str = typeof string === 'string'
+      ? string
+      : string.content
+    const pending: [index: number, match: RegExpExecArray][] = []
+
+    function toResult(index: number, match: RegExpExecArray) {
+      return {
+        index,
+        captureIndices: match.indices!.map((indice) => {
+          if (indice == null) {
+            return {
+              end: MAX,
+              start: MAX,
+              length: 0,
+            }
+          }
+          return {
+            start: indice[0],
+            length: indice[1] - indice[0],
+            end: indice[1],
+          }
+        }),
+      }
+    }
+
+    for (let i = 0; i < this.regexps.length; i++) {
+      const regexp = this.regexps[i]
+      if (!regexp)
+        continue
+      try {
+        regexp.lastIndex = startPosition
+        const match = regexp.exec(str)
+        if (!match)
+          continue
+        // If the match is at the start position, return it immediately
+        if (match.index === startPosition) {
+          return toResult(i, match)
+        }
+        // Otherwise, store it for later
+        pending.push([i, match])
+      }
+      catch (e) {
+        if (this.forgiving)
+          continue
+        throw e
+      }
+    }
+
+    // Find the closest match to the start position
+    if (pending.length) {
+      const minIndex = Math.min(...pending.map(m => m[1].index))
+      for (const [i, match] of pending) {
+        if (match.index === minIndex) {
+          return toResult(i, match)
+        }
+      }
+    }
+
+    return null
+  }
+}
+
+/**
+ * Use the modern JavaScript RegExp engine to implement the OnigScanner.
+ *
+ * As Oniguruma regex is more powerful than JavaScript regex, some patterns may not be supported.
+ * Errors will be thrown when parsing TextMate grammars with unsupported patterns.
+ * Set `forgiving` to `true` to ignore these errors and skip the unsupported patterns.
+ *
+ * @experimental
+ */
+export function createJavaScriptRegexEngine(options: JavaScriptRegexEngineOptions = {}): RegexEngine {
+  const {
+    forgiving = false,
+    cache = new Map(),
+  } = options
+
+  return {
+    createScanner(patterns: string[]) {
+      return new JavaScriptScanner(patterns, cache, forgiving, options.regexConstructor)
+    },
+    createString(s: string) {
+      return {
+        content: s,
+      }
+    },
+  }
+}
diff --git a/packages/engine-oniguruma/README.md b/packages/engine-oniguruma/README.md
new file mode 100644
index 00000000..7955dea0
--- /dev/null
+++ b/packages/engine-oniguruma/README.md
@@ -0,0 +1,9 @@
+# @shikijs/engine-oniguruma
+
+Engine for Shiki using Oniguruma RegExp engine in WebAssembly.
+
+[Documentation](https://shiki.style/guide/regex-engines)
+
+## License
+
+MIT
diff --git a/packages/engine-oniguruma/package.json b/packages/engine-oniguruma/package.json
new file mode 100644
index 00000000..95493260
--- /dev/null
+++ b/packages/engine-oniguruma/package.json
@@ -0,0 +1,58 @@
+{
+  "name": "@shikijs/engine-oniguruma",
+  "type": "module",
+  "version": "1.16.3",
+  "description": "Engine for Shiki using JavaScript's native RegExp",
+  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
+  "license": "MIT",
+  "homepage": "https://github.com/shikijs/shiki#readme",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/shikijs/shiki.git",
+    "directory": "packages/engine-oniguruma"
+  },
+  "bugs": "https://github.com/shikijs/shiki/issues",
+  "keywords": [
+    "shiki"
+  ],
+  "sideEffects": false,
+  "exports": {
+    ".": {
+      "types": "./dist/index.d.mts",
+      "default": "./dist/index.mjs"
+    },
+    "./wasm-inlined": {
+      "types": "./dist/wasm-inlined.d.mts",
+      "default": "./dist/wasm-inlined.mjs"
+    }
+  },
+  "main": "./dist/index.mjs",
+  "module": "./dist/index.mjs",
+  "types": "./dist/index.d.mts",
+  "typesVersions": {
+    "*": {
+      "wasm-inlined": [
+        "./dist/wasm-inlined.d.mts"
+      ],
+      "*": [
+        "./dist/*",
+        "./*"
+      ]
+    }
+  },
+  "files": [
+    "dist"
+  ],
+  "scripts": {
+    "build": "rimraf dist && rollup -c",
+    "dev": "rollup -cw",
+    "prepublishOnly": "nr build"
+  },
+  "dependencies": {
+    "@shikijs/types": "workspace:*",
+    "@shikijs/vscode-textmate": "catalog:"
+  },
+  "devDependencies": {
+    "vscode-oniguruma": "^1.7.0"
+  }
+}
diff --git a/packages/engine-oniguruma/rollup.config.mjs b/packages/engine-oniguruma/rollup.config.mjs
new file mode 100644
index 00000000..ed8280d7
--- /dev/null
+++ b/packages/engine-oniguruma/rollup.config.mjs
@@ -0,0 +1,90 @@
+// @ts-check
+import commonjs from '@rollup/plugin-commonjs'
+import json from '@rollup/plugin-json'
+import { nodeResolve } from '@rollup/plugin-node-resolve'
+import fs from 'fs-extra'
+import { defineConfig } from 'rollup'
+import dts from 'rollup-plugin-dts'
+import ts from 'rollup-plugin-typescript2'
+
+const entries = [
+  'src/index.ts',
+  'src/wasm-inlined.ts',
+]
+
+const plugins = [
+  ts({
+    check: false,
+  }),
+  nodeResolve(),
+  commonjs(),
+  json({
+    namedExports: false,
+    preferConst: true,
+    compact: true,
+  }),
+  wasmPlugin(),
+]
+
+const external = [
+  'hast',
+]
+
+export default defineConfig([
+  {
+    input: entries,
+    output: {
+      dir: 'dist',
+      format: 'esm',
+      entryFileNames: '[name].mjs',
+      chunkFileNames: () => {
+        return 'chunks-[name].mjs'
+      },
+    },
+    plugins: [
+      ...plugins,
+    ],
+    external,
+  },
+  {
+    input: entries,
+    output: {
+      dir: 'dist',
+      format: 'esm',
+      chunkFileNames: 'chunk-[name].d.mts',
+      entryFileNames: f => `${f.name.replace(/src[\\/]/, '')}.d.mts`,
+    },
+    plugins: [
+      dts({
+        respectExternal: true,
+      }),
+      {
+        name: 'post',
+        async buildEnd() {
+          await fs.writeFile('dist/onig.d.mts', 'declare const binary: ArrayBuffer; export default binary;', 'utf-8')
+        },
+      },
+    ],
+    onwarn: (warning, warn) => {
+      if (!/Circular|an empty chunk/.test(warning.message))
+        warn(warning)
+    },
+    external,
+  },
+])
+
+/**
+ * @returns {import('rollup').Plugin} Plugin
+ */
+export function wasmPlugin() {
+  return {
+    name: 'wasm',
+    async load(id) {
+      if (!id.endsWith('.wasm'))
+        return
+      const binary = await fs.readFile(id)
+      const base64 = binary.toString('base64')
+      return `export default Uint8Array.from(atob(${JSON.stringify(base64)}), c => c.charCodeAt(0))`
+    },
+  }
+}
diff --git a/packages/core/src/engines/wasm.ts b/packages/engine-oniguruma/src/index.ts
similarity index 85%
rename from packages/core/src/engines/wasm.ts
rename to packages/engine-oniguruma/src/index.ts
index 5a0cdfa8..cb591da9 100644
--- a/packages/core/src/engines/wasm.ts
+++ b/packages/engine-oniguruma/src/index.ts
@@ -1,6 +1,6 @@
-import { loadWasm, OnigScanner, OnigString } from './oniguruma'
+import type { LoadWasmOptions, RegexEngine } from '@shikijs/types'
 
-import type { LoadWasmOptions, RegexEngine } from '../types'
+import { loadWasm, OnigScanner, OnigString } from './oniguruma'
 
 export { loadWasm }
 
diff --git a/packages/core/src/engines/oniguruma/LICENSE b/packages/engine-oniguruma/src/oniguruma/LICENSE
similarity index 100%
rename from packages/core/src/engines/oniguruma/LICENSE
rename to packages/engine-oniguruma/src/oniguruma/LICENSE
diff --git a/packages/core/src/engines/oniguruma/index.ts b/packages/engine-oniguruma/src/oniguruma/index.ts
similarity index 98%
rename from packages/core/src/engines/oniguruma/index.ts
rename to packages/engine-oniguruma/src/oniguruma/index.ts
index 53cbf86e..dbcb5265 100644
--- a/packages/core/src/engines/oniguruma/index.ts
+++ b/packages/engine-oniguruma/src/oniguruma/index.ts
@@ -2,10 +2,19 @@
  * Copyright (C) Microsoft Corporation. All rights reserved.
  *-------------------------------------------------------- */
 
-import type { IOnigCaptureIndex, IOnigMatch, OnigScanner as IOnigScanner, OnigString as IOnigString } from '@shikijs/vscode-textmate'
-import { ShikiError } from '../../error'
+import { ShikiError } from '@shikijs/types'
+import type {
+  LoadWasmOptions,
+  WebAssemblyInstance,
+  WebAssemblyInstantiator,
+} from '@shikijs/types'
+import type {
+  IOnigCaptureIndex,
+  IOnigMatch,
+  OnigScanner as IOnigScanner,
+  OnigString as IOnigString,
+} from '@shikijs/vscode-textmate'
 import createOnigasm from './onig'
-import type { LoadWasmOptions, WebAssemblyInstance, WebAssemblyInstantiator } from '../../types'
 
 export type Instantiator = (importObject: Record<string, Record<string, WebAssembly.ImportValue>>) => Promise<WebAssembly.Exports>
 
diff --git a/packages/core/src/engines/oniguruma/onig.ts b/packages/engine-oniguruma/src/oniguruma/onig.ts
similarity index 100%
rename from packages/core/src/engines/oniguruma/onig.ts
rename to packages/engine-oniguruma/src/oniguruma/onig.ts
diff --git a/packages/engine-oniguruma/src/wasm-inlined.ts b/packages/engine-oniguruma/src/wasm-inlined.ts
new file mode 100644
index 00000000..da450f2e
--- /dev/null
+++ b/packages/engine-oniguruma/src/wasm-inlined.ts
@@ -0,0 +1,11 @@
+// @ts-expect-error this will be compiled to ArrayBuffer
+import binary from 'vscode-oniguruma/release/onig.wasm'
+import type { WebAssemblyInstantiator } from '@shikijs/types'
+
+export const wasmBinary = binary as ArrayBuffer
+
+export const getWasmInstance: WebAssemblyInstantiator = async (info) => {
+  return WebAssembly.instantiate(wasmBinary, info).then(wasm => wasm.instance.exports)
+}
+
+export default getWasmInstance
diff --git a/packages/monaco/package.json b/packages/monaco/package.json
index f80d7a05..414c3d38 100644
--- a/packages/monaco/package.json
+++ b/packages/monaco/package.json
@@ -47,6 +47,7 @@
   },
   "dependencies": {
     "@shikijs/core": "workspace:*",
+    "@shikijs/types": "workspace:*",
     "@shikijs/vscode-textmate": "catalog:"
   },
   "devDependencies": {
diff --git a/packages/monaco/src/index.ts b/packages/monaco/src/index.ts
index ac80e94c..04bfb0ca 100644
--- a/packages/monaco/src/index.ts
+++ b/packages/monaco/src/index.ts
@@ -1,5 +1,5 @@
 import { EncodedTokenMetadata, INITIAL } from '@shikijs/vscode-textmate'
-import type { ShikiInternal, ThemeRegistrationResolved } from '@shikijs/core'
+import type { ShikiInternal, ThemeRegistrationResolved } from '@shikijs/types'
 import type { StateStack } from '@shikijs/vscode-textmate'
 import type monacoNs from 'monaco-editor-core'
 
diff --git a/packages/rehype/package.json b/packages/rehype/package.json
index 74941721..ee4995d3 100644
--- a/packages/rehype/package.json
+++ b/packages/rehype/package.json
@@ -51,6 +51,7 @@
   },
   "dependencies": {
     "@shikijs/transformers": "workspace:*",
+    "@shikijs/types": "workspace:*",
     "@types/hast": "catalog:",
     "hast-util-to-string": "catalog:",
     "shiki": "workspace:*",
diff --git a/packages/rehype/src/core.ts b/packages/rehype/src/core.ts
index 872e71cc..e7b627bb 100644
--- a/packages/rehype/src/core.ts
+++ b/packages/rehype/src/core.ts
@@ -1,10 +1,10 @@
 import { toString } from 'hast-util-to-string'
 import { visit } from 'unist-util-visit'
-import type { Element, Root } from 'hast'
 import type {
   CodeToHastOptions,
   HighlighterGeneric,
-} from 'shiki/core'
+} from '@shikijs/types'
+import type { Element, Root } from 'hast'
 import type { Transformer } from 'unified'
 import { InlineCodeProcessors } from './inline'
 import type { RehypeShikiCoreOptions } from './types'
diff --git a/packages/rehype/src/index.ts b/packages/rehype/src/index.ts
index 7fc11e25..ba4175dc 100644
--- a/packages/rehype/src/index.ts
+++ b/packages/rehype/src/index.ts
@@ -1,9 +1,9 @@
 /// <reference types="mdast-util-to-hast" />
 
 import { bundledLanguages, getSingletonHighlighter } from 'shiki'
+import type { LanguageInput } from '@shikijs/types'
 import type { Root } from 'hast'
 import type { BuiltinLanguage, BuiltinTheme } from 'shiki'
-import type { LanguageInput } from 'shiki/core'
 import type { Plugin } from 'unified'
 import rehypeShikiFromHighlighter from './core'
 import type { RehypeShikiCoreOptions } from './types'
diff --git a/packages/shiki/package.json b/packages/shiki/package.json
index 3f3341cd..47613035 100644
--- a/packages/shiki/package.json
+++ b/packages/shiki/package.json
@@ -101,6 +101,7 @@
   },
   "dependencies": {
     "@shikijs/core": "workspace:*",
+    "@shikijs/types": "workspace:*",
     "@shikijs/vscode-textmate": "catalog:",
     "@types/hast": "catalog:"
   },
diff --git a/packages/shiki/rollup.config.mjs b/packages/shiki/rollup.config.mjs
index 5247a552..df2ab23f 100644
--- a/packages/shiki/rollup.config.mjs
+++ b/packages/shiki/rollup.config.mjs
@@ -24,6 +24,7 @@ const entries = [
 ]
 
 const external = [
+  '@shikijs/types',
   '@shikijs/core',
   '@shikijs/core/wasm-inlined',
   '@shikijs/core/types',
diff --git a/packages/shiki/src/bundle-full.ts b/packages/shiki/src/bundle-full.ts
index 96a2cee9..8a97bfeb 100644
--- a/packages/shiki/src/bundle-full.ts
+++ b/packages/shiki/src/bundle-full.ts
@@ -1,4 +1,4 @@
-import type { CreateHighlighterFactory, HighlighterGeneric } from '@shikijs/core'
+import type { CreateHighlighterFactory, HighlighterGeneric } from '@shikijs/types'
 import type {} from 'hast'
 import { bundledLanguages } from './assets/langs-bundle-full'
 import { createdBundledHighlighter, createSingletonShorthands, createWasmOnigEngine } from './core'
diff --git a/packages/shiki/src/bundle-web.ts b/packages/shiki/src/bundle-web.ts
index c0421ce4..2597c3ee 100644
--- a/packages/shiki/src/bundle-web.ts
+++ b/packages/shiki/src/bundle-web.ts
@@ -1,4 +1,4 @@
-import type { CreateHighlighterFactory, HighlighterGeneric } from '@shikijs/core'
+import type { CreateHighlighterFactory, HighlighterGeneric } from '@shikijs/types'
 import type {} from 'hast'
 
 import { bundledLanguages } from './assets/langs-bundle-web'
diff --git a/packages/shiki/src/theme-css-variables.ts b/packages/shiki/src/theme-css-variables.ts
index 8420af92..699ae64c 100644
--- a/packages/shiki/src/theme-css-variables.ts
+++ b/packages/shiki/src/theme-css-variables.ts
@@ -1,4 +1,4 @@
-import type { ThemeRegistration } from '@shikijs/core'
+import type { ThemeRegistration } from '@shikijs/types'
 
 export interface CssVariablesThemeOptions {
   /**
diff --git a/packages/twoslash/package.json b/packages/twoslash/package.json
index 3a5431e2..449af8c8 100644
--- a/packages/twoslash/package.json
+++ b/packages/twoslash/package.json
@@ -56,6 +56,7 @@
   },
   "dependencies": {
     "@shikijs/core": "workspace:*",
+    "@shikijs/types": "workspace:*",
     "twoslash": "catalog:"
   },
   "devDependencies": {
diff --git a/packages/twoslash/src/core.ts b/packages/twoslash/src/core.ts
index 8871324e..ef8a2ba8 100644
--- a/packages/twoslash/src/core.ts
+++ b/packages/twoslash/src/core.ts
@@ -3,7 +3,7 @@
  * Decoupled from twoslash's implementation and allowing to introduce custom implementation or cache system.
  */
 import { splitTokens } from '@shikijs/core'
-import type { ShikiTransformer, ShikiTransformerContextMeta } from '@shikijs/core'
+import type { ShikiTransformer, ShikiTransformerContextMeta } from '@shikijs/types'
 import type { Element, ElementContent, Text } from 'hast'
 
 import type { TwoslashExecuteOptions, TwoslashGenericFunction } from 'twoslash'
diff --git a/packages/twoslash/src/renderer-rich.ts b/packages/twoslash/src/renderer-rich.ts
index b4141596..5252100e 100644
--- a/packages/twoslash/src/renderer-rich.ts
+++ b/packages/twoslash/src/renderer-rich.ts
@@ -1,4 +1,4 @@
-import type { ShikiTransformerContextCommon } from '@shikijs/core'
+import type { ShikiTransformerContextCommon } from '@shikijs/types'
 import type { Element, ElementContent, Text } from 'hast'
 import type { NodeError, NodeHover, NodeQuery } from 'twoslash'
 import { ShikiTwoslashError } from './error'
diff --git a/packages/twoslash/src/types.ts b/packages/twoslash/src/types.ts
index 386c2dcf..12221275 100644
--- a/packages/twoslash/src/types.ts
+++ b/packages/twoslash/src/types.ts
@@ -1,6 +1,17 @@
-import type { CodeToHastOptions, ShikiTransformerContext } from '@shikijs/core'
+import type { CodeToHastOptions, ShikiTransformerContext } from '@shikijs/types'
 import type { Element, ElementContent, Text } from 'hast'
-import type { NodeCompletion, NodeError, NodeHighlight, NodeHover, NodeQuery, NodeTag, TwoslashExecuteOptions, TwoslashGenericFunction, TwoslashOptions, TwoslashReturn } from 'twoslash'
+import type {
+  NodeCompletion,
+  NodeError,
+  NodeHighlight,
+  NodeHover,
+  NodeQuery,
+  NodeTag,
+  TwoslashExecuteOptions,
+  TwoslashGenericFunction,
+  TwoslashOptions,
+  TwoslashReturn,
+} from 'twoslash'
 
 // We only pick necessary types to Shiki, making passing custom twoslash implementation easier
 export type TwoslashShikiReturn =
diff --git a/packages/types/README.md b/packages/types/README.md
new file mode 100644
index 00000000..371182c8
--- /dev/null
+++ b/packages/types/README.md
@@ -0,0 +1,7 @@
+# @shikijs/types
+
+Types for Shiki.
+
+## License
+
+MIT
diff --git a/packages/types/build.config.ts b/packages/types/build.config.ts
new file mode 100644
index 00000000..6afc6c96
--- /dev/null
+++ b/packages/types/build.config.ts
@@ -0,0 +1,19 @@
+import { defineBuildConfig } from 'unbuild'
+
+export default defineBuildConfig({
+  entries: [
+    'src/index.ts',
+  ],
+  declaration: true,
+  rollup: {
+    emitCJS: false,
+    dts: {
+      compilerOptions: {
+        paths: {},
+      },
+    },
+  },
+  externals: [
+    'hast',
+  ],
+})
diff --git a/packages/types/package.json b/packages/types/package.json
new file mode 100644
index 00000000..34e4e11e
--- /dev/null
+++ b/packages/types/package.json
@@ -0,0 +1,40 @@
+{
+  "name": "@shikijs/types",
+  "type": "module",
+  "version": "1.16.3",
+  "description": "Type definitions for Shiki",
+  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
+  "license": "MIT",
+  "homepage": "https://github.com/shikijs/shiki#readme",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/shikijs/shiki.git",
+    "directory": "packages/types"
+  },
+  "bugs": "https://github.com/shikijs/shiki/issues",
+  "keywords": [
+    "shiki"
+  ],
+  "sideEffects": false,
+  "exports": {
+    ".": {
+      "types": "./dist/index.d.mts",
+      "default": "./dist/index.mjs"
+    }
+  },
+  "main": "./dist/index.mjs",
+  "module": "./dist/index.mjs",
+  "types": "./dist/index.d.mts",
+  "files": [
+    "dist"
+  ],
+  "scripts": {
+    "build": "unbuild",
+    "dev": "unbuild --stub",
+    "prepublishOnly": "nr build"
+  },
+  "dependencies": {
+    "@shikijs/vscode-textmate": "catalog:",
+    "@types/hast": "catalog:"
+  }
+}
diff --git a/packages/types/src/bundle-factory.ts b/packages/types/src/bundle-factory.ts
new file mode 100644
index 00000000..109ec8da
--- /dev/null
+++ b/packages/types/src/bundle-factory.ts
@@ -0,0 +1,6 @@
+import type { HighlighterGeneric } from './highlighter'
+import type { BundledHighlighterOptions } from './options'
+
+export type CreateHighlighterFactory<L extends string, T extends string> = (
+  options: BundledHighlighterOptions<L, T>
+) => Promise<HighlighterGeneric<L, T>>
diff --git a/packages/core/src/types/decorations.ts b/packages/types/src/decorations.ts
similarity index 100%
rename from packages/core/src/types/decorations.ts
rename to packages/types/src/decorations.ts
diff --git a/packages/core/src/types/engines.ts b/packages/types/src/engines.ts
similarity index 75%
rename from packages/core/src/types/engines.ts
rename to packages/types/src/engines.ts
index ab24bdc3..8019ea98 100644
--- a/packages/core/src/types/engines.ts
+++ b/packages/types/src/engines.ts
@@ -30,22 +30,3 @@ export type LoadWasmOptionsPlain =
   | ArrayBufferView | ArrayBuffer | Response
 
 export type LoadWasmOptions = Awaitable<LoadWasmOptionsPlain> | (() => Awaitable<LoadWasmOptionsPlain>)
-
-export interface JavaScriptRegexEngineOptions {
-  /**
-   * Whether to allow invalid regex patterns.
-   */
-  forgiving?: boolean
-
-  /**
-   * Cache for regex patterns.
-   */
-  cache?: Map<string, RegExp | Error>
-
-  /**
-   * Custom pattern to RegExp constructor.
-   *
-   * By default `oniguruma-to-js` is used.
-   */
-  regexConstructor?: (pattern: string) => RegExp
-}
diff --git a/packages/core/src/error.ts b/packages/types/src/error.ts
similarity index 100%
rename from packages/core/src/error.ts
rename to packages/types/src/error.ts
diff --git a/packages/core/src/types/highlighter.ts b/packages/types/src/highlighter.ts
similarity index 96%
rename from packages/core/src/types/highlighter.ts
rename to packages/types/src/highlighter.ts
index 6d1ec9e6..73b97724 100644
--- a/packages/core/src/types/highlighter.ts
+++ b/packages/types/src/highlighter.ts
@@ -4,7 +4,15 @@ import type { LanguageInput, LanguageRegistration, ResolveBundleKey, SpecialLang
 import type { CodeToHastOptions } from './options'
 import type { Grammar } from './textmate'
 import type { SpecialTheme, ThemeInput, ThemeRegistrationAny, ThemeRegistrationResolved } from './themes'
-import type { CodeToTokensBaseOptions, CodeToTokensOptions, CodeToTokensWithThemesOptions, GrammarState, ThemedToken, ThemedTokenWithVariants, TokensResult } from './tokens'
+import type {
+  CodeToTokensBaseOptions,
+  CodeToTokensOptions,
+  CodeToTokensWithThemesOptions,
+  GrammarState,
+  ThemedToken,
+  ThemedTokenWithVariants,
+  TokensResult,
+} from './tokens'
 import type { Awaitable, MaybeArray } from './utils'
 
 /**
diff --git a/packages/core/src/types/index.ts b/packages/types/src/index.ts
similarity index 82%
rename from packages/core/src/types/index.ts
rename to packages/types/src/index.ts
index 53928df1..fbab80ed 100644
--- a/packages/core/src/types/index.ts
+++ b/packages/types/src/index.ts
@@ -1,5 +1,7 @@
+export * from './bundle-factory'
 export * from './decorations'
 export * from './engines'
+export * from './error'
 export * from './highlighter'
 export * from './langs'
 export * from './options'
diff --git a/packages/core/src/types/langs.ts b/packages/types/src/langs.ts
similarity index 100%
rename from packages/core/src/types/langs.ts
rename to packages/types/src/langs.ts
diff --git a/packages/core/src/types/options.ts b/packages/types/src/options.ts
similarity index 98%
rename from packages/core/src/types/options.ts
rename to packages/types/src/options.ts
index d7d7090c..be8f194e 100644
--- a/packages/core/src/types/options.ts
+++ b/packages/types/src/options.ts
@@ -1,5 +1,5 @@
-import type { LoadWasmOptions, RegexEngine } from '../types'
 import type { DecorationOptions } from './decorations'
+import type { LoadWasmOptions, RegexEngine } from './engines'
 import type { LanguageInput, LanguageRegistration, SpecialLanguage } from './langs'
 import type { SpecialTheme, ThemeInput, ThemeRegistrationAny } from './themes'
 import type { TokenizeWithThemeOptions, TokensResult } from './tokens'
diff --git a/packages/core/src/types/textmate.ts b/packages/types/src/textmate.ts
similarity index 100%
rename from packages/core/src/types/textmate.ts
rename to packages/types/src/textmate.ts
diff --git a/packages/core/src/types/themes.ts b/packages/types/src/themes.ts
similarity index 100%
rename from packages/core/src/types/themes.ts
rename to packages/types/src/themes.ts
diff --git a/packages/core/src/types/tokens.ts b/packages/types/src/tokens.ts
similarity index 94%
rename from packages/core/src/types/tokens.ts
rename to packages/types/src/tokens.ts
index 306ee352..5289aebb 100644
--- a/packages/core/src/types/tokens.ts
+++ b/packages/types/src/tokens.ts
@@ -1,10 +1,18 @@
-import type { IRawThemeSetting } from '@shikijs/vscode-textmate'
-import type { GrammarState } from '../textmate/grammar-state'
+import type { FontStyle, IRawThemeSetting } from '@shikijs/vscode-textmate'
 import type { SpecialLanguage } from './langs'
 import type { CodeOptionsThemes } from './options'
 import type { SpecialTheme, ThemeRegistrationAny } from './themes'
 
-export type { GrammarState }
+/**
+ * GrammarState is a special reference object that holds the state of a grammar.
+ *
+ * It's used to highlight code snippets that are part of the target language.
+ */
+export interface GrammarState {
+  readonly lang: string
+  readonly theme: string
+  get scopes(): string[]
+}
 
 export interface CodeToTokensBaseOptions<Languages extends string = string, Themes extends string = string> extends TokenizeWithThemeOptions {
   lang?: Languages | SpecialLanguage
@@ -226,11 +234,3 @@ export interface TokensResult {
    */
   rootStyle?: string
 }
-
-export enum FontStyle {
-  NotSet = -1,
-  None = 0,
-  Italic = 1,
-  Bold = 2,
-  Underline = 4,
-}
diff --git a/packages/core/src/types/transformers.ts b/packages/types/src/transformers.ts
similarity index 100%
rename from packages/core/src/types/transformers.ts
rename to packages/types/src/transformers.ts
diff --git a/packages/core/src/types/utils.ts b/packages/types/src/utils.ts
similarity index 100%
rename from packages/core/src/types/utils.ts
rename to packages/types/src/utils.ts
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b48e8c5a..4ec5341a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,8 +46,8 @@ catalogs:
       specifier: ^0.4.4
       version: 0.4.4
     '@shikijs/vscode-textmate':
-      specifier: ^9.2.0
-      version: 9.2.0
+      specifier: ^9.2.2
+      version: 9.2.2
     '@types/fs-extra':
       specifier: ^11.0.4
       version: 11.0.4
@@ -509,22 +509,46 @@ importers:
 
   packages/core:
     dependencies:
+      '@shikijs/engine-javascript':
+        specifier: workspace:*
+        version: link:../engine-javascript
+      '@shikijs/engine-oniguruma':
+        specifier: workspace:*
+        version: link:../engine-oniguruma
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       '@shikijs/vscode-textmate':
         specifier: 'catalog:'
-        version: 9.2.0
+        version: 9.2.2
       '@types/hast':
         specifier: ^3.0.4
         version: 3.0.4
+      hast-util-to-html:
+        specifier: 'catalog:'
+        version: 9.0.2
+
+  packages/engine-javascript:
+    dependencies:
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       oniguruma-to-js:
         specifier: 'catalog:'
         version: 0.3.3
       regex:
         specifier: 'catalog:'
         version: 4.3.2
-    devDependencies:
-      hast-util-to-html:
+
+  packages/engine-oniguruma:
+    dependencies:
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
+      '@shikijs/vscode-textmate':
         specifier: 'catalog:'
-        version: 9.0.2
+        version: 9.2.2
+    devDependencies:
       vscode-oniguruma:
         specifier: ^1.7.0
         version: 1.7.0
@@ -550,9 +574,12 @@ importers:
       '@shikijs/core':
         specifier: workspace:*
         version: link:../core
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       '@shikijs/vscode-textmate':
         specifier: 'catalog:'
-        version: 9.2.0
+        version: 9.2.2
     devDependencies:
       monaco-editor-core:
         specifier: 'catalog:'
@@ -572,6 +599,9 @@ importers:
       '@shikijs/transformers':
         specifier: workspace:*
         version: link:../transformers
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       '@types/hast':
         specifier: ^3.0.4
         version: 3.0.4
@@ -606,9 +636,12 @@ importers:
       '@shikijs/core':
         specifier: workspace:*
         version: link:../core
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       '@shikijs/vscode-textmate':
         specifier: 'catalog:'
-        version: 9.2.0
+        version: 9.2.2
       '@types/hast':
         specifier: ^3.0.4
         version: 3.0.4
@@ -634,6 +667,9 @@ importers:
       '@shikijs/core':
         specifier: workspace:*
         version: link:../core
+      '@shikijs/types':
+        specifier: workspace:*
+        version: link:../types
       twoslash:
         specifier: 'catalog:'
         version: 0.2.11(typescript@5.5.4)
@@ -654,6 +690,15 @@ importers:
         specifier: 'catalog:'
         version: 5.5.4
 
+  packages/types:
+    dependencies:
+      '@shikijs/vscode-textmate':
+        specifier: 'catalog:'
+        version: 9.2.2
+      '@types/hast':
+        specifier: ^3.0.4
+        version: 3.0.4
+
   packages/vitepress-twoslash:
     dependencies:
       '@shikijs/twoslash':
@@ -1628,7 +1673,7 @@ packages:
     resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+      rollup: ^4.21.2
     peerDependenciesMeta:
       rollup:
         optional: true
@@ -1780,8 +1825,8 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@shikijs/vscode-textmate@9.2.0':
-    resolution: {integrity: sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==}
+  '@shikijs/vscode-textmate@9.2.2':
+    resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==}
 
   '@stylistic/eslint-plugin@2.7.2':
     resolution: {integrity: sha512-3DVLU5HEuk2pQoBmXJlzvrxbKNpu2mJ0SRqz5O/CJjyNCr12ZiPcYMEtuArTyPOk5i7bsAU44nywh1rGfe3gKQ==}
@@ -6433,7 +6478,7 @@ snapshots:
   '@rollup/rollup-win32-x64-msvc@4.21.2':
     optional: true
 
-  '@shikijs/vscode-textmate@9.2.0': {}
+  '@shikijs/vscode-textmate@9.2.2': {}
 
   '@stylistic/eslint-plugin@2.7.2(eslint@9.10.0(jiti@1.21.6))(typescript@5.5.4)':
     dependencies:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index cbdbd4a4..35b27e84 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -19,7 +19,7 @@ catalog:
   '@rollup/plugin-node-resolve': ^15.2.3
   '@rollup/plugin-replace': ^5.0.7
   '@rollup/plugin-terser': ^0.4.4
-  '@shikijs/vscode-textmate': ^9.2.0
+  '@shikijs/vscode-textmate': ^9.2.2
   '@types/fs-extra': ^11.0.4
   '@types/hast': ^3.0.4
   '@types/markdown-it': ^14.1.2
diff --git a/tsconfig.json b/tsconfig.json
index edea8e45..26a868a3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -15,6 +15,10 @@
       "@shikijs/vitepress-twoslash/client": ["./packages/vitepress-twoslash/src/client.ts"],
       "@shikijs/vitepress-twoslash": ["./packages/vitepress-twoslash/src/index.ts"],
       "@shikijs/markdown-it": ["./packages/markdown-it/src/index.ts"],
+      "@shikijs/types": ["./packages/types/src/index.ts"],
+      "@shikijs/engine-javascript": ["./packages/engine-javascript/src/index.ts"],
+      "@shikijs/engine-oniguruma/wasm-inline": ["./packages/engine-oniguruma/src/wasm-inline.ts"],
+      "@shikijs/engine-oniguruma": ["./packages/engine-oniguruma/src/index.ts"],
       "shiki/core": ["./packages/shiki/src/core.ts"],
       "shiki/wasm": ["./packages/shiki/src/wasm.ts"],
       "shiki/langs": ["./packages/shiki/src/langs.ts"],


Test patch:
diff --git a/packages/shiki/test/engine-js/__records__/html-basic.json b/packages/engine-javascript/test/__records__/html-basic.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/html-basic.json
rename to packages/engine-javascript/test/__records__/html-basic.json
diff --git a/packages/shiki/test/engine-js/__records__/json-basic.json b/packages/engine-javascript/test/__records__/json-basic.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/json-basic.json
rename to packages/engine-javascript/test/__records__/json-basic.json
diff --git a/packages/shiki/test/engine-js/__records__/jsonc.json b/packages/engine-javascript/test/__records__/jsonc.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/jsonc.json
rename to packages/engine-javascript/test/__records__/jsonc.json
diff --git a/packages/shiki/test/engine-js/__records__/markdown.json b/packages/engine-javascript/test/__records__/markdown.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/markdown.json
rename to packages/engine-javascript/test/__records__/markdown.json
diff --git a/packages/shiki/test/engine-js/__records__/sql.json b/packages/engine-javascript/test/__records__/sql.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/sql.json
rename to packages/engine-javascript/test/__records__/sql.json
diff --git a/packages/shiki/test/engine-js/__records__/toml.json b/packages/engine-javascript/test/__records__/toml.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/toml.json
rename to packages/engine-javascript/test/__records__/toml.json
diff --git a/packages/shiki/test/engine-js/__records__/ts-basic.json b/packages/engine-javascript/test/__records__/ts-basic.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/ts-basic.json
rename to packages/engine-javascript/test/__records__/ts-basic.json
diff --git a/packages/shiki/test/engine-js/__records__/vue.json b/packages/engine-javascript/test/__records__/vue.json
similarity index 100%
rename from packages/shiki/test/engine-js/__records__/vue.json
rename to packages/engine-javascript/test/__records__/vue.json
diff --git a/packages/shiki/test/engine-js/compare.test.ts b/packages/engine-javascript/test/compare.test.ts
similarity index 77%
rename from packages/shiki/test/engine-js/compare.test.ts
rename to packages/engine-javascript/test/compare.test.ts
index b7774b5f..09feb9c0 100644
--- a/packages/shiki/test/engine-js/compare.test.ts
+++ b/packages/engine-javascript/test/compare.test.ts
@@ -1,12 +1,11 @@
-import { wasmBinary } from '@shikijs/core/wasm-inlined'
 import { describe, expect, it } from 'vitest'
-import { OnigScanner, OnigString } from '../../../core/src/engines/oniguruma'
-import { createHighlighterCore, createJavaScriptRegexEngine, loadWasm } from '../../src/core'
+import { OnigScanner, OnigString } from '../../engine-oniguruma/src/oniguruma'
+import { createHighlighterCore, createJavaScriptRegexEngine, loadWasm } from '../../shiki/src/core'
 
-import type { LanguageRegistration, RegexEngine, ThemeRegistration } from '../../src/core'
+import type { LanguageRegistration, RegexEngine, ThemeRegistration } from '../../shiki/src/core'
 import type { Instance } from './types'
 
-await loadWasm({ instantiator: obj => WebAssembly.instantiate(wasmBinary, obj) })
+await loadWasm(import('@shikijs/core/wasm-inlined'))
 
 function createWasmOnigLibWrapper(): RegexEngine & { instances: Instance[] } {
   const instances: Instance[] = []
@@ -45,8 +44,8 @@ export interface Cases {
 const cases: Cases[] = [
   {
     name: 'json-basic',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/json'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/json'),
     cases: [
       '{"foo":{"bar":1}}',
       '[undefined, null, true, false, 0, 1, 1.1, "foo", [], {}]',
@@ -54,8 +53,8 @@ const cases: Cases[] = [
   },
   {
     name: 'html-basic',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/html'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/html'),
     cases: [
       '<div class="foo">bar</div>',
       '<!DOCTYPE html><html><head><title>foo</title></head><body>bar</body></html>',
@@ -63,24 +62,24 @@ const cases: Cases[] = [
   },
   {
     name: 'ts-basic',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/typescript'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/typescript'),
     cases: [
       'const foo: string = "bar"',
     ],
   },
   {
     name: 'jsonc',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/jsonc'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/jsonc'),
     cases: [
       '// comment\n{"foo":"bar"}',
     ],
   },
   {
     name: 'vue',
-    theme: () => import('../../src/assets/themes/vitesse-dark'),
-    lang: () => import('../../src/assets/langs/vue'),
+    theme: () => import('../../shiki/src/assets/themes/vitesse-dark'),
+    lang: () => import('../../shiki/src/assets/langs/vue'),
     cases: [
       `<script setup>\nimport { ref } from 'vue'\n</script>`,
       `<template>\n<div>{{ foo }}</div>\n</template>`,
@@ -88,8 +87,8 @@ const cases: Cases[] = [
   },
   {
     name: 'toml',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/toml'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/toml'),
     cases: [
       [
         `# This is a TOML document`,
@@ -103,8 +102,8 @@ const cases: Cases[] = [
   },
   {
     name: 'sql',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/sql'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/sql'),
     cases: [
       'SELECT * FROM foo',
       [
@@ -118,8 +117,8 @@ const cases: Cases[] = [
   {
     skip: true,
     name: 'markdown',
-    theme: () => import('../../src/assets/themes/nord'),
-    lang: () => import('../../src/assets/langs/markdown'),
+    theme: () => import('../../shiki/src/assets/themes/nord'),
+    lang: () => import('../../shiki/src/assets/langs/markdown'),
     cases: [
       [
         '# Header',
diff --git a/packages/shiki/test/engine-js/general.test.ts b/packages/engine-javascript/test/general.test.ts
similarity index 94%
rename from packages/shiki/test/engine-js/general.test.ts
rename to packages/engine-javascript/test/general.test.ts
index 2a1eddac..7fbe236c 100644
--- a/packages/shiki/test/engine-js/general.test.ts
+++ b/packages/engine-javascript/test/general.test.ts
@@ -1,5 +1,6 @@
 import { describe, expect, it } from 'vitest'
-import { createHighlighter, createJavaScriptRegexEngine } from '../../src'
+import { createHighlighter } from '../../shiki/src/index'
+import { createJavaScriptRegexEngine } from '../src'
 
 describe('should', () => {
   it('works', async () => {
diff --git a/packages/shiki/test/engine-js/out/monokai-underline.html b/packages/engine-javascript/test/out/monokai-underline.html
similarity index 100%
rename from packages/shiki/test/engine-js/out/monokai-underline.html
rename to packages/engine-javascript/test/out/monokai-underline.html
diff --git a/packages/shiki/test/engine-js/types.ts b/packages/engine-javascript/test/types.ts
similarity index 100%
rename from packages/shiki/test/engine-js/types.ts
rename to packages/engine-javascript/test/types.ts
diff --git a/packages/shiki/test/engine-js/verify.test.ts b/packages/engine-javascript/test/verify.test.ts
similarity index 96%
rename from packages/shiki/test/engine-js/verify.test.ts
rename to packages/engine-javascript/test/verify.test.ts
index 3d1667ba..c4f7cf11 100644
--- a/packages/shiki/test/engine-js/verify.test.ts
+++ b/packages/engine-javascript/test/verify.test.ts
@@ -3,7 +3,7 @@ import { basename } from 'node:path'
 import { fileURLToPath } from 'node:url'
 import fg from 'fast-glob'
 import { describe, expect, it, onTestFailed } from 'vitest'
-import { JavaScriptScanner } from '../../../core/src/engines/javascript'
+import { JavaScriptScanner } from '../src'
 import type { Instance } from './types'
 
 const files = await fg('*.json', {
diff --git a/packages/shiki/test/core.test.ts b/packages/shiki/test/core.test.ts
index b46e2c9e..b4585c4d 100644
--- a/packages/shiki/test/core.test.ts
+++ b/packages/shiki/test/core.test.ts
@@ -1,6 +1,6 @@
 import { describe, expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import ts from '../src/assets/langs/typescript'
diff --git a/packages/shiki/test/get-highlighter.test.ts b/packages/shiki/test/get-highlighter.test.ts
index 0e99abdc..af277bf2 100644
--- a/packages/shiki/test/get-highlighter.test.ts
+++ b/packages/shiki/test/get-highlighter.test.ts
@@ -1,8 +1,5 @@
 import { getSingletonHighlighter } from 'shiki'
 import { expect, it } from 'vitest'
-// eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
-
 import js from '../src/assets/langs/javascript'
 import mtp from '../src/assets/themes/material-theme-palenight'
 import nord from '../src/assets/themes/nord'
@@ -13,9 +10,7 @@ it('getSingletonHighlighterCore', async () => {
   const shiki1 = await getSingletonHighlighterCore({
     themes: [nord],
     langs: [js as any],
-    loadWasm: {
-      instantiator: obj => WebAssembly.instantiate(wasmBinary, obj),
-    },
+    loadWasm: import('@shikijs/core/wasm-inlined'),
   })
 
   expect(shiki1.codeToHtml('console.log("Hi")', { lang: 'javascript', theme: 'nord' }))
diff --git a/packages/shiki/test/wasm1.test.ts b/packages/shiki/test/wasm1.test.ts
index 2fe6ae2c..a92c2a66 100644
--- a/packages/shiki/test/wasm1.test.ts
+++ b/packages/shiki/test/wasm1.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/packages/shiki/test/wasm2.test.ts b/packages/shiki/test/wasm2.test.ts
index 1edcca32..de7fb36d 100644
--- a/packages/shiki/test/wasm2.test.ts
+++ b/packages/shiki/test/wasm2.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/packages/shiki/test/wasm3.test.ts b/packages/shiki/test/wasm3.test.ts
index 408ab012..99d0025c 100644
--- a/packages/shiki/test/wasm3.test.ts
+++ b/packages/shiki/test/wasm3.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/packages/shiki/test/wasm4.test.ts b/packages/shiki/test/wasm4.test.ts
index 527525be..0f7c173f 100644
--- a/packages/shiki/test/wasm4.test.ts
+++ b/packages/shiki/test/wasm4.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/packages/shiki/test/wasm5.test.ts b/packages/shiki/test/wasm5.test.ts
index 55885601..c0894144 100644
--- a/packages/shiki/test/wasm5.test.ts
+++ b/packages/shiki/test/wasm5.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/packages/shiki/test/wasm6.test.ts b/packages/shiki/test/wasm6.test.ts
index 831055ad..c537e2e0 100644
--- a/packages/shiki/test/wasm6.test.ts
+++ b/packages/shiki/test/wasm6.test.ts
@@ -1,6 +1,6 @@
 import { expect, it } from 'vitest'
 // eslint-disable-next-line antfu/no-import-dist
-import { wasmBinary } from '../../core/dist/wasm-inlined.mjs'
+import { wasmBinary } from '../../engine-oniguruma/dist/wasm-inlined.mjs'
 
 import js from '../src/assets/langs/javascript'
 import nord from '../src/assets/themes/nord'
diff --git a/vitest.config.ts b/vitest.config.ts
index 77c2d24c..aa4c384d 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -44,10 +44,9 @@ export default defineConfig({
         '**/scripts/**',
 
         // Vendored code
-        '**/vendor/**',
-        '**/stack-element-metadata.ts',
-        '**/src/oniguruma/**',
+        'packages/engine-oniguruma/src/oniguruma/onig.ts',
         '**/src/assets/**',
+        '**/wasm-inlined.ts',
 
         // Integration packages that are hard to cover, or tested elsewhere
         'packages/cli/**',