Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace :demo do
end

namespace :packages do
# TODO: add tests and support for Vite and esbuild
# TODO: add tests and support for Vite
desc "Build & test the Node version of Ruby2JS plus frontend bundling packages"
task :test do

Expand All @@ -30,9 +30,7 @@ namespace :packages do
end

Dir.chdir 'packages/esbuild-plugin' do
npm_root = `npm root`.strip
sh 'yarn install' unless File.exist? 'yarn.lock'
sh "cp ../ruby2js/ruby2js.js #{npm_root}/@ruby2js/ruby2js/ruby2js.js"
sh 'yarn test'
end

Expand Down
37 changes: 35 additions & 2 deletions demo/ruby2js.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
require 'ruby2js/demo'
require 'cgi'
require 'pathname'
require 'json'

def parse_request(env=ENV)

Expand Down Expand Up @@ -57,6 +58,10 @@ def parse_request(env=ENV)

opts.on('--preset', "use sane defaults (modern eslevel & common filters)") {options[:preset] = true}

opts.on('-C', '--config [FILE]', "configuration file to use (default is config/ruby2js.rb)") {|filename|
options[:config_file] = filename
}

opts.on('--autoexports [default]', "add export statements for top level constants") {|option|
options[:autoexports] = option ? option.to_sym : true
}
Expand Down Expand Up @@ -90,6 +95,10 @@ def parse_request(env=ENV)
selected.push(*names)
end

opts.on('--filepath [PATH]', "supply a path if stdin is related to a source file") do |filepath|
options[:file] = filepath
end

opts.on('--identity', "triple equal comparison operators") {options[:comparison] = :identity}

opts.on('--import_from_skypack', "use Skypack for internal functions import statements") do
Expand Down Expand Up @@ -130,6 +139,10 @@ def parse_request(env=ENV)
options[:underscored_private] = true
end

opts.on("--sourcemap", "Provide a JSON object with the code and sourcemap") do
@provide_sourcemap = true
end

# shameless hack. Instead of repeating the available options, extract them
# from the OptionParser. Exclude default options and es20xx options.
options_available = opts.instance_variable_get(:@stack).last.list.
Expand Down Expand Up @@ -168,9 +181,29 @@ def parse_request(env=ENV)
# command line support
if ARGV.length > 0
options[:file] = ARGV.first
puts Ruby2JS.convert(File.read(ARGV.first), options).to_s
conv = Ruby2JS.convert(File.read(ARGV.first), options)
if @provide_sourcemap
puts(
{
code: conv.to_s,
sourcemap: conv.sourcemap,
}.to_json
)
else
puts conv.to_s
end
else
puts Ruby2JS.convert($stdin.read, options).to_s
conv = Ruby2JS.convert($stdin.read, options)
if @provide_sourcemap
puts(
{
code: conv.to_s,
sourcemap: conv.sourcemap,
}.to_json
)
else
puts conv.to_s
end
end

else
Expand Down
65 changes: 49 additions & 16 deletions lib/ruby2js.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
$VERBOSE = old_verbose
end

require 'ruby2js/configuration_dsl' unless RUBY_ENGINE == 'opal'
require 'ruby2js/converter'
require 'ruby2js/filter'
require 'ruby2js/namespace'
Expand Down Expand Up @@ -68,16 +69,11 @@ class Processor < Parser::AST::Processor
include Ruby2JS::Filter
BINARY_OPERATORS = Converter::OPERATORS[2..-1].flatten

attr_accessor :prepend_list, :disable_autoimports, :namespace
attr_accessor :prepend_list, :disable_autoimports, :disable_autoexports, :namespace

def initialize(comments)
@comments = comments

# check if magic comment is present:
first_comment = @comments.values.first&.map(&:text)&.first
@disable_autoimports = first_comment&.include?(" autoimports: false")
@disable_autoexports = first_comment&.include?(" autoexports: false")

@ast = nil
@exclude_methods = []
@prepend_list = Set.new
Expand Down Expand Up @@ -218,18 +214,10 @@ def on_send(node)
end
end

# TODO: this method has gotten long and unwieldy!
def self.convert(source, options={})
Filter.autoregister unless RUBY_ENGINE == 'opal'
options = options.dup
if options[:preset]
options[:eslevel] ||= @@eslevel_preset_default
options[:filters] = Filter::PRESET_FILTERS + Array(options[:filters]).uniq
options[:comparison] ||= :identity
options[:underscored_private] = true unless options[:underscored_private] == false
end
options[:eslevel] ||= @@eslevel_default
options[:strict] = @@strict_default if options[:strict] == nil
options[:module] ||= @@module_default || :esm

if Proc === source
file,line = source.source_location
Expand All @@ -246,11 +234,54 @@ def self.convert(source, options={})
comments = ast ? Parser::Source::Comment.associate(ast, comments) : {}
end

# check if magic comment is present
first_comment = comments.values.first&.map(&:text)&.first
if first_comment
if first_comment.include?(" ruby2js: preset")
options[:preset] = true
if first_comment.include?("filters: ")
options[:filters] = first_comment.match(%r(filters:\s*?([^\s]+)\s?.*$))[1].split(",").map(&:to_sym)
end
if first_comment.include?("eslevel: ")
options[:eslevel] = first_comment.match(%r(eslevel:\s*?([^\s]+)\s?.*$))[1].to_i
end
if first_comment.include?("disable_filters: ")
options[:disable_filters] = first_comment.match(%r(disable_filters:\s*?([^\s]+)\s?.*$))[1].split(",").map(&:to_sym)
end
end
disable_autoimports = first_comment.include?(" autoimports: false")
disable_autoexports = first_comment.include?(" autoexports: false")
end

unless RUBY_ENGINE == 'opal'
unless options.key?(:config_file) || !File.exist?("config/ruby2js.rb")
options[:config_file] ||= "config/ruby2js.rb"
end

if options[:config_file]
options = ConfigurationDSL.load_from_file(options[:config_file], options).to_h
end
end

if options[:preset]
options[:eslevel] ||= @@eslevel_preset_default
options[:filters] = Filter::PRESET_FILTERS + Array(options[:filters]).uniq
if options[:disable_filters]
options[:filters] -= options[:disable_filters]
end
options[:comparison] ||= :identity
options[:underscored_private] = true unless options[:underscored_private] == false
end
options[:eslevel] ||= @@eslevel_default
options[:strict] = @@strict_default if options[:strict] == nil
options[:module] ||= @@module_default || :esm

namespace = Namespace.new

filters = Filter.require_filters(options[:filters] || Filter::DEFAULTS)

unless filters.empty?
filter_options = options.merge({ filters: filters })
filters.dup.each do |filter|
filters = filter.reorder(filters) if filter.respond_to? :reorder
end
Expand All @@ -261,7 +292,9 @@ def self.convert(source, options={})
end
filter = filter.new(comments)

filter.options = options
filter.disable_autoimports = disable_autoimports
filter.disable_autoexports = disable_autoexports
filter.options = filter_options
filter.namespace = namespace
ast = filter.process(ast)

Expand Down
86 changes: 86 additions & 0 deletions lib/ruby2js/configuration_dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module Ruby2JS
class ConfigurationDSL
def self.load_from_file(config_file, options = {})
new(options).tap { _1.instance_eval(File.read(config_file), config_file, 1) }
end

def initialize(options = {})
@options = options
end

def preset(bool = true)
@options[:preset] = bool
end

def filter(name)
@options[:filters] ||= []
@options[:filters] << name
end

def remove_filter(name)
@options[:filters]&.reject! { _1 == name }
end

def eslevel(level)
@options[:eslevel] = level
end

def equality_comparison
@options[:comparison] = :equality
end

def identity_comparison
@options[:comparison] = :identity
end

def esm_modules
@options[:module] = :esm
end

def cjs_modules
@options[:module] = :cjs
end

def underscored_ivars
@options[:underscored_private] = true
end

# Only applies for ES2022+
def private_field_ivars
@options[:underscored_private] = false
end

def logical_or
@options[:or] = :logical
end

def nullish_or
@options[:or] = :nullish
end

def use_strict(bool = true)
@options[:strict] = bool
end

def autoimport(identifier = nil, file = nil, &block)
if block
@options[:autoimports] = block
return
elsif @options[:autoimports].is_a?(Proc)
@options[:autoimports] = {}
end

@options[:autoimports] ||= {}
@options[:autoimports][identifier] = file
end

def include_method(method_name)
@options[:include] ||= []
@options[:include] << method_name unless @options[:include].include?(method_name)
end

def to_h
@options
end
end
end
2 changes: 1 addition & 1 deletion lib/ruby2js/converter/return.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Converter
EXPRESSIONS = [ :array, :float, :hash, :int, :lvar, :nil, :send, :attr,
:str, :sym, :dstr, :dsym, :cvar, :ivar, :zsuper, :super, :or, :and,
:block, :const, :true, :false, :xnode, :taglit, :self,
:op_asgn, :and_asgn, :or_asgn, :taglit, :gvar, :csend ]
:op_asgn, :and_asgn, :or_asgn, :taglit, :gvar, :csend, :call ]

handle :autoreturn do |*statements|
return if statements == [nil]
Expand Down
4 changes: 4 additions & 0 deletions lib/ruby2js/filter/camelCase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ def on_sym(node)
handle_generic_node(super, :sym)
end

def on_assign(node)
S(:assign , node.children[0], *node.children[1..-1].map{ process _1 })
end

def on_defs(node)
node = super
return node if node.type != :defs
Expand Down
18 changes: 18 additions & 0 deletions lib/ruby2js/filter/require.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ def on_send(node)
target << child.children[1]
elsif child.type == :def
target << child.children[0]
elsif child.type == :send && child.children[1] == :async
target << child.children[2].children[0]
elsif child.type == :const
target << child.children[1]
elsif child.type == :array
child.children.each do |export_statement|
if export_statement.type == :const
target << export_statement.children[1]
elsif export_statement.type == :hash
default_exports << export_statement.children[0].children[1].children[1]
end
end
end
end

Expand All @@ -99,6 +111,12 @@ def on_send(node)
else
named_exports += auto_exports
end
default_exports.map! { _1.to_s.sub(/[?!]/, '').then do |name|
respond_to?(:camelCase) ? camelCase(name) : name.to_sym
end }
named_exports.map! { _1.to_s.sub(/[?!]/, '').then do |name|
respond_to?(:camelCase) ? camelCase(name) : name.to_sym
end }

imports = @require_seen[realpath]
imports << s(:const, nil, default_exports.first) unless default_exports.empty?
Expand Down
3 changes: 1 addition & 2 deletions packages/esbuild-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ruby2js/esbuild-plugin",
"version": "0.0.3",
"version": "1.0.0",
"description": "ruby2js plugin for esbuild",
"contributors": [
"Jared White",
Expand Down Expand Up @@ -28,7 +28,6 @@
"access": "public"
},
"dependencies": {
"@ruby2js/ruby2js": ">0.0.1",
"convert-source-map": "^1.8.0"
},
"devDependencies": {
Expand Down
Loading