diff --git a/docs/engines.md b/docs/engines.md
new file mode 100644
index 000000000..e50b2f16c
--- /dev/null
+++ b/docs/engines.md
@@ -0,0 +1,135 @@
+# Using in Rails engines
+
+If the application UI consists of multiple frontend application, you'd probably like to isolate their building too (e.g. if you use different frameworks/versions). Hence we needed our webpack(-er) to be isolated too: separate `package.json`, dev server, compilation process.
+
+You can do this by adding another Webpacker instance to your application.
+
+This guide describes how to do that using [Rails engines](https://guides.rubyonrails.org/engines.html).
+
+
+## Step 1: create Rails engine.
+
+First, you create a Rails engine (say, `MyEngine`). See the offical [Rails guide](https://guides.rubyonrails.org/engines.html).
+
+## Step 2: install Webpacker within the engine.
+
+There is no built-in tasks to install Webpacker within the engine, thus you have to add all the require files manually (you can copy them from the main app):
+- Add `config/webpacker.yml` and `config/webpack/*.js` files
+- Add `bin/webpack` and `bin/webpack-dev-server` files
+- Add `package.json` with required deps.
+
+
+## Step 3: configure Webpacker instance.
+
+```ruby
+module MyEngine
+ ROOT_PATH = Pathname.new(File.join(__dir__, ".."))
+
+ class << self
+ def webpacker
+ @webpacker ||= ::Webpacker::Instance.new(
+ root_path: ROOT_PATH,
+ config_path: ROOT_PATH.join("config/webpacker.yml")
+ )
+ end
+ end
+end
+```
+
+## Step 4: Configure dev server proxy.
+
+```ruby
+module MyEngine
+ class Engine < ::Rails::Engine
+ initializer "webpacker.proxy" do |app|
+ insert_middleware = begin
+ MyEngine.webpacker.config.dev_server.present?
+ rescue
+ nil
+ end
+ next unless insert_middleware
+
+ app.middleware.insert_before(
+ 0, "Webpacker::DevServerProxy",
+ ssl_verify_none: true,
+ webpacker: MyEngine.webpacker
+ )
+ end
+ end
+end
+```
+
+If you have multiple webpackers, you would probably want to run multiple dev servers at a time, and hence be able to configure their setting through env vars (e.g. within a `docker-compose.yml` file):
+
+```yml
+# webpacker.yml
+# ...
+development:
+ # ...
+ dev_server:
+ env_prefix: "MY_ENGINE_WEBPACKER_DEV_SERVER"
+ # ...
+```
+
+## Step 5: configure helper.
+
+```ruby
+require "webpacker/helper"
+
+module MyEngine
+ module ApplicationHelper
+ include ::Webpacker::Helper
+
+ def current_webpacker_instance
+ MyEngine.webpacker
+ end
+ end
+end
+```
+
+Now you can use `stylesheet_pack_tag` and `javascript_pack_tag` from within your engine.
+
+## Step 6: rake tasks.
+
+Add Rake task to compile assets in production (`rake my_engine:webpacker:compile`)
+
+```ruby
+namespace :my_engine do
+ namespace :webpacker do
+ desc "Install deps with yarn"
+ task :yarn_install do
+ Dir.chdir(File.join(__dir__, "../..")) do
+ system "yarn install --no-progress --production"
+ end
+ end
+
+ desc "Compile JavaScript packs using webpack for production with digests"
+ task compile: [:yarn_install, :environment] do
+ Webpacker.with_node_env("production") do
+ if MyEngine.webpacker.commands.compile
+ # Successful compilation!
+ else
+ # Failed compilation
+ exit!
+ end
+ end
+ end
+ end
+end
+```
+
+## Step 7: serving compiled packs.
+
+To serve static assets in production via Rails you might need to add a middleware and point it to your engine's webpacker output path:
+
+```ruby
+# application.rb
+
+config.middleware.use(
+ "Rack::Static",
+ urls: ["/my-engine-packs"], root: "my_engine/public"
+)
+```
+
+**NOTE:** in the example above we assume that your `public_output_path` is set to `my-engine-packs` in your engine's `webpacker.yml`.
+
\ No newline at end of file
diff --git a/lib/webpacker/compiler.rb b/lib/webpacker/compiler.rb
index a09590de3..f7d83c7e9 100644
--- a/lib/webpacker/compiler.rb
+++ b/lib/webpacker/compiler.rb
@@ -10,7 +10,7 @@ class Webpacker::Compiler
# Webpacker::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key'
cattr_accessor(:env) { {} }
- delegate :config, :logger, to: :@webpacker
+ delegate :config, :logger, to: :webpacker
def initialize(webpacker)
@webpacker = webpacker
@@ -37,6 +37,8 @@ def stale?
end
private
+ attr_reader :webpacker
+
def last_compilation_digest
compilation_digest_path.read if compilation_digest_path.exist? && config.public_manifest_path.exist?
rescue Errno::ENOENT, Errno::ENOTDIR
@@ -56,7 +58,11 @@ def record_compilation_digest
def run_webpack
logger.info "Compiling…"
- stdout, sterr , status = Open3.capture3(webpack_env, "#{RbConfig.ruby} #{@webpacker.root_path}/bin/webpack")
+ stdout, sterr , status = Open3.capture3(
+ webpack_env,
+ "#{RbConfig.ruby} ./bin/webpack",
+ chdir: File.expand_path(config.root_path)
+ )
if sterr == "" && status.success?
logger.info "Compiled all packs in #{config.public_output_path}"
@@ -71,17 +77,19 @@ def run_webpack
def default_watched_paths
[
*config.resolved_paths_globbed,
- "#{config.source_path.relative_path_from(Rails.root)}/**/*",
+ "#{config.source_path.relative_path_from(config.root_path)}/**/*",
"yarn.lock", "package.json",
"config/webpack/**/*"
].freeze
end
def compilation_digest_path
- config.cache_path.join("last-compilation-digest-#{Webpacker.env}")
+ config.cache_path.join("last-compilation-digest-#{webpacker.env}")
end
def webpack_env
+ return env unless defined?(ActionController::Base)
+
env.merge("WEBPACKER_ASSET_HOST" => ActionController::Base.helpers.compute_asset_host,
"WEBPACKER_RELATIVE_URL_ROOT" => ActionController::Base.relative_url_root)
end
diff --git a/lib/webpacker/dev_server.rb b/lib/webpacker/dev_server.rb
index 12b03f203..44d86cae5 100644
--- a/lib/webpacker/dev_server.rb
+++ b/lib/webpacker/dev_server.rb
@@ -1,4 +1,6 @@
class Webpacker::DevServer
+ DEFAULT_ENV_PREFIX = "WEBPACKER_DEV_SERVER".freeze
+
# Configure dev server connection timeout (in seconds), default: 0.01
# Webpacker.dev_server.connect_timeout = 1
cattr_accessor(:connect_timeout) { 0.01 }
@@ -49,9 +51,13 @@ def pretty?
fetch(:pretty)
end
+ def env_prefix
+ config.dev_server.fetch(:env_prefix, DEFAULT_ENV_PREFIX)
+ end
+
private
def fetch(key)
- ENV["WEBPACKER_DEV_SERVER_#{key.upcase}"] || config.dev_server.fetch(key, defaults[key])
+ ENV["#{env_prefix}_#{key.upcase}"] || config.dev_server.fetch(key, defaults[key])
end
def defaults
diff --git a/lib/webpacker/dev_server_proxy.rb b/lib/webpacker/dev_server_proxy.rb
index ed73ebac0..03787f103 100644
--- a/lib/webpacker/dev_server_proxy.rb
+++ b/lib/webpacker/dev_server_proxy.rb
@@ -1,18 +1,25 @@
require "rack/proxy"
class Webpacker::DevServerProxy < Rack::Proxy
+ delegate :config, :dev_server, to: :@webpacker
+
+ def initialize(app = nil, opts = {})
+ @webpacker = opts.delete(:webpacker) || Webpacker.instance
+ super
+ end
+
def rewrite_response(response)
_status, headers, _body = response
headers.delete "transfer-encoding"
- headers.delete "content-length" if Webpacker.dev_server.running? && Webpacker.dev_server.https?
+ headers.delete "content-length" if dev_server.running? && dev_server.https?
response
end
def perform_request(env)
- if env["PATH_INFO"].start_with?("/#{public_output_uri_path}") && Webpacker.dev_server.running?
- env["HTTP_HOST"] = env["HTTP_X_FORWARDED_HOST"] = env["HTTP_X_FORWARDED_SERVER"] = Webpacker.dev_server.host_with_port
- env["HTTP_X_FORWARDED_PROTO"] = env["HTTP_X_FORWARDED_SCHEME"] = Webpacker.dev_server.protocol
- unless Webpacker.dev_server.https?
+ if env["PATH_INFO"].start_with?("/#{public_output_uri_path}") && dev_server.running?
+ env["HTTP_HOST"] = env["HTTP_X_FORWARDED_HOST"] = env["HTTP_X_FORWARDED_SERVER"] = dev_server.host_with_port
+ env["HTTP_X_FORWARDED_PROTO"] = env["HTTP_X_FORWARDED_SCHEME"] = dev_server.protocol
+ unless dev_server.https?
env["HTTPS"] = env["HTTP_X_FORWARDED_SSL"] = "off"
end
env["SCRIPT_NAME"] = ""
@@ -25,6 +32,6 @@ def perform_request(env)
private
def public_output_uri_path
- Webpacker.config.public_output_path.relative_path_from(Webpacker.config.public_path)
+ config.public_output_path.relative_path_from(config.public_path)
end
end
diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb
index 53ddf9a3d..85cd274ad 100644
--- a/lib/webpacker/helper.rb
+++ b/lib/webpacker/helper.rb
@@ -1,4 +1,11 @@
module Webpacker::Helper
+ # Returns current Webpacker instance.
+ # Could be overriden to use multiple Webpacker
+ # configurations within the same app (e.g. with engines)
+ def current_webpacker_instance
+ Webpacker.instance
+ end
+
# Computes the relative path for a given Webpacker asset.
# Return relative path using manifest.json and passes it to asset_path helper
# This will use asset_path internally, so most of their behaviors will be the same.
@@ -11,8 +18,8 @@ module Webpacker::Helper
# # When extract_css is true in webpacker.yml or the file is not a css:
# <%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css"
def asset_pack_path(name, **options)
- if Webpacker.config.extract_css? || !stylesheet?(name)
- asset_path(Webpacker.manifest.lookup!(name), **options)
+ if current_webpacker_instance.config.extract_css? || !stylesheet?(name)
+ asset_path(current_webpacker_instance.manifest.lookup!(name), **options)
end
end
@@ -28,8 +35,8 @@ def asset_pack_path(name, **options)
# # When extract_css is true in webpacker.yml or the file is not a css:
# <%= asset_pack_url 'calendar.css' %> # => "http://example.com/packs/calendar-1016838bab065ae1e122.css"
def asset_pack_url(name, **options)
- if Webpacker.config.extract_css? || !stylesheet?(name)
- asset_url(Webpacker.manifest.lookup!(name), **options)
+ if current_webpacker_instance.config.extract_css? || !stylesheet?(name)
+ asset_url(current_webpacker_instance.manifest.lookup!(name), **options)
end
end
@@ -40,7 +47,7 @@ def asset_pack_url(name, **options)
# <%= image_pack_tag 'application.png', size: '16x10', alt: 'Edit Entry' %>
#
def image_pack_tag(name, **options)
- image_tag(asset_path(Webpacker.manifest.lookup!(name)), **options)
+ image_tag(asset_path(current_webpacker_instance.manifest.lookup!(name)), **options)
end
# Creates a script tag that references the named pack file, as compiled by webpack per the entries list
@@ -72,7 +79,7 @@ def javascript_pack_tag(*names, **options)
# <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
#
def stylesheet_pack_tag(*names, **options)
- if Webpacker.config.extract_css?
+ if current_webpacker_instance.config.extract_css?
stylesheet_link_tag(*sources_from_pack_manifest(names, type: :stylesheet), **options)
end
end
@@ -83,6 +90,6 @@ def stylesheet?(name)
end
def sources_from_pack_manifest(names, type:)
- names.map { |name| Webpacker.manifest.lookup!(name, type: type) }.flatten
+ names.map { |name| current_webpacker_instance.manifest.lookup!(name, type: type) }.flatten
end
end