Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,18 @@ The first thing you'll need to do is install `@markdoc/next.js` and add it to yo
// next.config.js
module.exports = withMarkdoc({
dir: process.cwd(), // Required for Turbopack file resolution
schemaPath: './markdoc', // Wherever your Markdoc schema lives
})({
pageExtensions: ['js', 'md'],
turbopack: {}, // Turbopack only runs the loader when a base config exists
});
```

Turbopack currently requires every schema entry file referenced by `schemaPath` to exist,
even if you are not customizing them yet. Create `config.js`, `nodes.js`, `tags.js`, and
`functions.js` in that directory (exporting empty objects is fine) so the loader can resolve
them during the build.

3. Create a new Markdoc file in `pages/docs` named `getting-started.md`.

```
Expand Down
56 changes: 45 additions & 11 deletions src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ const Markdoc = require('@markdoc/markdoc');

const DEFAULT_SCHEMA_PATH = './markdoc';

function normalize(s) {
return s.replace(/\\/g, path.win32.sep.repeat(2));
function getRelativeImportPath(from, to) {
const relative = path.relative(path.dirname(from), to);
if (!relative) {
return './';
}

// Module specifiers must use forward slashes on all platforms.
// Backslashes (or escaped versions) cause different loader output per OS
// and are not treated as path delimiters by bundlers.
const request = relative.split(path.sep).join(path.posix.sep);
return request.startsWith('.') ? request : `./${request}`;
}

async function gatherPartials(ast, schemaDir, tokenizer, parseOptions) {
Expand Down Expand Up @@ -67,15 +76,20 @@ async function load(source) {
const ast = Markdoc.parse(tokens, parseOptions);

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

// Grabs the path of the file relative to the `/{app,pages}` directory
// to pass into the app props later.
// This array access @ index 1 is safe since Next.js guarantees that
// all pages will be located under either {app,pages}/ or src/{app,pages}/
// https://nextjs.org/docs/app/building-your-application/configuring/src-directory
const filepath = this.resourcePath.split(appDir ? 'app' : 'pages')[1];
// Normalize to posix separators for consistent output across platforms.
// Undefined for non-page resources (e.g., .md imported as components).
const rawFilepath = this.resourcePath.split(appDir ? 'app' : 'pages')[1];
const filepath = rawFilepath
? rawFilepath.split(path.sep).join(path.posix.sep)
: rawFilepath;

const partials = await gatherPartials.call(
this,
Expand All @@ -93,14 +107,34 @@ async function load(source) {
const directoryExists = await fs.promises.stat(schemaDir);

// This creates import strings that cause the config to be imported runtime
async function importAtRuntime(variable) {
try {
const module = await resolve(schemaDir, variable);
return `import * as ${variable} from '${normalize(module)}'`;
} catch (error) {
return `const ${variable} = {};`;
const importAtRuntime = async (variable) => {
const requests = [variable];

// Turbopack module resolution currently requires explicit relative paths
// when `preferRelative` is used with bare specifiers (e.g. `tags`).
if (
typeof variable === 'string' &&
!variable.startsWith('.') &&
!variable.startsWith('/')
) {
requests.push(`./${variable}`);
}
}

let lastError;

for (const request of requests) {
try {
const module = await resolve(schemaDir, request);
const modulePath = getRelativeImportPath(this.resourcePath, module);
return `import * as ${variable} from '${modulePath}'`;
} catch (error) {
lastError = error;
}
}

console.debug('[Markdoc loader] Failed to resolve', { schemaDir, variable, error: lastError });
return `const ${variable} = {};`;
};

if (directoryExists) {
schemaCode = `
Expand Down
Loading
Loading