Prism is awesome out of the box, but it’s even awesomer when it’s customized to your own needs. This section will help you write new language definitions, plugins and all-around Prism hacking.
Every language is defined as a set of tokens, which are expressed as regular expressions. For example, this is the language definition for CSS:
A regular expression literal is the simplest way to express a token. An alternative way, with more options, is by using an object literal. With that notation, the regular expression describing the token would be the pattern
attribute:
...
'tokenname': {
pattern: /regex/
}
...
So far the functionality is exactly the same between the short and extended notations. However, the extended notation allows for additional options:
true
,
the first capturing group in the regex pattern
is discarded when matching this token, so it effectively behaves
as if it was lookbehind. For an example of this, check out the C-like language definition, in particular the comment and class-name tokens:
rest
is useful, check the Markup definitions above.latex-equation
is not supported by any theme, but it will be highlighted the same as a string.
{
'latex-equation': {
pattern: /\$(\\?.)*?\$/g,
alias: 'string'
}
}
/* foo */
appears inside a string, you would not want it to be highlighted as a comment.
The greedy-property allows a pattern to ignore previous matches of other patterns, and
overwrite them when necessary. Use this flag with restraint, as it incurs a small performance overhead.
The following example demonstrates its usage:
'string': {
pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: true
}
Unless explicitly allowed through the inside
property, each token cannot contain other tokens, so their order is significant. Although per the ECMAScript specification, objects are not required to have a specific ordering of their properties, in practice they do in every modern browser.
In most languages there are multiple different ways of declaring the same constructs (e.g. comments, strings, ...) and sometimes it is difficult or unpractical to match all of them with one single regular expression. To add multiple regular expressions for one token name an array can be used:
...
'tokenname': [ /regex0/, /regex1/, { pattern: /regex2/ } ]
...
Prism.languages.insertBefore(inside, before, insert, root)
This is a helper method to ease modifying existing languages. For example, the CSS language definition not only defines CSS highlighting for CSS documents,
but also needs to define highlighting for CSS embedded in HTML through <style>
elements. To do this, it needs to modify
Prism.languages.markup
and add the appropriate tokens. However, Prism.languages.markup
is a regular JavaScript object literal, so if you do this:
Prism.languages.markup.style = {
/* tokens */
};
then the style
token will be added (and processed) at the end. Prism.languages.insertBefore
allows you to insert
tokens before existing tokens. For the CSS example above, you would use it like this:
Prism.languages.insertBefore('markup', 'cdata', {
'style': {
/* tokens */
}
});
root
that contains the object to be modified.Prism.languages
.This section will explain the usual workflow of creating a new language definition.
As an example, we will create the language definition of the fictional Foo's Binary, Artistic Robots™ language or just Foo Bar for short.
Create a new file components/prism-foo-bar.js
.
In this example, we choose foo-bar
as the id of the new language. The language id has to be unique and should work well with the language-xxxx
CSS class name Prism uses to refer to language definitions. Your language id should ideally match the regular expression /^[a-z][a-z\d]*(?:-[a-z][a-z\d]*)*$/
.
Edit components.json
to register the new language by adding it to the languages
object. (Please note that all language entries are sorted alphabetically by title.)
Our new entry for this example will look like this:
"foo-bar": {
"title": "Foo Bar",
"owner": "Your GitHub name"
}
If your language definition depends any other languages, you have to specify this here as well by adding a "require"
property. E.g. "require": "clike"
, or "require" : ["markup", "css"]
. For more information on dependencies read the declaring dependencies section.
Note: Any changes made to components.json
require a rebuild (see step 3).
Rebuild Prism by running npx gulp
.
This will make your language available to the test page, or more precise: your local version of it. You can open your local test.html
page in any browser, select your language, and see how your language definition highlights any code you input.
Note: You have to reload the test page to apply changes made to prism-foo-bar.js
.
Write the language definition.
The above section already explains the makeup of language definitions.
Adding aliases.
Aliases for are useful if your language is known under more than just one name or there are very common abbreviations for your language (e.g. JS for JavaScript). Keep in mind that aliases are very similar to language ids in that they also have to be unique (i.e. there cannot be an alias which is the same as another alias of language id) and work as CSS class names.
In this example, we will register the alias foo
for foo-bar
because Foo Bar code is stored in .foo
files.
To add the alias, we add this line at the end of prism-foo-bar.js
:
Prism.languages.foo = Prism.languages['foo-bar'];
Aliases also have to be registered in components.json
by adding the alias
property to the language entry. In this example, the updated entry will look like this:
"foo-bar": {
"title": "Foo Bar",
"alias": "foo",
"owner": "Your GitHub name"
}
Note: alias
can also be a string array if you need to register multiple aliases.
Using aliasTitles
, it's also possible to give aliases specific titles. In this example, this won't be necessary but a good example as to where this is useful is the markup language:
"markup": {
"title": "Markup",
"alias": ["html", "xml", "svg", "mathml"],
"aliasTitles": {
"html": "HTML",
"xml": "XML",
"svg": "SVG",
"mathml": "MathML"
},
"option": "default"
}
Add some tests.
Create a folder tests/languages/foo-bar/
. This is where your test files will live. The test format and how to run tests is described here.
You should add a test for every major feature of your language. Test files should test the common case and certain edge cases (if any). Good examples are the tests of the JavaScript language.
You can use this template for new .test
files:
The code to test.
----------------------------------------------------
[ "JSON of the simplified token stream. We will add this later." ]
----------------------------------------------------
Brief description.
For every test file:
Add the code to test and a brief description.
Verify that your language definition correctly highlights the test code. This can be done using the test page.
Note: Using the Show tokens options, you see the token stream your language definition created.
Once you carefully checked that the test case is handled correctly (i.e. by using the test page), run the following command:
npm run test:languages -- --language=foo-bar --pretty
This command will check only your test files. The new test will fail because the specified JSON is incorrect but the error message of the failed test will also include the JSON of the simplified token stream Prism created. This is what we're after. Replace the current incorrect JSON with the output labeled Token Stream. (Please also adjust the indentation. We use tabs.)
Carefully check that the token stream JSON you just inserted is what you expect.
npm run test:languages -- --language=foo-bar --pretty
to verify that the test passes.Run npm test
to check that all tests pass, not just your language tests.
This will usually pass without problems. If you can't get all the tests to pass, skip this step.
Add an example page.
Create a new file examples/prism-foo-bar.html
. This will be the template containing the example markup. Just look at other examples to see how these files are structured.
We don't have any rules as to what counts as an example, so a single Full example section where you present the highlighting of the major features of the language is enough.
Run npx gulp
again.
Languages and plugins can depend on each other, so Prism has its own dependency system to declare and resolve dependencies.
You declare a dependency by adding a property to the entry of your language or plugin in the components.json
file. The name of the property will be dependency kind and its value will be the component id of the dependee. If multiple languages or plugins are depended upon then you can also declare an array of component ids.
In the following example, we will use the require
dependency kind to declare that a fictional language Foo depends on the JavaScript language and that another fictional language Bar depends on both JavaScript and CSS.
{
"languages": {
"javascript": { "title": "JavaScript" },
"css": { "title": "CSS" },
...,
"foo": {
"title": "Foo",
"require": "javascript"
},
"bar": {
"title": "Bar",
"require": ["css", "javascript"]
}
}
}
There are 3 kinds of dependencies:
require
modify
.
This kind of dependency is most useful if you e.g. extend another language or dependee as an embedded language (e.g. like PHP is embedded in HTML).
optional
require
dependencies which also guarantee that the dependees are loaded, optional
dependencies only guarantee the order of loaded components. modify
instead.
This kind of dependency is useful if you have embedded languages but you want to give the users a choice as to whether they want to include the embedded language. By using optional
dependencies, users can better control the bundle size of Prism by only including the languages they need.
E.g. HTTP can highlight JSON and XML payloads but it doesn't force the user to include these languages.
modify
optional
dependency which also declares that the depender might modify the dependees.
This kind of dependency is useful if your language modifies another language (e.g. by adding tokens).
E.g. CSS Extras adds new tokens to the CSS language.
To summarize the properties of the different dependency kinds:
Non-optional | Optional | |
---|---|---|
Read only | require |
optional |
Modifiable | modify |
Note: You can declare a component as both require
and modify
We consider the dependencies of components an implementation detail, so they may change from release to release. Prism will usually resolve dependencies for you automatically. So you won't have to worry about dependency loading if you download a bundle or use the loadLanguages
function in NodeJS, the AutoLoader, or our Babel plugin.
If you have to resolve dependencies yourself, use the getLoader
function exported by dependencies.js
. Example:
const getLoader = require('prismjs/dependencies');
const components = require('prismjs/components');
const componentsToLoad = ['markup', 'css', 'php'];
const loadedComponents = ['clike', 'javascript'];
const loader = getLoader(components, componentsToLoad, loadedComponents);
loader.load(id => {
require(`prismjs/components/prism-${id}.min.js`);
});
For more details on the getLoader
API, check out the inline documentation.
Prism’s plugin architecture is fairly simple. To add a callback, you use Prism.hooks.add(hookname, callback)
.
hookname
is a string with the hook id, that uniquely identifies the hook your code should run at.
callback
is a function that accepts one parameter: an object with various variables that can be modified, since objects in JavaScript are passed by reference.
For example, here’s a plugin from the Markup language definition that adds a tooltip to entity tokens which shows the actual character encoded:
Prism.hooks.add('wrap', function(env) {
if (env.token === 'entity') {
env.attributes['title'] = env.content.replace(/&/, '&');
}
});
Of course, to understand which hooks to use you would have to read Prism’s source. Imagine where you would add your code and then find the appropriate hook. If there is no hook you can use, you may request one to be added, detailing why you need it there.
Prism.highlightAll(async, callback)
This is the most high-level function in Prism’s API. It fetches all the elements that have a .language-xxxx
class
and then calls Prism.highlightElement()
on each one of them.
prism.js
file for the async highlighting to work. You can build your own bundle on the Download page.
async
is true, since in that case, the highlighting is done asynchronously.
Prism.highlightAllUnder(element, async, callback)
Fetches all the descendants of element
that have a .language-xxxx
class
and then calls Prism.highlightElement()
on each one of them.
.language-xxxx
class will be highlighted.Prism.highlightAll()
Prism.highlightAll()
Prism.highlightElement(element, async, callback)
Highlights the code inside a single element.
language-xxxx
to be processed, where xxxx
is a valid language identifier.Prism.highlightAll()
Prism.highlightAll()
Prism.highlight(text, grammar, language)
Low-level function, only use if you know what you’re doing. It accepts a string of text as input, the language definitions to use, and the name of the language, and returns a string with the HTML produced.
Prism.languages.markup
grammar
. E.g. markup
or javascript
The highlighted HTML
Prism.tokenize(text, grammar)
This is the heart of Prism, and the most low-level function you can use. It accepts a string of text as input and the language definitions to use, and returns an array with the tokenized code. When the language definition includes nested tokens, the function is called recursively on each of these tokens. This method could be useful in other contexts as well, as a very crude parser.
Prism.languages.markup
An array of strings, tokens (class Prism.Token
) and other arrays.