使用 webpack、bundle-loader react-router 进行隐式代码拆分

Posted

技术标签:

【中文标题】使用 webpack、bundle-loader react-router 进行隐式代码拆分【英文标题】:Implicitly code-splitting with webpack, bundle-loader react-router 【发布时间】:2016-08-14 13:06:09 【问题描述】:

Edit2: 编辑了正则表达式以匹配窗口的文件路径,现在正在发生代码拆分。但是我的根路由的子组件仍然无法加载。

编辑:自上周以来我的代码发生了变化,但我仍然被这个问题所困扰(我需要以声明方式声明我的路线,因此使用 JSX)。

首先,我使用的是带有 React、react-router、bundle-loader、Babel6、ES6 和 airbnb-eslint-config 的 Webpack 1.x。

我尝试关注Henley Edition's article 关于代码拆分和加载块(及其example repo),基于React's huge app example。

但我无法让bundle-loader 将我的代码拆分成块...

这是我的代码:

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;

入口点(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'));

现在webpack 的输出:

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

看,没有捆绑 :( 如果我理解得很好,webpack.config.js 中的第三个加载器应该处理 .js 文件中导入的文件并将它们放入chunks,这样它们就可以加载到dynamically (and lazily)

此外,我的页面无法加载。如果我从图片中取出代码,它可以工作:

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

但是,我的应用程序会很大,我绝对需要代码拆分。

有人可以给我一些见解吗?

提前致谢!

【问题讨论】:

【参考方案1】:

文章作者在这里。尝试运行npm start(运行开发服务器)或webpack -c webpack.config.js(将文件输出到__build__ 目录)。我想你只是忘了将 webpack 指向正确的配置文件。

【讨论】:

您好,谢谢,但我只有一个 webpack.config.js 文件...我尝试过,但没有运气。从那时起,我就采用了原始 React 的巨大应用示例的方式,无论如何,谢谢! 你好。我别无选择,只能在我的代码中实现你的方式(这很酷,因为我喜欢 JSX 的声明方式)。我更新了我的代码,你能看看吗?非常感谢!【参考方案2】:

使用module.exports 而不是export default

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,
;

module.exports = MainContent;

或者最好使用babel-plugin-add-module-exports插件它会转换

// index.js 
export default 'foo'

进入

'use strict';
Object.defineProperty(exports, "__esModule", 
  value: true
);
exports.default = 'foo';

【讨论】:

以上是关于使用 webpack、bundle-loader react-router 进行隐式代码拆分的主要内容,如果未能解决你的问题,请参考以下文章

React-Router4 按需加载的4种实现

react-router 4实现代码分割(code spliting)

【webpack】--config 的使用

webpack入门——webpack的使用

Webpack的使用

# Webpack 学习Webpack 搭建 Vue项目