Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 68.6k
A quick experiment to get allow React components in Markdown pages and throughout the site#610
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
b781e43797adb1c5a34e4d264c65d51688b35c38e5b1fa93f548853f6e5673c8ef282289d745b3c71bc7b8c8e0f7573ff2fe81204a48998c561bd3374d43b5c0843600c3309d07a7822d83028484473cb791c7c68731b68632ef6c2d07086f89c98392ba8d9c410020File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -89,6 +89,9 @@ const schema = { | ||
| href: { type: 'string' } | ||
| } | ||
| } | ||
| }, | ||
| interactive: { | ||
| type: 'boolean' | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -16,6 +16,7 @@ const pathUtils = require('./path-utils') | ||||||
| const Permalink = require('./permalink') | ||||||
| const languages = require('./languages') | ||||||
| const renderContent = require('./render-content') | ||||||
| const { renderReact } = require('./react/engine') | ||||||
| const frontmatter = require('./frontmatter') | ||||||
| const products = require('./all-products') | ||||||
| const slash = require('slash') | ||||||
| @@ -133,10 +134,31 @@ class Page { | ||||||
| this.title = await renderContent(this.rawTitle, context, { textOnly: true, encodeEntities: true }) | ||||||
| this.shortTitle = await renderContent(this.shortTitle, context, { textOnly: true, encodeEntities: true }) | ||||||
| const markdown = this.mapTopic | ||||||
| let markdown = this.mapTopic | ||||||
| ? getMapTopicContent(this, context.pages, context.redirects) | ||||||
| : this.markdown | ||||||
| // If the article is interactive parse the React! | ||||||
| if (this.interactive) { | ||||||
| // Search for the react code comments to find the react components | ||||||
| const reactComponents = markdown.match(/<!--react-->(.*?)<!--end-react-->/gs) | ||||||
| // Render each of the react components in the markdown | ||||||
| await Promise.all(reactComponents.map(async (reactComponent) => { | ||||||
| let componentStr = reactComponent | ||||||
| // Remove the React comment indicators | ||||||
| componentStr = componentStr.replace('<!--react-->\n', '').replace('<!--react-->', '') | ||||||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible to use a RegExp with replace, like
Suggested change
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you confirm this works @heiskr and if it does can you commit it? I get confused by the first replace having a new line character and the next one not. I personally like coding verbose. But that's a personal preference. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think its fine as is, just an FYI more than anything. | ||||||
| componentStr = componentStr.replace('\n<!--end-react-->', '').replace('<!--end-react-->', '') | ||||||
| // Get the rendered component | ||||||
| const renderedComponent = await renderReact(componentStr) | ||||||
| // Replace the react component with the rendered markdown | ||||||
| markdown = markdown.replace(reactComponent, renderedComponent) | ||||||
| })) | ||||||
| } | ||||||
| const html = await renderContent(markdown, context) | ||||||
| // product frontmatter may contain liquid | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| const babel = require('@babel/core') | ||
| const reactBabelOptions = { | ||
| presets: [ | ||
| '@babel/preset-env', | ||
| '@babel/preset-react' | ||
| ], | ||
| plugins: [ | ||
| '@babel/plugin-transform-react-jsx', | ||
| '@babel/plugin-proposal-object-rest-spread', | ||
| '@babel/plugin-proposal-class-properties', | ||
| '@babel/transform-runtime' | ||
| ] | ||
| } | ||
| const transform = code => | ||
| babel.transform(code, reactBabelOptions).code | ||
| module.exports = { | ||
| transform: transform, | ||
| reactBabelOptions: reactBabelOptions | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| const { renderToString } = require('react-dom/server') | ||
| const { transform } = require('./babel') | ||
| const React = require('react') | ||
| const fs = require('fs') | ||
| const path = require('path') | ||
| const dirTree = require('directory-tree') | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also have a ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You want to try and switch to it @heiskr so we don't have to add another package? Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like directory-tree and walk-sync have different output. walk-sync creates a flat array of just the file names, where directory-tree has a nested structure. I think there might be consequences to the difference as this code seems to look at just the first level. I think it's okay to ship directory-tree for now, and we can always change it later if we find another pattern we prefer. | ||
| // Name of directory for saving transformed components that should be gitignored | ||
| const dist = 'dist' | ||
| // Build React components | ||
| // This loops through the react components and transpiles them to /dist | ||
| // so they can be used by Node.js when we do server side rendering | ||
| const tree = dirTree('./react/') | ||
| if (tree) { | ||
| for (const index in tree.children) { | ||
| const file = tree.children[index] | ||
| if (file.type === 'file' && file.extension === '.js') { | ||
| if (!fs.existsSync(path.join(dist, 'react'))) { | ||
| fs.mkdirSync(path.join(dist, 'react'), { recursive: true }) | ||
| } | ||
| const content = transform(fs.readFileSync(file.path, 'utf8')) | ||
| fs.writeFileSync(path.join(dist, file.path), content) | ||
| } | ||
| } | ||
| } | ||
| // End Build React Components | ||
| // Register components | ||
| const components = { | ||
| // CodeBlock: require('../../dist/react/CodeBlock'), | ||
| // CodeEditor: require('../../dist/react/CodeEditor') | ||
| } | ||
| const renderReact = async componentStr => { | ||
| // Get component name as string so we can use it in the class name | ||
| // which will be needed later if we choose to do client side React hydration | ||
| const componentName = componentStr.match(/<([a-zA-Z]+)\s/)[1] | ||
| // Add the wrapper and class name so we can later use React hydration on the client | ||
| // side | ||
| const jsx = `<div className="react-component-${componentName}">\n${componentStr}\n</div>` | ||
| const component = transform(jsx) | ||
| // eslint-disable-next-line | ||
| const getComponent = new Function( | ||
| 'React', | ||
| ...Object.keys(components), | ||
| `${component.replace('React', 'return React')}` | ||
| ) | ||
| return renderToString(getComponent(React, ...Object.values(components))) | ||
| } | ||
| module.exports = { | ||
| renderReact | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.