prism-code-editor
TypeScript icon, indicating that this package has built-in type declarations

3.3.0 • Public • Published

Prism code editor

Demos | API Docs

Bundle size NPM package GitHub release

Lightweight, extensible code editor component for the web using Prism.

Editor example

Why?

There are multiple fully featured code editors for the web such as Monaco, Ace and CodeMirror. While these are awesome, they have a large footprint and are likely overkill for code examples, forms, playgrounds or anywhere you won't display large documents.

How?

This library overlays syntax highlighted code over a <textarea>. Libraries like CodeFlask, react-simple-code-editor and many others have been doing this for years, but this library offers some distinct advantages:

  • It uses a trimmed Prism's core less than ⅓ the size that no longer relies on global variables.
  • It re-exports Prism's languages that now automatically import their required dependencies and embedded languages are resolved at runtime.
  • It splits the highlighted code into lines. This makes it easy to add line numbers, highlight a line and only update changed lines in the DOM for efficient updates.
  • The core is light as a feather with a wide array of extensions you can choose from and multiple events to listen to.

Contents

Installation

npm i prism-code-editor

Basic usage

The library includes 4 different setups you can import. These will automatically import the necessary styles and scope them with a shadow root, add various extensions and import all language specific behavior. There are also web components wrapping these setups if that's preferred.

These setups are very cumbersome to customize and are therefore only recommended while getting started.

import { minimalEditor, basicEditor, fullEditor, readonlyEditor } from "prism-code-editor/setups"
// Importing Prism grammars
import "prism-code-editor/prism/languages/markup"

const editor = basicEditor(
  "#editor",
  {
    language: "html",
    theme: "github-dark",
  },
  () => console.log("ready"),
)

Advanced usage

With little effort, you can fully customize which extensions are added and how they're loaded. This won't use a shadow root which makes the editor much easier to style and customize.

// index.ts
import "prism-code-editor/prism/languages/markup"
import "prism-code-editor/prism/languages/css-extras"
import "prism-code-editor/prism/languages/javascript"

import { createEditor } from "prism-code-editor"
import { matchBrackets } from "prism-code-editor/match-brackets"
import { indentGuides } from "prism-code-editor/guides"

// Importing styles
import "prism-code-editor/layout.css"
import "prism-code-editor/scrollbar.css"
import "prism-code-editor/themes/github-dark.css"

export const editor = createEditor(
  "#editor",
  { language: "html" },
  indentGuides(),
  matchBrackets(),
)
  
import("./extensions")

To minimize your main JavaScript bundle, you can dynamically import all extensions (but some probably shouldn't be).

// extensions.ts
import "prism-code-editor/search.css"
import "prism-code-editor/copy-button.css"
import "prism-code-editor/languages/html"
import "prism-code-editor/languages/clike"
import "prism-code-editor/languages/css"

import { searchWidget, highlightSelectionMatches } from "prism-code-editor/search"
import { defaultCommands, editHistory } from "prism-code-editor/commands"
import { cursorPosition } from "prism-code-editor/cursor"
import { copyButton } from "prism-code-editor/copy-button"
import { matchTags } from "prism-code-editor/match-tags"
import { highlightBracketPairs } from "prism-code-editor/highlight-brackets"
import { editor } from "./index"

editor.addExtensions(
  highlightSelectionMatches(),
  searchWidget(),
  defaultCommands(),
  copyButton(),
  matchTags(),
  highlightBracketPairs(),
  cursorPosition(),
  editHistory(),
)

Importing Prism

If you want to add your own language to Prism or perform syntax highlighting outside of an editor, this is where to import from:

import {
  // Functions
  highlightText,
  highlightTokens,
  tokenizeText,
  withoutTokenizer,
  // Record storing loaded languages
  languages,
  // Symbols used in grammars
  tokenize,
  rest,
  // Token class
  Token
} from "prism-code-editor/prism"

// Utilities used by grammars
import {
  clone, insertBefore, extend, embeddedIn
} from "prism-code-editor/prism/utils"

// To add your own language, just mutate the languages record
languages["my-language"] = {
  // ...
}

For more information about these exports, read the API documentation.

Note: CRLF and CR line breaks are not supported. So before highlighting, you might need to normalize line breaks using something like text.replace(/\r\n?/g, "\n").

Importing grammars

As you might've seen from the examples, prism grammars are imported from prism-code-editor/prism/languages/*. Importing a grammar will automatically register it through side effects. If you're importing multiple grammars, import order usually won't matter. The exception comes when grammars modify other grammars. Take this example:

import "prism-code-editor/prism/languages/typescript"
import "prism-code-editor/prism/languages/js-templates"

This won't add js-templates features to typescript because it extended javascript before js-templates was added. Swapping the import order fixes the issue.

If you need access to many languages, you can import the following entry points:

  • prism-code-editor/prism/languages for all languages (~180kB)
  • prism-code-editor/prism/languages/common for 42 common languages (~30kB)

Take this simple markdown editor as an example. Here, only the markdown grammar is required initially. The common languages are dynamically imported and once they load, the editor is updated, which will highlight all markdown code blocks.

import "prism-code-editor/prism/languages/markdown"
import { createEditor } from "prism-code-editor"

const editor = createEditor("#editor", { language: "markdown" })

import("prism-code-editor/prism/languages/common").then(() => editor.update())

Usage with Node.js

The entry points prism-code-editor/prism, prism-code-editor/prism/utils and the grammars from prism-code-editor/prism/languages/* can all run on Node.js for those who want to generate HTML with it.

Examples

Extensions

Most behavior isn't included by default and must be imported. This is to keep the core small for those who don't need the extra functionality. See advanced usage for how to add extensions.

There are extensions adding:

  • Many common commands
  • Bracket matching and rainbow brackets
  • Tag matching
  • Indentation guides
  • Search, regex search and replace
  • Selection match highlighting
  • A copy button
  • Read-only code folding
  • Custom undo/redo

The default commands extension includes:

  • Wrapping selection in brackets/quotes
  • Automatic closing of brackets, quotes, and tags
  • Automatic indentation and indentation with Tab key

And it includes these commands:

  • Alt+ArrowUp/Down: Move line up/down
  • Shift+Alt+ArrowUp/Down: Copy line up/down
  • Ctrl+Enter (Cmd+Enter on MacOS) Insert blank line
  • Ctrl+[ (Cmd+[ on MacOS): Outdent line
  • Ctrl+] (Cmd+] on MacOS): Indent line
  • Shift+Ctrl+K (Shift+Cmd+K on MacOS): Delete line
  • Ctrl+/ (Cmd+/ on MacOS): Toggle comment
  • Shift+Alt+A: Toggle block comment
  • Ctrl+M (Ctrl+Shift+M on MacOS): Toggle Tab capturing

Importing extensions

import { matchBrackets } from "prism-code-editor/match-brackets"
import { matchTags } from "prism-code-editor/match-tags"
import { indentGuides } from "prism-code-editor/guides"
import {
  searchWidget, highlightSelectionMatches, highlightCurrentWord
} from "prism-code-editor/search"
import { defaultCommands, editHistory } from "prism-code-editor/commands"
import { cursorPosition } from "prism-code-editor/cursor"
import { copyButton } from "prism-code-editor/copy-button"
import { highlightBracketPairs } from "prism-code-editor/highlight-brackets"
import {
  readOnlycodeFolding,
  markdownFolding,
  blockCommentFolding
} from "prism-code-editor/code-folding"

// And CSS
import "prism-code-editor/search.css"
import "prism-code-editor/copy-button.css"
import "prism-code-editor/code-folding.css"

Creating your own

import { Extension } from "prism-code-editor"

interface MyExtension extends Extension {}

const myExtension = (): MyExtension => {
  // Add local variables and functions in the closure

  return {
    update(editor, options) {
      // Called when the extension is added to an editor
      // And when the options change
    },
  }
}

You can also write a class with an update method if that's preferred.

You can also use a plain function as an extension. This function won't be called when the editor's options change.

import { BasicExtension, createEditor } from "prism-code-editor"

const myExtension = (): BasicExtension => {
  return (editor, options) => {
    // This won't be called when the options change
  }
}

createEditor("#editor", {}, (editor, options) => {
  // This will be called before the first render
})

Handling Tab

If you're adding the default commands to your editor, the tab key is used for indentation. If this isn't wanted, you can change the behavior.

Users can at any time toggle tab capturing with Ctrl+M / Ctrl+Shift+M (Mac).

import { setIgnoreTab } from "prism-code-editor/commands"
setIgnoreTab(true)

Styling

Importing themes

There are currently 13 different themes you can import, one of them being from prism-code-editor/themes/github-dark.css.

You can also dynamically import themes into your JavaScript. This is used by the demo website.

import { loadTheme } from "prism-code-editor/themes"

const isDark = matchMedia("(prefers-color-scheme: dark)").matches

loadTheme(isDark ? "github-dark" : "github-light").then(theme => {
  console.log(theme)
})

Scrollbar styling

You can import a stylesheet that will give a custom scrollbar to desktop Chrome and Safari.

import "prism-code-editor/scrollbar.css"

You can change the color of the scrollbar thumb using the custom property --editor__bg-scrollbar. Different alphas will be set based on the state of the scrollbar thumb.

.prism-code-editor {
  /* Values are: Hue, saturation, lightness */
  --editor__bg-scrollbar: 210, 10%, 40%;
}

Advanced styling

If you're not using any of the setups, the styles aren't scoped using a shadow root, which makes them easy to change. If you want to change color, background, font, line-height or similar, you can do it on .prism-code-editor with CSS.

Default padding is 0.75em left/right and 0.5em top/bottom. If you want to change it, you can use the custom property --padding-inline for left/right. Padding top and bottom can changed by changing the margin on .pce-wrapper.

There are many classes added to .prism-code-editor you can use to style the editor based on its state:

  • pce-has-selection if the textarea has a selection, and pce-no-selection if not
  • pce-focus if the textarea is focused
  • show-line-numbers if line numbers are enabled
  • pce-wrap if word wrap is enabled, and pce-nowrap if not
  • pce-readonly if the editor is read-only

Creating a theme

It's likely that none of the themes perfectly fit your website. A great solution is to modify one of the included themes to better suit your website. Alternatively, you can import one of the themes and override some of the styles in your on stylesheets.

Below is some additional styling information:

  • .pce-line::before will match a line number
  • .pce-line.active-line matches the line with the cursor
  • .prism-code-editor::before is the background for the line numbers
  • The variable --number-spacing is the spacing to the right of the line numbers which defaults to 0.75em

Language specific behavior

If you're not using the setups, automatic indentation, toggling comments, and automatic closing of tags won't work. You'll need to import the behavior or define it yourself.

Importing

The easiest way to get this working, is to import all languages. This will add comment toggling, etc. to almost all Prism languages at ~3.6kB gzipped. It's recommended to dynamically import this since it's usually not needed before the page has loaded.

import("prism-code-editor/languages")

You can also import prism-code-editor/languages/common instead to support a subset of common languages at less than 1kB gzipped.

Individual imports

Lastly, if you know exactly which languages you need, you can import behavior for individual languages. Refer to the list below for which imports adds comment toggling, etc. to which language(s).

List of all imports

The import for ada would be prism-code-editor/languages/ada for example. This list is NOT for Prism grammars.

Import Languages/aliases added
abap abap
abnf abnf
actionscript actionscript
ada ada
agda agda
al al
antlr4 g4 and antlr4
apacheconf apacheconf
apex apex
apl apl
applescript applescript
aql aql
arduino ino and arduino
arff arff
arturo art and arturo
asciidoc adoc and asciidoc
asm arm-asm, armasm, asm6502, asmatmel and nasm
aspnet aspnet
autohotkey autohotkey
autoit autoit
avisynth avs and avisynth
avro-idl avdl and avro-idl
awk awk
bash bash
basic basic
batch batch
bbj bbj
bicep bicep
birb birb
bison bison
bqn bqn
brightscript brightscript
bro bro
bsl bsl
cfscript cfscript
chaiscript chaiscript
cil cil
cilk cilk-c, cilkc, cilk, cilk-cpp and cilkcpp
clike clike, js, javascript, ts, typescript, java, cs, csharp, c, cpp, go, d, dart, flow and haxe
clojure clojure
cmake cmake
cobol cobol
coffeescript coffee and coffeescript
concurnas conc and concurnas
cooklang cooklang
coq coq
cshtml razor and cshtml
css css, less, scss and sass
cue cue
cypher cypher
dataweave dataweave
dax dax
dhall dhall
django jinja2 and django
dns-zone-file dns-zone and dns-zone-file
docker dockerfile and docker
dot gv and dot
ebnf ebnf
editorconfig editorconfig
eiffel eiffel
ejs ejs
elixir elixir
elm elm
erb erb
erlang erlang
etlua etlua
excel-formula xlsx, xls and excel-formula
factor factor
false false
firestore-security-rules firestore-security-rules
fortran fortran
fsharp fsharp
ftl ftl
gap gap
gcode gcode
gdscript gdscript
gettext gettext
gherkin gherkin
git git
glsl glsl and hlsl
gml gamemakerlanguage and gml
gn gni and gn
go-module go-mod and go-module
gradle gradle
graphql graphql
groovy groovy
haml haml
handlebars mustache, hbs and handlebars
haskell idr, idris, hs, haskell, purs and purescript
hcl hcl
hoon hoon
html markup, html, markdown and md
ichigojam ichigojam
icon icon
iecst iecst
ignore npmignore, hgignore, gitignore and ignore
inform7 inform7
ini ini
io io
j j
jolie jolie
jq jq
json json, json5 and jsonp
jsx jsx and tsx
julia julia
keepalived keepalived
keyman keyman
kotlin kts, kt and kotlin
kumir kumir
kusto kusto
latex context, tex and latex
latte latte
lilypond ly and lilypond
linker-script ld and linker-script
liquid liquid
lisp emacs-lisp, emacs, elisp and lisp
livescript livescript
llvm llvm
lolcode lolcode
lua lua
magma magma
makefile makefile
mata mata
matlab matlab
maxscript maxscript
mel mel
mermaid mermaid
metafont metafont
mizar mizar
mongodb mongodb
monkey monkey
moonscript moon and moonscript
n1ql n1ql
n4js n4jsd and n4js
nand2tetris-hdl nand2tetris-hdl
naniscript nani and naniscript
neon neon
nevod nevod
nginx nginx
nim nim
nix nix
nsis nsis
objectivec objc and objectivec
ocaml ocaml
odin odin
opencl opencl
openqasm qasm and openqasm
oz oz
parigp parigp
parser parser
pascal pascaligo, objectpascal and pascal
peoplecode pcode and peoplecode
perl perl
php php
plant-uml plantuml and plant-uml
powerquery mscript, pq and powerquery
powershell powershell
processing processing
prolog prolog
promql promql
properties properties
protobuf protobuf
psl psl
pug pug
puppet puppet
pure pure
purebasic pbfasm and purebasic
python rpy, renpy, py and python
q q
qml qml
qore qore
qsharp qs and qsharp
r r
reason reason
rego rego
rescript res and rescript
rest rest
rip rip
roboconf roboconf
robotframework robot and robotframework
ruby crystal, rb and ruby
rust rust
sas sas
scala scala
scheme racket and scheme
smali smali
smalltalk smalltalk
smarty smarty
sml smlnj and sml
solidity sol and solidity
solution-file sln and solution-file
soy soy
splunk-spl splunk-spl
sqf sqf
sql plsql and sql
squirrel squirrel
stan stan
stata stata
stylus stylus
supercollider sclang and supercollider
swift swift
systemd systemd
tcl tcl
textile textile
toml toml
tremor trickle, troy and tremor
tt2 tt2
turtle rq, sparql, trig and turtle
twig twig
typoscript tsconfig and typoscript
unrealscript uc, uscript and unrealscript
uorazor uorazor
v v
vala vala
vbnet vbnet
velocity velocity
verilog verilog
vhdl vhdl
vim vim
visual-basic vba, vb and visual-basic
warpscript warpscript
wasm wasm
web-idl webidl and web-idl
wgsl wgsl
wiki wiki
wolfram nb, wl, mathematica and wolfram
wren wren
xeora xeora
xml xml, ssml, atom, rss, mathml and svg
xojo xojo
xquery xquery
yaml yml and yaml
yang yang
zig zig

Adding your own

import { languageMap } from "prism-code-editor"

languageMap.whatever = {
  comments: {
    line: "//",
    block: ["/*", "*/"]
  },
  getComments(editor, position) {
    // Method called when a user executes a comment toggling command
    // Useful if a language uses different comment tokens in different contexts
    // Currently used by JSX so {/* */} is used to toggle comments in JSX contexts
  },
  autoIndent: [
    // Whether to indent
    ([start], value) => /[([{][^\n)\]}]*$/.test(code.slice(0, start)),
    // Whether to add an extra line
    ([start, end], value) => /\[]|\(\)|{}/.test(code[start - 1] + code[end])
  ],
  autoCloseTags([start, end, direction], value) {
    // Function called when the user types ">", intended to auto close tags.
    // If a string is returned, it will get inserted behind the cursor.
  }
}

Avoiding layout shifts

Adding an editor in the middle of your page will cause layout shifts. This is bad for UX and should ideally be mitigated. One way is to reserve space for the editor by giving its container a fixed height or a grid layout. This works well enough for editors with vertical scrolling.

A second solution is to have a placeholder element which gets replaced by your editor. This is the ideal solution for read-only code examples where you want height: auto instead of a vertical scroll bar. To make this easier, there's a wrapper around createEditor intended for exactly this which replaces your element instead of appending the editor to it. The placeholder element's textContent will be used as the editor's code unless options.value is defined.

import { editorFromPlaceholder } from "prism-code-editor"

const editor = editorFromPlaceholder("#editor", { language: "javascript" })

If you know the height of the editor, your placeholder could be as simple as a div with a fixed height. If not, the placeholder element should have a very similar layout to the editor, i.e., same padding, font-size, font-family, white-space, line-height. How this achieved doesn't matter, but a solution is to use similar markup to the editor itself. Here's an example of this.

Tooltips

There's a utility to display tooltips above or below the cursor that can be imported from prism-code-editor/tooltips.

const addTooltip = (editor: PrismEditor, element: HTMLElement, fixedWidth?: boolean): [ShowTooltip, HideTooltip]

const [show, hide] = addTooltip(editor, element)

If you want the tooltip to always be visible when the user scrolls horizontally, add position: sticky along with the left and/or right CSS properties to your tooltip.

Overscroll

import { addOverscroll, removeOverscroll } from "prism-code-editor/tooltips"

addOverscroll(editor)

This will allow users to scroll until the last line is at the top of the editor.

RTL Support

RTL support is disabled by default. To enable it, you need to import an extra stylesheet, only then will the rtl option do something.

import "prism-code-editor/rtl-layout.css"

RTL support is currently experimental due to multiple browser bugs being present:

  • In Chrome and Safari, the line number background won't stick when scrolling. The line numbers themselves are given a background to compensate, but this isn't perfect.
  • In Firefox, the first tab character on a line will sometimes be incorrectly sized.
  • In Safari, absolutely positioned elements inside lines can change the order of characters when mixing RTL and LTR text and tab characters are super buggy.

If you want to use RTL directionality, you should be aware of these bugs and use spaces instead of tabs for indenting.

Editing key commands

Editing key commands is as simple as mutating the keyCommandMap for the editor. If you're adding the default commands to the editor, be sure to mutate the command map after adding that extension.

// Adding a Ctrl+Alt shortcut without removing the default enter functionality
const oldEnterCallback = editor.keyCommandMap.Enter

editor.keyCommandMap.Enter = (e, selection, value) => {
  if (e.altKey) {
    // Shortcut code goes here

    // returning true will automatically call e.preventDefault()
    return true
  }
  return oldEnterCallback?.(e, selection, value)
}

// Removing the default backspace command
editor.keyCommandMap.Backspace = null

Changing editor.inputCommandMap will work the exact same way.

Web components

The library includes a custom element wrapper for each of the 4 setups you can import.

import { addBasicEditor, PrismEditorElement } from "prism-code-editor/web-component"

// Adds a web component with the specified name
addBasicEditor("prism-editor")

const editorElement = document.querySelector<PrismEditorElement>("prism-editor")

// Add a listener for when the editor finishes loading
editorElement.addEventListener("ready", () => console.log("ready"))

// The editor can be accessed from the element
console.log(editorElement.editor)

Attributes include language, theme, tab-size, line-numbers, word-wrap, readonly, insert-spaces and rtl. These attributes are also writable properties on the element.

<prism-editor
  language="javascript"
  theme="vs-code-dark"
  tab-size="4"
  line-numbers
  insert-spaces
  word-wrap
>
  The editors initial code goes here
</prism-editor>

Performance

All the code is tokenized each time for simplicity's sake. Even though only lines that change are updated in the DOM, the editor slows down as more code is added, although not as quickly as with zero optimizations.

Once you start approaching 1000 LOC, the editor will start slowing down on most hardware. If you need to display that much code, consider a more robust/heavy library.

Compatibility

This has been tested to work in the latest desktop and mobile versions of both Safari, Chrome, and Firefox. It should work in slightly older browsers too, but there will be many bugs present in browsers that don't support beforeinput events.

This library does not support any Prism plugins since Prism hooks have been removed. Behavior like the Highlight Keywords plugin is included.

Some grammars have had small changes, most notably markup tags' grammar. So Prism themes will work to style the tokens, but there can be some slight differences.

PrismJS automatically adds the global regex flag to the pattern of greedy tokens. This has been removed, so if you're using your own Prism grammars, you might have to add the global flag to the greedy tokens.

Credits

This library is made possible thanks to Prism.

Contributing

Feature requests, bug reports, optimizations and potentially new themes and extensions are all welcome.

To test your changes during development, install dependencies:

cd package
pnpm install

And run the development server:

pnpm run dev

Package Sidebar

Install

npm i prism-code-editor

Weekly Downloads

956

Version

3.3.0

License

MIT

Unpacked Size

2.48 MB

Total Files

1756

Last publish

Collaborators

  • flamecaster