@enact/dev-utils
Version:
A collection of development utilities for Enact apps.
148 lines (126 loc) • 4.57 kB
JavaScript
/*
* vdom-server-render.js
*
* Uses a domserver component like react-dom/server to render the HTML string
* for a given javascript virtualdom Enact codebase.
*/
const fs = require('fs');
const path = require('path');
const requireUncached = require('import-fresh');
const reroute = require('mock-require');
const FileXHR = require('./FileXHR');
require('console.mute');
let chunkTarget;
let prerenderCache;
import('find-cache-directory').then(({default: findCacheDirectory}) => {
prerenderCache = path.join(
findCacheDirectory({
name: 'enact-dev',
create: true
}),
'prerender'
);
if (!fs.existsSync(prerenderCache)) fs.mkdirSync(prerenderCache);
});
// Skip using the polyfills embedded within the bundle and instead use a local core-js,
// since the bundle's target may differ in compatibility from the active Node process
// (and repeated renders cause memory leaks when embedded polyfills are used).
global.skipPolyfills = true;
require('core-js');
module.exports = {
/*
Stages a target chunk of sourcecode to a temporary directory to be prerendered.
Parameters:
code Target chunk's sourcecode string
opts:
chunk Chunk filename; used to visually note within thrown errors
externals Filepath to external Enact framework to use with rendering
*/
stage: function (code, opts) {
code = code.replace('__webpack_require__.e =', '__webpack_require__.e = function() {}; var origE =');
code = code.replace(
'function webpackAsyncContext(req) {',
'function webpackAsyncContext(req) {\n\treturn new Promise(function() {});'
);
if (opts.externals) {
// Add external Enact framework filepath if it's used.
code = code.replace(
/require\(["']enact_framework["']\)/g,
'require("' + path.resolve(path.join(opts.externals, 'enact.js')) + '")'
);
}
chunkTarget = path.join(prerenderCache, opts.chunk);
fs.writeFileSync(chunkTarget, code, {encoding: 'utf8'});
},
/*
Renders the staged chunk with desired options used.
Parameters:
opts:
server ReactDomServer or server with compatible APIs
locale Specific locale to use in rendering
externals Filepath to external Enact framework to use with rendering
fontGenerator Optional font-generator which can be used to dynamically generate locale-specific font settings
Returns:
HTML static rendered string of the app's initial state.
*/
render: function (opts) {
if (!chunkTarget) throw new Error('Source code not staged, unable render vdom into HTML string.');
let style, rendered;
if (opts.locale) {
global.XMLHttpRequest = FileXHR;
} else {
delete global.XMLHttpRequest;
}
try {
console.mute();
try {
const generator = require(path.resolve(opts.fontGenerator));
const locale = opts.locale || 'en-US';
style = generator(locale);
if (generator.fontOverrideGenerator) style += generator.fontOverrideGenerator(locale);
} catch (e) {
// Temporary fallback to use deprecated global hook.
global.enactHooks = global.enactHooks || {};
global.enactHooks.prerender = function (hook) {
if (hook.appendToHead) {
style = hook.appendToHead;
}
};
}
global.process.env.LANG = opts.locale;
if (opts.externals) {
// Ensure locale switching support is loaded globally with external framework usage.
const framework = requireUncached(path.resolve(path.join(opts.externals, 'enact.js')));
global.React = framework('react');
} else {
delete global.React;
}
const chunk = requireUncached(path.resolve(chunkTarget));
// Clear any server-related children modules from cache
Object.keys(require.cache)
.filter(c => c.startsWith(path.dirname(opts.server)))
.forEach(c => delete require.cache[c]);
// Use the specified server, optionally with exposed React, and generate HTML string
if (global.React) reroute('react', global.React);
const server = requireUncached(opts.server);
rendered = server.renderToString(chunk['default'] || chunk);
if (global.React) reroute.stop('react');
if (style) {
rendered = '<!-- head append start -->\n' + style + '\n<!-- head append end -->' + rendered;
}
// If --expose-gc is used in NodeJS, force garbage collect after prerender for minimal memory usage.
if (global.gc) global.gc();
console.resume();
} catch (e) {
console.resume();
throw e;
}
return rendered;
},
/*
Deletes any staged sourcecode cunks
*/
unstage: function () {
if (chunkTarget && fs.existsSync(chunkTarget)) fs.unlinkSync(chunkTarget);
}
};