Customize the react:component generator
This commit is contained in:
parent
6dfaf27f2e
commit
5af6cfb865
2 changed files with 288 additions and 0 deletions
251
gems/bess/lib/generators/react/component_generator.rb
Normal file
251
gems/bess/lib/generators/react/component_generator.rb
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
|
||||||
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
|
# from: https://github.com/reactjs/react-rails/blob/master/lib/generators/react/component_generator.rb
|
||||||
|
module React
|
||||||
|
module Generators
|
||||||
|
class ComponentGenerator < ::Rails::Generators::NamedBase
|
||||||
|
source_root File.expand_path '../../templates', __FILE__
|
||||||
|
desc <<-DESC.strip_heredoc
|
||||||
|
Description:
|
||||||
|
Scaffold a React component into `components/` of your Webpacker source or asset pipeline.
|
||||||
|
The generated component will include a basic render function and a PropTypes
|
||||||
|
hash to help with development.
|
||||||
|
Available field types:
|
||||||
|
Basic prop types do not take any additional arguments. If you do not specify
|
||||||
|
a prop type, the generic node will be used. The basic types available are:
|
||||||
|
any
|
||||||
|
array
|
||||||
|
bool
|
||||||
|
element
|
||||||
|
func
|
||||||
|
number
|
||||||
|
object
|
||||||
|
node
|
||||||
|
shape
|
||||||
|
string
|
||||||
|
Special PropTypes take additional arguments in {}, and must be enclosed in
|
||||||
|
single quotes to keep bash from expanding the arguments in {}.
|
||||||
|
instanceOf
|
||||||
|
takes an optional class name in the form of {className}
|
||||||
|
oneOf
|
||||||
|
behaves like an enum, and takes an optional list of strings that will
|
||||||
|
be allowed in the form of 'name:oneOf{one,two,three}'.
|
||||||
|
oneOfType.
|
||||||
|
oneOfType takes an optional list of react and custom types in the form of
|
||||||
|
'model:oneOfType{string,number,OtherType}'
|
||||||
|
Examples:
|
||||||
|
rails g react:component person name
|
||||||
|
rails g react:component restaurant name:string rating:number owner:instanceOf{Person}
|
||||||
|
rails g react:component food 'kind:oneOf{meat,cheese,vegetable}'
|
||||||
|
rails g react:component events 'location:oneOfType{string,Restaurant}'
|
||||||
|
DESC
|
||||||
|
|
||||||
|
argument :attributes,
|
||||||
|
:type => :array,
|
||||||
|
:default => [],
|
||||||
|
:banner => 'field[:type] field[:type] ...'
|
||||||
|
|
||||||
|
class_option :ts,
|
||||||
|
type: :boolean,
|
||||||
|
default: true,
|
||||||
|
desc: 'Output tsx class based component'
|
||||||
|
|
||||||
|
REACT_PROP_TYPES = {
|
||||||
|
'node' => 'PropTypes.node',
|
||||||
|
'bool' => 'PropTypes.bool',
|
||||||
|
'boolean' => 'PropTypes.bool',
|
||||||
|
'string' => 'PropTypes.string',
|
||||||
|
'number' => 'PropTypes.number',
|
||||||
|
'object' => 'PropTypes.object',
|
||||||
|
'array' => 'PropTypes.array',
|
||||||
|
'shape' => 'PropTypes.shape({})',
|
||||||
|
'element' => 'PropTypes.element',
|
||||||
|
'func' => 'PropTypes.func',
|
||||||
|
'function' => 'PropTypes.func',
|
||||||
|
'any' => 'PropTypes.any',
|
||||||
|
|
||||||
|
'instanceOf' => ->(type) {
|
||||||
|
'PropTypes.instanceOf(%s)' % type.to_s.camelize
|
||||||
|
},
|
||||||
|
|
||||||
|
'oneOf' => ->(*options) {
|
||||||
|
enums = options.map{ |k| "'#{k.to_s}'" }.join(',')
|
||||||
|
'PropTypes.oneOf([%s])' % enums
|
||||||
|
},
|
||||||
|
|
||||||
|
'oneOfType' => ->(*options) {
|
||||||
|
types = options.map{ |k| "#{lookup(k.to_s, k.to_s)}" }.join(',')
|
||||||
|
'PropTypes.oneOfType([%s])' % types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPESCRIPT_TYPES = {
|
||||||
|
'node' => 'React.ReactNode',
|
||||||
|
'bool' => 'boolean',
|
||||||
|
'boolean' => 'boolean',
|
||||||
|
'string' => 'string',
|
||||||
|
'number' => 'number',
|
||||||
|
'object' => 'object',
|
||||||
|
'array' => 'Array<any>',
|
||||||
|
'shape' => 'object',
|
||||||
|
'element' => 'object',
|
||||||
|
'func' => 'object',
|
||||||
|
'function' => 'object',
|
||||||
|
'any' => 'any',
|
||||||
|
|
||||||
|
'instanceOf' => ->(type) {
|
||||||
|
type.to_s.camelize
|
||||||
|
},
|
||||||
|
|
||||||
|
'oneOf' => ->(*opts) {
|
||||||
|
opts.map{ |k| "'#{k.to_s}'" }.join(" | ")
|
||||||
|
},
|
||||||
|
|
||||||
|
'oneOfType' => ->(*opts) {
|
||||||
|
opts.map{ |k| "#{ts_lookup(k.to_s, k.to_s)}" }.join(" | ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_component_file
|
||||||
|
template_extension = if options[:coffee]
|
||||||
|
'js.jsx.coffee'
|
||||||
|
elsif options[:ts]
|
||||||
|
'js.jsx.tsx'
|
||||||
|
elsif options[:es6] || webpacker?
|
||||||
|
'es6.jsx'
|
||||||
|
else
|
||||||
|
'js.jsx'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prefer webpacker to sprockets:
|
||||||
|
if webpacker?
|
||||||
|
new_file_name = file_name.camelize
|
||||||
|
extension = if options[:coffee]
|
||||||
|
'coffee'
|
||||||
|
elsif options[:ts]
|
||||||
|
'tsx'
|
||||||
|
else
|
||||||
|
'js'
|
||||||
|
end
|
||||||
|
target_dir = webpack_configuration.source_path
|
||||||
|
.join('components')
|
||||||
|
.relative_path_from(::Rails.root)
|
||||||
|
.to_s
|
||||||
|
else
|
||||||
|
new_file_name = file_name
|
||||||
|
extension = template_extension
|
||||||
|
target_dir = 'app/assets/javascripts/components'
|
||||||
|
end
|
||||||
|
|
||||||
|
file_path = File.join(target_dir, class_path, "#{new_file_name}.#{extension}")
|
||||||
|
template("component.#{template_extension}", file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def webpack_configuration
|
||||||
|
Webpacker.respond_to?(:config) ? Webpacker.config : Webpacker::Configuration
|
||||||
|
end
|
||||||
|
|
||||||
|
def component_name
|
||||||
|
file_name.camelize
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_header
|
||||||
|
if webpacker?
|
||||||
|
return %|import * as React from "react"\n| if options[:ts]
|
||||||
|
%|import React from "react"\nimport PropTypes from "prop-types"\n|
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_footer
|
||||||
|
if webpacker?
|
||||||
|
%|export default #{component_name}|
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def webpacker?
|
||||||
|
defined?(Webpacker)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_attributes!
|
||||||
|
self.attributes = (attributes || []).map do |attr|
|
||||||
|
name = ''
|
||||||
|
type = ''
|
||||||
|
args = ''
|
||||||
|
args_regex = /(?<args>{.*})/
|
||||||
|
|
||||||
|
name, type = attr.split(':')
|
||||||
|
|
||||||
|
if matchdata = args_regex.match(type)
|
||||||
|
args = matchdata[:args]
|
||||||
|
type = type.gsub(args_regex, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:ts]
|
||||||
|
{ :name => name, :type => ts_lookup(name, type, args), :union => union?(args) }
|
||||||
|
else
|
||||||
|
{ :name => name, :type => lookup(type, args) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def union?(args = '')
|
||||||
|
return args.to_s.gsub(/[{}]/, '').split(',').count > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ts_lookup(name, type = 'node', args = '')
|
||||||
|
ts_type = TYPESCRIPT_TYPES[type]
|
||||||
|
if ts_type.blank?
|
||||||
|
if type =~ /^[[:upper:]]/
|
||||||
|
ts_type = TYPESCRIPT_TYPES['instanceOf']
|
||||||
|
else
|
||||||
|
ts_type = TYPESCRIPT_TYPES['node']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
args = args.to_s.gsub(/[{}]/, '').split(',')
|
||||||
|
|
||||||
|
if ts_type.respond_to? :call
|
||||||
|
if args.blank?
|
||||||
|
return ts_type.call(type)
|
||||||
|
end
|
||||||
|
|
||||||
|
ts_type = ts_type.call(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
ts_type
|
||||||
|
end
|
||||||
|
|
||||||
|
def ts_lookup(name, type = 'node', args = '')
|
||||||
|
self.class.ts_lookup(name, type, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.lookup(type = 'node', options = '')
|
||||||
|
react_prop_type = REACT_PROP_TYPES[type]
|
||||||
|
if react_prop_type.blank?
|
||||||
|
if type =~ /^[[:upper:]]/
|
||||||
|
react_prop_type = REACT_PROP_TYPES['instanceOf']
|
||||||
|
else
|
||||||
|
react_prop_type = REACT_PROP_TYPES['node']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
options = options.to_s.gsub(/[{}]/, '').split(',')
|
||||||
|
|
||||||
|
react_prop_type = react_prop_type.call(*options) if react_prop_type.respond_to? :call
|
||||||
|
react_prop_type
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup(type = 'node', options = '')
|
||||||
|
self.class.lookup(type, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
37
gems/bess/lib/generators/templates/component.js.jsx.tsx
Normal file
37
gems/bess/lib/generators/templates/component.js.jsx.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<% # License: LGPL-3.0-or-later
|
||||||
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
||||||
|
# from: https://github.com/reactjs/react-rails/blob/master/lib/generators/templates/component.js.jsx.tsx
|
||||||
|
%>
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
<%= file_header %>
|
||||||
|
<% unions = attributes.select{ |a| a[:union] } -%>
|
||||||
|
<% if unions.size > 0 -%>
|
||||||
|
<% unions.each do |e| -%>
|
||||||
|
type <%= e[:name].titleize %> = <%= e[:type]%>
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
|
||||||
|
interface I<%= component_name %>Props {
|
||||||
|
<% if attributes.size > 0 -%>
|
||||||
|
<% attributes.each do | attribute | -%>
|
||||||
|
<% if attribute[:union] -%>
|
||||||
|
<%= attribute[:name].camelize(:lower) %>: <%= attribute[:name].titleize %>;
|
||||||
|
<% else -%>
|
||||||
|
<%= attribute[:name].camelize(:lower) %>: <%= attribute[:type] %>;
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function <%= component_name %>(props:I<%= component_name %>Props) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<% attributes.each do |attribute| -%>
|
||||||
|
<%= attribute[:name].titleize %>: {props.<%= attribute[:name].camelize(:lower) %>}
|
||||||
|
<% end -%>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
<%= file_footer %>
|
Loading…
Reference in a new issue