@@ -6,10 +6,11 @@ import htm from 'htm';
66 * @param {string } [options.pragma=h] JSX/hyperscript pragma.
77 * @param {string } [options.tag=html] The tagged template "tag" function name to process.
88 * @param {boolean } [options.monomorphic=false] Output monomorphic inline objects instead of using String literals.
9+ * @param {boolean } [options.useBuiltIns=false] Use the native Object.assign instead of trying to polyfill it.
910 */
1011export default function htmBabelPlugin ( { types : t } , options = { } ) {
1112 const pragma = options . pragma === false ? false : dottedIdentifier ( options . pragma || 'h' ) ;
12-
13+ const useBuiltIns = options . useBuiltIns ;
1314 const inlineVNodes = options . monomorphic || pragma === false ;
1415
1516 function dottedIdentifier ( keypath ) {
@@ -59,91 +60,60 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
5960
6061 return t . callExpression ( pragma , [ tag , props , children ] ) ;
6162 }
62-
63- let isVNode = t . isCallExpression ;
64- if ( inlineVNodes ) {
65- isVNode = node => {
66- if ( ! t . isObjectExpression ( node ) ) return false ;
67- return node . properties [ 0 ] . value . value !== 3 ;
68- } ;
69- }
70-
71- function childMapper ( child , index , children ) {
72- // JSX-style whitespace: (@TODO: remove? doesn't match the browser version)
73- if ( typeof child === 'string' && child . trim ( ) . length === 0 || child == null ) {
74- if ( index === 0 || index === children . length - 1 ) return null ;
75- }
76- if ( typeof child === 'string' && isVNode ( children [ index - 1 ] ) && isVNode ( children [ index + 1 ] ) ) {
77- child = child . trim ( ) ;
78- }
79- if ( typeof child === 'string' ) {
80- return stringValue ( child ) ;
63+
64+ function spreadNode ( args , state ) {
65+ // 'Object.assign({}, x)', can be collapsed to 'x'.
66+ if ( args . length === 2 && ! t . isNode ( args [ 0 ] ) && Object . keys ( args [ 0 ] ) . length === 0 ) {
67+ return propsNode ( args [ 1 ] ) ;
8168 }
82- return child ;
69+ const helper = useBuiltIns ? dottedIdentifier ( 'Object.assign' ) : state . addHelper ( 'extends' ) ;
70+ return t . callExpression ( helper , args . map ( propsNode ) ) ;
8371 }
84-
85- function h ( tag , props ) {
86- if ( typeof tag === 'string' ) {
87- tag = t . stringLiteral ( tag ) ;
88- }
89-
90- let propsNode ;
91-
92- if ( t . isObjectExpression ( props ) ) {
93- propsNode = props ;
94- for ( let i in props ) {
95- if ( props . hasOwnProperty ( i ) && props [ i ] && props [ i ] . type ) {
96- for ( let j = 0 ; j < props . properties . length ; j ++ ) {
97- if ( props . properties [ j ] . start > props [ i ] . start ) {
98- props . properties . splice ( j , 0 , t . objectProperty ( propertyName ( i ) , props [ i ] ) ) ;
99- break ;
100- }
101- }
102- delete props [ i ] ;
72+
73+ function propsNode ( props ) {
74+ return t . isNode ( props ) ? props : t . objectExpression (
75+ Object . keys ( props ) . map ( key => {
76+ let value = props [ key ] ;
77+ if ( typeof value === 'string' ) {
78+ value = t . stringLiteral ( value ) ;
10379 }
104- }
105- }
106- else {
107- propsNode = t . objectExpression (
108- Object . keys ( props ) . map ( key => {
109- let value = props [ key ] ;
110- if ( typeof value === 'string' ) {
111- value = t . stringLiteral ( value ) ;
112- }
113- else if ( typeof value === 'boolean' ) {
114- value = t . booleanLiteral ( value ) ;
115- }
116- else if ( typeof value === 'number' ) {
117- value = t . stringLiteral ( value + '' ) ;
118- }
119- return t . objectProperty ( propertyName ( key ) , value ) ;
120- } )
121- ) ;
122- }
123-
124- // recursive iteration of possibly nested arrays of children.
125- let children = [ ] ;
126- if ( arguments . length > 2 ) {
127- const stack = [ ] ;
128- // eslint-disable-next-line prefer-rest-params
129- for ( let i = arguments . length ; i -- > 2 ; ) stack . push ( arguments [ i ] ) ;
130- while ( stack . length ) {
131- const child = stack . pop ( ) ;
132- if ( Array . isArray ( child ) ) {
133- for ( let i = child . length ; i -- ; ) stack . push ( child [ i ] ) ;
134- }
135- else if ( child != null ) {
136- children . push ( child ) ;
80+ else if ( typeof value === 'boolean' ) {
81+ value = t . booleanLiteral ( value ) ;
13782 }
83+ return t . objectProperty ( propertyName ( key ) , value ) ;
84+ } )
85+ ) ;
86+ }
87+
88+ function transform ( { tag, props, children } , state ) {
89+ function childMapper ( child ) {
90+ if ( typeof child === 'string' ) {
91+ return stringValue ( child ) ;
13892 }
139- children = children . map ( childMapper ) . filter ( Boolean ) ;
93+ return t . isNode ( child ) ? child : transform ( child , state ) ;
14094 }
141- children = t . arrayExpression ( children ) ;
142-
143- return createVNode ( tag , propsNode , children ) ;
95+ const newTag = typeof tag === 'string' ? t . stringLiteral ( tag ) : tag ;
96+ const newProps = ! Array . isArray ( props ) ? propsNode ( props ) : spreadNode ( props , state ) ;
97+ const newChildren = t . arrayExpression ( children . map ( childMapper ) ) ;
98+ return createVNode ( newTag , newProps , newChildren ) ;
14499 }
145100
101+ function h ( tag , props , ...children ) {
102+ return { tag, props, children } ;
103+ }
104+
146105 const html = htm . bind ( h ) ;
106+
107+ function treeify ( statics , expr ) {
108+ const assign = Object . assign ;
109+ try {
110+ Object . assign = function ( ...objs ) { return objs ; } ;
111+ return html ( statics , ...expr ) ;
112+ }
113+ finally {
114+ Object . assign = assign ;
115+ }
116+ }
147117
148118 // The tagged template tag function name we're looking for.
149119 // This is static because it's generally assigned via htm.bind(h),
@@ -152,12 +122,14 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
152122 return {
153123 name : 'htm' ,
154124 visitor : {
155- TaggedTemplateExpression ( path ) {
125+ TaggedTemplateExpression ( path , state ) {
156126 const tag = path . node . tag . name ;
157127 if ( htmlName [ 0 ] === '/' ? patternStringToRegExp ( htmlName ) . test ( tag ) : tag === htmlName ) {
158128 const statics = path . node . quasi . quasis . map ( e => e . value . raw ) ;
159129 const expr = path . node . quasi . expressions ;
160- path . replaceWith ( html ( statics , ...expr ) ) ;
130+
131+ const tree = treeify ( statics , expr ) ;
132+ path . replaceWith ( transform ( tree , state ) ) ;
161133 }
162134 }
163135 }
0 commit comments