serlina
Version:
A progressive React serverside-rendering framework
240 lines (239 loc) • 9.98 kB
JavaScript
"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;