Hi,
When I was testing the support for engines, I configured my app as suggested in the docs: https://github.com/rails/webpacker/blob/master/docs/engines.md
STR
O
D
It seems like maybe we need to support prefixing for javascript_pack_tags in order to support correct routing and requesting of the apps? Like
// Some HTML
<%= javascript_pack_tag "engine-a:TestAppA" %>
<%= javascript_pack_tag "engine-b:TestAppB" %>
// Some HTML
Maybe I am missing something. Looking forward to your response.
Hi, just wanted to follow up on this and see if there was interest from the Webpacker team? @gauravtiwari? Definitely interested in pushing this forward and could take a look at this.
Hi @mathewbaltes. Disclaimer: I initially tuned out issues like these because I have never had any experience with engines.
There are many reasons why you wouldn't load (client side) multiple webpack instances, your two apps should be compiled together, each as packs, not in isolation.
Think further down the line when your code gets minified for production. In isolation, the variables in each instance get transformed into a, b, c etc. Loading these isolated scripts on the same page will result in collisions. App A thinks variable b as a different thing than App B.
Furthermore, frameworks like react and angular will refuse to load if they detect other instances of themselves.
@palkan Initially wrote the doc, I don't have enough experience with engines to make assumptions on what problems engines solve. From what I read in https://github.com/rails/webpacker/pull/1808, their case is very specific, and would not run into the problems I describe because their packs never come into contact. (Also @palkan, with webpack 4, there are new solutions available to isolate code)
Furthermore, frameworks like react and angular will refuse to load if they detect other instances of themselves.
That's the reason why the approach described in the docs uses completely separate Webpack apps)
It assumes having separate frontend applications (served by engines), not depending on each other and each having their own webpacker and only using javascript_pack_tag for its own pack (it's achieved by specified the current_webpacker_instance method poiting to a specific Webpacker).
If you want to provide some JS functionality via engine, which could be used by other engines/apps, that means that you're need to define a JS _package_ and add it as a dependency to your main package.json(AFAIK, it support specified _local_ packages).
Another idea that I'm keeping in my mind for as long as I've started working with Webpacker is an ability to _import_ JS code/package from engines independently on the way they've been installed (RubyGems, Git, path, whatever). Loading code could be implemented via a custom loader; how to specify a package using a Ruby gem name in the package.json鈥攈ave no idea so far.
with webpack 4, there are new solutions available to isolate code
@jakeNiemiec Would be glad to hear more about it)
is an ability to import JS code/package from engines independently on the way they've been installed...how to specify a package using a Ruby gem name in the package.json鈥攈ave no idea so far.
You could do yarn add file:'/path/to/it', but the location would require it's own package.json. I think this is why active storage uses rollup to downstream JS https://github.com/rails/rails/blob/master/activestorage/rollup.config.js. They even need to mirror their package on the JS registry: https://www.npmjs.com/package/@rails/activestorage
@zedtux and I were working out a similar issue in https://github.com/rails/webpacker/issues/2119
@palkan: It assumes having separate frontend applications (served by engines), not depending on each other and each having their own webpacker and only using javascript_pack_tag for its own pack
This is exactly what the concept of a webpacker pack is. In the webpack docs, packs are called entries: https://webpack.js.org/configuration/entry-context/#entry
entry: The point or points where to start the application bundling process. If an array is passed then all items will be processed.
A dynamically loaded module is not an entry point.
Simple rule: one entry point per HTML page. SPA: one entry point, MPA: multiple entry points.

In the end, it _really_ depends on the specifics (example: react can be pretty forgiving, while angular will throw ngZone errors all day). Engines will work as long as you use libs that never pollute the global scope. If apps are built in isolation, the webpack internals can malfunction because they can have different ideas of what require(moduleId) should return. This is what the user above is running into. You can sort of see this if you look at the client-side unminified output above.
Would be glad to hear more about it
Specific splitChunks examples: https://webpack.js.org/plugins/split-chunks-plugin/#defaults-example-2
Webpacker example: https://github.com/rails/webpacker/blob/master/docs/es6.md#dynamiclazy-chunk-loading
I can see this as a common scenario, even if the application contains 1 engine. (e.g. include pack from root application for main layout, include pack from engine for specific page). Especially for non-SPA applications.
As noted earlier, this can cause conflicts with the same JS library included more than once. However, using webpack chunks could break these libraries out to manage this.
You can add the following snippet as a temporary solution in config/initializers/assets.rb
module Webpacker::DynamicTag
def javascript_pack_tag(*names, **options)
return super unless options[:webpacker]
new_helper = self.dup
new_helper.define_singleton_method(:current_webpacker_instance) do
options[:webpacker].constantize.webpacker
end
new_helper.javascript_pack_tag(*names, **options.except(:webpacker))
end
end
Webpacker::Helper.prepend Webpacker::DynamicTag
Add the webpacker method in your root application and/or engines (note: engines code is the same as documented already for webpacker).
# config/application.rb
# Root rails app
module RootApp
# Primary Rails Application configuration
class Application < Rails::Application
#...
end
class << self
def webpacker
@webpacker ||= ::Webpacker.instance
end
end
end
# lib/my_engine.rb
# Engine
module MyEngine
ROOT_PATH = Pathname.new(File.expand_path('../..', __dir__))
class << self
def webpacker
@webpacker ||= ::Webpacker::Instance.new(
root_path: ROOT_PATH,
config_path: ROOT_PATH.join('config/webpacker.yml')
)
end
end
end
Then use the webpacker attribute in the javascript_pack_tag.
<%= javascript_pack_tag 'common', webpacker: 'RootApp' %>
<%= javascript_pack_tag 'application', webpacker: 'RootApp' %>
<%= javascript_pack_tag 'application', webpacker: 'MyEngine' %>
or even
<%= javascript_pack_tag 'common' %>
<%= javascript_pack_tag 'application' %>
<%= javascript_pack_tag 'application', webpacker: 'MyEngine' %>
Most helpful comment
I can see this as a common scenario, even if the application contains 1 engine. (e.g. include pack from root application for main layout, include pack from engine for specific page). Especially for non-SPA applications.
As noted earlier, this can cause conflicts with the same JS library included more than once. However, using webpack chunks could break these libraries out to manage this.
You can add the following snippet as a temporary solution in config/initializers/assets.rb
Add the webpacker method in your root application and/or engines (note: engines code is the same as documented already for webpacker).
Then use the webpacker attribute in the javascript_pack_tag.
or even