-
Notifications
You must be signed in to change notification settings - Fork 141
Extract tag handling from request handling #173
Conversation
👎 |
@@ -0,0 +1,4 @@ | |||
<script type="text/javascript"></script> |
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.
Without it, the pipe script would be inserted after the switcher and won't be available to it's content. @vigneshshanmugam mb you know the nice way to do it?
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 pipe is inserted before fragment
tag or one of the tags in handleTags list. Just pass them in the example :)
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.
You mean (it's internal)pipeTags
?
Nevermind :)
package-lock.json
Outdated
@@ -0,0 +1,2754 @@ | |||
{ | |||
"name": "node-tailor", |
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 use yarn.lock
, can you update yarn.lock instead?
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.
Yes, will do!
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 were no updates, I just happened to use latset npm & node %)
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.
Done
index.js
Outdated
@@ -64,7 +64,7 @@ module.exports = class Tailor extends EventEmitter { | |||
|
|||
requestOptions.parseTemplate = parseTemplate( | |||
[requestOptions.fragmentTag].concat(requestOptions.handledTags), | |||
['script', requestOptions.fragmentTag] | |||
['script', requestOptions.fragmentTag].concat(requestOptions.handledTags) |
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.
@vigneshshanmugam I guess you were meant this. Those custom handledTags
where not passed to pipe tags, so for my switcher
example it was not added. Not sure it's the best way, though.
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 you are right. Just feels like this gives too many options of having where pipe should be inserted. Actually, pipe is never required if its not a fragment, "script" was added because anyone can add fragment in the head like <script src="blah" type="fragment"/>
.
I would move the fragment
in your example before switcher
to make things easier.
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.
Or have a script tag :)
It was an interesting side-effect I didn't think about.
People who implement their own tags should kinda be aware then :)
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 true, lets remove these options and insert a empty div tag like before.
examples/custom-tags/index.js
Outdated
|
||
// Fragment server - Any http server that can serve fragments | ||
http.createServer((req, res) => { | ||
const urlObj = require('url').parse(req.url, true); |
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.
can you move the require to the top.
|
||
Promise.all([ | ||
templatePromise, | ||
contextPromise |
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.
an error in context should not control the page and return 500.
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's a catch above.. Not super visible, though %)
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.
cool. missed it :)
lib/block.js
Outdated
|
||
const resultStream = new StringifierStream((tag) => { | ||
|
||
const { |
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.
this destructuring logic happens on every invocation of tag, should be moved out of of this function
lib/block.js
Outdated
} | ||
}; | ||
|
||
fetchDynamicContext(tag) |
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.
IMO this creates unnecessary complexity in tailor itself and can be handled outside in requestFragment logic.
Tailor itself is extendable, we may not need this feature here.
Thoughts? @grassator @mo-gr
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.
Partially agreed :) It doesn't really belong to this PR, moving out.
lib/request-handler.js
Outdated
|
||
fragment.on('fallback', (err) => { | ||
this.emit('error', request, err); | ||
// TODO: add some responseHeaders? |
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.
why not responseHeaders
like before? Do you have anything specific in mind?
Its also configurable through filterResponseHeaders
function
lib/request-handler.js
Outdated
|
||
fragment.on('error', (err) => { | ||
this.emit('error', request, err); | ||
// TODO: add some responseHeaders? |
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.
Same here.
lib/request-handler.js
Outdated
}, options, context)); | ||
|
||
|
||
FRAGMENT_EVENTS.forEach((eventName) => { |
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.
assuming these events are for fragments. Could you explain me why it is required here?
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.
It's part of the tailor API (fragment:start, fragment:end, etc. events.) for fragments. It was there before (almost exact same code), difference is that now instead of subscribing to the fragment directly we subscribe to the "block" events (it propagates them), cause fragments are not directly exposed to the request handler.
Does this make sense to you?
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 cool, looks a bit bad that we need to handle it twice.
tests/serializer.js
Outdated
@@ -9,6 +9,8 @@ describe('Serializer', () => { | |||
const serializerOptions = { | |||
treeAdapter: adapter, | |||
slotMap: new Map(), | |||
addPlaceholders: true, |
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.
don't think its used anywhere.
lib/block.js
Outdated
return pipeDefinition(pipeInstanceName); | ||
} | ||
|
||
if (asyncStream && placeholder === 'async') { |
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.
don't think we need this asyncstream
check. Its gonna be present all the time.
As an enhancement we can do this
- create
asyncstream
and placeholders only when async fragments are present in the template.
lib/block.js
Outdated
handleTag, | ||
requestFragment, | ||
|
||
// Should the two below be one??? |
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.
can you explain what you mean here? having a single option instead of 3 separate options?
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, it's a note to myself: we have many pipe-related options, I was wondering if some of the options can be joined (the function part with the attribute part, cause we just pass one to another :D )
lib/block.js
Outdated
}); | ||
}); | ||
|
||
const isAsync = fragment.attributes.async; |
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.
shall we keep the code similar to how it was in request-handler?
Line 105 in 7d0fff2
const { attributes: { async, primary }, stream } = fragment; |
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!
Wow, how can you comment with code snippets like this? oO
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.
hehe.. there is a small option at the left end where you can copy the preview link once you click on the line.
lib/block.js
Outdated
try { | ||
result = handleTag(request, context, tag); | ||
} catch (error) { | ||
console.error(error); |
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.
should we handle the error here? isn't already wrapped in request-handler in catch statement
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 think we can remove the try-catch. If this throw, error
event should be emitted on the stringifier stream and get caught in the request-handler.
lib/block.js
Outdated
if (name === 'fragment') { | ||
// Freeze current context & update the indexes | ||
const fragmentContext = context; | ||
context = Object.assign({}, context, { |
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.
Since the only changing part here is index, can we have a just update index
instead of doing Object overrides.
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.
Actually, not sure here, I pass the context which was there at the time we found the tag further down the line. If I mutate it, fragment might be having some incorrect data inside and then I'd mess up indexes :(
lib/block.js
Outdated
result.on('fragment:found', () => { | ||
resultStream.emit('fragment:found'); | ||
|
||
context = Object.assign({}, context, { |
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.
same here.
lib/request-handler.js
Outdated
contextPromise | ||
]).then(([ parsedTemplate, context ]) => { | ||
const blockStream = buildBlock(request, Object.assign({ | ||
index: 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.
don't allow useland code to update index at any point.
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.
Wow, you're right. Thanks for catching!
perf/benchmark.js
Outdated
const handleTag = (request, context, tag) => { | ||
if (tag && tag.name === 'switcher') { | ||
const stream = buildBlock(request, context); | ||
process.nextTick(() => { |
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.
is it required? buildBlock
a sync function
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 was thinking about kinda real-time example when data is being pushed into this "block" sometime later on (but without big impact on performance).
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.
Hmmm true, then lets introduce artificial delay and check if back pressure is handled properly.. setTimeout
would suffice.
Also, let's rename
* template should be parsed into a set of Buffers + Objects |
|
@vigneshshanmugam we already have |
Rename is done, |
Now I also need to check if it make sense to split context and options again |
* Dynamic context removed (got carried from another experiment) * Destructive assignment in block.js moved one level above to not be called on every tag * Tests adjusted
* responseHeaders are used in the way it was used before * remove unused parameter (addPlaceholders) from tests * don't check for async (we assume it's always there)
* No new context obj created, mutate index field in te old one * Clean up commends * Align property names with the way they are in request handler * don't allow overriding index field
Rebased. No conflicts. Now going to apply options-context changes. |
tests/handle-tag.js
Outdated
@@ -42,7 +42,7 @@ describe('Handle tag', () => { | |||
mockHandleTag.returns(''); | |||
http.get('http://localhost:8080/template', (response) => { | |||
const request = mockHandleTag.args[0][0]; | |||
const tag = mockHandleTag.args[0][1]; | |||
const tag = mockHandleTag.args[0][3]; |
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.
can we keep the tag as arg1 .. otherwise it wont be backwards compatible.
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.
aaaaah
good point
yes
tests/process-template.js
Outdated
resultStream.write({ placeholder: 'async' }); | ||
resultStream.end(); | ||
}); | ||
it('insert pipe definition in the beginning'); |
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.
once we finish the tests for process-template we can go ahead and merge it.. rest looks good to me..
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.
Also seem like we're not changing the API much here. Mb minor version update would be suitable.
Codecov Report
@@ Coverage Diff @@
## master #173 +/- ##
==========================================
- Coverage 96.66% 96.61% -0.05%
==========================================
Files 13 14 +1
Lines 539 561 +22
Branches 93 95 +2
==========================================
+ Hits 521 542 +21
- Misses 18 19 +1
Continue to review full report at Codecov.
|
tests/process-template.js
Outdated
const doesMatch = r.test(data); | ||
let message = `No match for the fragmend(index: ${index}, text: ${fragmentText})`; | ||
if (!doesMatch) { | ||
console.log(r, data); |
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.
should we throw error here instead?
👍 |
1 similar comment
👍 |
Motivation
Right now in tailor
request-handler.js
contains quite a lot of logic. It's responsible for several things:With so many responsibilities it's getting harder to allow more flexible composition to users of tailor. One particular use-case: we want to introduce a new tag, which itself would result into some template which has to be processed in the similar way.
handleTag
function already allows returning streams, but it's quite an overhead to duplicate existing handling algorithm.Solution: extract tag/template handling logic and make it composable. Right now it's extracted under
buildBlock
which can be used inside your own implementation ofhandleTag
, so we allow recursion.Things which were standing in the way during implementation:
index
management andasyncStream
shouldWriteHead
was kind of everywhererequest
object is all over the place + kinda need to separate tailor context (the one overriding fragment info) and more broad term context (some state which is being modified during template processing, for instance, index)TODOs:
buidBlock
works properly