@ecopages/mdx 0.2.0-alpha.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -13
- package/README.md +34 -1
- package/package.json +2 -2
- package/src/mdx-renderer.d.ts +7 -6
- package/src/mdx-renderer.js +32 -33
- package/src/mdx-loader-plugin.ts +0 -63
- package/src/mdx-renderer.ts +0 -221
- package/src/mdx.plugin.ts +0 -141
package/CHANGELOG.md
CHANGED
|
@@ -4,30 +4,22 @@ All notable changes to `@ecopages/mdx` are documented here.
|
|
|
4
4
|
|
|
5
5
|
> **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.2.1] — 2026-04-16
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- Switched MDX compilation to the async pipeline for async remark and rehype plugin support.
|
|
11
|
+
- Added standalone non-React MDX server rendering with async compilation and opt-in `.md` support.
|
|
13
12
|
|
|
14
13
|
### Bug Fixes
|
|
15
14
|
|
|
16
|
-
- Fixed
|
|
17
|
-
- Fixed loader registration to respect configured extensions so standalone MDX no longer hijacks React `.mdx` pages during shared development and build flows.
|
|
18
|
-
- Fixed native Node startup compatibility by using Node-safe `source-map` interop.
|
|
19
|
-
|
|
20
|
-
### Refactoring
|
|
21
|
-
|
|
22
|
-
- Removed the React-specific renderer and HMR code from the package and aligned MDX with the unified orchestration pipeline.
|
|
15
|
+
- Fixed loader registration, Node `source-map` interop, and renderer-owned mixed-boundary rendering for standalone MDX routes.
|
|
23
16
|
|
|
24
17
|
### Documentation
|
|
25
18
|
|
|
26
|
-
- Updated the README for standalone MDX
|
|
19
|
+
- Updated the README for standalone non-React MDX usage, `.md` opt-in handling, and compiler configuration.
|
|
27
20
|
|
|
28
21
|
---
|
|
29
22
|
|
|
30
23
|
## Migration Notes
|
|
31
24
|
|
|
32
|
-
-
|
|
33
|
-
- The previous React-specific MDX path, including `useReact` and the React-specific HMR hooks, has been removed.
|
|
25
|
+
- Use `reactPlugin({ mdx: { enabled: true } })` for React-backed MDX routes; the standalone `@ecopages/mdx` plugin now targets non-React JSX runtimes.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ecopages/mdx
|
|
2
2
|
|
|
3
|
-
Integration plugin for standalone MDX support in Ecopages
|
|
3
|
+
Integration plugin for standalone MDX support in Ecopages for non-React JSX runtimes such as `@kitajs/html`. Use it when MDX should render directly on the server without React hydration.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -29,6 +29,39 @@ By default, the standalone plugin uses:
|
|
|
29
29
|
- `jsxImportSource: '@kitajs/html'`
|
|
30
30
|
- `jsxRuntime: 'automatic'`
|
|
31
31
|
|
|
32
|
+
## What This Integration Owns
|
|
33
|
+
|
|
34
|
+
- `.mdx` route files.
|
|
35
|
+
- Optional `.md` routes when you opt them into `extensions`.
|
|
36
|
+
- MDX compilation against a non-React JSX runtime.
|
|
37
|
+
|
|
38
|
+
## Configure Markdown Extensions
|
|
39
|
+
|
|
40
|
+
Use `extensions` when both `.mdx` and `.md` files should run through the MDX loader.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { mdxPlugin } from '@ecopages/mdx';
|
|
44
|
+
|
|
45
|
+
mdxPlugin({
|
|
46
|
+
extensions: ['.mdx', '.md'],
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Compiler Options
|
|
51
|
+
|
|
52
|
+
Pass `compilerOptions` to add remark, rehype, or recma plugins while keeping the non-React JSX runtime managed by the integration.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { mdxPlugin } from '@ecopages/mdx';
|
|
56
|
+
|
|
57
|
+
mdxPlugin({
|
|
58
|
+
compilerOptions: {
|
|
59
|
+
remarkPlugins: [],
|
|
60
|
+
rehypePlugins: [],
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
32
65
|
> [!WARNING]
|
|
33
66
|
> React runtimes are intentionally rejected by this standalone plugin.
|
|
34
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/mdx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MDX plugin for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"directory": "packages/integrations/mdx"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@ecopages/core": "0.2.
|
|
40
|
+
"@ecopages/core": "0.2.1",
|
|
41
41
|
"@kitajs/html": "^4.1.0",
|
|
42
42
|
"@mdx-js/mdx": "^3.1.0"
|
|
43
43
|
},
|
package/src/mdx-renderer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* This module contains the MDX renderer
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
-
import type { EcoComponent, EcoComponentConfig, EcoPageFile, EcoPagesElement, GetMetadata, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
|
|
5
|
+
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentConfig, EcoPageFile, EcoPagesElement, GetMetadata, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
|
|
6
6
|
import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
|
|
7
7
|
import type { AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
8
8
|
import type { CompileOptions } from '@mdx-js/mdx';
|
|
@@ -33,11 +33,12 @@ export declare class MDXRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
33
33
|
compilerOptions?: CompileOptions;
|
|
34
34
|
});
|
|
35
35
|
buildRouteRenderAssets(pagePath: string): Promise<ProcessedAsset[]>;
|
|
36
|
-
protected
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(_file: string, pageModule: TPageModule): TPageModule;
|
|
37
|
+
renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
|
|
38
|
+
protected createComponentBoundaryRuntime(options: {
|
|
39
|
+
boundaryInput: ComponentRenderInput;
|
|
40
|
+
rendererCache: Map<string, IntegrationRenderer<any>>;
|
|
41
|
+
}): import("@ecopages/core").ComponentBoundaryRuntime;
|
|
41
42
|
render({ params, query, props, locals, pageLocals, metadata, Page, HtmlTemplate, Layout, pageProps, }: MDXIntegrationRendererOpions): Promise<RouteRendererBody>;
|
|
42
43
|
renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
|
|
43
44
|
}
|
package/src/mdx-renderer.js
CHANGED
|
@@ -34,19 +34,17 @@ class MDXRenderer extends IntegrationRenderer {
|
|
|
34
34
|
}
|
|
35
35
|
return await this.resolveDependencies(components);
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
normalizeImportedPageFile(_file, pageModule) {
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
config,
|
|
42
|
-
getMetadata
|
|
43
|
-
} = await super.importPageFile(file);
|
|
39
|
+
const mdxModule = pageModule;
|
|
40
|
+
const { default: Page, config, getMetadata } = mdxModule;
|
|
44
41
|
if (typeof Page !== "function") {
|
|
45
42
|
throw new Error("MDX file must export a default function");
|
|
46
43
|
}
|
|
47
44
|
const resolvedLayout = config?.layout;
|
|
48
45
|
if (config) Page.config = config;
|
|
49
46
|
return {
|
|
47
|
+
...pageModule,
|
|
50
48
|
default: Page,
|
|
51
49
|
layout: resolvedLayout,
|
|
52
50
|
getMetadata
|
|
@@ -55,6 +53,18 @@ class MDXRenderer extends IntegrationRenderer {
|
|
|
55
53
|
invariant(false, `Error importing MDX file: ${error}`);
|
|
56
54
|
}
|
|
57
55
|
}
|
|
56
|
+
async renderComponent(input) {
|
|
57
|
+
return this.renderStringComponentBoundaryWithQueuedForeignBoundaries(
|
|
58
|
+
input,
|
|
59
|
+
input.component
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
createComponentBoundaryRuntime(options) {
|
|
63
|
+
return this.createQueuedBoundaryRuntime({
|
|
64
|
+
boundaryInput: options.boundaryInput,
|
|
65
|
+
rendererCache: options.rendererCache
|
|
66
|
+
});
|
|
67
|
+
}
|
|
58
68
|
async render({
|
|
59
69
|
params,
|
|
60
70
|
query,
|
|
@@ -68,42 +78,31 @@ class MDXRenderer extends IntegrationRenderer {
|
|
|
68
78
|
pageProps
|
|
69
79
|
}) {
|
|
70
80
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
return await this.renderPageWithDocumentShell({
|
|
82
|
+
page: {
|
|
83
|
+
component: Page,
|
|
84
|
+
props: { params, query, ...props, locals: pageLocals }
|
|
85
|
+
},
|
|
86
|
+
layout: Layout ? {
|
|
87
|
+
component: Layout,
|
|
88
|
+
props: locals ? { locals } : {}
|
|
89
|
+
} : void 0,
|
|
90
|
+
htmlTemplate: HtmlTemplate,
|
|
74
91
|
metadata,
|
|
75
|
-
children,
|
|
76
92
|
pageProps: pageProps || {}
|
|
77
93
|
});
|
|
78
|
-
return this.DOC_TYPE + body;
|
|
79
94
|
} catch (error) {
|
|
80
95
|
throw this.createRenderError("Error rendering page", error);
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
async renderToResponse(view, props, ctx) {
|
|
84
99
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
const children = Layout ? await Layout({ children: pageContent }) : pageContent;
|
|
93
|
-
const HtmlTemplate = await this.getHtmlTemplate();
|
|
94
|
-
const metadata = view.metadata ? await view.metadata({
|
|
95
|
-
params: {},
|
|
96
|
-
query: {},
|
|
97
|
-
props,
|
|
98
|
-
appConfig: this.appConfig
|
|
99
|
-
}) : this.appConfig.defaultMetadata;
|
|
100
|
-
body = this.DOC_TYPE + await HtmlTemplate({
|
|
101
|
-
metadata,
|
|
102
|
-
children,
|
|
103
|
-
pageProps: props
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return this.createHtmlResponse(body, ctx);
|
|
100
|
+
return await this.renderViewWithDocumentShell({
|
|
101
|
+
view,
|
|
102
|
+
props,
|
|
103
|
+
ctx,
|
|
104
|
+
layout: view.config?.layout
|
|
105
|
+
});
|
|
107
106
|
} catch (error) {
|
|
108
107
|
throw this.createRenderError("Error rendering view", error);
|
|
109
108
|
}
|
package/src/mdx-loader-plugin.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
4
|
-
import { type CompileOptions, compile } from '@mdx-js/mdx';
|
|
5
|
-
import sourceMap from 'source-map';
|
|
6
|
-
import { VFile } from 'vfile';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Resolves the MDX parser mode for a source file.
|
|
10
|
-
*
|
|
11
|
-
* Files with a `.md` extension must be forced into `mdx` mode when the caller
|
|
12
|
-
* explicitly opts them into the MDX pipeline. Leaving the compiler in `detect`
|
|
13
|
-
* mode would treat `.md` files as plain markdown, causing top-level ESM such as
|
|
14
|
-
* `import` and `export` to render as text instead of being compiled.
|
|
15
|
-
*
|
|
16
|
-
* @param filePath Absolute or relative source file path.
|
|
17
|
-
* @param compilerOptions User-provided MDX compiler options.
|
|
18
|
-
* @returns The compile format that should be passed to `@mdx-js/mdx`.
|
|
19
|
-
*/
|
|
20
|
-
function resolveCompileFormat(filePath: string, compilerOptions?: CompileOptions): CompileOptions['format'] {
|
|
21
|
-
const configuredFormat = compilerOptions?.format;
|
|
22
|
-
|
|
23
|
-
if (configuredFormat && configuredFormat !== 'detect') {
|
|
24
|
-
return configuredFormat;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return path.extname(filePath).toLowerCase() === '.md' ? 'mdx' : configuredFormat;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function createMdxLoaderPlugin(compilerOptions?: CompileOptions): EcoBuildPlugin {
|
|
31
|
-
const mdxExtensions = compilerOptions?.mdxExtensions ?? ['.mdx'];
|
|
32
|
-
const mdExtensions = compilerOptions?.mdExtensions ?? ['.md'];
|
|
33
|
-
const allExtensions = [...mdxExtensions, ...mdExtensions];
|
|
34
|
-
const escapedExts = allExtensions.map((ext) => ext.replace('.', '\\.'));
|
|
35
|
-
const filter = new RegExp(`(${escapedExts.join('|')})(\\?.*)?$`);
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
name: 'mdx-loader',
|
|
39
|
-
setup(build) {
|
|
40
|
-
build.onLoad({ filter }, async (args) => {
|
|
41
|
-
const filePath = args.path.includes('?') ? args.path.split('?')[0] : args.path;
|
|
42
|
-
const source = readFileSync(filePath, 'utf-8');
|
|
43
|
-
const file = new VFile({ path: filePath, value: source });
|
|
44
|
-
|
|
45
|
-
const compiled = await compile(file, {
|
|
46
|
-
...compilerOptions,
|
|
47
|
-
format: resolveCompileFormat(filePath, compilerOptions),
|
|
48
|
-
SourceMapGenerator: sourceMap.SourceMapGenerator,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const inlineSourceMap = compiled.map
|
|
52
|
-
? `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(compiled.map)).toString('base64')}\n`
|
|
53
|
-
: '';
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
contents: `${String(compiled.value)}${inlineSourceMap}`,
|
|
57
|
-
loader: compilerOptions?.jsx ? 'jsx' : 'js',
|
|
58
|
-
resolveDir: path.dirname(args.path),
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
package/src/mdx-renderer.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This module contains the MDX renderer
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
EcoComponent,
|
|
8
|
-
EcoComponentConfig,
|
|
9
|
-
EcoPageFile,
|
|
10
|
-
EcoPagesElement,
|
|
11
|
-
GetMetadata,
|
|
12
|
-
IntegrationRendererRenderOptions,
|
|
13
|
-
PageMetadataProps,
|
|
14
|
-
RouteRendererBody,
|
|
15
|
-
} from '@ecopages/core';
|
|
16
|
-
import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
|
|
17
|
-
import { invariant } from '@ecopages/core/utils/invariant';
|
|
18
|
-
import type { AssetProcessingService, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
19
|
-
import type { CompileOptions } from '@mdx-js/mdx';
|
|
20
|
-
import { PLUGIN_NAME } from './mdx.plugin.ts';
|
|
21
|
-
import { rapidhash } from '@ecopages/core/hash';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A structure representing an MDX file
|
|
25
|
-
*/
|
|
26
|
-
export type MDXFile = {
|
|
27
|
-
default: EcoComponent;
|
|
28
|
-
config?: EcoComponentConfig;
|
|
29
|
-
getMetadata: GetMetadata;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Options for the MDX renderer
|
|
34
|
-
*/
|
|
35
|
-
interface MDXIntegrationRendererOpions<C = EcoPagesElement> extends IntegrationRendererRenderOptions<C> {}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A renderer for the MDX integration.
|
|
39
|
-
*/
|
|
40
|
-
export class MDXRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
41
|
-
name = PLUGIN_NAME;
|
|
42
|
-
compilerOptions: CompileOptions;
|
|
43
|
-
|
|
44
|
-
constructor({
|
|
45
|
-
compilerOptions,
|
|
46
|
-
...options
|
|
47
|
-
}: {
|
|
48
|
-
appConfig: any;
|
|
49
|
-
assetProcessingService: AssetProcessingService;
|
|
50
|
-
resolvedIntegrationDependencies: ProcessedAsset[];
|
|
51
|
-
runtimeOrigin: string;
|
|
52
|
-
compilerOptions?: CompileOptions;
|
|
53
|
-
}) {
|
|
54
|
-
super(options);
|
|
55
|
-
this.compilerOptions = compilerOptions || {};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
override async buildRouteRenderAssets(pagePath: string): Promise<ProcessedAsset[]> {
|
|
59
|
-
const { default: pageComponent } = await this.importPageFile(pagePath);
|
|
60
|
-
const config = pageComponent.config;
|
|
61
|
-
const components: Partial<EcoComponent>[] = [];
|
|
62
|
-
|
|
63
|
-
const resolvedLayout = config?.layout;
|
|
64
|
-
|
|
65
|
-
if (resolvedLayout?.config?.dependencies) {
|
|
66
|
-
components.push({ config: resolvedLayout.config });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (config?.dependencies) {
|
|
70
|
-
components.push({
|
|
71
|
-
config: {
|
|
72
|
-
...config,
|
|
73
|
-
__eco: {
|
|
74
|
-
id: rapidhash(pagePath).toString(36),
|
|
75
|
-
file: pagePath,
|
|
76
|
-
integration: this.name,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return await this.resolveDependencies(components);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
protected override async importPageFile(file: string): Promise<
|
|
86
|
-
EcoPageFile<{
|
|
87
|
-
layout?:
|
|
88
|
-
| EcoComponent<any>
|
|
89
|
-
| {
|
|
90
|
-
config: EcoComponentConfig | undefined;
|
|
91
|
-
};
|
|
92
|
-
}>
|
|
93
|
-
> {
|
|
94
|
-
try {
|
|
95
|
-
const {
|
|
96
|
-
default: Page,
|
|
97
|
-
config,
|
|
98
|
-
getMetadata,
|
|
99
|
-
} = (await super.importPageFile(file)) as EcoPageFile<{
|
|
100
|
-
layout?:
|
|
101
|
-
| EcoComponent<any>
|
|
102
|
-
| {
|
|
103
|
-
config: EcoComponentConfig | undefined;
|
|
104
|
-
};
|
|
105
|
-
}> & {
|
|
106
|
-
config?: EcoComponentConfig;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
if (typeof Page !== 'function') {
|
|
110
|
-
throw new Error('MDX file must export a default function');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const resolvedLayout = config?.layout;
|
|
114
|
-
|
|
115
|
-
if (config) Page.config = config;
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
default: Page,
|
|
119
|
-
layout: resolvedLayout,
|
|
120
|
-
getMetadata,
|
|
121
|
-
};
|
|
122
|
-
} catch (error) {
|
|
123
|
-
invariant(false, `Error importing MDX file: ${error}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async render({
|
|
128
|
-
params,
|
|
129
|
-
query,
|
|
130
|
-
props,
|
|
131
|
-
locals,
|
|
132
|
-
pageLocals,
|
|
133
|
-
metadata,
|
|
134
|
-
Page,
|
|
135
|
-
HtmlTemplate,
|
|
136
|
-
Layout,
|
|
137
|
-
pageProps,
|
|
138
|
-
}: MDXIntegrationRendererOpions): Promise<RouteRendererBody> {
|
|
139
|
-
try {
|
|
140
|
-
const pageContent = await Page({ params, query, ...props, locals: pageLocals });
|
|
141
|
-
const children =
|
|
142
|
-
typeof Layout === 'function' ? await Layout({ children: pageContent, locals }) : pageContent;
|
|
143
|
-
|
|
144
|
-
const body = await HtmlTemplate({
|
|
145
|
-
metadata,
|
|
146
|
-
children,
|
|
147
|
-
pageProps: pageProps || {},
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return this.DOC_TYPE + body;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
throw this.createRenderError('Error rendering page', error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async renderToResponse<P = Record<string, unknown>>(
|
|
157
|
-
view: EcoComponent<P>,
|
|
158
|
-
props: P,
|
|
159
|
-
ctx: RenderToResponseContext,
|
|
160
|
-
): Promise<Response> {
|
|
161
|
-
try {
|
|
162
|
-
const Layout = view.config?.layout as
|
|
163
|
-
| ((props: { children: EcoPagesElement } & Record<string, unknown>) => Promise<EcoPagesElement>)
|
|
164
|
-
| undefined;
|
|
165
|
-
|
|
166
|
-
const viewFn = view as (props: P) => Promise<EcoPagesElement>;
|
|
167
|
-
const pageContent = await viewFn(props);
|
|
168
|
-
|
|
169
|
-
let body: string;
|
|
170
|
-
if (ctx.partial) {
|
|
171
|
-
body = pageContent as string;
|
|
172
|
-
} else {
|
|
173
|
-
const children = Layout ? await Layout({ children: pageContent }) : pageContent;
|
|
174
|
-
|
|
175
|
-
const HtmlTemplate = await this.getHtmlTemplate();
|
|
176
|
-
const metadata: PageMetadataProps = view.metadata
|
|
177
|
-
? await view.metadata({
|
|
178
|
-
params: {},
|
|
179
|
-
query: {},
|
|
180
|
-
props: props as Record<string, unknown>,
|
|
181
|
-
appConfig: this.appConfig,
|
|
182
|
-
})
|
|
183
|
-
: this.appConfig.defaultMetadata;
|
|
184
|
-
|
|
185
|
-
body =
|
|
186
|
-
this.DOC_TYPE +
|
|
187
|
-
(await HtmlTemplate({
|
|
188
|
-
metadata,
|
|
189
|
-
children: children as EcoPagesElement,
|
|
190
|
-
pageProps: props as Record<string, unknown>,
|
|
191
|
-
}));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return this.createHtmlResponse(body, ctx);
|
|
195
|
-
} catch (error) {
|
|
196
|
-
throw this.createRenderError('Error rendering view', error);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Factory function to create an MDX renderer class with specific compiler options.
|
|
203
|
-
*
|
|
204
|
-
* @param compilerOptions - Compiler options for MDX compilation.
|
|
205
|
-
* @returns A new MDXRenderer class extended with the provided context.
|
|
206
|
-
*/
|
|
207
|
-
export function createMDXRenderer(compilerOptions: CompileOptions): typeof MDXRenderer {
|
|
208
|
-
return class extends MDXRenderer {
|
|
209
|
-
constructor(options: {
|
|
210
|
-
appConfig: any;
|
|
211
|
-
assetProcessingService: AssetProcessingService;
|
|
212
|
-
resolvedIntegrationDependencies: ProcessedAsset[];
|
|
213
|
-
runtimeOrigin: string;
|
|
214
|
-
}) {
|
|
215
|
-
super({
|
|
216
|
-
...options,
|
|
217
|
-
compilerOptions,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
}
|
package/src/mdx.plugin.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
2
|
-
import type { EcoPagesElement } from '@ecopages/core';
|
|
3
|
-
import { IntegrationPlugin, type IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
|
|
4
|
-
import { deepMerge } from '@ecopages/core/utils/deep-merge';
|
|
5
|
-
import { Logger } from '@ecopages/logger';
|
|
6
|
-
import type { CompileOptions } from '@mdx-js/mdx';
|
|
7
|
-
import { createMdxLoaderPlugin } from './mdx-loader-plugin.ts';
|
|
8
|
-
import { createMDXRenderer, MDXRenderer } from './mdx-renderer.ts';
|
|
9
|
-
|
|
10
|
-
const appLogger = new Logger('[MDXPlugin]');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The name of the MDX plugin
|
|
14
|
-
*/
|
|
15
|
-
export const PLUGIN_NAME = 'MDX';
|
|
16
|
-
|
|
17
|
-
export type MDXPluginConfig = Partial<Omit<IntegrationPluginConfig, 'name'>> & {
|
|
18
|
-
compilerOptions?: CompileOptions;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const defaultOptions: CompileOptions = {
|
|
22
|
-
format: 'detect',
|
|
23
|
-
outputFormat: 'program',
|
|
24
|
-
jsxImportSource: '@kitajs/html',
|
|
25
|
-
jsxRuntime: 'automatic',
|
|
26
|
-
development: process.env.NODE_ENV === 'development',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Splits configured markdown extensions into the two buckets understood by the
|
|
31
|
-
* MDX loader.
|
|
32
|
-
*
|
|
33
|
-
* `.mdx` remains the native MDX extension list. `.md` is special: it is only
|
|
34
|
-
* treated as MDX when a caller explicitly opts it into the pipeline, so we keep
|
|
35
|
-
* it separate rather than hiding that behavior inside a pair of constructor
|
|
36
|
-
* filters.
|
|
37
|
-
*/
|
|
38
|
-
function splitMarkdownExtensions(extensions: string[]): Pick<CompileOptions, 'mdExtensions' | 'mdxExtensions'> {
|
|
39
|
-
const mdExtensions: string[] = [];
|
|
40
|
-
const mdxExtensions: string[] = [];
|
|
41
|
-
|
|
42
|
-
for (const extension of extensions) {
|
|
43
|
-
if (extension === '.md') {
|
|
44
|
-
mdExtensions.push(extension);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
mdxExtensions.push(extension);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { mdExtensions, mdxExtensions };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The MDX plugin class
|
|
56
|
-
* This plugin provides support for MDX components in Ecopages.
|
|
57
|
-
*
|
|
58
|
-
* Standalone `mdxPlugin()` is intended for non-React JSX runtimes such as
|
|
59
|
-
* `@kitajs/html`. React-backed MDX should be configured through
|
|
60
|
-
* `reactPlugin({ mdx: { enabled: true, compilerOptions: ... } })` instead.
|
|
61
|
-
*/
|
|
62
|
-
export class MDXPlugin extends IntegrationPlugin<EcoPagesElement> {
|
|
63
|
-
renderer: typeof MDXRenderer;
|
|
64
|
-
private compilerOptions: CompileOptions;
|
|
65
|
-
private mdxLoaderPlugin: EcoBuildPlugin | undefined;
|
|
66
|
-
|
|
67
|
-
constructor({ compilerOptions, ...options }: MDXPluginConfig = { extensions: ['.mdx'] }) {
|
|
68
|
-
super({
|
|
69
|
-
name: PLUGIN_NAME,
|
|
70
|
-
extensions: ['.mdx'],
|
|
71
|
-
...options,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const { mdExtensions, mdxExtensions } = splitMarkdownExtensions(this.extensions);
|
|
75
|
-
|
|
76
|
-
const finalCompilerOptions = deepMerge(
|
|
77
|
-
{
|
|
78
|
-
...defaultOptions,
|
|
79
|
-
mdxExtensions,
|
|
80
|
-
mdExtensions,
|
|
81
|
-
},
|
|
82
|
-
compilerOptions,
|
|
83
|
-
);
|
|
84
|
-
const jsxImportSource = finalCompilerOptions.jsxImportSource;
|
|
85
|
-
|
|
86
|
-
if (jsxImportSource === 'react' || (jsxImportSource?.startsWith('react/') ?? false)) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
'Standalone `mdxPlugin()` does not support React JSX runtimes. Use `reactPlugin({ mdx: { enabled: true, compilerOptions: ... } })` instead.',
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
this.compilerOptions = finalCompilerOptions;
|
|
93
|
-
this.renderer = createMDXRenderer(finalCompilerOptions);
|
|
94
|
-
|
|
95
|
-
appLogger.debug(`MDX plugin configured with jsxImportSource: ${jsxImportSource ?? 'default'}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
override get plugins(): EcoBuildPlugin[] {
|
|
99
|
-
if (this.mdxLoaderPlugin) {
|
|
100
|
-
return [this.mdxLoaderPlugin];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return [];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Materializes the MDX loader once so config-time sealing and runtime setup
|
|
108
|
-
* can share the same loader instance.
|
|
109
|
-
*/
|
|
110
|
-
private ensureLoaderPlugin(): void {
|
|
111
|
-
if (this.mdxLoaderPlugin) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.mdxLoaderPlugin = createMdxLoaderPlugin(this.compilerOptions);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Prepares the MDX loader contribution before config build seals the manifest.
|
|
120
|
-
*/
|
|
121
|
-
override async prepareBuildContributions(): Promise<void> {
|
|
122
|
-
this.ensureLoaderPlugin();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Runs runtime-only MDX setup after build contributions are already prepared.
|
|
127
|
-
*/
|
|
128
|
-
override async setup(): Promise<void> {
|
|
129
|
-
this.ensureLoaderPlugin();
|
|
130
|
-
await super.setup();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Factory function to create an MDX plugin instance.
|
|
136
|
-
* @param options Configuration options for the MDX plugin
|
|
137
|
-
* @returns A new MDXPlugin instance
|
|
138
|
-
*/
|
|
139
|
-
export function mdxPlugin(options?: MDXPluginConfig): MDXPlugin {
|
|
140
|
-
return new MDXPlugin(options);
|
|
141
|
-
}
|