isomorfeus-asset-manager 0.12.3 → 0.12.7

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: 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