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

[docs] Use custom webpack loader for markdown #26774

Merged
merged 18 commits into from
Jun 25, 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
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ module.exports = {
{
files: ['docs/pages/**/*.js'],
rules: {
// The code is already coupled to webpack.
'import/no-webpack-loader-syntax': 'off',

'react/prop-types': 'off',
},
},
Expand Down
3 changes: 2 additions & 1 deletion docs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ module.exports = {

// We want to speed-up the build of pull requests.
// For crowdin PRs we want to build all locales for testing.
if (process.env.PULL_REQUEST === 'true' && !l10nPRInNetlify && !vercelDeploy) {
// FIXME: Revert before merging
if (process.env.PULL_REQUEST === 'false' && !l10nPRInNetlify && !vercelDeploy) {
Comment on lines +194 to +195
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm taking care of it #26970.

// eslint-disable-next-line no-console
console.log('Considering only English for SSR');
traverse(pages, 'en');
Expand Down
2 changes: 0 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,11 @@
"lodash": "^4.17.15",
"lz-string": "^1.4.4",
"markdown-to-jsx": "^7.0.0",
"marked": "^2.0.0",
"material-ui-popup-state": "^1.4.1",
"next": "^10.0.0",
"notistack": "^1.0.0",
"nprogress": "^0.2.0",
"postcss": "^8.0.6",
"prismjs": "^1.17.1",
"prop-types": "^15.7.2",
"raw-loader": "^1.0.0",
"react": "^17.0.1",
Expand Down
127 changes: 127 additions & 0 deletions docs/packages/markdown/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const { promises: fs, readFileSync } = require('fs');
const path = require('path');
const { notEnglishMarkdownRegExp, prepareMarkdown } = require('./parseMarkdown');

/**
* @param {string} string
*/
function upperCaseFirst(string) {
return `${string[0].toUpperCase()}${string.slice(1)}`;
}

/**
* @param {string} key
* @example keyToJSIdentifier('index.md') === 'IndexMd'
* @example keyToJSIdentifier('index-ja.md') === 'IndexJaMd'
*/
function keyToJSIdentifier(key) {
const delimiter = /(\.|-)/;
return key
.split(delimiter)
.filter((part) => !delimiter.test(part))
.map(upperCaseFirst)
.join('');
}

/**
* @example findTranslatedVersions('/a/b/index.md') === ['index-en.md', 'index-ja.md', 'index.md']
* @param {string} englishFilepath An absolute path to the english version written in markdown (.md)
*/
async function findTranslatedVersions(englishFilepath) {
const filename = path.basename(englishFilepath, '.md');
const files = await fs.readdir(path.dirname(englishFilepath));

// Given: index.md
// Match: index-en.md, index-ja.md
// Don't Match: otherindex-en.md, index-eng.md, index-e.md, index.tsx
const translatedVersionRegExp = new RegExp(`^${filename}${notEnglishMarkdownRegExp.source}`);

return files
.filter((filepath) => {
return translatedVersionRegExp.test(filepath);
})
.concat(path.basename(englishFilepath));
}

/**
* @type {import('webpack').loader.Loader}
*/
module.exports = async function demoLoader() {
const rawKeys = await findTranslatedVersions(this.resourcePath);

// TODO: Remove requireRaw mock (needs work in prepareMarkdown)
const requireRaw = (key) => {
const filepath = path.join(path.dirname(this.resourcePath), key);
this.addDependency(filepath);
return readFileSync(filepath, { encoding: 'utf-8' });
};
requireRaw.keys = () => rawKeys;
const pageFilename = this.context.replace(this.rootContext, '').replace(/^\/src\/pages\//, '');
const { docs } = prepareMarkdown({ pageFilename, requireRaw });

const demos = {};
const demoKeys = [];
new Set(
docs.en.rendered
.filter((markdownOrComponentConfig) => {
return typeof markdownOrComponentConfig !== 'string' && markdownOrComponentConfig.demo;
})
.map((demoConfig) => {
return path.basename(demoConfig.demo);
}),
).forEach((filename) => {
const demoName = `pages/${pageFilename}/${filename
.replace(/\.\//g, '')
.replace(/\.tsx/g, '.js')}`;

demos[demoName] = {
module: filename,
raw: requireRaw(filename),
};
demoKeys.push(filename);

try {
const moduleTS = filename.replace(/\.js$/, '.tsx');
const rawTS = requireRaw(moduleTS);

demos[demoName].moduleTS = moduleTS;
demos[demoName].rawTS = rawTS;
demoKeys.push(moduleTS);
} catch (error) {
// TS version of the demo doesn't exist. This is fine.
}
});

/**
* @param {string} key
*/
function getRequireDemoIdentifier(key) {
return keyToJSIdentifier(key);
}

const transformed = `
${demoKeys
.map((key) => {
return `import ${getRequireDemoIdentifier(key)} from './${key}';`;
})
.join('\n')}

export const docs = ${JSON.stringify(docs, null, 2)};
export const demos = ${JSON.stringify(demos, null, 2)};
export function requireDemo(module) {
return {
${demoKeys
.map((key) => {
// TODO: Remove ES module interop once all demos are loaded via loader
// i.e. replace `{ default: ... }` with `...`
return `'${key}': { default: ${getRequireDemoIdentifier(key)} }`;
})
.join(',\n')}
}[module];
}
requireDemo.keys = () => {
return ${JSON.stringify(demoKeys, null, 2)}
}`;

return transformed;
};
17 changes: 17 additions & 0 deletions docs/packages/markdown/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@material-ui/markdown",
"version": "0.1.0",
"private": true,
"type": "commonjs",
"main": "./parseMarkdown.js",
"exports": {
".": "./parseMarkdown.js",
"./loader": "./loader.js",
"./prism": "./prism.js"
},
"dependencies": {
"lodash": "^4.17.15",
"marked": "^2.0.0",
"prismjs": "^1.17.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import marked from 'marked';
import { LANGUAGES_IN_PROGRESS } from 'docs/src/modules/constants';
import kebabCase from 'lodash/kebabCase';
import textToHash from 'docs/src/modules/utils/textToHash';
import prism from 'docs/src/modules/utils/prism';
const marked = require('marked');
const kebabCase = require('lodash/kebabCase');
const textToHash = require('./textToHash');
const prism = require('./prism');

// TODO: pass as argument
const LANGUAGES_IN_PROGRESS = ['en', 'zh', 'ru', 'pt', 'es', 'fr', 'de', 'ja'];

const headerRegExp = /---[\r\n]([\s\S]*)[\r\n]---/;
const titleRegExp = /# (.*)[\r\n]/;
Expand All @@ -25,7 +27,7 @@ const notEnglishMarkdownRegExp = /-([a-z]{2})\.md$/;
* should output:
* { title: 'Backdrop React Component', components: ['Backdrop'] }
*/
export function getHeaders(markdown) {
function getHeaders(markdown) {
let header = markdown.match(headerRegExp);

if (!header) {
Expand Down Expand Up @@ -64,14 +66,14 @@ export function getHeaders(markdown) {
return headers;
}

export function getContents(markdown) {
function getContents(markdown) {
return markdown
.replace(headerRegExp, '') // Remove header information
.split(/^{{("(?:demo|component)":[^}]*)}}$/gm) // Split markdown into an array, separating demos
.filter((content) => !emptyRegExp.test(content)); // Remove empty lines
}

export function getTitle(markdown) {
function getTitle(markdown) {
const matches = markdown.match(titleRegExp);

if (!matches || !matches[1]) {
Expand All @@ -81,16 +83,19 @@ export function getTitle(markdown) {
return matches[1];
}

export function getDescription(markdown) {
function getDescription(markdown) {
const matches = markdown.match(descriptionRegExp);
if (matches === null) {
return undefined;
}

return matches?.[1].trim();
return matches[1].trim();
}

/**
* @param {string} markdown
*/
export function renderInline(markdown) {
function renderInline(markdown) {
return marked.parseInline(markdown);
}

Expand Down Expand Up @@ -120,7 +125,7 @@ const externs = [
* @param {TableOfContentsEntry[]} context.toc - WILL BE MUTATED
* @param {string} context.userLanguage
*/
export function createRender(context) {
function createRender(context) {
const { headingHashes, toc, userLanguage } = context;
const headingHashesFallbackTranslated = {};
let headingIndex = -1;
Expand Down Expand Up @@ -238,10 +243,13 @@ export function createRender(context) {
* @param {() => string} config.requireRaw - returnvalue of require.context
* @param {string} config.pageFilename - filename relative to nextjs pages directory
*/
export function prepareMarkdown(config) {
function prepareMarkdown(config) {
const { pageFilename, requireRaw } = config;

const demos = {};
/**
* @type {Record<string, { rendered: Array<string | { component: string } | { demo:string }> }>}
*/
const docs = {};
const headingHashes = {};

Expand Down Expand Up @@ -336,3 +344,14 @@ ${headers.components

return { demos, docs };
}

module.exports = {
createRender,
notEnglishMarkdownRegExp,
getContents,
getDescription,
getHeaders,
getTitle,
prepareMarkdown,
renderInline,
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import prism from 'prismjs';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-diff';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-jsx';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-tsx';
const prism = require('prismjs');
require('prismjs/components/prism-css');
require('prismjs/components/prism-diff');
require('prismjs/components/prism-javascript');
require('prismjs/components/prism-json');
require('prismjs/components/prism-jsx');
require('prismjs/components/prism-markup');
require('prismjs/components/prism-tsx');

export default function highlight(code, language) {
function highlight(code, language) {
let prismLanguage;
switch (language) {
case 'ts':
Expand Down Expand Up @@ -42,3 +42,5 @@ export default function highlight(code, language) {

return prism.highlight(code, prismLanguage);
}

module.exports = highlight;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function makeUnique(hash, unique, i = 1) {
* @param {Record<string, boolean>} [unique] - Ensures that each output is unique in `unique`
* @returns {string} that is safe to use in fragment links
*/
export default function textToHash(text, unique = {}) {
function textToHash(text, unique = {}) {
return makeUnique(
encodeURI(
text
Expand All @@ -32,3 +32,5 @@ export default function textToHash(text, unique = {}) {
unique,
);
}

module.exports = textToHash;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { renderInline as renderInlineMarkdown } from './parseMarkdown';
import { parseInline as renderInlineMarkdown } from 'marked';
import textToHash from './textToHash';

describe('textToHash', () => {
Expand Down
12 changes: 2 additions & 10 deletions docs/pages/blog/2019-developer-survey-results.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import * as React from 'react';
import TopLayoutBlog from 'docs/src/modules/components/TopLayoutBlog';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
import { docs } from '!@material-ui/markdown/loader!./2019-developer-survey-results.md';

const pageFilename = 'blog/2019-developer-survey-results';
const requireRaw = require.context('!raw-loader!./', false, /2019-developer-survey-results\.md$/);

export default function Page({ docs }) {
export default function Page() {
return <TopLayoutBlog docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
12 changes: 2 additions & 10 deletions docs/pages/blog/2019.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import * as React from 'react';
import TopLayoutBlog from 'docs/src/modules/components/TopLayoutBlog';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
import { docs } from '!@material-ui/markdown/loader!./2019.md';

const pageFilename = 'blog/2019';
const requireRaw = require.context('!raw-loader!./', false, /2019\.md$/);

export default function Page({ docs }) {
export default function Page() {
return <TopLayoutBlog docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
12 changes: 2 additions & 10 deletions docs/pages/blog/2020-developer-survey-results.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import * as React from 'react';
import TopLayoutBlog from 'docs/src/modules/components/TopLayoutBlog';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
import { docs } from '!@material-ui/markdown/loader!./2020-developer-survey-results.md';

const pageFilename = 'blog/2020-developer-survey-results';
const requireRaw = require.context('!raw-loader!./', false, /2020-developer-survey-results\.md$/);

export default function Page({ docs }) {
export default function Page() {
return <TopLayoutBlog docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
Loading