├── .rspec ├── spec ├── fixtures │ ├── no_requires.rb │ ├── required_tree_test │ │ ├── required_file1.rb │ │ └── required_file2.rb │ ├── jst_file.jst.ejs │ ├── required_file.js │ ├── source_map │ │ └── subfolder │ │ │ └── other_file.rb │ ├── opal_file.rb │ ├── sprockets_file.js.rb │ ├── require_tree_test.rb │ ├── sprockets_require_tree_test.rb │ ├── file_with_directives.js │ ├── requires.rb │ └── complex_sprockets.js.rb.erb ├── spec_helper.rb ├── sprockets │ ├── erb_spec.rb │ ├── processor_spec.rb │ └── server_spec.rb ├── tilt │ └── opal_spec.rb └── sprockets_spec.rb ├── lib ├── opal-sprockets.rb └── opal │ ├── sprockets │ ├── version.rb │ ├── environment.rb │ ├── mime_types.rb │ ├── erb.rb │ ├── assets_helper.rb │ ├── server.rb │ └── processor.rb │ ├── processor.rb │ ├── environment.rb │ └── sprockets.rb ├── bin ├── rake ├── setup ├── console └── rspec ├── example ├── config.ru ├── Gemfile ├── index.html.erb ├── app │ ├── user.rb │ └── application.rb └── Gemfile.lock ├── .gitignore ├── Rakefile ├── Gemfile ├── .github └── workflows │ └── build.yml ├── opal-sprockets.gemspec ├── CHANGELOG.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format doc 3 | -------------------------------------------------------------------------------- /spec/fixtures/no_requires.rb: -------------------------------------------------------------------------------- 1 | puts 'hi there' 2 | -------------------------------------------------------------------------------- /lib/opal-sprockets.rb: -------------------------------------------------------------------------------- 1 | require 'opal/sprockets' 2 | -------------------------------------------------------------------------------- /spec/fixtures/required_tree_test/required_file1.rb: -------------------------------------------------------------------------------- 1 | p 1 2 | -------------------------------------------------------------------------------- /spec/fixtures/required_tree_test/required_file2.rb: -------------------------------------------------------------------------------- 1 | p 2 2 | -------------------------------------------------------------------------------- /spec/fixtures/jst_file.jst.ejs: -------------------------------------------------------------------------------- 1 | #= require sprockets_file 2 | -------------------------------------------------------------------------------- /spec/fixtures/required_file.js: -------------------------------------------------------------------------------- 1 | console.log('required file'); 2 | -------------------------------------------------------------------------------- /spec/fixtures/source_map/subfolder/other_file.rb: -------------------------------------------------------------------------------- 1 | puts 'other!' 2 | -------------------------------------------------------------------------------- /spec/fixtures/opal_file.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | puts 'hi from opal!' 3 | -------------------------------------------------------------------------------- /spec/fixtures/sprockets_file.js.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'native' 3 | puts 'sprockets!' 4 | -------------------------------------------------------------------------------- /spec/fixtures/require_tree_test.rb: -------------------------------------------------------------------------------- 1 | require_tree '../fixtures/required_tree_test' 2 | 3 | puts 5 4 | -------------------------------------------------------------------------------- /spec/fixtures/sprockets_require_tree_test.rb: -------------------------------------------------------------------------------- 1 | #=require_tree ./required_tree_test 2 | 3 | puts 5 4 | -------------------------------------------------------------------------------- /spec/fixtures/file_with_directives.js: -------------------------------------------------------------------------------- 1 | //= require required_file 2 | console.log('file with directives'); 3 | -------------------------------------------------------------------------------- /lib/opal/sprockets/version.rb: -------------------------------------------------------------------------------- 1 | module Opal 2 | module Sprockets 3 | VERSION = '1.0.4' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/requires.rb: -------------------------------------------------------------------------------- 1 | require 'foo' 2 | 3 | puts 'hello' 4 | 5 | require 'bar' 6 | 7 | puts 'goodbye' 8 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "rubygems" 4 | require "bundler/setup" 5 | 6 | load Gem.bin_path("rake", "rake") 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | gem install bundler --conservative 7 | 8 | bundle update 9 | -------------------------------------------------------------------------------- /example/config.ru: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | run Opal::Sprockets::Server.new { |s| 5 | s.main = 'application' 6 | s.append_path 'app' 7 | } 8 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | require_relative "../lib/opal/sprockets/version.rb" 4 | gem 'opal', "~> #{Opal::Sprockets::OPAL_VERSION}" 5 | gem 'opal-sprockets', path: '..' 6 | -------------------------------------------------------------------------------- /spec/fixtures/complex_sprockets.js.rb.erb: -------------------------------------------------------------------------------- 1 | #= require no_requires 2 | #= require ./jst_file 3 | #= require_tree ./required_tree_test 4 | require 'file_with_directives' 5 | require <%= "base64".inspect %> 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | .yardoc 4 | .bundle 5 | /Gemfile.lock 6 | build/ 7 | /.github_access_token 8 | /tmp 9 | .ruby-gemset 10 | .ruby-version 11 | .rvmrc 12 | .rspec-local 13 | /coverage 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) 8 | 9 | task :default => :spec 10 | -------------------------------------------------------------------------------- /lib/opal/processor.rb: -------------------------------------------------------------------------------- 1 | warn "Opal::Processor is deprecated, please switch to Opal::Sprockets::Processor.", uplevel: 1 2 | 3 | require 'opal/sprockets/processor' 4 | Opal::Processor = Opal::Sprockets::Processor 5 | -------------------------------------------------------------------------------- /lib/opal/environment.rb: -------------------------------------------------------------------------------- 1 | warn "Opal::Environment is deprecated, please switch to Opal::Sprockets::Environment.", uplevel: 1 2 | 3 | require 'opal/sprockets/environment' 4 | Opal::Environment = Opal::Sprockets::Environment 5 | -------------------------------------------------------------------------------- /example/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | opal-sprockets demo 5 | 6 | <%= javascript_include_tag 'application' %> 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | RSpec.configure do |config| 5 | config.before do 6 | Opal::Config.reset! 7 | Opal::Sprockets::Processor.reset_cache_key! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "opal" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /example/app/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | attr_reader :name 3 | 4 | def initialize(name) 5 | @name = name 6 | end 7 | 8 | def authenticated? 9 | if admin? or special_persmission? 10 | true 11 | else 12 | raise "not authenticated" 13 | end 14 | end 15 | 16 | def admin? 17 | @name == 'Bob' 18 | end 19 | 20 | def special_persmission? 21 | false 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/opal/sprockets/environment.rb: -------------------------------------------------------------------------------- 1 | require 'sprockets' 2 | require 'opal/sprockets/processor' 3 | require 'opal/sprockets/erb' 4 | 5 | class Opal::Sprockets::Environment < ::Sprockets::Environment 6 | def initialize *args 7 | super 8 | append_opal_paths 9 | end 10 | 11 | def use_gem gem_name 12 | Opal.use_gem gem_name 13 | append_opal_paths 14 | end 15 | 16 | private 17 | 18 | def append_opal_paths 19 | Opal.paths.each { |p| append_path p } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | opal_path = File.expand_path('../opal') 5 | 6 | if ENV['OPAL_VERSION'] 7 | gem 'opal', ENV['OPAL_VERSION'] 8 | elsif File.exist? opal_path 9 | gem 'opal', path: opal_path 10 | else 11 | gem 'opal', github: 'opal/opal' 12 | end 13 | 14 | rack_version = ENV['RACK_VERSION'] 15 | sprockets_version = ENV['SPROCKETS_VERSION'] 16 | 17 | gem 'rack', rack_version if rack_version 18 | gem 'sprockets', sprockets_version if sprockets_version 19 | -------------------------------------------------------------------------------- /example/app/application.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'user' 3 | require 'native' 4 | 5 | module MyApp 6 | class Application 7 | attr_reader :user 8 | 9 | def initialize 10 | @user = User.new('Bill') 11 | @user.authenticated? 12 | rescue 13 | @user = User.new('Bob') 14 | @user.authenticated? 15 | p @user 16 | end 17 | end 18 | end 19 | 20 | $app = MyApp::Application.new 21 | 22 | p $app 23 | puts "Done!" 24 | 25 | $$.document.write("user is #{$app.user.name}") 26 | -------------------------------------------------------------------------------- /lib/opal/sprockets.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'opal/sprockets/version' 3 | 4 | module Opal 5 | module Sprockets 6 | require 'opal/sprockets/assets_helper' 7 | require 'opal/sprockets/mime_types' 8 | extend AssetsHelper 9 | extend MimeTypes 10 | 11 | require 'opal/sprockets/environment' 12 | require 'opal/sprockets/erb' 13 | require 'opal/sprockets/processor' 14 | require 'opal/sprockets/server' 15 | end 16 | 17 | autoload :Processor, 'opal/processor' 18 | autoload :Environment, 'opal/environment' 19 | end 20 | -------------------------------------------------------------------------------- /lib/opal/sprockets/mime_types.rb: -------------------------------------------------------------------------------- 1 | module Opal::Sprockets::MimeTypes 2 | def register_mime_type(mime_type) 3 | mime_types << mime_type 4 | end 5 | 6 | def mime_types 7 | @mime_types ||= [] 8 | end 9 | 10 | def sprockets_extnames_regexp(sprockets, opal_only: false) 11 | opal_extnames = sprockets.mime_types.map do |type, hash| 12 | hash[:extensions] if !opal_only || Opal::Sprockets.mime_types.include?(type) 13 | end.compact.flatten 14 | 15 | opal_extnames << ".js" unless opal_only 16 | 17 | Regexp.union(opal_extnames.map { |i| /#{Regexp.escape(i)}\z/ }) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | - push 3 | - pull_request 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ 'ubuntu-latest' ] 10 | ruby: [ "3.0", "2.7", "2.6", "2.5" ] 11 | opal: [ "1.0.3", "1.1.0" ] 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | env: 16 | OPAL_VERSION: ${{ matrix.opal }} 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby }} 23 | - name: Setup project 24 | run: bin/setup 25 | - name: Run test 26 | run: bin/rake 27 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | opal-sprockets (0.4.5.1.0.3.7) 5 | opal (~> 1.0.0) 6 | sprockets (~> 3.7) 7 | tilt (>= 1.4) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | ast (2.4.0) 13 | concurrent-ruby (1.1.5) 14 | opal (1.0.0) 15 | ast (>= 2.3.0) 16 | parser (= 2.5.3.0) 17 | parser (2.5.3.0) 18 | ast (~> 2.4.0) 19 | rack (2.0.7) 20 | sprockets (3.7.2) 21 | concurrent-ruby (~> 1.0) 22 | rack (> 1, < 3) 23 | tilt (2.0.9) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | opal (~> 1.0.0) 30 | opal-sprockets! 31 | 32 | BUNDLED WITH 33 | 1.17.3 34 | -------------------------------------------------------------------------------- /lib/opal/sprockets/erb.rb: -------------------------------------------------------------------------------- 1 | require 'tilt' 2 | require 'sprockets' 3 | require 'opal/sprockets/processor' 4 | require 'opal/erb' 5 | 6 | class Opal::Sprockets::ERB < ::Opal::Sprockets::Processor 7 | def call 8 | compiler = Opal::ERB::Compiler.new(@data, logical_path.sub(/#{Opal::REGEXP_START}templates\//, '')) 9 | @data = compiler.prepared_source 10 | super 11 | end 12 | 13 | # @deprecated 14 | ::Opal::ERB::Processor = self 15 | end 16 | 17 | Sprockets.register_mime_type 'application/html+javascript+ruby', extensions: ['.opalerb', '.opal.erb', '.html.opal.erb'] 18 | Sprockets.register_transformer 'application/html+javascript+ruby', 'application/javascript', Opal::ERB::Processor 19 | Opal::Sprockets.register_mime_type 'application/html+javascript+ruby' 20 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /spec/sprockets/erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'opal/sprockets/erb' 3 | 4 | describe Opal::Sprockets::ERB do 5 | let(:pathname) { Pathname("/Code/app/mylib/opal/foo.#{ext}") } 6 | let(:data) { %{<% print("") %><%= name %>} } 7 | let(:input) { { 8 | environment: Opal::Sprockets::Environment.new.tap {|e| e.append_path "/Code/app/mylib/opal"}, 9 | data: data, 10 | filename: pathname.to_s, 11 | load_path: "/Code/app/mylib/opal", 12 | metadata: {}, 13 | cache: Sprockets::Cache.new 14 | } } 15 | let(:ext) { 'opalerb' } 16 | 17 | it 'renders the template' do 18 | result = described_class.call(input) 19 | 20 | expect(result[:data]).to include('"true) 22 | output = template.render 23 | expect(output).to include('"hi from opal!"') 24 | expect(output).to include('self.$require("corelib/runtime");') 25 | end 26 | 27 | it "support :builder option" do 28 | builder = Opal::Builder.new(:stubs=>['opal']) 29 | template = described_class.new('./spec/fixtures/opal_file.rb', :builder=>builder) 30 | 31 | 2.times do 32 | output = template.render 33 | expect(output.scan(/hi from opal!/).length).to eql(1) 34 | expect(output).not_to include('self.$require("corelib/runtime");') 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /opal-sprockets.gemspec: -------------------------------------------------------------------------------- 1 | require_relative 'lib/opal/sprockets/version' 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = 'opal-sprockets' 5 | spec.version = Opal::Sprockets::VERSION 6 | spec.authors = ['Elia Schito', 'Adam Beynon', 'Andy Maleh'] 7 | spec.email = 'elia@schito.me' 8 | 9 | spec.summary = 'Sprockets support for Opal.' 10 | spec.homepage = 'https://github.com/opal/opal-sprockets#readme' 11 | spec.license = 'MIT' 12 | 13 | spec.metadata['homepage_uri'] = spec.homepage 14 | spec.metadata['source_code_uri'] = 'https://github.com/opal/opal-sprockets' 15 | spec.metadata['changelog_uri'] = 'https://github.com/opal/opal-sprockets/blob/master/CHANGELOG.md' 16 | 17 | spec.required_ruby_version = Gem::Requirement.new('>= 2.5') 18 | 19 | # Specify which files should be added to the gem when it is released. 20 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 21 | files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } 22 | 23 | spec.files = files.grep_v(%r{^(test|spec|features)/}) 24 | spec.test_files = files.grep(%r{^(test|spec|features)/}) 25 | spec.bindir = "exe" 26 | spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } 27 | spec.require_paths = ["lib"] 28 | 29 | spec.add_dependency 'sprockets', "~> 4.0" 30 | spec.add_dependency 'opal', [">= 1.0", "< 2.0"] 31 | spec.add_dependency 'tilt', '>= 1.4' 32 | 33 | spec.add_development_dependency 'rake' 34 | spec.add_development_dependency 'rspec' 35 | spec.add_development_dependency 'rack-test' 36 | spec.add_development_dependency 'sourcemap' 37 | spec.add_development_dependency 'ejs' 38 | spec.add_development_dependency 'pry' 39 | end 40 | -------------------------------------------------------------------------------- /spec/sprockets_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'opal/sprockets' 3 | 4 | describe Opal::Sprockets do 5 | let(:env) { Sprockets::Environment.new } 6 | before { Opal.paths.each { |path| env.append_path path } } 7 | 8 | describe '.load_asset' do 9 | it 'loads the main asset' do 10 | code = described_class.load_asset('console') 11 | expect(code).to include('Opal.require("console");') 12 | end 13 | 14 | it 'marks as loaded stubs and all non-opal assets' do 15 | allow(Opal::Config).to receive(:stubbed_files) { %w[foo bar] } 16 | 17 | code = described_class.load_asset('baz') 18 | expect(code).to include(%{Opal.loaded(["foo","bar"].concat(typeof(OpalLoaded) === "undefined" ? [] : OpalLoaded));}) 19 | expect(code).to include('Opal.require("baz");') 20 | end 21 | 22 | it 'tries to load an asset if it is registered as opal module' do 23 | code = described_class.load_asset('foo') 24 | expect(code).to include('Opal.require("foo");') 25 | end 26 | 27 | it 'warns the user that passing an env is not needed, only once' do 28 | expect(described_class).to receive(:warn).once 29 | described_class.load_asset('foo', env) 30 | described_class.load_asset('foo', env) 31 | described_class.load_asset('foo', env) 32 | described_class.load_asset('foo', 'bar', env) 33 | described_class.load_asset('foo', 'bar', env) 34 | end 35 | 36 | it 'accepts multiple names' do 37 | code = described_class.load_asset('foo', 'bar') 38 | expect(code).to include('Opal.require("foo");') 39 | expect(code).to include('Opal.require("bar");') 40 | end 41 | 42 | it 'detects deprecated env with multiple names' do 43 | code = described_class.load_asset('foo', 'bar', env) 44 | expect(code).to eq([ 45 | 'Opal.loaded(typeof(OpalLoaded) === "undefined" ? [] : OpalLoaded);', 46 | 'Opal.require("foo");', 47 | 'Opal.require("bar");', 48 | ].join("\n")) 49 | end 50 | end 51 | 52 | describe '.javascript_include_tag' do 53 | it 'works with trailing / in the prefix' do 54 | code = described_class.javascript_include_tag('corelib/runtime', prefix: '/', sprockets: env, debug: false) 55 | expect(code).to include('src="/corelib/runtime.') 56 | expect(code).not_to include('//') 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/sprockets/processor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'opal/sprockets/processor' 3 | 4 | describe Opal::Sprockets::Processor do 5 | let(:pathname) { Pathname("/Code/app/mylib/opal/foo.#{ext}") } 6 | let(:data) { "foo" } 7 | let(:input) { { 8 | environment: Opal::Sprockets::Environment.new.tap {|e| e.append_path "/Code/app/mylib/opal"}, 9 | data: data, 10 | filename: pathname.to_s, 11 | load_path: "/Code/app/mylib/opal", 12 | metadata: {}, 13 | cache: Sprockets::Cache.new 14 | } } 15 | 16 | before do 17 | allow(Sprockets::SourceMapUtils).to receive(:format_source_map) 18 | allow(Sprockets::SourceMapUtils).to receive(:combine_source_maps) 19 | end 20 | 21 | %w[.rb .opal].each do |ext| 22 | describe %{with extension "#{ext}"} do 23 | let(:ext) { ext } 24 | 25 | it "is registered for '#{ext}' files" do 26 | mime_type = Sprockets.mime_types.find { |_,v| v[:extensions].include? ".rb" }.first 27 | transformers = Sprockets.transformers[mime_type] 28 | transformer = transformers["application/javascript"] 29 | expect(transformer.processors).to include(described_class) 30 | end 31 | 32 | it "compiles the code" do 33 | result = described_class.call(input.merge data: "puts 'Hello, World!'\n") 34 | 35 | expect(result[:data]).to include('"Hello, World!"') 36 | end 37 | 38 | describe '.stubbed_files' do 39 | it 'requires non-stubbed files' do 40 | result = described_class.call(input.merge(data: 'require "set"')) 41 | 42 | expect(result[:required].first).to include("stdlib/set.rb") 43 | end 44 | 45 | it 'skips require of stubbed file' do 46 | Opal::Config.stubbed_files << 'set' 47 | result = described_class.call(input.merge(data: "require 'set'")) 48 | 49 | expect(result[:required]).not_to include("set.rb") 50 | end 51 | 52 | it 'marks a stubbed file as loaded' do 53 | Opal::Config.stubbed_files << 'set' 54 | result = described_class.call(input.merge(data: "require 'set'")) 55 | 56 | expect(result[:data]).not_to include(::Opal::Sprockets.load_asset('set')) 57 | end 58 | end 59 | 60 | describe '.cache_key' do 61 | it 'can be reset' do 62 | old_cache_key = described_class.cache_key 63 | Opal::Config.arity_check_enabled = !Opal::Config.arity_check_enabled 64 | stale_cache_key = described_class.cache_key 65 | 66 | described_class.reset_cache_key! 67 | reset_cache_key = described_class.cache_key 68 | 69 | expect(stale_cache_key).to eq(old_cache_key) 70 | expect(reset_cache_key).not_to eq(old_cache_key) 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/opal/sprockets/assets_helper.rb: -------------------------------------------------------------------------------- 1 | module Opal::Sprockets::AssetsHelper 2 | # Bootstraps modules loaded by sprockets on `Opal.modules` marking any 3 | # non-Opal asset as already loaded. 4 | # 5 | # @example 6 | # 7 | # Opal::Sprockets.load_asset('application') 8 | # 9 | # @example Will output the following JavaScript: 10 | # 11 | # Opal.loaded("jquery.self", "yet_another_carousel.self"); 12 | # Opal.require("opal", "application"); 13 | # 14 | # @param name [String] The logical name of the main asset to be loaded (without extension) 15 | # 16 | # @return [String] JavaScript code 17 | def load_asset(*names) 18 | if names.last.is_a?(::Sprockets::Environment) 19 | unless @load_asset_warning_displayed 20 | @load_asset_warning_displayed = true 21 | warn "Passing a sprockets environment to Opal::Sprockets.load_asset no more needed.\n #{caller(1, 3).join("\n ")}" 22 | end 23 | names.pop 24 | end 25 | 26 | names = names.map { |name| name.sub(/(\.(js|rb|opal))*\z/, '') } 27 | stubbed = ::Opal::Config.stubbed_files.to_a 28 | 29 | loaded = 'typeof(OpalLoaded) === "undefined" ? [] : OpalLoaded' 30 | loaded = "#{stubbed.to_json}.concat(#{loaded})" if stubbed.any? 31 | 32 | [ 33 | "Opal.loaded(#{loaded});", 34 | *names.map { |name| "Opal.require(#{name.to_json});" } 35 | ].join("\n") 36 | end 37 | 38 | # Mark an asset as already loaded. 39 | # This is useful for requiring JavaScript files which are not managed by Opal's laoding system. 40 | # 41 | # @param [String] name The "logical name" of the asset 42 | # @return [String] JavaScript code 43 | def loaded_asset(name) 44 | %{if (typeof(OpalLoaded) === 'undefined') OpalLoaded = []; OpalLoaded.push(#{name.to_json});} 45 | end 46 | 47 | # Generate a `} 70 | else 71 | scripts << %{} 72 | end 73 | 74 | scripts << %{} 75 | 76 | scripts.join "\n" 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/opal/sprockets/server.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'rack' 3 | require 'sprockets' 4 | 5 | class Opal::Sprockets::Server 6 | attr_accessor :debug, :use_index, :index_path, :main, :public_root, 7 | :public_urls, :sprockets, :prefix 8 | 9 | def initialize options = {} 10 | @use_index = true 11 | @public_root = nil 12 | @public_urls = ['/'] 13 | @sprockets = options.fetch(:sprockets, ::Sprockets::Environment.new) 14 | @debug = options.fetch(:debug, true) 15 | @prefix = options.fetch(:prefix, '/assets') 16 | 17 | Opal.paths.each { |p| @sprockets.append_path(p) } 18 | 19 | yield self if block_given? 20 | create_app 21 | end 22 | 23 | def public_dir=(dir) 24 | @public_root = dir 25 | @public_urls = ["/"] 26 | end 27 | 28 | def source_map=(enabled) 29 | Opal::Config.source_map_enabled = enabled 30 | end 31 | 32 | def source_map_enabled 33 | Opal::Config.source_map_enabled 34 | end 35 | 36 | def append_path path 37 | @sprockets.append_path path 38 | end 39 | 40 | def use_gem gem_name 41 | @sprockets.use_gem gem_name 42 | end 43 | 44 | def create_app 45 | server, sprockets, prefix = self, @sprockets, self.prefix 46 | sprockets.logger.level ||= Logger::DEBUG 47 | 48 | @app = Rack::Builder.app do 49 | not_found = lambda { |env| [404, {}, []] } 50 | use Rack::Deflater 51 | use Rack::ShowExceptions 52 | use Index, server if server.use_index 53 | map(prefix) { run sprockets } 54 | run Rack::Static.new(not_found, root: server.public_root, urls: server.public_urls) 55 | end 56 | end 57 | 58 | def call(env) 59 | @app.call env 60 | end 61 | 62 | class Index 63 | 64 | def initialize(app, server) 65 | @app = app 66 | @server = server 67 | @index_path = server.index_path 68 | end 69 | 70 | def call(env) 71 | if %w[/ /index.html].include? env['PATH_INFO'] 72 | [200, { 'Content-Type' => 'text/html' }, [html]] 73 | else 74 | @app.call env 75 | end 76 | end 77 | 78 | # Returns the html content for the root path. Supports ERB 79 | def html 80 | if @index_path 81 | raise "index does not exist: #{@index_path}" unless File.exist?(@index_path) 82 | Tilt.new(@index_path).render(self) 83 | else 84 | raise "Main asset path not configured (set 'main' within Opal::Server.new block)" if @server.main.nil? 85 | source 86 | end 87 | end 88 | 89 | def javascript_include_tag name 90 | sprockets = @server.sprockets 91 | prefix = @server.prefix 92 | debug = @server.debug 93 | 94 | ::Opal::Sprockets.javascript_include_tag(name, sprockets: sprockets, prefix: prefix, debug: debug) 95 | end 96 | 97 | def source 98 | <<-HTML 99 | 100 | 101 | 102 | 103 | Opal Server 104 | 105 | 106 | #{javascript_include_tag @server.main} 107 | 108 | 109 | HTML 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.4](https://github.com/opal/opal-sprockets/compare/v1.0.3...v1.0.4) 4 | 5 | *3 August 2024* 6 | 7 | - Fix issue with upgrading gems breaking Opal compilation in Rails applications due to incorrect Sprockets caching 8 | 9 | ## [1.0.3](https://github.com/opal/opal-sprockets/compare/v1.0.2...v1.0.3) 10 | 11 | *24 December 2024* 12 | 13 | - Supporting Ruby 3.1 14 | 15 | ## [1.0.2](https://github.com/opal/opal-sprockets/compare/v1.0.1...v1.0.2) 16 | 17 | *24 August 2021* 18 | 19 | - Fix some off-by-one errors with source-maps by not using a newline for the source-map comment 20 | 21 | 22 | ## [1.0.1](https://github.com/opal/opal-sprockets/compare/v1.0.0...v1.0.1) 23 | 24 | *28 July 2021* 25 | 26 | - Open the opal dependency to all v1 versions 27 | 28 | 29 | ## [1.0.0](https://github.com/opal/opal-sprockets/compare/v0.4.9.1.0.3.7...v1.0.0) 30 | 31 | *19 February 2021* 32 | 33 | - Bump the supported sprockets version to v4 34 | - Add support for Opal v1.1 35 | - Only support Ruby comments in directive preprocessor (no one should have ever used "//", ["/*", or "*/") 36 | - Fix the namespaces and move everything under Opal::Sprockets (the legacy namespaces will be dropped in version 1.1) 37 | - The version schema has been simplified, not expecting sprockets to have major earthquakes like it was for v4 38 | 39 | 40 | ## [v0.4.9](https://github.com/opal/opal-sprockets/compare/v0.4.8.1.0.3.7...v0.4.9.1.0.3.7) 41 | 42 | *11 September 2020* 43 | 44 | - Avoid OpalLoaded undefined when applications have no dependencies. 45 | 46 | 47 | ## [v0.4.8](https://github.com/opal/opal-sprockets/compare/v0.4.7.1.0.3.7...v0.4.8.1.0.3.7) 48 | 49 | *14 September 2019* 50 | 51 | - Revert the changes in 0.4.7 52 | 53 | 54 | ## [v0.4.7](https://github.com/opal/opal-sprockets/compare/v0.4.6.1.0.3.7...v0.4.7.1.0.3.7) 55 | 56 | *14 September 2019* 57 | 58 | - Require `opal/sprockets` before calling `Opal::Sprockets.loaded_asset` 59 | 60 | 61 | ## [v0.4.6](https://github.com/opal/opal-sprockets/compare/v0.4.5.1.0.3.7...v0.4.6.1.0.3.7) 62 | 63 | *24 July 2019* 64 | 65 | - Allow multiple calls to the code produced by `Opal::Sprockets.load_asset` 66 | 67 | 68 | ## [v0.4.5](https://github.com/opal/opal-sprockets/compare/v0.4.4.1.0.3.7...v0.4.5.1.0.3.7) 69 | 70 | *25 May 2019* 71 | 72 | - Opal is now loaded as part of the bootstrap process instead of being marked as preloaded by the processor 73 | - Simplified code for loading-related scripts 74 | 75 | 76 | ## [v0.4.4](https://github.com/opal/opal-sprockets/compare/v0.4.3.0.11.0.3.7...v0.4.4.1.0.3.7) 77 | 78 | *12 May 2019* 79 | 80 | - Target Opal v1.0 81 | 82 | 83 | ## [v0.4.3](https://github.com/opal/opal-sprockets/compare/v0.4.2.0.11.0.3.1...v0.4.3.0.11.0.3.7) 84 | 85 | *13 February 2019* 86 | 87 | - Drop support for older Sprockets versions 88 | 89 | 90 | ## [v0.4.2](https://github.com/opal/opal-sprockets/compare/v0.4.1.0.11.0.3.1...v0.4.2.0.11.0.3.1) 91 | 92 | *7 September 2018* 93 | 94 | - Inline source-maps with their source-contents for better performance and simpler architecture 95 | 96 | 97 | ## [v0.4.1](https://github.com/opal/opal-sprockets/compare/v0.4.0.0.10.0.3.0.0...v0.4.1.0.11.0.3.1) 98 | 99 | *30 December 2017* 100 | 101 | - Added support for Opal 0.11 102 | - `Opal::Sprockets.load_asset` now works without the need to access the sprockets environment (needed by `sprockets-rails` v3+) 103 | - Documentation updates 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Opal Sprockets 2 | 3 | _Adds sprockets support for [Opal](http://opalrb.com)._ 4 | 5 | ## Installation 6 | 7 | Add to your `Gemfile`: 8 | 9 | ```ruby 10 | gem "opal-sprockets" 11 | ``` 12 | 13 | ### A note on the version number 14 | 15 | The version number is an attempt to keep track and support different combinations of both opal and sprockets without cluttering the code with giant `if`s and conditional requires. The structure is roughly as follows: 16 | 17 | `..` 18 | 19 | For example version `0.4.1.0.11.0.rc1.3.1` is build taking into account the following components: 20 | 21 | BASE_VERSION = '0.4.1' 22 | OPAL_VERSION = '0.11.0.rc1' 23 | SPROCKETS_VERSION = '3.1' 24 | 25 | 26 | ## Usage 27 | 28 | Sprockets uses a set of load paths to resolve dependencies. This gem extends 29 | sprockets to provide opal load paths to sprockets. `opal-sprockets` provides 30 | a template processor for all files with `.rb` or `.opal` extensions. 31 | 32 | ```ruby 33 | #= require opal 34 | 35 | puts "opal running in sprockets!" 36 | ``` 37 | 38 | ### Improved require support 39 | 40 | By default, sprockets will examine your code for processor directive comments 41 | to handle requires, e.g. `#= require opal`. Opal takes this one step futher 42 | by extending the opal processor to automatically detect and register any 43 | `require` call made inside your ruby code: 44 | 45 | ```ruby 46 | require "opal" 47 | require "opal-jquery" 48 | 49 | puts "opal-jquery is now available!" 50 | ``` 51 | 52 | Opal cannot require files at runtime, so this trick allows ruby code to use 53 | the nicer ruby syntax for requiring dependencies. 54 | 55 | ## Example 56 | 57 | Sprockets uses a load path for code files, so make a simple `app/` directory 58 | with some code inside `app/application.rb`: 59 | 60 | ```ruby 61 | # app/application.rb 62 | 63 | require "opal" 64 | 65 | puts "hello, world" 66 | ``` 67 | 68 | The opal corelib and runtime can be included in your app simply by adding 69 | `require "opal"`. We also need an html file to test the application with, 70 | so add `index.html`: 71 | 72 | ```html 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ``` 83 | 84 | ### Running Application 85 | 86 | `opal-sprockets` comes with a simple `Server` class that can be used to easily 87 | configure applications inside `config.ru`: 88 | 89 | ```ruby 90 | # config.ru 91 | 92 | require 'bundler' 93 | Bundler.require 94 | 95 | run Opal::Sprockets::Server.new { |s| 96 | s.append_path 'app' 97 | s.main = 'application' 98 | 99 | # This can be used to provide a custom index file. 100 | # s.index_path = 'my_index.erb' 101 | } 102 | ``` 103 | 104 | This just adds the `app/` directory to the load path, and tells sprockets that 105 | `application.rb` will be the main file to load. 106 | 107 | Now just run the rack app: 108 | 109 | ``` 110 | $ bundle exec rackup 111 | ``` 112 | 113 | And then visit `http://127.0.0.1:9292` in any browser. 114 | 115 | ### Source Maps 116 | 117 | `opal-sprockets` will create source maps for all assets by default. You can disable this with: 118 | 119 | ```ruby 120 | Opal::Config.source_map_enabled = false 121 | ``` 122 | 123 | ## License 124 | 125 | (The MIT License) 126 | 127 | Copyright (C) 2013 by Adam Beynon 128 | Copyright (C) 2013 by Elia Schito 129 | 130 | Permission is hereby granted, free of charge, to any person obtaining a copy 131 | of this software and associated documentation files (the "Software"), to deal 132 | in the Software without restriction, including without limitation the rights 133 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 134 | copies of the Software, and to permit persons to whom the Software is 135 | furnished to do so, subject to the following conditions: 136 | 137 | The above copyright notice and this permission notice shall be included in 138 | all copies or substantial portions of the Software. 139 | 140 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 141 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 142 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 143 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 144 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 145 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 146 | THE SOFTWARE. 147 | -------------------------------------------------------------------------------- /spec/sprockets/server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'sourcemap' 3 | require 'rack/test' 4 | 5 | describe Opal::Sprockets::Server do 6 | include Rack::Test::Methods 7 | 8 | def app 9 | described_class.new { |s| 10 | s.main = 'opal' 11 | s.debug = false 12 | s.append_path File.expand_path('../../fixtures', __FILE__) 13 | s.sprockets.logger = Logger.new(nil) 14 | } 15 | end 16 | 17 | it 'serves assets from /assets' do 18 | get '/assets/opal.js' 19 | expect(last_response).to be_ok 20 | end 21 | 22 | it 'serves assets with complex sprockets requires' do 23 | asset = app.sprockets['complex_sprockets', accept: "application/javascript"] 24 | expect(asset).to be_truthy 25 | 26 | assets = asset.metadata[:included].map do |sub_asset| 27 | URI.parse(sub_asset).path.split(%r'/fixtures/|/stdlib/').last 28 | end 29 | 30 | %w[ 31 | base64.rb 32 | no_requires.rb 33 | jst_file.jst.ejs 34 | required_tree_test/required_file1.rb 35 | required_tree_test/required_file2.rb 36 | file_with_directives.js 37 | ].each do |logical_path| 38 | expect(assets).to include(logical_path) 39 | end 40 | end 41 | 42 | it 'recompiles the asset when its dependencies change' do 43 | does_include = proc do |it, what, what_not=[], what_else=nil, what_else_not=nil| 44 | expect(it).to include what_else if what_else 45 | expect(it).not_to include what_else_not if what_else_not 46 | what.each { |i| expect(it).to include %{modules["required_tree_test/required_file#{i}"]} } 47 | what_not.each { |i| expect(it).not_to include %{modules["required_tree_test/required_file#{i}"]} } 48 | end 49 | 50 | ["default", "debug"].each do |pipeline| 51 | asset = app.sprockets['require_tree_test', pipeline: pipeline, accept: "application/javascript"] 52 | expect(asset).to be_truthy 53 | does_include.(asset.to_s, [1,2], [3], nil, "UNIQUESTRING") 54 | get "/assets/require_tree_test.#{pipeline}.js" 55 | does_include.(last_response.body, [1,2], [3], nil, "UNIQUESTRING") 56 | 57 | sleep 1 # Make sure to modify mtime 58 | 59 | File.write(__dir__+"/../fixtures/required_tree_test/required_file2.rb", "p 'UNIQUESTRING1'") 60 | 61 | asset = app.sprockets['require_tree_test', pipeline: pipeline, accept: "application/javascript"] 62 | expect(asset).to be_truthy 63 | does_include.(asset.to_s, [1,2], [3], "UNIQUESTRING1") 64 | get "/assets/require_tree_test.#{pipeline}.js" 65 | does_include.(last_response.body, [1,2], [3], "UNIQUESTRING1") 66 | 67 | sleep 1 # Make sure to modify mtime 68 | 69 | File.write(__dir__+"/../fixtures/required_tree_test/required_file2.rb", "p 'UNIQUESTRING2'") 70 | 71 | asset = app.sprockets['require_tree_test', pipeline: pipeline, accept: "application/javascript"] 72 | expect(asset).to be_truthy 73 | does_include.(asset.to_s, [1,2], [3], "UNIQUESTRING2") 74 | get "/assets/require_tree_test.#{pipeline}.js" 75 | does_include.(last_response.body, [1,2], [3], "UNIQUESTRING2") 76 | 77 | sleep 1 # Make sure to modify mtime 78 | 79 | File.write(__dir__+"/../fixtures/required_tree_test/required_file2.rb", "p 'UNIQUESTRING3'") 80 | File.write(__dir__+"/../fixtures/required_tree_test/required_file3.rb", "p 3") 81 | 82 | asset = app.sprockets['require_tree_test', pipeline: pipeline, accept: "application/javascript"] 83 | expect(asset).to be_truthy 84 | does_include.(asset.to_s, [1,2,3], [], "UNIQUESTRING3") 85 | get "/assets/require_tree_test.#{pipeline}.js" 86 | does_include.(last_response.body, [1,2], [], "UNIQUESTRING3") # fails with 3 - it doesn't get new files 87 | 88 | sleep 1 # Make sure to modify mtime 89 | ensure 90 | File.write(__dir__+"/../fixtures/required_tree_test/required_file2.rb", "p 2\n") 91 | File.unlink(__dir__+"/../fixtures/required_tree_test/required_file3.rb") rescue nil 92 | end 93 | end 94 | 95 | describe 'source maps' do 96 | RSpec::Matchers.define :include_source_map do 97 | match do |actual_response| 98 | actual_response.ok? && 99 | actual_response.body.lines.last.start_with?('//# sourceMappingURL=') 100 | end 101 | end 102 | 103 | def extract_map(path, response) 104 | last_line = response.body.lines.last 105 | if last_line.start_with? "//# sourceMappingURL=data:application/json;base64," 106 | b64_encoded = last_line.split('//# sourceMappingURL=data:application/json;base64,', 2)[1] 107 | json_string = Base64.decode64(b64_encoded) 108 | else 109 | map_file = last_line.split('//# sourceMappingURL=', 2)[1].chomp 110 | map_file = relative_path(path, map_file) 111 | map_file = get map_file 112 | json_string = map_file.body 113 | end 114 | JSON.parse(json_string, symbolize_names: true) 115 | end 116 | 117 | def relative_path(path, file) 118 | URI.join("http://example.com/", path, file).path 119 | end 120 | 121 | it 'serves map for a top level file' do 122 | path = '/assets/opal_file.debug.js' 123 | get path 124 | expect(last_response).to include_source_map 125 | 126 | map = extract_map(path, last_response) 127 | 128 | expect(map[:file]).to eq('opal_file.rb') 129 | expect(map[:sections].last[:map][:sources]).to eq(['opal_file.source.rb']) 130 | 131 | expect(get(relative_path(path, map[:sections].last[:map][:sources][0])).body.chomp) 132 | .to eq("require 'opal'\nputs 'hi from opal!'") 133 | end 134 | 135 | it 'serves map for a subfolder file' do 136 | path = '/assets/source_map/subfolder/other_file.debug.js' 137 | get path 138 | expect(last_response).to include_source_map 139 | 140 | map = extract_map(path, last_response) 141 | 142 | expect(map[:file]).to eq('source_map/subfolder/other_file.rb') 143 | expect(map[:sections].first[:map][:sources]).to eq(['other_file.source.rb']) 144 | 145 | expect(get(relative_path(path, map[:sections][0][:map][:sources][0])).body.chomp) 146 | .to eq("puts 'other!'") 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/opal/sprockets/processor.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require 'base64' 3 | require 'tilt/opal' 4 | require 'sprockets' 5 | require 'opal/builder' 6 | require 'opal/sprockets' 7 | 8 | # Internal: The Processor class is used to make ruby files (with .rb or .opal 9 | # extensions) available to any sprockets based server. Processor will then 10 | # get passed any ruby source file to build. 11 | class Opal::Sprockets::Processor 12 | @@cache_key = nil 13 | def self.cache_key 14 | gem_config = Gem.loaded_specs.map {|gem_key, gem_spec| [gem_spec.name, gem_spec.version.to_s] } 15 | @@cache_key ||= ['Opal', Opal::VERSION, Opal::Config.config, gem_config].to_json.freeze 16 | end 17 | 18 | def self.reset_cache_key! 19 | @@cache_key = nil 20 | end 21 | 22 | def self.call(input) 23 | data, map, dependencies, required = input[:cache].fetch([self.cache_key, input[:filename], input[:data]]) do 24 | new(input).call 25 | end 26 | 27 | if map 28 | map = ::Sprockets::SourceMapUtils.combine_source_maps(input[:metadata][:map], map) 29 | end 30 | 31 | { 32 | data: data, 33 | map: map, 34 | dependencies: input[:metadata][:dependencies].to_a + dependencies.to_a, 35 | required: input[:metadata][:required].to_a + required.to_a, 36 | } 37 | end 38 | 39 | def initialize(input) 40 | @input = input 41 | @sprockets = input[:environment] 42 | @context = sprockets.context_class.new(input) 43 | @data = input[:data] 44 | end 45 | 46 | attr_reader :input, :sprockets, :context, :data 47 | 48 | # In Sprockets 3 logical_path has an odd behavior when the filename is "index" 49 | # thus we need to bake our own logical_path 50 | def logical_path 51 | @logical_path ||= context.filename.gsub(%r{^#{Regexp.escape(context.root_path)}/?(.*?)}, '\1') 52 | end 53 | 54 | def call 55 | compiler_options = Opal::Config.compiler_options.merge(requirable: true, file: logical_path) 56 | 57 | compiler = Opal::Compiler.new(data, compiler_options) 58 | result = compiler.compile 59 | 60 | process_requires(compiler.requires, context) 61 | process_required_trees(compiler.required_trees, context) 62 | 63 | if Opal::Config.source_map_enabled 64 | map = compiler.source_map.as_json.transform_keys!(&:to_s) 65 | map["sources"][0] = input[:filename] 66 | map = ::Sprockets::SourceMapUtils.format_source_map(map, input) 67 | end 68 | 69 | [result.to_s, map , context.metadata[:dependencies], context.metadata[:required]] 70 | end 71 | 72 | def sprockets_extnames_regexp 73 | @sprockets_extnames_regexp ||= Opal::Sprockets.sprockets_extnames_regexp(@sprockets) 74 | end 75 | 76 | def process_requires(requires, context) 77 | requires.each do |required| 78 | required = required.to_s.sub(sprockets_extnames_regexp, '') 79 | context.require_asset required unless ::Opal::Config.stubbed_files.include? required 80 | end 81 | end 82 | 83 | # Internal: Add files required with `require_tree` as asset dependencies. 84 | # 85 | # Mimics (v2) Sprockets::DirectiveProcessor#process_require_tree_directive 86 | def process_required_trees(required_trees, context) 87 | return if required_trees.empty? 88 | 89 | # This is the root dir of the logical path, we need this because 90 | # the compiler gives us the path relative to the file's logical path. 91 | dirname = File.dirname(input[:filename]).gsub(/#{Regexp.escape File.dirname(context.logical_path)}#{Opal::REGEXP_END}/, '') 92 | dirname = Pathname(dirname) 93 | 94 | required_trees.each do |original_required_tree| 95 | required_tree = Pathname(original_required_tree) 96 | 97 | unless required_tree.relative? 98 | raise ArgumentError, "require_tree argument must be a relative path: #{required_tree.inspect}" 99 | end 100 | 101 | required_tree = dirname.join(input[:filename], '..', required_tree) 102 | 103 | unless required_tree.directory? 104 | raise ArgumentError, "require_tree argument must be a directory: #{{source: original_required_tree, pathname: required_tree}.inspect}" 105 | end 106 | 107 | context.depend_on required_tree.to_s 108 | 109 | environment = context.environment 110 | 111 | processor = ::Sprockets::DirectiveProcessor.new 112 | processor.instance_variable_set('@dirname', File.dirname(input[:filename])) 113 | processor.instance_variable_set('@environment', environment) 114 | path = processor.__send__(:expand_relative_dirname, :require_tree, original_required_tree) 115 | absolute_paths = environment.__send__(:stat_sorted_tree_with_dependencies, path).first.map(&:first) 116 | 117 | absolute_paths.each do |path| 118 | path = Pathname(path) 119 | pathname = path.relative_path_from(dirname).to_s 120 | pathname_noext = pathname.sub(sprockets_extnames_regexp, '') 121 | 122 | if path.to_s == logical_path then next 123 | elsif ::Opal::Config.stubbed_files.include?(pathname_noext) then next 124 | elsif path.directory? then context.depend_on(path.to_s) 125 | else context.require_asset(pathname_noext) 126 | end 127 | end 128 | end 129 | end 130 | 131 | private 132 | 133 | def to_data_uri_comment(map_to_json) 134 | "//# sourceMappingURL=data:application/json;base64,#{Base64.encode64(map_to_json).delete("\n")}" 135 | end 136 | 137 | def stubbed_files 138 | ::Opal::Config.stubbed_files 139 | end 140 | 141 | module PlainJavaScriptLoader 142 | def self.call(input) 143 | sprockets = input[:environment] 144 | 145 | opal_extnames_regexp = Opal::Sprockets.sprockets_extnames_regexp(sprockets, opal_only: true) 146 | 147 | if input[:filename] =~ opal_extnames_regexp 148 | input[:data] 149 | else 150 | "#{input[:data]};#{Opal::Sprockets.loaded_asset(input[:name])}" 151 | end 152 | end 153 | end 154 | end 155 | 156 | Sprockets.register_mime_type 'application/ruby', extensions: ['.rb', '.opal', '.js.rb', '.js.opal'] 157 | Sprockets.register_transformer 'application/ruby', 'application/javascript', Opal::Sprockets::Processor 158 | Opal::Sprockets.register_mime_type 'application/ruby' 159 | 160 | Sprockets.register_mime_type 'application/ruby+ruby', extensions: ['.rb.erb', '.opal.erb', '.js.rb.erb', '.js.opal.erb'] 161 | Sprockets.register_transformer 'application/ruby+ruby', 'application/ruby', Sprockets::ERBProcessor 162 | Opal::Sprockets.register_mime_type 'application/ruby+ruby' 163 | 164 | Sprockets.register_preprocessor 'application/ruby', Sprockets::DirectiveProcessor.new(comments: ["#"]) 165 | Sprockets.register_preprocessor 'application/ruby+ruby', Sprockets::DirectiveProcessor.new(comments: ["#"]) 166 | 167 | Sprockets.register_postprocessor 'application/javascript', Opal::Sprockets::Processor::PlainJavaScriptLoader 168 | --------------------------------------------------------------------------------