1 | const {transformSync} = require('@babel/core')
|
2 | const styleToObject = require('style-to-object')
|
3 | const camelCaseCSS = require('camelcase-css')
|
4 | const uniq = require('lodash.uniq')
|
5 | const {paramCase, toTemplateLiteral} = require('@mdx-js/util')
|
6 | const BabelPluginApplyMdxProp = require('babel-plugin-apply-mdx-type-prop')
|
7 | const BabelPluginExtractImportNames = require('babel-plugin-extract-import-names')
|
8 |
|
9 |
|
10 | const spaceSeparatedProperties = [
|
11 | 'acceptCharset',
|
12 | 'accessKey',
|
13 | 'autoComplete',
|
14 | 'className',
|
15 | 'controlsList',
|
16 | 'headers',
|
17 | 'htmlFor',
|
18 | 'httpEquiv',
|
19 | 'itemProp',
|
20 | 'itemRef',
|
21 | 'itemType',
|
22 | 'ping',
|
23 | 'rel',
|
24 | 'sandbox'
|
25 | ]
|
26 |
|
27 |
|
28 | function toJSX(node, parentNode = {}, options = {}) {
|
29 | const {
|
30 |
|
31 | skipExport = false,
|
32 | preserveNewlines = false,
|
33 | wrapExport
|
34 | } = options
|
35 | let children = ''
|
36 |
|
37 | if (node.properties != null) {
|
38 |
|
39 | if (typeof node.properties.style === 'string') {
|
40 | let styleObject = {}
|
41 | styleToObject(node.properties.style, function (name, value) {
|
42 | styleObject[camelCaseCSS(name)] = value
|
43 | })
|
44 | node.properties.style = styleObject
|
45 | }
|
46 |
|
47 |
|
48 | if (node.properties.class) {
|
49 | node.properties.className = node.properties.class
|
50 | delete node.properties.class
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 | const paramCaseRe = /^(aria[A-Z])|(data[A-Z])/
|
56 | node.properties = Object.entries(node.properties).reduce(
|
57 | (properties, [key, value]) =>
|
58 | Object.assign({}, properties, {
|
59 | [paramCaseRe.test(key) ? paramCase(key) : key]: value
|
60 | }),
|
61 | {}
|
62 | )
|
63 | }
|
64 |
|
65 | if (node.type === 'root') {
|
66 | const importNodes = []
|
67 | const exportNodes = []
|
68 | const jsxNodes = []
|
69 | let layout
|
70 | for (const childNode of node.children) {
|
71 | if (childNode.type === 'import') {
|
72 | importNodes.push(childNode)
|
73 | continue
|
74 | }
|
75 |
|
76 | if (childNode.type === 'export') {
|
77 | if (childNode.default) {
|
78 | layout = childNode.value
|
79 | .replace(/^export\s+default\s+/, '')
|
80 | .replace(/;\s*$/, '')
|
81 | continue
|
82 | }
|
83 |
|
84 | exportNodes.push(childNode)
|
85 | continue
|
86 | }
|
87 |
|
88 | jsxNodes.push(childNode)
|
89 | }
|
90 |
|
91 | const exportNames = exportNodes
|
92 | .map(node =>
|
93 | node.value.match(/^export\s*(var|const|let|class|function)?\s*(\w+)/)
|
94 | )
|
95 | .map(match => (Array.isArray(match) ? match[2] : null))
|
96 | .filter(Boolean)
|
97 |
|
98 | const importStatements = importNodes
|
99 | .map(childNode => toJSX(childNode, node))
|
100 | .join('\n')
|
101 | const exportStatements = exportNodes
|
102 | .map(childNode => toJSX(childNode, node))
|
103 | .join('\n')
|
104 | const layoutProps = `const layoutProps = {
|
105 | ${exportNames.join(',\n')}
|
106 | };`
|
107 | const mdxLayout = `const MDXLayout = ${layout ? layout : '"wrapper"'}`
|
108 |
|
109 | const fn = `function MDXContent({ components, ...props }) {
|
110 | return (
|
111 | <MDXLayout
|
112 | {...layoutProps}
|
113 | {...props}
|
114 | components={components}>
|
115 | ${jsxNodes.map(childNode => toJSX(childNode, node)).join('')}
|
116 | </MDXLayout>
|
117 | )
|
118 | };
|
119 | MDXContent.isMDXComponent = true`
|
120 |
|
121 |
|
122 | const babelPluginExtractImportNamesInstance = new BabelPluginExtractImportNames()
|
123 | transformSync(importStatements, {
|
124 | configFile: false,
|
125 | babelrc: false,
|
126 | plugins: [
|
127 | require('@babel/plugin-syntax-jsx'),
|
128 | require('@babel/plugin-syntax-object-rest-spread'),
|
129 | babelPluginExtractImportNamesInstance.plugin
|
130 | ]
|
131 | })
|
132 | const importNames = babelPluginExtractImportNamesInstance.state.names
|
133 |
|
134 | const babelPluginApplyMdxPropInstance = new BabelPluginApplyMdxProp()
|
135 | const babelPluginApplyMdxPropToExportsInstance = new BabelPluginApplyMdxProp()
|
136 |
|
137 | const fnPostMdxTypeProp = transformSync(fn, {
|
138 | configFile: false,
|
139 | babelrc: false,
|
140 | plugins: [
|
141 | require('@babel/plugin-syntax-jsx'),
|
142 | require('@babel/plugin-syntax-object-rest-spread'),
|
143 | babelPluginApplyMdxPropInstance.plugin
|
144 | ]
|
145 | }).code
|
146 |
|
147 | const exportStatementsPostMdxTypeProps = transformSync(exportStatements, {
|
148 | configFile: false,
|
149 | babelrc: false,
|
150 | plugins: [
|
151 | require('@babel/plugin-syntax-jsx'),
|
152 | require('@babel/plugin-syntax-object-rest-spread'),
|
153 | babelPluginApplyMdxPropToExportsInstance.plugin
|
154 | ]
|
155 | }).code
|
156 |
|
157 | const allJsxNames = [
|
158 | ...babelPluginApplyMdxPropInstance.state.names,
|
159 | ...babelPluginApplyMdxPropToExportsInstance.state.names
|
160 | ]
|
161 | const jsxNames = allJsxNames.filter(name => name !== 'MDXLayout')
|
162 |
|
163 | const importExportNames = importNames.concat(exportNames)
|
164 | const fakedModulesForGlobalScope =
|
165 | `const makeShortcode = name => function MDXDefaultShortcode(props) {
|
166 | console.warn("Component " + name + " was not imported, exported, or provided by MDXProvider as global scope")
|
167 | return <div {...props}/>
|
168 | };
|
169 | ` +
|
170 | uniq(jsxNames)
|
171 | .filter(name => !importExportNames.includes(name))
|
172 | .map(name => `const ${name} = makeShortcode("${name}");`)
|
173 | .join('\n')
|
174 |
|
175 | const moduleBase = `${importStatements}
|
176 | ${exportStatementsPostMdxTypeProps}
|
177 | ${fakedModulesForGlobalScope}
|
178 | ${layoutProps}
|
179 | ${mdxLayout}`
|
180 |
|
181 | if (skipExport) {
|
182 | return `${moduleBase}
|
183 | ${fnPostMdxTypeProp}`
|
184 | }
|
185 |
|
186 | if (wrapExport) {
|
187 | return `${moduleBase}
|
188 | ${fnPostMdxTypeProp}
|
189 | export default ${wrapExport}(MDXContent)`
|
190 | }
|
191 |
|
192 | return `${moduleBase}
|
193 | export default ${fnPostMdxTypeProp}`
|
194 | }
|
195 |
|
196 |
|
197 | if (node.children) {
|
198 | children = node.children
|
199 | .map(childNode => {
|
200 | const childOptions = Object.assign({}, options, {
|
201 |
|
202 | preserveNewlines: preserveNewlines || node.tagName === 'pre'
|
203 | })
|
204 | return toJSX(childNode, node, childOptions)
|
205 | })
|
206 | .join('')
|
207 | }
|
208 |
|
209 | if (node.type === 'element') {
|
210 | let props = ''
|
211 |
|
212 | if (node.properties) {
|
213 | spaceSeparatedProperties.forEach(prop => {
|
214 | if (Array.isArray(node.properties[prop])) {
|
215 | node.properties[prop] = node.properties[prop].join(' ')
|
216 | }
|
217 | })
|
218 |
|
219 | if (Object.keys(node.properties).length > 0) {
|
220 | props = JSON.stringify(node.properties)
|
221 | }
|
222 | }
|
223 |
|
224 | return `<${node.tagName}${
|
225 | parentNode.tagName ? ` parentName="${parentNode.tagName}"` : ''
|
226 | }${props ? ` {...${props}}` : ''}>${children}</${node.tagName}>`
|
227 | }
|
228 |
|
229 |
|
230 | if (node.type === 'text') {
|
231 |
|
232 |
|
233 | const shouldPreserveNewlines =
|
234 | preserveNewlines || parentNode.tagName === 'p'
|
235 |
|
236 | if (node.value === '\n' && !shouldPreserveNewlines) {
|
237 | return node.value
|
238 | }
|
239 |
|
240 | return toTemplateLiteral(node.value)
|
241 | }
|
242 |
|
243 | if (node.type === 'comment') {
|
244 | return `{/*${node.value}*/}`
|
245 | }
|
246 |
|
247 | if (node.type === 'import' || node.type === 'export' || node.type === 'jsx') {
|
248 | return node.value
|
249 | }
|
250 | }
|
251 |
|
252 | function compile(options = {}) {
|
253 | this.Compiler = function (tree) {
|
254 | return toJSX(tree, {}, options)
|
255 | }
|
256 | }
|
257 |
|
258 | module.exports = compile
|
259 | exports = compile
|
260 | exports.toJSX = toJSX
|
261 | exports.default = compile
|