Skip to content

Commit 973e5f3

Browse files
Add Turbopack support for Markdoc files (#64)
* Add Turbopack support for Markdoc files * Improve Turbopack implementation and clean up code * add turbo pack config only when needed Co-authored-by: Mike Fix <[email protected]> * Further improve Turbopack implementation and cleanup * Update index.js * Apply suggestion from @mfix-stripe * adding dir as type, fixing turbopack and updating readme * Update README.md * Update README.md * clean up --------- Co-authored-by: Mike Fix <[email protected]> Co-authored-by: Mike Fix <[email protected]>
1 parent 518f65a commit 973e5f3

File tree

6 files changed

+125
-10
lines changed

6 files changed

+125
-10
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ The first thing you'll need to do is install `@markdoc/next.js` and add it to yo
1414
```
1515
2. Open `next.config.js` and add the following code:
1616

17+
When using Webpack:
18+
1719
```js
1820
// next.config.js
1921

@@ -24,6 +26,17 @@ The first thing you'll need to do is install `@markdoc/next.js` and add it to yo
2426
});
2527
```
2628

29+
For [Turbopack support](https://nextjs.org/docs/app/api-reference/turbopack), add the following configuration:
30+
31+
```js
32+
// next.config.js
33+
module.exports = withMarkdoc({
34+
dir: process.cwd(), // Required for Turbopack file resolution
35+
})({
36+
pageExtensions: ['js', 'md'],
37+
});
38+
```
39+
2740
3. Create a new Markdoc file in `pages/docs` named `getting-started.md`.
2841

2942
```

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface MarkdocNextJsOptions {
2828
allowComments?: boolean;
2929
};
3030
schemaPath?: string;
31+
dir?: string;
3132
}
3233

3334
declare function createMarkdocPlugin(

src/index.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
1+
function createTurbopackConfig(nextConfig, pluginOptions) {
2+
const turbopack = nextConfig.turbopack;
3+
4+
if (!turbopack) {
5+
return;
6+
}
7+
8+
const extension = pluginOptions.extension || /\.(md|mdoc)$/;
9+
10+
// Extract file extensions from regex pattern like /\.(md|mdoc)$/ to create glob patterns for Turbopack
11+
const extensionPatterns =
12+
extension instanceof RegExp
13+
? extension.source
14+
.match(/\\\.\(([^)]+)\)\$?/)?.[1]
15+
?.split('|')
16+
.map((e) => `*.${e}`) || ['*.md', '*.mdoc']
17+
: [extension];
18+
19+
const rules = extensionPatterns.reduce((acc, pattern) => {
20+
acc[pattern] = {
21+
loaders: [
22+
{
23+
loader: require.resolve('./loader'),
24+
options: {
25+
...pluginOptions,
26+
},
27+
},
28+
],
29+
as: '*.js',
30+
};
31+
return acc;
32+
}, {});
33+
34+
return {
35+
...nextConfig.turbopack,
36+
rules: {
37+
...nextConfig.turbopack.rules,
38+
...rules,
39+
},
40+
};
41+
}
42+
143
const withMarkdoc =
244
(pluginOptions = {}) =>
345
(nextConfig = {}) => {
46+
const extension = pluginOptions.extension || /\.(md|mdoc)$/;
47+
448
return Object.assign({}, nextConfig, {
549
webpack(config, options) {
650
config.module.rules.push({
7-
test: pluginOptions.extension || /\.(md|mdoc)$/,
51+
test: extension,
852
use: [
953
// Adding the babel loader enables fast refresh
1054
options.defaultLoaders.babel,
@@ -15,7 +59,6 @@ const withMarkdoc =
1559
pagesDir: options.defaultLoaders.babel.options.pagesDir,
1660
...pluginOptions,
1761
dir: options.dir,
18-
nextRuntime: options.nextRuntime,
1962
},
2063
},
2164
],
@@ -27,6 +70,8 @@ const withMarkdoc =
2770

2871
return config;
2972
},
73+
74+
turbopack: createTurbopackConfig(nextConfig, pluginOptions),
3075
});
3176
};
3277

src/loader.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,19 @@ async function load(source) {
6262

6363
const tokenizer = new Markdoc.Tokenizer(options);
6464
const parseOptions = {slots};
65-
6665
const schemaDir = path.resolve(dir, schemaPath || DEFAULT_SCHEMA_PATH);
6766
const tokens = tokenizer.tokenize(source);
6867
const ast = Markdoc.parse(tokens, parseOptions);
6968

70-
const isPage = this.resourcePath.startsWith(appDir || pagesDir);
69+
// Determine if this is a page file by checking if it starts with the provided directories
70+
const isPage = (appDir && this.resourcePath.startsWith(appDir)) ||
71+
(pagesDir && this.resourcePath.startsWith(pagesDir));
7172

7273
// Grabs the path of the file relative to the `/{app,pages}` directory
7374
// to pass into the app props later.
7475
// This array access @ index 1 is safe since Next.js guarantees that
7576
// all pages will be located under either {app,pages}/ or src/{app,pages}/
76-
// https://nextjs.org/docs/advanced-features/src-directory
77+
// https://nextjs.org/docs/app/building-your-application/configuring/src-directory
7778
const filepath = this.resourcePath.split(appDir ? 'app' : 'pages')[1];
7879

7980
const partials = await gatherPartials.call(
@@ -134,7 +135,7 @@ import yaml from 'js-yaml';
134135
// renderers is imported separately so Markdoc isn't sent to the client
135136
import Markdoc, {renderers} from '@markdoc/markdoc'
136137
137-
import {getSchema, defaultObject} from '${normalize(await resolve(__dirname, './runtime'))}';
138+
import {getSchema, defaultObject} from '@markdoc/next.js/runtime';
138139
/**
139140
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
140141
* This enables typescript/ESnext support

tests/__snapshots__/index.test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import yaml from 'js-yaml';
66
// renderers is imported separately so Markdoc isn't sent to the client
77
import Markdoc, {renderers} from '@markdoc/markdoc'
88
9-
import {getSchema, defaultObject} from './src/runtime.js';
9+
import {getSchema, defaultObject} from '@markdoc/next.js/runtime';
1010
/**
1111
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
1212
* This enables typescript/ESnext support
@@ -103,7 +103,7 @@ import yaml from 'js-yaml';
103103
// renderers is imported separately so Markdoc isn't sent to the client
104104
import Markdoc, {renderers} from '@markdoc/markdoc'
105105
106-
import {getSchema, defaultObject} from './src/runtime.js';
106+
import {getSchema, defaultObject} from '@markdoc/next.js/runtime';
107107
/**
108108
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
109109
* This enables typescript/ESnext support
@@ -195,7 +195,7 @@ import yaml from 'js-yaml';
195195
// renderers is imported separately so Markdoc isn't sent to the client
196196
import Markdoc, {renderers} from '@markdoc/markdoc'
197197
198-
import {getSchema, defaultObject} from './src/runtime.js';
198+
import {getSchema, defaultObject} from '@markdoc/next.js/runtime';
199199
/**
200200
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
201201
* This enables typescript/ESnext support
@@ -292,7 +292,7 @@ import yaml from 'js-yaml';
292292
// renderers is imported separately so Markdoc isn't sent to the client
293293
import Markdoc, {renderers} from '@markdoc/markdoc'
294294
295-
import {getSchema, defaultObject} from './src/runtime.js';
295+
import {getSchema, defaultObject} from '@markdoc/next.js/runtime';
296296
/**
297297
* Schema is imported like this so end-user's code is compiled using build-in babel/webpack configs.
298298
* This enables typescript/ESnext support

tests/index.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ const React = require('react');
66
const enhancedResolve = require('enhanced-resolve');
77
const loader = require('../src/loader');
88

9+
// Mock the runtime module using Jest
10+
jest.mock('@markdoc/next.js/runtime', () => require('../src/runtime'), {virtual: true});
11+
912
const source = fs.readFileSync(require.resolve('./fixture.md'), 'utf-8');
1013

1114
// https://stackoverflow.com/questions/53799385/how-can-i-convert-a-windows-path-to-posix-path-using-node-path
@@ -264,3 +267,55 @@ test('import as frontend component', async () => {
264267

265268
expect(normalizeOperatingSystemPaths(output)).toMatchSnapshot();
266269
});
270+
271+
test('Turbopack configuration', () => {
272+
const withMarkdoc = require('../src/index.js');
273+
274+
// Test basic Turbopack configuration
275+
const config = withMarkdoc()({
276+
pageExtensions: ['js', 'md', 'mdoc'],
277+
turbopack: {
278+
rules: {},
279+
},
280+
});
281+
282+
expect(config.turbopack).toBeDefined();
283+
expect(config.turbopack.rules).toBeDefined();
284+
expect(config.turbopack.rules['*.md']).toBeDefined();
285+
expect(config.turbopack.rules['*.mdoc']).toBeDefined();
286+
287+
// Verify rule structure
288+
const mdRule = config.turbopack.rules['*.md'];
289+
expect(mdRule.loaders).toHaveLength(1);
290+
expect(mdRule.loaders[0].loader).toContain('loader');
291+
expect(mdRule.as).toBe('*.js');
292+
293+
// Test that existing turbopack config is preserved
294+
const configWithExisting = withMarkdoc()({
295+
pageExtensions: ['js', 'md'],
296+
turbopack: {
297+
rules: {
298+
'*.svg': {
299+
loaders: ['@svgr/webpack'],
300+
as: '*.js',
301+
},
302+
},
303+
},
304+
});
305+
306+
expect(configWithExisting.turbopack.rules['*.svg']).toBeDefined();
307+
expect(configWithExisting.turbopack.rules['*.md']).toBeDefined();
308+
309+
// Test custom extension
310+
const configWithCustomExt = withMarkdoc({
311+
extension: /\.(markdown|mdx)$/,
312+
})({
313+
pageExtensions: ['js', 'markdown', 'mdx'],
314+
turbopack: {
315+
rules: {},
316+
},
317+
});
318+
319+
expect(configWithCustomExt.turbopack.rules['*.markdown']).toBeDefined();
320+
expect(configWithCustomExt.turbopack.rules['*.mdx']).toBeDefined();
321+
});

0 commit comments

Comments
 (0)