304 lines
7.9 KiB
JavaScript
304 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
const { src, dest, series, parallel, watch } = require('gulp');
|
|
|
|
const rename = require('gulp-rename');
|
|
const terser = require('gulp-terser');
|
|
const header = require('gulp-header');
|
|
const concat = require('gulp-concat');
|
|
const replace = require('gulp-replace');
|
|
const cleanCSS = require('gulp-clean-css');
|
|
const webfont = require('webfont').default;
|
|
const pump = require('pump');
|
|
const util = require('util');
|
|
const fs = require('fs');
|
|
|
|
const paths = require('./paths');
|
|
const { changes, linkify } = require('./changelog');
|
|
const { docs } = require('./docs');
|
|
|
|
|
|
const componentsPromise = new Promise((resolve, reject) => {
|
|
fs.readFile(paths.componentsFile, {
|
|
encoding: 'utf-8'
|
|
}, (err, data) => {
|
|
if (!err) {
|
|
resolve(JSON.parse(data));
|
|
} else {
|
|
reject(err);
|
|
}
|
|
});
|
|
});
|
|
|
|
function inlineRegexSource() {
|
|
return replace(
|
|
/\/((?:[^\n\r[\\\/]|\\.|\[(?:[^\n\r\\\]]|\\.)*\])+)\/\s*\.\s*source\b/g,
|
|
(m, source) => {
|
|
// escape backslashes
|
|
source = source.replace(/\\(.)|\[(?:\\s\\S|\\S\\s)\]/g, function (m, g1) {
|
|
if (g1) {
|
|
// characters like /\n/ can just be kept as "\n" instead of being escaped to "\\n"
|
|
if (/[nrt0/]/.test(g1)) {
|
|
return m;
|
|
}
|
|
if ('\\' == g1) {
|
|
return '\\\\\\\\'; // escape using 4 backslashes
|
|
}
|
|
return '\\\\' + g1;
|
|
} else {
|
|
return '[^]';
|
|
}
|
|
});
|
|
// escape single quotes
|
|
source = source.replace(/'/g, "\\'");
|
|
// wrap source in single quotes
|
|
return "'" + source + "'";
|
|
}
|
|
);
|
|
}
|
|
|
|
function minifyJS() {
|
|
return [
|
|
inlineRegexSource(),
|
|
terser({
|
|
ecma: 5,
|
|
compress: {
|
|
passes: 3,
|
|
},
|
|
output: {
|
|
comments: false
|
|
}
|
|
})
|
|
];
|
|
}
|
|
|
|
|
|
function minifyComponents(cb) {
|
|
pump([src(paths.components), ...minifyJS(), rename({ suffix: '.min' }), dest('components')], cb);
|
|
}
|
|
function minifyPlugins(cb) {
|
|
pump([src(paths.plugins), ...minifyJS(), rename({ suffix: '.min' }), dest('plugins')], cb);
|
|
}
|
|
function minifyPluginCSS(cb) {
|
|
pump([src(paths.pluginsCSS), cleanCSS(), rename({ suffix: '.min' }), dest('plugins')], cb);
|
|
}
|
|
function minifyThemes(cb) {
|
|
pump([src(paths.themes), cleanCSS(), rename({ suffix: '.min' }), dest('themes')], cb);
|
|
}
|
|
function build(cb) {
|
|
pump([src(paths.main), header(`
|
|
/* **********************************************
|
|
Begin <%= file.relative %>
|
|
********************************************** */
|
|
|
|
`), concat('prism.js'), dest('./')], cb);
|
|
}
|
|
|
|
async function componentsJsonToJs() {
|
|
const data = await componentsPromise;
|
|
const js = `var components = ${JSON.stringify(data)};
|
|
if (typeof module !== 'undefined' && module.exports) { module.exports = components; }`;
|
|
return util.promisify(fs.writeFile)(paths.componentsFileJS, js);
|
|
}
|
|
|
|
function watchComponentsAndPlugins() {
|
|
watch(paths.components, parallel(minifyComponents, build));
|
|
watch(paths.plugins, parallel(minifyPlugins, build));
|
|
}
|
|
|
|
async function languagePlugins() {
|
|
const data = await componentsPromise;
|
|
/** @type {Record<string, string | null>} */
|
|
const languagesMap = {};
|
|
const dependenciesMap = {};
|
|
const aliasMap = {};
|
|
|
|
/**
|
|
* Tries to guess the name of a language given its id.
|
|
*
|
|
* From `prism-show-language.js`.
|
|
*
|
|
* @param {string} id The language id.
|
|
* @returns {string}
|
|
*/
|
|
function guessTitle(id) {
|
|
if (!id) {
|
|
return id;
|
|
}
|
|
return (id.substring(0, 1).toUpperCase() + id.substring(1)).replace(/s(?=cript)/, 'S');
|
|
}
|
|
|
|
/**
|
|
* @param {string} key
|
|
* @param {string} title
|
|
*/
|
|
function addLanguageTitle(key, title) {
|
|
if (!(key in languagesMap)) {
|
|
if (guessTitle(key) === title) {
|
|
languagesMap[key] = null;
|
|
} else {
|
|
languagesMap[key] = title;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const id in data.languages) {
|
|
if (id !== 'meta') {
|
|
const language = data.languages[id];
|
|
const title = language.displayTitle || language.title;
|
|
|
|
addLanguageTitle(id, title);
|
|
|
|
for (const name in language.aliasTitles) {
|
|
addLanguageTitle(name, language.aliasTitles[name]);
|
|
}
|
|
|
|
if (language.alias) {
|
|
if (typeof language.alias === 'string') {
|
|
aliasMap[language.alias] = id;
|
|
addLanguageTitle(language.alias, title);
|
|
} else {
|
|
language.alias.forEach(function (alias) {
|
|
aliasMap[alias] = id;
|
|
addLanguageTitle(alias, title);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (language.require) {
|
|
dependenciesMap[id] = language.require;
|
|
}
|
|
}
|
|
}
|
|
|
|
function formattedStringify(json) {
|
|
return JSON.stringify(json, null, '\t').replace(/\n/g, '\n\t');
|
|
}
|
|
|
|
/** @type {Record<string, string>} */
|
|
const nonNullLanguageMap = {
|
|
'none': 'Plain text',
|
|
'plain': 'Plain text',
|
|
'plaintext': 'Plain text',
|
|
'text': 'Plain text',
|
|
'txt': 'Plain text'
|
|
};
|
|
for (const id in languagesMap) {
|
|
const title = languagesMap[id];
|
|
if (title) {
|
|
nonNullLanguageMap[id] = title;
|
|
}
|
|
}
|
|
|
|
const jsonLanguagesMap = formattedStringify(nonNullLanguageMap);
|
|
const jsonDependenciesMap = formattedStringify(dependenciesMap);
|
|
const jsonAliasMap = formattedStringify(aliasMap);
|
|
|
|
const tasks = [
|
|
{
|
|
plugin: paths.showLanguagePlugin,
|
|
maps: { languages: jsonLanguagesMap }
|
|
},
|
|
{
|
|
plugin: paths.autoloaderPlugin,
|
|
maps: { aliases: jsonAliasMap, dependencies: jsonDependenciesMap }
|
|
}
|
|
];
|
|
|
|
// TODO: Use `Promise.allSettled` (https://github.com/tc39/proposal-promise-allSettled)
|
|
const taskResults = await Promise.all(tasks.map(async task => {
|
|
try {
|
|
const value = await new Promise((resolve, reject) => {
|
|
const stream = src(task.plugin)
|
|
.pipe(replace(
|
|
/\/\*(\w+)_placeholder\[\*\/[\s\S]*?\/\*\]\*\//g,
|
|
(m, mapName) => `/*${mapName}_placeholder[*/${task.maps[mapName]}/*]*/`
|
|
))
|
|
.pipe(dest(task.plugin.substring(0, task.plugin.lastIndexOf('/'))));
|
|
|
|
stream.on('error', reject);
|
|
stream.on('end', resolve);
|
|
});
|
|
return { status: 'fulfilled', value };
|
|
} catch (error) {
|
|
return { status: 'rejected', reason: error };
|
|
}
|
|
}));
|
|
|
|
const rejectedTasks = taskResults.filter(/** @returns {r is {status: 'rejected', reason: any}} */ r => r.status === 'rejected');
|
|
if (rejectedTasks.length > 0) {
|
|
throw rejectedTasks.map(r => r.reason);
|
|
}
|
|
}
|
|
|
|
async function treeviewIconFont() {
|
|
// List of all icons
|
|
// Add new icons to the end of the list.
|
|
const iconList = [
|
|
'file', 'folder',
|
|
'image', 'audio', 'video',
|
|
'text', 'code',
|
|
'archive', 'pdf',
|
|
'excel', 'powerpoint', 'word'
|
|
];
|
|
const fontName = 'PrismTreeview';
|
|
|
|
// generate the font
|
|
const result = await webfont({
|
|
files: iconList.map(n => `plugins/treeview/icons/${n}.svg`),
|
|
formats: ['woff'],
|
|
fontName,
|
|
sort: false
|
|
});
|
|
|
|
/** @type {Buffer} */
|
|
const woff = result.woff;
|
|
/**
|
|
* @type {{ contents: string; srcPath: string; metadata: Metadata }[]}
|
|
* @typedef Metadata
|
|
* @property {string} path
|
|
* @property {string} name
|
|
* @property {string[]} unicode
|
|
* @property {boolean} renamed
|
|
* @property {number} width
|
|
* @property {number} height
|
|
* */
|
|
const glyphsData = result.glyphsData;
|
|
|
|
const fontFace = `
|
|
/* @GENERATED-FONT */
|
|
@font-face {
|
|
font-family: "${fontName}";
|
|
/**
|
|
* This font is generated from the .svg files in the \`icons\` folder. See the \`treeviewIconFont\` function in
|
|
* \`gulpfile.js/index.js\` for more information.
|
|
*
|
|
* Use the following escape sequences to refer to a specific icon:
|
|
*
|
|
* - ${glyphsData.map(({ metadata }) => {
|
|
const codePoint = metadata.unicode[0].codePointAt(0);
|
|
return `\\${codePoint.toString(16)} ${metadata.name}`;
|
|
}).join('\n\t * - ')}
|
|
*/
|
|
src: url("data:application/font-woff;base64,${woff.toString('base64')}")
|
|
format("woff");
|
|
}
|
|
`.trim();
|
|
|
|
const cssPath = 'plugins/treeview/prism-treeview.css';
|
|
const fontFaceRegex = /\/\*\s*@GENERATED-FONT\s*\*\/\s*@font-face\s*\{(?:[^{}/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\}/;
|
|
|
|
const css = fs.readFileSync(cssPath, 'utf-8');
|
|
fs.writeFileSync(cssPath, css.replace(fontFaceRegex, fontFace), 'utf-8');
|
|
}
|
|
|
|
const components = minifyComponents;
|
|
const plugins = series(languagePlugins, treeviewIconFont, minifyPlugins, minifyPluginCSS);
|
|
|
|
module.exports = {
|
|
watch: watchComponentsAndPlugins,
|
|
default: series(parallel(components, plugins, minifyThemes, componentsJsonToJs, build), docs),
|
|
linkify,
|
|
changes
|
|
};
|