MithrilJS / mithril-node-render

Use mithril views to render server side
MIT License
210 stars 44 forks source link

Future-proofing #89

Closed Niklas81 closed 5 years ago

Niklas81 commented 5 years ago

Is there any way we can run mithril-node-render with Mithril 2.0.0-rc.3? If not now, are you planning to support Mithril 2.0 at some point in the future / when it's not in pre-release?

ACXgit commented 5 years ago

I'm running it in a new project I started last week, and so far it works as expected for me. Which issues have you encountered?

Niklas81 commented 5 years ago

I have multiple issues. They aren't all related to mithril-node-render, but still quite blocking.. Knowing it should work helped a bit in itself, though.

On a page that performs a Promise.all and then in the .then runs m.redraw I get

ReferenceError: requestAnimationFrame is not defined
    at Function.redraw (/home/node/app/node_modules/mithril/mithril.js:1326:4)

in server side. I use Axios instead of m.request, which is why I need to run that after data fetching.

I noticed that m.redraw() is async now, but not sure how to best refactor to adjust to this. Using m.redraw.sync(), makes it work, but what I can gather from the API this is only intended to make videos work on iOS.. What's the reccomended solution in my case? Does the choice stand between m.redraw.sync() or process.browser ? m.redraw() : '' or is there a better solution?

Loading the same page without m.redraw, the page renders perfectly-server side, but then I have an issue with Mithril itself (or perhaps Webpack when loading module with specific version?) as I get Unhandled promise rejection TypeError: "m is not a function".

How can I solve this?

ACXgit commented 5 years ago

On the Node.js side I include a file with the following content:

// This mocks the browser on Node.js
require('mithril/test-utils/browserMock')(global)
// This is to make m.request work on Node.js, in case you use it
global.window.XMLHttpRequest = require('w3c-xmlhttprequest').XMLHttpRequest
// This is to avoid the requestAnimationFrame error
global.requestAnimationFrame = () => {}

Let me know if it helps.

StephanHoyer commented 5 years ago

The route we take is to create a custom server-side m, since mithril-node-render pretty much only needs hyperscript to run.

// m.js

'use strict'

if (process.browser) {
  module.exports = require('mithril')
} else {
  module.exports = require('mithril/hyperscript')
  module.exports.route = {
    get: noop,
    param: noop,
  }
  module.exports.redraw = noop
}

Usage

const m = require('/m.js')

This way you don't have any issues with browser stuff in node. You can also patch this as you like and also ad some custom helper functions like bss or classnames.

We use browserify for bundling. I think this is also possible for other bundlers.

Niklas81 commented 5 years ago

I really like that solution, saves a lot of process.browser conditional statements, and lets more aspects of Mithril be isomorphic. Presumably noop should be defined as () => {}?

Thank you both! It works well with Webpack as well, the other issue was simply a result of Webpack loading Mithril's .mjs file by default; it was simply a matter of fixing the config.