isomorfeus-asset-manager 0.12.2 → 0.12.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 169aeadc71daafe7fa04cdb85aea6c6c00e4827624c1a85a89bc7c0555a063c3
4
- data.tar.gz: 622b5c1979563fb4d5bb7a1d064bc8cebbdb41be5626cc8d7a234e15f21bbd24
3
+ metadata.gz: 748ed2bb10bab68fad00bba234cc0175bc5d570d4e5173da53190922d2cfa413
4
+ data.tar.gz: 905ce53e7fefea4a9d69475c370d507e876348e38f7e296fd53b12f6c7b3923a
5
5
  SHA512:
6
- metadata.gz: 59b5412998db5107f023cde6368ca9137f328f54fda0b8e0b10b8871a2db0936371e422ee86ee35fb55c99b15fa86a0a6db3fd2f99a65d488c4dce73df25242d
7
- data.tar.gz: 741c7cab68752ff9c7fb13dc4a3dfa924a9e884bb94f587b1a237ad9dbdb0239174f43b94bb8bbabd73e15e7529bd739973bff9b3640c55351f464a0658f7cfd
6
+ metadata.gz: dae52b87da0a0da3f1808ae5d113632b5f7262701e58e399d8eb6b8ee4d45b317f285a03efda3df8ab9f6f2e1387c04c6a1e286b294214a9fb16cbb610725381
7
+ data.tar.gz: d8b04e26aeb39f2cd9abaf2f4f107a8a9d253f87b332f014fdad41cd487050c851301ebf8e01ef1a7a9f804134fab08a7b5eba22d12a9ccbdb16076d5e360bc8
@@ -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.2'
3
+ VERSION = '0.12.6'
4
4
  end
5
5
  end
@@ -1,9 +1,5 @@
1
1
  module Isomorfeus
2
2
  class AssetManager
3
- def self.finalize(tmpdir)
4
- proc { FileUtils.rm_rf(tmpdir) }
5
- end
6
-
7
3
  def initialize
8
4
  Isomorfeus.set_node_paths
9
5
 
@@ -12,6 +8,7 @@ module Isomorfeus
12
8
  @output_path = File.join(@tmpdir, 'output')
13
9
  FileUtils.mkdir_p(@imports_path)
14
10
  FileUtils.mkdir_p(@output_path)
11
+ @server_path = File.join(Isomorfeus.app_root, 'server')
15
12
 
16
13
  @context = ExecJS.permissive_compile(init_js)
17
14
 
@@ -25,17 +22,17 @@ module Isomorfeus
25
22
  Isomorfeus.add_ssr_js_import(ssr_imports_file) if File.exist?(ssr_imports_file)
26
23
  end
27
24
 
28
- ObjectSpace.define_finalizer(self, self.class.finalize(@tmpdir))
25
+ init_hmr_listener if Isomorfeus.development? && !Isomorfeus.hmr_listener
29
26
  end
30
27
 
31
- def transition(asset_key, asset)
28
+ def transition(asset_key, asset, analyze: false)
32
29
  return if !Isomorfeus.development? && asset.bundled?
33
30
  asset.mutex.synchronize do
34
31
  return if !Isomorfeus.development? && asset.bundled?
35
32
  asset.touch
36
- compile_ruby_and_save(asset_key, asset)
33
+ build_ruby_and_save(asset_key, asset)
37
34
  save_imports(asset_key, asset)
38
- run_esbuild(asset_key, asset)
35
+ run_esbuild(asset_key, asset, analyze)
39
36
  asset.bundle = bundled_asset(asset_key)
40
37
  asset.bundle_map = bundled_asset_map(asset_key) unless Isomorfeus.production?
41
38
  end
@@ -55,7 +52,7 @@ module Isomorfeus
55
52
  File.write(File.join(@imports_path, asset_key), asset.to_s)
56
53
  end
57
54
 
58
- def compile_ruby_and_save(asset_key, asset)
55
+ def build_ruby_and_save(asset_key, asset)
59
56
  asset.ruby_imports.each do |ruby_import|
60
57
  out_file = File.join(@imports_path, ruby_import.module_name + '.js')
61
58
  next if !Isomorfeus.development? && File.exist?(out_file)
@@ -71,6 +68,13 @@ module Isomorfeus
71
68
  end
72
69
  end
73
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
+
74
78
  def print_message(m, level)
75
79
  l = m['location']
76
80
  STDERR.puts "#{l['file']}:#{l['line']}:#{l['column']}: #{level}: #{m['text']}"
@@ -78,17 +82,6 @@ module Isomorfeus
78
82
  end
79
83
 
80
84
  def handle_errors(asset_key, result)
81
- # Todo simplify
82
- unless result['warnings'].empty?
83
- result['warnings'].each do |w|
84
- print_message(w, 'warning')
85
- unless w['notes'].empty?
86
- w['notes'].each do |n|
87
- print_message(n, 'note')
88
- end
89
- end
90
- end
91
- end
92
85
  unless result['errors'].empty?
93
86
  result['errors'].each do |e|
94
87
  print_message(w, 'error')
@@ -98,7 +91,7 @@ module Isomorfeus
98
91
  end
99
92
  end
100
93
  end
101
- raise "Aseet Manager: error bundling '#{asset_key}'"
94
+ raise "Asset Manager: error bundling '#{asset_key}'"
102
95
  end
103
96
  end
104
97
 
@@ -115,23 +108,52 @@ module Isomorfeus
115
108
  JAVASCRIPT
116
109
  end
117
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
+
118
139
  # possible future improvement
119
140
  # loader: {
120
141
  # '.png': 'dataurl',
121
142
  # '.svg': 'text',
122
143
  # },
123
- def run_esbuild(asset_key, asset)
144
+ def run_esbuild(asset_key, asset, analyze = false)
124
145
  resolve_paths = ENV['NODE_PATH'].split(Gem.win_platform? ? ';' : ':')
125
146
  resolve_paths << 'node_modules'
126
147
  resolve_paths.uniq!
127
148
 
128
149
  result = @context.exec <<~JAVASCRIPT
129
- return esbuild.buildSync({
150
+ let res = esbuild.buildSync({
130
151
  entryPoints: [path.resolve(global.imports_path, '#{asset_key}')],
131
152
  bundle: true,
132
153
  color: false,
133
154
  format: '#{asset.target == :node ? 'cjs' : 'iife'}',
134
155
  legalComments: 'linked',
156
+ metafile: true,
135
157
  minify: #{Isomorfeus.production? ? 'true' : 'false'},
136
158
  nodePaths: #{resolve_paths},
137
159
  outdir: global.output_path,
@@ -142,7 +164,15 @@ module Isomorfeus
142
164
  target: 'es6',
143
165
  write: true
144
166
  });
167
+ global.res_meta = res.metafile;
168
+ return res;
145
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
146
176
  handle_errors(asset_key, result)
147
177
  end
148
178
  end