Skip to content

Commit

Permalink
Add support for ESM based plugins
Browse files Browse the repository at this point in the history
Resolves #1635
  • Loading branch information
Gerrit0 committed Jul 25, 2022
1 parent 6ba7e26 commit 3d63da6
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Removed `Reflection#kindString`, use `ReflectionKind.singularString(reflection.kind)` or `ReflectionKind.pluralString(reflection.kind)` instead.
- Properties related to rendering are no longer stored on `Reflection`, including `url`, `anchor`, `hasOwnDocument`, and `cssClasses`.
- Removed special case for `--plugin none`, to disable plugin auto discovery, set `"plugin": []` in a config file.
- `Application.bootstrap` is now async to support ESM based plugins, #1635.

# Unreleased

Expand Down
4 changes: 2 additions & 2 deletions bin/typedoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ app.options.addReader(new td.TypeDocReader());
app.options.addReader(new td.TSConfigReader());
app.options.addReader(new td.ArgumentsReader(300));

app.bootstrap();

run(app)
.catch((error) => {
console.error("TypeDoc exiting with unexpected error:");
Expand All @@ -35,6 +33,8 @@ run(app)

/** @param {td.Application} app */
async function run(app) {
await app.bootstrap();

if (app.options.getValue("version")) {
console.log(app.toString());
return ExitCodes.Ok;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class Application extends ChildableComponent<
*
* @param options The desired options to set.
*/
bootstrap(options: Partial<TypeDocOptions> = {}): void {
async bootstrap(options: Partial<TypeDocOptions> = {}): Promise<void> {
for (const [key, val] of Object.entries(options)) {
try {
this.options.setValue(key as keyof TypeDocOptions, val);
Expand All @@ -137,7 +137,7 @@ export class Application extends ChildableComponent<
this.logger.level = this.options.getValue("logLevel");

const plugins = discoverPlugins(this);
loadPlugins(this, plugins);
await loadPlugins(this, plugins);

this.options.reset();
for (const [key, val] of Object.entries(options)) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/factories/index-signature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as assert from "assert";
import assert from "assert";
import * as ts from "typescript";
import {
DeclarationReflection,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/factories/signature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from "typescript";
import * as assert from "assert";
import assert from "assert";
import {
DeclarationReflection,
IntrinsicType,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/symbols.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as assert from "assert";
import assert from "assert";
import * as ts from "typescript";
import {
DeclarationReflection,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as assert from "assert";
import assert from "assert";
import * as ts from "typescript";
import {
ArrayType,
Expand Down
24 changes: 21 additions & 3 deletions src/lib/utils/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import * as FS from "fs";
import * as Path from "path";
import { isAbsolute } from "path";

import type { Application } from "../application";
import type { Logger } from "./loggers";
import { nicePath } from "./paths";

export function loadPlugins(app: Application, plugins: readonly string[]) {
export async function loadPlugins(
app: Application,
plugins: readonly string[]
) {
for (const plugin of plugins) {
const pluginDisplay = getPluginDisplayName(plugin);

try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const instance = require(plugin);
let instance: any;
try {
instance = require(plugin);
} catch (error: any) {
if (error.code === "ERR_REQUIRE_ESM") {
// On Windows, we need to ensure this path is a file path.
// Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME
const esmPath = isAbsolute(plugin)
? `file:///${plugin}`
: plugin;
instance = await import(esmPath);
} else {
throw error;
}
}
const initFunction = instance.load;

if (typeof initFunction === "function") {
initFunction(app);
await initFunction(app);
app.logger.info(`Loaded plugin ${pluginDisplay}`);
} else {
app.logger.error(
Expand Down
4 changes: 3 additions & 1 deletion src/test/slow/visual.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { deepStrictEqual as equal, ok } from "assert";
import { RegSuitCore } from "reg-suit-core";
// Using a require here to avoid loading types since they're busted.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { RegSuitCore } = require("reg-suit-core");
import { captureRegressionScreenshots } from "../capture-screenshots";

describe("Visual Test", () => {
Expand Down
93 changes: 93 additions & 0 deletions src/test/utils/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Project, tempdirProject } from "@typestrong/fs-fixture-builder";
import type { Application } from "../../index";
import { loadPlugins } from "../../lib/utils/plugins";
import { TestLogger } from "../TestLogger";
import { join, resolve } from "path";

describe("loadPlugins", () => {
let project: Project;
let logger: TestLogger;
const fakeApp = {} as any as Application;
beforeEach(() => {
project = tempdirProject();
logger = fakeApp.logger = new TestLogger();
});

afterEach(() => {
project.rm();
});

it("Should support loading a basic plugin", async () => {
project.addJsonFile("package.json", {
type: "commonjs",
main: "index.js",
});
project.addFile("index.js", "exports.load = function load() {}");
project.write();

const plugin = resolve(project.cwd);
await loadPlugins(fakeApp, [plugin]);
logger.expectMessage(`info: Loaded plugin ${plugin}`);
});

it("Should support loading a ESM plugin", async () => {
project.addJsonFile("package.json", {
type: "module",
main: "index.js",
});
project.addFile("index.js", "export function load() {}");
project.write();

const plugin = join(resolve(project.cwd), "index.js");
await loadPlugins(fakeApp, [plugin]);
logger.expectMessage(`info: Loaded plugin ${plugin}`);
});

it("Should handle errors when requiring plugins", async () => {
project.addJsonFile("package.json", {
type: "commonjs",
main: "index.js",
});
project.addFile("index.js", "throw Error('bad')");
project.write();

const plugin = join(resolve(project.cwd), "index.js");
await loadPlugins(fakeApp, [plugin]);
logger.expectMessage(
`error: The plugin ${plugin} could not be loaded.`
);
});

it("Should handle errors when loading plugins", async () => {
project.addJsonFile("package.json", {
type: "commonjs",
main: "index.js",
});
project.addFile(
"index.js",
"exports.load = function load() { throw Error('bad') }"
);
project.write();

const plugin = join(resolve(project.cwd), "index.js");
await loadPlugins(fakeApp, [plugin]);
logger.expectMessage(
`error: The plugin ${plugin} could not be loaded.`
);
});

it("Should handle plugins without a load method", async () => {
project.addJsonFile("package.json", {
type: "commonjs",
main: "index.js",
});
project.addFile("index.js", "");
project.write();

const plugin = join(resolve(project.cwd), "index.js");
await loadPlugins(fakeApp, [plugin]);
logger.expectMessage(
`error: Invalid structure in plugin ${plugin}, no load function found.`
);
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "CommonJS",
"module": "Node16",
"lib": ["es2020"],
"target": "es2020",

Expand Down

0 comments on commit 3d63da6

Please sign in to comment.