I think that was it! Here is code that doesn’t seem to leak (so far):
import { WebApp } from 'meteor/webapp'
import React from 'react'
import s2s from 'string-to-stream'
import sq from 'streamqueue'
import { StaticRouter } from 'react-router'
import { renderToString } from 'react-dom/server'
import { FastRender } from 'meteor/staringatlights:fast-render'
import { LoadableCaptureProvider, preloadAllLoadables } from 'meteor/npdev:react-loadable'
import { HelmetProvider } from 'react-helmet-async'
import { ServerStyleSheets, ThemeProvider } from '@material-ui/styles'
// import { green, red } from '@material-ui/core/colors'
import App from '/imports/App'
import theme from '/imports/ui/common/theme'
import { DataCaptureProvider } from 'meteor/npdev:collections'
h = React.createElement // eslint-disable-line
preloadAllLoadables().then(() => FastRender.onPageLoad(async sink => {
const context = {}
const helmetContext = {}
let dataHandle = {}
let loadableHandle = {}
const sheets = new ServerStyleSheets()
const app = <ThemeProvider theme={theme}>
<HelmetProvider context={helmetContext}>
<LoadableCaptureProvider handle={loadableHandle}>
<DataCaptureProvider handle={dataHandle}>
<StaticRouter location={sink.request.url} context={context}>
<App />
</StaticRouter>
</DataCaptureProvider>
</LoadableCaptureProvider>
</HelmetProvider>
</ThemeProvider>
let html
try {
html = renderToString(sheets.collect(app))
} catch (e) {
console.error(e)
WebApp.addHtmlAttributeHook(() => ({
lang: 'en'
}))
return
}
let { helmet } = helmetContext
const meta = helmet.meta.toString()
meta && sink.appendToHead(meta + '\n')
const title = helmet.title.toString()
title && sink.appendToHead(title + '\n')
const link = helmet.link.toString()
link && sink.appendToHead(link + '\n')
WebApp.addHtmlAttributeHook(() => {
const attrs = Object.assign({
lang: 'en',
'xmlns:og': 'http://ogp.me/ns#'
}, helmet?.htmlAttributes?.toComponent() || {})
helmet = null
return attrs
})
// :TODO: Figure out how to do helmet.bodyAttributes...
const css = sheets.toString()
sink.appendToHead(`<style id="jss-server-side">\n${css}\n</style>`)
// :HACK: The meteor css bundle should come after the JSS output, so just move it manually
sink.appendToHead('\n<script id="css-fixer">elm=document.getElementsByClassName("__meteor-css__")[0];elm.parentNode.appendChild(elm);elm=document.getElementById("css-fixer");elm.parentNode.removeChild(elm);delete elm</script>')
const queuedStreams = sq(
() => s2s(html),
() => {
html = null
return s2s(loadableHandle.toScriptTag())
},
() => {
loadableHandle = null
return s2s(dataHandle.toScriptTag())
},
() => {
dataHandle = null
return s2s('')
}
)
sink.renderIntoElementById('root', queuedStreams)
}))
I am getting some sync errors on hydration, but it’s no longer leaking! I’ll investigate sync errors later.