react-loadable → loadable-components 로 마이그레이션 과정
기본 설치
yarn add @loadable/component
코드 스플리팅
import loadable from '@loadable/component';
import PageLoader from 'components/common/Loader/PageLoader';
export const Home = loadable(
() => import('./Home/Home'),
{ fallback: <PageLoader /> },
);
ssr 모듈 설치
yarn add @loadable/server && yarn add --dev @loadable/babel-plugin @loadable/webpack-plugin
package.json bebel plugins 설정
"plugins": [
"@loadable/babel-plugin"
]
webpack.config.prod.js
const LoadablePlugin = require('@loadable/webpack-plugin');
// ...
module.exports = {
// ...
plugins: [
new LoadablePlugin()
],
}
Setup ChunkExtractor server-side
import fs from 'fs';
import path from 'path';
import React from 'react';
import express from 'express';
import cookieParser from 'cookie-parser';
import { StaticRouter, matchPath } from 'react-router-dom';
import { renderToString } from 'react-dom/server';
import { Helmet } from 'react-helmet';
import { ChunkExtractor } from '@loadable/server';
import { Provider } from 'react-redux';
import App from './shared/App';
import configure from './store/configure';
import prefetchConfig from './prefetchConfig';
import stats from '../build/loadable-stats';
const app = express();
const indexHtml = fs.readFileSync(path.resolve(__dirname, '../build/index.html'), 'utf8');
function createPage(rootHtml, extractor, state, helmet) {
let html = indexHtml;
const chunkStyles = extractor.getStyleTags();
const chunkScripts = extractor.getScriptTags();
html = html.replace(
'<div id="root"></div>',
`<div id="root">${rootHtml}</div>`,
);
const mainStyles = html.match(/<link href="\\/static\\/css\\/..*.chunk.css" rel="stylesheet">/)[0];
html = html.replace(mainStyles, `${chunkStyles}`);
html = html.replace(
'<meta helmet>',
`${helmet.title.toString()}${helmet.meta.toString()}${helmet.link.toString()}`,
);
const customScripts = `<script>
window.ssr = true;
window.__PRELOADED_STATE__ = ${JSON.stringify(state).replace(
/</g,
'\\\\\\u003c',
)};
window.shouldCancel = true;
</script>`;
const scripsRegex = /<script src="\\/static\\/js\\/.*.chunk.js"><\\/script>|<script>.*<\\/script>/gi;
html = html.replace(scripsRegex, '');
html = html.replace('</body>', `${customScripts}${chunkScripts}`);
return html;
}
const serverRender = async (req, res) => {
const store = configure();
const promises = [];
prefetchConfig.forEach((route) => {
const match = matchPath(req.path, route);
if (match) {
const { params } = match;
const { query, cookies } = req;
const p = route.prefetch({ store, params, query, cookies });
promises.push(p);
}
});
await Promise.all(promises);
const extractor = new ChunkExtractor({ stats });
const jsx = extractor.collectChunks(
<Provider store={store}>
<StaticRouter location={req.url}>
<App />
</StaticRouter>
</Provider>,
);
const html = renderToString(jsx);
const state = store.getState();
const helmet = Helmet.renderStatic();
res.send(createPage(html, extractor, state, helmet));
};
app.get('/', (req, res) => {
if (req.path === '/') {
return res.redirect('/scrap');
}
return serverRender(req, res);
});
app.use(cookieParser());
app.use(express.static(path.resolve(__dirname, '../build')));
app.use((req, res, next) => {
if (!req.route) {
return serverRender(req, res);
}
return next();
});
app.listen(3001, () => {
console.log('Running on <http://localhost:3001/>');
});