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/>');
});