Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Plugin and PluginManager to TypeScript #2133

Merged
merged 2 commits into from
Feb 11, 2023
Merged
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
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ packages/core/src/html/vueSlotSyntaxProcessor.js
packages/core/src/html/MdAttributeRenderer.js
packages/core/src/html/MarkdownProcessor.js
packages/core/src/html/warnings.js
packages/core/src/plugins/Plugin.js
packages/core/src/plugins/PluginManager.js

# Rules for pure JS files
packages/core/src/lib/markdown-it/patches/*
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,7 @@ packages/core/src/html/vueSlotSyntaxProcessor.js
packages/core/src/html/MdAttributeRenderer.js
packages/core/src/html/MarkdownProcessor.js
packages/core/src/html/warnings.js
packages/core/src/plugins/Plugin.js
packages/core/src/plugins/PluginManager.js

# --- packages/core end ---
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@types/jest": "^27.4.1",
"@types/js-beautify": "^1.13.3",
"@types/lodash": "^4.14.181",
"@types/node": "^18.11.19",
jovyntls marked this conversation as resolved.
Show resolved Hide resolved
"@types/nunjucks": "^3.2.1",
"@types/path-is-inside": "^1.0.0",
"@types/url-parse": "^1.4.8",
Expand Down
30 changes: 16 additions & 14 deletions packages/core/src/html/NodeProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ const FRONTMATTER_FENCE = '---';

cheerio.prototype.options.decodeEntities = false; // Don't escape HTML entities

export type NodeProcessorConfig = {
baseUrl: string,
baseUrlMap: Set<string>,
rootPath: string,
outputPath: string,
ignore: string[],
addressablePagesSource: string[],
intrasiteLinkValidation: { enabled: boolean },
codeLineNumbers: boolean,
plantumlCheck: boolean,
headerIdMap: {
[id: string]: number,
},
};

export class NodeProcessor {
frontmatter: { [key: string]: string } = {};

Expand All @@ -54,20 +69,7 @@ export class NodeProcessor {
processedModals: { [id: string]: boolean } = {};

constructor(
private config: {
baseUrl: string,
baseUrlMap: Set<string>,
rootPath: string,
outputPath: string,
ignore: string[],
addressablePagesSource: string[],
intrasiteLinkValidation: { enabled: boolean },
codeLineNumbers: boolean,
plantumlCheck: boolean,
headerIdMap: {
[id: string]: number,
},
},
private config: NodeProcessorConfig,
private pageSources: PageSources,
private variableProcessor: VariableProcessor,
private pluginManager: any,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,66 @@
const path = require('path');
const fs = require('fs-extra');
const cheerio = require('cheerio'); require('../patches/htmlparser2');
import path from 'path';
import fs from 'fs-extra';
import cheerio from 'cheerio';

import { DomElement } from 'htmlparser2';
import isString from 'lodash/isString';
import * as logger from '../utils/logger';
import * as urlUtil from '../utils/urlUtil';
import { NodeProcessorConfig } from '../html/NodeProcessor';

require('../patches/htmlparser2');

const _ = { isString };

const PLUGIN_OUTPUT_SITE_ASSET_FOLDER_NAME = 'plugins';

const logger = require('../utils/logger');
const urlUtil = require('../utils/urlUtil');
export interface PluginContext {
[key: string]: any;
}

export interface FrontMatter {
[key: string]: any;
}

type TagConfigAttributes = {
name: string,
isRelative: boolean,
isSourceFile: boolean
};

export type TagConfigs = {
isSpecial: boolean,
attributes: TagConfigAttributes[]
};

/**
* Wrapper class around a loaded plugin module
*/
class Plugin {
constructor(pluginName, pluginPath, pluginOptions, siteOutputPath) {
export class Plugin {
pluginName: string;
plugin: {
beforeSiteGenerate: (...args: any[]) => any;
getLinks: (...args: any[]) => any;
getScripts: (...args: any[]) => any;
postRender: (pluginContext: PluginContext, frontmatter: FrontMatter, content: string) => string;
processNode: (pluginContext: PluginContext, node: DomElement, config?: NodeProcessorConfig) => string;
postProcessNode: (pluginContext: PluginContext, node: DomElement, config?: NodeProcessorConfig) => string;
tagConfig: { [key: string]: TagConfigs };
};

pluginOptions: PluginContext;
pluginAbsolutePath: string;
pluginAssetOutputPath: string;

constructor(pluginName: string, pluginPath: string, pluginOptions: PluginContext, siteOutputPath: string) {
this.pluginName = pluginName;

/**
* The plugin module
* @type {Object}
*/
// eslint-disable-next-line global-require,import/no-dynamic-require
this.plugin = require(pluginPath);

/**
* @type {Object<string, any>}
*/
this.pluginOptions = pluginOptions || {};

// For resolving plugin asset source paths later
Expand All @@ -49,12 +86,12 @@ class Plugin {
* @param baseUrl baseUrl of the site
* @return String html of the element, with the attribute's asset resolved
*/
_getResolvedAssetElement(assetElementHtml, tagName, attrName, baseUrl) {
_getResolvedAssetElement(assetElementHtml: string, tagName: string, attrName: string, baseUrl: string) {
const $ = cheerio.load(assetElementHtml);
const el = $(`${tagName}[${attrName}]`);

el.attr(attrName, (i, assetPath) => {
if (!assetPath || urlUtil.isUrl(assetPath)) {
el.attr(attrName, (_i: any, assetPath: any): string => {
if (!assetPath || !_.isString(assetPath) || urlUtil.isUrl(assetPath)) {
return assetPath;
}

Expand All @@ -79,19 +116,20 @@ class Plugin {
/**
* Collect page content inserted by plugins
*/
getPageNjkLinksAndScripts(frontmatter, content, baseUrl) {
getPageNjkLinksAndScripts(frontmatter: FrontMatter, content: string, baseUrl: string) {
let links = [];
let scripts = [];

if (this.plugin.getLinks) {
const pluginLinks = this.plugin.getLinks(this.pluginOptions, frontmatter, content);
links = pluginLinks.map(linkHtml => this._getResolvedAssetElement(linkHtml, 'link', 'href', baseUrl));
links = pluginLinks.map(
(linkHtml: string) => this._getResolvedAssetElement(linkHtml, 'link', 'href', baseUrl));
}

if (this.plugin.getScripts) {
const pluginScripts = this.plugin.getScripts(this.pluginOptions, frontmatter, content);
scripts = pluginScripts.map(scriptHtml => this._getResolvedAssetElement(scriptHtml, 'script',
'src', baseUrl));
scripts = pluginScripts.map((scriptHtml: string) => this._getResolvedAssetElement(scriptHtml, 'script',
'src', baseUrl));
}

return {
Expand All @@ -100,22 +138,22 @@ class Plugin {
};
}

postRender(frontmatter, content) {
postRender(frontmatter: FrontMatter, content: string) {
if (this.plugin.postRender) {
return this.plugin.postRender(this.pluginOptions, frontmatter, content);
}
return content;
}

processNode(node, config) {
processNode(node: DomElement, config: NodeProcessorConfig) {
if (!this.plugin.processNode) {
return;
}

this.plugin.processNode(this.pluginOptions, node, config);
}

postProcessNode(node, config) {
postProcessNode(node: DomElement, config: NodeProcessorConfig) {
if (!this.plugin.postProcessNode) {
return;
}
Expand All @@ -127,7 +165,3 @@ class Plugin {
return this.plugin.tagConfig;
}
}

module.exports = {
Plugin,
};
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
const path = require('path');
const fs = require('fs-extra');
const walkSync = require('walk-sync');
import merge from 'lodash/merge';

import { DomElement } from 'htmlparser2';
import path from 'path';
import fs from 'fs-extra';
import walkSync from 'walk-sync';
import flatMap from 'lodash/flatMap';
import get from 'lodash/get';
import includes from 'lodash/includes';
import isError from 'lodash/isError';
import * as logger from '../utils/logger';
import {
FrontMatter, Plugin, PluginContext, TagConfigs,
} from './Plugin';
import { NodeProcessorConfig } from '../html/NodeProcessor';

const { ignoreTags } = require('../patches');

const { Plugin } = require('./Plugin');

const _ = {};
_.flatMap = require('lodash/flatMap');
_.get = require('lodash/get');
_.includes = require('lodash/includes');
_.merge = require('lodash/merge');

const logger = require('../utils/logger');
const _ = {
flatMap,
get,
includes,
isError,
merge,
};

const MARKBIND_PLUGIN_DIRECTORY = __dirname;
const MARKBIND_DEFAULT_PLUGIN_DIRECTORY = path.join(__dirname, 'default');
const MARKBIND_PLUGIN_PREFIX = 'markbind-plugin-';
const PROJECT_PLUGIN_FOLDER_NAME = '_markbind/plugins';

class PluginManager {
constructor(config, plugins, pluginsContext) {
type PageAsset = {
pluginScripts: string[],
pluginLinks: string[],
};

export class PluginManager {
static tagConfig: { [key: string]: TagConfigs };

config: NodeProcessorConfig;
plugins: { [key: string]: Plugin };
pluginsRaw: string[];
pluginsContextRaw: PluginContext;
htmlBeautifyOptions: { [key: string]: any };

constructor(config: NodeProcessorConfig, plugins: string[], pluginsContext: PluginContext) {
this.config = config;

/**
* @type {Object<string, Plugin>}
*/
this.plugins = {};

/**
* Raw array of plugin names as read from the site configuration
* @type {Array}
*/
this.pluginsRaw = plugins;

/**
* Raw representation of the site configuration's plugisnContext key
* @type {Object<string, Object<string, any>>}
*/
this.pluginsContextRaw = pluginsContext;

Expand All @@ -55,8 +73,6 @@ class PluginManager {
* Load all plugins of the site
*/
_collectPlugins() {
module.paths.push(path.join(this.config.rootPath, 'node_modules'));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this line because the module object is not compatible with ES6 imports. This is manually implemented below around line 146.


const defaultPluginNames = walkSync(MARKBIND_DEFAULT_PLUGIN_DIRECTORY, {
directories: false,
globs: [`${MARKBIND_PLUGIN_PREFIX}*.js`],
Expand All @@ -78,7 +94,7 @@ class PluginManager {
* @param plugin name of the plugin
* @param isDefault whether the plugin is a default plugin
*/
_loadPlugin(plugin, isDefault) {
_loadPlugin(plugin: string, isDefault: boolean) {
try {
// Check if already loaded
if (this.plugins[plugin]) {
Expand Down Expand Up @@ -108,7 +124,7 @@ class PluginManager {
* @param projectRootPath root of the MarkBind project
* @param pluginName name of the plugin
*/
static _getPluginPath(projectRootPath, pluginName) {
static _getPluginPath(projectRootPath: string, pluginName: string) {
// Check in project folder
const pluginPath = path.join(projectRootPath, PROJECT_PLUGIN_FOLDER_NAME, `${pluginName}.js`);
if (fs.existsSync(pluginPath)) {
Expand All @@ -127,7 +143,19 @@ class PluginManager {
return markbindDefaultPluginPath;
}

return require.resolve(pluginName);
// Check the environment's node_modules folders
try {
const resolvedPluginPath = require.resolve(pluginName);
return resolvedPluginPath;
} catch (err) {
// An error may be thrown because the module is not found, or for other reasons.
// If the error is due to MODULE_NOT_FOUND, search project's node_modules
if (_.isError(err) && (err as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
return require.resolve(pluginName, { paths: [path.join(projectRootPath, 'node_modules')] });
jovyntls marked this conversation as resolved.
Show resolved Hide resolved
}
// Re-throw all other errors
throw err;
}
}

/**
Expand All @@ -142,7 +170,7 @@ class PluginManager {
return;
}

Object.entries(pluginTagConfig).forEach(([tagName, tagConfig]) => {
Object.entries(pluginTagConfig).forEach(([tagName, tagConfig]: [string, TagConfigs]) => {
if (tagConfig.isSpecial) {
specialTags.add(tagName.toLowerCase());
}
Expand All @@ -168,26 +196,26 @@ class PluginManager {
/**
* Run getLinks and getScripts hooks
*/
collectPluginPageNjkAssets(frontmatter, content, pageAsset) {
collectPluginPageNjkAssets(frontmatter: FrontMatter, content: string, pageAsset: PageAsset) {
const pluginLinksAndScripts = Object.values(this.plugins)
.map(plugin => plugin.getPageNjkLinksAndScripts(frontmatter, content, this.config.baseUrl));

pageAsset.pluginLinks = _.flatMap(pluginLinksAndScripts, pluginResult => pluginResult.links);
pageAsset.pluginScripts = _.flatMap(pluginLinksAndScripts, pluginResult => pluginResult.scripts);
}

postRender(frontmatter, content) {
postRender(frontmatter: FrontMatter, content: string) {
return Object.values(this.plugins)
.reduce((renderedContent, plugin) => plugin.postRender(frontmatter, renderedContent), content);
}

processNode(node) {
processNode(node: DomElement) {
Object.values(this.plugins).forEach((plugin) => {
plugin.processNode(node, this.config);
});
}

postProcessNode(node) {
postProcessNode(node: DomElement) {
Object.values(this.plugins).forEach((plugin) => {
plugin.postProcessNode(node, this.config);
});
Expand All @@ -196,7 +224,3 @@ class PluginManager {

// Static property for easy access in linkProcessor
PluginManager.tagConfig = {};

module.exports = {
PluginManager,
};
Loading