server_component 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +42 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +46 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +19 -0
- data/README.md +108 -0
- data/Rakefile +12 -0
- data/app/controllers/server_component/base_component_controller.rb +95 -0
- data/app/controllers/server_component/component_routers_controller.rb +27 -0
- data/app/controllers/server_component/components_controller.rb +35 -0
- data/app/helpers/server_component_helper.rb +30 -0
- data/bin/rails +9 -0
- data/bin/webpack +4 -0
- data/config/routes.rb +6 -0
- data/lib/generators/server_component/component_controller_generator.rb +28 -0
- data/lib/generators/server_component/install_generator.rb +31 -0
- data/lib/generators/templates/AFTER_INSTALL +45 -0
- data/lib/generators/templates/api_component_router.rb +3 -0
- data/lib/generators/templates/component_controller.rb.tt +5 -0
- data/lib/generators/templates/server_component.rb +64 -0
- data/lib/server_component.rb +29 -0
- data/lib/server_component/component_router.rb +58 -0
- data/lib/server_component/engine.rb +7 -0
- data/lib/server_component/js_function_builder.rb +0 -0
- data/lib/server_component/jsrb_handler.rb +10 -0
- data/lib/server_component/railtie.rb +17 -0
- data/lib/server_component/version.rb +5 -0
- data/server_component.gemspec +40 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dba7844e0d4e1ca570d77659d7ef3eea346900fc8ab9e84afef4dba723bf8113
|
4
|
+
data.tar.gz: 8d331e9937dfc21a5f281724831a18ab9c56dc6168b4df3dc0ca398bea09d9dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a70a74596bbf8482aa244ed247bf04a8a412318c2aed0e8533c19715c9551a55099245bf8c7b93d02b5ebfd21b505c07734a655bb951dab25a9dcfc73df29796
|
7
|
+
data.tar.gz: 963688b759513463ef25f378c074830ec2e83710b1465a16ee1a5b31fc495eeabb3a798c3560dd405d58409d05a0747fab5e451ab3c38b0c3b5ea80b490377e7
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Ruby CircleCI 2.0 configuration file
|
2
|
+
#
|
3
|
+
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
4
|
+
#
|
5
|
+
version: 2
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
docker:
|
9
|
+
- image: circleci/ruby:2.5.1-node-browsers
|
10
|
+
working_directory: ~/repo
|
11
|
+
steps:
|
12
|
+
- checkout
|
13
|
+
- run:
|
14
|
+
name: install dependencies
|
15
|
+
working_directory: ~/repo
|
16
|
+
command: |
|
17
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
18
|
+
- run:
|
19
|
+
name: install binstubs
|
20
|
+
working_directory: ~/repo
|
21
|
+
command: |
|
22
|
+
bundle exec rake app:update:bin webpacker:binstubs
|
23
|
+
- run:
|
24
|
+
name: install dependencies
|
25
|
+
working_directory: ~/repo/spec/dummy
|
26
|
+
command: |
|
27
|
+
yarn install
|
28
|
+
- run:
|
29
|
+
name: run tests
|
30
|
+
working_directory: ~/repo
|
31
|
+
command: |
|
32
|
+
bundle exec rspec
|
33
|
+
- run:
|
34
|
+
name: run rubocop
|
35
|
+
working_directory: ~/repo
|
36
|
+
command: |
|
37
|
+
bundle exec rubocop
|
38
|
+
- run:
|
39
|
+
name: report coverage
|
40
|
+
working_directory: ~/repo
|
41
|
+
command: |
|
42
|
+
bash <(curl -s https://codecov.io/bash) -f coverage/.resultset.json
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'lib/generators/templates/**/*'
|
4
|
+
- 'tmp/**/*'
|
5
|
+
- 'bin/*'
|
6
|
+
- 'spec/dummy/bin/**/*'
|
7
|
+
- 'spec/dummy/node_modules/**/*'
|
8
|
+
- 'vendor/**/*'
|
9
|
+
TargetRubyVersion: 2.5
|
10
|
+
|
11
|
+
Metrics/LineLength:
|
12
|
+
Max: 130
|
13
|
+
|
14
|
+
Metrics/MethodLength:
|
15
|
+
Max: 30
|
16
|
+
|
17
|
+
Metrics/AbcSize:
|
18
|
+
Max: 25
|
19
|
+
|
20
|
+
Metrics/ClassLength:
|
21
|
+
Max: 200
|
22
|
+
|
23
|
+
Style/ClassVars:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/MissingRespondToMissing:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/GuardClause:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Naming/MemoizedInstanceVariableName:
|
33
|
+
EnforcedStyleForLeadingUnderscores: optional
|
34
|
+
|
35
|
+
Style/Documentation:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/AccessModifierDeclarations:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/NumericPredicate:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Metrics/BlockLength:
|
45
|
+
Exclude:
|
46
|
+
- 'spec/**/*'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in server_component.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'execjs'
|
9
|
+
gem 'stitch-rb'
|
10
|
+
|
11
|
+
group :development, :test do
|
12
|
+
gem 'capybara', '~> 2.18.0'
|
13
|
+
gem 'pry-byebug'
|
14
|
+
gem 'rspec-rails', '~> 3.8'
|
15
|
+
gem 'rubocop'
|
16
|
+
gem 'selenium-webdriver'
|
17
|
+
gem 'simplecov'
|
18
|
+
gem 'webpacker', '4.0.0.rc.2'
|
19
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
[![CircleCI](https://circleci.com/gh/effective-spa/server_component_rails.svg?style=svg)](https://circleci.com/gh/effective-spa/server_component_rails) [![codecov](https://codecov.io/gh/effective-spa/server_component_rails/branch/master/graph/badge.svg)](https://codecov.io/gh/effective-spa/server_component_rails) [![Maintainability](https://api.codeclimate.com/v1/badges/3a8af74f091ff7c3dbf4/maintainability)](https://codeclimate.com/github/effective-spa/server_component_rails/maintainability)
|
2
|
+
|
3
|
+
# ServerComponent
|
4
|
+
|
5
|
+
ServerComponent makes it possible to implement React component as Rails controller.
|
6
|
+
This gem is designed to be used with [server_component](https://github.com/effective-spa/server_component) NPM package.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile and run `bundle install`:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'server_component'
|
14
|
+
```
|
15
|
+
|
16
|
+
Run this command to initialize configuration file and base classes.
|
17
|
+
|
18
|
+
```sh
|
19
|
+
$ rails generate server_component:install
|
20
|
+
```
|
21
|
+
|
22
|
+
## Getting Started
|
23
|
+
|
24
|
+
Add a new component controller:
|
25
|
+
|
26
|
+
```rb
|
27
|
+
class CounterComponentController < ServerComponent::ComponentController
|
28
|
+
def self.initial_state
|
29
|
+
{
|
30
|
+
count: 0
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def increment
|
35
|
+
set_state do |s|
|
36
|
+
s.count { |prev| prev + 1 }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def decrement
|
41
|
+
set_state do |s|
|
42
|
+
s.count { |prev| prev - 1 }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Specify in a component router:
|
49
|
+
|
50
|
+
```rb
|
51
|
+
class ApiComponentRouter < ServerComponent::ComponentRouter
|
52
|
+
component :counter
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
In the client code (with Babel >= 7):
|
57
|
+
|
58
|
+
```js
|
59
|
+
import React, { Component } from 'react';
|
60
|
+
import ReactDOM from 'react-dom';
|
61
|
+
import ServerComponent, { server_component, consumer } from 'server_component';
|
62
|
+
|
63
|
+
@server_component('counter')
|
64
|
+
class CounterContainer extends Component { }
|
65
|
+
|
66
|
+
@consumer('counter')
|
67
|
+
class CounterBody extends Component {
|
68
|
+
render() {
|
69
|
+
const { increment, decrement, state: { count } } = this.props.counter;
|
70
|
+
return (
|
71
|
+
<div>
|
72
|
+
<h1>Counter</h1>
|
73
|
+
<button onClick={increment}>increment</button>
|
74
|
+
<button onClick={decrement}>decrement</button>
|
75
|
+
<hr />
|
76
|
+
<div>Count: {counter}</div>
|
77
|
+
</div>
|
78
|
+
);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
class Main extends Component {
|
83
|
+
render() {
|
84
|
+
return (
|
85
|
+
<ServerComponent.Use at="/react" name="api">
|
86
|
+
<CounterContainer>
|
87
|
+
<CounterBody />
|
88
|
+
</CounterContainer>
|
89
|
+
</ServerComponent.Use>
|
90
|
+
);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
ReactDOM.render(<Main />, document.getElementById('root'));
|
95
|
+
```
|
96
|
+
|
97
|
+
## API Documentation
|
98
|
+
|
99
|
+
(TODO)
|
100
|
+
|
101
|
+
## Contributing
|
102
|
+
|
103
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/effective-spa/server_component.
|
104
|
+
|
105
|
+
|
106
|
+
## License
|
107
|
+
|
108
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
# This let CI know tasks 'app:update:bin' and 'webpacker:binstubs'
|
11
|
+
require_relative 'spec/dummy/config/application'
|
12
|
+
Rails.application.load_tasks
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
class FetcherConfigurator
|
5
|
+
include ServerComponentHelper
|
6
|
+
|
7
|
+
def initialize(component_class, actions)
|
8
|
+
@component_class = component_class
|
9
|
+
@actions = actions
|
10
|
+
end
|
11
|
+
|
12
|
+
def before
|
13
|
+
jsrb = Jsrb::Base.new
|
14
|
+
set_state(jsrb) do |s|
|
15
|
+
yield(s, jsrb.expr.context.data)
|
16
|
+
end
|
17
|
+
set_value(:start_function, jsrb.generate_code)
|
18
|
+
end
|
19
|
+
|
20
|
+
def accept_file!
|
21
|
+
set_value :upload, true
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_value(key, value)
|
25
|
+
@actions.each do |action|
|
26
|
+
@component_class.action_descriptors[action][key] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class BaseComponentController < ServerComponent.parent_controller.constantize
|
32
|
+
class_attribute :initial_state, :action_on_mount
|
33
|
+
|
34
|
+
helper ServerComponentHelper
|
35
|
+
include ServerComponentHelper
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_state(&block) # rubocop:disable Naming/AccessorMethodName
|
40
|
+
jsrb = Jsrb::Base.new
|
41
|
+
super(jsrb, &block)
|
42
|
+
render jsrb: jsrb.generate_code
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def to_json
|
47
|
+
{
|
48
|
+
action_on_mount: action_on_mount,
|
49
|
+
initial_state: initial_state,
|
50
|
+
actions: action_descriptors
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def action_descriptors
|
55
|
+
if instance_variable_defined?('@action_descriptors')
|
56
|
+
instance_variable_get '@action_descriptors'
|
57
|
+
else
|
58
|
+
h = {}
|
59
|
+
instance_variable_set '@action_descriptors', h
|
60
|
+
h
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def action(*action_names, &block)
|
65
|
+
options = action_names.extract_options!
|
66
|
+
raise ArgumentError, 'specify at least one action!' if action_names.empty?
|
67
|
+
|
68
|
+
action_names.each do |action_name|
|
69
|
+
action_descriptors[action_name.to_sym] = {}
|
70
|
+
end
|
71
|
+
fetcher(*action_names, &block) if block
|
72
|
+
if options[:on_mount]
|
73
|
+
raise ArgumentError, 'on_mount option can accept only one action' if action_names.size > 1
|
74
|
+
|
75
|
+
on_mount action_names.first
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_mount(action)
|
80
|
+
self.action_on_mount = action
|
81
|
+
end
|
82
|
+
|
83
|
+
def fetcher(*actions)
|
84
|
+
configurator = FetcherConfigurator.new(self, actions.map(&:to_sym))
|
85
|
+
yield configurator
|
86
|
+
end
|
87
|
+
|
88
|
+
def before(*actions, &block)
|
89
|
+
fetcher(*actions) do |c|
|
90
|
+
c.before(&block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
class ComponentRoutersController < ServerComponent.parent_controller.constantize
|
5
|
+
def show
|
6
|
+
component_router = find_component_router
|
7
|
+
render json: component_router.to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def find_component_router
|
13
|
+
component_router_class_name = "#{name.camelize}ComponentRouter"
|
14
|
+
component_router_class =
|
15
|
+
begin
|
16
|
+
component_router_class_name.constantize
|
17
|
+
rescue NameError
|
18
|
+
raise ActionController::RoutingError, "No route matches routes/#{name}"
|
19
|
+
end
|
20
|
+
component_router_class.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
params[:name]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
class ComponentsController < ServerComponent.parent_controller.constantize
|
5
|
+
def perform
|
6
|
+
component_class = find_component_class!
|
7
|
+
component_class.dispatch(component_action, request, response)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def find_component_class!
|
13
|
+
klass_name = "#{component_name.camelize}ComponentController"
|
14
|
+
klass =
|
15
|
+
begin
|
16
|
+
klass_name.constantize
|
17
|
+
rescue NameError
|
18
|
+
raise ActionController::RoutingError, "No route matches #{component_name}/#{component_action}"
|
19
|
+
end
|
20
|
+
unless klass <= ServerComponent::BaseComponentController
|
21
|
+
raise ActionController::RoutingError, "No route matches #{component_name}/#{component_action}"
|
22
|
+
end
|
23
|
+
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
|
27
|
+
def component_name
|
28
|
+
params[:component_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def component_action
|
32
|
+
params[:component_action_name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponentHelper
|
4
|
+
class SetStateDSL
|
5
|
+
def initialize(new_state, prev_state)
|
6
|
+
@new_state = new_state
|
7
|
+
@prev_state = prev_state
|
8
|
+
end
|
9
|
+
|
10
|
+
# rubocop:disable Style/MethodMissingSuper
|
11
|
+
def method_missing(name, *args)
|
12
|
+
if (matches = name.to_s.match(/^(.+)=$/))
|
13
|
+
@new_state.member!(matches[1]).assign!(args[0]).as_statement!
|
14
|
+
elsif block_given?
|
15
|
+
block_result = yield @prev_state.member!(name)
|
16
|
+
@new_state.member!(name).assign!(block_result).as_statement!
|
17
|
+
else
|
18
|
+
@new_state.member!(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# rubocop:enable Style/MethodMissingSuper
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_state(jsrb) # rubocop:disable Naming/AccessorMethodName
|
25
|
+
new_state = jsrb.var! { {} }
|
26
|
+
prev_state = jsrb.expr.component.state
|
27
|
+
yield SetStateDSL.new(new_state, prev_state)
|
28
|
+
jsrb.expr.component.setState.call(new_state).as_statement!
|
29
|
+
end
|
30
|
+
end
|
data/bin/rails
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is a manually created bin file.
|
4
|
+
# Some webpacker tasks depend on the current directory,
|
5
|
+
# which is fixed in https://github.com/rails/webpacker/pull/1475
|
6
|
+
# but in webpacker 3.x, it's not fixed yet;)
|
7
|
+
APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
|
8
|
+
require_relative '../spec/dummy/config/boot'
|
9
|
+
require 'rails/commands'
|
data/bin/webpack
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
module Generators
|
5
|
+
class ComponentControllerGenerator < Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path('../templates', __dir__)
|
7
|
+
|
8
|
+
desc 'Creates a ServerComponent initializer.'
|
9
|
+
|
10
|
+
def create_controller_file
|
11
|
+
template 'component_controller.rb', File.join('app/controllers', class_path, "#{file_name}_component_controller.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
def component
|
15
|
+
rgx = /class [A-z::]+ComponentRouter < ServerComponent::ComponentRouter\n/
|
16
|
+
router_path = File.join('app/component_routers', "#{router_name}_component_router.rb")
|
17
|
+
routing_code = " component '#{name}'\n"
|
18
|
+
inject_into_file router_path, routing_code, after: rgx
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def router_name
|
24
|
+
@options.fetch(:router, 'api')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __dir__)
|
7
|
+
|
8
|
+
desc 'Creates a ServerComponent initializer.'
|
9
|
+
|
10
|
+
def create_initializer_file
|
11
|
+
template 'server_component.rb', 'config/initializers/server_component.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_route
|
15
|
+
route "mount ServerComponent::Engine, at: 'react'"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_directory
|
19
|
+
empty_directory 'app/component_routers'
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_one_router
|
23
|
+
template 'api_component_router.rb', 'app/component_routers/api_component_router.rb'
|
24
|
+
end
|
25
|
+
|
26
|
+
def show_readme
|
27
|
+
readme 'AFTER_INSTALL' if behavior == :invoke
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
What's next:
|
4
|
+
|
5
|
+
1. Add "component_routers" directory into autoload_paths
|
6
|
+
in config/application.rb
|
7
|
+
|
8
|
+
config.autoload_paths << Rails.root.join('component_routers')
|
9
|
+
|
10
|
+
|
11
|
+
2. Create component controller. If you want BookComponent for example, run:
|
12
|
+
|
13
|
+
$ rails generate server_component:component_controller book --router api
|
14
|
+
|
15
|
+
|
16
|
+
3. Conjoin client application with `server_component`:
|
17
|
+
|
18
|
+
// Create a component class.
|
19
|
+
|
20
|
+
@ServerComponent.component('book')
|
21
|
+
class BookContainer extends React.Component {}
|
22
|
+
|
23
|
+
// Create a component-attached class.
|
24
|
+
|
25
|
+
@ServerComponent.attach('book')
|
26
|
+
class Book extends React.Component {
|
27
|
+
render() {
|
28
|
+
const { state: { list } } = this.props.book;
|
29
|
+
return (
|
30
|
+
<ul>
|
31
|
+
{list.map(book => <li>{book.title}</li>)}
|
32
|
+
</ul>
|
33
|
+
);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
// And use in your root render function:
|
38
|
+
|
39
|
+
<ServerComponent.Use at="/react" name="api">
|
40
|
+
<BookContainer>
|
41
|
+
<Book />
|
42
|
+
</BookContainer>
|
43
|
+
</ServerComponent.Use>
|
44
|
+
|
45
|
+
===============================================================================
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Use this hook to configure ServerComponent
|
4
|
+
ServerComponent.setup do |config|
|
5
|
+
# context_script is executed before response scripts.
|
6
|
+
# It is useful if you want to add functions
|
7
|
+
# invoked from JSRB renderer.
|
8
|
+
# You can get identifiers from JSRB by calling
|
9
|
+
# `js.identifier_name` .
|
10
|
+
# The combination of 'add_custom_chain' option will
|
11
|
+
# make your JSRB code more readable.
|
12
|
+
config.context_script = <<~JAVASCRIPT
|
13
|
+
// Returns new object or array
|
14
|
+
// updating a property by immediate value or updator
|
15
|
+
// function in the specified path.
|
16
|
+
// without side effect in the original.
|
17
|
+
//
|
18
|
+
// example:
|
19
|
+
// immutable_update({ foo: 1 }, 'foo', 2) => { foo: 2 }
|
20
|
+
//
|
21
|
+
var immutable_update = (function () {
|
22
|
+
function _is_array(target) {
|
23
|
+
return Object.prototype.toString.call(target) === '[object Array]';
|
24
|
+
}
|
25
|
+
|
26
|
+
function _is_object(target) {
|
27
|
+
return typeof target === 'object' && target !== null;
|
28
|
+
}
|
29
|
+
|
30
|
+
function _clone_array_or_object(target) {
|
31
|
+
if (_is_array(target)) {
|
32
|
+
return target.slice(0);
|
33
|
+
} else if (_is_object(target)) {
|
34
|
+
return Object(target);
|
35
|
+
} else {
|
36
|
+
throw 'Argument Error: target is neither object nor array';
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
function _clone_update_internal(obj, field, value) {
|
41
|
+
if (value instanceof Function) {
|
42
|
+
obj[field] = value(obj[field]);
|
43
|
+
} else {
|
44
|
+
obj[field] = value;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
return function (target, field, updator) {
|
49
|
+
var clone = _clone_array_or_object(target);
|
50
|
+
_clone_update_internal(clone, field, updator);
|
51
|
+
return clone;
|
52
|
+
}
|
53
|
+
})();
|
54
|
+
JAVASCRIPT
|
55
|
+
|
56
|
+
# Adds functionality to JSRB DSL.
|
57
|
+
# If you add 'func!' to 'some_func',
|
58
|
+
# then `obj.func!(arg)` is equivalent to `js.some_func.(obj, arg)`
|
59
|
+
# where `obj` is a chain instance in JSRB.
|
60
|
+
config.add_custom_chain(:update!, :immutable_update)
|
61
|
+
|
62
|
+
# Specify parent class of ServerComponent's controllers.
|
63
|
+
# config.parent_controller = 'ApplicationController'
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/dependencies'
|
4
|
+
require 'jsrb'
|
5
|
+
|
6
|
+
module ServerComponent
|
7
|
+
autoload :VERSION, 'server_component/version'
|
8
|
+
autoload :ComponentRouter, 'server_component/component_router'
|
9
|
+
|
10
|
+
class NamespaceNotFound < StandardError; end
|
11
|
+
|
12
|
+
mattr_accessor :parent_controller
|
13
|
+
@@parent_controller = 'ApplicationController'
|
14
|
+
|
15
|
+
def self.setup
|
16
|
+
yield self
|
17
|
+
end
|
18
|
+
|
19
|
+
mattr_accessor :context_script
|
20
|
+
@@context_script = ''
|
21
|
+
|
22
|
+
def self.add_custom_chain(name, function_name)
|
23
|
+
Jsrb::ExprChain.add_custom_chain name, function_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'server_component/engine'
|
28
|
+
require 'server_component/jsrb_handler'
|
29
|
+
require 'server_component/railtie' if defined?(Rails)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
class ComponentRouter
|
5
|
+
class_attribute :_namespace
|
6
|
+
|
7
|
+
def to_json
|
8
|
+
h = {}
|
9
|
+
h[:components] = components.each_with_object({}) do |component, json|
|
10
|
+
component_controller_class = find_component_controller_class(component)
|
11
|
+
json[component] = component_controller_class.to_json
|
12
|
+
end
|
13
|
+
h[:context_script] = context_script
|
14
|
+
h
|
15
|
+
end
|
16
|
+
|
17
|
+
def components
|
18
|
+
self.class.components
|
19
|
+
end
|
20
|
+
|
21
|
+
def namespace
|
22
|
+
self.class._namespace
|
23
|
+
end
|
24
|
+
|
25
|
+
def context_script
|
26
|
+
ServerComponent.context_script
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def find_component_controller_class(component)
|
32
|
+
name = (namespace ? "#{namespace}/#{component}" : component).to_s
|
33
|
+
"#{name.camelize}ComponentController".constantize
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def namespace(mod)
|
38
|
+
self._namespace = mod
|
39
|
+
end
|
40
|
+
|
41
|
+
def components
|
42
|
+
if instance_variable_defined?('@components')
|
43
|
+
instance_variable_get '@components'
|
44
|
+
else
|
45
|
+
a = []
|
46
|
+
instance_variable_set '@components', a
|
47
|
+
a
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def component(*component_names)
|
52
|
+
component_names.each do |name|
|
53
|
+
components << name.to_sym
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServerComponent
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
initializer :server_component do
|
6
|
+
ActiveSupport.on_load :action_view do
|
7
|
+
ActionView::Template.register_template_handler :jsrb, JsrbHandler
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveSupport.on_load :action_controller do
|
11
|
+
ActionController::Renderers.add :jsrb do |obj, _options|
|
12
|
+
send_data obj.to_s, type: Mime[:js]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'server_component/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'server_component'
|
9
|
+
spec.version = ServerComponent::VERSION
|
10
|
+
spec.authors = ['Shun MIZUKAMI']
|
11
|
+
spec.email = ['norainu234@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'ServerComponent support for Rails'
|
14
|
+
spec.description = 'ServerComponent makes it possible to implement React component as Rails controller'
|
15
|
+
spec.homepage = 'https://github.com/effective-spa/server_component_rails'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
|
+
else
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
+
'public gem pushes.'
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
f.match(%r{^(test|spec|features)/})
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
spec.add_runtime_dependency 'jsrb', '~> 0.1'
|
35
|
+
spec.add_runtime_dependency 'rails', '~> 5.0', '>= 5.0.0'
|
36
|
+
|
37
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
38
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: server_component
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shun MIZUKAMI
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jsrb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 5.0.0
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '5.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 5.0.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.13'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.13'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '10.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '10.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.0'
|
89
|
+
description: ServerComponent makes it possible to implement React component as Rails
|
90
|
+
controller
|
91
|
+
email:
|
92
|
+
- norainu234@gmail.com
|
93
|
+
executables: []
|
94
|
+
extensions: []
|
95
|
+
extra_rdoc_files: []
|
96
|
+
files:
|
97
|
+
- ".circleci/config.yml"
|
98
|
+
- ".gitignore"
|
99
|
+
- ".rspec"
|
100
|
+
- ".rubocop.yml"
|
101
|
+
- ".ruby-version"
|
102
|
+
- ".travis.yml"
|
103
|
+
- Gemfile
|
104
|
+
- README.md
|
105
|
+
- Rakefile
|
106
|
+
- app/controllers/server_component/base_component_controller.rb
|
107
|
+
- app/controllers/server_component/component_routers_controller.rb
|
108
|
+
- app/controllers/server_component/components_controller.rb
|
109
|
+
- app/helpers/server_component_helper.rb
|
110
|
+
- bin/rails
|
111
|
+
- bin/webpack
|
112
|
+
- config/routes.rb
|
113
|
+
- lib/generators/server_component/component_controller_generator.rb
|
114
|
+
- lib/generators/server_component/install_generator.rb
|
115
|
+
- lib/generators/templates/AFTER_INSTALL
|
116
|
+
- lib/generators/templates/api_component_router.rb
|
117
|
+
- lib/generators/templates/component_controller.rb.tt
|
118
|
+
- lib/generators/templates/server_component.rb
|
119
|
+
- lib/server_component.rb
|
120
|
+
- lib/server_component/component_router.rb
|
121
|
+
- lib/server_component/engine.rb
|
122
|
+
- lib/server_component/js_function_builder.rb
|
123
|
+
- lib/server_component/jsrb_handler.rb
|
124
|
+
- lib/server_component/railtie.rb
|
125
|
+
- lib/server_component/version.rb
|
126
|
+
- server_component.gemspec
|
127
|
+
homepage: https://github.com/effective-spa/server_component_rails
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata:
|
131
|
+
allowed_push_host: https://rubygems.org
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 2.7.6
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: ServerComponent support for Rails
|
152
|
+
test_files: []
|