Skip to content

Commit

Permalink
feat: loadable components with partial js loading
Browse files Browse the repository at this point in the history
  • Loading branch information
oguzhanaslan committed May 25, 2021
1 parent ea56003 commit 407f975
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 91 deletions.
3 changes: 2 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = api => {
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions'
'@babel/plugin-proposal-throw-expressions',
'@loadable/babel-plugin',
];

const basePresets = [];
Expand Down
62 changes: 34 additions & 28 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import arg from 'arg';
import fs from 'fs';
import path from 'path';
import clc from "cli-color";
import clc from 'cli-color';
import { spawn } from 'child_process';

import normalizeUrl from './os';
Expand All @@ -19,10 +19,10 @@ function parseArgumentsIntoOptions(rawArgs) {
'--no-bundle': Boolean,
'--analyze': Boolean,
'--port': Number,
'--ssr': Boolean,
'--ssr': Boolean
},
{
argv: rawArgs.slice(2),
argv: rawArgs.slice(2)
}
);
const argsList = removeUnneccesaryValueInObject({
Expand All @@ -32,17 +32,18 @@ function parseArgumentsIntoOptions(rawArgs) {
noBundle: args['--no-bundle'],
analyze: args['--analyze'],
configFile: args['--config'],
ssr: args['--ssr'],
ssr: args['--ssr']
});

return argsList;
}

function getVoltranConfigs(configFile) {
const normalizePath = normalizeUrl(path.resolve(__dirname));
const dirName = normalizePath.indexOf('node_modules') > -1 ?
normalizePath.split('/node_modules')[0] :
normalizePath.split('voltran/lib')[0] + 'voltran';
const normalizePath = normalizeUrl(path.resolve(__dirname));
const dirName =
normalizePath.indexOf('node_modules') > -1
? normalizePath.split('/node_modules')[0]
: `${normalizePath.split('voltran/lib')[0]}voltran`;
const voltranConfigs = require(path.resolve(dirName, configFile));

return voltranConfigs;
Expand All @@ -68,35 +69,40 @@ function runDevelopmentMode() {
function runProductionMode(voltranConfigs, onlyBundle) {
const bundle = require('../src/tools/bundle');

bundle()
.then((res) => {
console.log(clc.green('Bundle is completed.\n',`File: ${voltranConfigs.distFolder}/server/server.js`));
bundle().then(() => {
console.log(
clc.green('Bundle is completed.\n', `File: ${voltranConfigs.distFolder}/server/server.js`)
);

if (!onlyBundle) {
serve(voltranConfigs);
}
});
if (!onlyBundle) {
serve(voltranConfigs);
}
});
}

function serve(voltranConfigs) {
console.log(clc.green('Project Serve is starting...'));

const out = spawn('node', [
'-r',
'source-map-support/register',
'--max-http-header-size=20480',
`${voltranConfigs.distFolder}/server/server.js`
], {env: {'NODE_ENV': 'production', ...process.env}});
const out = spawn(
'node',
[
'-r',
'source-map-support/register',
'--max-http-header-size=20480',
`${voltranConfigs.distFolder}/server/server.js`
],
{ env: { NODE_ENV: 'production', ...process.env } }
);

out.stdout.on('data', (data) => {
out.stdout.on('data', data => {
console.log(data.toString());
});

out.stderr.on('data', (data) => {
out.stderr.on('data', data => {
console.error(data.toString());
});

out.on('close', (code) => {
out.on('close', code => {
console.log(`child process exited with code ${code}`);
});
}
Expand All @@ -123,17 +129,17 @@ export function cli(args) {
if (isValid) {
const createdConfig = `module.exports = ${JSON.stringify(mergeAllConfigs)}`;

fs.writeFile(path.resolve(__dirname, '../voltran.config.js'), createdConfig, function (err) {
fs.writeFile(path.resolve(__dirname, '../voltran.config.js'), createdConfig, function(err) {
if (err) throw err;

console.log('File is created successfully.', mergeAllConfigs.dev);

if (mergeAllConfigs.dev) {
runDevelopmentMode();
} else {
argumentList.noBundle ?
serve(voltranConfigs) :
runProductionMode(mergeAllConfigs, argumentList.bundle);
argumentList.noBundle
? serve(voltranConfigs)
: runProductionMode(mergeAllConfigs, argumentList.bundle);
}
});
} else {
Expand Down
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@babel/core": "7.10.4",
"@babel/eslint-parser": "^7.13.14",
"@babel/plugin-proposal-class-properties": "7.10.4",
"@babel/plugin-proposal-numeric-separator": "7.10.4",
"@babel/plugin-proposal-optional-chaining": "7.10.4",
Expand All @@ -29,14 +30,18 @@
"@babel/preset-env": "7.10.4",
"@babel/preset-react": "7.10.4",
"@babel/runtime": "^7.11.2",
"@loadable/babel-plugin": "^5.13.2",
"@loadable/component": "^5.12.0",
"@loadable/server": "^5.12.0",
"@loadable/webpack-plugin": "^5.12.0",
"@researchgate/react-intersection-observer": "1.0.3",
"arg": "^4.1.3",
"assets-webpack-plugin": "3.8.4",
"async": "^3.2.0",
"autoprefixer": "9.3.1",
"axios": "0.19.0",
"babel-eslint": "10.0.1",
"@babel/eslint-parser": "^7.12.1",
"babel-loader": "8.0.4",
"classnames": "2.2.6",
"clean-webpack-plugin": "1.0.0",
"cli-color": "^2.0.0",
Expand All @@ -46,14 +51,16 @@
"copy-webpack-plugin": "4.5.2",
"css-loader": "1.0.1",
"eev": "0.1.5",
"eslint": "6.1.0",
"esbuild-loader": "^2.11.0",
"eslint": "6.1.0",
"eslint-config-airbnb": "18.0.1",
"eslint-config-prettier": "6.3.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.1",
"eslint-plugin-react": "7.14.3",
"eslint-plugin-react-hooks": "^4.2.0",
"esm": "^3.2.25",
"file-loader": "1.1.11",
"helmet": "3.21.3",
Expand Down
15 changes: 8 additions & 7 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,31 @@ import cluster from 'cluster';
import logger from './universal/utils/logger';
import Hiddie from 'hiddie';
import http from 'http';
import voltranConfig from '../voltran.config';
import prom from 'prom-client';
import {HTTP_STATUS_CODES} from './universal/utils/constants';

import voltranConfig from '../voltran.config';
import { HTTP_STATUS_CODES } from './universal/utils/constants';

const enablePrometheus = voltranConfig.monitoring.prometheus;

function triggerMessageListener(worker) {
worker.on('message', function (message) {
worker.on('message', function(message) {
if (message?.options?.forwardAllWorkers) {
sendMessageToAllWorkers(message);
}
});
}

function sendMessageToAllWorkers(message) {
Object.keys(cluster.workers).forEach(function (key) {
Object.keys(cluster.workers).forEach(function(key) {
const worker = cluster.workers[key];
worker.send({
msg: message.msg,
msg: message.msg
});
}, this);
}

cluster.on('fork', (worker) => {
cluster.on('fork', worker => {
triggerMessageListener(worker);
});

Expand All @@ -52,7 +53,7 @@ if (cluster.isMaster) {
return res.end(await aggregatorRegistry.clusterMetrics());
}
res.statusCode = HTTP_STATUS_CODES.NOT_FOUND;
res.end(JSON.stringify({message: 'not found'}));
res.end(JSON.stringify({ message: 'not found' }));
});

http.createServer(hiddie.run).listen(metricsPort, () => {
Expand Down
4 changes: 2 additions & 2 deletions src/universal/components/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function componentClassName(componentName, context) {

function Html({
componentName,
children,
bodyHtml,
styleTags,
initialState,
fullWidth,
Expand All @@ -41,7 +41,7 @@ function Html({
class="${voltranConfig.prefix}-voltran-body voltran-body ${
isMobileFragment ? 'mobile' : ''
}${fullWidth ? 'full' : ''} ${componentClassName(componentName, context)}">
${children}
${bodyHtml}
</div>
<div>REPLACE_WITH_LINKS</div>
<div>REPLACE_WITH_SCRIPTS</div>
Expand Down
23 changes: 13 additions & 10 deletions src/universal/partials/withBaseComponent.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { loadableReady } from '@loadable/component';

import ClientApp from '../components/ClientApp';
import { WINDOW_GLOBAL_PARAMS } from '../utils/constants';
Expand Down Expand Up @@ -35,16 +36,18 @@ const withBaseComponent = (PageComponent, pathName) => {

const initialState = fragments[id].STATE;

ReactDOM.hydrate(
<ClientApp>
<PageComponent {...staticProps} initialState={initialState} history={history} />
</ClientApp>,
componentEl,
() => {
componentEl.style.pointerEvents = 'auto';
componentEl.setAttribute('voltran-hydrated', 'true');
}
);
loadableReady(() => {
ReactDOM.hydrate(
<ClientApp>
<PageComponent {...staticProps} initialState={initialState} history={history} />
</ClientApp>,
componentEl,
() => {
componentEl.style.pointerEvents = 'auto';
componentEl.setAttribute('voltran-hydrated', 'true');
}
);
});
});
}

Expand Down
40 changes: 31 additions & 9 deletions src/universal/service/RenderService.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import React from 'react';
import { ServerStyleSheet } from 'styled-components';
import PureHtml, { generateLinks, generateScripts } from '../components/PureHtml';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { ChunkExtractor } from '@loadable/server';

/* Components */
import PureHtml, { generateLinks, generateScripts } from '../components/PureHtml';
import ConnectedApp from '../components/App';
import Html from '../components/Html';

/* Utils */
import ServerApiManagerCache from '../core/api/ServerApiManagerCache';
import createBaseRenderHtmlProps from '../utils/baseRenderHtml';
import { guid } from '../utils/helper';
import path from 'path';

/* Config */
const voltranConfig = require('../../../voltran.config');

const loadableStats = path.resolve(
process.cwd(),
`${voltranConfig.inputFolder}/universal/loadable-stats.json`
);

const getStates = async (component, context, predefinedInitialState) => {
const initialState = predefinedInitialState || { data: {} };
Expand Down Expand Up @@ -41,6 +55,10 @@ const renderLinksAndScripts = (html, links, scripts) => {
.replace('<div>REPLACE_WITH_SCRIPTS</div>', scripts);
};

const getExtractor = (entrypoints, statsFile = loadableStats) => {
return new ChunkExtractor({ statsFile, entrypoints });
};

const renderHtml = (component, initialState, context) => {
// eslint-disable-next-line no-param-reassign
component.id = guid();
Expand All @@ -51,21 +69,25 @@ const renderHtml = (component, initialState, context) => {
return PureHtml(component.path, component.name, initialStateWithLocation);
}

const children = ReactDOMServer.renderToString(
sheet.collectStyles(
<StaticRouter location={component.path} context={context}>
<ConnectedApp initialState={initialStateWithLocation} location={context} />
</StaticRouter>
)
const Fragment = () => (
<StaticRouter location={component.path} context={context}>
<ConnectedApp initialState={initialStateWithLocation} location={context} />
</StaticRouter>
);

const extractor = getExtractor([component.name]);
const jsx = extractor.collectChunks(<Fragment />);
const styleSheet = new ServerStyleSheet();

const bodyHtml = ReactDOMServer.renderToString(styleSheet.collectStyles(jsx));

const styleTags = sheet.getStyleTags();
const resultPath = `'${component.path}'`;

return Html({
resultPath,
componentName: component.name,
children,
bodyHtml,
styleTags,
initialState: initialStateWithLocation,
fullWidth: component.fullWidth,
Expand Down Expand Up @@ -108,7 +130,7 @@ const renderComponent = async (component, context, predefinedInitialState = null
fullWidth: component.fullWidth,
isMobileComponent: component.isMobileComponent,
isPreviewQuery: component.isPreviewQuery,
responseOptions,
responseOptions
};
};

Expand Down
Loading

0 comments on commit 407f975

Please sign in to comment.