Skip to content

Best way to run JavaScript on the server before some components #142

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

Closed
SoonDead opened this issue Jun 23, 2015 · 12 comments
Closed

Best way to run JavaScript on the server before some components #142

SoonDead opened this issue Jun 23, 2015 · 12 comments

Comments

@SoonDead
Copy link
Contributor

I have a scenario where I have a few independent components on a page accessing data from a "central store". The central store needs to be initialized before the components first render.

My first solution was (before introducing server side rendering), to simply call store.initialize() before React.render()

Now I would need to somehow call store.initialize() on the server before calling @Html.React() in the same JS context.

I could call ReactSiteConfiguration.Configuration.AddScript() on a file that initializes the store, but that runs on every page of my application.

I want to initialize the store on specific pages only.

I would somehow need to get the JavaScript engine instance that React.NET uses from JsEngineSwitcher and call Execute("store.initialize();") on it.

Can I do that?

@Daniel15
Copy link
Member

This is an interesting request. I could add a callback that's called when a JS engine is initialised, somewhere around here: https://github.com/reactjs/React.NET/blob/master/src/React.Core/JavaScriptEngineFactory.cs#L106. That would run for every request though, which you don't want to do.

I think the issue you'll face is that JavaScript engines are actually shared/pooled between multiple requests, so you shouldn't really have any singletons. This is the same with pretty much any server-side JavaScript environment (such as ReactJS.NET but also with Node.js). You could work around that by turning off pooling, which wouldl force it to create a brand new engine for every request. This is a bit slower. The other approach would be to have a unique ID per request and store all data for that particular request in an object keyed by that ID (eg. data[uniqueID] = {foo: 'bar'};)

I would somehow need to get the JavaScript engine instance that React.NET uses from JsEngineSwitcher and call Execute("store.initialize();") on it.

You can do exactly that if you want to test it out. You can get the React environment via:

var environment = React.AssemblyRegistration.Container.Resolve<IReactEnvironment>();

This has an execute method to run arbitrary JavaScript.

Like I mentioned earlier, ensure you turn off pooling if you have any global per-request data.

@PeteDuncanson
Copy link

@Daniel15 don't you load an engine from the pool per request lifecycle? So the same engine gets used through out the life span of a request. I believe so.

If thats the case then you could inject a global variable into the engine using the Environment.Execute mentioned above, then do all your React goodness in your templates and then finally clean up the variable before the request finishes by Executing a delete statement with the var name:

delete myStore;

If you set global vars you HAVE to clear them out manually at the end of each request or they gobble up memory real quick as it seems they won't get garbage collected otherwise.

What is your store doing that needs initialising? Could it not be lazy loaded when the first component hits it?

@Daniel15
Copy link
Member

Oh good point, that would work. I think I'll add support for running some code before and after engines are obtained from the pool, it could be useful.

What is your store doing that needs initialising? Could it not be lazy loaded when the first component hits it?

This might work too.

@ghost
Copy link

ghost commented Sep 15, 2015

Really would be great if somehow the React Router could be used in conjunction with React.NET.

@PeteDuncanson
Copy link

I've been away on holiday but I'm hoping to have a play around with React.Net again and see if we can't port over some of the bits we've learnt from SuperChargedReact as promised. First up would be pulling the start up code into a external file (or at least giving you the option to) so you can run React.Router or just render a component.

@Jarlotee
Copy link

Jarlotee commented Oct 12, 2016

@Daniel15

What is the correct way to set a global variable in the engine?

I have been using this:

React.ReactEnvironment.Current
    .Execute("var myStore = Components.StoreFactory({ hello: 'world' });");

But find that after rendering a few components the variable is no longer available from the global scope.

@Daniel15
Copy link
Member

@Jarlotee - It's tricky, since the JavaScript engines are pooled and reused across instances. You could turn off pooling in the config (SetReuseJavaScriptEngines(false)) but that affects performance. ReactJS.NET is like Node in this way - The environment should be considered global. I mentioned that in my second comment on this issue 😄

In general I haven't really thought of a good way of doing this, but I'm open to ideas!

@Jarlotee
Copy link

Jarlotee commented Oct 12, 2016

Ah just saw #270 where this change was made, looks like a work around for garbage collections.

@PeteDuncanson
Copy link

Best bet is to always add it in every run if you want to be sure its there. This can be quite speedy if you use a compiled script (as in ClearScript compiled scripts which parse the script once then save it for reuse to save the parse step next time) but that takes some tinkering to get working in React.Net due to the JSPool stuff. Could add it into the ClearScript code though but you might have to add to the Interfaces etc. for all the engines.

@PeteDuncanson
Copy link

Also if you add it to your main app.js (or whatever your start page is in your bundle, assuming you are webpacking it up or similar?) then you can use this to force it to be put into global if using "require" (ala node modules) or "import" if you are going the ES6/Babel route:

global.myStore = new MegaStore();

@Lercher
Copy link

Lercher commented Oct 17, 2016

Hi all,

I have a similar issue with server side rendering using React.Core and pooling enabled, when I wanted to pass a .Net service as a function to a component. I noticed that I can't submit a .Net model object's method, a delegate nor a Func<> to environment.CreateComponent(), most probably because only it's JSON serialized form is passed to RenderHtml(). I think this is by design and that it is OK.

So, just to evaluate, I looked up the source code, hacked a handle to a IJsEngine object and injected a .Net object directly with engine.EmbedHostObject("myservice", new MyService());. Now I can indeed use myservice-methods as I wanted, single-threaded. So there are no principal obstacles using .Net methods. Now here is the problem: since RenderHtml() indirectly uses the environment's Engine property, you cannot be sure which engine from a pool is used to render the component and thus if the .net service targets the desired component.

My request is: Please add a way to make full .Net objects including their methods accessible to the rendering process. How? I don't know. Probably by adding two optional parameters to RenderHtml(..., string globalObjectName, object/dynamic globalObject), or by providing a public way to allocate/deallocate an engine and making RenderHtml(IReactComponent cmp, bool renderContainerOnly = false, bool renderServerOnly = false) a method of IJsEngine. I'm not quite sure, if a callback that's called when a JS engine is initialised, would be a good way, it would be "too global" imho.

PS: I'm fully aware that such code won't work client side any more in the first place, unless such global service variables can be provided as JS code on the client.

@dustinsoftware
Copy link
Member

Evaluating arbitrary Javascript at runtime is possible now by implementing IRenderFunctions

See how we're implementing CSS-in-JS support as an example: https://github.com/reactjs/React.NET/blob/master/src/React.Core/RenderFunctions/StyledComponentsFunctions.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants