Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicitly code-splitting with webpack, bundle-loader react-router

Edit2: Edited the regex to match window's file paths and the code splitting is now happening. But my children components to my root route still won't load.

Edit: My code has changed since last week, but I'm still stuck on that problem (and I need to declare my routes in a declarative way, so using JSX).

First of all, I'm using Webpack 1.x with React, react-router, bundle-loader, Babel6, ES6 and airbnb-eslint-config.

I tried following Henley Edition's article about code-splitting and loading chunks (with its example repo), based on React's huge app example.

But I couldn't manage to make bundle-loader split my code into chunks...

Here's my code :

webpack.config.js

const webpack = require('webpack');
const path = require('path');

const nodeDir = `${__dirname}/node_modules`;

const routeComponentRegex = /src[\/\\]views[\/\\]([^\/\\]+)[\/\\]js[\/\\]([^\/\\]+).js$/;
const paths = [ // Only to test which files match the regex, juste in case
  'src/views/index/js/Index.js',
  'src/views/login-page/js/LoginForm.js',
  'src/common/main-content/js/MainContent.js',
  'src/routes/main.js',
];

console.log(routeComponentRegex.test(paths[0])); // prints 'true'
console.log(routeComponentRegex.test(paths[1])); // prints 'true'
console.log(routeComponentRegex.test(paths[2])); // prints 'false'
console.log(routeComponentRegex.test(paths[3])); // prints 'false'

const config = {
  resolve: {
    alias: {
      react: `${nodeDir}/react`,
      'react-dom': `${nodeDir}/react-dom`,
      'react-router': `${nodeDir}/react-router`,
      'react-fetch': `${nodeDir}/react-fetch`,
      'react-cookie': `${nodeDir}/react-cookie`,
      'react-bootstrap': `${nodeDir}/react-bootstrap`,
      'react-bootstrap-daterangepicker': `${nodeDir}/react-bootstrap-daterangepicker`,
      'react-bootstrap-datetimepicker': `${nodeDir}/react-bootstrap-datetimepicker`,
      velocity: `${nodeDir}/velocity-animate`,
      moment: `${nodeDir}/moment`,
      slimscroll: `${nodeDir}/slimscroll`,
    },
  },
  entry: {
    app: './client/src/routes/js/main',
    vendors: [
      'react', 'react-dom',
      'react-router', 'react-fetch', 'react-cookie',
      'react-bootstrap', 'react-bootstrap-daterangepicker', 'react-bootstrap-datetimepicker',
      'velocity', 'moment', 'slimscroll',
    ],
  },
  output: {
    path: path.join(__dirname, 'public/dist'),
    publicPath: '/dist/',
    filename: 'bundles/[name].bundle.js',
    chunkFilename: 'chunks/[name].chunk.js',
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        include: path.join(__dirname, 'client'),
        exclude: routeComponentRegex,
        loader: 'babel',
      },
      {
        test: /\.css$/,
        include: path.join(__dirname, 'client'),
        exclude: routeComponentRegex,
        loader: 'style!css-loader?modules&importLoaders=1' +
        '&localIdentName=[name]__[local]___[hash:base64:5]',
      },
      {
        test: routeComponentRegex,
        include: path.join(__dirname, 'client'),
        loaders: ['bundle?lazy', 'babel'],
      },
    ],
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendors', 'bundles/vendors.js', Infinity),
  ],
};

module.exports = config;

client/src/views/main-content/js/MainContent.js

import React from 'react';
import { Link } from 'react-router';

const MainContent = (props) => (
    <div>
      <h1>App</h1>
      <ul>
        <li><Link to="/login">Login</Link></li>
      </ul>
      {props.children}
    </div>
);

MainContent.propTypes = {
  children: React.PropTypes.node.isRequired,
};

export default MainContent;

public/src/views/index/js/Index.js

import React from 'react';

const Index = () => (
    <h2>Index Page</h2>
);

export default Index;

public/src/views/login/js/Login.js

import React from 'react';

const LoginForm = () => (
  <div className="box box-default">
    <h2>Login Page</h2>
  </div>
);

export default LoginForm;

Entry point (client/src/routes/main.js) :

import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

import MainContent from '../../common/main-content/js/MainContent';

// modules supposed to be loaded lazily
import Index from '../../views/index/js/Index';
import Login from '../../views/login/js/Login';
import ShortOffers from '../../views/short-offers/js/ShortOffers';
import CreateJobOffer from '../../views/create-job-offer/js/CreateJobOffer';

function lazyLoadComponent(lazyModule) {
  return (location, cb) => {
    lazyModule(module => {
      cb(null, module);
    });
  };
}

function lazyLoadComponents(lazyModules) {
  return (location, cb) => {
    const moduleKeys = Object.keys(lazyModules);
    const promises = moduleKeys.map(key =>
      new Promise(resolve => lazyModules[key](resolve))
    );

    Promise.all(promises).then(modules => {
      cb(null, modules.reduce((obj, module, i) => {
        obj[moduleKeys[i]] = module;
        return obj;
      }, {}));
    });
  };
}

render((
  <Router history={browserHistory}>
    <Route path="/" component={MainContent}>
      <IndexRoute getComponent={lazyLoadComponent(Index)} />
      <Route path="short-offers" getComponent={lazyLoadComponent(ShortOffers)} />
      <Route path="create-job-offer" getComponent={lazyLoadComponent(CreateJobOffer)} />
    </Route>
    <Route path="login" getComponent={lazyLoadComponent(Login)} />
  </Router>
), document.getElementById('content'));

Now webpack's output :

Hash: a885854f956aa8d2a00c
Version: webpack 1.13.0
Time: 6321ms
                Asset     Size  Chunks             Chunk Names
bundles/app.bundle.js  84.7 kB       0  [emitted]  app
   bundles/vendors.js  2.55 MB       1  [emitted]  vendors
chunk    {0} bundles/app.bundle.js (app) 89 kB {1} [rendered]
    [0] multi app 28 bytes {0} [built]
     + 26 hidden modules
chunk    {1} bundles/vendors.js (vendors) 2.45 MB [rendered]
    [0] multi vendors 148 bytes {1} [built]
     + 626 hidden modules

See, no bundles :( If I understood well, the third loader in webpack.config.js should take care of files imported in .js files and make them into chunks, so they could be loaded dynamically (and lazily).

Additionally, my pages don't load. If I take out the code splitting out of the picture, it works :

render((
  <Router history={browserHistory}>
    <Route path="/" component={MainContent}>
      <IndexRoute component={Index} />
      <Route path="short-offers" getComponent={ShortOffers} />
      <Route path="create-job-offer" getComponent={CreateJobOffer} />
    </Route>
    <Route path="login" getComponent={LoginPage} />
  </Router>
), document.getElementById('content'));

But, my app is gonna be huge and I absolutely need code-splitting.

Would anyone please have a piece of insight to give me?

Thanks in advance!

like image 280
adrienharnay Avatar asked Apr 21 '16 11:04

adrienharnay


1 Answers

Article author here. Try just running npm start (runs dev server) or webpack -c webpack.config.js (outputs files to __build__ directory). I think you just forgot to point webpack at the correct config file.

like image 80
Evan Henley Avatar answered Oct 05 '22 23:10

Evan Henley