Skip to content

Commit

Permalink
Add util for webpack plugin to restrict languages
Browse files Browse the repository at this point in the history
  • Loading branch information
MattIPv4 committed May 5, 2023
1 parent 05e3a54 commit 366480a
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 13 deletions.
25 changes: 12 additions & 13 deletions modifiers/prismjs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2022 DigitalOcean
Copyright 2023 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -21,24 +21,17 @@ limitations under the License.
*/

const Prism = require('../vendor/prismjs');
const components = require('../vendor/prismjs/components');

const safeObject = require('../util/safe_object');
const findTagOpen = require('../util/find_tag_open');
const findAttr = require('../util/find_attr');
const { languages, languageAliases, getDependencies } = require('../util/prism_util');

/**
* @typedef {Object} PrismJsOptions
* @property {string} [delimiter=','] String to split fence information on.
*/

// Get languages that Prism supports
const languages = new Set(Object.keys(components.languages).filter(lang => lang !== 'meta'));
const languageAliases = Object.entries(components.languages).reduce((aliases, [ lang, { alias } ]) => {
if (alias) (Array.isArray(alias) ? alias : [ alias ]).forEach(a => { aliases[a] = lang; });
return aliases;
}, {});

/**
* Helper to load in a language if not yet loaded.
*
Expand All @@ -47,8 +40,12 @@ const languageAliases = Object.entries(components.languages).reduce((aliases, [
*/
const loadLanguage = language => {
if (language in Prism.languages) return;
// eslint-disable-next-line import/no-dynamic-require
require(`../vendor/prismjs/components/prism-${language}`)(Prism);
try {
// eslint-disable-next-line import/no-dynamic-require
require(`../vendor/prismjs/components/prism-${language}`)(Prism);
} catch (err) {
console.error('Failed to load Prism language', language, err);
}
};

// Load our HTML plugin
Expand Down Expand Up @@ -166,10 +163,12 @@ module.exports = (md, options) => {
const { before, inside, after } = extractCodeBlock(rendered, language);

// Load requirements for language
const comp = components.languages[language.clean];
if (comp.require) (Array.isArray(comp.require) ? comp.require : [ comp.require ]).forEach(loadLanguage);
getDependencies(language.clean).forEach(loadLanguage);
loadLanguage(language.clean);

// If we failed to load the language (it's a dynamic require), return original
if (!(language.clean in Prism.languages)) return rendered;

// Highlight the code with Prism
const highlighted = Prism.highlight(inside, Prism.languages[language.clean], language.clean);

Expand Down
102 changes: 102 additions & 0 deletions util/prism_util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2023 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

/**
* @module util/prism_util
*/

const regexEscape = require('./regex_escape');
const { languages: languagesData } = require('../vendor/prismjs/components');

/**
* Deduplicate an array.
*
* @param {any[]} arr Array to deduplicate.
* @returns {any[]}
* @private
*/
const dedupe = arr => Array.from(new Set(arr));

/**
* All languages that Prism supports.
*
* @type {Readonly<Set<string>>}
*/
const languages = Object.freeze(new Set(Object.keys(languagesData).filter(lang => lang !== 'meta')));

/**
* Mapped Prism aliases to their language.
*
* @type {Readonly<Map<string, string>>}
*/
const languageAliases = Object.freeze(Object.entries(languagesData).reduce((aliases, [ lang, { alias } ]) => {
if (alias) (Array.isArray(alias) ? alias : [ alias ]).forEach(a => { aliases.set(a, lang); });
return aliases;
}, new Map()));

/**
* Get all language dependencies for a Prism given language.
*
* @param {string} lang Prism language name to get dependencies for.
* @returns {string[]}
*/
const getDependencies = lang => {
if (!languages.has(lang)) throw new Error(`Unknown Prism language: ${lang}`);

const required = languagesData[lang].require || [];
const modify = languagesData[lang].modify || [];
const dependencies = [
...(Array.isArray(required)
? required
: [ required ]),
...(Array.isArray(modify)
? modify
: [ modify ]),
];
return dedupe([
...dependencies,
...dependencies.map(dep => getDependencies(dep)).flat(),
]);
};

/**
* Plugin to restrict the languages that are bundled for Prism.
*
* This plugin requires that Webpack is installed as a dependency with `ContextReplacementPlugin` available.
*
* @param {string[]} langs Prism languages to restrict to.
* @returns {import('webpack').Plugin}
*/
const restrictWebpack = langs => {
// Webpack is not a dependency, so we only load it here if the user uses this
// eslint-disable-next-line import/no-extraneous-dependencies
const { ContextReplacementPlugin } = require('webpack');

const withDependencies = dedupe(langs.map(lang => [ lang, ...getDependencies(lang) ]).flat());
return new ContextReplacementPlugin(
/@digitalocean[/\\]do-markdownit[/\\]vendor[/\\]prismjs[/\\]components$/,
new RegExp(`prism-(${[ 'core', ...withDependencies.map(dep => regexEscape(dep)) ].join('|')})\\.js$`),
);
};

module.exports = {
languages,
languageAliases,
getDependencies,
restrictWebpack,
};

0 comments on commit 366480a

Please sign in to comment.