Welcome to our new site! Please share feedback

Customization

When building Mitosis components, you might sometimes have unique and special needs. If you want to transform your Mitosis-generated output to fit your needs, by doing things like:

  • add a special import statement at the top of each mitosis file
  • remove a specific style attribute for one given target (for example, if you want your react-native output to omit a specific styling attribute that you rley on elsewhere.)
  • modify only some of your components to be dynamically imported

This (and much more) is possible thanks to Mitosis' powerful plugin system.

Plugins

In your directory's mitosis.config.js, you can provide a plugins array for each code generator. You have many different kinds of plugins:

export type Plugin = {
  name?: stirng;
  order?: number; // Sort plugins by number, no matter the position of the array
  build?: {
    // Happens before build
    pre?: (targetContext: TargetContext) => void | Promise<void>;
    // Happens after build
    post?: (
      targetContext: TargetContext,
      files?: {
        componentFiles: OutputFiles[];
        nonComponentFiles: OutputFiles[];
      },
    ) => void | Promise<void>;
  };
  json?: {
    // Happens before any modifiers
    pre?: (json: MitosisComponent) => MitosisComponent | void;
    // Happens after built in modifiers
    post?: (json: MitosisComponent) => MitosisComponent | void;
  };
  code?: {
    // Happens before formatting
    pre?: (code: string, json: MitosisComponent) => string;
    // Happens after formatting
    post?: (code: string, json: MitosisComponent) => string;
  };
};

We run plugins at 4 different points:

  • preJSON: before any default modifiers run on the Mitosis JSON
  • postJSON: after all built-in modifiers run on the Mitosis JSON
  • preCode: before any formatting runs on the Mitosis output (we format using prettier)
  • postCode: after any formatting runs on the Mitosis output (we format using prettier)

The JSON plugins receive the Mitosis component's full JSON object as an argument. Similarly, the code plugins receive the code string.

We even use plugins internally to generate Mitosis components! Here's an example of our react-native plugin.

You will see that we traverse the JSON nodes, and for each MitosisNode, we remove class and className values and bindings. That's because React-Native does not support class-names on mobile.

useMetadata

What happens if you want a plugin to only apply to a specific set of components? Or if you'd like to provide some metadata for your plugin, and that metadata will depend on which component is being compiled?

This is where our useMetadata hook comes in handy. All you need to do is import and use the hook (you can use it anywhere in your mitosis component file, even at the top root level!):

import { useMetadata } from '@builder.io/mitosis';

useMetadata({ mySpecialComponentType: 'ABC' });

export default function SmileReviews(props: SmileReviewsProps) {
  return <div>{/**/}</div>;
}

The metadata will be stored in your mitosis component's JSON , under json.meta.useMetadata.mySpecialComponentType. You can then use it in your JSON pre/post plugins:

const plugin = {
  json: {
    pre: (json: MitosisComponent) => {
      const myComponentType = json.meta.useMetadata?.mySpecialComponentType;
      if (myComponentType === 'ABC') {
        //...
      }
    },
  },
};

Check the example to see a complex useMetadata usage.

Component JSON

The way Mitosis' engine works is:

  • you write a .lite.jsx or .lite.tsx component
  • the mitosis JSX parser converts it to a MitosisComponent JSON
  • that JSON is fed to the generator(s) of your choice, which provide it to the plugins.

For more information on what the JSON contains, check out the documented types:

  • MitosisComponent
  • MitosisNode: Each MitosisComponent will have multiple MitosisNode under component.children. Each node represents a DOM/JSX node

Example

This is an example what you could do with a plugin. In this example we want to create a documentation for one of our components, based on the target.

The example component button.docs.lite.tsx:

/* button.docs.lite.tsx */
import { useMetadata } from '@builder.io/mitosis';
import MyButton from './my-button.lite';

useMetadata({
  docs: {
    name: "This is the name of my button"
  },
});

export default function ButtonDocs(props: any) {
  return <MyButton name={props.name}></MyButton>;
}

The mitosis config mitosis.config.cjs:

/**
 * @type {import('@builder.io/mitosis'.MitosisConfig)}
 */
module.exports = {
  files: 'src/**',
  targets: [
    'angular',
    'react',
    'vue'
  ],
  commonOptions: {
    typescript: true,
    explicitBuildFileExtensions: {
      '.md': /.*(docs\.lite\.tsx)$/g
    },
    plugins: [
      () => ({
        code: {
          post: (code, json) => {
            if (json.meta?.useMetadata?.docs) {
              return (
                `# ${json.name} - ${json.pluginData?.target}\n\n` +
                `${JSON.stringify(json.meta?.useMetadata?.docs)}\n\n` +
                'This is the content:\n' +
                '````\n' +
                code +
                '\n````'
              );
            }

            return code;
          }
        }
      })
    ]
  }
};

We generate a new button.docs.md with a content based on the target, e.g. vue:

# ButtonDocs - vue

{"name":"This is the name of my button"}

This is the content:
\````
<template>
  <MyButton :name="name"></MyButton>
</template>

<script setup lang="ts">
  import MyButton from "./my-button.vue";

  const props = defineProps(["name"]);
</script>

\````

Note: We use commonOptions here to apply the plugin to every target.

We use explicitBuildFileExtensions to transform the file extension always to .md, otherwise you would get the default extension for your target. Like .vue for vue or .tsx for react.