hexo-renderer-mdx 1.0.2 → 1.0.3

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.
Files changed (3) hide show
  1. package/README.md +27 -1
  2. package/index.js +80 -10
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -8,7 +8,8 @@ A [Hexo](https://hexo.io/) renderer plugin for [MDX](https://mdxjs.com/) - Markd
8
8
  - ⚛️ React component integration
9
9
  - 📝 Markdown compatibility
10
10
  - 🎨 Custom component support
11
- - 🔥 Fast compilation with @mdx-js/mdx
11
+ - ES6 import statements for external packages
12
+ - �� Fast compilation with @mdx-js/mdx
12
13
 
13
14
  ## Installation
14
15
 
@@ -171,6 +172,31 @@ export const Alert = ({ children, type = 'info' }) => (
171
172
  </div>
172
173
  ```
173
174
 
175
+ **Import Statements** - Import external modules and packages:
176
+
177
+ ```mdx
178
+ import React from 'react';
179
+ import { format } from 'date-fns';
180
+
181
+ <div>
182
+ Today's date: {format(new Date(), 'MMMM dd, yyyy')}
183
+ </div>
184
+ ```
185
+
186
+ You can also import local components or utilities:
187
+
188
+ ```mdx
189
+ import MyCustomComponent from './components/MyCustomComponent';
190
+ import { helper } from './utils/helpers';
191
+
192
+ <MyCustomComponent data={helper()} />
193
+ ```
194
+
195
+ **Note**: Make sure any packages you import are installed in your Hexo project:
196
+ ```bash
197
+ npm install date-fns --save
198
+ ```
199
+
174
200
  #### 5. Building and Deploying
175
201
 
176
202
  Build your site as usual:
package/index.js CHANGED
@@ -4,6 +4,36 @@ const { renderToString } = require('react-dom/server');
4
4
  const React = require('react');
5
5
  const fs = require('fs');
6
6
  const { createRequire } = require('module');
7
+ const { pathToFileURL, fileURLToPath } = require('url');
8
+
9
+ let babelRegistered = false;
10
+ function ensureBabelRegister(filePath) {
11
+ if (babelRegistered) return;
12
+ const localRequire = createRequire(filePath);
13
+ let babelRegister;
14
+ try {
15
+ babelRegister = localRequire('@babel/register');
16
+ } catch (errLocal) {
17
+ try {
18
+ babelRegister = require('@babel/register');
19
+ } catch (errGlobal) {
20
+ throw new Error(
21
+ 'Failed to set up Babel register: please install @babel/register (and babel plugins) in your Hexo project or renderer package.'
22
+ );
23
+ }
24
+ }
25
+ babelRegister({
26
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
27
+ plugins: [
28
+ '@babel/plugin-syntax-dynamic-import',
29
+ ['@babel/plugin-transform-react-jsx', {
30
+ runtime: 'automatic'
31
+ }]
32
+ ],
33
+ ignore: [/node_modules/]
34
+ });
35
+ babelRegistered = true;
36
+ }
7
37
 
8
38
  // Create a require for loading ESM modules
9
39
  let compile;
@@ -56,16 +86,19 @@ async function loadCompile() {
56
86
  * @returns {Promise<string>} The rendered HTML
57
87
  */
58
88
  async function mdxRenderer(data) {
59
- const { text, path } = data;
89
+ const { text, path: filePath } = data;
60
90
 
61
91
  try {
92
+ // Ensure Babel can handle JSX/TS imports from MDX files (e.g., local components).
93
+ ensureBabelRegister(filePath);
94
+
62
95
  // Ensure compile function is loaded
63
96
  await loadCompile();
64
97
 
65
98
  // Read the original file directly to bypass Hexo's template processing
66
99
  let content;
67
100
  try {
68
- content = fs.readFileSync(path, 'utf8');
101
+ content = fs.readFileSync(filePath, 'utf8');
69
102
  } catch (err) {
70
103
  // If reading fails, fall back to the provided text
71
104
  content = text;
@@ -85,6 +118,7 @@ async function mdxRenderer(data) {
85
118
  const compiled = await compile(content, {
86
119
  outputFormat: 'function-body',
87
120
  development: true,
121
+ baseUrl: pathToFileURL(filePath),
88
122
  // remarkRehypeOptions for markdown processing
89
123
  remarkRehypeOptions: {
90
124
  allowDangerousHtml: true
@@ -97,13 +131,47 @@ async function mdxRenderer(data) {
97
131
  // When development: true, the compiled code uses jsxDEV from react/jsx-dev-runtime
98
132
  const jsxDevRuntime = require('react/jsx-dev-runtime');
99
133
 
100
- // Create and execute the MDX module function
101
- // Note: Using new Function() here is safe because:
102
- // 1. Input comes from MDX files in the user's Hexo project (not untrusted external input)
103
- // 2. MDX compilation itself validates and sanitizes the content
104
- // 3. This is a build-time operation, not runtime user input
105
- const fn = new Function(code);
106
- const mdxModule = fn.call(null, jsxDevRuntime);
134
+ // Replace dynamic imports with a shim that resolves relative to the MDX file and uses require to stay in CJS.
135
+ const toModuleNamespace = (mod) => {
136
+ // If it already looks like an ES module with a default export, return as-is
137
+ if (mod && typeof mod === 'object' && 'default' in mod) return mod;
138
+ // For CJS modules, wrap as ES module: spread first, then set default to ensure it's not overwritten
139
+ if (mod && typeof mod === 'object') {
140
+ return { ...mod, default: mod };
141
+ }
142
+ // For primitive or function values, just set as default
143
+ return { default: mod };
144
+ };
145
+ const dynamicImport = (specifier) => {
146
+ const asString = String(specifier);
147
+ const req = createRequire(filePath);
148
+ // Check if it's already a file:// URL string
149
+ if (asString.startsWith('file://')) {
150
+ try {
151
+ const fsPath = fileURLToPath(asString);
152
+ return Promise.resolve(toModuleNamespace(req(fsPath)));
153
+ } catch (err) {
154
+ // Re-throw with better error message
155
+ throw new Error(`Failed to require file:// URL: ${err.message}`);
156
+ }
157
+ }
158
+ try {
159
+ // Try to construct a URL to see if it's relative
160
+ const resolvedUrl = new URL(asString, pathToFileURL(filePath));
161
+ if (resolvedUrl.protocol === 'file:') {
162
+ return Promise.resolve(toModuleNamespace(req(fileURLToPath(resolvedUrl))));
163
+ }
164
+ return Promise.resolve(toModuleNamespace(req(asString)));
165
+ } catch (urlErr) {
166
+ // If URL construction failed, try bare require
167
+ return Promise.resolve(toModuleNamespace(req(asString)));
168
+ }
169
+ };
170
+
171
+ // Swap all occurrences of 'import(' (awaited or not) with our shim to avoid vm dynamic import callbacks.
172
+ const patchedCode = code.replace(/import\(/g, 'dynamicImport(');
173
+ const fn = new Function('jsxRuntime', 'dynamicImport', `return (async () => { ${patchedCode} })();`);
174
+ const mdxModule = await fn(jsxDevRuntime, dynamicImport);
107
175
 
108
176
  // The result has a default export which is the MDX component
109
177
  const MDXContent = mdxModule.default;
@@ -116,8 +184,10 @@ async function mdxRenderer(data) {
116
184
  return html;
117
185
  } catch (err) {
118
186
  // Provide more detailed error information
119
- const errorMsg = `MDX compilation failed for ${path}: ${err.message}`;
187
+ const errorMsg = `MDX compilation failed for ${filePath}: ${err.message}`;
120
188
  console.error(errorMsg);
189
+ console.error('Full error stack:');
190
+ console.error(err.stack);
121
191
  if (err.position) {
122
192
  console.error(`Error at line ${err.position.start.line}, column ${err.position.start.column}`);
123
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-renderer-mdx",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "MDX renderer plugin for Hexo with React component support",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "homepage": "https://github.com/Bryan0324/hexo-renderer-mdx#readme",
27
27
  "dependencies": {
28
+ "@babel/register": "^7.28.3",
28
29
  "@mdx-js/mdx": "^3.0.0",
29
30
  "react": "^18.2.0",
30
31
  "react-dom": "^18.2.0"