这是Webpack+React系列配置过程记录的第四篇。其他内容请参考:

自从前几篇文章介绍如何搭建React+Webpack单页面应用开发环境之后,我就基于这个环境对我的书籍分享网站的管理后台进行业务代码的实现。随着业务代码量的增加,我自定义的React组件也越来越多,这导致每次我刷新浏览器地址的时候都要等待挺久的一段时间。

解决这个问题的思路还是比较简单,分块加载每次需要用到什么就加载什么。基于这个思路进一步扩展一下,我想要针对CDN后者浏览器的缓存做一下优化,从而让浏览器每次只加载被我修改的那部分代码。

代码切割

参考Webpack官方文档,代码分割可以从以下几个方面进行。

CSS资源

之前我们的CSS样式通过Webpack编译到JS代码中,然后由JS代码动态插入到head标签里。这种加载CSS样式的方式,一方面会让JS代码非常大,另一方面会导致在异步加载方式渲染页面的时候网页会闪烁。

这里我们换一种加载方式,让CSS代码作为独立资源导出。这样就减少了JS代码规模,利用浏览器的多个连接同时加载JS代码和CSS代码,提高加载速度。这需要用到一个Webpack的插件:ExtractTextPlugin。

安装ExtractTextPlugin:

npm install --save-dev extract-text-webpack-plugin 

修改webpack.config.js文件:

// 引入ExtractTextPlugin
var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 修改module.rules中关于CSS的节点的内容
//{
// test: /\.css$/,
// use: ['style-loader', 'css-loader']
//},
{
test: /-m\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]-[local]-[hash:base64:5]'
}
}
]
})
},
{
test: /^((?!(-m)).)*\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
} // 在webpack的plugins节点增加下面一行:
plugins: [
new ExtractTextPlugin('styles.css'), // 增加的行,样式将输出到styles.css
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]

上面的配置使用ExtractTextPlugin让Webpack把结果生成到styles.css文件中。这个文件对外的访问目录与js一样。我在这里使用了两种处理CSS文件的方式。首先是带-m结尾的文件,我使用css-loader的启用了模块化处理,让我能够在js中以对象的方式应用css样式。然后是非-m结尾的文件,让webpack调用css-loader和style-loader默认处理。

下面验证一下效果。

在src目录下我创建一个css文件,BasicExample-m.css,内容如下:

.red {
color: red;
}

在BasicExample.js文件中引入css文件,然后在js中应用red样式到一个p标签(这也是我为什么要让css文件名是-m结尾的原因)。改动如下:

...
// 引入
import styles from './BasicExample-m.css';
...
// 应用
<p className={styles.red}>Red Text</p>
...

修改一下index.html,让它引入styles.css即可。

<html>
<head>
<link rel="stylesheet" href="/styles.css"/>
</head>
<body>
<p>Hello world</p>
<div id='main'></div>
<script src="/out.js"></script>
</body>
</html>

启动,然后在浏览器查看一下效果。

启用开发者工具查看网络请求,发现确实请求了styles.css和out.js文件;而且请求到的index.html内容中,head标签内也没有发现嵌入了样式代码。

第三方依赖

第三方依赖在开发过程中属于不常变化的部分,导出到一个独立文件。

假设我的项目使用了第三方库jQuery,因此我使用npm install --save jquery安装了jQuery依赖。

首先我们在src/index.js中添加对jQuery的调用代码,这是为了模拟实际开发中对第三方依赖的调用。如果你的代码没有调用依赖的代码,Webpack找不到入口,也就没有必要为之导出JS文件了。

index.js的内容改动如下:

...

ReactDOM.render(
<AppContainer>
<BasicExample/>
</AppContainer>,
document.getElementById('main')
);
// 添加的代码
import $ from 'jquery';
$('body').append('<p>Hello vendor</p>'); if (module.hot) {
module.hot.accept();
}

接下来开始真正配置针对第三方依赖的代码分割,需要用到Webpack内置的优化插件CommonsChunkPlugin。修改webpack.config.js文件中output节点和plugins节点的代码:

...
entry: {
main:[
'react-hot-loader/patch'
'webpack-hot-middleware/client',
'./src/index.js'
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public')
},
...
plugins: [
new ExtractTextPlugin('styles.css'),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// TODO 对其他第三方依赖也要在这里进行代码分割
return module.context && module.context.indexOf('jquery') !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common'
})
]
...

首先修改了输出的filename,使之根据模块名称命名文件。并且配置了入口为main,因此将代码将导出到main.js而不是原来我们配置的out.js了。

你可能会注意到我两次用到了CommonsChunkPlugin插件。这样做是有原因的。我配置了名为vendor的导出项,用于导出第三方依赖的代码到vendor.js。但是由于Webpack在导出代码的时候会往代码里面加入运行时相关的代码。这就造成我们的main.js和vendor.js都包含同样的Webpack运行时相关代码。所以我配置了第二个名为common的导出项,把这部分的代码抽离出来存放在common.js中。

最后在index.html中引用common.js、vendor.js和main.js。需要注意的是这三个文件之间是有依赖关系的。vendor和main依赖了common,main依赖了vendor。都是调用关系,注意即可。

运行可以看到页面显示了jQuery插入的“Hello vendor”了。打开控制台也可以看到网页请求的内容。

应用代码

对应用里面的代码进行分割就不是通过配置Webpack实现的,而是使用Webpack提供的dynamic import方式实现。Webpack针对React或Vue等框架都有不同的解决方法。我尽在这里介绍React配合react-router如何实现异步加载React组件。

首先需要知道的是dynamic import通过返回Promise的方式实现异步加载功能。

import('./component.js')
.then((m) => {
// 处理异步加载到的模块m
})
.catch((err) => {
// 错误处理
});

要注意的是import的参数不能使用变量,简单原则是至少要让Webpack知晓应该预先加载哪些内容。这里的参数除了使用常量之外,还可以使用模板字符串`componentDir/${name}.js`

其实到这里基本完成代码切割了,接下来做得就是结合react-router实现按模块异步加载。这是跟业务代码相关的,因此每个人的做法都是不一样的。所以以下代码仅供参考。

异步加载

我参考react-router的例子写了个简单的异步加载组件AsyncLoader.js,内容:

import React from 'react';

export default class AsyncLoader extends React.Component {

  static propTypes = {
path: React.PropTypes.string.isRequired,
loading: React.PropTypes.element,
}; static defaultProps = {
path: '',
loading: <p>Loading...</p>,
error: <p>Error</p>
}; constructor(props) {
super(props);
this.state = {
module: null
};
} componentWillMount() {
this.load(this.props);
} componentWillReceiveProps(nextProps) {
if (nextProps.path !== this.props.path
|| nextProps.error !== this.props.error
|| nextProps.loading !== this.props.loading) {
this.load(nextProps);
}
} load(props) { this.setState({module: props.loading}); // TODO:异步代码的路径希望做成可以配置的方式
import(`./path/${props.path}`)
.then((m) => {
let Module = m.default ? m.default : m;
console.log("module: ", Module);
this.setState({module: <Module/>});
}).catch(() => {
this.setState({module: props.error});
});
} render() {
return this.state.module;
}
}

使用方法

<Route
exact path='/book'
render={()=><AsyncLoader path={'./components/Book.js'}/>}
/>

Webpack打包的时候会根据import的参数生成相应的js文件,默认使用id(webpack生成的,从0开始)命名这个文件。

这个过程中我踩了一个坑,这里提出来供大家参考一下。

问题是这样的,当前路径为http://localhost/books时发出异步加载请求,浏览器请求的代码为正常的http://localhost/0.js;但是当前路径为http://localhost/books/detail时发出异步加载请求,浏览器请求的是http://localhost/books/0.js,而/books/0.js这个文件是不存在的。

这个问题折磨了我挺长时间的。后来发现解决办法很简单,只需要在webpack.config.js文件的output节点中添加publicPath属性和值就可以了。虽然没有官方文档可以参考,但是我测试发现,Webpack生成js的时候,如果没有指明publicPath则生成的代码中异步请求是相对于当前地址开始的;否则是相对于publicPath的值。

我把BasicExample.js中的Counter.js修改成异步加载,运行结果如下所示:

React配合Webpack实现代码分割与异步加载的更多相关文章

  1. webpack散记---代码分割 和 懒加载

    webpack methods ES 2015 Loader spec (1)webpack methods方法 require.ensure //可以动态加载依赖 []:dependencies / ...

  2. webpack4 系列教程(四): 单页面解决方案--代码分割和懒加载

    本节课讲解webpack4打包单页应用过程中的代码分割和代码懒加载.不同于多页面应用的提取公共代码,单页面的代码分割和懒加载不是通过webpack配置来实现的,而是通过webpack的写法和内置函数实 ...

  3. 基于webpack的前端工程化开发解决方案探索(二):代码分割与图片加载

    今天我们继续来进行webpack工程化开发的探索! 首先来验证上一篇文章   基于webpack的前端工程化开发解决方案探索(一):动态生成HTML  中的遗留问题:webpack将如何处理按需加载的 ...

  4. react以组件为中心的代码分割和懒加载

    背景 随着项目越来越复杂,功能够越来越多,JS单个文件就会比较臃肿,js代码拆分显得必不可少. Js文件拆分主要分为按照路由进行js拆分.按照组件进行js拆分. 按照路由拆分:因为本项目请求路径得原因 ...

  5. RequireJS解决代码依赖问题,异步加载js,避免页面失去相应

    RequireJS的目标是鼓励代码的模块化,它使用了不同于传统<script>标签的脚本加载步骤.可以用它来加速.优化代码,但其主要目的还是为了代码的模块化.它鼓励在使用脚本时以modul ...

  6. Android学习笔记(二)之异步加载图片

    最近在android开发中碰到比较棘手的问题,就是加载图片内存溢出.我开发的是一个新闻应用,应用中用到大量的图片,一个界面中可能会有上百张图片.开发android应用的朋友可能或多或少碰到加载图片内存 ...

  7. 基于jQuery的图片异步加载和预加载实例

    如今的网页中有很多图片,比如相册列表,那么如果一次性读取图片将会瞬间加重服务器的负担,所以我们用jQuery来实现图片的异步加载和预加载功能,这样在页面的可视范围内才会加载图片,当拖动页面至可视界面时 ...

  8. require.ensure的用法;异步加载-代码分割;

    webpack异步加载的原理 webpack ensure相信大家都听过.有人称它为异步加载,也有人说做代码切割,那这 个家伙到底是用来干嘛的?其实说白了,它就是把js模块给独立导出一个.js文件的, ...

  9. webpack:代码分割与按需加载

    代码分割就是我们根据实际业务需求将代码进行分割,然后在合适的时候在将其加载进入文档中. 代码中总有些东西我们希望拆分开来,比如: 使用概率较低的模块,希望后期使用的时候异步加载 框架代码,希望能利用浏 ...

随机推荐

  1. 老李分享:https协议

    老李分享:https协议   最近我们看到很多站点使用 HTTPS 协议提供网页服务.通常情况下我们都是在一些包含机密信息的站点像银行看到 HTTPS 协议. 如果你访问 google,查看一下地址栏 ...

  2. 手机自动化测试:appium源码分析之bootstrap十五

    手机自动化测试:appium源码分析之bootstrap十五   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

  3. 2.Redis的基本配置

    一.参数配置 redis.conf的主要配置参数的意义: daemonize:是否以后台daemon方式运行 pidfile:pid文件位置 port:监听的端口号 timeout:请求超时时间 lo ...

  4. [Linux] PHP程序员玩转Linux系列-Nginx中的HTTPS

    1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...

  5. java中GUI的awt和Swing的知识点

    刚刚学习了java的GUI,写了几个程序,基本熟悉了awt和Swing,下面和大家分享一下知识点 1.JFrame的层次结构 参考:http://tieba.baidu.com/p/200421612 ...

  6. git教程(简单的带你学好git)

    刚开始使用的时候没有找到好的资源学习,下面这个资源不错,大家可以参考学习. http://www.liaoxuefeng.com/wiki/0013739516305929606dd183612485 ...

  7. Java 静态代理与动态代理

    代理模式 设想你的项目依赖第三方,但是你需要对其接口做一些数据检验.性能数据记录.异常处理等,合适的方法就是使用设计模式里的代理模式. 代理模式是常用的java设计模式,代理类与委托类有同样的接口,代 ...

  8. ios 视频/图片压缩

    - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typica ...

  9. java多线程基本概述(三)——同步块

    1.1.synchronized方法的弊端 package commonutils; public class CommonUtils { public static long beginTime1; ...

  10. Hive 的简单使用及调优参考文档

    Hive 的简单使用及调优参考文档   HIVE的使用 命令行界面 使用一下命令查看hive的命令行页面, hive --help --service cli 简化命令为hive –h 会输出下面的这 ...