Skip to content

Instantly share code, notes, and snippets.

@rtfeldman
Last active July 3, 2022 03:57
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rtfeldman/db7b121100b6c6ff435b to your computer and use it in GitHub Desktop.
Save rtfeldman/db7b121100b6c6ff435b to your computer and use it in GitHub Desktop.
# Elm Sprockets Integration for Rails 3
#
# I'm working on turning this into a Gem - see https://github.com/NoRedInk/sprockets-elm - but
# I know a lot more about Elm than I do Rails, and haven't gotten that version working yet.
# If you know how to help Gemify this, by all means please hit me up! https://twitter.com/rtfeldman
# I could definitely use the help.
#
# Anyway, in the meantime, this is what we're using at NoRedInk to integrate Elm into our asset
# pipeline, and it works like a charm. Just copy this into config/initializers/elm_sprockets.rb
#
# For this to work, make sure you have elm-make on your PATH. You can get it with:
#
# npm install -g elm
#
# Now you should be able to //=require any *.js.elm file (must end with .js.elm and not just .elm!)
# from any JS file that Sprockets knows about, e.g.
#
# //= require MyElmModule.js.elm
require 'open3'
require 'tempfile'
class ElmCompiler < Sprockets::Processor
def self.default_mime_type
'text/x-elm'
end
def evaluate(context, _locals)
pathname = context.pathname.to_s
if pathname =~ /\.js.*\.elm$/
add_elm_dependencies pathname, context
ElmCompiler.compile pathname
else
""
end
end
private
# Add all Elm modules imported in the target file as dependencies, then
# recursively do the same for each of those dependent modules.
def add_elm_dependencies(filepath, context)
# Turn e.g. ~/NoRedInk/app/assets/javascripts/Quiz/QuestionStoreAPI.js.elm
# into just ~/NoRedInk/app/assets/javascripts/
dirname = context.pathname.to_s.gsub Regexp.new(context.logical_path + ".+$"), ""
File.read(filepath).each_line do |line|
# e.g. `import Quiz.QuestionStore exposing (..)`
match = line.match(/^import\s+([^\s]+)/)
unless match.nil?
# e.g. Quiz.QuestionStore
module_name = match.captures[0]
# e.g. Quiz/QuestionStore
dependency_logical_name = module_name.gsub(".", "/")
# e.g. ~/NoRedInk/app/assets/javascripts/Quiz/QuestionStore.elm
dependency_filepath = dirname + dependency_logical_name + ".elm"
# If we don't find the dependency in our filesystem, assume it's because
# it comes in through a third-party package rather than our sources.
if File.file? dependency_filepath
context.depend_on dependency_logical_name
add_elm_dependencies dependency_filepath, context
end
end
end
end
def self.compile(pathname)
temp_file = Tempfile.new 'compiled_elm_output.js'
cmd = `node -e "process.stdout.write(require('elm').getPathTo('elm-make'));"`
begin
# need to specify LANG or else build will fail on jenkins
# with error "elm-make: elm-stuff/build-artifacts/NoRedInk/NoRedInk/1.0.0/Quiz-QuestionStore.elmo: hGetContents: invalid argument (invalid byte sequence)"
Open3.popen3({'LANG' => 'en_US.UTF-8'}, cmd, pathname.to_s, "--yes", "--output", temp_file.path, chdir: Rails.root) do |_stdin, stdout, stderr, wait_thr|
compiler_output = stdout.gets(nil)
stdout.close
compiler_err = stderr.gets(nil)
stderr.close
process_status = wait_thr.value
if process_status.exitstatus != 0
raise compiler_err
end
end
temp_file.read
ensure
temp_file.unlink
end
end
end
Rails.application.assets.register_engine('.elm', ElmCompiler)
Rails.application.assets.register_mime_type('text/x-elm', '.elm')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment