From ea13263408b715ed1ff9189e4cfc0ce6049a5331 Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Wed, 5 Feb 2025 11:10:03 -0600 Subject: [PATCH] Add import helper to load ESM modules --- lib/nodo/core.rb | 10 ++++++++-- lib/nodo/dependency.rb | 33 +++++++++++++++++++++++++++----- lib/nodo/nodo.cjs | 8 ++++++-- test/nodo_test.rb | 43 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/lib/nodo/core.rb b/lib/nodo/core.rb index e0b139e..0b6a6a8 100644 --- a/lib/nodo/core.rb +++ b/lib/nodo/core.rb @@ -82,7 +82,7 @@ def generate_core_code def generate_class_code <<~JS - (() => { + (async () => { const __nodo_klass__ = { nodo: global.nodo }; #{dependencies.map(&:to_js).join} #{constants.map(&:to_js).join} @@ -109,7 +109,13 @@ def finalize_context(context_id) def require(*mods) deps = mods.last.is_a?(Hash) ? mods.pop : {} mods = mods.map { |m| [m, m] }.to_h - self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) } + self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package, type: :cjs) } + end + + def import(*mods) + deps = mods.last.is_a?(Hash) ? mods.pop : {} + mods = mods.map { |m| [m, m] }.to_h + self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package, type: :esm) } end def function(name, _code = nil, timeout: Nodo.timeout, code: nil, &block) diff --git a/lib/nodo/dependency.rb b/lib/nodo/dependency.rb index 61943b8..62e290d 100644 --- a/lib/nodo/dependency.rb +++ b/lib/nodo/dependency.rb @@ -1,12 +1,22 @@ module Nodo class Dependency - attr_reader :name, :package - - def initialize(name, package) - @name, @package = name, package + attr_reader :name, :package, :type + + def initialize(name, package, type:) + @name, @package, @type = name, package, type end - + def to_js + case type + when :cjs then to_cjs + when :esm then to_esm + else raise "Unknown dependency type: #{type}" + end + end + + private + + def to_cjs <<~JS const #{name} = __nodo_klass__.#{name} = (() => { try { @@ -18,5 +28,18 @@ def to_js })(); JS end + + def to_esm + <<~JS + const #{name} = __nodo_klass__.#{name} = await (async () => { + try { + return await nodo.import(#{package.to_json}); + } catch(e) { + e.nodo_dependency = #{package.to_json}; + throw e; + } + })(); + JS + end end end diff --git a/lib/nodo/nodo.cjs b/lib/nodo/nodo.cjs index 110569b..f80705e 100644 --- a/lib/nodo/nodo.cjs +++ b/lib/nodo/nodo.cjs @@ -99,8 +99,12 @@ module.exports = (function() { try { if (DEFINE_METHOD == method) { - classes[class_name] = vm.runInThisContext(input, class_name); - respond_with_data(res, class_name, start); + Promise.resolve(vm.runInThisContext(input, class_name)).then((result) => { + classes[class_name] = result; + respond_with_data(res, class_name, start); + }).catch((error) => { + respond_with_error(res, 500, error); + }) } else if (EVALUATE_METHOD == method) { Promise.resolve(vm.runInNewContext(input, context)).then((result) => { respond_with_data(res, result, start); diff --git a/test/nodo_test.rb b/test/nodo_test.rb index 5512827..e01b39a 100644 --- a/test/nodo_test.rb +++ b/test/nodo_test.rb @@ -167,8 +167,8 @@ def test_logging assert_match /Nodo::JavaScriptError/, Nodo.logger.errors.first end end - - def test_dependency_error + + def test_require_dependency_error with_logger nil do nodo = Class.new(Nodo::Core) do require 'foobarfoo' @@ -260,9 +260,44 @@ def test_dynamic_imports_in_evaluation uuid = nodo.new.evaluate("nodo.import('uuid').then((uuid) => uuid.v4()).catch((e) => null)") assert_uuid uuid end - + + def test_import + nodo = Class.new(Nodo::Core) do + import :fs + function :exists_file, "(file) => fs.existsSync(file)" + end + assert_equal true, nodo.instance.exists_file(__FILE__) + assert_equal false, nodo.instance.exists_file('FOOBARFOO') + end + + def test_import_npm + nodo = Class.new(Nodo::Core) do + import :uuid + function :v4, "() => uuid.v4()" + end + assert uuid = nodo.new.v4 + assert_equal 36, uuid.size + end + + def test_import_dependency_error + with_logger nil do + nodo = Class.new(Nodo::Core) do + import 'foobarfoo' + end + assert_raises Nodo::DependencyError do + nodo.new + end + end + end + + def test_evaluation_can_access_imports + nodo = Class.new(Nodo::Core) { import :uuid } + uuid = nodo.new.evaluate('uuid.v4()') + assert_uuid uuid + end + private - + def test_logger Object.new.instance_exec do def errors; @errors ||= []; end