isomorfeus-asset-manager 0.12.3 → 0.12.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f575fac540b8916e9bdb3c48d41b94ec54397e02825211f43b7f199babab387
4
- data.tar.gz: daacbb919e74341f9a2d7a8403656c1c50854c934fae45e50aab444e07aeab3e
3
+ metadata.gz: 6f2bba888a2b50846a6384f6b5a04036b3e5e7e9e367f867b4cb23996ceb8ba1
4
+ data.tar.gz: 2ef547ce6fb466c561b1c4964d561c2c5612a2a641179b1dfdb33f555142f6b0
5
5
  SHA512:
6
- metadata.gz: bd0528ded14edd3a7fdd0d8fbd92c7d52c0d9e68c14c6c4ca1637ca966cceec25f3220f98b963aab961b44383f4e73dac8b23db2f023ccb59f999094969e5881
7
- data.tar.gz: 0f6524ae7a5210e36a83565cda386e32254c468fb24b83202bf038099578b159097e62d9daa1d513e71d346f5b5318deba4ccd634318cc92c5dddc4bacc33b89
6
+ metadata.gz: 5b65f19ea35eb669fc45e946afe44b7da36b75910d28b69c4f9fdd9657f23c446c0f22e5fb505bf78755537428f91dcddd391edd4d818f9cabc8b2b3ca115e68
7
+ data.tar.gz: 7acea3a93903a46f1d59ae45f83617c619812836015c58bbbab9b7ebf1e24c21708b013021eb1898ab9cfcdafaf991ae0f821db6dddbd256097a170118a0a133
@@ -63,6 +63,31 @@ module Isomorfeus
63
63
  end
64
64
  js << "\n" if !@js_imports.empty? && !@ruby_imports.empty?
65
65
  unless @ruby_imports.empty?
66
+ if Isomorfeus.development? && @target == :browser
67
+ js << <<~JAVASCRIPT
68
+ // Isomorfeus Asset Manager HMR code begin
69
+ let ws_protocol = (window.location.protocol == 'https:') ? 'wss:' : 'ws:';
70
+ let ws_url = ws_protocol + '//' + window.location.host + "#{Isomorfeus.assets_websocket_path}";
71
+ let hmr_ws = new WebSocket(ws_url);
72
+ hmr_ws.onmessage = function(event) {
73
+ let update = JSON.parse(event.data);
74
+ if (typeof update.error !== 'undefined') { console.error(update.error); return; }
75
+ let start_index = '/* Generated by Opal 1.2.0 */\\nOpal.modules[\\"'.length;
76
+ let end_index = update.javascript.indexOf('"', start_index);
77
+ let opal_module_name = update.javascript.substr(start_index, end_index - start_index);
78
+ console.log('Updating ', opal_module_name);
79
+ if (typeof Opal !== 'undefined' && typeof Opal.require_table !== "undefined" && Opal.require_table['corelib/module']) {
80
+ try {
81
+ eval(update.javascript);
82
+ if (Opal.require_table[opal_module_name]) { Opal.load.call(Opal, opal_module_name); }
83
+ else { Opal.require.call(Opal, opal_module_name); }
84
+ Opal.Isomorfeus.$force_render();
85
+ } catch (e) { console.error(e); return; }
86
+ }
87
+ }
88
+ // Isomorfeus Asset Manager HMR code end
89
+ JAVASCRIPT
90
+ end
66
91
  js << "#{@ruby_imports.map(&:to_s).join("\n")}"
67
92
  end
68
93
  js
@@ -1,18 +1,20 @@
1
1
  module Isomorfeus
2
2
  # available settings
3
3
  if RUBY_ENGINE == 'opal'
4
- add_client_option(:assets_websocket_path)
5
4
  add_client_option(:assets_path)
6
- add_client_option(:opal_assets_path)
7
5
  else
8
6
  class << self
9
7
  attr_reader :env
10
8
  attr_accessor :root
11
9
  attr_accessor :app_root
12
10
  attr_accessor :assets_path
11
+ attr_accessor :assets_websocket_path
13
12
  attr_accessor :asset_manager_tmpdir
13
+ attr_accessor :asset_manager_hmr_channel
14
+ attr_accessor :asset_manager_hmr_dirs
14
15
  attr_accessor :node_paths
15
16
  attr_accessor :assets
17
+ attr_accessor :hmr_listener
16
18
 
17
19
  def add_web_js_import(*args)
18
20
  Isomorfeus.assets['web.js'].add_js_import(*args)
@@ -62,6 +64,10 @@ module Isomorfeus
62
64
  end
63
65
  end
64
66
 
67
+ self.hmr_listener = nil
68
+ self.asset_manager_hmr_channel = :isomorfeus_asset_manager_module_updates
69
+ self.asset_manager_hmr_dirs = %w[channels, components, data, operations, policies]
70
+ self.assets_websocket_path = '/_assets_websocket'
65
71
  self.assets_path = '/assets'
66
72
  self.assets = {
67
73
  'web.js' => Isomorfeus::AssetManager::Asset.new(:browser),
@@ -3,10 +3,11 @@
3
3
  module Isomorfeus
4
4
  class AssetManager
5
5
  class RackMiddleware
6
- WS_RESPONSE = [0, {}, []]
6
+ WS_RESPONSE = [0, {}, []].freeze
7
7
  attr_reader :asset_manager
8
8
 
9
9
  def initialize(app)
10
+ STDERR.puts "asset manager"
10
11
  @app = app
11
12
  @asset_manager = Isomorfeus::AssetManager.new
12
13
  @compressible_types = %w[application/javascript text/javascript]
@@ -17,67 +18,70 @@ module Isomorfeus
17
18
  @assets_path = Isomorfeus.assets_path + '/'
18
19
  @assets_path_size = @assets_path.size
19
20
  end
21
+ rescue Exception => e
22
+ STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}\n"
20
23
  end
21
24
 
22
25
  def call(env)
23
- begin
24
- path_info = env['PATH_INFO']
25
- if path_info.start_with?(@assets_path)
26
- asset_key = path_info[@assets_path_size..-1]
26
+ path_info = env['PATH_INFO']
27
+ if path_info.start_with?(@assets_path)
28
+ asset_key = path_info[@assets_path_size..-1]
27
29
 
28
- # get js
29
- if Isomorfeus.assets.has_key?(asset_key)
30
- asset = Isomorfeus.assets[asset_key]
31
- if asset && asset.target != :node
32
- asset_manager.transition(asset_key, asset)
33
- headers = {}
34
- headers['Last-Modified'] = asset.mtime
35
- headers[Rack::CONTENT_TYPE] = 'application/javascript'
36
- if should_gzip?(env)
37
- headers['Content-Encoding'] = "gzip"
38
- headers[Rack::CONTENT_LENGTH] = asset.bundle_gz_size
39
- return [200, headers, asset.bundle_gz]
40
- else
41
- headers[Rack::CONTENT_LENGTH] = asset.bundle_size
42
- return [200, {}, asset.bundle]
43
- end
44
- end
45
-
46
- # get source map
47
- elsif asset_key.end_with?('.js.map')
48
- asset = Isomorfeus.assets[asset_key[0..-5]]
49
- if asset && asset.target != :node
50
- asset_manager.transition(asset_key, asset) unless asset.bundled?
51
- headers = {}
52
- headers['Last-Modified'] = asset.mtime
53
- headers[Rack::CONTENT_TYPE] = 'application/json'
54
- if should_gzip?(env)
55
- headers['Content-Encoding'] = "gzip"
56
- headers[Rack::CONTENT_LENGTH] = asset.bundle_map_gz.size
57
- return [200, headers, asset.bundle_map_gz]
58
- else
59
- headers[Rack::CONTENT_LENGTH] = asset.bundle_map.size
60
- return [200, {}, asset.bundle_map]
61
- end
62
- end
30
+ # get js
31
+ if Isomorfeus.assets.key?(asset_key)
32
+ asset = Isomorfeus.assets[asset_key]
33
+ if asset && asset.target != :node
34
+ asset_manager.transition(asset_key, asset)
35
+ headers = {}
36
+ headers['Last-Modified'] = asset.mtime
37
+ headers[Rack::CONTENT_TYPE] = 'application/javascript'
38
+ if should_gzip?(env)
39
+ headers['Content-Encoding'] = "gzip"
40
+ headers[Rack::CONTENT_LENGTH] = asset.bundle_gz_size
41
+ return [200, headers, asset.bundle_gz]
42
+ else
43
+ headers[Rack::CONTENT_LENGTH] = asset.bundle_size
44
+ return [200, {}, asset.bundle]
45
+ end
63
46
  end
64
- # elsif Isomorfeus.development? && path_info == Isomorfeus.api_asset_hmr_socket_path
65
- # if env['rack.upgrade?'] == :websocket
66
- # env['rack.upgrade'] = Isomorfeus::AssetManager::ServerSocketProcessor.new
67
- # end
68
- # WS_RESPONSE
47
+
48
+ # get source map
49
+ elsif asset_key.end_with?('.js.map')
50
+ asset = Isomorfeus.assets[asset_key[0..-5]]
51
+ if asset && asset.target != :node
52
+ asset_manager.transition(asset_key, asset) unless asset.bundled?
53
+ headers = {}
54
+ headers['Last-Modified'] = asset.mtime
55
+ headers[Rack::CONTENT_TYPE] = 'application/json'
56
+ if should_gzip?(env)
57
+ headers['Content-Encoding'] = "gzip"
58
+ headers[Rack::CONTENT_LENGTH] = asset.bundle_map_gz.size
59
+ return [200, headers, asset.bundle_map_gz]
60
+ else
61
+ headers[Rack::CONTENT_LENGTH] = asset.bundle_map.size
62
+ return [200, {}, asset.bundle_map]
63
+ end
64
+ end
65
+ end
66
+
67
+ # hot reloading subscription
68
+ elsif Isomorfeus.development? && path_info == Isomorfeus.assets_websocket_path
69
+ if env['rack.upgrade?'] == :websocket
70
+ env['rack.upgrade'] = Isomorfeus::AssetManager::ServerSocketProcessor.new
69
71
  end
70
- rescue Exception => e
71
- STDERR.puts "#{e}\n#{e.backtrace.join("\n")}"
72
+ WS_RESPONSE
73
+ else
72
74
  @app.call(env)
73
75
  end
76
+ rescue Exception => e
77
+ STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}\n"
74
78
  @app.call(env)
75
79
  end
76
80
 
77
81
  def should_gzip?(env)
78
- return true if env.has_key?('HTTP_ACCEPT_ENCODING') && env['HTTP_ACCEPT_ENCODING'].match?(/\bgzip\b/)
82
+ return true if env.key?('HTTP_ACCEPT_ENCODING') && env['HTTP_ACCEPT_ENCODING'].match?(/\bgzip\b/)
79
83
  return false if /\bno-transform\b/.match?(env[Rack::CACHE_CONTROL].to_s) || env['Content-Encoding']&.!~(/\bidentity\b/)
80
- return false if @compressible_types && !(env.has_key?(Rack::CONTENT_TYPE) && @compressible_types.include?(env[Rack::CONTENT_TYPE][/[^;]*/]))
84
+ return false if @compressible_types && !(env.key?(Rack::CONTENT_TYPE) && @compressible_types.include?(env[Rack::CONTENT_TYPE][/[^;]*/]))
81
85
  true
82
86
  end
83
87
  end
@@ -1,55 +1,21 @@
1
- module Isomorfeus
2
- class AssetManager
3
- class ServerSocketProcessor
4
- # include Isomorfeus::Transport::ServerProcessor
5
-
6
- # def on_message(client, data)
7
- # # TODO
8
- # if Isomorfeus.development?
9
- # write_lock = Isomorfeus.zeitwerk_lock.try_write_lock
10
- # if write_lock
11
- # Isomorfeus.zeitwerk.reload
12
- # Isomorfeus.zeitwerk_lock.release_write_lock
13
- # end
14
- # Isomorfeus.zeitwerk_lock.acquire_read_lock
15
- # end
16
- # request_hash = Oj.load(data, mode: :strict)
17
- # handler_instance_cache = {}
18
- # response_agent_array = []
19
- # Thread.current[:isomorfeus_user] = user(client)
20
- # Thread.current[:isomorfeus_pub_sub_client] = client
21
- # process_request(request_hash, handler_instance_cache, response_agent_array)
22
- # handler_instance_cache.each_value do |handler|
23
- # handler.resolve if handler.resolving?
24
- # end
25
- # result = {}
26
- # response_agent_array.each do |response_agent|
27
- # result.deep_merge!(response_agent.result)
28
- # end
29
- # client.write Oj.dump(result, mode: :strict) unless result.empty?
30
- # ensure
31
- # Thread.current[:isomorfeus_user] = nil
32
- # Thread.current[:isomorfeus_pub_sub_client] = nil
33
- # Isomorfeus.zeitwerk_lock.release_read_lock if Isomorfeus.development?
34
- # end
35
-
36
- # def on_close(client)
37
- # # nothing for now
38
- # end
39
-
40
- # def on_open(client)
41
- # # nothing for now
42
- # end
43
-
44
- # def on_shutdown(client)
45
- # # nothing for now
46
- # end
47
-
48
- # def user(client)
49
- # current_user = client.instance_variable_get(:@isomorfeus_user)
50
- # return current_user if current_user
51
- # Anonymous.new
52
- # end
53
- end
54
- end
55
- end
1
+ module Isomorfeus
2
+ class AssetManager
3
+ class ServerSocketProcessor
4
+ def on_message(client, data)
5
+ # nothing for now
6
+ end
7
+
8
+ def on_close(client)
9
+ # client.unsubscribe Isomorfeus.asset_manager_hmr_channel
10
+ end
11
+
12
+ def on_open(client)
13
+ client.subscribe Isomorfeus.asset_manager_hmr_channel
14
+ end
15
+
16
+ def on_shutdown(client)
17
+ # nothing for now
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  class AssetManager
3
- VERSION = '0.12.3'
3
+ VERSION = '0.12.7'
4
4
  end
5
5
  end
@@ -8,6 +8,7 @@ module Isomorfeus
8
8
  @output_path = File.join(@tmpdir, 'output')
9
9
  FileUtils.mkdir_p(@imports_path)
10
10
  FileUtils.mkdir_p(@output_path)
11
+ @server_path = File.join(Isomorfeus.app_root, 'server')
11
12
 
12
13
  @context = ExecJS.permissive_compile(init_js)
13
14
 
@@ -20,16 +21,18 @@ module Isomorfeus
20
21
  ssr_imports_file = File.join(imports_path, 'ssr.js')
21
22
  Isomorfeus.add_ssr_js_import(ssr_imports_file) if File.exist?(ssr_imports_file)
22
23
  end
24
+
25
+ init_hmr_listener if Isomorfeus.development? && !Isomorfeus.hmr_listener
23
26
  end
24
27
 
25
- def transition(asset_key, asset)
28
+ def transition(asset_key, asset, analyze: false)
26
29
  return if !Isomorfeus.development? && asset.bundled?
27
30
  asset.mutex.synchronize do
28
31
  return if !Isomorfeus.development? && asset.bundled?
29
32
  asset.touch
30
- compile_ruby_and_save(asset_key, asset)
33
+ build_ruby_and_save(asset_key, asset)
31
34
  save_imports(asset_key, asset)
32
- run_esbuild(asset_key, asset)
35
+ run_esbuild(asset_key, asset, analyze)
33
36
  asset.bundle = bundled_asset(asset_key)
34
37
  asset.bundle_map = bundled_asset_map(asset_key) unless Isomorfeus.production?
35
38
  end
@@ -49,12 +52,12 @@ module Isomorfeus
49
52
  File.write(File.join(@imports_path, asset_key), asset.to_s)
50
53
  end
51
54
 
52
- def compile_ruby_and_save(asset_key, asset)
55
+ def build_ruby_and_save(asset_key, asset)
53
56
  asset.ruby_imports.each do |ruby_import|
54
57
  out_file = File.join(@imports_path, ruby_import.module_name + '.js')
55
58
  next if !Isomorfeus.development? && File.exist?(out_file)
56
59
  js_map_path = File.join(@imports_path, ruby_import.module_name + '.js.map')
57
- result = Opal::Builder.build(ruby_import.resolved_path, { es6_wrap: true })
60
+ result = Opal::Builder.build(ruby_import.resolved_path, { esm: true })
58
61
  FileUtils.mkdir_p(File.join(*[@imports_path].concat(ruby_import.module_name.split('/')[0...-1]))) if ruby_import.module_name.include?('/')
59
62
  js = result.to_s
60
63
  unless Isomorfeus.production?
@@ -65,6 +68,13 @@ module Isomorfeus
65
68
  end
66
69
  end
67
70
 
71
+ def compile_ruby(file)
72
+ source = File.read(file)
73
+ module_file = file[(Isomorfeus.app_root.size + 1)..-1]
74
+ compiler = Opal::Compiler.new(source, requirable: true, file: module_file)
75
+ { javascript: compiler.compile }
76
+ end
77
+
68
78
  def print_message(m, level)
69
79
  l = m['location']
70
80
  STDERR.puts "#{l['file']}:#{l['line']}:#{l['column']}: #{level}: #{m['text']}"
@@ -72,17 +82,6 @@ module Isomorfeus
72
82
  end
73
83
 
74
84
  def handle_errors(asset_key, result)
75
- # Todo simplify
76
- unless result['warnings'].empty?
77
- result['warnings'].each do |w|
78
- print_message(w, 'warning')
79
- unless w['notes'].empty?
80
- w['notes'].each do |n|
81
- print_message(n, 'note')
82
- end
83
- end
84
- end
85
- end
86
85
  unless result['errors'].empty?
87
86
  result['errors'].each do |e|
88
87
  print_message(w, 'error')
@@ -92,7 +91,7 @@ module Isomorfeus
92
91
  end
93
92
  end
94
93
  end
95
- raise "Aseet Manager: error bundling '#{asset_key}'"
94
+ raise "Asset Manager: error bundling '#{asset_key}'"
96
95
  end
97
96
  end
98
97
 
@@ -109,23 +108,52 @@ module Isomorfeus
109
108
  JAVASCRIPT
110
109
  end
111
110
 
111
+ def init_hmr_listener
112
+ return unless Dir.exist?(Isomorfeus.app_root)
113
+ listen_dirs = []
114
+ Isomorfeus.asset_manager_hmr_dirs.each do |dir|
115
+ if File.absolute_path?(dir)
116
+ listen_dirs << dir if Dir.exist?(dir)
117
+ else
118
+ listen_dir = File.join(Isomorfeus.app_root, dir)
119
+ listen_dirs << listen_dir if Dir.exist?(listen_dir)
120
+ end
121
+ end
122
+ Isomorfeus.hmr_listener = Listen.to(*listen_dirs) do |modified, added, _|
123
+ (modified + added).each do |file|
124
+ next if file.start_with?(@server_path)
125
+ begin
126
+ update = compile_ruby(file)
127
+ update_json = Oj.dump(update, mode: :strict)
128
+ Iodine.publish(Isomorfeus.asset_manager_hmr_channel, update_json)
129
+ rescue Exception => e
130
+ message = "#{e.message}\n#{e.backtrace.join("\n")}"
131
+ STDERR.puts message
132
+ Iodine.publish(Isomorfeus.asset_manager_hmr_channel, Oj.dump({ error: message }, mode: :struct))
133
+ end
134
+ end
135
+ end
136
+ Isomorfeus.hmr_listener.start
137
+ end
138
+
112
139
  # possible future improvement
113
140
  # loader: {
114
141
  # '.png': 'dataurl',
115
142
  # '.svg': 'text',
116
143
  # },
117
- def run_esbuild(asset_key, asset)
144
+ def run_esbuild(asset_key, asset, analyze = false)
118
145
  resolve_paths = ENV['NODE_PATH'].split(Gem.win_platform? ? ';' : ':')
119
146
  resolve_paths << 'node_modules'
120
147
  resolve_paths.uniq!
121
148
 
122
149
  result = @context.exec <<~JAVASCRIPT
123
- return esbuild.buildSync({
150
+ let res = esbuild.buildSync({
124
151
  entryPoints: [path.resolve(global.imports_path, '#{asset_key}')],
125
152
  bundle: true,
126
153
  color: false,
127
154
  format: '#{asset.target == :node ? 'cjs' : 'iife'}',
128
155
  legalComments: 'linked',
156
+ metafile: true,
129
157
  minify: #{Isomorfeus.production? ? 'true' : 'false'},
130
158
  nodePaths: #{resolve_paths},
131
159
  outdir: global.output_path,
@@ -136,7 +164,15 @@ module Isomorfeus
136
164
  target: 'es6',
137
165
  write: true
138
166
  });
167
+ global.res_meta = res.metafile;
168
+ return res;
139
169
  JAVASCRIPT
170
+ if analyze
171
+ analysis = @context.await <<~JAVASCRIPT
172
+ esbuild.analyzeMetafile(global.res_meta, { verbose: true });
173
+ JAVASCRIPT
174
+ STDOUT.puts "Bundle analysis for #{asset_key}:\n#{analysis}\n"
175
+ end
140
176
  handle_errors(asset_key, result)
141
177
  end
142
178
  end