├── .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 |
--------------------------------------------------------------------------------