Skip to content
Closed
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
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
"import": "./dist/index.js",
"require": "./dist/cjs/index.js"
},
"./core.js": {
"types": "./types/core.d.ts",
"import": "./dist/core.js",
"require": "./dist/cjs/core.js"
},
"./global.js": {
"types": "./types/global.d.ts",
"import": "./dist/global.js",
"require": "./dist/cjs/global.js"
},
Comment thread
DmitrySharabin marked this conversation as resolved.
"./shared.js": {
"types": "./types/shared.d.ts",
"import": "./dist/shared.js",
Expand Down
11 changes: 8 additions & 3 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import MagicString from 'magic-string';
import { rollup } from 'rollup';
import ts from 'typescript';
import { webfont } from 'webfont';
import pkg from '../package.json' with { type: 'json' };
import components from '../src/components.json' with { type: 'json' };
import { toArray } from '../src/util/iterables.js';
import { parallel, runTask, series } from './tasks.js';
Expand Down Expand Up @@ -204,12 +205,15 @@ const dataToInsert = {
.map(({ name, title }) => [name, title])
);
},
version_placeholder: () => Promise.resolve(pkg.version),
};

/** @type {Plugin} */
const dataInsertPlugin = {
name: 'data-insert',
async renderChunk (code, chunk) {
// Replace the placeholders before tree-shaking, or Rollup takes their source values as constants:
// with `version` in `src/version.js` as `'dev'`, it drops the major-version check from every build
async transform (code, id) {
const pattern = /\/\*\s*(\w+)\[\s*\*\/[\s\S]*?\/\*\s*\]\s*\*\//g;

// search for placeholders
Expand All @@ -231,7 +235,7 @@ const dataInsertPlugin = {
dataByName[name] = await dataToInsert[name]();
}
else {
throw new Error(`Unknown placeholder ${name} in ${chunk.fileName}`);
throw new Error(`Unknown placeholder ${name} in ${id}`);
}
}

Expand Down Expand Up @@ -405,6 +409,7 @@ async function buildTypes () {
async function buildJS () {
const input = {
'index': path.join(SRC_DIR, 'index.js'),
'core': path.join(SRC_DIR, 'core.js'),
'global': path.join(SRC_DIR, 'global.js'),
'shared': path.join(SRC_DIR, 'shared.js'),
};
Expand Down Expand Up @@ -450,7 +455,7 @@ async function buildJS () {
iife: {
rollupOptions: {
...defaultRollupOptions,
input: path.join(SRC_DIR, 'auto-start.js'),
input: path.join(SRC_DIR, 'iife.js'),
plugins: [
...defaultRollupOptions.plugins.slice(0, -1), // remove default terser plugin
rollupTerser({ ...terserOptions, module: false }),
Expand Down
12 changes: 11 additions & 1 deletion src/auto-start.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import Prism from './global.js';
import { manual } from './config.js';
import Prism, { sharedPrism } from './core/prism.js';
import './plugins/autoloader/autoloader.js';
import { documentReady } from './util/async.js';

// Of all entries, only `prismjs` and the IIFE build highlight the page by themselves.
// They do so only in the copy of Prism that created the global instance,
// so a later copy of the same major version never highlights again
if (!manual && !sharedPrism) {
Prism.waitFor.push(documentReady());
Prism.ready.then(() => Prism.highlightAll()).catch(Prism.config.errorHandler);
}

export default Prism;
19 changes: 14 additions & 5 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const hasDOM = typeof document !== 'undefined' && typeof window !== 'undefined';
import Prism from './core/classes/prism.js';

export const hasDOM = typeof document !== 'undefined' && typeof window !== 'undefined';
const scriptElement = hasDOM ? document.currentScript : null;

/**
* @type {GlobalConfig}
*/
const globalConfig = globalThis.Prism?.constructor?.name === 'Object' ? globalThis.Prism : {};
const globalConfig =
typeof globalThis.Prism === 'object' && !Prism.isPrism(globalThis.Prism)
? (globalThis.Prism ?? {})
: {};

/**
* @param {string} name
Expand Down Expand Up @@ -61,16 +66,20 @@ function getGlobalArraySetting (name) {
return [];
}

/**
* Whether the auto-start entry (`prismjs` and the IIFE build) leaves highlighting the page to the user.
*/
export const manual = getGlobalBooleanSetting('manual', !hasDOM);

/**
* @type {PrismConfig}
*/
export const globalDefaults = {
manual: getGlobalBooleanSetting('manual', !hasDOM),
silent: getGlobalBooleanSetting('silent', false),
languages: getGlobalArraySetting('languages'),
plugins: getGlobalArraySetting('plugins'),
languagePath: /** @type {string} */ (getGlobalSetting('language-path') ?? './languages/'),
pluginPath: /** @type {string} */ (getGlobalSetting('plugin-path') ?? './plugins/'),
languagePath: /** @type {string | undefined} */ (getGlobalSetting('language-path')),
pluginPath: /** @type {string | undefined} */ (getGlobalSetting('plugin-path')),
};

export default globalDefaults;
Expand Down
2 changes: 1 addition & 1 deletion src/core.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { Prism } from './core/prism.js';
export { default as Prism } from './core/classes/prism.js';
export { Token } from './core/classes/token.js';
43 changes: 35 additions & 8 deletions src/core/classes/component-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default class ComponentRegistry extends EventTarget {
/**
* Path to the components, used for loading.
*
* @type {string}
* @type {string | undefined}
*/
path;

Expand All @@ -54,18 +54,20 @@ export default class ComponentRegistry extends EventTarget {

/**
*
* @param {ComponentRegistryOptions} options
* @param {ComponentRegistryOptions} [options]
*/
constructor (options) {
constructor (options = {}) {
super();

this.options = options;
let { path, preload, prism } = options;

this.prism = prism;
// Only the registry in registry.js has no instance, and it never reads `prism`
this.prism = /** @type {Prism} */ (prism);

path = path.endsWith('/') ? path : path + '/';
this.path = path;
if (path) {
this.path = path.endsWith('/') ? path : path + '/';
}
Comment thread
DmitrySharabin marked this conversation as resolved.

if (preload) {
void this.loadAll(preload);
Expand Down Expand Up @@ -155,6 +157,24 @@ export default class ComponentRegistry extends EventTarget {
return false;
}

/**
* Calls `callback` with every component the registry has now, then with each one added later.
*
* @param {(component: T) => void} callback
* @returns {() => void} Stops calls for components added later
*/
subscribe (callback) {
for (const component of Object.values(this.cache)) {
callback(component);
}

/** @param {Event} e */
let listener = e =>
callback(/** @type {CustomEvent<AddEventPayload<T>>} */ (e).detail.component);
this.addEventListener('add', listener);
return () => this.removeEventListener('add', listener);
}

/**
*
* @param {string} id
Expand Down Expand Up @@ -188,6 +208,13 @@ export default class ComponentRegistry extends EventTarget {
return this.loading[id];
}

if (!this.path) {
throw new Error(
`Cannot load "${id}" because the registry has no "path" to load component files from. ` +
'Pass "path" when you create the registry, or add the component with "add()" instead.'
);
}

const loadingComponent = import(this.path + id + '.js')
.then(m => {
/** @type {T} */
Expand Down Expand Up @@ -226,9 +253,9 @@ export default class ComponentRegistry extends EventTarget {

/**
* @typedef {object} ComponentRegistryOptions
* @property {string} path Path to the components
* @property {string} [path] Path to the components
* @property {string[]} [preload] List of component ids to preload
* @property {Prism} prism A reference to the Prism instance
* @property {Prism} [prism] A reference to the Prism instance
*/

/**
Expand Down
8 changes: 1 addition & 7 deletions src/core/classes/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ export default class Plugin extends EventTarget {

for (const def of this.require) {
// Ensure all required plugins and languages are registered
if (def.grammar) {
// We have a language definition
this.registry.prism.languageRegistry.add(def);
}
else {
this.registry.add(def);
}
this.registry.prism.register(def);
}
}

Expand Down
61 changes: 48 additions & 13 deletions src/core/classes/prism.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import globalDefaults from '../../config.js';
import { allSettled, documentReady, nextTick } from '../../util/async.js';
import { allSettled, nextTick } from '../../util/async.js';
import { version } from '../../version.js';
import { highlightAll } from '../highlight-all.js';
import { highlightElement } from '../highlight-element.js';
import { highlight } from '../highlight.js';
Expand All @@ -13,6 +13,24 @@ import PluginRegistry from './plugin-registry.js';
* In most use cases, you just need the pre-existing Prism instance, see {@link prism}.
*/
export default class Prism {
/**
* The version of Prism, such as `2.0.0`. The build fills it in, so unbuilt source has `dev`.
*/
static version = version;

/**
* Checks whether `value` is a Prism instance, even one from another copy of Prism,
* such as the IIFE build and an ESM import.
* `instanceof` and `#private` fields miss an instance of another copy, because each copy has its own class.
* So the check looks for `isPrism` on the value's class: every copy of the class has this method.
*
* @param {any} value
* @returns {value is Prism}
*/
static isPrism (value) {
return typeof value?.constructor?.isPrism === 'function';
}

/**
* @type {Hooks}
*/
Expand All @@ -31,7 +49,14 @@ export default class Prism {
/**
* @type {PrismConfig}
*/
config = globalDefaults;
config;

/**
* The registry the languages and plugins add themselves to on import, if this is the global instance.
*
* @type {ComponentRegistry<ComponentProto> | undefined}
*/
registry;

/**
* @type {Promise<unknown>[]}
Expand All @@ -47,7 +72,7 @@ export default class Prism {
* @param {PrismConfig} [config={}]
*/
constructor (config = {}) {
this.config = Object.assign({}, globalDefaults, config);
this.config = { ...config };

this.config.errorHandler ??= /** @type {PrismConfig['errorHandler']} */ (
this.config.silent ? () => undefined : console.error
Expand All @@ -56,13 +81,13 @@ export default class Prism {
const reportError = this.config.errorHandler;

this.languageRegistry = new LanguageRegistry({
path: /** @type {string} */ (this.config.languagePath),
path: this.config.languagePath ?? './languages/',
preload: this.config.languages,
prism: this,
});

this.pluginRegistry = new PluginRegistry({
path: /** @type {string} */ (this.config.pluginPath),
path: this.config.pluginPath ?? './plugins/',
prism: this,
});

Expand All @@ -77,12 +102,6 @@ export default class Prism {
.catch(reportError);
this.waitFor.push(pluginsReady);
}

if (!this.config.manual) {
this.waitFor.push(documentReady());

this.ready.then(() => this.highlightAll()).catch(reportError);
}
}

get languages () {
Expand All @@ -93,6 +112,21 @@ export default class Prism {
return this.pluginRegistry.cache;
}

/**
* Registers a language or plugin with this instance.
*
* @param {ComponentProto} def
* @returns {boolean} `false` if it was already registered
*/
register (def) {
// Only languages have a grammar
if (def.grammar) {
return this.languageRegistry.add(def);
}

return this.pluginRegistry.add(def);
}
Comment thread
DmitrySharabin marked this conversation as resolved.

/**
* Load a language by its id.
*
Expand Down Expand Up @@ -165,5 +199,6 @@ export default class Prism {
* @import { HighlightAllOptions } from '../highlight-all.js';
* @import { HighlightElementOptions } from '../highlight-element.js';
* @import { HighlightOptions } from '../highlight.js';
* @import { PrismConfig, PluginProto, Language, LanguageProto, Grammar, TokenStream } from '../../types.d.ts';
* @import ComponentRegistry from './component-registry.js';
* @import { PrismConfig, ComponentProto, PluginProto, Language, LanguageProto, Grammar, TokenStream } from '../../types.d.ts';
*/
9 changes: 3 additions & 6 deletions src/core/highlight-all.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import singleton from './prism.js';

/**
* This is the most high-level function in Prism's API.
* It queries all the elements that have a `.language-xxxx` class and then calls {@link Prism#highlightElement} on
Expand All @@ -14,7 +12,6 @@ import singleton from './prism.js';
* @param {HighlightAllOptions} [options={}]
*/
export function highlightAll (options = {}) {
const prism = this ?? singleton;
const { root, async, callback } = options;

/** @type {HookEnv} */
Expand All @@ -25,14 +22,14 @@ export function highlightAll (options = {}) {
'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
};

prism.hooks.run('before-highlightall', env);
this.hooks.run('before-highlightall', env);

env.elements = [...env.root.querySelectorAll(env.selector)];

prism.hooks.run('before-all-elements-highlight', env);
this.hooks.run('before-all-elements-highlight', env);

for (const element of env.elements) {
prism.highlightElement(element, { async, callback: env.callback });
this.highlightElement(element, { async, callback: env.callback });
}
}

Expand Down
Loading
Loading