UNPKG

serlina

Version:

A progressive React serverside-rendering framework

240 lines (239 loc) 9.98 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); require("@babel/polyfill"); const webpack = require('webpack'); const webpack_config_1 = require("./config/webpack.config"); const fs = require("fs"); const ReactDOMServer = require("react-dom/server"); const path = require("path"); const React = require("react"); const glob = require("glob"); const WDS = require('webpack-dev-server'); const Document_1 = require("./components/Document"); const rimraf = require('rimraf'); const react_helmet_1 = require("react-helmet"); // @ts-ignore global.Helmet = react_helmet_1.Helmet; const Head_1 = require("./components/Head"); const eventbus_1 = require("./utils/eventbus"); const DEV_SERVER_HOST = '127.0.0.1'; const DEV_SERVER_PORT = 3000; const noCacheRequire = (pkg) => { delete require.cache[pkg]; return require(pkg); }; class Serlina { constructor(options) { this._injectedPayload = {}; this._webpackConfig = {}; this.__eventBus = new eventbus_1.default(); this._makeWebpackConfig = (onFinishedClientSideCompilation) => { return webpack_config_1.default(Object.assign({}, this.options, { onFinishedClientSideCompilation, pages: this._pageEntries, customConfig: this.options.serlinaConfig.webpack })); }; this._onFinishedClientSideCompilation = (state, ctx) => { const json = state['client side'].stats.toJson(); this.chunks = json.chunks; this.__eventBus.emit('compiled'); json.errors.forEach(error => { console.log(error); }); }; let { baseDir = '', host = DEV_SERVER_HOST, port = DEV_SERVER_PORT, useStream = false, // @ts-ignore outputPath = path.resolve(baseDir, '.serlina'), dev = true, // @ts-ignore publicPath = '/', forceBuild = false, __serlinaConfig, __testing, inlineCSS } = options; if (dev) { publicPath = 'http://' + host + ':' + port + '/'; } const serlinaConfig = __serlinaConfig ? __serlinaConfig : (fs.existsSync(path.resolve(baseDir, './serlina.config.js')) ? require(path.resolve(baseDir, './serlina.config.js')) : {}); // @ts-ignore this.options = { baseDir, host, port, outputPath, dev, publicPath, forceBuild, __testing, serlinaConfig, inlineCSS }; this.resolveOutput = (...args) => path.resolve.call(null, this.options.outputPath, ...args); } get _pageEntries() { const pagesPath = path.resolve(this.options.baseDir, './pages'); const pages = glob.sync('**/*.*', { cwd: pagesPath }); return pages; } build() { const webpackConfig = this._makeWebpackConfig(this._onFinishedClientSideCompilation); rimraf.sync(this.options.outputPath); return webpack(webpackConfig); } waitForChunks() { return __awaiter(this, void 0, void 0, function* () { console.log('[serlina]', 'Waiting for compilation finish.'); return new Promise((resolve) => { this.__eventBus.on('compiled', () => { resolve(); }); }); }); } prepare() { if (this.options.dev !== true) { if (this.options.forceBuild === false) { if (!fs.existsSync(this.resolveOutput('./assetsmap.json'))) { throw new Error('assetsmap.json is not found. Do you forget running `serlina build`?'); } this.assetsMap = require(this.resolveOutput('./assetsmap.json')); return Promise.resolve(); } else { // TODO: force build } } const webpackConfig = this._makeWebpackConfig(this._onFinishedClientSideCompilation); this._webpackConfig = webpackConfig; const [serverSide, clientSide, vendors] = webpackConfig; if (this.options.dev === true && this.options.__testing !== true) { const devServerOptions = { quiet: true, }; // WDS.addDevServerEntrypoints(clientSide, devServerOptions) const compiler = webpack([clientSide, serverSide, vendors]); const devServer = new WDS(compiler, devServerOptions); return new Promise((res) => { devServer.listen(this.options.port, this.options.host, () => { this.__eventBus.on('compiled', res); }); }); } if (this.options.__testing) { return new Promise((res, rej) => { webpack(webpackConfig, (err, stats) => { if (err) { rej(err); } res(stats.toJson()); }); }); } } inject(payload) { this._injectedPayload = payload; } render(pageName, injectedPayload) { return __awaiter(this, void 0, void 0, function* () { if (pageName.startsWith('/')) pageName = pageName.replace('/', ''); let page; if (this.options.dev) { if (!fs.existsSync((this.resolveOutput(pageName + '.cmd.js')))) { pageName = '_404'; if (fs.existsSync(this.resolveOutput('./_404.cmd.js'))) { page = noCacheRequire(this.resolveOutput('./_404.cmd.js')); } else { page = { default: require('./components/_404') }; } } else { page = noCacheRequire(this.resolveOutput(pageName + '.cmd.js')); } } else { if (!fs.existsSync((this.resolveOutput(pageName + '.cmd.js')))) { pageName = '_404'; if (fs.existsSync(this.resolveOutput('./404.cmd.js'))) { page = require(this.resolveOutput('./404.cmd.js')); } else { page = { default: require('./components/_404') }; } } else { page = require(this.resolveOutput(`./${pageName}.cmd.js`)); } } const injected = Object.assign({}, this._injectedPayload, injectedPayload); const initialProps = page.default.getInitialProps ? yield page.default.getInitialProps(injected) : {}; let pageScripts = []; let pageStyles = []; if (this.options.dev) { if (!this.chunks) { yield this.waitForChunks(); } const pageChunk = this.chunks.find(chunk => chunk.id === pageName); let files = []; if (pageChunk) { files = pageChunk.files; } else if (pageName === '_404') { files = files.concat([ '_404.js' ]); } pageScripts = files.filter(file => file.endsWith('.js')).concat([ '_SERLINA_VENDOR.js', '_SERLINA_MAIN.js' ]); pageStyles = files.filter(file => file.endsWith('.css')); } else { pageScripts = [ this.assetsMap[pageName] && this.assetsMap[pageName].js, this.assetsMap['_SERLINA_VENDOR'].js, this.assetsMap['_SERLINA_MAIN'].js ].filter(_ => _); pageStyles = this.assetsMap[pageName] && this.assetsMap[pageName].css ? [this.assetsMap[pageName].css] : []; } const body = ReactDOMServer.renderToString(React.createElement(page.default, initialProps)); if (this.options.__testing) { Head_1.default.canUseDOM = false; } const helmet = Head_1.default.renderStatic(); let inlineCSSString = null; if (this.options.inlineCSS === true) { inlineCSSString = pageStyles.map(fileName => { return fs.readFileSync(this.resolveOutput(fileName), 'utf8'); }); } const string = '<!DOCTYPE html>' + ReactDOMServer[this.options.useStream ? 'renderToNodeStream' : 'renderToStaticMarkup'](React.createElement(Document_1.default, { pageScripts, pageStyles, pageName, inlineCSSString, publicPath: this.options.publicPath, initialProps, body, helmet })); return { body: string, __webpackConfig: this._webpackConfig, __injected: injected, __initialProps: initialProps, __pageScripts: pageScripts, __pageStyles: pageStyles, }; }); } } exports.default = Serlina;