Skip to content

Commit

Permalink
feat: add explicitFileExtension for MitosisConfig (#1635)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmerget authored Jan 7, 2025
1 parent b387d21 commit 995eb95
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 9 deletions.
34 changes: 34 additions & 0 deletions .changeset/cold-dryers-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
'@builder.io/mitosis': patch
'@builder.io/mitosis-cli': patch
---

[All] Add new `explicitBuildFileExtensions` to `MitosisConfig`. This allows users to manage the extension of some components explicitly. This is very useful for plugins:

```ts
/**
* Can be used for cli builds. Preserves explicit filename extensions when regex matches, e.g.:
* {
* explicitBuildFileExtension: {
* ".ts":/*.figma.lite.tsx/g,
* ".md":/*.docs.lite.tsx/g
* }
* }
*/
explicitBuildFileExtensions?: Record<string, RegExp>;

```

[All] Add new `pluginData` object to `MitosisComponent` which will be filled during build via cli. Users get some additional information to use them for plugins:

```ts
/**
* This data is filled inside cli to provide more data for plugins
*/
pluginData?: {
target?: Target;
path?: string;
outputDir?: string;
outputFilePath?: string;
};
```
23 changes: 23 additions & 0 deletions e2e/e2e-app/mitosis.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@ module.exports = {
],
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;
},
},
}),
],
},
options: {
angular: {
Expand Down
12 changes: 12 additions & 0 deletions e2e/e2e-app/src/components/explicit.docs.lite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMetadata } from '@builder.io/mitosis';
import ComponentWithTypes from './component-with-types.lite';

useMetadata({
docs: {
name: 'This is the name',
},
});

export default function AbcButton(props: any) {
return <ComponentWithTypes name={props.name}></ComponentWithTypes>;
}
12 changes: 9 additions & 3 deletions packages/cli/src/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ async function buildAndOutputComponentFiles({
path: overrideFilePath,
target,
});
const outputDir = `${options.dest}/${outputPath}`;

debugTarget(`transpiling ${path}...`);
let transpiled = '';
Expand All @@ -328,7 +329,14 @@ async function buildAndOutputComponentFiles({
debugTarget(`override exists for ${path}: ${!!overrideFile}`);
}
try {
const component = shouldOutputTypescript ? typescriptMitosisJson : javascriptMitosisJson;
const component: MitosisComponent = shouldOutputTypescript
? typescriptMitosisJson
: javascriptMitosisJson;

/**
* This will allow plugins to work additional data
*/
component.pluginData = { outputFilePath, outputDir, path, target };

transpiled = overrideFile ?? generator(options.options[target])({ path, component });
debugTarget(`Success: transpiled ${path}. Output length: ${transpiled.length}`);
Expand All @@ -340,8 +348,6 @@ async function buildAndOutputComponentFiles({

transpiled = transformImports({ target, options })(transpiled);

const outputDir = `${options.dest}/${outputPath}`;

await outputFile(`${outputDir}/${outputFilePath}`, transpiled);
});
return await Promise.all(output);
Expand Down
32 changes: 27 additions & 5 deletions packages/core/src/helpers/component-file-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ export const checkIsMitosisComponentFilePath = (filePath: string) =>
*/
export const INPUT_EXTENSION_REGEX = /\.(svelte|(lite(\.tsx|\.jsx)?))/g;

const getExplicitFileExtension = (
path: string,
explicitBuildFileExtensions?: Record<string, RegExp>,
): string | undefined => {
if (explicitBuildFileExtensions) {
for (const [extension, regex] of Object.entries(explicitBuildFileExtensions)) {
const match = path.match(regex);
if (match) {
return extension;
}
}
}
return undefined;
};

export const renameComponentFile = ({
path,
target,
Expand All @@ -32,15 +47,22 @@ export const renameComponentFile = ({
path: string;
target: Target;
options: MitosisConfig;
}) =>
path.replace(
INPUT_EXTENSION_REGEX,
}) => {
const explicitExtension = getExplicitFileExtension(
path,
options.options[target]?.explicitBuildFileExtensions,
);

const extension =
explicitExtension ??
getComponentFileExtensionForTarget({
type: 'filename',
target,
isTypescript: checkShouldOutputTypeScript({ options, target }),
}),
);
});

return path.replace(INPUT_EXTENSION_REGEX, extension);
};

/**
* just like `INPUT_EXTENSION_REGEX`, but adds trailing quotes to the end of import paths.
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/types/mitosis-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ export type MitosisComponent = {
propsTypeRef?: string;
defaultProps?: MitosisState;
style?: string;

/**
* This data is filled inside cli to provide more data for plugins
*/
pluginData?: {
target?: Target;
path?: string;
outputDir?: string;
outputFilePath?: string;
};

/**
* Used to store context of a component for a specific framework
* that we need access only during compilation (for internal use only) and gets removed after compilation.
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/types/transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ export interface BaseTranspilerOptions {
* Preserves explicit filename extensions in import statements.
*/
explicitImportFileExtension?: boolean;
/**
* Can be used for cli builds. Preserves explicit filename extensions when regex matches, e.g.:
* {
* explicitBuildFileExtension: {
* ".ts":/*.figma.lite.tsx/g,
* ".md":/*.docs.lite.tsx/g
* }
* }
*/
explicitBuildFileExtensions?: Record<string, RegExp>;
}
95 changes: 94 additions & 1 deletion packages/docs/src/routes/docs/customizability/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,104 @@ const plugin = {

The way Mitosis' engine works is:

- you write a `.lite.jsx` component
- 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](https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/types/mitosis-component.ts)
- [MitosisNode](https://github.com/BuilderIO/mitosis/blob/main/packages/core/src/types/mitosis-node.ts): 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`:

````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`:

````js
/**
* @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`:
````markdown
# 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.

0 comments on commit 995eb95

Please sign in to comment.