Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor singleton usage and make it possible to use with engines #1836

Merged
merged 7 commits into from
Dec 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions docs/engines.md
Original file line number Diff line number Diff line change
@@ -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`.

16 changes: 12 additions & 4 deletions lib/webpacker/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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}"
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion lib/webpacker/dev_server.rb
Original file line number Diff line number Diff line change
@@ -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 }
Expand Down Expand Up @@ -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
Expand Down
19 changes: 13 additions & 6 deletions lib/webpacker/dev_server_proxy.rb
Original file line number Diff line number Diff line change
@@ -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"] = ""
Expand All @@ -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
21 changes: 14 additions & 7 deletions lib/webpacker/helper.rb
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand All @@ -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

Expand All @@ -40,7 +47,7 @@ def asset_pack_url(name, **options)
# <%= image_pack_tag 'application.png', size: '16x10', alt: 'Edit Entry' %>
# <img alt='Edit Entry' src='/packs/application-k344a6d59eef8632c9d1.png' width='16' height='10' />
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
Expand Down Expand Up @@ -72,7 +79,7 @@ def javascript_pack_tag(*names, **options)
# <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
# <link rel="stylesheet" media="screen" href="/packs/calendar-1016838bab065ae1e122.css" 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
Expand All @@ -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