diff --git a/babel.config.js b/babel.config.js index 9598a2a..e85b2b1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,7 +9,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 = []; diff --git a/lib/cli.js b/lib/cli.js index 6cea134..9ec323d 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -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'; @@ -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({ @@ -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; @@ -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}`); }); } @@ -123,7 +129,7 @@ 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); @@ -131,9 +137,9 @@ export function cli(args) { if (mergeAllConfigs.dev) { runDevelopmentMode(); } else { - argumentList.noBundle ? - serve(voltranConfigs) : - runProductionMode(mergeAllConfigs, argumentList.bundle); + argumentList.noBundle + ? serve(voltranConfigs) + : runProductionMode(mergeAllConfigs, argumentList.bundle); } }); } else { diff --git a/package.json b/package.json index ba2d6ed..5e4c938 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,10 @@ "@babel/plugin-transform-runtime": "7.10.4", "@babel/preset-env": "7.14.4", "@babel/preset-react": "7.0.0", - "@researchgate/react-intersection-observer": "1.0.3", + "@loadable/babel-plugin": "^5.12.0", + "@loadable/component": "^5.12.0", + "@loadable/server": "^5.12.0", + "@loadable/webpack-plugin": "^5.12.0", "arg": "^4.1.3", "assets-webpack-plugin": "3.8.4", "async": "^3.2.0", @@ -37,7 +40,6 @@ "babel-core": "7.0.0-bridge.0", "babel-eslint": "10.0.1", "babel-loader": "^8.0.4", - "classnames": "2.2.6", "clean-webpack-plugin": "1.0.0", "cli-color": "^2.0.0", "compose-middleware": "5.0.0", @@ -51,18 +53,17 @@ "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", "hiddie": "^1.0.0", "husky": "^3.1.0", - "identity-obj-proxy": "3.0.0", - "intersection-observer": "0.7.0", - "js-cookie": "^2.2.1", "lodash": "4.17.19", "mini-css-extract-plugin": "0.4.4", "newrelic": "^6.13.0", @@ -78,7 +79,6 @@ "prop-types": "15.6.2", "query-string": "6.10.1", "react": "16.12.0", - "react-autosuggest": "9.4.3", "react-dom": "16.12.0", "react-hot-loader": "^4.12.18", "react-router": "5.1.2", diff --git a/src/main.js b/src/main.js index fa6bdf2..ed735a0 100644 --- a/src/main.js +++ b/src/main.js @@ -4,14 +4,15 @@ 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); } @@ -19,15 +20,15 @@ function triggerMessageListener(worker) { } 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); }); @@ -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, () => { diff --git a/src/universal/components/Html.js b/src/universal/components/Html.js index 61cf88e..6b51678 100644 --- a/src/universal/components/Html.js +++ b/src/universal/components/Html.js @@ -24,7 +24,7 @@ function componentClassName(componentName, context) { function Html({ componentName, - children, + bodyHtml, styleTags, initialState, fullWidth, @@ -41,7 +41,7 @@ function Html({ class="${voltranConfig.prefix}-voltran-body voltran-body ${ isMobileFragment ? 'mobile' : '' }${fullWidth ? 'full' : ''} ${componentClassName(componentName, context)}"> - ${children} + ${bodyHtml}
REPLACE_WITH_LINKS
REPLACE_WITH_SCRIPTS
diff --git a/src/universal/partials/withBaseComponent.js b/src/universal/partials/withBaseComponent.js index 0f78d45..d69aab6 100644 --- a/src/universal/partials/withBaseComponent.js +++ b/src/universal/partials/withBaseComponent.js @@ -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'; @@ -35,16 +36,18 @@ const withBaseComponent = (PageComponent, pathName) => { const initialState = fragments[id].STATE; - ReactDOM.hydrate( - - - , - componentEl, - () => { - componentEl.style.pointerEvents = 'auto'; - componentEl.setAttribute('voltran-hydrated', 'true'); - } - ); + loadableReady(() => { + ReactDOM.hydrate( + + + , + componentEl, + () => { + componentEl.style.pointerEvents = 'auto'; + componentEl.setAttribute('voltran-hydrated', 'true'); + } + ); + }); }); } diff --git a/src/universal/service/RenderService.js b/src/universal/service/RenderService.js index 33acc48..4d6ed90 100644 --- a/src/universal/service/RenderService.js +++ b/src/universal/service/RenderService.js @@ -55,7 +55,7 @@ const renderHtml = (component, initialState, context) => { return PureHtml(component.path, component.name, initialStateWithLocation); } - const children = ReactDOMServer.renderToString( + const bodyHtml = ReactDOMServer.renderToString( sheet.collectStyles( @@ -69,7 +69,7 @@ const renderHtml = (component, initialState, context) => { return Html({ resultPath, componentName: component.name, - children, + bodyHtml, styleTags, initialState: initialStateWithLocation, fullWidth: component.fullWidth, diff --git a/src/universal/utils/baseRenderHtml.js b/src/universal/utils/baseRenderHtml.js index a0ef08f..5cac9ce 100644 --- a/src/universal/utils/baseRenderHtml.js +++ b/src/universal/utils/baseRenderHtml.js @@ -14,16 +14,21 @@ const cleanAssetsPrefixFromAssetURI = assetURI => { const cssContentCache = {}; -if (process.env.NODE_ENV === 'production') { - Object.keys(assets).forEach(name => { - if (assets[name].css) { - cssContentCache[name] = fs.readFileSync( - path.resolve( - process.cwd(), - `${voltranConfig.publicDistFolder}/${cleanAssetsPrefixFromAssetURI(assets[name].css)}` - ), - 'utf8' - ); +if (process.env.NODE_ENV === 'production' && assets.assetsByChunkName) { + Object.keys(assets.assetsByChunkName). + forEach(name => { + if (assets.assetsByChunkName[name] && Array.isArray(assets.assetsByChunkName[name]) && assets.assetsByChunkName[name].length > 0) { + assets.assetsByChunkName[name].map(fileName => { + if (fileName.endsWith('.css')) { + cssContentCache[name] = fs.readFileSync( + path.resolve( + process.cwd(), + `${voltranConfig.publicDistFolder}/${cleanAssetsPrefixFromAssetURI(fileName)}`, + ), + 'utf8', + ); + } + }); } }); } @@ -33,12 +38,12 @@ const getScripts = (name, subComponentFiles) => { const scripts = [ { src: `${assetsBaseUrl}${assets.client.js}`, - isAsync: false + isAsync: false, }, { src: `${assetsBaseUrl}${assets[name].js}`, - isAsync: false - } + isAsync: false, + }, ]; const mergedScripts = subComponentFilesScripts && subComponentFilesScripts.length > 0 @@ -48,7 +53,7 @@ const getScripts = (name, subComponentFiles) => { return mergedScripts; }; -const getStyles = async (name, subComponentFiles) => { +const getStyles = async(name, subComponentFiles) => { const links = []; const subComponentFilesStyles = subComponentFiles.styles; @@ -59,7 +64,7 @@ const getStyles = async (name, subComponentFiles) => { criticalStyleComponent: process.env.NODE_ENV === 'production' && !voltranConfig.criticalCssDisabled ? cssContentCache[name] - : undefined + : undefined, }); } @@ -70,7 +75,7 @@ const getStyles = async (name, subComponentFiles) => { criticalStyleComponent: process.env.NODE_ENV === 'production' && !voltranConfig.criticalCssDisabled ? cssContentCache.client - : undefined + : undefined, }); } @@ -88,15 +93,15 @@ const getActiveComponent = name => { return { resultPath: path, componentName: name, - url: path + url: path, }; }; -const createBaseRenderHtmlProps = async (name, subComponentFiles) => { +const createBaseRenderHtmlProps = async(name, subComponentFiles) => { return { scripts: getScripts(name, subComponentFiles), links: await getStyles(name, subComponentFiles), - activeComponent: getActiveComponent(name) + activeComponent: getActiveComponent(name), }; }; diff --git a/webpack.client.config.js b/webpack.client.config.js index cb0adfe..dbbaae3 100644 --- a/webpack.client.config.js +++ b/webpack.client.config.js @@ -10,9 +10,7 @@ const TerserWebpackPlugin = require('terser-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); -const { ESBuildMinifyPlugin } = require('esbuild-loader'); - -require('intersection-observer'); +const LoadablePlugin = require('@loadable/webpack-plugin'); const { createComponentName } = require('./src/universal/utils/helper.js'); @@ -34,7 +32,6 @@ const voltranClientConfig = voltranClientConfigPath ? require(voltranConfig.webpackConfiguration.client) : ''; -const normalizeUrl = require('./lib/os.js'); const replaceString = require('./config/string.js'); const fragmentManifest = require(voltranConfig.routing.dictionary); @@ -42,7 +39,6 @@ const fragmentManifest = require(voltranConfig.routing.dictionary); const isDebug = voltranConfig.dev; const reScript = /\.(js|jsx|mjs)$/; const distFolderPath = voltranConfig.distFolder; -const prometheusFile = voltranConfig.monitoring.prometheus; const chunks = {}; @@ -117,11 +113,13 @@ const clientConfig = webpackMerge(commonConfig, voltranClientConfig, { rules: [ { test: reScript, - loader: 'esbuild-loader', + loader: 'babel-loader', include: [path.resolve(__dirname, 'src'), voltranConfig.inputFolder], options: { - loader: 'jsx', - target: 'es2015' + cacheDirectory: isDebug, + babelrc: false, + plugins: ['@loadable/babel-plugin'], + ...babelConfig() } }, { @@ -209,10 +207,6 @@ const clientConfig = webpackMerge(commonConfig, voltranClientConfig, { optimization: { minimizer: [ - new ESBuildMinifyPlugin({ - target: 'es2015', - css: true - }), new TerserWebpackPlugin({ sourceMap: isDebug, parallel: true, @@ -237,6 +231,15 @@ const clientConfig = webpackMerge(commonConfig, voltranClientConfig, { GO_PIPELINE_LABEL: JSON.stringify(GO_PIPELINE_LABEL) }), + new LoadablePlugin({ + filename: path.resolve( + process.cwd(), + `${voltranConfig.inputFolder}/universal/loadable-stats.json` + ), + writeToDisk: true, + outputAsset: false + }), + new CopyWebpackPlugin([ { from: voltranConfig.output.client.publicPath, diff --git a/webpack.server.config.js b/webpack.server.config.js index 1b9b79b..ecb0cf7 100644 --- a/webpack.server.config.js +++ b/webpack.server.config.js @@ -10,6 +10,7 @@ const voltranConfig = require('./voltran.config'); const appConfigFilePath = `${voltranConfig.appConfigFile.entry}/${env}.conf.js`; const appConfig = require(appConfigFilePath); // eslint-disable-line import/no-dynamic-require +const babelConfig = require('./babel.server.config'); const commonConfig = require('./webpack.common.config'); const postCssConfig = require('./postcss.config'); const replaceString = require('./config/string.js'); @@ -47,11 +48,12 @@ const serverConfig = webpackMerge(commonConfig, voltranServerConfig, { rules: [ { test: /\.(js|jsx|mjs)$/, - loader: 'esbuild-loader', + loader: 'babel-loader', include: [path.resolve(__dirname, 'src'), voltranConfig.inputFolder], options: { - loader: 'jsx', - target: 'es2015' + cacheDirectory: true, + babelrc: false, + ...babelConfig() } }, {