isomorfeus-preact 10.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +62 -0
- data/lib/browser/delegate_native.rb +70 -0
- data/lib/browser/element.rb +176 -0
- data/lib/browser/element/canvas.rb +17 -0
- data/lib/browser/element/media.rb +78 -0
- data/lib/browser/event.rb +92 -0
- data/lib/browser/event_target.rb +39 -0
- data/lib/browser/file_list.rb +125 -0
- data/lib/browser/iterable.rb +15 -0
- data/lib/isomorfeus-preact.rb +109 -0
- data/lib/isomorfeus/preact/config.rb +189 -0
- data/lib/isomorfeus/preact/memcached_component_cache.rb +19 -0
- data/lib/isomorfeus/preact/redis_component_cache.rb +19 -0
- data/lib/isomorfeus/preact/thread_local_component_cache.rb +17 -0
- data/lib/isomorfeus/preact_view_helper.rb +188 -0
- data/lib/isomorfeus/props/validate_hash_proxy.rb +186 -0
- data/lib/isomorfeus/props/validator.rb +159 -0
- data/lib/isomorfeus/top_level.rb +101 -0
- data/lib/isomorfeus/top_level_ssr.rb +27 -0
- data/lib/isomorfeus_preact/lucid_app/api.rb +22 -0
- data/lib/isomorfeus_preact/lucid_app/base.rb +7 -0
- data/lib/isomorfeus_preact/lucid_app/mixin.rb +16 -0
- data/lib/isomorfeus_preact/lucid_app/native_component_constructor.rb +91 -0
- data/lib/isomorfeus_preact/lucid_component/api.rb +68 -0
- data/lib/isomorfeus_preact/lucid_component/app_store_proxy.rb +37 -0
- data/lib/isomorfeus_preact/lucid_component/base.rb +7 -0
- data/lib/isomorfeus_preact/lucid_component/class_store_proxy.rb +44 -0
- data/lib/isomorfeus_preact/lucid_component/initializer.rb +14 -0
- data/lib/isomorfeus_preact/lucid_component/instance_store_proxy.rb +44 -0
- data/lib/isomorfeus_preact/lucid_component/mixin.rb +15 -0
- data/lib/isomorfeus_preact/lucid_component/native_component_constructor.rb +84 -0
- data/lib/isomorfeus_preact/lucid_component/styles_api.rb +31 -0
- data/lib/isomorfeus_preact/lucid_component/styles_wrapper.rb +40 -0
- data/lib/isomorfeus_preact/lucid_func/base.rb +7 -0
- data/lib/isomorfeus_preact/lucid_func/initializer.rb +11 -0
- data/lib/isomorfeus_preact/lucid_func/mixin.rb +12 -0
- data/lib/isomorfeus_preact/lucid_func/native_component_constructor.rb +55 -0
- data/lib/isomorfeus_preact/preact/function_component/api.rb +123 -0
- data/lib/isomorfeus_preact/preact/function_component/base.rb +9 -0
- data/lib/isomorfeus_preact/preact/function_component/initializer.rb +10 -0
- data/lib/isomorfeus_preact/preact/function_component/mixin.rb +12 -0
- data/lib/isomorfeus_preact/preact/function_component/native_component_constructor.rb +48 -0
- data/lib/lucid_app/context.rb +24 -0
- data/lib/lucid_prop_declaration/mixin.rb +126 -0
- data/lib/preact.rb +309 -0
- data/lib/preact/component/api.rb +124 -0
- data/lib/preact/component/base.rb +9 -0
- data/lib/preact/component/callbacks.rb +102 -0
- data/lib/preact/component/elements.rb +64 -0
- data/lib/preact/component/initializer.rb +11 -0
- data/lib/preact/component/mixin.rb +15 -0
- data/lib/preact/component/native_component_constructor.rb +65 -0
- data/lib/preact/component/params.rb +18 -0
- data/lib/preact/component/props.rb +55 -0
- data/lib/preact/component/resolution.rb +97 -0
- data/lib/preact/component/state.rb +58 -0
- data/lib/preact/context_wrapper.rb +46 -0
- data/lib/preact/native_constant_wrapper.rb +29 -0
- data/lib/preact/options.rb +98 -0
- data/lib/preact/ref.rb +17 -0
- data/lib/preact/version.rb +3 -0
- metadata +301 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Browser
|
2
|
+
module EventTarget
|
3
|
+
# Add the block as a handler for the specified event name. Will use either
|
4
|
+
# `addEventListener` or `addListener` if they exist.
|
5
|
+
#
|
6
|
+
# @param event_name [String] the name of the event
|
7
|
+
# @return [Proc] the block to pass to `off` to remove this handler
|
8
|
+
# @yieldparam event [Browser::Event] the event object
|
9
|
+
def on event_name, &block
|
10
|
+
wrapper = proc { |event| block.call Event.new(event) }
|
11
|
+
|
12
|
+
if `#@native.addEventListener !== undefined`
|
13
|
+
`#@native.addEventListener(event_name, wrapper)`
|
14
|
+
elsif `#@native.addListener !== undefined`
|
15
|
+
`#@native.addListener(event_name, wrapper)`
|
16
|
+
else
|
17
|
+
warn "[Browser] Not entirely sure how to add an event listener to #{self}"
|
18
|
+
end
|
19
|
+
|
20
|
+
wrapper
|
21
|
+
end
|
22
|
+
|
23
|
+
# Remove an event handler
|
24
|
+
#
|
25
|
+
# @param event_name [String] the name of the event
|
26
|
+
# @block the handler to remove, as returned from `on`
|
27
|
+
def off event_name, &block
|
28
|
+
if `#@native.removeEventListener !== undefined`
|
29
|
+
`#@native.removeEventListener(event_name, block)`
|
30
|
+
elsif `#@native.removeListener !== undefined`
|
31
|
+
`#@native.removeListener(event_name, block)`
|
32
|
+
else
|
33
|
+
warn "[Browser] Not entirely sure how to remove an event listener from #{self}"
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Browser
|
2
|
+
class FileList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# @param native [JS] the native FileList object to wrap
|
6
|
+
def initialize native
|
7
|
+
@native = `#{native} || []`
|
8
|
+
@files = length.times.each_with_object([]) { |index, array|
|
9
|
+
array[index] = File.new(`#@native[index]`)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param index [Integer] the index of the file in the list
|
14
|
+
# @return [Browser::FileList::File] the file at the specified index
|
15
|
+
def [] index
|
16
|
+
@files[index]
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Integer] the number of files in this list
|
20
|
+
def length
|
21
|
+
`#@native.length`
|
22
|
+
end
|
23
|
+
alias size length
|
24
|
+
|
25
|
+
# Call the given block for each file in the list
|
26
|
+
#
|
27
|
+
# @yieldparam file [Browser::FileList::File]
|
28
|
+
def each &block
|
29
|
+
@files.each do |file|
|
30
|
+
block.call file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert this FileList into an array
|
35
|
+
def to_a
|
36
|
+
@files.dup # Don't return a value that can mutate our internal state
|
37
|
+
end
|
38
|
+
alias to_ary to_a
|
39
|
+
|
40
|
+
# @return [String] a string representation of this FileList
|
41
|
+
def to_s
|
42
|
+
@files.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# An individual item in a FileList
|
46
|
+
class File
|
47
|
+
attr_reader :data
|
48
|
+
|
49
|
+
# @param native [JS] the native File object to wrap
|
50
|
+
def initialize native
|
51
|
+
@native = native
|
52
|
+
@data = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String] the filename
|
56
|
+
def name
|
57
|
+
`#@native.name`
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Integer] the size of this file on disk
|
61
|
+
def size
|
62
|
+
`#@native.size`
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [String] the MIME type of the file, detected by the browser
|
66
|
+
def type
|
67
|
+
`#@native.type`
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Time] the timestamp of the file
|
71
|
+
def last_modified
|
72
|
+
`#@native.lastModifiedDate`
|
73
|
+
end
|
74
|
+
|
75
|
+
# Read the file from disk into memory
|
76
|
+
#
|
77
|
+
# @return [Promise] a promise that resolves when finished loading and
|
78
|
+
# rejects if an error occurs while loading.
|
79
|
+
def read
|
80
|
+
promise = Promise.new
|
81
|
+
reader = FileReader.new
|
82
|
+
reader.on :load do
|
83
|
+
result = reader.result
|
84
|
+
|
85
|
+
@data = result
|
86
|
+
promise.resolve result
|
87
|
+
end
|
88
|
+
|
89
|
+
reader.on :error do
|
90
|
+
promise.reject reader.result
|
91
|
+
end
|
92
|
+
|
93
|
+
reader.read_as_binary_string self
|
94
|
+
|
95
|
+
promise
|
96
|
+
end
|
97
|
+
|
98
|
+
# Convert to the native object
|
99
|
+
#
|
100
|
+
# @return [JS.HTMLElement] the underlying native element
|
101
|
+
def to_n
|
102
|
+
@native
|
103
|
+
end
|
104
|
+
|
105
|
+
# The object that reads the file from disk.
|
106
|
+
#
|
107
|
+
# @api private
|
108
|
+
class FileReader
|
109
|
+
include EventTarget
|
110
|
+
|
111
|
+
def initialize
|
112
|
+
@native = `new FileReader()`
|
113
|
+
end
|
114
|
+
|
115
|
+
def result
|
116
|
+
`#@native.result`
|
117
|
+
end
|
118
|
+
|
119
|
+
def read_as_binary_string file
|
120
|
+
`#@native.readAsBinaryString(#{file.to_n})`
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
require 'isomorfeus-redux'
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require 'zeitwerk'
|
5
|
+
|
6
|
+
if on_browser?
|
7
|
+
require 'browser/event'
|
8
|
+
require 'browser/event_target'
|
9
|
+
require 'browser/delegate_native'
|
10
|
+
require 'browser/element'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'isomorfeus/preact/config'
|
14
|
+
|
15
|
+
# allow mounting of components
|
16
|
+
if on_browser?
|
17
|
+
require 'isomorfeus/top_level'
|
18
|
+
else
|
19
|
+
require 'isomorfeus/top_level_ssr'
|
20
|
+
end
|
21
|
+
|
22
|
+
# preact
|
23
|
+
require 'preact/version'
|
24
|
+
require 'preact'
|
25
|
+
require 'preact/ref'
|
26
|
+
|
27
|
+
# props
|
28
|
+
require 'isomorfeus/props/validate_hash_proxy'
|
29
|
+
require 'isomorfeus/props/validator'
|
30
|
+
require 'lucid_prop_declaration/mixin'
|
31
|
+
require 'preact/component/props'
|
32
|
+
|
33
|
+
# HTML Elements and Fragment support
|
34
|
+
require 'preact/component/elements'
|
35
|
+
|
36
|
+
# Preact Wrappers
|
37
|
+
require 'preact/context_wrapper'
|
38
|
+
require 'preact/native_constant_wrapper'
|
39
|
+
|
40
|
+
# Preact::Component
|
41
|
+
require 'preact/component/api'
|
42
|
+
require 'preact/component/callbacks'
|
43
|
+
require 'preact/component/initializer'
|
44
|
+
require 'preact/component/native_component_constructor'
|
45
|
+
require 'preact/component/state'
|
46
|
+
require 'preact/component/params'
|
47
|
+
require 'preact/component/resolution'
|
48
|
+
require 'preact/component/mixin'
|
49
|
+
require 'preact/component/base'
|
50
|
+
|
51
|
+
# init LucidApplicationContext (Store Provider and Consumer)
|
52
|
+
require 'lucid_app/context'
|
53
|
+
LucidApp::Context.create_application_context
|
54
|
+
|
55
|
+
class Object
|
56
|
+
include Preact::Component::Resolution
|
57
|
+
end
|
58
|
+
|
59
|
+
Isomorfeus.zeitwerk = Zeitwerk::Loader.new
|
60
|
+
|
61
|
+
Isomorfeus.zeitwerk.push_dir('isomorfeus_preact')
|
62
|
+
require_tree 'isomorfeus_preact', :autoload
|
63
|
+
|
64
|
+
Isomorfeus.zeitwerk.push_dir('components')
|
65
|
+
else
|
66
|
+
require 'uri'
|
67
|
+
require 'oj'
|
68
|
+
require 'opal'
|
69
|
+
require 'opal-activesupport'
|
70
|
+
require 'opal-zeitwerk'
|
71
|
+
require 'opal-webpack-loader'
|
72
|
+
require 'isomorfeus-redux'
|
73
|
+
require 'isomorfeus-speednode'
|
74
|
+
require 'preact/version'
|
75
|
+
require 'isomorfeus/preact/config'
|
76
|
+
|
77
|
+
# props
|
78
|
+
require 'isomorfeus/props/validate_hash_proxy'
|
79
|
+
require 'isomorfeus/props/validator'
|
80
|
+
require 'lucid_prop_declaration/mixin'
|
81
|
+
|
82
|
+
Isomorfeus.env = ENV['RACK_ENV']
|
83
|
+
|
84
|
+
if Isomorfeus.development?
|
85
|
+
require 'net/http'
|
86
|
+
Isomorfeus.ssr_hot_asset_url = 'http://localhost:3036/assets/'
|
87
|
+
end
|
88
|
+
|
89
|
+
Isomorfeus.server_side_rendering = true
|
90
|
+
|
91
|
+
# caches
|
92
|
+
require 'isomorfeus/preact/thread_local_component_cache'
|
93
|
+
require 'isomorfeus/preact/memcached_component_cache'
|
94
|
+
require 'isomorfeus/preact/redis_component_cache'
|
95
|
+
require 'isomorfeus/preact_view_helper'
|
96
|
+
|
97
|
+
Isomorfeus.component_cache_init do
|
98
|
+
Isomorfeus::ThreadLocalComponentCache.new
|
99
|
+
end
|
100
|
+
|
101
|
+
Opal.append_path(__dir__.untaint)
|
102
|
+
|
103
|
+
require 'concurrent'
|
104
|
+
require 'zeitwerk'
|
105
|
+
|
106
|
+
Isomorfeus.zeitwerk = Zeitwerk::Loader.new
|
107
|
+
Isomorfeus.zeitwerk_lock = Concurrent::ReentrantReadWriteLock.new if Isomorfeus.development?
|
108
|
+
# nothing to push_dir to zeitwerk here, as components are available only within browser/SSR
|
109
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
if RUBY_ENGINE == 'opal'
|
3
|
+
class << self
|
4
|
+
attr_accessor :current_user_sid
|
5
|
+
attr_accessor :initial_state_fetched
|
6
|
+
attr_accessor :top_component
|
7
|
+
attr_accessor :ssr_response_status
|
8
|
+
attr_reader :initialized
|
9
|
+
attr_reader :env
|
10
|
+
attr_accessor :zeitwerk
|
11
|
+
|
12
|
+
def init
|
13
|
+
return if initialized
|
14
|
+
@initialized = true
|
15
|
+
Isomorfeus.init_store
|
16
|
+
execute_init_classes
|
17
|
+
end
|
18
|
+
|
19
|
+
def force_init!
|
20
|
+
unless Isomorfeus.initial_state_fetched
|
21
|
+
Isomorfeus.initial_state_fetched = true
|
22
|
+
Redux::Store.preloaded_state = Isomorfeus.store.get_state
|
23
|
+
end
|
24
|
+
Isomorfeus.force_init_store!
|
25
|
+
execute_init_classes
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_client_init_class_name(init_class_name)
|
29
|
+
client_init_class_names << init_class_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_client_init_after_store_class_name(init_class_name)
|
33
|
+
client_init_after_store_class_names << init_class_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_client_option(key, value = nil)
|
37
|
+
self.class.attr_accessor(key)
|
38
|
+
self.send("#{key}=", value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# only used for SSR
|
42
|
+
def cached_component_classes
|
43
|
+
@cached_component_classes ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
# only used for SSR
|
47
|
+
def cached_component_class(class_name)
|
48
|
+
return cached_component_classes[class_name] if cached_component_classes.key?(class_name)
|
49
|
+
cached_component_classes[class_name] = "::#{class_name}".constantize
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute_init_classes
|
53
|
+
client_init_class_names.each do |constant|
|
54
|
+
constant.constantize.send(:init)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_init_after_store_classes
|
59
|
+
client_init_after_store_class_names.each do |constant|
|
60
|
+
constant.constantize.send(:init)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def env=(env_string)
|
65
|
+
@env = env_string ? env_string : 'development'
|
66
|
+
@development = (@env == 'development') ? true : false
|
67
|
+
@production = (@env == 'production') ? true : false
|
68
|
+
@test = (@env == 'test') ? true : false
|
69
|
+
end
|
70
|
+
|
71
|
+
def development?
|
72
|
+
@development
|
73
|
+
end
|
74
|
+
|
75
|
+
def production?
|
76
|
+
@production
|
77
|
+
end
|
78
|
+
|
79
|
+
def test?
|
80
|
+
@test
|
81
|
+
end
|
82
|
+
|
83
|
+
def start_app!
|
84
|
+
Isomorfeus.zeitwerk.setup
|
85
|
+
Isomorfeus::TopLevel.mount!
|
86
|
+
end
|
87
|
+
|
88
|
+
def force_render
|
89
|
+
begin
|
90
|
+
if Isomorfeus.top_component
|
91
|
+
Preact.find_dom_node(Isomorfeus.top_component) if on_browser? || on_desktop? # if not mounted will raise
|
92
|
+
if `typeof Opal.global.deepForceUpdate === 'undefined'`
|
93
|
+
Isomorfeus.top_component.JS.forceUpdate()
|
94
|
+
else
|
95
|
+
`Opal.global.deepForceUpdate(#{Isomorfeus.top_component})`
|
96
|
+
end
|
97
|
+
end
|
98
|
+
rescue Exception => e
|
99
|
+
# TODO try mount first
|
100
|
+
# if it fails
|
101
|
+
`console.error("force_render failed'! Error: " + #{e.message} + "! Reloading page.")`
|
102
|
+
`location.reload()` if on_browser?
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
self.add_client_option(:client_init_class_names, [])
|
109
|
+
self.add_client_option(:client_init_after_store_class_names, [])
|
110
|
+
else
|
111
|
+
class << self
|
112
|
+
attr_reader :component_cache_init_block
|
113
|
+
attr_accessor :server_side_rendering
|
114
|
+
attr_accessor :ssr_hot_asset_url
|
115
|
+
attr_reader :env
|
116
|
+
attr_accessor :zeitwerk
|
117
|
+
attr_accessor :zeitwerk_lock
|
118
|
+
|
119
|
+
def component_cache_init(&block)
|
120
|
+
@component_cache_init_block = block
|
121
|
+
end
|
122
|
+
|
123
|
+
def configuration(&block)
|
124
|
+
block.call(self)
|
125
|
+
end
|
126
|
+
|
127
|
+
def env=(env_string)
|
128
|
+
@env = env_string ? env_string.to_s : 'development'
|
129
|
+
@development = (@env == 'development') ? true : false
|
130
|
+
@production = (@env == 'production') ? true : false
|
131
|
+
@test = (@env == 'test') ? true : false
|
132
|
+
end
|
133
|
+
|
134
|
+
def development?
|
135
|
+
@development
|
136
|
+
end
|
137
|
+
|
138
|
+
def production?
|
139
|
+
@production
|
140
|
+
end
|
141
|
+
|
142
|
+
def test?
|
143
|
+
@test
|
144
|
+
end
|
145
|
+
|
146
|
+
def ssr_contexts
|
147
|
+
@ssr_contexts ||= {}
|
148
|
+
end
|
149
|
+
|
150
|
+
def version
|
151
|
+
Isomorfeus::VERSION
|
152
|
+
end
|
153
|
+
|
154
|
+
def load_configuration(directory)
|
155
|
+
Dir.glob(File.join(directory, '*.rb')).sort.each do |file|
|
156
|
+
require File.expand_path(file)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class << self
|
163
|
+
def raise_error(error_class: nil, message: nil, stack: nil)
|
164
|
+
error_class = RuntimeError unless error_class
|
165
|
+
execution_environment = if on_browser? then 'on Browser'
|
166
|
+
elsif on_ssr? then 'in Server Side Rendering'
|
167
|
+
elsif on_server? then 'on Server'
|
168
|
+
elsif on_mobile? then 'on Mobile'
|
169
|
+
elsif on_database? then 'on Database'
|
170
|
+
else
|
171
|
+
'on Client'
|
172
|
+
end
|
173
|
+
error = error_class.new("Isomorfeus in #{env} #{execution_environment}:\n#{message}")
|
174
|
+
error.set_backtrace(stack) if stack
|
175
|
+
|
176
|
+
if Isomorfeus.development?
|
177
|
+
if RUBY_ENGINE == 'opal'
|
178
|
+
ecn = error_class ? error_class.name : ''
|
179
|
+
m = message ? message : ''
|
180
|
+
s = stack ? stack : ''
|
181
|
+
`console.error(ecn, m, s)`
|
182
|
+
else
|
183
|
+
STDERR.puts "#{ecn}: #{m}\n #{s}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
raise error
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|