-
Notifications
You must be signed in to change notification settings - Fork 235
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
Java script engine switcher and highlight js #452
Java script engine switcher and highlight js #452
Conversation
It's actually failing here due to the AutoCodeBlock unit test while using Jint. I'm going to mark it as skipped. |
Wondering a little bit about the utility of switching JS engines. Not that it isn't a good idea, I'd just like to understand some of the use cases a bit better. For example:
|
So my thought is that regardless of what engine that's used it would have to be abstracted out. Might as well useJavaScriptEngineSwitcher has that abstraction. Any new engine is gonna have support for that library, so no matter what engine picked today the next latest and greatest should be able to be plugged in pretty easily behind the scenes. As to the use cases, I don't think a module developer would control it - that would be chaos with the different platform scenarios. Like if someone wrote a module that relied on ECMAScript 6 so it requests a specific engine...yeah. Might want to discourage those shenanigans. Really I think the only time someone would be messing with that would be an end user. And the reason for doing so would definitely be perf. People swapping the engine out for compatibility sake would mean that modules are out there that only support a specific subset of JS features that only one engine supports - that'd be ugly. But take a look at these benchmarks - some drastic differences here - https://asmagin.com/2016/10/22/how-to-marry-reactjs-and-sitecore-with-webpack-part-3-js-engines-performance/. I suspect once |
That makes a ton of sense, thanks. It sounds like there's essentially two compelling reasons to go this route:
Agree with all your other points, especially discouraging swapping the engine inside a module. In fact, it probably makes sense not to even expose that option from the execution context and only allow it to be changed from the config file/calling code (similar to some of the other objects that become read-only during execution). I was thinking a lot about this ability last night and today - could be a game changer. Generation time highlighting, Sass, Less, diagrams, list goes on. JS opens a whole new world of possibilities. Thanks for continuing to press this initiative! Hoping I have a chance to merge this first PR tomorrow. |
Cool. I'm trying to redo the |
This applies the highlight.js highlighting on the server allowing the client side script to be dropped. It does this using the MsieJavaScriptEngine to execute the script. The script itself is actually the node version, but I applied browserify to it so that it wouldn't require node anymore. Kind of a hack, but it works. I included the steps I took to build the file in regenerating-highlight-all.js so that it could be updated in the future with a newer version.
MsieJsEngine is certainly not thread safe so I was rebuildingi t from scratch within the loop. No sense in doing that, so switch the code to use a ThreadLocal instance to pool the JS instances between threads. Not a huge win for small sites, but for a 1000 document test run it went from 15s to 1s on my machine.
Adds a test where the code block doesn't have a language and highlightAuto must be called. This not only covers that scenario but is an important test case for javascript execution engines. Because highlight.js doesn't know the language it must iterate through the languages it is configured with and check them one by one using regular expressions. These have a wide variety of implementations so it actually provides a good test case for a javascript languages regex support. It seems anything doing JS -> IL starts to fall short here due to the differences in supported Regex escaping and language features. And with regex being a huge part of linting and text manipulation supporting regex is a vital part of evaluating a JS engine.
Adds the following packages to Core * JavaScriptEngineSwitcher - an abstraction around common JS processing libraries * JsPool - a pool around the engine switcher. We'll be creating these a lot potentially so good pooling is a must * Jint - managed implementation of a javascript processor. We'll make this the default. * JavaScriptEngineSwitcher.Jint - allows Jint to be plugged into the JavaScriptEngineSwitcher For a module that wants to use the javascript engine it's pretty simple. They just need to call GetJsEngineFromPool off of the context. When they are done then must call ReturnJsEngineToPool, This exposes more or less the same interface as IJsEngine from the JavaScriptEngineSwitcher so it should be easy to find help and examples for devs to work with. The implementation is just a wrapper of the default implementation plus a couple of helpers around ensuring libraries only get loaded once per engine. Things get tricky in two spots 1. Testing. With this being exposed off of IExecutionContext the TestContext project needed some implementation. Rather than bring in a JS engine here too I made it a Func<> that could be set to a factory for building up the JsEngine by the test. I added a Wyam.Tests.JavaScript with an implementation of an engine that could be used for testing. It's just a copy and paste from Wyam.Core but it does allow test projects to be able to avoid needing to reference Core just to get some JS processing to work 2. Reloading the configuration. Because the configuration could potentially change the JS engine it needed the ability to wipe the singleton that JavaScriptEngineSwitcher uses. Had to add a couple static methods off of Engine and ExecutionContext to get those settings cleared out properly.
Test is failing due to Jint's regex implementation, but is still valuable to keep around for testing those fixes in the future
Give me a bit on this. Ran into some edge cases with the docs that I missed. |
This was escaping ALL @ signs in the document instead of just the ones in the code blocks. Not good for things like actual javascript and the such. I tried to get AngularSharp to be nice and let me escape it via the InnerHtml but they insisted on changing it back. Taking a step back I realized all of this is foolish. This should really only be called after Razor is invoked. Added a test to make sure the highlighting is cool with the escaped html in a code block razor will generate.
The ADA and Lisp languages used some regex syntax that .Net couldn't handle. By changing this it allows Jint to run successfully.
Ok, ran this through the docs site. Ran into an interesting problem with |
😨 |
Oh yeah. That's where the DotTrace PR came from. I have some things I'm going to try tomorrow. I suspect a good chunk of the slow down is due to the fact many elements don't have an explicit language set on the code block so highlight.js is having to do more work than needed to figure out how to render it. Going to try and make that explicit in the recipe maybe. Along the same lines I'm going to add two configuration options. One that skips Now I'm probably not the average use case, but my box has 8 cores and it was pretty bored while running through this. If I recall correctly, |
Starting to look through this. What happens if a module doesn't call |
I'm thinking more about the pool and how it's being used here. It looks like the benefit of using JSPool is that you can bypass both the JS engine instantiation time and the long loading time of shared state (from a library or other "initialization" JS code) when you know you're going to use the engine over and over in a concurrent manner. This performance savings only happens once you grab the same instance over again, right? In other words, if my pool has 10 slots and I don't return the engines before I need the next one, I'll end up the same performance-wise as if I had just created 10 engines from scratch. Also, if you know you're only going to be running synchronously, then the pool doesn't make sense - just create a single JS engine, initialize it with your shared state, and reuse that over and over. I'm wondering if we're making the best use of pooling here. It seems like you realize the most benefit when module-specific initialization (like loading highlight.js) is performed by the pool itself (granted, we're avoiding running the initialization more than once in the highlight case by using the engine's I guess what I'm getting at is perhaps the pool itself should be created by the modules? Or at least provide custom pool creation as an option for the modules. I do really like the level of abstraction here, especially that we can consume JS engines from module libraries without bringing in Wyam.Core (even if it does mean essentially duplicating the interfaces from JavaScriptEngineSwitcher). Rough outline of what I'm thinking:
Here's what I'm thinking - I'll go ahead and merge this in right now and then make some changes. You can see what I adjusted from the commit diff and then we can iterate in another PR if needed. |
Okay, my adjustments are in. First off, I loved the abstraction concept you build so I tried to stay close to that. The big differences are:
Anyway, certainly still open for debate and comments - just figured it was easier to explain where I was thinking we should go with code. |
Nice. All that makes perfect sense. I was thinking about throwing the the pool into a using statement or making some sort of My wife is working the night shift tonight again so I'll pull this down and play with it. Going to give kick the tires of the changes by using |
Hi! I'm the developer of JSPool. I stumbled on this pull request as I'm considering switching the ReactJS.NET site to use Wyam, and was looking through the code of
@daveaglick - Yeah, ReturnEngineToPool is a bit silly. I've been meaning to clean up the API a bit. The reasoning behind it is that JSPool currently returns the JavaScript engine directly from JavaScriptEngineSwitcher, without any modifications or wrapper around it. This means that the engine has no idea about the pool, and disposing it will actually dispose the engine. In order to override the Dispose handler to return the engine to the pool (rather than actually disposing it), I'd need to have a wrapper around the engine itself, and proxy all the methods. I do think this is a reasonable idea though, so I'll probably do it in a future JSPool release. Thanks for the feedback 😄 |
@Daniel15 Great to hear from you! We've gotten great use from JSPool - it's helped opened up a whole class of generation-time JavaScript execution cases. There's so much that could be done there and I've only started to scratch the surface of what we could bring in that way. I've got my own share of "silly" APIs so no worries at all about the disposal pattern 😄. In fact...
That's exactly what I ended up doing: https://github.com/Wyamio/Wyam/blob/develop/src/core/Wyam.Core/JavaScript/PooledJsEngine.cs |
…o call ReturnEngineToPool To handle this, the engine is wrapped in a class that overrides Dispose and does the right thing. References #7 References https://github.com/Wyamio/Wyam/pull/452
I started working on JSPool again and did exactly that (wrapped the engine and overrode Out of curiosity, how did you find JSPool? |
This builds upon the PR #442. Didn't want to commit there and throw off any work that you might have done trying to get the Jint engine playing nicely incase this work is just too out there and needs to be tossed.
This additional commit adds the following packages to Core:
For a module that wants to use the javascript engine it's pretty simple. They just need to call GetJsEngineFromPool off of the context. When they are done then must call ReturnJsEngineToPool, This exposes more or less the same interface as IJsEngine from the JavaScriptEngineSwitcher so it should be easy to find help and examples for devs to work with. The implementation is just a wrapper of the default implementation plus a couple of helpers around ensuring libraries only get loaded once per engine.
Things get tricky in two spots
One big advantage of doing it this was is that I was able to swap out the JS engine from Jint pretty easily in my config for Vroom. All I did was add this code and wallah