-
-
Notifications
You must be signed in to change notification settings - Fork 54
Expose a public API to allow assets to be served in a custom way using ember serve
#80
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thank you for getting this into words, now that we have something to concretely discuss, it seems like we can actually rely more on express middleware than I initially thought.
Rather than creating something similar to express middleware here, it could very likely be just express middleware. This allows us to lean on existing expectations of express, and avoid introducing unnecessary concepts.
If we rethink the existing single middleware (ServeFilesAddon and broccoli-middleware), to rather be a chain of middleware it would be something like:
- existing middleware
- watcher middleware: new, ember-cli internal addon named: 'ember-cli:broccoli:watcher"
- index serving middleware:new, ember-cli internal addon named 'ember-cli:broccoli:serve-files'
Implementation of watcher middleware
would build:
function broccoliMiddleware(request, response, next) {
watcher.then(function() {
// do broccoli specific stuff
request.headers['x-broccoli'] = {
outputPath,
// if we eventually need to add more things
};
// a good default, downstream can always change this
response.setHeader('Cache-Control', 'private, max-age=0, must-revalidate')
// call the next middleware, which by default will be the index serving one
// but can also be be add-on provided
next();
}, function(reason) {
// unchanged for this feature
})
Implementation of: index serving middleware (new)
would be:
function broccoliServeIndex(request, response, next) {
// most what is in todays broccoliMiddleware watcher.then(function() { /* this stuff */ };
buffer = fs.readFileSync(filename)
response.writeHead(200)
response.end(buffer)
}
Implementation of an add-on that installs its own middleware
"name": "fastboot:middleware",
"keywords": [
"ember-addon"
],
"ember-addon": {
"before": "ember-cli:broccoli:serve-files"
}
}
// index.js
function fastbootMIddleware(request, response, next) {
if (request.url === request.serveUrl) {
var fastboot = new FastBoot(data.outputPath);
fastboot.visit(request.url).then(result => result.html()).then(html => response.end(html)).catch(next);
}
}
module.exports = {
serverMiddleware(startOptions) {
var app = startOptions.app;
app.use(fastbootMiddleware);
}
};
This may require other tweaks, but should keep the complexity of the overall system from creeping by ensuring the number of concepts that are different with overlapping goals to a minimum.
This also allows us, to introduce literally no additional add-on API surface area. With the exception, of allowing middleware to be inserted where previously a single middleware incorrectly handled two separate tasks. This prevented the extension point we required.
|
||
In general we only expect one addon (representing a platform) to provide this hook to override the serve file behavior. If there is more than one addon that is providing this hook, then `ember serve` should throw an error. This is because we do not expect more than one addon to decide how the assets should be served. | ||
|
||
Having said that, two or more addons representing different infrastructures may be used together in an app. In order to allow that, we will expose an API via environment.js where an app can specify that it wants override this behavior of throwing an error if more than one addon defines `serveFileHandler` function. If this API is used, the last addon will win always. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
environment.js
is for configuration for our UI code, not for our build. This configuration needs a different place, maybe ember-cli-build, or likely accessible via addon API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should likely not speculate on how to solve this. Lets define the problem we want to solve, then work together to figure out how to solve. I think this falls into "Unanswered questions"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Will move it to "unresolved section".
@rwjblue has some thoughts, we will sync up tomorrow morning, and update our comment based on that :) |
@stefanpenner Thanks for the feedback. I will wait for @rwjblue feedback too and then update this RFC. Here are my two cents (from a naive ember-cli developer who is still learning 😄 ):
Thoughts? |
@kratiahuja there's also https://ember-cli.com/api/classes/Addon.html#method_serverMiddleware which is used more generally (it's the mechanism As I understand it,
This sounds very similar to splitting up |
@hjdivad I agree we should avoid two different APIs for similar functionality in general. But here is a usecase that will not work with what Stef is proposing (unless I am misunderstanding something):
If that is the case, who is going to be responsible for setting the correct headers? It is very likely people will end up copying over the setting headers. Atleast in my mind, it seems a lot confusing and lot of friction as to when If everyone strongly proposes to go down the |
@kratiahuja ah okay thank you for clarifying. So you're absolutely right that we missed a piece in our earlier comment, which is that addons (including ones that specify See this example in serve-files and the dag code and its call site |
@hjdivad Thanks for clarifying! After speaking with you in person I have the clear picture. I will update this RFC by end of today 😄 |
@kratiahuja - I had a conversation with @hjdivad and @stefanpenner about my earlier reservations also, I am fully on board with the suggestion in #80 (review). |
@stefanpenner @rwjblue @hjdivad Updated per feedback. I also took the liberty to include the changes in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great @kratiahuja! I think there's just a small realignment needed per #80 (review)
The only public API advocated in that comment was to have two named middlewares,
ember-cli:broccoli:watcher
ember-cli:broccoli:serve-files
And for this to replace serve-files-middleware
This would allow addon authors to be able to insert their own file serving logic via
"ember-addon": {
"before": "ember-cli:broccoli:serve-files"
}
without having to deal with the watcher portion and could instead act just like a regular express middleware.
The rest of the comment discussed a starting point for a refactor that would clean up ServeFilesAddon
and broccoli-middleware
while producing the above API.
As the rfc currently reads to me, its summary is:
- leave
serve-files-middleware
in the pipeline - add
broccoli:serve-files
as a second middleware - break up
broccoli-middleware
into some functions.
This doesn't seem to introduce an extension point between the watcher task and the file serving task, which is where the extension point is needed
Can we update the rfc to
- Be explicit that the only public API change to ember-cli users is the two named middlewares
- Be clear that that the extension point between those middlewares is between the watcher and the file server (which would still be responsible for setting headers)
That said, the suggested internal changes to broccoli-middleware, ie splitting it up into reusable functions, looks great! 👍
@hjdivad @stefanpenner @rwjblue Updated per feedback. |
In order for the above API to be exposed, we need to drop the `serve-files` addon in `ember-cli`, refactor `broccoli-middleware` and create the two new addons. | ||
|
||
### Refactor `broccoli-middleware` to be a utility | ||
`broccoli-middleware` is currently responsible for setting the response headers and serving the files. It is a middleware that does these two tasks. It doesn't expose a proper API to do the two tasks differently. We would like to refactor `broccoli-middleware` to be a utility such that it exposes three different APIs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A further refinement that utilizes the existing middleware ecosystem, and prevents any new concepts from being added would be as follows:
from the point of view of ember-cli, I'm not sure this matters. I believe ember-cli should ask broccoli-middleware for the two middleware. The reason for this, is to ensure we don't add additional concepts, when the existing concepts suffice. In addition, the express middleware ecosystem is what we use, and we should continue to maintain and improve compatibility with the rest of the ecosystem.
it needs:
- watcher middleware
- serve files middelware
watcher middleware
require('broccoli-middleware').forWatcher(watcher);
Implemented something like:
function forWatcher(watcher) {
return function middleware(request, response, next) {
// content the same as from: https://github.com/ember-cli/rfcs/pull/80#pullrequestreview-9709454
}
}
Serve Files
require('broccoli-middleware').serveFiles;
Which is basically as described in: #80 (review)
These two middleware can then be inserted in the existing express middleware chain, and if plugin authors wish to augment, they have that extension point.
Now, to implement the existing middleware (for backwards compat), we can merely have 1 middleware that in-effect invokes the above 2 in sequence
@stefanpenner Updated per feedback. Please let me know if I missed something. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
I really like that we are able to not introduce any new concepts. Instead, largely cleaning up ember-cli to enable this feature.
@hjdivad r? and if you are good, I believe this is good.
This doesn't affect the learning of ember itself, so it will not need ember core teams sign-off.
@ember-cli/core would love some final 👀 on this, but I believe we are looking great.
N/A | ||
|
||
# Unresolved questions | ||
- [ ] Should this addons be better named? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they look good to me
looks great, thanks @kratiahuja! |
My proposal is that 50% of @ember-cli/core saying that something can proceed to FCP can trigger FCP without it being pulled into the slightly longer "assign, review, (possibly) attach FCP, ship" process core team meeting cadence which makes it at least three weeks of meetings and 15 days. This async approach will likely require working to bring somebody up to speed who is not already familiar with the space–which is a feature, not a bug. Here is the 50% straw poll. Once we have 50% representation we can move forward and attach FCP. Regardless this is on the agenda for Thursday and reviewing this is part of your homework. 😛 |
The Ember process is emphatically not based upon voting, but on consensus. Subteams make progress on RFCs they are responsible for through a consensus of the subteam that the discussion on the RFC has reached a "steady state," and the process is not adding any new information. In general, once the discussion has reached a steady state, there is broad consensus among the participants about what to do. Consensus does not mean that everyone emphatically agrees with a decision, but rather than none of the participants is prepared to formally object to a proposed disposition. Occasionally, an RFC reaches a steady state without a broad consensus of all participants. In this case, a consensus of the subteam members can move the process forward. The assumption is that most conversations will progress to a natural broad consensus naturally, and that the fact that the subteam members can resolve steady-state deadlocks will motivate participants to volunteer new information or propose new compromises. A subteam initiates the FCP process when:
The intent of the FCP process is to solicit new information, so it's quite common for a final comment period to move the conversation back into contention. If this happens, rinse and repeat. If it doesn't, the proposed disposition takes effect. TL;DR In Ember governance, subteams make decisions by consensus, and the FCP process means that the subteam members have come to consensus that the conversation has reached a steady state and have come to consensus about the proposed disposition. |
The major difference between a consensus process like we use in Ember and a voting process is that votes force people with minor (or no) preferences off the fence and give those perspective equal force in the discussion as people with very strong feelings. A consensus process, by contrast, allows people with strong feelings to object to a particular decision, but creates social norms that:
There are still some pathologies that a well-run consensus process can produce, and I would recommend watching Brendan Eich's talk Ecma TC39: The Good, The Bad, and The Ugly for an enumeration of some of the things that can go wrong in these processes, which can help members enforce the social norms. |
@wycats I don't disagree at all with what you're saying–this proposal is not actually intended to circumvent consensus in any way. It's intended to get us there more rapidly in cases where we're reviewing asynchronously and consider it very likely that we already have consensus. I've attached the full conversation thread from #dev-ember-cli below for the context that was missing in the above note. Reading the thread below you'll see the concerns I voice are exceedingly similar to your own. The other alternative here takes us three meetings to get through, wherein this makes it possible to address an RFC in just one week for things which we don't believe to be controversial. This whole thing was in order to address the fact that RFCs are often where ideas go to die because of how heavy-handed it is.
|
@nathanhammond The Rust projects had roughly the same problem and solved it in a very similar way:
Members of the subteam are expected to check the checkbox once they have read the RFC (not to indicate an agreement with the request to enter FCP). Once a member of the subteam has checked their checkbox, they can comment on the thread with a formal concern. If someone raises a concern, it is attached to the original request. Here's an example of this process at work in the Rust RFC process: rust-lang/rfcs#1414 (comment) In this case, the concern was raised at rust-lang/rfcs#1414 (comment) The Rust project has a pretty nice bot that manages the whole thing that we could use (I've been meaning to suggest that we do so). The bot also manages a Dashboard: http://rusty-dash.com/fcp. I log into this dashboard periodically to look for any requests-for-consensus bearing my name. TL;DR the intent is very similar to yours (speed up async decisionmaking and avoid meeting bottlenecks), and assumes consensus. The "checkoff" indicates that a subteam member has read the proposal, and a separate process is used to file objections (to make it clear what exactly your objection is). The nice thing about meetings is that if you're ever blocked on getting someone to check something off, you can, worst case, bring it to a head at the next meeting, which is no worse than where we are today. |
In order for the above API to be exposed, we need to drop the `serve-files` addon in `ember-cli`, refactor `broccoli-middleware` and create the two new addons. | ||
|
||
### Refactor `broccoli-middleware` to expose additional middlewares | ||
*Note*: This refactor section is only for making the reader understand how the integration is meant to work in `ember-cli`. This is not going to `ember-cli` public API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing word: going to [be]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed...
@nathanhammond @stefanpenner @rwjblue @hjdivad Following up here as well. Is there anything blocking for this RFC to enter FCP? If there is nothing blocking, I would prefer this RFC enters FCP. |
After discussion in our last core team meeting we consider this approach to be ideal and that this feature as proposed no longer truly requires an RFC. We see it as changing only private API in a way which approximates a refactor. Given that we've started the RFC process we intend to finish it but we hope to work within the Ember community leadership to come up with something better. Regardless, this RFC is in final comment period. Barring tremendous concerns we intend to land it and the associated PR. |
I plan to send out a PR next week :) |
@kratiahuja Apologies for the complicated process involved for this RFC. We are actively trying to make it better for the future. |
@kellyselden No worries at all. In the bargain, I did get to learn how to write RFCs 😄 |
Here is the implementation of this RFC:
Plan of action
|
Given that we have received no additional feedback during the FCP period and the team's comfort with the proposed solution (and sketch of the implementation!) we're excited to land this RFC. Thank you @kratiahuja for shepherding this through the process! |
Split serving assets into two different in-repo addons Implementation of [RFC # 80 of ember-cli](ember-cli/rfcs#80). This PR primarily does the following: - [x] Introduces `ember-cli:broccoli:watcher` addon that is responsible for setting the response headers for every asset request being served. - [x] Introduces `ember-cli:broccoli:serve-files` addon that is responsible for serving the assets - [x] Removes `serve-files` addon since the above two addon do the same work - [x] Release and update `broccoli-middleware` version after [PR](ember-cli/broccoli-middleware#21) is merged. - [x] Fix broken tests here after the `broccoli-middleware` version is updated. - [x] Update `ember-cli-inject-livereload` from `serve-files` to `ember-cli:broccoli:serve-files` [here](ember-cli/ember-cli-inject-live-reload#39) **Note**: `broccoli-middleware` output format is now changed but to maintain backward compatibility it still has the original one middleware that does both setting response headers and serving assets. TODO: - [ ] Release a major version of `ember-cli-inject-live-reload` that is compatible with the first release of ember-cli containing this PR. - [ ] Update change log?
This RFC attempts to resolve this issue. For more background, please read that issue.
rendered
cc: @stefanpenner @ryanone @masonwan @kristoferbaxter