Skip to content

Instantly share code, notes, and snippets.

@rubys
Last active August 4, 2023 22:24
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rubys/82c7477609f561c4c5fdabb462db26d2 to your computer and use it in GitHub Desktop.
Save rubys/82c7477609f561c4c5fdabb462db26d2 to your computer and use it in GitHub Desktop.
#
# Proof of concept - import maps with JSX transpilation. Can trivially
# be extended to TypeScript and all the languages currently supported
# with Sprockets.
#
# There are two parts to this:
# * Have `find_javascript_files_in_tree` in importmap-rails to not only
# find JS files, but also files that can be transpiled to JS.
# * Add sprockets JSX transformer, making use of esbuild
#
# Note: as sprockets requires assets to appear in a manifest, this
# proof of concept takes advantage of that as a mechanism for opting
# in to additional file extensions. Overall flow:
#
# Add *this* file to config/initializers
#
# Add to config/importmap.rb:
# pin_all_from "app/javascript/components", under: "components"
#
# Add to app/assets/config/manifest.js:
# //= link_tree ../../javascript/components .jsx
#
# Create your application, and import your components!
require 'open3'
class Importmap::Map
# determine what extensions to look for by parsing the manifest
def exts_for_path(path)
exts = Set.new ['.js']
# Read the manifest
config_dir = File.join(Rails.root, 'app/assets/config')
manifest_file = File.join(config_dir, 'manifest.js')
manifest = IO.read(manifest_file)
# Extract extensions from the manifest
manifest.scan(%r{//=\s+link_\w+\s+(\S+)\s+(\.\S+)}).map do |match|
link = File.expand_path(match[0], config_dir)
exts << match[1] if path.relative_path_from(link).to_s =~ /^(\w|\.$)/
end
exts
rescue
['.js']
end
# find all files matching the manifest in a given path
def find_javascript_files_in_tree(path)
files = []
exts_for_path(path).each do |ext|
files += Dir[path.join("**/*#{ext}")].collect do |file|
next if File.directory? file
Pathname.new(file.chomp(ext) + '.js')
end
end
files.compact
end
end
module JsxTransformer
include Sprockets
VERSION = '1'
def self.cache_key
@cache_key ||= "#{name}::#{VERSION}".freeze
end
def self.call(input)
data = input[:data]
input[:cache].fetch([self.cache_key, data]) do
out, err, status = Open3.capture3('esbuild', '--sourcemap',
"--sourcefile=#{input[:filename]}", '--loader=jsx',
stdin_data: input[:data])
if status.success? and err.empty?
out
else
raise Error, "esbuild exit status=#{status.exitstatus}\n#{err}"
end
end
end
end
Sprockets.register_mime_type 'application/jsx', extensions: ['.jsx']
Sprockets.register_transformer 'application/jsx', 'application/javascript',
JsxTransformer

What if you could have JSX and import maps?

What follows ia a proof of concept of exactly that!

  • rails new reakt --dev

  • cd react

  • copy importmap-jsx.rb (above) to config/initializers

  • ./bin/rails generate controller components index

  • add to app/views/components/index.html.erb: <div id="root"></div>

  • change get in config/routes.rb to: root 'components#index'

  • ./bin/importmap pin react react-dom

  • add to config/importmap.rb: pin_all_from "app/javascript/components", under: "components"

  • mkdir app/javascript/components

  • add to app/javascript/application.js: import "components"

  • add to app/assets/config/manifest.js: //= link_tree ../../javascript/components .jsx

  • create app/javascript/components/index.jsx:

       import React from "react"
       import { render } from "react-dom"
    
       render(
         <h1>hello world</h1>,
         document.getElementById("root")
       )
    
  • ./bin/rails server

  • visit http://localhost:3000/

@artemave
Copy link

This is really helpful, thanks.

Any tips how to handle import url from './image.png'? As far as I could see this is only possible with esbuild --bundle, but that was it also bundles regular js imports which defeats the purpose.

@ariajanke
Copy link

What license is this released under?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment