Skip to content

Commit

Permalink
refactor: add findCompatiblePlugin to separate it from instantiatin…
Browse files Browse the repository at this point in the history
…g the plugin
  • Loading branch information
fwouts committed Oct 16, 2023
1 parent 5773312 commit e9025c0
Show file tree
Hide file tree
Showing 57 changed files with 164 additions and 135 deletions.
6 changes: 3 additions & 3 deletions chromeless/src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { startPreview } from "./preview";

export async function createChromelessWorkspace({
rootDir,
frameworkPlugins,
frameworkPlugin,
reader,
logger,
}: {
rootDir: string;
frameworkPlugins: FrameworkPluginFactory[];
frameworkPlugin: FrameworkPluginFactory;
logger?: Logger;
reader?: Reader;
}): Promise<
Expand All @@ -30,7 +30,7 @@ export async function createChromelessWorkspace({
const clientDirPath = path.join(__dirname, "..", "client", "dist");
const workspace = await createWorkspace({
rootDir,
frameworkPlugins,
frameworkPlugin,
logger,
reader,
onServerStart: async () => ({
Expand Down
64 changes: 25 additions & 39 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ import prettyLogger from "pino-pretty";
import { crawlFiles } from "./crawl-files";
import { getFreePort } from "./get-free-port";
import { extractPackageDependencies } from "./plugins/dependencies";
import type {
FrameworkPlugin,
FrameworkPluginFactory,
} from "./plugins/framework";
import type { FrameworkPluginFactory } from "./plugins/framework";
import type { OnServerStart } from "./preview-env";
import { Previewer } from "./previewer";
import { ApiRouter } from "./router";
export type { PackageDependencies } from "./plugins/dependencies";
export { findCompatiblePlugin } from "./plugins/find-compatible-plugin";
export type {
FrameworkPlugin,
FrameworkPluginFactory,
Expand All @@ -42,15 +40,9 @@ process.on("unhandledRejection", (e) => {
console.error("Encountered an unhandled promise", e);
});

export class NoCompatiblePluginError extends Error {
constructor(message: string) {
super(message);
}
}

export async function createWorkspace({
rootDir,
frameworkPlugins,
frameworkPlugin: frameworkPluginFactory,
logger = createLogger(
{ level: process.env["PREVIEWJS_LOG_LEVEL"]?.toLowerCase() || "warn" },
prettyLogger({ colorize: true })
Expand All @@ -59,47 +51,41 @@ export async function createWorkspace({
onServerStart = () => Promise.resolve({}),
}: {
rootDir: string;
frameworkPlugins: FrameworkPluginFactory[];
frameworkPlugin: FrameworkPluginFactory;
logger?: Logger;
reader?: Reader;
onServerStart?: OnServerStart;
}): Promise<Workspace> {
const dependencies = await extractPackageDependencies(logger, rootDir);
let frameworkPlugin!: FrameworkPlugin;
for (const candidate of frameworkPlugins) {
if (await candidate.isCompatible(dependencies)) {
frameworkPlugin = await candidate.create({
rootDir,
reader,
logger,
dependencies,
});
}
}
if (!frameworkPlugin) {
throw new NoCompatiblePluginError(
`No compatible plugin found for workspace with root: ${rootDir}`
const expectedPluginApiVersion = 5;
if (!frameworkPluginFactory.info) {
throw new Error(
`Provided framework plugin is incompatible with this version of Preview.js. Please upgrade it.`
);
}
logger.debug(
`Creating workspace with framework plugin ${frameworkPlugin.name} from root: ${rootDir}`
);
const expectedPluginApiVersion = 4;
if (
!frameworkPlugin.pluginApiVersion ||
frameworkPlugin.pluginApiVersion < expectedPluginApiVersion
} else if (
frameworkPluginFactory.info.apiVersion > expectedPluginApiVersion
) {
throw new Error(
`Preview.js framework plugin ${frameworkPlugin.name} is incompatible with this version of Preview.js. Please upgrade it.`
`Preview.js framework plugin ${frameworkPluginFactory.info.name} is too recent. Please upgrade Preview.js or use an older version of ${frameworkPluginFactory.info.name}.`
);
} else if (frameworkPlugin.pluginApiVersion > expectedPluginApiVersion) {
}
const dependencies = await extractPackageDependencies(logger, rootDir);
if (!(await frameworkPluginFactory.isCompatible(dependencies))) {
throw new Error(
`Preview.js framework plugin ${frameworkPlugin.name} is too recent. Please upgrade Preview.js or use an older version of ${frameworkPlugin.name}.`
`Preview.js framework plugin ${frameworkPluginFactory.info.name} is not compatible with workspace dependencies.`
);
}
logger.debug(
`Creating workspace with framework plugin ${frameworkPluginFactory.info.name} from root: ${rootDir}`
);
const frameworkPlugin = await frameworkPluginFactory.create({
rootDir,
reader,
logger,
dependencies,
});
const activePreviewers = new Set<Previewer>();
const workspace: Workspace = {
frameworkPluginName: frameworkPlugin.name,
frameworkPluginName: frameworkPluginFactory.info.name,
crawlFiles: frameworkPlugin.crawlFiles,
typeAnalyzer: frameworkPlugin.typeAnalyzer,
rootDir,
Expand Down
20 changes: 20 additions & 0 deletions core/src/plugins/find-compatible-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Logger } from "pino";
import { extractPackageDependencies } from "./dependencies";
import type { FrameworkPluginFactory } from "./framework";

export async function findCompatiblePlugin(
logger: Logger,
rootDir: string,
frameworkPlugins: FrameworkPluginFactory[]
) {
const dependencies = await extractPackageDependencies(logger, rootDir);
for (const candidate of frameworkPlugins) {
if (!candidate.info) {
continue;
}
if (await candidate.isCompatible(dependencies)) {
return candidate.info!.name;
}
}
return null;
}
15 changes: 10 additions & 5 deletions core/src/plugins/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type vite from "vite";
import type { PackageDependencies } from "./dependencies";

export interface FrameworkPluginFactory {
/** This will always be set in current plugin versions. */
info?: FrameworkPluginInfo;
isCompatible(dependencies: PackageDependencies): Promise<boolean>;
create(options: {
rootDir: string;
Expand All @@ -15,10 +17,13 @@ export interface FrameworkPluginFactory {
}

export interface FrameworkPlugin extends Analyzer {
readonly pluginApiVersion?: number;
readonly name: string;
readonly defaultWrapperPath: string;
readonly previewDirPath: string;
readonly viteConfig: (configuredPlugins: vite.Plugin[]) => vite.UserConfig;
defaultWrapperPath: string;
previewDirPath: string;
viteConfig: (configuredPlugins: vite.Plugin[]) => vite.UserConfig;
dispose(): void;
}

type FrameworkPluginInfo = {
name: string;
apiVersion: number;
};
6 changes: 4 additions & 2 deletions framework-plugins/preact/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { crawlFile } from "./crawl-file.js";
import { PREACT_SPECIAL_TYPES } from "./special-types.js";

const preactFrameworkPlugin: FrameworkPluginFactory = {
info: {
apiVersion: 5,
name: "@previewjs/plugin-preact",
},
isCompatible: async (dependencies) => {
const version = await dependencies["preact"]?.readInstalledVersion();
if (!version) {
Expand All @@ -30,8 +34,6 @@ const preactFrameworkPlugin: FrameworkPluginFactory = {
},
});
return {
pluginApiVersion: 4,
name: "@previewjs/plugin-preact",
defaultWrapperPath: "__previewjs__/Wrapper.tsx",
previewDirPath,
typeAnalyzer,
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/preact/tests/jsx.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const testApp = path.join(__dirname, "apps", "preact-app");

test.describe.parallel("preact/jsx", () => {
const test = previewTest([pluginFactory], testApp);
const test = previewTest(pluginFactory, testApp);

test("renders JSX component", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/preact/tests/refreshing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test.describe.configure({ mode: "parallel" });

test.describe("preact/refreshing", () => {
const _test = previewTest(
[pluginFactory],
pluginFactory,
path.join(__dirname, "apps", "preact-app")
);

Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/preact/tests/storybook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const testApp = path.join(__dirname, "apps", "preact-app");

test.describe.parallel("preact/storybook", () => {
const test = previewTest([pluginFactory], testApp);
const test = previewTest(pluginFactory, testApp);

test("renders CSF2 story with no args", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/preact/tests/wrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Wrapper = ({ children }: { children: ComponentChildren }) => {
test.describe.parallel("preact/wrapper", () => {
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const test = previewTest(
[pluginFactory],
pluginFactory,
path.join(__dirname, "apps", "preact-app")
);

Expand Down
8 changes: 5 additions & 3 deletions framework-plugins/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import url from "url";
import { reactImportsPlugin } from "./react-js-imports-plugin.js";

const reactFrameworkPlugin: FrameworkPluginFactory = {
info: {
apiVersion: 5,
name: "@previewjs/plugin-react",
},
isCompatible: async (dependencies) => {
const version = await dependencies["react"]?.readInstalledVersion();
if (!version) {
return false;
}
const [major, minor] = version.split(".").map((n) => parseInt(n)) as [
number,
number,
number
];
if (isNaN(major) || isNaN(minor)) {
return false;
Expand All @@ -29,8 +33,6 @@ const reactFrameworkPlugin: FrameworkPluginFactory = {
});
const previewDirPath = path.join(__dirname, "..", "preview");
return {
pluginApiVersion: 4,
name: "@previewjs/plugin-react",
defaultWrapperPath: "__previewjs__/Wrapper.tsx",
previewDirPath,
...analyzerPlugin,
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/action-logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/action logs", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("emits action event for auto-generated callbacks", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/console", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("intercepts logs", async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/default-exports.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/default exports", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("renders default export component (arrow function)", async (preview) => {
await preview.fileManager.update(
Expand Down
4 changes: 2 additions & 2 deletions framework-plugins/react/tests/error-handling.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
test.describe.parallel("react/error handling", () => {
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("handles syntax errors gracefully", async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down Expand Up @@ -327,7 +327,7 @@ test.describe.parallel("react/error handling", () => {
});
}

previewTest([pluginFactory], testApp("-sass"))(
previewTest(pluginFactory, testApp("-sass"))(
"fails correctly when encountering broken SASS",
async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/forwarded-ref.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/forwarded ref", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("renders forwarded ref component", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/jsx.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/jsx", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("renders JSX component", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/props.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/props", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("controls props", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/refreshing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/refreshing", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("renders top-level component", async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/storybook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/storybook", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("renders CSF2 story with no args", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/react/tests/wrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const testApp = (suffix: string | number) =>
for (const reactVersion of reactVersions()) {
test.describe.parallel(`v${reactVersion}`, () => {
test.describe.parallel("react/wrapper", () => {
const test = previewTest([pluginFactory], testApp(reactVersion));
const test = previewTest(pluginFactory, testApp(reactVersion));

test("refreshes when wrapper is added", async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down
6 changes: 4 additions & 2 deletions framework-plugins/solid/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { optimizeSolidDepsPlugin } from "./optimize-deps-plugin.js";
import { SOLID_SPECIAL_TYPES } from "./special-types.js";

const solidFrameworkPlugin: FrameworkPluginFactory = {
info: {
apiVersion: 5,
name: "@previewjs/plugin-solid",
},
isCompatible: async (dependencies) => {
const version = await dependencies["solid-js"]?.readInstalledVersion();
if (!version) {
Expand All @@ -30,8 +34,6 @@ const solidFrameworkPlugin: FrameworkPluginFactory = {
},
});
return {
pluginApiVersion: 4,
name: "@previewjs/plugin-solid",
defaultWrapperPath: "__previewjs__/Wrapper.tsx",
previewDirPath,
typeAnalyzer,
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/solid/tests/action-logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const testApp = path.join(__dirname, "apps", "solid");

test.describe.parallel("solid/action logs", () => {
const test = previewTest([pluginFactory], testApp);
const test = previewTest(pluginFactory, testApp);

test("emits action event for auto-generated callbacks", async (preview) => {
await preview.fileManager.update(
Expand Down
2 changes: 1 addition & 1 deletion framework-plugins/solid/tests/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const testApp = path.join(__dirname, "apps", "solid");

test.describe.parallel("solid/console", () => {
const test = previewTest([pluginFactory], testApp);
const test = previewTest(pluginFactory, testApp);

test("intercepts logs", async (preview) => {
await preview.show("src/App.tsx:App");
Expand Down
Loading

0 comments on commit e9025c0

Please sign in to comment.