-
Notifications
You must be signed in to change notification settings - Fork 132
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
Plugin system #142
Plugin system #142
Conversation
- move utilities from tests.js to test_utils.js - added hooks class + tests - Added hooks to JSEP + tests for their arbitrary usage - Moved Ternary to a plugin. Default JSEP still includes ternary support.
src/hooks.js
Outdated
run(name, env) { | ||
this[name] = this[name] || []; | ||
this[name].forEach(function (callback) { | ||
callback.call(env && env.context ? env.context : env, env); |
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.
with JSEP, we kind of need access to the Jsep instance, so I think it should always be bound to this
from the caller? I went back and forth on how to pass in local information, but since the plugins need to be able to manipulate the results - or provide new results, without returning a value, I still ended up with this.node
and this.nodes
being passed in and then read back out. It allows reassigning a value, or even clearing a parsed tree. Any better ideas?
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.
The usual pattern is that the hook creation code just passes this
instead of a custom env
object.
Are there any local variables we want to be passing that are not part of the state on this
?
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.
Oh I see. Yeah, in that case the typical pattern is that you pass in {context: this, node, nodes}
as the env. I don't think it's a good idea to have instance properties that are essentially local variables that don't make sense outside the execution context that created them. Are these properties more generally useful?
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.
Thanks for the suggestion! I'll push a change that uses env.context and env.node. See if it's more what you had in mind?
src/jsep.js
Outdated
const nodes = this.gobbleExpressions(); | ||
Jsep.hooks.run('before-all', this); | ||
this.nodes = this.gobbleExpressions(); | ||
Jsep.hooks.run('after-all', this); |
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.
could potentially pass in the nodes[] array. Hooks wouldn't be able to reassign to it (but would be able to push/pop to manipulate the array)
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're passing in this
, so you're passing that as well. Not sure what change you're proposing?
Edit: nvm, got it. See comment above.
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 changed this to run with just env.node. So now all uses would just be accessing env.node
.
src/jsep.js
Outdated
* @returns {?jsep.Expression} | ||
*/ | ||
gobbleExpression() { | ||
const test = this.gobbleBinaryExpression(); | ||
|
||
this.node = false; |
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 know this pattern was commented on in #123 - it's basically trying to set an initial value, then it can check if it was set by the hook (without having a return value from multiple potential hooks). Any other ideas or suggestions?
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.
If you actually use an env
object (with context: this
) instead of this
, you can just check for the presence of a given property. Not sure how generally useful node
can be if read at random times and I can easily see bugs happening because some code forgot to reset 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.
As a general comment, we should have hooks and variables exposed to hooks be guided by the needs of actual plugins, not by the needs that we anticipate theoretical future plugins may have. Thanks to your fantastic work, we have a good starter set of plugins that can guide us in terms of what we need to do. Here, I see a lot of working around to cater to the use case of plugins modifying the current node before it's returned. Is this something the plugins we have actually require? (it may be -- I don't know).
Also, I think essentially you are using this.node
to mean "return value of the current function" which is going to be quite confusing. Do we actually need to be able to override every possible return value?
src/jsep.js
Outdated
* @returns {?jsep.Expression} | ||
*/ | ||
gobbleExpression() { | ||
const test = this.gobbleBinaryExpression(); | ||
|
||
this.node = false; |
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.
If you actually use an env
object (with context: this
) instead of this
, you can just check for the presence of a given property. Not sure how generally useful node
can be if read at random times and I can easily see bugs happening because some code forgot to reset it.
- switch from `this.node` to local method object `env.node` - make after-all hook use env.node instead of nodes - Update readme
src/hooks.js
Outdated
* @public | ||
*/ | ||
add(name, callback, first) { | ||
if (typeof arguments[0] != 'string') { |
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.
Nitpick: spaces for indentation in this entire file (I assume it's the default setting in your editor, so it happened when you copy/pasted).
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.
So sorry! I added .editorconfig
and added the tab as a rule to eslint so it should be fixed :-)
LGTM, I guess we should wait until the other PR is merged now. |
add lint rule for tabs update all spaces to tabs to pass lint
# Conflicts: # rollup.config.js # src/jsep.js # test/tests.js
I merged in the main branch to update. I checked the performance of this PR versus the current and I'm a little concerned that this seems to make jsep significantly slower in parsing expressions (~35% slower, from my tests so far). There are ways to make it less readable for some minor gains (i.e. trading off setup time vs parsing time, possibly removing some hooks that aren't used just yet). Any thoughts? |
- add Jsep.hooksAdd() shortcut to Jsep.hooks.add() - pre-compute the calls to all registered hooks, binding to this from the constructor. Wrap the object-handling of the node argument into the hooks and return the result. This changes `Jsep.hooks.run('name', { context: this, node: {} })` to `updatedNode = this['name'](node)'` - remove `before-all` and `after-all` hooks - updated tsd.d.ts (including spacing)
@LeaVerou - I made another update for performance optimization: I can revert if you prefer, but I felt that the performance hit of the hook calls was worth the change (and it's easier to use within the code, after the constructor pre-computes the calls). I'm open to further feedback and thoughts if you have any :-) |
It would be good to see some actual timings instead of deciding solely on the basis of percentages. A 35% increase in running time can be ok if it takes execution time from 2ms to 3ms, but not ok if it takes it from 400ms to 600ms (numbers are random to make a point). In a few words, what do you think helped improve performance in the latest commit? Is it adding the It would be good to find a way to improve performance that doesn't involve repeating hook names all around the codebase. E.g. if we know we always want to bind to Overall, I would have liked to merge this PR at the state it was before the last commit, and iterate on the performance improvement vs maintainability tradeoff of the last commit in a separate PR. |
The biggest performance bottleneck seemed to be from calling the Would you be okay with a local method like this?
it still has the if-statement for every run call, but then no setup in the constructor. It simplifies the callers and keeps performance close to the original. Or I can just back out my change (or create a new PR from the earlier commit)? The |
I think so, but is Though if we're down to object creation and binding making a difference, I suspect the gains or losses are too marginal to matter, even with parsing multiple expressions repeatedly. |
- Remove pre-compute optimization, but add local method this.runHook(name, node). This will check if there is a hook to run before calling hooks.run() in order to save time. Performance is around 89% of the code without hooks (but without the optimization was around 67% - mostly due to making the { context: this, node } object)
Hopefully okay now @LeaVerou ? Or if it needs anything else changed just let me know! :-) |
Any further thoughts on this @LeaVerou ? |
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.
Sorry, I thought we had merged this! Approved.
(after #141)
Splitting out part of #123 to start a plugin system