Branch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
'use strict';
const fs = require('fs');
const { JSDOM } = require('jsdom');
const components = require('../../components.json');
const getLoader = require('../../dependencies');
const coreChecks = require('./checks');
const { languages: languagesCatalog, plugins: pluginsCatalog } = components;
/**
* @typedef {import('../../components/prism-core')} Prism
*/
/**
* @typedef PrismLoaderContext
* @property {Prism} Prism The Prism instance.
* @property {Set<string>} loaded A set of loaded components.
*/
/**
* @typedef {import("jsdom").DOMWindow & { Prism: Prism }} PrismWindow
*
* @typedef PrismDOM
* @property {JSDOM} dom
* @property {PrismWindow} window
* @property {Document} document
* @property {Prism} Prism
* @property {(languages: string | string[]) => void} loadLanguages
* @property {(plugins: string | string[]) => void} loadPlugins
*/
/** @type {Map<string, string>} */
const fileSourceCache = new Map();
/** @type {() => any} */
let coreSupplierFunction = null;
/** @type {Map<string, (Prism: any) => void>} */
const languageCache = new Map();
module.exports = {
/**
* Creates a new Prism instance with the given language loaded
*
* @param {string|string[]} languages
* @returns {import('../../components/prism-core')}
*/
createInstance(languages) {
let context = {
loaded: new Set(),
Prism: this.createEmptyPrism()
};
context = this.loadLanguages(languages, context);
return context.Prism;
},
/**
* Creates a new JavaScript DOM instance with Prism being loaded.
*
* @returns {PrismDOM}
*/
createPrismDOM() {
const dom = new JSDOM(``, {
runScripts: 'outside-only'
});
const window = dom.window;
window.self = window; // set self for plugins
window.eval(this.loadComponentSource('core'));
/** The set of loaded languages and plugins */
const loaded = new Set();
/**
* Loads the given languages or plugins.
*
* @param {string | string[]} languagesOrPlugins
*/
const load = (languagesOrPlugins) => {
getLoader(components, toArray(languagesOrPlugins), [...loaded]).load(id => {
let source;
if (languagesCatalog[id]) {
source = this.loadComponentSource(id);
} else if (pluginsCatalog[id]) {
source = this.loadPluginSource(id);
} else {
throw new Error(`Language or plugin '${id}' not found.`);
}
window.eval(source);
loaded.add(id);
});
};
return {
dom,
window: /** @type {PrismWindow} */ (window),
document: window.document,
Prism: window.Prism,
loadLanguages: load,
loadPlugins: load,
};
},
/**
* Loads the given languages and appends the config to the given Prism object.
*
* @private
* @param {string|string[]} languages
* @param {PrismLoaderContext} context
* @returns {PrismLoaderContext}
*/
loadLanguages(languages, context) {
getLoader(components, toArray(languages), [...context.loaded]).load(id => {
if (!languagesCatalog[id]) {
throw new Error(`Language '${id}' not found.`);
}
// get the function which adds the language from cache
let languageFunction = languageCache.get(id);
if (languageFunction === undefined) {
// make a function from the code which take "Prism" as an argument, so the language grammar
// references the function argument
const func = new Function('Prism', this.loadComponentSource(id));
languageCache.set(id, languageFunction = (Prism) => func(Prism));
}
languageFunction(context.Prism);
context.loaded.add(id);
});
return context;
},
/**
* Creates a new empty prism instance
*
* @private
* @returns {Prism}
*/
createEmptyPrism() {
if (!coreSupplierFunction) {
const source = this.loadComponentSource('core');
// Core exports itself in 2 ways:
// 1) it uses `module.exports = Prism` which what we'll use
// 2) it uses `global.Prism = Prism` which we want to sabotage to prevent leaking
const func = new Function('module', 'global', source);
coreSupplierFunction = () => {
const module = {
// that's all we care about
exports: {}
};
func(module, {});
return module.exports;
};
}
const Prism = coreSupplierFunction();
coreChecks(Prism);
return Prism;
},
/**
* Loads the given component's file source as string
*
* @private
* @param {string} name
* @returns {string}
*/
loadComponentSource(name) {
return this.loadFileSource(__dirname + '/../../components/prism-' + name + '.js');
},
/**
* Loads the given plugin's file source as string
*
* @private
* @param {string} name
* @returns {string}
*/
loadPluginSource(name) {
return this.loadFileSource(`${__dirname}/../../plugins/${name}/prism-${name}.js`);
},
/**
* Loads the given file source as string
*
* @private
* @param {string} src
* @returns {string}
*/
loadFileSource(src) {
let content = fileSourceCache.get(src);
if (content === undefined) {
fileSourceCache.set(src, content = fs.readFileSync(src, 'utf8'));
}
return content;
}
};
/**
* Wraps the given value in an array if it's not an array already.
*
* @param {T[] | T} value
* @returns {T[]}
* @template T
*/
function toArray(value) {
return Array.isArray(value) ? value : [value];
}