-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
How to use this gem from within rails engines? #348
Comments
@fiedl Out-of-the-box webpacker doesn't support engines yet. There are quite a few moving pieces that needs to be considered for this setup to work properly and usually people will have different use cases. I started doing some work on this but it's going to take time. Feel free to leave any ideas/suggestions 👍 |
@gauravtiwari Yes, after diving into this, I see your point. One would have to decide for each component where it belongs---to the engine, the main app, or both---and tie the pieces together in the right manner. Some thoughts on this:
|
@gauravtiwari I started adding webpacker support to ManageIQ, where the UI is just an engine (so, app root != UI root), and we may need to support reading assets from multiple engines, and we need to output them to the rails root folder, not engine root. To achieve that, I needed to override most of the webpacker methods to use the engine root instead of hardcoding .. And I still haven't figured out what would be needed to be able to call I still haven't finished work on the "read assets from multiple engines" bit, but so far, I have a rake task that outputs a json of all the engines and their root paths (and filters those engines by existence of So.. I think at least these changes would be needed:
If you're interested in the changes that we needed (and feel free to criticize and/or make suggestions), ManageIQ/manageiq-ui-classic#1132 . Furthermore (and these are only my personal opinion):
EDIT: Oh, and if there was a nice way of disabling the EDIT2: Aand making all the compile-time yarn deps as |
Right now i'm doing this, but obviously the solution is quite incomplete. Here is what I'm doing, what I see as the potential solution by default wepbacker will always look for the manifest in the /public/packs folder of the Root application, that's because the default For webpacker to be successful in an engine environment we need things to be isolated like every other part. Which means, when we install the engine into an app, it needs to have an 'install step' that basically installs the pre-built modules into the rails app We will probably need to modify the I will have more clarity on this as I develop the solution. right now for me developing the engine with JS code I am just using a symlink from the dummy app's A question for @dhh is also that should the Root app using the engine have to worry about compiling an engine's assets? If not it should make thing simpler since we would expect the JS code from the engine to already be compiled and usable which would mean we just need to make the |
I've made a POC using webpack and rails engines that I believe that can be reused to solve this request: see ohadlevy/foreman@2afa796 for a reference. |
I would love to see some progress on this feature - this is preventing me from using webpacker in my newest project. |
Please Do Investigate 👍
… On Jul 19, 2017, at 23:03, Jevon Wright ***@***.***> wrote:
I would love to see some progress on this feature - this is preventing me from using webpacker in my newest project.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
we have been using webpack successfully (without webpacker) with rails engines on theforeman/foreman project. |
Sounds like something we should be able to automate 👍
…On Thu, Jul 20, 2017 at 7:01 AM, Ohad Levy ***@***.***> wrote:
we have been using webpack successfully (without webpacker) with rails
engines on theforeman/foreman <https://github.com/theforeman/foreman>
project.
they key was to create a simple ruby script
<https://github.com/theforeman/foreman/blob/develop/script/plugin_webpack_directories.rb>
that loads all engines paths, and pass that output as json to webpack,
which in turn simply add those path to its lookup paths
<https://github.com/theforeman/foreman/blob/develop/config/webpack.config.js#L30>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#348 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAKtcL8JHDO8sv-eFyRzPOOtBxPNcmoks5sP0GAgaJpZM4NUgTi>
.
|
Should the Root app using the engine have to worry about compiling an engine's assets? On the surface, it's a fair argument that an asset engine (which is essentially what a webpack app gem is) could be expected to have all the assets pre-compiled, because... once it's released the assets aren't going to change... but looking at how assets in engines currently work, that is not the case, probably to allow including uncompiled assets within other uncompiled assets in the Root app (e.g. application.scss could use mixins from an engine's scss files) if that's the intent of the engine. I'm not totally sure whether that would ever be the case with a webpack engine I guess... is it feasible that there might be a use case for an engine that provides pieces to be used within a larger webpack app? Perhaps the engine would expose some Elm module or React component, and the Root app's UI would utilize that component from its own Elm/React app? In Elm at least, that actually doesn't make sense as a use case, because all modules have to be explicitly included in the elm-package.json, and will then be added to the dependency graph independently of any Rails Engine. But I'm not sure the same can be said of every potential webpacked app - for instance, what if your Engine's app is just straight ES6 that exposes some modules? Could the Root app import those modules from the engine to be used as potential internal components of it's reactive UI? If that kind of integration is possible (and useful, which seems likely to me if it's possible), then in order to facilitate it, a Root app obviously needs to be able to compile the engine's assets (after they are included and used however they are going to be used). My main argument against allowing this type of integration, through a Rails Engine specifically, would be that it's mixing what are becoming ever more the separate responsibilities of the server-side app and the client-side app. That is, if your reactive UI requires some component, it should import it using a javascript dependency manager like npm, whereas the Gemfile should manage dependencies for your server-side Rails app (which may include some compiled reactive UI app to be used on the front-end, like any other asset to be sent to the client) My question is: is that a good enough argument to make it impossible using webpacker (by assuming there will be no precompiled integration between the UI components of an engine and its root app, and hence not providing any considerations to facilitate such integration)? |
I was originally patiently waiting for this kind of support so I could upgrade a gem I manage to be used with webpacker, but @mltsy's argument resonated pretty well with me. Javascript provided by an engine to be used through webpacker could just be put in (an) npm package(s). This would also allow the javascript to become part of the npm ecosystem, becoming a dependency or depending on other packages. The engine could still provide CSS and JS when needed through sprockets. One downside is the potential overlap of an engine providing a page that depends on javascript that is also provided in its npm package. It could lead to a few use-cases of an end user having to download the same javascript code from multiple locations |
@mltsy @tomprats I partially agree with this view. However, let's say the engine is a pluggable CMS that defines its own JS dependencies (similarly to gems in As of now, in my case, most of the JS dependencies defined in a Rails engine are facilitated via https://rails-assets.org. It would be fantastic to be able to define them via webpacker. |
Yeah - this is a fairly unique situation where we have essentially two dependency managers in a single application (bundler and npm), and specifically the situation you're describing, where we have a Rails app with a bundler dependency on a Rails Engine, and an npm dependency on the "webpack app" (for lack of a better term, assuming it's not published as an npm package) that is defined in the Rails Engine. And that's the sticking point - the Rails engine "depends on" the webpack app that is implemented in its The best solution I can think of to handle that would be to somehow create/expose a kind of meta-package (or legitimate package?) out of the webpack app(s) contained within the Rails Engine, and automatically add that package to the |
@mltsy @tomasc @tomprats @ohadlevy @soundasleep @fiedl: The beta version of React on Rails is built on top of Webpacker and here are the docs for using it. I just released v9 beta.1, built on top of: |
How about specifying more than one in default: &default
source_path:
- app/javascript
- engine/javascript
source_entry_path: packs
public_output_path: packs
cache_path: tmp/cache/webpacker Specify the entry point of the application and the entry point of the engine, and build it with the application. |
Thanks @chimame, looks like a good idea, I will give it a try and report back. |
@chimame I have successfully done something similar to your suggestion. Since all the engines in my app are in vendor/gems, my source_path definition in webpacker.yml is:
And this pulls in the modules from all the engines as well as the main app. This is working very well for me, except I had to hack a single line in the rails webpacker npm module, as the manifest.json keys were not correct. I would happily create a PR for this, but I think there may be other use cases I'm not thinking of. Also, although my hack doesn't break the tests, I don't think this line of code is covered by tests and it's not clear to me why it was written that way. I have a small example app with this implemented at https://github.com/lazylester/webpack_example |
Hmm... You're right! That's cool! At least partly... I mean that example app isn't quite the proof of concept that is necessary, because it doesn't actually I was slightly misunderstanding the role of npm and webpack. npm defines and downloads the sources (dependencies) from which webpack can choose what to include in any given pack. An engine is just another source for webpack to pull from, so it supplements the sources supplied by npm. Then webpack can choose from either npm's dependencies or the additional sources provided by the engine(s)... Now... that is still a little bit disconcerting, just because those sources are outside of npm's dependency graph, meaning... the other thing we still need to solve is how to install the dependencies of the engine's pack(s). I see your "inventory" engine has no package.json (no JS dependencies) - that's not too likely in the real world, I imagine. Would you want to try adding and using one dependency and see if that works? I can't imagine running |
@mitsy you're right, I don't currently require any modules in the engine. I have done it elsewhere but forgot to include it in the example app. I'll update the example and post here. However my engines do not have any of their own npm dependencies, just local modules, within the engine, and a global (ractivejs) module that is in the main app's node modules. So, yes, my approach has limitations. and doesn't cover many use cases. But it gets me to production! |
@justin808 - now that I understand what I'm looking for more I looked through the docs for using react_on_rails to see how you're handling this issue too. I see you're using a rails task to run a command provided by the engine (configuration.build_production_command) to compile the engine's pack, but (and this may be my ignorance about webpack) I see two issues in using this as a general solution:
|
Aha! Maybe we could use npm's local dependency feature? Yarn also respects this syntax... so we could tell the user to add this local dependency ( I have to try this... when I get a chance... (if anybody else does, I'd love to hear the result) |
@mltsy I think it's a good idea. If both the idea to add to my
|
@mitsy I updated my example app https://github.com/lazylester/webpack_example. The engine now has a local module of its own and npm modules. You'd probably have to build the packs for the engines in addition to the packs for the main app in a build script, but I assume that's not a big deal, since there has to be a build script anyway, right? I'll generate a PR for the webpacker change that permitted this to work. It may or may not be accepted, depending on whether it breaks something else. As I mentioned before, the tests still pass. |
Pull request: #875 |
Nice! So... I guess there are two use-cases for engines here which have different solutions:
In case 2, that's what react_on_rails seems to be doing - it provides a rails task for compiling the engine's pack, and then you can use it like any other asset. @lazylester and @chimame's solutions are nicer ways to be able to do that without having to run a separate task, by including the engine's entry points in the webpacker sources of the main app. ⭐ The use case I'm looking to solve for my personal use is case 1 though. And now that I think about it, we don't even need to include the engine's entry points in the webpacker sources to enable that, we just need to depend on it as a local dependency in package.json right? If that's true, it makes this all very clean, since there are orthogonal/independent solutions to each use case, and each can be employed depending on the purpose of the engine. (I still have to try the local dependency thing though for case 1, and the specifics of automating case 2 still need to be ironed out, since neither suggestion currently works with webpacker) Since they seem to be orthogonal concerns, I suggest we open a new ticket for one of these use cases, and clarify the title of this ticket to represent the other - but I'm not sure which one corresponds best to the original post here... @fiedl ? |
@tvdeyen are you choosing to release npm packages with same versions as your ruby gems, with every release, as rails does? If so, do you have any tooling to make that harder to mess up? Do you have anything in place to ensure the user is using an appropriate or compatible version of the npm gem, corresponding to the ruby gem version they are using? Or if not "ensure", to even let them figure out what versions are compatible? These are the two areas I could see leading to problems, and could really use some 'best practices' or examples or recommendations on. |
@jrochkind No, not yet. But I could imagine some kind of This is something that could probably live in @dhh how do you manage this in Rails gems and NPM modules? Is there something that could be make accessible for Engine authors? Would be great. |
@jrochkind something like this? 🤔 Edit: Version that uses the actual resolved npm package version initializer "alchemy.check_package_version" do
yarn_list = `yarn list --silent --flat --depth 0 --pattern alchemy_cms`
package_version = yarn_list.match(/\d\.\d\.\d/).to_s
if Gem::Version.new(package_version) < Alchemy.gem_version
abort "Your Alchemy npm package is outdated, please run `yarn upgrade @alchemy_cms/admin`"
end
end There are still a couple of caveats here:
WDYT? |
Yeah, I think this approach makes sense, but I suspect there will be all sorts of edge case details to get right, it'll have to be worked out. I believe for pre-releases, different version strings are required. I think it would be nice if there were an out of the box maintained solution to this, perhaps from webpacker or rails. I think it's currently these issues are currently the biggest additional pain to separating JS into an npm package; with them solved it could seem no more challenging than the old sprockets-focused solution of packaging your JS in the gem. |
This is not perfect, but works pretty well. It figures out what the paths are to the engines on the file system, and then adds them as entry points to the parent Webpacker. Get paths to engines on file system#! /usr/bin/env ruby
# file: get_engine_paths
require 'bundler'
require 'json'
# gem names in Gemfile
engine_names = ['my-gem-name']
engine_paths = Bundler.load.specs
.select{ |dep| engine_names.include?(dep.name) }
.map{ |dep| dep.to_spec.full_gem_path }
puts engine_paths.to_json Needs to be executable ( Make Webpacker find packs in engines// file: webpack/environment.js
const { environment } = require("@rails/webpacker");
const { execSync } = require("child_process");
const { basename, resolve } = require("path");
const { readdirSync } = require("fs");
const babelLoader = environment.loaders.get("babel");
// Get paths to all engines' folders
const scriptPath = resolve(__dirname, "./get_engine_paths");
const buffer = execSync(scriptPath);
const enginePaths = JSON.parse(buffer);
enginePaths.forEach((path) => {
const packsFolderPath = `${path}/app/javascript/packs`;
const entryFiles = readdirSync(packsFolderPath);
entryFiles.forEach((file) => {
// File name without .js
const name = basename(file, ".js");
const entryPath = `${packsFolderPath}/${file}`;
environment.entry.set(name, entryPath);
});
// Otherwise babel won't transpile the file
babelLoader.include.push(`${path}/app/javascript`);
});
module.exports = environment; Run
|
@valterkraemer does that solution assume the path is always the same in every environment, or does it actually look it up "live"? At asset compile time? Asset delivery time? Of course the path is usually diferent between a dev and production deploy, and sometimes can be different between different production deploys. |
It looks it up at asset build time, so it should have no problem with changing paths. |
Introduce new `lcms_engine_javascript_pack_tag` method to be used to include(and compile) lcms-engine based packs. This is a solution to be able to isolate project specific packs and packs created inside the gem. As an alternative we could use the solution provided here - rails/webpacker#348 (comment) This looks more reliable to me.
Introduce new `lcms_engine_javascript_pack_tag` method to be used to include(and compile) lcms-engine based packs. This is a solution to be able to isolate project specific packs and packs created inside the gem. As an alternative we could use the solution provided here - rails/webpacker#348 (comment) This looks more reliable to me. - Update Dockerfile to set explicitly locale. - Update gems to the latest versions
Introduce new `lcms_engine_javascript_pack_tag` method to be used to include(and compile) lcms-engine based packs. This is a solution to be able to isolate project specific packs and packs created inside the gem. As an alternative we could use the solution provided here - rails/webpacker#348 (comment) This looks more reliable to me. - Update Dockerfile to set explicitly locale.
We have a pretty low-effort way of adding JS code from engines into Webpacker, by utilizing the existing configuration for additional paths. This PR adds all installed engines to `Configuration#additional_paths` automatically, so JS from those engines can be imported like so: ```javascript import Engine from "my-cool-engine" ``` ...as long as there's an **app/javascript/my-cool-engine/index.js** file. As it stands right now, there's no automatic namespacing or anything. We're just looking for all files in `app/javascript` in all engines. Not sure if that's the approach we should go with or not, but it was the easiest way from point A to point B, so I felt like it was a good first step towards getting rails#348 resolved.
@valterkraemer I have a similar approach, but it uses master...tubbo:add-installed-engines-to-additional-load-paths You use it by calling the const { environment, gem } = require("@rails/webpacker")
environment.config.resolve.modules = [ gem("my-engine") ]
module.exports = environment Then in your JS code: import MyEngine from "my-engine" This does require your code to be well namespaced, there's no automatic namespacing...it's just as if the files lived side by side in your app/javascript folder. So files in different gems with the same name would conflict with one another, and I suppose whichever file Webpack found first would be the one it used. But I think that's a decent trade-off for now. |
@tubbo that's exciting. Do you think it would be feasible to turn it into a shareable package so other people can use this technique with share code? I guess it's functionality spans both JS and ruby, so maybe not? |
Thanks @tubbo. Weren't aware of |
Not sure if this'll help anyone but I had good success with the following. In my setup I have a folder
A slight difference in this one is the
Hopefully it's easy enough to see how this could be expanded for uses other than Stimulus.js. |
For anyone who's coming down this road, here's a summary of my research over the past few days: There are four primary options in terms of using webpacker with a Rails Engine / Gem. The first point of decision is whether you need your modules to be isolated from each other or if you're okay sharing between the host app and the engine. If you choose isolation, then your engine assets are compiled on their own and loaded separately. This is probably the right choice if you're worried about conflicts between the two sides, or if you can't realistically specify what version of a module the host app must use (e.g. a FOSS engine that has to operate as a black box and can't guarantee that the host app won't have a conflicting module version installed). (If it's not quite clear what problem can occur here, imagine this situation: the engine's javascript is written using The problem is that isolation is tricky to achieve. There's no consensus among those who have attempted to install a separate instance of webpacker within their engine as to whether it works, let alone whether it works well. If you can force the host app to use the engine's module versions, i.e. you can say by dint of controlling both that they will both use Options 1 and 2: Shared ModulesWhat this should look like when it's all working: // host: /app/javascript/packs/application.js
import Rails from "@rails/ujs"
//...
import "my_engine" There are three primary obstacles to overcome with shared modules:
In both cases, you'll need to set the engine up as follows. We'll assume that your engine is called
// Require your modules here, but you'll run into conflicts if you
// try to let the host app AND the engine start Rails-UJS /
// Turbolinks etc... I generally let the host / dummy app start them.
console.log("Called from my_engine."); There are two different options here depending on whether the source for your engine lives within the host app. Option One: Engine source within host app (e.g.
|
@robyurkowski wow, you seem to have figured out how to do some things I didn't think were possible! Thank you so much for that! I wonder if you want to write that up as a blog post somewhere too maybe? (dev.to?) The option 2 seems closest in results to what we do/did with engines and sprockets -- that's what I had pretty much given up as not possible, but i just didn't understand webpack well enough to figure it out.(execSync... woah!) I wonder if anything could/should be done in webpacker or another third party gem to encapsulate/DRY some of that, to make it even easier/more reliable to use? or in particular to avoid the need to "freedom patch" webpacker like this, add a feature to make it configurable instead of requiring a patch? Although wait actually -- does this allow the engine to express it's own npm dependencies that will get integrated too, or no, just isolated assets? If not dependencies, that could be a barrier. "first-class" support for an engine providing assets that can be referenced in the host app's webpacker would be a huge advance for rails webpacker ecosystem. |
My gut tells me that the simplest way around the assets issues:
|
I wanted to report back in that there was two other significant changes that I had to make with Option 2 above. // host app: config/webpack/environment.js
environment.config.resolve.modules = [
gem("my_engine"),
];
// Adding the line below
environment.loaders.get("babel").include.push(gem("my_engine"));
module.exports = environment; Without this, the engine's NPM dependencies The second thing is more of a resolution thing: instead of doing (I'll try to encorporate these back into the body above when I get a bit of time.) @jrochkind I'll definitely turn it into a blog post at some point -- thanks for the encouragement! With the above fixes, this does allow the engine to express its own NPM dependencies. I was able to get Stimulus, ActionCable, Cable_Ready, and StimulusReflex working almost purely in the engine. There's a few things that need to be fixed up and that I need to clear up but I at least have a working proof of concept that you can put webpack assets in a Rails engine. |
@robyurkowski I totally don't understand how engine's npm dependencies get picked up (and where engine would express them; just in a package.json at top-level of engine source dir?). I haven't spent time with it in code though, just reading your text. Btu this sounds promissing. It's just complicated enough that, if it can be tested and proven... I wonder if it makes sense to create re-usable code packages to implement this? Maybe one npm package and one ruby gem, to turn incorporation of engine JS and dependencies into one line in JS and one line in ruby? But one step at a time. First blog post would be great, and the several of us trying to reproduce "manually" to verify it works for us, I guess? |
@jrochkind That's exactly right. I have a package.json that's in my engine root; there's also the yarn postinstall script in the host app that will trigger the engine's yarn install. The engine also produces its own And I agree with you -- this is complicated enough that even with my notes and git logs it's not super straightforward to build a sample app. I would definitely hope that we can get some of this merged into Webpacker and Rails proper. I'd rather not have to use external libs to do it if I can get my way; it's definitely not unusual that people want to load assets from their gems, let alone their engines. In fact, it's one of the critical things that's preventing the retirement of Sprockets, I think. I'm under the gun right now but in the next few weeks I'll hopefully have some time to at least throw up a sample app and a blog post or two about my findings. In the meantime I think there's enough guidance in my post if you want to try and throw one together; I can definitely spare minutes here and there to debug if you run into problems. |
I think it'd be very helpful if you can post whatever you can here in this issue, so that anyone else that might be having this issue can pick up the torch. I also definitely agree that this is a blocker to getting off of sprockets, for anyone who uses engines with assets, especially for private projects where making an NPM package isn't realistic |
An open-source example is very helpful. Check out what I did with https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh |
@bablu-cs, you'd be better asking on StackOverflow. This error isn't related to the issue we're talking about here as it's not related to |
I'm not sure if this is the right approach and maybe I've not understood this new part of the pipeline, yet, but:
Suppose, the major part of the app code lives inside a rails engine including all javascripts. And there are several main apps using that engine. The main apps only contain some layout changes and some minor patches.
Therefore, in order to integrate the webpacker gem into this setup, I'm trying to do the heavy lifting inside the engine, i.e. keep the work that has to be done inside all the main apps as little as possible.
How would I do that? Any pointers or suggestions are appreciated.
I'm not sure if this is a duplicate of #21. But in any case, I'd like to conclude this issue with a step-by-step guide how to approach this, for others facing the same use case.
What to do in the engine
webpacker
in the*.gemspec
file.require 'webpacker'
in thelib/foo/engine.rb
.What to do in each main app
bundle install
./bin/webpack-dev-server
in theProcfile
if using Foreman.The text was updated successfully, but these errors were encountered: