Compile/Package Component (or Addon) Standalone

Does anyone know of or can point me in the right direction of a way to package a component or addon into its JS/CSS standalone? We have a use case for this and have a very hacky way of achieving what we want right now but its extraordinarily fragile and cumbersome.

Right now we have a few ui components for docker machine drivers in our app. But docker machine drivers can really be created by anyone and we have customers that do. We don’t want to muddy up our main app’s package with addons, or flood our ui with random 3rd party components that will only be used by a select few. Currently we have a simple skeleton component where the user clones the repo, changes some static fields, adds the ui logic to the skeleton, and finally they host the files somewhere. If they want to add the custom driver ui to our ui they add the path to a db field that we read on app boot. We only use 1 route for the driver ui and swap out components on the fly to get around feeding these into our router map. You can see the craziness that is our skeleton here @ ui-driver-skel

I’ve looked at solutions like ember-giftwrap and Ember Package but both of these have not been maintained in years and are focused on addons. I tried to update giftwrap using the latest ember-cli but I lack the knowledge of Ember internals to really move this forward in a decent amount of time.

I did have the idea that this could be done via an addon that re-exports its single machine driver ui component but again I can’t figure out how to compile the addon code without the addons other dependencies. Doing it this way I think I could avoid router issues and still use the “component” flow described above.

A couple side notes: Rancher server serves a ui from our own cdn, which can be changed via a setting if you’d like to fork or create your own custom UI on top of our api. We dont want to force users to do this if they want to add a custom driver because it creates upstream/downstream maintainability problems.

A lot of our customers have zero front end experience so the solution should be as turn key as possible. The bulk of the custom drivers that are developed take most of the logic from a supported driver (ie we develop and maintain) and peace meal it together :frowning:

If I’m understanding you correctly, the issue is that you want to give your customers a way to write and host customized components that integrate back into your application (when that particular customer is using it).

There’s not really a lot that’s needed to build a component. The Javascript file goes through Babel, where Babel is configured to turn the module into an AMD define. There’s no ember-special magic. The template file goes through the Ember template compiler. That also results in an AMD define. If you put the results of both those things into a file and add it to a webpage, your component will work.

But for your use case, there is a hiccup to what I’ve described so far. You don’t want to get locked into one Ember version forever. And the template compiler doesn’t guarantee compatibility across versions – you’re expected to build a whole app with the same compiler. If you have your customers publish the compiled templates, you’re locking in a version.

This same problem would apply to ember-giftwrap – it is explicitly about building an addon for a particular Ember version.

So I would suggest doing your own runtime compilation instead. The template compiler is published inside ember-source. Normally the precompile step happens during the build and not in the browser, but you can load ember-template-compiler.js into the browser just fine, and then you can do things like:

Ember.HTMLBars.precompile('hello {{world}}');
// result: "{"id":null,"block":"{\"symbols\":[],\"statements\":[[0,\"hello \"],[1,[21,\"world\"],false]],\"hasEval\":false}","meta":{}}"
2 Likes

@ef4 thanks for the feedback! You are correct in you’re understanding. Based on your run down you are suggesting that I do a build time compilation via Bable for the component.js, then at runtime before we register the component.js with our app we would use ember-template-compiler to compile the template.hbs, then register the component with our ember app and we’re off the races? I’m going to take a run at this and see how it goes!

Yes, that basic strategy should work. Obviously there are a bunch of little details to figure out, but I think the idea is sound.

@ef4 Do you think you could post some tips, or point me in the direction where I can investigate more, on how to register this component at run time? I thought I knew what I needed to do (using factory registrations) but i’m spinning my wheels. Will this need to be done in an initializer or can I do this somehow on the fly? Ideally we’d like to load it only when the component is needed but I could do this in an engine initializer if that kind of lazy loading is not possible.

I think all you need is define. You don’t need to register anything, because there are already sufficient rules for how to resolve a component.

For example, make a regular component in your app and then go find it in the built output in dist.

@ef4 Awesome thanks so much! I’m getting super close with your help. I’ve got the component loading into our Ember App and I’ve even got the template compiling. Problem is when I try to set the components layout in init before the super(...arguments) call i’m getting an error from the CurlyComponentManager

Uncaught TypeError: template.asWrappedLayout is not a function
at CurlyComponentManager.getDynamicLayout

Maybe I am trying to set this in the wrong location? I don’t get any errors when actually using the precompile function and my results look very similar to yours. I’ve tried setting module name in the precompile options figuring that maybe it needed that but no go. I’m not sure if I’m missing some options or what? Any ideas?

EDIT: I was poking around at this more and on a whim swapped out precompile with compile method and we’re off to the races! I’m not sure if using the compile method is the correct way to proceed but I’m going with it.

Yeah, I think you guessed right.

There is both a precompile (that normally happens during the build) and a compile (which always happens at runtime, because it needs runtime info). Since you’re doing everything at runtime, skipping right to compile sounds right.