-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[optimize] inject publicPath at request time #14007
[optimize] inject publicPath at request time #14007
Conversation
This is great, but it seems like way too large of a change to drop into 6.0. Let's target 6.1 so it can go through a full QA run. |
I'm not against this approach as I do think we need a solution to this one way or another, but I can't help but feel like @spalger Do you think |
I was also hopeful for |
To me, Again, I'm not against this particular change, but it seems like a stopgap to me. I'd like to better understand the issues around |
Alright, I gave up too early on using The worst breaking change is that links like |
If we do this, I think pulling off the bandaid is probably a good idea in the long run. We can create a blog post about the change in advance to provide details to plugin authors and whatnot, just like we did with the elasticsearch cluster changes in 5.2. So the real question is whether we're OK with removing support for relative paths, which is effectively what |
++ I like the idea of moving to Also agree on pulling off the bandaid. We shouldn't have links to |
6c65d67
to
1a16412
Compare
1a16412
to
9c31dbe
Compare
Alright, we explored the idea of using @kjbekkelund @epixa @jbudz @azasypkin who's excited to review this?! |
…uest-time-public-path-replace
👍 I'll try to get this reviewed tomorrow |
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.
LGTM. Added a couple comments that are semi-nitpicky. Tested on both Kibana and xpack and didn't hit any issues.
|
||
/** | ||
* Get the hash of a file via a file descriptor | ||
* @param {Fs.FileDescriptor} fd |
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.
Add the others params
* @return {undefined} | ||
*/ | ||
export function createBundlesRoute({ bundlesPath, basePublicPath }) { | ||
const cache = new LruCache(100); |
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.
Either too generically named or needs a comment, especially as it's not used in this file either. Maybe fileHashCache
or something like that(?)
* @property {Hapi.Request} options.request | ||
* @property {string} options.bundlesPath | ||
* @property {string} options.publicPath | ||
* @property {MapLike} [options.cache] |
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.
cache
isn't optional, as far as I can tell
import { replacePlaceholder } from '../public_path_placeholder'; | ||
|
||
/** | ||
* Create a Hapi response for the requested path |
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 bit more explanation maybe? Mention caching etc. Maybe pull in some of the info from your PR comment?
* @param {Object} options | ||
* @property {string} options.bundlesPath | ||
* @property {string} options.basePublicPath | ||
* @return {undefined} |
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.
undefined
is not correct, it's returning an object
…uest-time-public-path-replace
Thanks for the feedback @kjbekkelund, should all be addressed. Hopefully @epixa @jbudz or @azasypkin have a chance to look into this soon 😄 |
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.
LGTM, haven't noticed anything suspicious while testing so far.
// will store the 100 most recently used hashes. | ||
const fileHashCache = new LruCache(100); | ||
|
||
if (typeof bundlesPath !== 'string' || !isAbsolute(bundlesPath)) { |
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.
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.
Yeah, I can include the bundlesPath value in the error message, but I figure the only time this will be triggered is when the user is monkeying around with the config file, so the more expressive error message will be more useful than seeing their value (and makes the error message longer, maybe wrap, etc.)
|
||
return { | ||
method: 'GET', | ||
path: `/bundles/{path*}`, |
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.
optional nit: quoting string with good old '
should be enough here.
onPreHandler: { | ||
method(request, reply) { | ||
const ext = extname(request.params.path); | ||
if (ext !== '.js' && ext !== '.css') { |
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.
question: Should .js.map
files be treated as .js
files?
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.
Hmm... Good question... I'm tempted to say that since the placeholder only shows up in the webpack loader that processing sourcemap files isn't necessary. None of the source that is actually on disk should be using the placeholder, and if it happens to be the source maps shouldn't really be impacted outside of the line with the replacement.
import { open, fstat, createReadStream, close } from 'fs'; | ||
|
||
import Boom from 'boom'; | ||
import { fromNode as fcb } from 'bluebird'; |
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.
question: since we started to use RxJS
, maybe you can use Rx.Observable.bindNodeCallback
+ toPromise
instead of Bluebird (IIRC we'd like to eventually get rid of this lib), what do you think?
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.
I don't think we should start using RxJS outside of where it's especially helpful, yet. It's still unclear what direction we are going to go when it comes to which observable library we use
bundlesPath: '/bundles', | ||
basePublicPath: 'a/' | ||
}); | ||
}).to.throwError(/start and not end with a \//); |
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.
nit: could you please add case for the happy path too, as in the test above?
} | ||
|
||
function assertBufferMatch(a, b) { | ||
if (a.length !== b.length || a.indexOf(b) !== 0) { |
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.
question: why not just expect(a).to.eql(b)
instead of assertBufferMatch
? It seems to be supported...
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.
Huh, I guess I didn't check
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.
Makes sense I suppose, they're basically just arrays
|
||
expect(response.statusCode).to.be(200); | ||
const image = readFileSync(resolve(outputFixture, 'image.png')); | ||
expect(response.headers).to.have.property('content-length', image.length); |
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.
optional nit: our code is responsible for setting correct content-type now, maybe it makes sense to verify it here too.
@@ -0,0 +1,46 @@ | |||
import { Transform } from 'stream'; |
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.
nit: would be great to have some unit tests for this file :)
src/utils/streams/replace_stream.js
Outdated
buffer = buffer.slice(index + toReplace.length); | ||
} | ||
|
||
if (buffer.length > replacement.length) { |
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.
nit: could you please add comment explaining what this if
case is for?
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.
Nice, this was actually a bug: 8ca1c33
…Replace, not replacement
* [optimize] inject publicPath at request time * [optimize/getFileHash] finish doc block * [optimize/bundlesRoute] correct return value doc type * [optimize/bundleRoute] use more descriptive name for file hash cache * [optimize/dynamicAssetResponse] add more details to doc * [utils/createReplaceStream] trim the buffer based on the length of toReplace, not replacement * [utils/createReplaceStream] add inline docs * [utils/createReplaceStream] write unit tests * [optimize/bundleRoute] expect supports buffers * [optimize/bundleRoute/basePublicPath/tests] add happy path * [optimize/bundlesRoute/tests] verify content-type header * [optimize/bundlesRoute] use ' (cherry picked from commit 1ea82fa)
Fixes #10724
Webpack bundles are now created using a placeholder for the
publicPath
rather than embedding the basePath directly into the assets. This means that:The logic for serving these "dynamic" assets is completely based on hapijs/Inert, which is what was serving the assets before now. It's not exactly trivial but it does protect against path traversal attacks, produce and cache etags, efficiently use file descriptors, and doesn't require forking or hacking Inert/Hapi.
If we wanted to use a single handler for all files we would also need to add some of the lookup tricks supported by Inert's
directory
handler does, but I'm not convinced that's better.