@ecopages/ecopages-jsx 0.2.0-beta.12 → 0.2.0-beta.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/ecopages-jsx",
3
- "version": "0.2.0-beta.12",
3
+ "version": "0.2.0-beta.14",
4
4
  "description": "JSX integration plugin for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -30,17 +30,14 @@
30
30
  "url": "git+https://github.com/ecopages/ecopages.git",
31
31
  "directory": "packages/integrations/ecopages-jsx"
32
32
  },
33
- "bundleDependencies": [
34
- "@ecopages/mdx-core"
35
- ],
36
33
  "dependencies": {
37
- "@ecopages/mdx-core": "0.2.0-beta.12",
34
+ "@ecopages/mdx": "0.2.0-beta.14",
38
35
  "@mdx-js/mdx": "^3.1.1",
39
36
  "vfile": "^6.0.3"
40
37
  },
41
38
  "peerDependencies": {
42
- "@ecopages/core": "0.2.0-beta.12",
43
- "@ecopages/jsx": "0.3.0-alpha.25",
44
- "@ecopages/radiant": "0.3.0-alpha.25"
39
+ "@ecopages/core": "0.2.0-beta.14",
40
+ "@ecopages/jsx": "0.3.0-beta.0",
41
+ "@ecopages/radiant": "0.3.0-beta.0"
45
42
  }
46
43
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tracks the source files that participate in the active SSR render tree.
3
+ *
4
+ * @remarks
5
+ * The JSX integration renderer calls {@link updateEjsxHmrOwnership} after each
6
+ * page, component, or view render so the HMR strategy can answer
7
+ * "does this changed file affect SSR HTML?" without re-walking the tree on
8
+ * every watcher event.
9
+ *
10
+ * The state is module-scoped because the HMR strategy and renderer live in
11
+ * different call sites (server strategy vs SSR renderer) and share no other
12
+ * lifecycle anchor. A fresh state is installed only when the source-file set
13
+ * changes, so the renderer's per-render hook stays O(1) in the steady state.
14
+ */
15
+ import type { EcoComponent } from '@ecopages/core';
16
+ export type EjsxHmrOwnershipState = {
17
+ fileOwners: ReadonlySet<string>;
18
+ hash: string;
19
+ };
20
+ /**
21
+ * Returns the most recently recorded active render tree.
22
+ */
23
+ export declare function getEjsxHmrOwnership(): EjsxHmrOwnershipState;
24
+ /**
25
+ * Updates the active render tree from one or more root components.
26
+ *
27
+ * @remarks
28
+ * Walks each root's `config.__eco.file`, `config.dependencies.components[*].config`,
29
+ * and `config.layouts[*].config` recursively, collecting every file path. The
30
+ * state is replaced only when the resulting file set has a different hash, so
31
+ * the renderer's per-render hook is free in the steady state.
32
+ */
33
+ export declare function updateEjsxHmrOwnership(components: ReadonlyArray<EcoComponent | undefined>): void;
34
+ /**
35
+ * Resets the state to empty. Used by tests and by HMR manager teardown.
36
+ */
37
+ export declare function resetEjsxHmrOwnership(): void;
@@ -0,0 +1,63 @@
1
+ import path from "node:path";
2
+ import { rapidhash } from "@ecopages/core/hash";
3
+ const EMPTY_STATE = {
4
+ fileOwners: /* @__PURE__ */ new Set(),
5
+ hash: ""
6
+ };
7
+ let currentState = EMPTY_STATE;
8
+ function getEjsxHmrOwnership() {
9
+ return currentState;
10
+ }
11
+ function updateEjsxHmrOwnership(components) {
12
+ const files = collectFileOwners(components);
13
+ const hash = hashFileSet(files);
14
+ if (hash === currentState.hash) {
15
+ return;
16
+ }
17
+ currentState = {
18
+ fileOwners: files,
19
+ hash
20
+ };
21
+ }
22
+ function resetEjsxHmrOwnership() {
23
+ currentState = EMPTY_STATE;
24
+ }
25
+ function collectFileOwners(components) {
26
+ const files = /* @__PURE__ */ new Set();
27
+ const visited = /* @__PURE__ */ new Set();
28
+ const visit = (config) => {
29
+ const file = config?.__eco?.file;
30
+ if (!file) {
31
+ return;
32
+ }
33
+ const resolved = path.resolve(file);
34
+ if (visited.has(resolved)) {
35
+ return;
36
+ }
37
+ visited.add(resolved);
38
+ files.add(resolved);
39
+ for (const dependency of config?.dependencies?.components ?? []) {
40
+ visit(dependency?.config);
41
+ }
42
+ for (const layout of config?.layouts ?? []) {
43
+ visit(layout?.config);
44
+ }
45
+ };
46
+ for (const component of components) {
47
+ visit(component?.config);
48
+ }
49
+ return files;
50
+ }
51
+ function hashFileSet(files) {
52
+ if (files.size === 0) {
53
+ return "";
54
+ }
55
+ const sorted = Array.from(files).sort();
56
+ const joined = sorted.join("\n");
57
+ return rapidhash(joined).toString(36);
58
+ }
59
+ export {
60
+ getEjsxHmrOwnership,
61
+ resetEjsxHmrOwnership,
62
+ updateEjsxHmrOwnership
63
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * HMR strategy for JSX integration-owned SSR source modules.
3
+ *
4
+ * @remarks
5
+ * Edits to files that produce SSR HTML (page sources, layout sources, components
6
+ * that the active render tree depends on) need a soft page refetch so the
7
+ * browser picks up the new markup. Plain `update` events re-import modules on
8
+ * the client but leave the server-rendered DOM stale.
9
+ *
10
+ * The strategy matches a file when any of these hold:
11
+ * - the file lives under `pagesDir` or `layoutsDir` and has a JSX `templatesExt`
12
+ * - the file appears in the active render tree's component dependency graph
13
+ *
14
+ * It defers registered HMR entrypoints to {@link JsHmrStrategy} so script-only
15
+ * edits keep their existing `update`-then-hot-accept path. It also defers
16
+ * include templates and explicit server views to {@link ServerRenderedTemplateHmrStrategy}
17
+ * by returning `false` for those paths.
18
+ */
19
+ import { HmrStrategy, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
20
+ export interface EcopagesJsxHmrStrategyContext {
21
+ getWatchedFiles(): Map<string, string>;
22
+ getSrcDir(): string;
23
+ getPagesDir(): string;
24
+ getLayoutsDir(): string;
25
+ getIncludesDir(): string;
26
+ getTemplateExtensions(): ReadonlyArray<string>;
27
+ }
28
+ export declare class EcopagesJsxHmrStrategy extends HmrStrategy {
29
+ readonly type: 100;
30
+ private readonly context;
31
+ constructor(context: EcopagesJsxHmrStrategyContext);
32
+ matches(filePath: string): boolean;
33
+ process(filePath: string): Promise<HmrAction>;
34
+ private isPageOrLayoutSource;
35
+ private hasJsxTemplateExtension;
36
+ }
@@ -0,0 +1,62 @@
1
+ import path from "node:path";
2
+ import { HmrStrategy, HmrStrategyType } from "@ecopages/core/hmr/hmr-strategy";
3
+ import { getEjsxHmrOwnership } from "./ecopages-jsx-hmr-ownership.js";
4
+ class EcopagesJsxHmrStrategy extends HmrStrategy {
5
+ type = HmrStrategyType.INTEGRATION;
6
+ context;
7
+ constructor(context) {
8
+ super();
9
+ this.context = context;
10
+ }
11
+ matches(filePath) {
12
+ if (this.context.getWatchedFiles().has(filePath)) {
13
+ return false;
14
+ }
15
+ const resolvedPath = path.resolve(filePath);
16
+ const srcDir = path.resolve(this.context.getSrcDir());
17
+ if (!resolvedPath.startsWith(`${srcDir}${path.sep}`) && resolvedPath !== srcDir) {
18
+ return false;
19
+ }
20
+ const includesDir = path.resolve(this.context.getIncludesDir());
21
+ if (includesDir && (resolvedPath === includesDir || resolvedPath.startsWith(`${includesDir}${path.sep}`))) {
22
+ return false;
23
+ }
24
+ if (this.isPageOrLayoutSource(resolvedPath)) {
25
+ return this.hasJsxTemplateExtension(resolvedPath);
26
+ }
27
+ const ownership = getEjsxHmrOwnership();
28
+ if (ownership.fileOwners.has(resolvedPath)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+ async process(filePath) {
34
+ return {
35
+ type: "broadcast",
36
+ events: [
37
+ {
38
+ type: "layout-update",
39
+ path: filePath,
40
+ timestamp: Date.now()
41
+ }
42
+ ]
43
+ };
44
+ }
45
+ isPageOrLayoutSource(resolvedPath) {
46
+ const pagesDir = path.resolve(this.context.getPagesDir());
47
+ const layoutsDir = path.resolve(this.context.getLayoutsDir());
48
+ if (pagesDir && (resolvedPath === pagesDir || resolvedPath.startsWith(`${pagesDir}${path.sep}`))) {
49
+ return true;
50
+ }
51
+ if (layoutsDir && (resolvedPath === layoutsDir || resolvedPath.startsWith(`${layoutsDir}${path.sep}`))) {
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ hasJsxTemplateExtension(resolvedPath) {
57
+ return this.context.getTemplateExtensions().some((extension) => resolvedPath.endsWith(extension));
58
+ }
59
+ }
60
+ export {
61
+ EcopagesJsxHmrStrategy
62
+ };
@@ -1,7 +1,7 @@
1
1
  import type { CompileOptions } from '@mdx-js/mdx';
2
2
  import type { EcoComponent, EcoComponentConfig, EcoFunctionComponent, EcoPageFile, GetMetadata } from '@ecopages/core';
3
3
  import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
4
- import { appendMdxExtensions, createMdxExtensionFilter } from '@ecopages/mdx-core';
4
+ import { appendMdxExtensions, createMdxExtensionFilter } from '@ecopages/mdx/core';
5
5
  import type { JsxRenderable } from '@ecopages/jsx';
6
6
  import type { EcopagesJsxMdxCompileOptions, EcopagesJsxMdxOptions } from './ecopages-jsx.types.js';
7
7
  export type ResolvedMdxCompileOptions = EcopagesJsxMdxCompileOptions & Pick<CompileOptions, 'jsxImportSource' | 'jsxRuntime'>;
@@ -1,11 +1,12 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { ensurePageConfigLayouts } from "@ecopages/core/eco/page-layout-normalization";
2
3
  import { rapidhash } from "@ecopages/core/hash";
3
4
  import {
4
5
  appendMdxExtensions,
5
6
  createMdxExtensionFilter,
6
7
  createMdxLoaderPlugin as createMdxLoaderPluginCore,
7
8
  resolveMdxCompilerOptions as resolveMdxCompilerOptionsCore
8
- } from "@ecopages/mdx-core";
9
+ } from "@ecopages/mdx/core";
9
10
  import { VFile } from "vfile";
10
11
  import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
11
12
  const resolveMdxCompilerOptions = (mdxOptions) => resolveMdxCompilerOptionsCore(mdxOptions, {
@@ -49,13 +50,16 @@ const normalizeMdxPageModule = (file, module) => {
49
50
  const Page = module.default;
50
51
  const normalizedConfig = {
51
52
  ...module.config ?? Page.config ?? {},
52
- ...module.layout ? { layout: module.layout } : {},
53
53
  __eco: module.config?.__eco ?? Page.config?.__eco ?? {
54
54
  id: String(rapidhash(file)),
55
55
  file,
56
56
  integration: ECOPAGES_JSX_PLUGIN_NAME
57
57
  }
58
58
  };
59
+ if (module.layout && !(normalizedConfig.layouts && normalizedConfig.layouts.length > 0)) {
60
+ normalizedConfig.layout = module.layout;
61
+ }
62
+ ensurePageConfigLayouts(normalizedConfig);
59
63
  const wrappedPage = async (props) => await Page(props);
60
64
  wrappedPage.config = normalizedConfig;
61
65
  if (module.getMetadata ?? Page.metadata) {
@@ -57,10 +57,10 @@ class EcopagesJsxRadiantSsrPolicy {
57
57
  "./radiant-element-ssr-bridge.js",
58
58
  radiantLightDomShimEntry
59
59
  ).href;
60
- EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise = Promise.all([
61
- import(radiantElementSsrRuntimeModuleUrl),
62
- import(radiantLightDomShimEntry)
63
- ]).then(([radiantElementSsrRuntimeModule, lightDomShimModule2]) => {
60
+ EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise = (async () => {
61
+ const lightDomShimModule2 = await import(radiantLightDomShimEntry);
62
+ ensureRadiantLightDomGlobals(lightDomShimModule2.installLightDomShim);
63
+ const radiantElementSsrRuntimeModule = await import(radiantElementSsrRuntimeModuleUrl);
64
64
  const modules = {
65
65
  installLightDomShim: lightDomShimModule2.installLightDomShim,
66
66
  resolveRadiantElementRenderBridge: radiantElementSsrRuntimeModule.resolveRadiantElementRenderBridge,
@@ -68,11 +68,37 @@ class EcopagesJsxRadiantSsrPolicy {
68
68
  };
69
69
  EcopagesJsxRadiantSsrPolicy.runtimeModules = modules;
70
70
  return modules;
71
- });
71
+ })();
72
72
  }
73
- const lightDomShimModule = await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
74
- lightDomShimModule.installLightDomShim();
73
+ await EcopagesJsxRadiantSsrPolicy.runtimeModulesPromise;
74
+ const lightDomShimModule = EcopagesJsxRadiantSsrPolicy.runtimeModules;
75
+ if (lightDomShimModule) {
76
+ ensureRadiantLightDomGlobals(lightDomShimModule.installLightDomShim);
77
+ }
78
+ }
79
+ }
80
+ function ensureRadiantLightDomGlobals(installLightDomShim) {
81
+ if (typeof globalThis.HTMLElement !== "undefined") {
82
+ return;
83
+ }
84
+ const window = installLightDomShim();
85
+ if (!window) {
86
+ return;
75
87
  }
88
+ Object.assign(globalThis, {
89
+ CSS: window.CSS,
90
+ CustomEvent: window.CustomEvent,
91
+ Document: window.Document,
92
+ Element: window.Element,
93
+ Event: window.Event,
94
+ EventTarget: window.EventTarget,
95
+ HTMLScriptElement: window.HTMLScriptElement,
96
+ HTMLElement: window.HTMLElement,
97
+ Node: window.Node,
98
+ document: window.document,
99
+ customElements: window.customElements,
100
+ window
101
+ });
76
102
  }
77
103
  export {
78
104
  EcopagesJsxRadiantSsrPolicy
@@ -37,4 +37,10 @@ export declare class EcopagesJsxRenderer extends IntegrationRenderer<JsxRenderab
37
37
  private withCustomElementRenderHook;
38
38
  private withPreparedRadiantRuntime;
39
39
  private createIntrinsicCustomElementRenderHook;
40
+ /**
41
+ * Records the source files that produced the current render so
42
+ * {@link EcopagesJsxHmrStrategy} can match later watcher events without
43
+ * re-walking the component tree.
44
+ */
45
+ private recordHmrOwnership;
40
46
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from "./ecopages-jsx-mdx.js";
11
11
  import { EcopagesJsxRenderSession } from "./ecopages-jsx-render-session.js";
12
12
  import { EcopagesJsxRadiantSsrPolicy } from "./ecopages-jsx-radiant-ssr-policy.js";
13
+ import { updateEjsxHmrOwnership } from "./ecopages-jsx-hmr-ownership.js";
13
14
  class EcopagesJsxRenderer extends IntegrationRenderer {
14
15
  name = ECOPAGES_JSX_PLUGIN_NAME;
15
16
  mdxExtensions;
@@ -107,7 +108,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
107
108
  return await this.withPreparedRadiantRuntime(
108
109
  () => this.renderSession.withActiveScope(async () => {
109
110
  try {
110
- return await this.renderPageWithDocumentShell({
111
+ const result = await this.renderPageWithDocumentShell({
111
112
  page: {
112
113
  component: options.Page,
113
114
  props: {
@@ -126,6 +127,8 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
126
127
  metadata: options.metadata,
127
128
  pageProps: options.pageProps ?? {}
128
129
  });
130
+ this.recordHmrOwnership([options.Page, options.Layout, options.HtmlTemplate]);
131
+ return result;
129
132
  } catch (error) {
130
133
  throw this.createRenderError("Error rendering page", error);
131
134
  }
@@ -159,6 +162,7 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
159
162
  ...queuedForeignSubtreeResolution.assets,
160
163
  ...componentAssets
161
164
  ]);
165
+ this.recordHmrOwnership([input.component]);
162
166
  return {
163
167
  html: queuedForeignSubtreeResolution.html,
164
168
  canAttachAttributes: true,
@@ -181,12 +185,15 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
181
185
  throw new TypeError("JSX renderer expected a callable view component.");
182
186
  }
183
187
  const viewComponent = view;
184
- return await this.renderViewWithDocumentShell({
188
+ const layouts = viewComponent.config?.layouts;
189
+ const response = await this.renderViewWithDocumentShell({
185
190
  view: viewComponent,
186
191
  props,
187
192
  ctx,
188
- layout: viewComponent.config?.layout
193
+ layout: layouts?.[layouts.length - 1]
189
194
  });
195
+ this.recordHmrOwnership([view]);
196
+ return response;
190
197
  } catch (error) {
191
198
  throw this.createRenderError("Error rendering view", error);
192
199
  }
@@ -216,6 +223,14 @@ class EcopagesJsxRenderer extends IntegrationRenderer {
216
223
  return instance ? this.radiantSsrPolicy.renderIntrinsicElementMarkup(instance) : void 0;
217
224
  };
218
225
  }
226
+ /**
227
+ * Records the source files that produced the current render so
228
+ * {@link EcopagesJsxHmrStrategy} can match later watcher events without
229
+ * re-walking the component tree.
230
+ */
231
+ recordHmrOwnership(components) {
232
+ updateEjsxHmrOwnership(components);
233
+ }
219
234
  }
220
235
  export {
221
236
  EcopagesJsxRenderer
@@ -1,4 +1,5 @@
1
1
  import { IntegrationPlugin, type EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
+ import type { HmrStrategy } from '@ecopages/core/hmr/hmr-strategy';
2
3
  import type { JsxRenderable } from '@ecopages/jsx';
3
4
  import { EcopagesJsxRenderer } from './ecopages-jsx-renderer.js';
4
5
  import type { EcopagesJsxPluginOptions } from './ecopages-jsx.types.js';
@@ -21,6 +22,16 @@ export declare class EcopagesJsxPlugin extends IntegrationPlugin<JsxRenderable>
21
22
  private getDependencies;
22
23
  /** Ensures MDX build hooks are ready before Ecopages collects contributions. */
23
24
  prepareBuildContributions(): Promise<void>;
25
+ /**
26
+ * Returns the JSX-integration HMR strategy.
27
+ *
28
+ * @remarks
29
+ * The strategy handles `layout-update` broadcasts for page sources, layout
30
+ * sources, and any component file in the active render tree. It defers
31
+ * registered script entrypoints to the generic JS strategy and includes /
32
+ * explicit server views to `ServerRenderedTemplateHmrStrategy`.
33
+ */
34
+ getHmrStrategy(): HmrStrategy | undefined;
24
35
  /**
25
36
  * Registers MDX tooling and completes the base integration setup.
26
37
  */
@@ -3,6 +3,7 @@ import {
3
3
  } from "@ecopages/core/plugins/integration-plugin";
4
4
  import { AssetFactory } from "@ecopages/core/services/asset-processing-service";
5
5
  import { ECOPAGES_JSX_PLUGIN_NAME } from "./ecopages-jsx.constants.js";
6
+ import { RADIANT_INSTALL_HYDRATOR_FILEPATH } from "./resolve-radiant-install-hydrator.js";
6
7
  import {
7
8
  appendMdxExtensions,
8
9
  createMdxLoaderPlugin,
@@ -10,6 +11,7 @@ import {
10
11
  resolveMdxCompilerOptions
11
12
  } from "./ecopages-jsx-mdx.js";
12
13
  import { EcopagesJsxRenderer } from "./ecopages-jsx-renderer.js";
14
+ import { EcopagesJsxHmrStrategy } from "./ecopages-jsx-hmr-strategy.js";
13
15
  const RADIANT_HYDRATOR_SCRIPT_ID = "ecopages-jsx-radiant-hydrator";
14
16
  const resolvePluginOptions = (options) => {
15
17
  const { extensions: userExtensions, radiant, mdx, ...baseConfig } = options ?? {};
@@ -72,7 +74,7 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
72
74
  return [
73
75
  AssetFactory.createNodeModuleScript({
74
76
  position: "head",
75
- importPath: "@ecopages/radiant/client/install-hydrator",
77
+ importPath: RADIANT_INSTALL_HYDRATOR_FILEPATH,
76
78
  bundle: false,
77
79
  attributes: {
78
80
  "data-eco-script-id": RADIANT_HYDRATOR_SCRIPT_ID
@@ -84,6 +86,30 @@ class EcopagesJsxPlugin extends IntegrationPlugin {
84
86
  async prepareBuildContributions() {
85
87
  this.ensureMdxLoaderPlugin();
86
88
  }
89
+ /**
90
+ * Returns the JSX-integration HMR strategy.
91
+ *
92
+ * @remarks
93
+ * The strategy handles `layout-update` broadcasts for page sources, layout
94
+ * sources, and any component file in the active render tree. It defers
95
+ * registered script entrypoints to the generic JS strategy and includes /
96
+ * explicit server views to `ServerRenderedTemplateHmrStrategy`.
97
+ */
98
+ getHmrStrategy() {
99
+ if (!this.hmrManager || !this.appConfig) {
100
+ return void 0;
101
+ }
102
+ const context = this.hmrManager.getDefaultContext();
103
+ const absolutePaths = this.appConfig.absolutePaths;
104
+ return new EcopagesJsxHmrStrategy({
105
+ getWatchedFiles: context.getWatchedFiles,
106
+ getSrcDir: context.getSrcDir,
107
+ getPagesDir: context.getPagesDir,
108
+ getLayoutsDir: context.getLayoutsDir,
109
+ getIncludesDir: () => absolutePaths.includesDir,
110
+ getTemplateExtensions: () => this.extensions
111
+ });
112
+ }
87
113
  /**
88
114
  * Registers MDX tooling and completes the base integration setup.
89
115
  */
@@ -0,0 +1,2 @@
1
+ /** Absolute path to `@ecopages/radiant/client/install-hydrator` from this integration package. */
2
+ export declare const RADIANT_INSTALL_HYDRATOR_FILEPATH: string;
@@ -0,0 +1,7 @@
1
+ import { fileURLToPath } from "node:url";
2
+ const RADIANT_INSTALL_HYDRATOR_FILEPATH = fileURLToPath(
3
+ import.meta.resolve("@ecopages/radiant/client/install-hydrator")
4
+ );
5
+ export {
6
+ RADIANT_INSTALL_HYDRATOR_FILEPATH
7
+ };
@@ -1,22 +0,0 @@
1
- {
2
- "name": "@ecopages/mdx-core",
3
- "version": "0.2.0-beta.12",
4
- "description": "Shared MDX loader utilities for Ecopages integrations",
5
- "license": "MIT",
6
- "type": "module",
7
- "exports": {
8
- ".": {
9
- "default": "./src/index.js",
10
- "types": "./src/index.d.ts"
11
- }
12
- },
13
- "peerDependencies": {
14
- "@ecopages/core": "0.2.0-beta.12",
15
- "@mdx-js/mdx": "^3.1.0"
16
- },
17
- "dependencies": {
18
- "source-map": "^0.7.6",
19
- "vfile": "^6.0.3"
20
- },
21
- "types": "./src/index.d.ts"
22
- }
@@ -1,2 +0,0 @@
1
- export { createMdxLoaderPlugin, type CreateMdxLoaderPluginOptions } from './mdx-loader-plugin.js';
2
- export { appendMdxExtensions, createMdxExtensionFilter, mergePluginLists, resolveCompileFormat, resolveLoaderExtensions, resolveMdxCompilerOptions, type MdxCompilerOptionsInput, } from './mdx-utils.js';
@@ -1,18 +0,0 @@
1
- import { createMdxLoaderPlugin } from "./mdx-loader-plugin.js";
2
- import {
3
- appendMdxExtensions,
4
- createMdxExtensionFilter,
5
- mergePluginLists,
6
- resolveCompileFormat,
7
- resolveLoaderExtensions,
8
- resolveMdxCompilerOptions
9
- } from "./mdx-utils.js";
10
- export {
11
- appendMdxExtensions,
12
- createMdxExtensionFilter,
13
- createMdxLoaderPlugin,
14
- mergePluginLists,
15
- resolveCompileFormat,
16
- resolveLoaderExtensions,
17
- resolveMdxCompilerOptions
18
- };
@@ -1,11 +0,0 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
- import { type CompileOptions } from '@mdx-js/mdx';
3
- export interface CreateMdxLoaderPluginOptions {
4
- name: string;
5
- compilerOptions?: CompileOptions;
6
- extensions?: string[];
7
- defaultMdExtensions?: string[];
8
- includeSourceMap?: boolean;
9
- loader?: 'js' | 'jsx';
10
- }
11
- export declare function createMdxLoaderPlugin(options: CreateMdxLoaderPluginOptions): EcoBuildPlugin;
@@ -1,44 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { compile } from "@mdx-js/mdx";
4
- import sourceMap from "source-map";
5
- import { VFile } from "vfile";
6
- import { createMdxExtensionFilter, resolveCompileFormat, resolveLoaderExtensions } from "./mdx-utils.js";
7
- function createMdxLoaderPlugin(options) {
8
- const {
9
- name,
10
- compilerOptions,
11
- extensions = resolveLoaderExtensions(compilerOptions, {
12
- defaultMdExtensions: options.defaultMdExtensions
13
- }),
14
- includeSourceMap = true,
15
- loader = compilerOptions?.jsx ? "jsx" : "js"
16
- } = options;
17
- const filter = createMdxExtensionFilter(extensions, { allowQueryString: true });
18
- return {
19
- name,
20
- setup(build) {
21
- build.onLoad({ filter }, async (args) => {
22
- const filePath = args.path.includes("?") ? args.path.split("?")[0] : args.path;
23
- const source = await readFile(filePath, "utf-8");
24
- const file = new VFile({ path: filePath, value: source });
25
- const compiled = await compile(file, {
26
- ...compilerOptions,
27
- format: resolveCompileFormat(filePath, compilerOptions),
28
- SourceMapGenerator: sourceMap.SourceMapGenerator
29
- });
30
- const inlineSourceMap = includeSourceMap && compiled.map ? `
31
- //# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(compiled.map)).toString("base64")}
32
- ` : "";
33
- return {
34
- contents: `${String(compiled.value)}${inlineSourceMap}`,
35
- loader,
36
- resolveDir: path.dirname(args.path)
37
- };
38
- });
39
- }
40
- };
41
- }
42
- export {
43
- createMdxLoaderPlugin
44
- };
@@ -1,29 +0,0 @@
1
- import type { CompileOptions } from '@mdx-js/mdx';
2
- export interface MdxCompilerOptionsInput {
3
- compilerOptions?: CompileOptions;
4
- remarkPlugins?: CompileOptions['remarkPlugins'];
5
- rehypePlugins?: CompileOptions['rehypePlugins'];
6
- recmaPlugins?: CompileOptions['recmaPlugins'];
7
- }
8
- export declare const mergePluginLists: <T>(...lists: Array<readonly T[] | null | undefined>) => T[] | undefined;
9
- export declare const appendMdxExtensions: (target: string[], mdxExtensions: string[]) => void;
10
- export declare const createMdxExtensionFilter: (extensions: string[], options?: {
11
- allowQueryString?: boolean;
12
- }) => RegExp;
13
- export declare function resolveLoaderExtensions(compilerOptions?: CompileOptions, options?: {
14
- defaultMdExtensions?: string[];
15
- }): string[];
16
- /**
17
- * Resolves the MDX parser mode for a source file.
18
- *
19
- * Files with a `.md` extension must be forced into `mdx` mode when the caller
20
- * explicitly opts them into the MDX pipeline. Leaving the compiler in `detect`
21
- * mode would treat `.md` files as plain markdown, causing top-level ESM such as
22
- * `import` and `export` to render as text instead of being compiled.
23
- */
24
- export declare function resolveCompileFormat(filePath: string, compilerOptions?: CompileOptions): CompileOptions['format'];
25
- export declare function resolveMdxCompilerOptions(mdxOptions: MdxCompilerOptionsInput, options: {
26
- jsxImportSource: string;
27
- jsxRuntime?: CompileOptions['jsxRuntime'];
28
- defaults?: CompileOptions;
29
- }): CompileOptions;
@@ -1,55 +0,0 @@
1
- import path from "node:path";
2
- const mergePluginLists = (...lists) => {
3
- const merged = lists.flatMap((list) => list ? [...list] : []);
4
- return merged.length > 0 ? merged : void 0;
5
- };
6
- const appendMdxExtensions = (target, mdxExtensions) => {
7
- for (const extension of mdxExtensions) {
8
- if (!target.includes(extension)) {
9
- target.push(extension);
10
- }
11
- }
12
- };
13
- const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
- const createMdxExtensionFilter = (extensions, options) => {
15
- const escaped = extensions.map(escapeRegex);
16
- const suffix = options?.allowQueryString ? "(\\?.*)?$" : "$";
17
- return new RegExp(`(${escaped.join("|")})${suffix}`);
18
- };
19
- function resolveLoaderExtensions(compilerOptions, options) {
20
- const mdxExtensions = compilerOptions?.mdxExtensions ?? [".mdx"];
21
- const mdExtensions = compilerOptions?.mdExtensions ?? options?.defaultMdExtensions ?? [];
22
- return [...mdxExtensions, ...mdExtensions];
23
- }
24
- function resolveCompileFormat(filePath, compilerOptions) {
25
- const configuredFormat = compilerOptions?.format;
26
- if (configuredFormat && configuredFormat !== "detect") {
27
- return configuredFormat;
28
- }
29
- return path.extname(filePath).toLowerCase() === ".md" ? "mdx" : configuredFormat;
30
- }
31
- function resolveMdxCompilerOptions(mdxOptions, options) {
32
- const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = mdxOptions;
33
- const resolved = {
34
- ...options.defaults,
35
- ...compilerOptions,
36
- jsxImportSource: options.jsxImportSource,
37
- jsxRuntime: options.jsxRuntime ?? "automatic",
38
- development: process.env.NODE_ENV === "development"
39
- };
40
- const mergedRemark = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
41
- const mergedRehype = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
42
- const mergedRecma = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
43
- if (mergedRemark) resolved.remarkPlugins = mergedRemark;
44
- if (mergedRehype) resolved.rehypePlugins = mergedRehype;
45
- if (mergedRecma) resolved.recmaPlugins = mergedRecma;
46
- return resolved;
47
- }
48
- export {
49
- appendMdxExtensions,
50
- createMdxExtensionFilter,
51
- mergePluginLists,
52
- resolveCompileFormat,
53
- resolveLoaderExtensions,
54
- resolveMdxCompilerOptions
55
- };