1+ import anyBase from 'any-base' ;
12import frontMatter from 'front-matter' ;
23import { getLoaderConfig } from 'loader-utils' ;
4+ import hash from 'sha.js' ;
35import HighlightJS from 'highlight.js' ;
46import MarkdownIt from 'markdown-it' ;
57import { DOM as ReactDOM } from 'react' ;
68
9+ import VOID_ELEMENTS from 'html-void-elements' ;
10+
711import formatImport from './formatters/import' ;
812import formatModule from './formatters/module' ;
913import formatStatic from './formatters/static' ;
1014import htmlToJsx from './html-to-jsx' ;
15+ import processHtml from './process-html' ;
1116import StringReplacementCache from './string-replacement-cache' ;
1217
1318const DEFAULT_CONFIGURATION = {
@@ -16,6 +21,14 @@ const DEFAULT_CONFIGURATION = {
1621 markdownItPlugins : [ ]
1722} ;
1823
24+ const lowercaseHash = ( content ) => (
25+ anyBase ( anyBase . HEX , 'abcdefghijklmnopqrstuvwxyz' ) (
26+ hash ( 'sha256' )
27+ . update ( content , 'utf-8' )
28+ . digest ( 'hex' )
29+ )
30+ ) ;
31+
1932module . exports = function ( source ) {
2033 // This loader is deterministic and will return the same thing for the same inputs!
2134 this . cacheable && this . cacheable ( ) ;
@@ -113,7 +126,34 @@ module.exports = function(source) {
113126 ) ;
114127 }
115128
116- const html = renderer . render ( markdownSansAssignments ) ;
129+ const tagCache = { } ;
130+
131+ const html = processHtml (
132+ renderer . render ( markdownSansAssignments ) ,
133+ ( match , tagFragment , tag ) => {
134+ switch ( tagFragment ) {
135+ case '<' :
136+ // tag names which won't survive browser serialization, or those with
137+ // special characters, need to be cached
138+ if ( tag . tagName . indexOf ( '.' ) !== - 1 || tag . tagName . toLowerCase ( ) !== tag . tagName ) {
139+ const nameHash = lowercaseHash ( tag . tagName ) ;
140+ tagCache [ nameHash ] = tag . tagName ;
141+ tag . tagName = nameHash ;
142+ }
143+ return `<${ tag . tagName } ` ;
144+ case '/>' :
145+ return (
146+ VOID_ELEMENTS . indexOf ( tagCache [ tag . tagName ] || tag . tagName ) === - 1
147+ ? `></${ tag . tagName } >`
148+ : match
149+ ) ;
150+ case '</' :
151+ return `</${ tag . tagName } ` ;
152+ }
153+
154+ return match ;
155+ }
156+ ) ;
117157
118158 let jsx = htmlToJsx (
119159 html || '<!-- no input given -->' ,
@@ -123,17 +163,28 @@ module.exports = function(source) {
123163 // Unload caches so we've got our values back!
124164 jsx = stylePropertyCache . unload ( assignmentExpressionCache . unload ( jsx ) ) ;
125165
126- // Pass through `elementProps` to tags React knows about (the others are already under our control)
127- if ( config . passElementProps ) {
128- jsx = jsx . replace (
129- / < ( [ ^ \/ ] [ ^ \s > ] * ) ( [ ^ / > \s ] * ) / g,
130- ( match , tagName ) => {
131- return ( tagName in ReactDOM )
132- ? `${ match } {...elementProps['${ tagName } ']}`
133- : match ;
166+ jsx = processHtml (
167+ jsx ,
168+ ( match , tagFragment , { tagName, state } ) => {
169+ const correctedTagName = tagCache [ tagName ] || tagName ;
170+
171+ switch ( tagFragment ) {
172+ case '<' :
173+ // Pass through `elementProps` to tags React knows about (the others are already under our control)
174+ if ( config . passElementProps && correctedTagName in ReactDOM ) {
175+ return `<${ correctedTagName } {...elementProps['${ correctedTagName } ']}` ;
176+ }
177+ return `<${ correctedTagName } ` ;
178+ case '/>' :
179+ case '>' :
180+ return match ;
181+ case '</' :
182+ return `</${ correctedTagName } ` ;
134183 }
135- ) ;
136- }
184+
185+ return match ;
186+ }
187+ ) . replace ( / \s \s \{ / , ' {' ) ; // Remove double spaces before spread statements;
137188
138189 return formatModule (
139190 config ,
0 commit comments