UNPKG

@dark-engine/platform-server

Version:
159 lines (158 loc) 5.67 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.inject = exports.convertStreamToPromise = exports.renderToStream = exports.renderToString = void 0; const node_stream_1 = require('node:stream'); const core_1 = require('@dark-engine/core'); const platform_browser_1 = require('@dark-engine/platform-browser'); const dom_1 = require('../dom'); const native_element_1 = require('../native-element'); const constants_1 = require('../constants'); const spawn = core_1.nextTick; let nextRootId = -1; let isInjected = false; function inject() { core_1.platform.createElement = dom_1.createNativeElement; core_1.platform.raf = core_1.dummyFn; core_1.platform.caf = core_1.dummyFn; core_1.platform.spawn = spawn; core_1.platform.commit = dom_1.commit; core_1.platform.finishCommit = dom_1.finishCommit; core_1.platform.detectIsDynamic = core_1.falseFn; isInjected = true; } exports.inject = inject; function scheduleRender(options) { !isInjected && inject(); const { element, onCompleted, onError, onStart } = options; const rootId = getNextRootId(); const callback = () => { (0, core_1.setRootId)(rootId); const $scope = (0, core_1.$$scope)(); const fiber = new core_1.Fiber().mutate({ el: new native_element_1.TagNativeElement(core_1.ROOT), inst: new core_1.TagVirtualNode(core_1.ROOT, {}, (0, core_1.flatten)([element || (0, core_1.createReplacer)()])), tag: core_1.CREATE_EFFECT_TAG, }); const emitter = $scope.getEmitter(); $scope.setIsStream(true); $scope.resetMount(); $scope.setWorkInProgress(fiber); $scope.setUnitOfWork(fiber); onStart(); emitter.on('finish', () => { emitter.kill(); onCompleted(); }); emitter.on('error', err => { emitter.kill(); onError(err); }); }; core_1.scheduler.schedule(callback, { priority: core_1.TaskPriority.NORMAL, forceAsync: true }); } function renderToReadableStream(element, options, fromStream) { const { bootstrapScripts = [], bootstrapModules = [], chunkSize = 500, awaitMetatags = false } = options || {}; const stream = new node_stream_1.Readable({ encoding: 'utf-8', read() {} }); let canSendChunks = true; let hasMetatags = false; let content = ''; let stash = ''; const onStart = () => { const emitter = (0, core_1.$$scope)().getEmitter(); emitter.on('box', box => { if (!hasMetatags && (0, platform_browser_1.detectIsMetatagsBox)(box)) { const data = createMetadata(box.vNodes); hasMetatags = true; if (awaitMetatags) { canSendChunks = true; content += data + stash; stash = ''; } else if (!fromStream) { content = content.replace(HEAD_CLOSED_CHUNK, data + HEAD_CLOSED_CHUNK); } } }); emitter.on('chunk', fiber => { const chunk = (0, dom_1.createChunk)(fiber); if (chunk === HEAD_CLOSED_CHUNK && awaitMetatags && !hasMetatags) { canSendChunks = false; } if (canSendChunks) { if (chunk === BODY_CLOSED_CHUNK && (bootstrapScripts.length > 0 || bootstrapModules.length > 0)) { content += addScripts(bootstrapScripts, false); content += addScripts(bootstrapModules, true); } content += chunk; if (content.length >= chunkSize) { stream.push(content); content = ''; } } else { stash += chunk; } }); }; const onCompleted = () => { const rootId = (0, core_1.getRootId)(); if (content) { stream.push(content); content = ''; } stream.push(withState()); stream.push(null); (0, core_1.unmountRoot)(rootId); }; const onError = err => { const rootId = (0, core_1.getRootId)(); stream.emit('error', new Error(err)); stream.push(null); (0, core_1.unmountRoot)(rootId); }; scheduleRender({ element, onStart, onCompleted, onError }); return stream; } function renderToString(element) { return convertStreamToPromise(renderToReadableStream(element)); } exports.renderToString = renderToString; function renderToStream(element, options) { const stream = renderToReadableStream(element, options, true); stream.push(constants_1.DOCTYPE); return stream; } exports.renderToStream = renderToStream; function convertStreamToPromise(stream) { return new Promise((resolve, reject) => { let data = ''; stream.on('data', chunk => (data += chunk)); stream.on('end', () => resolve(data)); stream.on('error', reject); }); } exports.convertStreamToPromise = convertStreamToPromise; function addScripts(scripts, isModule) { if (scripts.length === 0) return ''; let content = ''; scripts.forEach(x => (content += isModule ? createModule(x) : createScript(x))); return content; } function withState(content = '') { const $scope = (0, core_1.$$scope)(); const state = $scope.getResources(); const resources = {}; if (state.size === 0) return content; state.forEach((value, key) => (resources[key] = value)); const encoded = Buffer.from(JSON.stringify(resources)).toString('base64'); const $content = `${content}<script type="${core_1.STATE_SCRIPT_TYPE}">"${encoded}"</script>`; return $content; } const createMetadata = vNodes => (0, dom_1.createNativeChildrenNodes)(vNodes) .map(x => x.renderToString()) .join(''); const createModule = src => `<script type="module" src="${src}" defer></script>`; const createScript = src => `<script src="${src}" defer></script>`; const getNextRootId = () => ++nextRootId; const HEAD_CLOSED_CHUNK = '</head>'; const BODY_CLOSED_CHUNK = '</body>'; //# sourceMappingURL=render.js.map