rails-hyperstack 1.0.alpha1.3 → 1.0.alpha1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,11 +6,128 @@ module Rails
6
6
 
7
7
  protected
8
8
 
9
+ def warnings
10
+ @warnings ||= []
11
+ end
12
+
13
+ def create_component_file(template)
14
+ clear_cache
15
+ insure_hyperstack_loader_installed
16
+ check_javascript_link_directory
17
+ added = webpack_check
18
+ insure_base_component_class_exists
19
+ @no_help = options.key?('no-help')
20
+ self.components.each do |component|
21
+ component_array = component.split('::')
22
+ @modules = component_array[0..-2]
23
+ @file_name = component_array.last
24
+ @indent = 0
25
+ template template,
26
+ Rails.root.join('app', 'hyperstack', 'components',
27
+ *@modules.map(&:downcase),
28
+ "#{@file_name.underscore}.rb")
29
+ end
30
+ add_route
31
+ return unless added
32
+
33
+ say '📦 Webpack integrated with Hyperstack. '\
34
+ 'Add javascript assets to app/javascript/packs/client_only.js and /client_and_server.js 📦', :green
35
+ end
36
+
37
+
38
+ def clear_cache
39
+ run 'rm -rf tmp/cache' unless Dir.exist?(Rails.root.join('app', 'hyperstack'))
40
+ end
41
+
42
+ def insure_hyperstack_loader_installed
43
+ hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+}
44
+ application_js = Rails.root.join(
45
+ 'app', 'assets', 'javascripts', 'application.js'
46
+ )
47
+ if File.exist? application_js
48
+ unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader }
49
+ require_tree = %r{//=\s+require_tree\s+}
50
+ if File.foreach(application_js).any? { |l| l =~ require_tree }
51
+ inject_into_file 'app/assets/javascripts/application.js', verbose: false, before: require_tree do
52
+ "//= require hyperstack-loader\n"
53
+ end
54
+ else
55
+ warnings <<
56
+ " ***********************************************************\n"\
57
+ " * Could not add `//= require hyperstack-loader` directive *\n"\
58
+ " * to the app/assets/application.js file. *\n"\
59
+ " * Normally this directive is added just before the *\n"\
60
+ " * `//= require_tree .` directive at the end of the file, *\n"\
61
+ " * but no require_tree directive was found. You need to *\n"\
62
+ " * manually add `//= require hyperstack-loader` to the *\n"\
63
+ " * app/assets/application.js file. *\n"\
64
+ " ***********************************************************\n"
65
+ end
66
+ end
67
+ else
68
+ create_file application_js, "//= require hyperstack-loader\n"
69
+ warnings <<
70
+ " ***********************************************************\n"\
71
+ " * Could not find the app/assets/application.js file. *\n"\
72
+ " * We created one for you, and added the *\n"\
73
+ " * `<%= javascript_include_tag 'application' %>` to your *\n"\
74
+ " * `html.erb` files immediately after any *\n"\
75
+ " * `<%= javascript_pack 'application' %>` tags we found. *\n"\
76
+ " ***********************************************************\n"
77
+ application_pack_tag =
78
+ /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/
79
+ Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file|
80
+ if File.foreach(file).any? { |l| l =~ application_pack_tag }
81
+ inject_into_file file, verbose: false, after: application_pack_tag do
82
+ "\n <%= javascript_include_tag 'application' %>"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def check_javascript_link_directory
90
+ manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js")
91
+ return unless File.exist? manifest_js_file
92
+ return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty?
93
+
94
+ append_file manifest_js_file, "//= link_directory ../javascripts .js\n", verbose: false
95
+ end
96
+
97
+ def insure_base_component_class_exists
98
+ @component_base_class = options['base-class'] || Hyperstack.component_base_class
99
+ file_name = Rails.root.join(
100
+ 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb"
101
+ )
102
+ template 'hyper_component_template.rb', file_name unless File.exist? file_name
103
+ end
104
+
9
105
  def add_to_manifest(manifest, &block)
10
- if File.exists? "app/javascript/packs/#{manifest}"
11
- append_file "app/javascript/packs/#{manifest}", &block
106
+ if File.exist? "app/javascript/packs/#{manifest}"
107
+ append_file "app/javascript/packs/#{manifest}", verbose: false, &block
12
108
  else
13
- create_file "app/javascript/packs/#{manifest}", &block
109
+ create_file "app/javascript/packs/#{manifest}", verbose: false, &block
110
+ end
111
+ end
112
+
113
+ def add_route
114
+ return unless options['add-route']
115
+ if self.components.count > 1
116
+ warnings <<
117
+ " ***********************************************************\n"\
118
+ " * The add-route option ignored because more than one *\n"\
119
+ " * component is being generated. *\n"\
120
+ " ***********************************************************\n"
121
+ return
122
+ end
123
+ action_name = (@modules+[@file_name.underscore]).join('__')
124
+ path = options['add-route'] == 'add-route' ? '/(*others)' : options['add-route']
125
+ routing_code = "get '#{path}', to: 'hyperstack##{action_name}'\n"
126
+ log :route, routing_code
127
+ [/mount\s+Hyperstack::Engine[^\n]+\n/m, /\.routes\.draw do\s*\n/m].each do |sentinel|
128
+ in_root do
129
+ inject_into_file 'config/routes.rb', routing_code.indent(2), after: sentinel, verbose: false, force: false
130
+ end
14
131
  end
15
132
  end
16
133
 
@@ -18,6 +135,154 @@ module Rails
18
135
  return if system("yarn add #{package}#{'@' + version if version}")
19
136
  raise Thor::Error.new("yarn failed to install #{package} with version #{version}")
20
137
  end
138
+
139
+ def install_webpack
140
+ insure_yarn_loaded
141
+ add_webpacker_manifests
142
+ add_webpacks
143
+ cancel_react_source_import
144
+ install_webpacker
145
+ end
146
+
147
+ def inject_into_initializer(s)
148
+ file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb')
149
+ if File.exist?(file_name)
150
+ prepend_to_file(file_name, verbose: false) { "#{s}\n" }
151
+ else
152
+ create_file file_name, <<-RUBY
153
+ #{s}
154
+
155
+ # server_side_auto_require will patch the ActiveSupport Dependencies module
156
+ # so that you can define classes and modules with files in both the
157
+ # app/hyperstack/xxx and app/xxx directories. For example you can split
158
+ # a Todo model into server and client related definitions and place this
159
+ # in `app/hyperstack/models/todo.rb`, and place any server only definitions in
160
+ # `app/models/todo.rb`.
161
+
162
+ require "hyperstack/server_side_auto_require.rb"
163
+
164
+ # set the component base class
165
+
166
+ Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent'
167
+
168
+ # prerendering is default :off, you should wait until your
169
+ # application is relatively well debugged before turning on.
170
+
171
+ Hyperstack.prerendering = :off # or :on
172
+
173
+ # add this line if you need jQuery AND ARE NOT USING WEBPACK
174
+ # Hyperstack.import 'hyperstack/component/jquery', client_only: true
175
+
176
+ # change definition of on_error to control how errors such as validation
177
+ # exceptions are reported on the server
178
+ module Hyperstack
179
+ def self.on_error(operation, err, params, formatted_error_message)
180
+ ::Rails.logger.debug(
181
+ "\#{formatted_error_message}\\n\\n" +
182
+ Pastel.new.red(
183
+ 'To further investigate you may want to add a debugging '\\
184
+ 'breakpoint to the on_error method in config/initializers/hyperstack.rb'
185
+ )
186
+ )
187
+ end
188
+ end if Rails.env.development?
189
+ RUBY
190
+ end
191
+ # whenever we modify the initializer its best to empty the cache, BUT
192
+ # we only need to it once per generator execution
193
+ run 'rm -rf tmp/cache' unless @cache_emptied_already
194
+ @cache_emptied_already = true
195
+ end
196
+
197
+ private
198
+
199
+ def webpack_check
200
+ return unless defined? ::Webpacker
201
+
202
+ client_and_server = Rails.root.join("app", "javascript", "packs", "client_only.js")
203
+ return if File.exist? client_and_server
204
+
205
+ # Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails hyperstack:install:webpack' }
206
+
207
+ # say "warning: you are running webpacker, but the hyperstack webpack files have not been created.\n"\
208
+ # " Suggest you run bundle exec rails hyperstack:install:webpack soon.\n"\
209
+ # " Or to avoid this warning create an empty file named app/javascript/packs/client_only.js",
210
+ # :red
211
+ install_webpack
212
+ true
213
+ end
214
+
215
+ def insure_yarn_loaded
216
+ begin
217
+ yarn_version = `yarn --version`
218
+ raise Errno::ENOENT if yarn_version.blank?
219
+ rescue Errno::ENOENT
220
+ raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker")
221
+ end
222
+ end
223
+
224
+ def add_webpacker_manifests
225
+ create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT
226
+ //app/javascript/packs/client_and_server.js
227
+ // these packages will be loaded both during prerendering and on the client
228
+ React = require('react'); // react-js library
229
+ createReactClass = require('create-react-class'); // backwards compatibility with ECMA5
230
+ History = require('history'); // react-router history library
231
+ ReactRouter = require('react-router'); // react-router js library
232
+ ReactRouterDOM = require('react-router-dom'); // react-router DOM interface
233
+ ReactRailsUJS = require('react_ujs'); // interface to react-rails
234
+ // to add additional NPM packages run `yarn add package-name@version`
235
+ // then add the require here.
236
+ JAVASCRIPT
237
+ create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT
238
+ //app/javascript/packs/client_only.js
239
+ // add any requires for packages that will run client side only
240
+ ReactDOM = require('react-dom'); // react-js client side code
241
+ jQuery = require('jquery'); // remove if you don't need jQuery
242
+ // to add additional NPM packages call run yarn add package-name@version
243
+ // then add the require here.
244
+ JAVASCRIPT
245
+ append_file 'config/initializers/assets.rb', verbose: false do
246
+ <<-RUBY
247
+ Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s
248
+ RUBY
249
+ end
250
+ inject_into_file 'config/environments/test.rb', verbose: false, before: /^end/ do
251
+ <<-RUBY
252
+
253
+ # added by hyperstack installer
254
+ config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s
255
+ RUBY
256
+ end
257
+ end
258
+
259
+ def add_webpacks
260
+ yarn 'react', '16'
261
+ yarn 'react-dom', '16'
262
+ yarn 'react-router', '^5.0.0'
263
+ yarn 'react-router-dom', '^5.0.0'
264
+ yarn 'react_ujs', '^2.5.0'
265
+ yarn 'jquery', '^3.4.1'
266
+ yarn 'create-react-class'
267
+ end
268
+
269
+ def cancel_react_source_import
270
+ inject_into_initializer(
271
+ "# Hyperstack.import 'react/react-source-browser' "\
272
+ "# uncomment this line if you want hyperstack to use its copy of react"
273
+ )
274
+ end
275
+
276
+ def install_webpacker
277
+ return if defined?(::Webpacker)
278
+
279
+ gem "webpacker"
280
+ Bundler.with_unbundled_env do
281
+ run "bundle install"
282
+ end
283
+ `spring stop`
284
+ Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' }
285
+ end
21
286
  end
22
287
  end
23
288
  end
@@ -0,0 +1,229 @@
1
+ require_relative '../hyperstack/install_generator_base'
2
+ module Install
3
+ class HyperstackGenerator < Rails::Generators::Base
4
+
5
+ class_option 'skip-webpack', type: :boolean
6
+ class_option 'skip-hot-reloader', type: :boolean
7
+ class_option 'add-framework', type: :string
8
+
9
+ def insure_yarn_loaded
10
+ return if skip_webpack?
11
+ begin
12
+ yarn_version = `yarn --version`
13
+ raise Errno::ENOENT if yarn_version.blank?
14
+ rescue Errno::ENOENT
15
+ raise Thor::Error.new("please insure the yarn command is available if using webpacker")
16
+ end
17
+ end
18
+
19
+ APPJS = 'app/assets/javascripts/application.js'
20
+
21
+ def inject_hyperstack_loader_js
22
+ unless File.foreach(APPJS).any?{ |l| l['//= require hyperstack-loader'] }
23
+ inject_into_file 'app/assets/javascripts/application.js', before: %r{//= require_tree .} do
24
+ "//= require hyperstack-loader\n"
25
+ end
26
+ end
27
+ end
28
+
29
+ def create_hyperstack_files_and_directories
30
+ create_file 'app/hyperstack/components/hyper_component.rb', <<-RUBY
31
+ class HyperComponent
32
+ include Hyperstack::Component
33
+ include Hyperstack::State::Observable
34
+ param_accessor_style :accessors
35
+ end
36
+ RUBY
37
+ create_file 'app/hyperstack/operations/.keep', ''
38
+ create_file 'app/hyperstack/stores/.keep', ''
39
+ create_file 'app/hyperstack/models/.keep', ''
40
+ end
41
+
42
+ def move_and_update_application_record
43
+ unless File.exists? 'app/hyperstack/models/application_record.rb'
44
+ `mv app/models/application_record.rb app/hyperstack/models/application_record.rb`
45
+ create_file 'app/models/application_record.rb', <<-RUBY
46
+ # app/models/application_record.rb
47
+ # the presence of this file prevents rails migrations from recreating application_record.rb see https://github.com/rails/rails/issues/29407
48
+
49
+ require 'models/application_record.rb'
50
+ RUBY
51
+ end
52
+ end
53
+
54
+ def create_policies_directory
55
+ create_file 'app/policies/application_policy.rb', <<-RUBY
56
+ # app/policies/application_policy.rb
57
+
58
+ # Policies regulate access to your public models
59
+ # The following policy will open up full access (but only in development)
60
+ # The policy system is very flexible and powerful. See the documentation
61
+ # for complete details.
62
+ class Hyperstack::ApplicationPolicy
63
+ # Allow any session to connect:
64
+ always_allow_connection
65
+ # Send all attributes from all public models
66
+ regulate_all_broadcasts { |policy| policy.send_all }
67
+ # Allow all changes to models
68
+ allow_change(to: :all, on: [:create, :update, :destroy]) { true }
69
+ # allow remote access to all scopes - i.e. you can count or get a list of ids
70
+ # for any scope or relationship
71
+ ApplicationRecord.regulate_scope :all
72
+ end unless Rails.env.production?
73
+ RUBY
74
+ end
75
+
76
+ def add_router
77
+ generate "hyper:router", "App"
78
+ route "get '/(*other)', to: 'hyperstack#app'"
79
+ end
80
+ if false
81
+ def add_webpackin
82
+ run 'yarn add react'
83
+ run 'yarn add react-dom'
84
+ run 'yarn add react-router'
85
+ create_file 'app/javascript/packs/hyperstack.js', <<-CODE
86
+ // Import all the modules
87
+ import React from 'react';
88
+ import ReactDOM from 'react-dom';
89
+
90
+ // for opal/hyperstack modules to find React and others they must explicitly be saved
91
+ // to the global space, otherwise webpack will encapsulate them locally here
92
+ global.React = React;
93
+ global.ReactDOM = ReactDOM;
94
+ CODE
95
+ inject_into_file 'app/views/layouts/application.html.erb', before: %r{<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>} do
96
+ <<-CODE
97
+ <%= javascript_pack_tag 'hyperstack' %>
98
+ CODE
99
+ end
100
+ gem 'webpacker'
101
+ end
102
+
103
+
104
+ else
105
+ def add_webpacker_manifests
106
+ return if skip_webpack?
107
+ create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT
108
+ //app/javascript/packs/client_and_server.js
109
+ // these packages will be loaded both during prerendering and on the client
110
+ React = require('react'); // react-js library
111
+ History = require('history'); // react-router history library
112
+ ReactRouter = require('react-router'); // react-router js library
113
+ ReactRouterDOM = require('react-router-dom'); // react-router DOM interface
114
+ ReactRailsUJS = require('react_ujs'); // interface to react-rails
115
+ // to add additional NPM packages call run yarn add package-name@version
116
+ // then add the require here.
117
+ JAVASCRIPT
118
+ create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT
119
+ //app/javascript/packs/client_only.js
120
+ // add any requires for packages that will run client side only
121
+ ReactDOM = require('react-dom'); // react-js client side code
122
+ jQuery = require('jquery');
123
+ // to add additional NPM packages call run yarn add package-name@version
124
+ // then add the require here.
125
+ JAVASCRIPT
126
+ append_file 'config/initializers/assets.rb' do
127
+ <<-RUBY
128
+ Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s
129
+ RUBY
130
+ end
131
+ inject_into_file 'config/environments/test.rb', before: /^end/ do
132
+ <<-RUBY
133
+
134
+ # added by hyperstack installer
135
+ config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s
136
+ RUBY
137
+ end
138
+
139
+ end
140
+
141
+ def add_webpacks
142
+ return if skip_webpack?
143
+ yarn 'react', '16'
144
+ yarn 'react-dom', '16'
145
+ yarn 'react-router', '^5.0.0'
146
+ yarn 'react-router-dom', '^5.0.0'
147
+ # yarn 'history'#, '4.2' this will be brought in by react-router
148
+ yarn 'react_ujs', '^2.5.0'
149
+ yarn 'jquery', '^3.4.1'
150
+ end
151
+
152
+ def add_webpacker_gem
153
+ gem 'webpacker'
154
+ end
155
+
156
+ def add_framework
157
+ framework = options['add-framework']
158
+ return unless framework
159
+ generate "hyperstack:install_#{framework}", "--no-build"
160
+ end
161
+
162
+ # def build_webpack
163
+ # system('bin/webpack')
164
+ # end
165
+ end
166
+ # all generators should be run before the initializer due to the opal-rails opal-jquery
167
+ # conflict
168
+
169
+ def create_initializer
170
+ create_file 'config/initializers/hyperstack.rb', <<-CODE
171
+ # config/initializers/hyperstack.rb
172
+ # If you are not using ActionCable, see http://hyperstack.orgs/docs/models/configuring-transport/
173
+ Hyperstack.configuration do |config|
174
+ config.transport = :action_cable
175
+ config.prerendering = :off # or :on
176
+ config.cancel_import 'react/react-source-browser' # bring your own React and ReactRouter via Yarn/Webpacker
177
+ config.import 'hyperstack/component/jquery', client_only: true # remove this line if you don't need jquery
178
+ config.import 'hyperstack/hotloader', client_only: true if Rails.env.development?
179
+ end
180
+
181
+ # useful for debugging
182
+ module Hyperstack
183
+ def self.on_error(operation, err, params, formatted_error_message)
184
+ ::Rails.logger.debug(
185
+ "\#{formatted_error_message}\\n\\n" +
186
+ Pastel.new.red(
187
+ 'To further investigate you may want to add a debugging '\\
188
+ 'breakpoint to the on_error method in config/initializers/hyperstack.rb'
189
+ )
190
+ )
191
+ end
192
+ end if Rails.env.development?
193
+ CODE
194
+ end
195
+
196
+ def inject_engine_to_routes
197
+ # this needs to be the first route, thus it must be the last method executed
198
+ route 'mount Hyperstack::Engine => \'/hyperstack\'' # this route should be first in the routes file so it always matches
199
+ end
200
+
201
+ def add_opal_hot_reloader
202
+ return if options['skip-hot-reloader']
203
+ create_file 'Procfile', <<-TEXT
204
+ web: bundle exec rails s -b 0.0.0.0
205
+ hot-loader: bundle exec hyperstack-hotloader -p 25222 -d app/hyperstack
206
+ TEXT
207
+ gem_group :development do
208
+ gem 'foreman'
209
+ end
210
+ end
211
+
212
+ def add_gems
213
+
214
+ end
215
+
216
+ def install
217
+ Bundler.with_unbundled_env do
218
+ run "bundle install"
219
+ end
220
+ run 'bundle exec rails webpacker:install'
221
+ end
222
+
223
+ private
224
+
225
+ def skip_webpack?
226
+ options['skip-webpack'] #|| !defined?(Webpacker)
227
+ end
228
+ end
229
+ end