@@ -55,6 +55,11 @@ class ComponentGenerator < ::Rails::Generators::NamedBase
5555 default : false ,
5656 desc : 'Output es6 class based component'
5757
58+ class_option :ts ,
59+ type : :boolean ,
60+ default : false ,
61+ desc : 'Output tsx class based component'
62+
5863 class_option :coffee ,
5964 type : :boolean ,
6065 default : false ,
@@ -89,9 +94,38 @@ class ComponentGenerator < ::Rails::Generators::NamedBase
8994 }
9095 }
9196
97+ TYPESCRIPT_TYPES = {
98+ 'node' => 'React.ReactNode' ,
99+ 'bool' => 'boolean' ,
100+ 'boolean' => 'boolean' ,
101+ 'string' => 'string' ,
102+ 'number' => 'number' ,
103+ 'object' => 'object' ,
104+ 'array' => 'Array<any>' ,
105+ 'shape' => 'object' ,
106+ 'element' => 'object' ,
107+ 'func' => 'object' ,
108+ 'function' => 'object' ,
109+ 'any' => 'any' ,
110+
111+ 'instanceOf' => -> ( type ) {
112+ type . to_s . camelize
113+ } ,
114+
115+ 'oneOf' => -> ( *opts ) {
116+ opts . map { |k | "'#{ k . to_s } '" } . join ( " | " )
117+ } ,
118+
119+ 'oneOfType' => -> ( *opts ) {
120+ opts . map { |k | "#{ ts_lookup ( k . to_s , k . to_s ) } " } . join ( " | " )
121+ }
122+ }
123+
92124 def create_component_file
93125 template_extension = if options [ :coffee ]
94126 'js.jsx.coffee'
127+ elsif options [ :ts ]
128+ 'js.jsx.tsx'
95129 elsif options [ :es6 ] || webpacker?
96130 'es6.jsx'
97131 else
@@ -101,7 +135,13 @@ def create_component_file
101135 # Prefer webpacker to sprockets:
102136 if webpacker?
103137 new_file_name = file_name . camelize
104- extension = options [ :coffee ] ? 'coffee' : 'js'
138+ extension = if options [ :coffee ]
139+ 'coffee'
140+ elsif options [ :ts ]
141+ 'tsx'
142+ else
143+ 'js'
144+ end
105145 target_dir = webpack_configuration . source_path
106146 . join ( 'components' )
107147 . relative_path_from ( ::Rails . root )
@@ -128,6 +168,7 @@ def component_name
128168
129169 def file_header
130170 if webpacker?
171+ return %|import * as React from "react"\n | if options [ :ts ]
131172 %|import React from "react"\n import PropTypes from "prop-types"\n |
132173 else
133174 ''
@@ -146,23 +187,58 @@ def webpacker?
146187 defined? ( Webpacker )
147188 end
148189
149- def parse_attributes!
150- self . attributes = ( attributes || [ ] ) . map do |attr |
151- name = ''
152- type = ''
153- options = ''
154- options_regex = /(?<options>{.*})/
190+ def parse_attributes!
191+ self . attributes = ( attributes || [ ] ) . map do |attr |
192+ name = ''
193+ type = ''
194+ args = ''
195+ args_regex = /(?<args>{.*})/
196+
197+ name , type = attr . split ( ':' )
198+
199+ if matchdata = args_regex . match ( type )
200+ args = matchdata [ :args ]
201+ type = type . gsub ( args_regex , '' )
202+ end
203+
204+ if options [ :ts ]
205+ { :name => name , :type => ts_lookup ( name , type , args ) , :union => union? ( args ) }
206+ else
207+ { :name => name , :type => lookup ( type , args ) }
208+ end
209+ end
210+ end
211+
212+ def union? ( args = '' )
213+ return args . to_s . gsub ( /[{}]/ , '' ) . split ( ',' ) . count > 1
214+ end
155215
156- name , type = attr . split ( ':' )
216+ def self . ts_lookup ( name , type = 'node' , args = '' )
217+ ts_type = TYPESCRIPT_TYPES [ type ]
218+ if ts_type . blank?
219+ if type =~ /^[[:upper:]]/
220+ ts_type = TYPESCRIPT_TYPES [ 'instanceOf' ]
221+ else
222+ ts_type = TYPESCRIPT_TYPES [ 'node' ]
223+ end
224+ end
157225
158- if matchdata = options_regex . match ( type )
159- options = matchdata [ :options ]
160- type = type . gsub ( options_regex , '' )
161- end
226+ args = args . to_s . gsub ( /[{}]/ , '' ) . split ( ',' )
162227
163- { :name => name , :type => lookup ( type , options ) }
164- end
165- end
228+ if ts_type . respond_to? :call
229+ if args . blank?
230+ return ts_type . call ( type )
231+ end
232+
233+ ts_type = ts_type . call ( *args )
234+ end
235+
236+ ts_type
237+ end
238+
239+ def ts_lookup ( name , type = 'node' , args = '' )
240+ self . class . ts_lookup ( name , type , args )
241+ end
166242
167243 def self . lookup ( type = 'node' , options = '' )
168244 react_prop_type = REACT_PROP_TYPES [ type ]
0 commit comments