Closed mitchlloyd closed 8 years ago
Hold off on using the application visit API.
Losing shared services would likely be a non-starter for us. There are a couple of places where we have existing client side routing, and thus can't really use Ember routing. Shared services let us coordinate between multiple components on a page in the absence of the router.
Hold off on using Ember Wormhole for now.
:+1:
Drop support for Ember 1.x.
:+1:
Remove the cancel routing feature. Introduce a new {{ember-islands}} component.
I'm not sure where I stand on this one yet. In your example where would isInServerRenderedApp
be defined? Is it a property I'm passing in to Ember.Application.create
?
The idea behind using a component over an initializer is to give users control over rendering the components (when to do it / whether to do it) and make rending a little more idiomatic. Currently there are instructions about using a bypass
flag to avoid rendering in the readme -- we could remove this configuration. The entire cancelRouting
hack was introduced to handle a use case where someone wanted to render their application template in one deployment and not render it when using Ember islands in another deployment environment. Adding that feature was probably a mistake. Allowing someone to control their application.hbs
content with the normal template helpers seems very preferable.
Finally I think this opens up new possibilities in the future like rendering and tearing down components by using {{#if}}
, looping over components while rending special markup around them, and all the other great stuff we can normally do with components.
One way to fill out the example I posted:
{{#if isInServerRenderedApp}}
{{ember-islands}}
{{else}}
{{my-other-content}}
{{/if}}
Would be to put this in your application controller:
import config from 'my-app/config/environment.js';
export default Ember.Controller.extend({
isInServerRenderedApp: computed(function() {
return config.isInServerRenderedApp;
})
});
Where you have defined the isInServerRenderedApp
in different deployed environments. I'm not super clear on that use case to be honest, but the point is that is would be easy to control when rendering happens without learning about configuration flags.
Thanks, that makes it much clearer. FWIW our use case (progressively enhancing a Rails app) only has the one environment, and we control whether Islands is on during application creation, which is manually done. But we could still achieve the same control with the component based initialization.
...we control whether Islands is on during application creation, which is manually done.
Could you expand a little on what you're doing here and why you're doing it? If I understand what you're doing I should be able to write the "Bypassing the Addon" section of the README. I also want to know if you're using workarounds that the library could handle.
Sure. We have a combination of Ember enabled pages at the moment - 'island' pages, which have one or two components, enabled by ember-islands; and 'continent' pages, which are full Ember apps (routing and all), albeit apps contained within the Rails generated header and footer. For example, https://kickstarter.com/team is a continent app.
To facilitate this we have a simple Rails helper for including Ember:
def require_ember(into: nil)
ember_options = {}.tap do |hash|
if into
# if we have a root element enable routing
# otherwise, boot into islands mode (the default)
hash[:rootElement] = into
hash[:EMBER_ISLANDS] = {
bypass: true
}
end
end
content_for :javascripts do
# <do some specific stuff to include the Ember js files>
"require('frontend/app')['default'].create(#{ember_options.to_json})";
end
end
You'd do something like require_ember(into: '#team-container')
to include a full routed Ember app into the page.
We'd still be able to do something similar with the component example.
I think this new approach may open up some nice options.
We could drop the Ember Islands config flag in the Rails helper:
def require_ember(into: 'body')
ember_options = { rootElement: into }
content_for :javascripts do
# <do some specific stuff to include the Ember js files>
"require('frontend/app')['default'].create(#{ember_options.to_json})";
end
end
Then let's say that your /dashboard
page needs to use Ember Islands. Inside of your Ember app you could have a dashboard.hbs
template like this:
{{! inside of app/templates/dashboard.hbs}}
{{ember-islands}}
Now we'll say the /team
page doesn't need Ember Islands. You could have a team
route and team
template without any Ember Islands component.
{{! inside of app/templates/team.hbs}}
My plain ol' team stuff
Does this seem like it would work? The current next-version
branch should work with all versions of Ember 2.x. Might be cool to try it out and I'd be happy to take a look with you if we can arrange a time outside of PDT working hours.
Yea, I don't mind that. At the start of this process I figured island components would just kind of... exist, without being used in the rest of the Ember app. But grouping them under routes from the start lights a path towards actually migrating each page that has an island, by already having its URL in Ember.
Lemme find some time to give it a try and get back to you.
This addon works today, but as Ember and its ecosystem improve we have a chance to simplify this approach and even add some new features to further this project's goal.
That goal is to make Ember a viable framework in a server-rendered application. The hope is that users would introduce Ember into a small part of their application and then slowly refactor to a client-rendered app where Ember really shines. Any changes should support this goal.
I'd like to know about any concerns with this plan breaking old use cases and whether there are new use cases that should be considered for the next version.
Possibilities for Ember Islands Next
Droping Old Versions of Ember
By droping old versions of Ember (pre 2.0) we eliminate a lot of complexity and maintenance from the project.Dropping everything before Ember 2.3 will allow us to use the newEmber.getOwner
API and remove the need for an initializer. This will make the process of looking upcomponent-lookup:main
less brittle.Using the Visit API
As shown here the newest version of the
visit
api would allow this addon to use all public APIs for rendering.In addition to supporting the current syntax:
This would also make it possible to render instances of an Ember application routed to specific URLs:
Applications that are rendered this way could use routing that is not tied to the address bar with the application
{ location: none }
option.The biggest issue with this approach is handling shared services. If you have a service that watches the window size, maintains a user session, or caches model data (Ember Data), you'll want services that can be shared between all of your application instances.
A
visit
test shows an approach of sharing services, but in practice I've found it difficult to make this work. I've especially had problems with other addons that register their own services. Creating and registering services early (in an application initializer) is possible, but breaks the paradigm of lazily instantiated services.One workaround is to register services in a way that forces them to be singletons:
The next step would be to look through the list of services in requirejs (pretty hacky stuff) and do something in the registry to make sure they are registered in this singleton style. I haven't been able to get this working with Ember Data yet.
Using this approach it seems natural to set
autoboot
tofalse
and boot an application once for each Ember Island marker in the static HTML. This means that we need code outside of the Ember system to find the application and boot instances of it. This works, but again is a little hacky.Another concern with this approach over the current one is that we can no longer pass all of
data-attrs
as theattrs
of a component. Instead we would need to pass the attrs as one attribute likeelementAttrs
Once we have a splat operator the old functionality would be possible again.I'm not sure if the ability to render different URLs of an Ember application provides much value.
Using a syntax like this:
doesn't lend itself to refactoring to a full Ember application as much as the component syntax does.
The goal of the
visit
approach was to find a more Ember-friendly way of rendering these components, but I've found it pretty akward to try treating many Ember applications as one. Manipulating the registry and creating my own boot process seems pretty complicated.Simplifying the Current Approach
Use a Component Instead of Initializers
In the past we've used initializers to render the components onto the page. We've made it over the hump of Initializer Chaos™, but in the next version we can make things simpler by having users put a component into their application template:
This component would look for the special
[data-component]
tags on the page, and render them using the same approach used today (componentLookup
&appendTo
).Drop "cancelRouting" feature
I used private APIs to cancel routing. This supported a use case where an Ember app was sometimes used in the context of a server-rendered application and sometimes used as a stand-alone client-rendered application. I plan to drop this support since I think this use case is easier to just handle with templating:
Using Ember Wormhole
Ember Wormhole uses private component APIs and knowledge of HTMLbars inner workings to accomplish something pretty similar to Ember Islands. Ember Islands uses the undocumented
component-lookup
utility to find components and then render them into elements on the page. Ember Islands has it pretty easy compared to Ember Wormhole because it does not need to teardown components, but the new{{ember-islands}}
component probably does need to support component teardown to avoid surprising users that wrap it in an{{#if}}
helper.The primary advantage of using Ember Wormhole is that two project don't have to maintain code that relies on private Ember APIs -- any breakage could be handled in Ember Wormhole.
The primary disadvantage that I see is that, without a splat operator, the components would have to use a namespaced attribute like
elementAttrs
.Current Plan
For a 1.0 release the current plan is:
visit
API.{{ember-islands}}
component.cc @praxxis, @chancancode, @himynameisjonas, @rwjblue