Skip to content

Commit

Permalink
feat: add additional plugin type to run before or after cli build pro… (
Browse files Browse the repository at this point in the history
#1636)

* feat: add additional plugin type to run before or after cli build process

* chore: update changeset

* fix: cli tests

* Update packages/cli/src/build/build.ts

Co-authored-by: Sami Jaber <[email protected]>

---------

Co-authored-by: Sami Jaber <[email protected]>
  • Loading branch information
nmerget and samijaber authored Jan 13, 2025
1 parent 995eb95 commit 341f281
Show file tree
Hide file tree
Showing 20 changed files with 271 additions and 78 deletions.
6 changes: 6 additions & 0 deletions .changeset/lovely-deers-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@builder.io/mitosis': patch
'@builder.io/mitosis-cli': patch
---

[All] add additional ``build`` type for [Plugin](https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/types/plugins.ts) to allow users to run plugins before/after cli build process
114 changes: 114 additions & 0 deletions packages/cli/src/__tests__/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { componentToVue, MitosisPlugin, OutputFiles, TargetContext } from '@builder.io/mitosis';
import { describe, expect, test } from 'vitest';
import { runBuildPlugins, sortPlugins } from '../build/build';

const beforeBuild = (c) => {
console.log(`beforeBuild ${JSON.stringify(c)}`);
};
const afterbuild = (c, f) => {
console.log(`afterbuild ${JSON.stringify(c)} ${JSON.stringify(f)}`);
};
const beforeBuildSecond = (c) => {
console.log(`beforeBuildSecond ${JSON.stringify(c)}`);
};
const afterbuildSecond = (c, f) => {
console.log(`afterbuildSecond ${JSON.stringify(c)} ${JSON.stringify(f)}`);
};
const beforeBuildNo = (c) => {
console.log(`beforeBuildNo ${JSON.stringify(c)}`);
};
const afterbuildNo = (c, f) => {
console.log(`afterbuildNo ${JSON.stringify(c)} ${JSON.stringify(f)}`);
};

const testPlugins: MitosisPlugin[] = [
() => ({
name: 'no-order',
build: {
pre: beforeBuildNo,
post: afterbuildNo,
},
}),
() => ({
name: 'second',
order: 2,
build: {
pre: beforeBuildSecond,
post: afterbuildSecond,
},
}),
() => ({
name: 'first',
order: 1,
build: {
pre: beforeBuild,
post: afterbuild,
},
}),
];

describe('mitosis plugin test', () => {
test('formatHook test mixPlugin', () => {
const plugins = sortPlugins([...testPlugins]).map((plugin) => plugin());
expect(plugins).toEqual([
{
name: 'no-order',
build: {
pre: beforeBuildNo,
post: afterbuildNo,
},
},
{
name: 'first',
order: 1,
build: {
pre: beforeBuild,
post: afterbuild,
},
},
{
name: 'second',
order: 2,
build: {
pre: beforeBuildSecond,
post: afterbuildSecond,
},
},
]);
});
test('runHook test', async () => {
const _log = console.log;
const logs = [];
console.log = (str) => {
logs.push(str);
};
const c: TargetContext = {
target: 'vue',
generator: () => componentToVue(),
outputPath: '',
};
const f: {
componentFiles: OutputFiles[];
nonComponentFiles: OutputFiles[];
} = {
componentFiles: [{ outputDir: '', outputFilePath: '' }],
nonComponentFiles: [{ outputDir: '', outputFilePath: '' }],
};
await runBuildPlugins('pre', testPlugins)(c as TargetContext);
const preResult = [
`beforeBuildNo {"target":"vue","outputPath":""}`,
'beforeBuildSecond {"target":"vue","outputPath":""}',
'beforeBuild {"target":"vue","outputPath":""}',
];
expect(logs).toEqual(preResult);
await runBuildPlugins('post', testPlugins)(c, f);

expect(logs).toEqual([
...preResult,
'afterbuildNo {"target":"vue","outputPath":""} {"componentFiles":[{"outputDir":"","outputFilePath":""}],"nonComponentFiles":[{"outputDir":"","outputFilePath":""}]}',
'afterbuildSecond {"target":"vue","outputPath":""} {"componentFiles":[{"outputDir":"","outputFilePath":""}],"nonComponentFiles":[{"outputDir":"","outputFilePath":""}]}',
'afterbuild {"target":"vue","outputPath":""} {"componentFiles":[{"outputDir":"","outputFilePath":""}],"nonComponentFiles":[{"outputDir":"","outputFilePath":""}]}',
]);
console.log = _log;
});
});
80 changes: 57 additions & 23 deletions packages/cli/src/build/build.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {
BaseTranspilerOptions,
MitosisComponent,
MitosisConfig,
MitosisPlugin,
OutputFiles,
ParseMitosisOptions,
Target,
TranspilerGenerator,
TargetContext,
checkIsMitosisComponentFilePath,
checkIsSvelteComponentFilePath,
checkShouldOutputTypeScript,
Expand Down Expand Up @@ -37,6 +40,12 @@ const getTargetPath = ({ target }: { target: Target }): string => {
}
};

export const sortPlugins = (plugins?: MitosisPlugin[]): MitosisPlugin[] => {
if (!plugins) return [];

return plugins.sort((a: MitosisPlugin, b: MitosisPlugin) => (a().order || 0) - (b().order || 0));
};

const DEFAULT_CONFIG = {
targets: [],
dest: 'output',
Expand All @@ -57,11 +66,11 @@ const getOptions = (config?: MitosisConfig): MitosisConfig => {
generators: Object.assign(targets, config?.generators),
};

/**
* Apply common options to all targets
*/
if (newConfig.commonOptions) {
for (const target of newConfig.targets || []) {
for (const target of newConfig.targets || []) {
/**
* Apply common options to all targets
*/
if (newConfig.commonOptions) {
newConfig.options[target] = {
...newConfig.commonOptions,
...newConfig.options[target],
Expand All @@ -71,6 +80,9 @@ const getOptions = (config?: MitosisConfig): MitosisConfig => {
],
} as any;
}
const targetConfig: any = newConfig.options[target];
const plugins: MitosisPlugin[] | undefined = targetConfig?.plugins;
newConfig.options[target] = { ...targetConfig, plugins: sortPlugins(plugins) };
}

return newConfig;
Expand Down Expand Up @@ -116,7 +128,9 @@ const getRequiredParsers = (
): { javascript: boolean; typescript: boolean } => {
const targetsOptions = Object.values(options.options);

const targetsRequiringTypeScript = targetsOptions.filter((option) => option.typescript).length;
const targetsRequiringTypeScript = targetsOptions.filter(
(option: BaseTranspilerOptions) => option.typescript,
).length;
const needsTypeScript = targetsRequiringTypeScript > 0;

/**
Expand Down Expand Up @@ -237,12 +251,6 @@ const getMitosisComponentJSONs = async (options: MitosisConfig): Promise<ParsedM
);
};

interface TargetContext {
target: Target;
generator: TranspilerGenerator<Required<MitosisConfig['options']>[Target]>;
outputPath: string;
}

interface TargetContextWithConfig extends TargetContext {
options: MitosisConfig;
}
Expand All @@ -261,17 +269,39 @@ const buildAndOutputNonComponentFiles = async (targetContext: TargetContextWithC
return await outputNonComponentFiles({ ...targetContext, files });
};

export function runBuildPlugins(type: 'pre' | 'post', plugins: MitosisPlugin[]) {
const debugTarget = debug(`mitosis:plugin:build:${type}`);

return async (
targetContext: TargetContext,
files?: {
componentFiles: OutputFiles[];
nonComponentFiles: OutputFiles[];
},
) => {
for (let pluginFn of plugins) {
const plugin = pluginFn();
if (!plugin.build || !plugin.build[type]) continue;
debugTarget(`before run ${plugin.name ?? 'build'} ${type} plugin...`);
await plugin.build[type]?.(targetContext, files);
debugTarget(`run ${plugin.name ?? 'build'} ${type} plugin done`);
}
};
}

export async function build(config?: MitosisConfig) {
// merge default options
const options = getOptions(config);

// get all mitosis component JSONs
const mitosisComponents = await getMitosisComponentJSONs(options);

const targetContexts = getTargetContexts(options);
const targetContexts: TargetContext[] = getTargetContexts(options);

await Promise.all(
targetContexts.map(async (targetContext) => {
const plugins: MitosisPlugin[] = options?.options[targetContext.target]?.plugins ?? [];
await runBuildPlugins('pre', plugins)(targetContext);
// clean output directory
await clean(options, targetContext.target);
// clone mitosis JSONs for each target, so we can modify them in each generator without affecting future runs.
Expand All @@ -286,6 +316,10 @@ export async function build(config?: MitosisConfig) {
console.info(
`Mitosis: ${targetContext.target}: generated ${x[1].length} components, ${x[0].length} regular files.`,
);
await runBuildPlugins('post', plugins)(targetContext, {
componentFiles: x[1],
nonComponentFiles: x[0],
});
}),
);

Expand All @@ -301,7 +335,7 @@ async function buildAndOutputComponentFiles({
options,
generator,
outputPath,
}: TargetContextWithConfig & { files: ParsedMitosisJson[] }) {
}: TargetContextWithConfig & { files: ParsedMitosisJson[] }): Promise<OutputFiles[]> {
const debugTarget = debug(`mitosis:${target}`);
const shouldOutputTypescript = checkShouldOutputTypeScript({ options, target });

Expand Down Expand Up @@ -349,6 +383,7 @@ async function buildAndOutputComponentFiles({
transpiled = transformImports({ target, options })(transpiled);

await outputFile(`${outputDir}/${outputFilePath}`, transpiled);
return { outputDir, outputFilePath };
});
return await Promise.all(output);
}
Expand Down Expand Up @@ -378,15 +413,14 @@ const outputNonComponentFiles = async ({
}: TargetContext & {
files: { path: string; output: string }[];
options: MitosisConfig;
}) => {
const folderPath = `${options.dest}/${outputPath}`;
}): Promise<OutputFiles[]> => {
const outputDir = `${options.dest}/${outputPath}`;
return await Promise.all(
files.map(({ path, output }) =>
outputFile(
`${folderPath}/${getNonComponentOutputFileName({ options, path, target })}`,
output,
),
),
files.map(async ({ path, output }) => {
const outputFilePath = getNonComponentOutputFileName({ options, path, target });
await outputFile(`${outputDir}/${outputFilePath}`, output);
return { outputDir, outputFilePath };
}),
);
};

Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
builderContentToMitosisComponent,
compileAwayBuilderComponents,
GeneratorOptions,
MitosisComponent,
MitosisPlugin,
Target,
builderContentToMitosisComponent,
compileAwayBuilderComponents,
parseJsx,
parseSvelte,
Plugin,
Target,
targets,
} from '@builder.io/mitosis';
import { GluegunCommand } from 'gluegun';
Expand Down Expand Up @@ -44,7 +44,7 @@ const command: GluegunCommand = {

const header = opts.header;

const plugins: Plugin[] = [];
const plugins: MitosisPlugin[] = [];

if (!opts.builderComponents) {
plugins.push(compileAwayBuilderComponents());
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/generators/helpers/functions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { prefixWithFunction } from '@/helpers/patterns';
import { Plugin } from '../../modules/plugins';
import { MitosisPlugin } from '../../modules/plugins';

export const FUNCTION_HACK_PLUGIN: Plugin = () => ({
export const FUNCTION_HACK_PLUGIN: MitosisPlugin = () => ({
json: {
pre: (json) => {
for (const key in json.state) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/generators/qwik/component-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { MitosisComponent } from '@/types/mitosis-component';
import { TranspilerGenerator } from '@/types/transpiler';
import { format } from 'prettier/standalone';
import {
Plugin,
MitosisPlugin,
runPostCodePlugins,
runPostJsonPlugins,
runPreCodePlugins,
Expand All @@ -28,7 +28,7 @@ Error.stackTraceLimit = 9999;

const DEBUG = false;

const PLUGINS: Plugin[] = [
const PLUGINS: MitosisPlugin[] = [
() => ({
json: {
post: (json) => {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/generators/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { TranspilerGenerator } from '@/types/transpiler';
import json5 from 'json5';
import { camelCase, size } from 'lodash';
import traverse from 'neotraverse/legacy';
import { MitosisNode, Plugin } from '../..';
import { MitosisNode, MitosisPlugin } from '../..';
import { VALID_HTML_TAGS } from '../../constants/html_tags';
import { ToReactOptions, componentToReact } from '../react';
import { sanitizeReactNativeBlockStyles } from './sanitize-react-native-block-styles';
Expand Down Expand Up @@ -111,7 +111,7 @@ export const collectReactNativeStyles = (
* Plugin that handles necessary transformations from React to React Native:
* - Converts DOM tags to <View /> and <Text />
*/
const PROCESS_REACT_NATIVE_PLUGIN: Plugin = () => ({
const PROCESS_REACT_NATIVE_PLUGIN: MitosisPlugin = () => ({
json: {
pre: (json: MitosisComponent) => {
traverse(json).forEach((node) => {
Expand Down Expand Up @@ -150,7 +150,7 @@ const PROCESS_REACT_NATIVE_PLUGIN: Plugin = () => ({
/**
* Removes React Native className and class properties from the JSON
*/
const REMOVE_REACT_NATIVE_CLASSES_PLUGIN: Plugin = () => ({
const REMOVE_REACT_NATIVE_CLASSES_PLUGIN: MitosisPlugin = () => ({
json: {
pre: (json: MitosisComponent) => {
traverse(json).forEach(function (node) {
Expand All @@ -176,7 +176,7 @@ const REMOVE_REACT_NATIVE_CLASSES_PLUGIN: Plugin = () => ({
/**
* Converts class and className properties to twrnc style syntax
*/
const TWRNC_STYLES_PLUGIN: Plugin = () => ({
const TWRNC_STYLES_PLUGIN: MitosisPlugin = () => ({
json: {
post: (json: MitosisComponent) => {
traverse(json).forEach(function (node) {
Expand Down Expand Up @@ -234,7 +234,7 @@ const TWRNC_STYLES_PLUGIN: Plugin = () => ({
* Converts class and className properties to native-wind style syntax
* Note: We only support the "with babel" setup: https://www.nativewind.dev/guides/babel
*/
const NATIVE_WIND_STYLES_PLUGIN: Plugin = () => ({
const NATIVE_WIND_STYLES_PLUGIN: MitosisPlugin = () => ({
json: {
post: (json: MitosisComponent) => {
traverse(json).forEach(function (node) {
Expand Down
Loading

0 comments on commit 341f281

Please sign in to comment.