Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(mdx-loader): migrate package to TypeScript #5347

Merged
merged 8 commits into from
Aug 12, 2021
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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module.exports = {
// Ignore certain webpack alias because it can't be resolved
'import/no-unresolved': [
ERROR,
{ignore: ['^@theme', '^@docusaurus', '^@generated']},
{ignore: ['^@theme', '^@docusaurus', '^@generated', 'unist', 'mdast']},
],
'import/extensions': OFF,
'header/header': [
Expand Down
8 changes: 6 additions & 2 deletions packages/docusaurus-mdx-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"name": "@docusaurus/mdx-loader",
"version": "2.0.0-beta.4",
"description": "Docusaurus Loader for MDX",
"main": "src/index.js",
"types": "src/index.d.ts",
"main": "lib/index.js",
"types": "src/types.d.ts",
"publishConfig": {
"access": "public"
},
Expand Down Expand Up @@ -39,6 +39,10 @@
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.4",
"@types/escape-html": "^1.0.1",
"@types/mdast": "^3.0.7",
"@types/stringify-object": "^3.3.1",
"@types/unist": "^2.0.6",
"remark": "^12.0.0",
"remark-mdx": "^1.6.21",
"to-vfile": "^6.0.0",
Expand Down
19 changes: 0 additions & 19 deletions packages/docusaurus-mdx-loader/src/index.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,45 @@
* LICENSE file in the root directory of this source tree.
*/

const {readFile} = require('fs-extra');
const mdx = require('@mdx-js/mdx');
const chalk = require('chalk');
const emoji = require('remark-emoji');
const {
import {readFile} from 'fs-extra';
import mdx from '@mdx-js/mdx';
import chalk from 'chalk';
import emoji from 'remark-emoji';
import {
parseFrontMatter,
parseMarkdownContentTitle,
} = require('@docusaurus/utils');
const stringifyObject = require('stringify-object');
const headings = require('./remark/headings');
const toc = require('./remark/toc');
const unwrapMdxCodeBlocks = require('./remark/unwrapMdxCodeBlocks');
const transformImage = require('./remark/transformImage');
const transformLinks = require('./remark/transformLinks');
const {escapePath} = require('@docusaurus/utils');
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
escapePath,
} from '@docusaurus/utils';
import stringifyObject from 'stringify-object';
import headings from './remark/headings';
import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import {getFileLoaderUtils} from '@docusaurus/core/lib/webpack/utils';
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';

// TODO temporary until Webpack5 export this type
// see https://github.com/webpack/webpack/issues/11630
interface Loader extends Function {
(this: any, source: string): Promise<string | Buffer | void | undefined>;
}

const {
loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils();

const DEFAULT_OPTIONS = {
const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = {
rehypePlugins: [],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
};

// When this throws, it generally means that there's no metadata file associated with this MDX document
// It can happen when using MDX partials (usually starting with _)
// That's why it's important to provide the "isMDXPartial" function in config
async function readMetadataPath(metadataPath) {
async function readMetadataPath(metadataPath: string) {
try {
return await readFile(metadataPath, 'utf8');
} catch (e) {
Expand All @@ -48,15 +57,14 @@ async function readMetadataPath(metadataPath) {
// We don't do that for all frontMatters, only for the configured keys
// {image: "./myImage.png"} => {image: require("./myImage.png")}
function createFrontMatterAssetsExportCode(
filePath,
frontMatter,
frontMatterAssetKeys = [],
frontMatter: Record<string, unknown>,
frontMatterAssetKeys: string[] = [],
) {
if (frontMatterAssetKeys.length === 0) {
return 'undefined';
}

function createFrontMatterAssetRequireCode(value) {
function createFrontMatterAssetRequireCode(value: unknown) {
// Only process string values starting with ./
// We could enhance this logic and check if file exists on disc?
if (typeof value === 'string' && value.startsWith('./')) {
Expand Down Expand Up @@ -84,7 +92,7 @@ function createFrontMatterAssetsExportCode(
return exportValue;
}

module.exports = async function docusaurusMdxLoader(fileString) {
const docusaurusMdxLoader: Loader = async function (fileString) {
const callback = this.async();
const filePath = this.resourcePath;
const reqOptions = this.getOptions() || {};
Expand Down Expand Up @@ -122,35 +130,25 @@ module.exports = async function docusaurusMdxLoader(fileString) {
return callback(err);
}

let exportStr = ``;
exportStr += `\nexport const frontMatter = ${stringifyObject(frontMatter)};`;
exportStr += `\nexport const frontMatterAssets = ${createFrontMatterAssetsExportCode(
filePath,
let exportStr = `
export const frontMatter = ${stringifyObject(frontMatter)};
export const frontMatterAssets = ${createFrontMatterAssetsExportCode(
frontMatter,
reqOptions.frontMatterAssetKeys,
)};`;
exportStr += `\nexport const contentTitle = ${stringifyObject(
contentTitle,
)};`;
)};
export const contentTitle = ${stringifyObject(contentTitle)};`;

// MDX partials are MDX files starting with _ or in a folder starting with _
// Partial are not expected to have an associated metadata file or frontmatter
const isMDXPartial = options.isMDXPartial
? options.isMDXPartial(filePath)
: false;
const isMDXPartial = options.isMDXPartial && options.isMDXPartial(filePath);

if (isMDXPartial && hasFrontMatter) {
const errorMessage = `Docusaurus MDX partial files should not contain FrontMatter.
Those partial files use the _ prefix as a convention by default, but this is configurable.
File at ${filePath} contains FrontMatter that will be ignored: \n${JSON.stringify(
frontMatter,
null,
2,
)}`;

if (options.isMDXPartialFrontMatterWarningDisabled === true) {
// no warning
} else {
File at ${filePath} contains FrontMatter that will be ignored:
${JSON.stringify(frontMatter, null, 2)}`;

if (!options.isMDXPartialFrontMatterWarningDisabled) {
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
if (shouldError) {
return callback(new Error(errorMessage));
Expand All @@ -176,12 +174,14 @@ File at ${filePath} contains FrontMatter that will be ignored: \n${JSON.stringif
}

const code = `
import React from 'react';
import { mdx } from '@mdx-js/react';
import React from 'react';
import { mdx } from '@mdx-js/react';

${exportStr}
${result}
`;
${exportStr}
${result}
`;

return callback(null, code);
};

export default docusaurusMdxLoader;
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@

/* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */

const {parseMarkdownHeadingId} = require('@docusaurus/utils');
const visit = require('unist-util-visit');
const toString = require('mdast-util-to-string');
const slugs = require('github-slugger')();
import {parseMarkdownHeadingId} from '@docusaurus/utils';
import visit, {Visitor} from 'unist-util-visit';
import toString from 'mdast-util-to-string';
import Slugger from 'github-slugger';
import type {Transformer} from 'unified';
import type {Parent} from 'unist';
import type {Heading, Text} from 'mdast';

function headings() {
const transformer = (ast) => {
const slugs = new Slugger();

function headings(): Transformer {
const transformer: Transformer = (ast) => {
slugs.reset();

function visitor(headingNode) {
const data = headingNode.data || (headingNode.data = {}); // eslint-disable-line
const properties = data.hProperties || (data.hProperties = {});
const visitor: Visitor<Heading> = (headingNode) => {
const data = headingNode.data || (headingNode.data = {});
const properties = (data.hProperties || (data.hProperties = {})) as {
id: string;
};
let {id} = properties;

if (id) {
Expand All @@ -29,7 +36,7 @@ function headings() {
);
const heading = toString(
headingTextNodes.length > 0
? {children: headingTextNodes}
? ({children: headingTextNodes} as Parent)
: headingNode,
);

Expand All @@ -42,8 +49,9 @@ function headings() {
// When there's an id, it is always in the last child node
// Sometimes heading is in multiple "parts" (** syntax creates a child node):
// ## part1 *part2* part3 {#id}
const lastNode =
headingNode.children[headingNode.children.length - 1];
const lastNode = headingNode.children[
headingNode.children.length - 1
] as Text;

if (headingNode.children.length > 1) {
const lastNodeText = parseMarkdownHeadingId(lastNode.value).text;
Expand All @@ -63,12 +71,12 @@ function headings() {

data.id = id;
properties.id = id;
}
};

visit(ast, 'heading', visitor);
};

return transformer;
}

module.exports = headings;
export default headings;
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,42 @@
* LICENSE file in the root directory of this source tree.
*/

const {parse} = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const stringifyObject = require('stringify-object');
const search = require('./search');
import {parse, ParserOptions} from '@babel/parser';
import type {Identifier} from '@babel/types';
import traverse from '@babel/traverse';
import stringifyObject from 'stringify-object';
import search from './search';
import type {Plugin, Transformer} from 'unified';
import type {Node, Parent} from 'unist';
import type {Literal} from 'mdast';

const parseOptions = {
const parseOptions: ParserOptions = {
plugins: ['jsx'],
sourceType: 'module',
};
const isImport = (child) => child.type === 'import';
const hasImports = (index) => index > -1;
const isExport = (child) => child.type === 'export';

const isTarget = (child, name) => {
const isImport = (child: Node): child is Literal => child.type === 'import';
const hasImports = (index: number) => index > -1;
const isExport = (child: Node): child is Literal => child.type === 'export';

interface PluginOptions {
name?: string;
}

const isTarget = (child: Literal, name: string) => {
let found = false;
const ast = parse(child.value, parseOptions);
traverse(ast, {
VariableDeclarator: (path) => {
if (path.node.id.name === name) {
if ((path.node.id as Identifier).name === name) {
found = true;
}
},
});

return found;
};

const getOrCreateExistingTargetIndex = (children, name) => {
const getOrCreateExistingTargetIndex = (children: Node[], name: string) => {
let importsIndex = -1;
let targetIndex = -1;

Expand All @@ -58,12 +66,12 @@ const getOrCreateExistingTargetIndex = (children, name) => {
return targetIndex;
};

const plugin = (options = {}) => {
const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const name = options.name || 'toc';

const transformer = (node) => {
const transformer: Transformer = (node) => {
const headings = search(node);
const {children} = node;
const {children} = node as Parent<Literal>;
const targetIndex = getOrCreateExistingTargetIndex(children, name);

if (headings && headings.length) {
Expand All @@ -76,4 +84,4 @@ const plugin = (options = {}) => {
return transformer;
};

module.exports = plugin;
export default plugin;
Loading