isomorfeus-preact 10.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|