1- import htm from 'htm ' ;
1+ import { build , treeify } from '../../src/build.mjs ' ;
22
33/**
44 * @param {Babel } babel
@@ -7,15 +7,15 @@ import htm from 'htm';
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.
99 * @param {boolean } [options.useBuiltIns=false] Use the native Object.assign instead of trying to polyfill it.
10+ * @param {boolean } [options.useNativeSpread=false] Use the native { ...a, ...b } syntax for prop spreads.
1011 * @param {boolean } [options.variableArity=true] If `false`, always passes exactly 3 arguments to the pragma function.
1112 */
1213export default function htmBabelPlugin ( { types : t } , options = { } ) {
1314 const pragma = options . pragma === false ? false : dottedIdentifier ( options . pragma || 'h' ) ;
1415 const useBuiltIns = options . useBuiltIns ;
16+ const useNativeSpread = options . useNativeSpread ;
1517 const inlineVNodes = options . monomorphic || pragma === false ;
1618
17- const symbol = Symbol ( ) ;
18-
1919 function dottedIdentifier ( keypath ) {
2020 const path = keypath . split ( '.' ) ;
2121 let out ;
@@ -31,12 +31,32 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
3131 const end = parts . pop ( ) || '' ;
3232 return new RegExp ( parts . join ( '/' ) , end ) ;
3333 }
34-
34+
3535 function propertyName ( key ) {
36- if ( key . match ( / ( ^ \d | [ ^ a - z 0 - 9 _ $ ] ) / i) ) return t . stringLiteral ( key ) ;
37- return t . identifier ( key ) ;
36+ if ( t . isValidIdentifier ( key ) ) {
37+ return t . identifier ( key ) ;
38+ }
39+ return t . stringLiteral ( key ) ;
3840 }
39-
41+
42+ function objectProperties ( obj ) {
43+ return Object . keys ( obj ) . map ( key => {
44+ const values = obj [ key ] . map ( valueOrNode =>
45+ t . isNode ( valueOrNode ) ? valueOrNode : t . valueToNode ( valueOrNode )
46+ ) ;
47+
48+ let node = values [ 0 ] ;
49+ if ( values . length > 1 && ! t . isStringLiteral ( node ) && ! t . isStringLiteral ( values [ 1 ] ) ) {
50+ node = t . binaryExpression ( '+' , t . stringLiteral ( '' ) , node ) ;
51+ }
52+ values . slice ( 1 ) . forEach ( value => {
53+ node = t . binaryExpression ( '+' , node , value ) ;
54+ } ) ;
55+
56+ return t . objectProperty ( propertyName ( key ) , node ) ;
57+ } ) ;
58+ }
59+
4060 function stringValue ( str ) {
4161 if ( options . monomorphic ) {
4262 return t . objectExpression ( [
@@ -75,18 +95,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
7595 return t . callExpression ( pragma , [ tag , props ] . concat ( children ) ) ;
7696 }
7797
78- function flatten ( props , result = [ ] ) {
79- const { [ symbol ] : head , ...tail } = props ;
80- if ( head ) head . forEach ( obj => {
81- flatten ( obj , result ) ;
82- } ) ;
83- if ( Object . keys ( tail ) . length > 0 ) {
84- result . push ( tail ) ;
85- }
86- return result ;
87- }
88-
8998 function spreadNode ( args , state ) {
99+ if ( args . length === 0 ) {
100+ return t . nullLiteral ( ) ;
101+ }
90102 if ( args . length > 0 && t . isNode ( args [ 0 ] ) ) {
91103 args . unshift ( { } ) ;
92104 }
@@ -99,28 +111,31 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
99111 if ( args . length === 2 && ! t . isNode ( args [ 0 ] ) && Object . keys ( args [ 0 ] ) . length === 0 ) {
100112 return propsNode ( args [ 1 ] ) ;
101113 }
114+
115+ if ( useNativeSpread ) {
116+ const properties = [ ] ;
117+ args . forEach ( arg => {
118+ if ( t . isNode ( arg ) ) {
119+ properties . push ( t . spreadElement ( arg ) ) ;
120+ }
121+ else {
122+ properties . push ( ...objectProperties ( arg ) ) ;
123+ }
124+ } ) ;
125+ return t . objectExpression ( properties ) ;
126+ }
127+
102128 const helper = useBuiltIns ? dottedIdentifier ( 'Object.assign' ) : state . addHelper ( 'extends' ) ;
103129 return t . callExpression ( helper , args . map ( propsNode ) ) ;
104130 }
105131
106132 function propsNode ( props ) {
107- return t . isNode ( props ) ? props : 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- return t . objectProperty ( propertyName ( key ) , value ) ;
117- } )
118- ) ;
133+ return t . isNode ( props ) ? props : t . objectExpression ( objectProperties ( props ) ) ;
119134 }
120135
121136 function transform ( node , state ) {
122137 if ( node === undefined ) return t . identifier ( 'undefined' ) ;
123- if ( node == null ) return t . nullLiteral ( ) ;
138+ if ( node === null ) return t . nullLiteral ( ) ;
124139
125140 const { tag, props, children } = node ;
126141 function childMapper ( child ) {
@@ -130,27 +145,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
130145 return t . isNode ( child ) ? child : transform ( child , state ) ;
131146 }
132147 const newTag = typeof tag === 'string' ? t . stringLiteral ( tag ) : tag ;
133- const newProps = props ? spreadNode ( flatten ( props ) , state ) : t . nullLiteral ( ) ;
148+ const newProps = spreadNode ( props , state ) ;
134149 const newChildren = t . arrayExpression ( children . map ( childMapper ) ) ;
135150 return createVNode ( newTag , newProps , newChildren ) ;
136151 }
137-
138- function h ( tag , props , ...children ) {
139- return { tag, props, children } ;
140- }
141-
142- const html = htm . bind ( h ) ;
143-
144- function treeify ( statics , expr ) {
145- const assign = Object . assign ;
146- try {
147- Object . assign = function ( ...objs ) { return { [ symbol ] : objs } ; } ;
148- return html ( statics , ...expr ) ;
149- }
150- finally {
151- Object . assign = assign ;
152- }
153- }
154152
155153 // The tagged template tag function name we're looking for.
156154 // This is static because it's generally assigned via htm.bind(h),
@@ -165,7 +163,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
165163 const statics = path . node . quasi . quasis . map ( e => e . value . raw ) ;
166164 const expr = path . node . quasi . expressions ;
167165
168- const tree = treeify ( statics , expr ) ;
166+ const tree = treeify ( build ( statics ) , expr ) ;
169167 const node = ! Array . isArray ( tree )
170168 ? transform ( tree , state )
171169 : t . arrayExpression ( tree . map ( root => transform ( root , state ) ) ) ;
@@ -174,4 +172,4 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
174172 }
175173 }
176174 } ;
177- }
175+ }
0 commit comments