class Isomorfeus::AssetManager

Constants

VERSION

Public Class Methods

new() click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 3
def initialize
  Isomorfeus.set_node_paths

  @tmpdir = Isomorfeus.asset_manager_tmpdir
  @imports_path = File.join(@tmpdir, 'imports')
  @output_path = File.join(@tmpdir, 'output')
  FileUtils.mkdir_p(@imports_path)
  FileUtils.mkdir_p(@output_path)
  @server_path = File.join(Isomorfeus.app_root, 'server')

  @context = ExecJS.permissive_compile(init_js)

  imports_path = File.expand_path(File.join(Isomorfeus.app_root, 'imports'))
  if Dir.exist?(imports_path)
    common_imports_file = File.join(imports_path, 'common.js')
    Isomorfeus.add_common_js_import(common_imports_file) if File.exist?(common_imports_file)
    web_imports_file = File.join(imports_path, 'web.js')
    Isomorfeus.add_web_js_import(web_imports_file) if File.exist?(web_imports_file)
    ssr_imports_file = File.join(imports_path, 'ssr.js')
    Isomorfeus.add_ssr_js_import(ssr_imports_file) if File.exist?(ssr_imports_file)
  end

  init_hmr_listener if Isomorfeus.development? && !Isomorfeus.hmr_listener
end

Public Instance Methods

transition(asset_key, asset, analyze: false) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 28
def transition(asset_key, asset, analyze: false)
  return if !Isomorfeus.development? && asset.bundled?
  asset.mutex.synchronize do
    return if !Isomorfeus.development? && asset.bundled?
    asset.touch
    build_ruby_and_save(asset_key, asset)
    save_imports(asset_key, asset)
    run_esbuild(asset_key, asset, analyze)
    asset.bundle = bundled_asset(asset_key)
    asset.bundle_map = bundled_asset_map(asset_key) unless Isomorfeus.production?
  end
end

Private Instance Methods

build_ruby_and_save(asset_key, asset) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 55
def build_ruby_and_save(asset_key, asset)
  asset.ruby_imports.each do |ruby_import|
    out_file = File.join(@imports_path, ruby_import.module_name + '.js')
    next if !Isomorfeus.development? && File.exist?(out_file)
    js_map_path = File.join(@imports_path, ruby_import.module_name + '.js.map')
    result = Opal::Builder.build(ruby_import.resolved_path, { esm: true })
    FileUtils.mkdir_p(File.join(*[@imports_path].concat(ruby_import.module_name.split('/')[0...-1]))) if ruby_import.module_name.include?('/')
    js = result.to_s
    unless Isomorfeus.production?
      js << "\n//# sourceMappingURL=file://#{js_map_path}\n"
      File.write(js_map_path, result.source_map)
    end
    File.write(out_file, js)
  end
end
bundled_asset(asset_key) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 43
def bundled_asset(asset_key)
  File.read(File.join(@output_path, asset_key))
end
bundled_asset_map(asset_key) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 47
def bundled_asset_map(asset_key)
  File.read(File.join(@output_path, asset_key + '.map'))
end
compile_ruby(file) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 71
def compile_ruby(file)
  source = File.read(file)
  module_file = file[(Isomorfeus.app_root.size + 1)..-1]
  compiler = Opal::Compiler.new(source, requirable: true, file: module_file)
  { javascript: compiler.compile }
end
handle_errors(asset_key, result) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 84
def handle_errors(asset_key, result)
  unless result['errors'].empty?
    result['errors'].each do |e|
      print_message(w, 'error')
      unless w['notes'].empty?
        e['notes'].each do |n|
          print_message(n, 'note')
        end
      end
    end
    raise "Asset Manager: error bundling '#{asset_key}'" 
  end
end
init_hmr_listener() click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 111
def init_hmr_listener
  return unless Dir.exist?(Isomorfeus.app_root)
  listen_dirs = []
  Isomorfeus.asset_manager_hmr_dirs.each do |dir|
    if File.absolute_path?(dir)
      listen_dirs << dir if Dir.exist?(dir)
    else
      listen_dir = File.join(Isomorfeus.app_root, dir)
      listen_dirs << listen_dir if Dir.exist?(listen_dir)
    end
  end
  Isomorfeus.hmr_listener = Listen.to(*listen_dirs) do |modified, added, _|
    (modified + added).each do |file|
      next if file.start_with?(@server_path)
      begin
        update = compile_ruby(file)
        update_json = Oj.dump(update, mode: :strict)
        Iodine.publish(Isomorfeus.asset_manager_hmr_channel, update_json)
      rescue Exception => e
        message = "#{e.message}\n#{e.backtrace.join("\n")}"
        STDERR.puts message
        Iodine.publish(Isomorfeus.asset_manager_hmr_channel, Oj.dump({ error: message }, mode: :struct))
      end
    end
  end
  Isomorfeus.hmr_listener.start
end
init_js() click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 98
    def init_js
      <<~JAVASCRIPT
      'use strict';
      const path = require('path');
      var esbuild_r;
      try { esbuild_r = require('esbuild'); }
      catch { esbuild_r = require('esbuild-wasm'); }
      const esbuild = esbuild_r;
      global.imports_path = '#{@imports_path}';
      global.output_path = '#{@output_path}';
      JAVASCRIPT
    end
print_message(m, level) click to toggle source
run_esbuild(asset_key, asset, analyze = false) click to toggle source

possible future improvement loader: {

'.png': 'dataurl',
'.svg': 'text',

},

# File lib/isomorfeus/asset_manager.rb, line 144
    def run_esbuild(asset_key, asset, analyze = false)
      resolve_paths = ENV['NODE_PATH'].split(Gem.win_platform? ? ';' : ':')
      resolve_paths << 'node_modules'
      resolve_paths.uniq!

      result = @context.exec <<~JAVASCRIPT
      let res = esbuild.buildSync({
        entryPoints: [path.resolve(global.imports_path, '#{asset_key}')],
        bundle: true,
        color: false,
        format: '#{asset.target == :node ? 'cjs' : 'iife'}',
        legalComments: 'linked',
        metafile: true,
        minify: #{Isomorfeus.production? ? 'true' : 'false'},
        nodePaths: #{resolve_paths},
        outdir: global.output_path,
        platform: '#{asset.target}',
        publicPath: '/assets',
        sourcemap: #{!Isomorfeus.production? ? 'true' : 'false'},
        splitting: false,
        target: 'es6',
        write: true
      });
      global.res_meta = res.metafile;
      return res;
      JAVASCRIPT
      if analyze
        analysis = @context.await <<~JAVASCRIPT
        esbuild.analyzeMetafile(global.res_meta, { verbose: true });
        JAVASCRIPT
        STDOUT.puts "Bundle analysis for #{asset_key}:\n#{analysis}\n"
      end
      handle_errors(asset_key, result)
    end
save_imports(asset_key, asset) click to toggle source
# File lib/isomorfeus/asset_manager.rb, line 51
def save_imports(asset_key, asset)
  File.write(File.join(@imports_path, asset_key), asset.to_s)
end