Skip to content

Commit

Permalink
Javascript compiler for ESM chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
brenogazzola committed Dec 14, 2021
1 parent 9ca9fcf commit 82309b2
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 1 deletion.
29 changes: 29 additions & 0 deletions benchmarks/javascript_asset_urls
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env ruby

# Benchmark file for the JavascriptAssetUrls compiler

require "active_support/ordered_options"
require "benchmark/ips"
require "open-uri"

require_relative "./trackrod"
require_relative "../lib/propshaft"
require_relative "../lib/propshaft/compilers"
require_relative "../lib/propshaft/compilers/javascript_asset_urls"

trackrod = Trackrod.new(Dir.mktmpdir)
trackrod.build

assets = ActiveSupport::OrderedOptions.new
assets.paths = [ trackrod.root ]
assets.prefix = "/assets"
assets.compilers = [ [ "text/javascript", Propshaft::Compilers::JavascriptAssetUrls ] ]

assembly = Propshaft::Assembly.new(assets)
asset = assembly.load_path.find(trackrod.assets.javascript)
compiler = Propshaft::Compilers::JavascriptAssetUrls.new(assembly)

Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
x.report("compile") { compiler.compile(asset.logical_path, asset.content) }
end
12 changes: 11 additions & 1 deletion benchmarks/trackrod.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def build
def assets
@assets ||= ActiveSupport::InheritableOptions.new(
css: "stylesheets/application.css",
js: "javascript/application.js",
javascript: "javascript/application.js",
images: (small_images + large_images).map { "images/#{_1}" }
)
end
Expand All @@ -41,6 +41,12 @@ def create_css
end

def create_javascript
File.open("#{javascript}/application.js", "a") do |file|
10.times do |idx|
FileUtils.touch "#{javascript}/chunk-#{idx}.js"
file.write(chunk(idx))
end
end
end

def create_images
Expand All @@ -62,6 +68,10 @@ def background(img, idx)
".background_#{idx} {\n background: url('../images/#{img}') \n}\n\n"
end

def chunk(idx)
"import { __toModule, require_chunk_#{idx} } from './chunk-#{idx}.js'\n\n"
end

def create_dir(path)
Dir.mkdir(path) unless File.exist?(path)
end
Expand Down
1 change: 1 addition & 0 deletions lib/propshaft/assembly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require "propshaft/processor"
require "propshaft/compilers"
require "propshaft/compilers/css_asset_urls"
require "propshaft/compilers/javascript_asset_urls"
require "propshaft/compilers/source_mapping_urls"

class Propshaft::Assembly
Expand Down
36 changes: 36 additions & 0 deletions lib/propshaft/compilers/javascript_asset_urls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require "propshaft/errors"

class Propshaft::Compilers::JavascriptAssetUrls
attr_reader :assembly

ASSET_URL_PATTERN = /((?:import|from)\(?\s*["'](.*\.js)["']\)?)/

def initialize(assembly)
@assembly = assembly
end

def compile(logical_path, input)
input.gsub(ASSET_URL_PATTERN) { asset_url resolve_path(logical_path.dirname, $2), logical_path, $2, $1 }
end

private
def resolve_path(directory, filename)
if filename.start_with?("../")
Pathname.new(directory + filename).relative_path_from("").to_s
elsif filename.start_with?("/")
filename.delete_prefix("/").to_s
else
(directory + filename.delete_prefix("./")).to_s
end
end

def asset_url(resolved_path, logical_path, pattern, match)
if asset = assembly.load_path.find(resolved_path)
match.gsub(pattern, "#{assembly.config.prefix}/#{asset.digested_path}")
else
Propshaft.logger.warn "Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}"
match
end
end
end
1 change: 1 addition & 0 deletions lib/propshaft/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Railtie < ::Rails::Railtie
config.assets.compilers = [
[ "text/css", Propshaft::Compilers::CssAssetUrls ],
[ "text/css", Propshaft::Compilers::SourceMappingUrls ],
[ "text/javascript", Propshaft::Compilers::JavascriptAssetUrls ],
[ "text/javascript", Propshaft::Compilers::SourceMappingUrls ]
]
config.assets.sweep_cache = Rails.env.development?
Expand Down
Empty file.
7 changes: 7 additions & 0 deletions test/fixtures/assets/vendor/foobar/source/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {
__toModule,
require_lazysizes
} from "./chunk-SVLTBI7C.js"

// app/assets/vendor/foobar/source/test.js
var import_lazysizes = __toModule(require_lazysizes())
48 changes: 48 additions & 0 deletions test/propshaft/compilers/javascript_asset_urls_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require "test_helper"
require "minitest/mock"
require "propshaft/asset"
require "propshaft/assembly"
require "propshaft/compilers"

class Propshaft::Compilers::JavascriptAssetUrlsTest < ActiveSupport::TestCase
setup do
@assembly = Propshaft::Assembly.new(ActiveSupport::OrderedOptions.new.tap { |config|
config.paths = [ Pathname.new("#{__dir__}/../../fixtures/assets/vendor") ]
config.output_path = Pathname.new("#{__dir__}/../../fixtures/output")
config.prefix = "/assets"
})

@assembly.compilers.register "text/javascript", Propshaft::Compilers::JavascriptAssetUrls
end

test "single quotes" do
compiled = compile_asset_with_content(%(from './chunk-SVLTBI7C.js'))
assert_match(/from '\/assets\/foobar\/source\/chunk-SVLTBI7C-[a-z0-9]{40}.js'/, compiled)
end

test "double quotes" do
compiled = compile_asset_with_content(%(from "./chunk-SVLTBI7C.js"))
assert_match(/from "\/assets\/foobar\/source\/chunk-SVLTBI7C-[a-z0-9]{40}.js"/, compiled)
end

test "import without parenthesis" do
compiled = compile_asset_with_content(%(import "./chunk-SVLTBI7C.js"))
assert_match(/import "\/assets\/foobar\/source\/chunk-SVLTBI7C-[a-z0-9]{40}.js"/, compiled)
end

test "import with parenthesis" do
compiled = compile_asset_with_content(%(import\("./chunk-SVLTBI7C.js"\)))
assert_match(/import\("\/assets\/foobar\/source\/chunk-SVLTBI7C-[a-z0-9]{40}.js"\)/, compiled)
end

private
def compile_asset_with_content(content)
root_path = Pathname.new("#{__dir__}/../../fixtures/assets/vendor")
logical_path = "foobar/source/test.js"

asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path)
asset.stub :content, content do
@assembly.compilers.compile(asset)
end
end
end

0 comments on commit 82309b2

Please sign in to comment.