Skip to content

Commit 94fcd09

Browse files
committed
support loading markdoc components client side
1 parent 0867e4d commit 94fcd09

File tree

4 files changed

+31
-48
lines changed

4 files changed

+31
-48
lines changed

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const withMarkdoc =
1212
loader: require.resolve('./loader'),
1313
options: {
1414
appDir: options.defaultLoaders.babel.options.appDir,
15+
pagesDir: options.defaultLoaders.babel.options.pagesDir,
1516
...pluginOptions,
1617
dir: options.dir,
1718
nextRuntime: options.nextRuntime,

src/loader.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,7 @@ async function gatherPartials(ast, schemaDir, tokenizer, parseOptions) {
3030
partials = {
3131
...partials,
3232
[file]: content,
33-
...(await gatherPartials.call(
34-
this,
35-
ast,
36-
schemaDir,
37-
tokenizer,
38-
parseOptions
39-
)),
33+
...(await gatherPartials.call(this, ast, schemaDir, tokenizer, parseOptions)),
4034
};
4135
}
4236
}
@@ -63,6 +57,7 @@ async function load(source) {
6357
},
6458
nextjsExports = ['metadata', 'revalidate'],
6559
appDir = false,
60+
pagesDir,
6661
} = this.getOptions() || {};
6762

6863
const tokenizer = new Markdoc.Tokenizer(options);
@@ -72,6 +67,8 @@ async function load(source) {
7267
const tokens = tokenizer.tokenize(source);
7368
const ast = Markdoc.parse(tokens, parseOptions);
7469

70+
const isPage = this.resourcePath.startsWith(appDir || pagesDir);
71+
7572
// Grabs the path of the file relative to the `/{app,pages}` directory
7673
// to pass into the app props later.
7774
// This array access @ index 1 is safe since Next.js guarantees that
@@ -88,8 +85,7 @@ async function load(source) {
8885
);
8986

9087
// IDEA: consider making this an option per-page
91-
const dataFetchingFunction =
92-
mode === 'server' ? 'getServerSideProps' : 'getStaticProps';
88+
const dataFetchingFunction = mode === 'server' ? 'getServerSideProps' : 'getStaticProps';
9389

9490
let schemaCode = 'const schema = {};';
9591
try {
@@ -138,18 +134,14 @@ import yaml from 'js-yaml';
138134
// renderers is imported separately so Markdoc isn't sent to the client
139135
import Markdoc, {renderers} from '@markdoc/markdoc'
140136
141-
import {getSchema, defaultObject} from '${normalize(
142-
await resolve(__dirname, './runtime')
143-
)}';
137+
import {getSchema, defaultObject} from '${normalize(await resolve(__dirname, './runtime'))}';
144138
/**
145139
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
146140
* This enables typescript/ESnext support
147141
*/
148142
${schemaCode}
149143
150-
const tokenizer = new Markdoc.Tokenizer(${
151-
options ? JSON.stringify(options) : ''
152-
});
144+
const tokenizer = new Markdoc.Tokenizer(${options ? JSON.stringify(options) : ''});
153145
154146
/**
155147
* Source will never change at runtime, so parse happens at the file root
@@ -170,7 +162,7 @@ const frontmatter = ast.attributes.frontmatter
170162
171163
const {components, ...rest} = getSchema(schema)
172164
173-
async function getMarkdocData(context = {}) {
165+
${isPage ? 'async ' : ''}function getMarkdocData(context = {}) {
174166
const partials = ${JSON.stringify(partials)};
175167
176168
// Ensure Node.transformChildren is available
@@ -196,7 +188,7 @@ async function getMarkdocData(context = {}) {
196188
* transform must be called in dataFetchingFunction to support server-side rendering while
197189
* accessing variables on the server
198190
*/
199-
const content = await Markdoc.transform(ast, cfg);
191+
const content = ${isPage ? 'await ' : ''}Markdoc.transform(ast, cfg);
200192
201193
// Removes undefined
202194
return JSON.parse(
@@ -211,7 +203,7 @@ async function getMarkdocData(context = {}) {
211203
}
212204
213205
${
214-
appDir
206+
appDir || !isPage
215207
? ''
216208
: `export async function ${dataFetchingFunction}(context) {
217209
return {
@@ -221,10 +213,12 @@ ${
221213
};
222214
}`
223215
}
224-
${appDir ? nextjsExportsCode : ''}
216+
${appDir && isPage ? nextjsExportsCode : ''}
225217
export const markdoc = {frontmatter};
226-
export default${appDir ? ' async' : ''} function MarkdocComponent(props) {
227-
const markdoc = ${appDir ? 'await getMarkdocData()' : 'props.markdoc'};
218+
export default${appDir && isPage ? ' async' : ''} function MarkdocComponent(props) {
219+
const markdoc = ${
220+
isPage ? (appDir ? 'await getMarkdocData()' : 'props.markdoc') : 'getMarkdocData()'
221+
};
228222
// Only execute HMR code in development
229223
return renderers.react(markdoc.content, React, {
230224
components: {

tests/__snapshots__/index.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const tokenizer = new Markdoc.Tokenizer({\\"allowComments\\":true});
116116
* Source will never change at runtime, so parse happens at the file root
117117
*/
118118
const source = \\"---\\\\ntitle: Custom title\\\\n---\\\\n\\\\n# {% $markdoc.frontmatter.title %}\\\\n\\\\n{% tag /%}\\\\n\\";
119-
const filepath = undefined;
119+
const filepath = \\"/test/index.md\\";
120120
const tokens = tokenizer.tokenize(source);
121121
const parseOptions = {\\"slots\\":false};
122122
const ast = Markdoc.parse(tokens, parseOptions);

tests/index.test.js

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,17 @@ function evaluate(output) {
5959
}
6060

6161
function options(config = {}) {
62+
const dir = `${'/Users/someone/a-next-js-repo'}/${config.appDir ? 'app' : 'pages'}`;
63+
6264
const webpackThis = {
6365
context: __dirname,
6466
getOptions() {
6567
return {
6668
...config,
6769
dir: __dirname,
6870
nextRuntime: 'nodejs',
71+
appDir: config.appDir ? dir : undefined,
72+
pagesDir: config.appDir ? undefined : dir,
6973
};
7074
},
7175
getLogger() {
@@ -77,12 +81,10 @@ function options(config = {}) {
7781
const resolve = enhancedResolve.create(options);
7882
return async (context, file) =>
7983
new Promise((res, rej) =>
80-
resolve(context, file, (err, result) =>
81-
err ? rej(err) : res(result)
82-
)
84+
resolve(context, file, (err, result) => (err ? rej(err) : res(result)))
8385
).then(normalizeAbsolutePath);
8486
},
85-
resourcePath: '/Users/someone/a-next-js-repo/pages/test/index.md',
87+
resourcePath: dir + '/test/index.md',
8688
};
8789

8890
return webpackThis;
@@ -102,15 +104,13 @@ async function callLoader(config, source) {
102104
}
103105

104106
test('should not fail build if default `schemaPath` is used', async () => {
105-
await expect(callLoader(options(), source)).resolves.toEqual(
106-
expect.any(String)
107-
);
107+
await expect(callLoader(options(), source)).resolves.toEqual(expect.any(String));
108108
});
109109

110110
test('should fail build if invalid `schemaPath` is used', async () => {
111-
await expect(
112-
callLoader(options({schemaPath: 'unknown_schema_path'}), source)
113-
).rejects.toThrow("Cannot find module 'unknown_schema_path'");
111+
await expect(callLoader(options({schemaPath: 'unknown_schema_path'}), source)).rejects.toThrow(
112+
"Cannot find module 'unknown_schema_path'"
113+
);
114114
});
115115

116116
test('file output is correct', async () => {
@@ -154,11 +154,7 @@ test('file output is correct', async () => {
154154
});
155155

156156
expect(page.default(data.props)).toEqual(
157-
React.createElement(
158-
'article',
159-
undefined,
160-
React.createElement('h1', undefined, 'Custom title')
161-
)
157+
React.createElement('article', undefined, React.createElement('h1', undefined, 'Custom title'))
162158
);
163159
});
164160

@@ -179,11 +175,7 @@ test('app router', async () => {
179175
});
180176

181177
expect(await page.default({})).toEqual(
182-
React.createElement(
183-
'article',
184-
undefined,
185-
React.createElement('h1', undefined, 'Custom title')
186-
)
178+
React.createElement('article', undefined, React.createElement('h1', undefined, 'Custom title'))
187179
);
188180
});
189181

@@ -193,9 +185,7 @@ test('app router metadata', async () => {
193185
source.replace('---', '---\nmetadata:\n title: Metadata title')
194186
);
195187

196-
expect(output).toContain(
197-
'export const metadata = frontmatter.nextjs?.metadata;'
198-
);
188+
expect(output).toContain('export const metadata = frontmatter.nextjs?.metadata;');
199189
});
200190

201191
test.each([
@@ -211,9 +201,7 @@ test.each([
211201
const page = evaluate(output);
212202

213203
const data = await page.getStaticProps({});
214-
expect(data.props.markdoc.content.children[0].children[0]).toEqual(
215-
'Custom title'
216-
);
204+
expect(data.props.markdoc.content.children[0].children[0]).toEqual('Custom title');
217205
expect(data.props.markdoc.content.children[1]).toEqual(expectedChild);
218206
});
219207

0 commit comments

Comments
 (0)