This is experimental CommonJS modules implementation in Ruby. The main difference is that in this implementation everything is local, there isn't any messing with the global namespace. It has a lot of advantages include hot code reloading.
class Task
attr_reader :name
def initialize(name)
@name
end
end
# Export single value.
export { Task }
Task = import('task')
# Export a variable.
export VERSION: '0.0.1'
# Export a function.
def exports.main(args)
task = Task.new(args.shift)
puts "~ #{task.name}"
end
#!/usr/bin/env ruby -Ilib
require 'import'
runner = import('runner')
# => #<Imports::Export:0x00007f8dae26cd00
# @_DATA_ = {
# :VERSION => "0.0.1",
# :main => #<Method #main>},
# @_FILE_ = "lib/runner.rb">
# Run the code.
runner.main(ARGV)
The syntax is very flexible. Check the examples for more.
Kernel#import
is a substitute for:
Kernel#require
when used with a path relative to$LOAD_PATH
orKernel#require_relative
when used with a path starting with./
or../
.
This object is available as a top-level method, since everything is evaluated against an instance of Import::Context
You can assign anything to exports
. Currently the only limitation is that the value cannot be nil
.
exports.VERSION = '0.0.1'
# import('example.rb')
# => #<Imports::Export:0x00007f8dae26cd00
# @_DATA_ = {
# :VERSION => "0.0.1",
# @_FILE_ = "example.rb">
If you export key default
, then only specified value will be exported, rather than an instance of Imports::Exports
holding multiple values.
exports.default = "Only this will be exported."
# import('example.rb')
# => "Only this will be exported."
You can also define singleton methods on the exports
object:
def exports.main(*args)
# TODO: Implement me.
end
# => #<Imports::Export:0x00007f8dae26cd00
# @_DATA_ = {
# :main => #<Method #main>},
# @_FILE_ = "example.rb">
This is the only thing that the export
method doesn't support.
Also, here we are in an Imports::Exports
instance rather than in Imports::Context
.
Because of that we use __ACCESSOR__
s on Imports::Exports
rather than accessor
s.
This is a convenience method for assigning things to exports
# Using a block.
export { DefaultValue }
# Using hash.
export default: DefaultValue
# Using hash.
export one: ClassOne, two: ClassTwo
# Using names from #name as the key.
# Every exported object has to have the #name method defined.
# That means you have to do it manually for anonymous classes.
class ClassOne; end
class ClassTwo; end
ClassThree = Class.new do
def self.name
'ClassThree'
end
end
export ClassOne, ClassTwo, ClassThree
Ruby developers seldom distinguish between public and private APIs in their projects. Everything's goes into the global namespace, hencer everything is kinda public.
With commonjs_modules, you can choose what you export and what not.
TODO: Example.
This makes use of Ruby modules for namespacing obsolete. Obviously, they still have their use as mixins.
This is a great news. With one global namespace, it's necessary to go full on with the namespacing craziness having all these LibName::SubModule::Module::ClassName
and dealing either with horrible nesting or with potential for missing module on which we want to definie a class.
Without a global namespace, everything is essentially flat. If we import a Task
, there's no chance of colision, because we import everything manually and it's crystal clear where every single thing is coming from.
Even though all the gems out there are using the global namespace, it doesn't matter, it still a great way to organise your code. It plays well with the traditional approach.
OriginalLib = import('original_lib')
class OriginalLibNew < OriginalLib
def method_i_want_to_override
# ..
end
end
The big downside is you can way goodbye YARD, RDoc and many other static code analysers.
I assume Rubocop, CodeClimate and similar tools will be thrown off as well.
class Hour; end
export { Hour } if defined?(export)
- It DOES make sense to have default and others, see interfacer!
- This sets name: a = Testx = Class.new
- Create missing tests, fix existing ones.
- exports.default = Class.new {}. What .name to set? The file I guess.
- Tag and release version 0.1.
- This:
exports.myFnName do
end
# name here?
exports.default do
end
# -> Same as def exports.myFnName, but different namespace.
- Tweak rSpec to evaluate test files against
Imports::Context
or something alike, otherwise we have the annoying constant was already defined messages all over.