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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ jobs:
- name: Build packages
run: pnpm build

- name: Publish snapshot
if: ${{ github.event_name == 'pull_request' }}
run: >
./node_modules/.bin/pkg-pr-new publish --pnpm
./packages/plugins/*
./packages/integrations/*
./packages/core
./packages/ctx
./packages/exception
./packages/components
./packages/transformer
./packages/prose
./packages/utils
./packages/kit
./packages/crepe

e2e:
runs-on: ubuntu-latest
needs: install-deps
Expand Down
60 changes: 50 additions & 10 deletions dev/src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ function mergeDeep<T>(target: T, ...sources: T[]): T {
return mergeDeep(target, ...sources)
}

function viteBuild(path: string, options: BuildOptions = {}): BuildOptions {
function viteBuild(
path: string,
options: BuildOptions = {},
userExternal: CustomOptions['external'] = []
): BuildOptions {
const dir = dirname(fileURLToPath(path))
const packageDirName = basename(dir)

Expand All @@ -44,20 +48,52 @@ function viteBuild(path: string, options: BuildOptions = {}): BuildOptions {
...packageJson.peerDependencies,
...globalPackageJson.devDependencies,
}

// Auto-detect multiple entry points from package.json exports
const exports = packageJson.exports || {}
const entries: Record<string, string> = {}

for (const [key, value] of Object.entries(exports)) {
if (typeof value === 'object' && value !== null && 'import' in value) {
const importPath = (value as any).import as string
if (importPath.startsWith('./src/')) {
const entryName = key === '.' ? 'index' : key.replace('./', '')
entries[entryName] = resolve(dir, importPath.replace('./', ''))
}
}
}

// If no entries found from exports, fall back to default index.ts
if (Object.keys(entries).length === 0) {
entries.index = resolve(dir, 'src', 'index.ts')
}

const isMultiEntry = Object.keys(entries).length > 1

return mergeDeep<BuildOptions>(
{
sourcemap: true,
emptyOutDir: false,
lib: {
entry: resolve(dir, 'src', 'index.ts'),
name: `milkdown_${packageDirName}`,
fileName: 'index',
formats: ['es'],
},
lib: isMultiEntry
? {
entry: entries,
formats: ['es'],
}
: {
entry: entries.index as string,
name: `milkdown_${packageDirName}`,
fileName: 'index',
formats: ['es'],
},
rollupOptions: {
external: Array.from(new Set([...Object.keys(deps), ...external])),
external: Array.from(
new Set([...Object.keys(deps), ...external, ...userExternal])
),
output: {
dir: resolve(dir, 'lib'),
...(isMultiEntry && {
entryFileNames: '[name].js',
}),
},
},
minify: false,
Expand All @@ -67,6 +103,10 @@ function viteBuild(path: string, options: BuildOptions = {}): BuildOptions {
)
}

type CustomOptions = {
external?: (string | RegExp)[]
}

/**
* Config for plugins
*
Expand All @@ -76,10 +116,10 @@ function viteBuild(path: string, options: BuildOptions = {}): BuildOptions {
*/
export function pluginViteConfig(
packageDirName: string,
options: UserConfig = {}
options: UserConfig & CustomOptions = {}
) {
return defineConfig({
...options,
build: viteBuild(packageDirName, options.build),
build: viteBuild(packageDirName, options.build, options.external),
})
}
52 changes: 52 additions & 0 deletions docs/api/plugin-highlight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @milkdown/plugin-highlight

Highlight plugin for [milkdown](https://milkdown.dev).
Built on top of [prosemirror-highlight](https://github.com/ocavue/prosemirror-highlight).

Supports:

- [Shiki](https://github.com/shikijs/shiki)
- [Lowlight](https://github.com/robertknight/lowlight.js) (based on [Highlight.js](https://github.com/highlightjs/highlight.js))
- [Refractor](https://github.com/wooorm/refractor) (based on [Prism.js](https://github.com/PrismJS/prism))
- [Sugar high](https://github.com/huozhi/sugar-high)

@highlightPluginConfig
@highlightPlugin

## Usage

```typescript
// For shiki
import { getSingletonHighlighter } from 'shiki'
import { createParser } from '@milkdown/plugin-highlight/shiki'
const highlighter = await getSingletonHighlighter({
themes: ['github-light'],
langs: ['javascript', 'typescript', 'python'],
})
const parser = createParser(highlighter)

// For lowlight
import 'highlight.js/styles/default.css'
import { common, createLowlight } from 'lowlight'
import { createParser } from '@milkdown/plugin-highlight/lowlight'
const lowlight = createLowlight(common)
const parser = createParser(lowlight)

// For refractor
import { refractor } from 'refractor/all'
import { createParser } from '@milkdown/plugin-highlight/refractor'
const parser = createParser(refractor)

// For sugar high
import { createParser } from '@milkdown/plugin-highlight/sugar-high'
const parser = createParser()

// Setup
import { highlight, highlightPluginConfig } from '@milkdown/plugin-highlight'
Editor.make()
.config((ctx) => {
ctx.set(highlightPluginConfig.key, { parser })
})
.use(highlight)
.create()
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"lint-staged": "^16.0.0",
"oxlint": "^1.8.0",
"pathe": "^2.0.0",
"pkg-pr-new": "^0.0.58",
"postcss": "^8.4.38",
"postcss-cli": "^11.0.0",
"postcss-nested": "^7.0.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/plugins/plugin-highlight/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @milkdown/plugin-highlight

The highlight plugin of [milkdown](https://milkdown.dev/).

# Official Documentation

Documentation can be found on the [Milkdown website](https://milkdown.dev/).

# License

Milkdown is open sourced software licensed under [MIT license](https://github.com/Milkdown/milkdown/blob/main/LICENSE).
79 changes: 79 additions & 0 deletions packages/plugins/plugin-highlight/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"name": "@milkdown/plugin-highlight",
"type": "module",
"version": "7.15.5",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/Milkdown/milkdown.git",
"directory": "packages/plugins/plugin-highlight"
},
"keywords": [
"milkdown",
"milkdown plugin"
],
"sideEffects": false,
"exports": {
".": {
"import": "./src/index.ts"
},
"./lowlight": {
"import": "./src/lowlight.ts"
},
"./refractor": {
"import": "./src/refractor.ts"
},
"./shiki": {
"import": "./src/shiki.ts"
},
"./sugar-high": {
"import": "./src/sugar-high.ts"
}
},
"main": "./src/index.ts",
"publishConfig": {
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"exports": {
".": {
"import": "./lib/index.js",
"types": "./lib/index.d.ts"
},
"./lowlight": {
"import": "./lib/lowlight.js",
"types": "./lib/lowlight.d.ts"
},
"./refractor": {
"import": "./lib/refractor.js",
"types": "./lib/refractor.d.ts"
},
"./shiki": {
"import": "./lib/shiki.js",
"types": "./lib/shiki.d.ts"
},
"./sugar-high": {
"import": "./lib/sugar-high.js",
"types": "./lib/sugar-high.d.ts"
}
}
},
"scripts": {
"build": "vite build"
},
"files": [
"lib",
"src"
],
"dependencies": {
"@milkdown/core": "workspace:*",
"@milkdown/ctx": "workspace:*",
"@milkdown/prose": "workspace:*",
"@milkdown/utils": "workspace:*",
"prosemirror-highlight": "^0.13.0",
"tslib": "^2.8.1"
},
"devDependencies": {
"@milkdown/preset-commonmark": "workspace:*",
"sugar-high": "^0.9.3"
}
}
97 changes: 97 additions & 0 deletions packages/plugins/plugin-highlight/src/__test__/highlight.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import '@testing-library/jest-dom/vitest'
import { defaultValueCtx, Editor, editorViewCtx } from '@milkdown/core'
import { commonmark } from '@milkdown/preset-commonmark'
import { createParser } from 'prosemirror-highlight/sugar-high'
import { expect, it } from 'vitest'

import { highlight, highlightPluginConfig } from '..'

function createEditor() {
const parser = createParser()
const editor = Editor.make()
editor
.config((ctx) => {
ctx.set(highlightPluginConfig.key, { parser })
})
.use(commonmark)
.use(highlight)
return editor
}

const defaultValue = `
\`\`\`js
console.log('Hello, world!');
\`\`\`
`

it('should render highlighted code', async () => {
const editor = createEditor()
editor.config((ctx) => {
ctx.set(defaultValueCtx, defaultValue)
})

await editor.create()

const view = editor.ctx.get(editorViewCtx)
const dom = view.dom

const spans = dom.querySelectorAll('span')
expect(spans).toMatchInlineSnapshot(`
NodeList [
<span
class="sh__token--identifier"
style="color: var(--sh-identifier);"
>
console
</span>,
<span
class="sh__token--sign"
style="color: var(--sh-sign);"
>
.
</span>,
<span
class="sh__token--property"
style="color: var(--sh-property);"
>
log
</span>,
<span
class="sh__token--sign"
style="color: var(--sh-sign);"
>
(
</span>,
<span
class="sh__token--string"
style="color: var(--sh-string);"
>
'
</span>,
<span
class="sh__token--string"
style="color: var(--sh-string);"
>
Hello, world!
</span>,
<span
class="sh__token--string"
style="color: var(--sh-string);"
>
'
</span>,
<span
class="sh__token--sign"
style="color: var(--sh-sign);"
>
)
</span>,
<span
class="sh__token--sign"
style="color: var(--sh-sign);"
>
;
</span>,
]
`)
})
Loading
Loading