isomorfeus-asset-manager 0.12.1 → 0.12.5

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: 4322f1c6ad8dff9d4df265bc44715ed4bb057e031ac81d9d3a13e8f7eef885f7
4
- data.tar.gz: 3fb59bd726d4c25cd9f214b511d44759aea3e4af056e298ccd6afd70971c7ccf
3
+ metadata.gz: 118c1d13be80aba5339ecc42d8af01d22b1aa97ac929b8eb7915638ce28a3449
4
+ data.tar.gz: 8828cc73dc59ad93686d0e5c50a5df9da7ba25b9a1e8925537ce04b8bd359df3
5
5
  SHA512:
6
- metadata.gz: 9c73e59b221e26ff3d9e90920d02b6c6f47400e3808e36099d43d0f838090ba31d8231dbec1af43c5adddad62974a10be5b3560ed1a6e29c9adff6d38b6efda2
7
- data.tar.gz: bfbef83aa44a0763209e1cedab3cc754e066e630309feea7252ff7b81e7922c7ad274e1aa00e6b628554fb38519bc644898cb5e9d740b42479b5691c8424b3cf
6
+ metadata.gz: 3e8c8ebec132650ef035c830ee3dcf5caecf9a22cd7674c9cacc1ac673e66b2c75e29b4f414c2222d1fc25cbfc4609e3ab6ccb8f8806d3b4e2cd6aa7a3fd568f
7
+ data.tar.gz: 64de09f406887a737cfd1dc3e976cd4b911acf49cc92e2b64ce24cad185994b1b49ba05f51a5783230a61401d3f5cbab687b4c661f6a16ab4b348aa4c2b22b2d
@@ -6,11 +6,14 @@ module Isomorfeus
6
6
  attr_reader :bundle_map
7
7
  attr_reader :bundle_map_gz
8
8
  attr_reader :mtime, :ruby_imports, :target
9
+ attr_reader :mutex
9
10
 
10
11
  def initialize(target = :browser)
12
+ @mutex = Mutex.new
11
13
  raise "Unknown asset target!" unless %i[browser node].include?(target)
12
14
  @target = target
13
15
  @bundled = false
16
+ @ruby_compiled = false
14
17
  @js_imports = []
15
18
  @ruby_imports = []
16
19
  end
@@ -31,7 +34,6 @@ module Isomorfeus
31
34
  @bundle_gz = Zlib::gzip(b, level: Zlib::BEST_COMPRESSION)
32
35
  @bundle_gz_size = @bundle_gz.size
33
36
  end
34
- @mtime = Time.now
35
37
  @bundle
36
38
  end
37
39
 
@@ -39,7 +41,6 @@ module Isomorfeus
39
41
  @bundled = true
40
42
  @bundle_map = m
41
43
  @bundle_map_gz = Zlib::gzip(m, level: Zlib::BEST_COMPRESSION) unless @target == :node
42
- @mtime = Time.now unless @mtime
43
44
  @bundle_map
44
45
  end
45
46
 
@@ -51,6 +52,10 @@ module Isomorfeus
51
52
  @ruby_imports.map(&:module_name)
52
53
  end
53
54
 
55
+ def touch
56
+ @mtime = Time.now
57
+ end
58
+
54
59
  def to_s
55
60
  js = @target == :node ? '' : "if (typeof globalThis !== 'undefined' && typeof global === 'undefined') { globalThis.global = globalThis; }\n"
56
61
  unless @js_imports.empty?
@@ -58,6 +63,31 @@ module Isomorfeus
58
63
  end
59
64
  js << "\n" if !@js_imports.empty? && !@ruby_imports.empty?
60
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
61
91
  js << "#{@ruby_imports.map(&:to_s).join("\n")}"
62
92
  end
63
93
  js
@@ -1,17 +1,19 @@
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
12
+ attr_accessor :asset_manager_tmpdir
13
+ attr_accessor :asset_manager_hmr_channel
13
14
  attr_accessor :node_paths
14
15
  attr_accessor :assets
16
+ attr_accessor :hmr_listener
15
17
 
16
18
  def add_web_js_import(*args)
17
19
  Isomorfeus.assets['web.js'].add_js_import(*args)
@@ -61,9 +63,14 @@ module Isomorfeus
61
63
  end
62
64
  end
63
65
 
66
+ self.hmr_listener = nil
67
+ self.asset_manager_hmr_channel = :isomorfeus_asset_manager_module_updates
68
+ self.assets_websocket_path = '/_assets_websocket'
64
69
  self.assets_path = '/assets'
65
70
  self.assets = {
66
71
  'web.js' => Isomorfeus::AssetManager::Asset.new(:browser),
67
72
  'ssr.js' => Isomorfeus::AssetManager::Asset.new(:node)
68
73
  }
74
+ self.asset_manager_tmpdir = Dir.mktmpdir
75
+ Kernel.at_exit { FileUtils.rm_rf(Isomorfeus.asset_manager_tmpdir) if Dir.exist?(Isomorfeus.asset_manager_tmpdir) }
69
76
  end
@@ -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.1'
3
+ VERSION = '0.12.5'
4
4
  end
5
5
  end
@@ -1,13 +1,9 @@
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
 
10
- @tmpdir = Dir.mktmpdir
6
+ @tmpdir = Isomorfeus.asset_manager_tmpdir
11
7
  @imports_path = File.join(@tmpdir, 'imports')
12
8
  @output_path = File.join(@tmpdir, 'output')
13
9
  FileUtils.mkdir_p(@imports_path)
@@ -25,17 +21,19 @@ module Isomorfeus
25
21
  Isomorfeus.add_ssr_js_import(ssr_imports_file) if File.exist?(ssr_imports_file)
26
22
  end
27
23
 
28
- ObjectSpace.define_finalizer(self, self.class.finalize(@tmpdir))
24
+ init_hmr_listener if Isomorfeus.development? && !Isomorfeus.hmr_listener
29
25
  end
30
26
 
31
- def transition(asset_key, asset)
27
+ def transition(asset_key, asset, analyze: false)
32
28
  return if !Isomorfeus.development? && asset.bundled?
33
- compile_ruby_and_save(asset_key, asset)
34
- save_imports(asset_key, asset)
35
- run_esbuild(asset_key, asset)
36
- asset.bundle = bundled_asset(asset_key)
37
- if !Isomorfeus.production? && asset.target != :node
38
- asset.bundle_map = bundled_asset_map(asset_key)
29
+ asset.mutex.synchronize do
30
+ return if !Isomorfeus.development? && asset.bundled?
31
+ asset.touch
32
+ build_ruby_and_save(asset_key, asset)
33
+ save_imports(asset_key, asset)
34
+ run_esbuild(asset_key, asset, analyze)
35
+ asset.bundle = bundled_asset(asset_key)
36
+ asset.bundle_map = bundled_asset_map(asset_key) unless Isomorfeus.production?
39
37
  end
40
38
  end
41
39
 
@@ -53,20 +51,29 @@ module Isomorfeus
53
51
  File.write(File.join(@imports_path, asset_key), asset.to_s)
54
52
  end
55
53
 
56
- def compile_ruby_and_save(asset_key, asset)
54
+ def build_ruby_and_save(asset_key, asset)
57
55
  asset.ruby_imports.each do |ruby_import|
56
+ out_file = File.join(@imports_path, ruby_import.module_name + '.js')
57
+ next if !Isomorfeus.development? && File.exist?(out_file)
58
+ js_map_path = File.join(@imports_path, ruby_import.module_name + '.js.map')
58
59
  result = Opal::Builder.build(ruby_import.resolved_path, { es6_wrap: true })
59
60
  FileUtils.mkdir_p(File.join(*[@imports_path].concat(ruby_import.module_name.split('/')[0...-1]))) if ruby_import.module_name.include?('/')
60
61
  js = result.to_s
61
- if !Isomorfeus.production? && asset.target != :node
62
- js_map_path = File.join(@imports_path, ruby_import.module_name + '.js.map')
62
+ unless Isomorfeus.production?
63
63
  js << "\n//# sourceMappingURL=file://#{js_map_path}\n"
64
64
  File.write(js_map_path, result.source_map)
65
65
  end
66
- File.write(File.join(@imports_path, ruby_import.module_name + '.js'), js)
66
+ File.write(out_file, js)
67
67
  end
68
68
  end
69
69
 
70
+ def compile_ruby(file)
71
+ source = File.read(file)
72
+ module_file = file[(Isomorfeus.app_root.size + 1)..-1]
73
+ compiler = Opal::Compiler.new(source, requirable: true, file: module_file)
74
+ { javascript: compiler.compile }
75
+ end
76
+
70
77
  def print_message(m, level)
71
78
  l = m['location']
72
79
  STDERR.puts "#{l['file']}:#{l['line']}:#{l['column']}: #{level}: #{m['text']}"
@@ -74,17 +81,6 @@ module Isomorfeus
74
81
  end
75
82
 
76
83
  def handle_errors(asset_key, result)
77
- # Todo simplify
78
- unless result['warnings'].empty?
79
- result['warnings'].each do |w|
80
- print_message(w, 'warning')
81
- unless w['notes'].empty?
82
- w['notes'].each do |n|
83
- print_message(n, 'note')
84
- end
85
- end
86
- end
87
- end
88
84
  unless result['errors'].empty?
89
85
  result['errors'].each do |e|
90
86
  print_message(w, 'error')
@@ -94,7 +90,7 @@ module Isomorfeus
94
90
  end
95
91
  end
96
92
  end
97
- raise "Aseet Manager: error bundling '#{asset_key}'"
93
+ raise "Asset Manager: error bundling '#{asset_key}'"
98
94
  end
99
95
  end
100
96
 
@@ -111,34 +107,62 @@ module Isomorfeus
111
107
  JAVASCRIPT
112
108
  end
113
109
 
110
+ def init_hmr_listener
111
+ return unless Dir.exist?(Isomorfeus.app_root)
112
+ Isomorfeus.hmr_listener = Listen.to(Isomorfeus.app_root) do |modified, added, _|
113
+ (modified + added).each do |file|
114
+ begin
115
+ start = Time.now
116
+ update = compile_ruby(file)
117
+ update_json = Oj.dump(update, mode: :strict)
118
+ Iodine.publish(Isomorfeus.asset_manager_hmr_channel, update_json)
119
+ rescue Exception => e
120
+ message = "#{e.message}\n#{e.backtrace.join("\n")}"
121
+ STDERR.puts message
122
+ Iodine.publish(Isomorfeus.asset_manager_hmr_channel, Oj.dump({ error: message }, mode: :struct))
123
+ end
124
+ end
125
+ end
126
+ Isomorfeus.hmr_listener.start
127
+ end
128
+
114
129
  # possible future improvement
115
130
  # loader: {
116
131
  # '.png': 'dataurl',
117
132
  # '.svg': 'text',
118
133
  # },
119
- def run_esbuild(asset_key, asset)
134
+ def run_esbuild(asset_key, asset, analyze = false)
120
135
  resolve_paths = ENV['NODE_PATH'].split(Gem.win_platform? ? ';' : ':')
121
136
  resolve_paths << 'node_modules'
122
137
  resolve_paths.uniq!
123
138
 
124
139
  result = @context.exec <<~JAVASCRIPT
125
- return esbuild.buildSync({
140
+ let res = esbuild.buildSync({
126
141
  entryPoints: [path.resolve(global.imports_path, '#{asset_key}')],
127
142
  bundle: true,
128
143
  color: false,
129
144
  format: '#{asset.target == :node ? 'cjs' : 'iife'}',
130
145
  legalComments: 'linked',
146
+ metafile: true,
131
147
  minify: #{Isomorfeus.production? ? 'true' : 'false'},
132
148
  nodePaths: #{resolve_paths},
133
149
  outdir: global.output_path,
134
150
  platform: '#{asset.target}',
135
151
  publicPath: '/assets',
136
- sourcemap: #{(!Isomorfeus.production? && asset.target != :node) ? 'true' : 'false'},
152
+ sourcemap: #{!Isomorfeus.production? ? 'true' : 'false'},
137
153
  splitting: false,
138
154
  target: 'es6',
139
155
  write: true
140
156
  });
157
+ global.res_meta = res.metafile;
158
+ return res;
141
159
  JAVASCRIPT
160
+ if analyze
161
+ analysis = @context.await <<~JAVASCRIPT
162
+ esbuild.analyzeMetafile(global.res_meta, { verbose: true });
163
+ JAVASCRIPT
164
+ STDOUT.puts "Bundle analysis for #{asset_key}:\n#{analysis}\n"
165
+ end
142
166
  handle_errors(asset_key, result)
143
167
  end
144
168
  end