关于webpack代码拆分的解析


Posted in Javascript onJuly 20, 2017

最近在学习VUE的时候接触到了webpack,今天就留个小笔记,介绍一下webpack代码拆分

如果利用 webpack 将项目中的所有代码打包在一起,很多时候是不适用的,因为代码中有些东西我们总是希望将其拆分出来。比如:

  1. 样式表,希望利用 link 标签引入
  2. 使用概率较低的模块,希望后期需要的时候异步加载
  3. 框架代码,希望能利用浏览器缓存下部分不易变动的代码

下面是我在阅读 webpack 的官方文档时候,记录的一些笔记,部分地方使用了自己的话来讲,力图让它显得更易懂。

按需加载拆分

webpack 可以帮助我们将代码分成不同的逻辑块,在需要的时候加载这些代码。

使用 require.ensure() 来拆分代码

require.ensure() 是一种使用 CommonJS 的形式来异步加载模块的策略。在代码中通过 require.ensure([<fileurl>]) 引用模块,其使用方法如下:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

第一个参数指定依赖的模块,第二个参数是一个函数,在这个函数里面你可以使用 require 来加载其他的模块,webpack 会收集 ensure 中的依赖,将其打包在一个单独的文件中,在后续用到的时候使用 jsonp 异步地加载进去。

require.ensure(['./a'], function(require){
  let b = require('./b');
  let a = require('./a');
  console.log(a+b)
});

以上代码,a 和 b 会被打包在一起,在代码中执行到这段代码的时候,会异步地去加载,加载完成后执行函数里面的逻辑。

let a = require('./a');
require.ensure(['./a'], function(require){
  let b = require('./b');
  console.log(a+b)
});

如果这样写,那么 a 不会和 b 打包在一起,因为 a 已经被打包在主代码中了。

require.ensure(['./c'], function(require){
  let a = require('./a');
  console.log(a)
});

require.ensure(['./c'], function(require){
  let b = require('./b');
  console.log(b)
});

以上代码中两个模块都依赖了 c 模块,这个时候会拆分出两个模块,其中都包含了 c 模块,因为在实际运用中,以上两个 require.ensure 的执行顺序不确定,执行与否也不确定,因此需要将 c 模块都打包进去。

require.ensure 还可以传入第三个参数,这个参数用来指定打包的包名,对于上面这种情况,c 模块被打包入了两个包中,如果事先明确这两个包都会被使用,那么不妨将这两个包合并为一个,这样就不会有 c 模块被打包两次的问题了,所以可以将 chunkName 指定为同一个名字。

require.ensure(['./c'], function(require){
  let a = require('./a');
  console.log(a)
}, 'd');

require.ensure(['./c'], function(require){
  let b = require('./b');
  console.log(b)
}, 'd');

ok,这样以上两个 require.ensure 拆出来的包就合并为同一个了。

CSS 拆分

开发者,可能希望能将工程中的所有引入的 CSS 拆分为单个文件,这样可以利用缓存,且利用 CSS 和 JavaScript 并行加载,来加速 web 应用。

使用 css-loader

为了加载 css,这里需要用到 css-loader,配置方法如下:

module: {
  loaders: [{
    test: /\.css$/,
    exclude: /node_modules/,
    loader: 'css-loader'
  }]
}

这样在代码中就可以写如下代码:

let css = require('./main.css');
console.log('' + css);

通过 require 一个 css 得到其内容,当然了这里 require('./main.css') 实际得到的是一个对象,需要调用其 toString 方法将其转换为字符串。在代码中引用一段 css,这常常不是我们想要的。为此可以使用 style-loader 在代码执行起来的时候,会将这些 css 插入到 style 标签中,只是这里 css 还是存在于 js 中的,是后来动态插入到页面中的:

module: {
  loaders: [{
    test: /\.css$/,
    exclude: /node_modules/,
    loader: 'style-loader!css-loader'
  }]
}

更多时候,是希望将 css 拆分为单个文件,然后使用 link 标签嵌入到 html 中,CSS 和 JavaScript 可以并行加载,css 还可以被缓存下来。

使用 extract-text-webpack-plugin 来拆分 css

为了使用这个插件首先需要通过 npm 来安装它:

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

然后在 webpack 的配置文件中使用该插件:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = function () {
  return {
    entry: './index.js',
    output: {
      path: './build',
      filename: 'bundle.js'
    },
    module: {
      loaders: [{
        test: /\.css$/,
        exclude: /node_modules/,
        // 在 loader 中使用该插件
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
      }]
    },
    plugins: [
      // 将其添加在插件中
      new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })
    ]
  }
}

需要注意的是,对于 webpack1 和 webpack2 这个插件的配置方法是不同的,差别比较细微,详情请看官方文档 extract text plugin for webpack 2

拆分业务代码与框架代码

通常一个 web 应用都会引用若干第三方库,这些第三方库通常比较稳定不会经常变动,但是如果将业务代码和框架代码打包在了一起,这样业务代码每次变动打包得到的结果都会变动,及时只改变了一个字符,浏览器也无法利用缓存,必须全部重新加载。因此,何不将第三方库单独打包在一起呢?

这里举个案例,一个 react 项目中使用了 react react-dom 这两个包,我希望将他们打包在一起,将业务代码打包在一起。

下面一步一步来:

1. 安装 react 和 react-dom:

npm i react react-dom --save

2. 配置 entry,output 和 loader

先使用单入口,让代码工作起来。另外因为使用了 react 所以要使用 babel-loader 来加载 js

// webpack.config.js

module.exports = {
  entry: 'index.js',
  output: {
    path: 'build/',
    filname: '[name]@[chunkhash].js'
  },
  module:{
    loaders:[{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel'
    }]
  }
}

3. 编写业务代码

index.js:

import React from 'react';
import ReactDOM from 'react-dom';


var Hello = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});

ReactDOM.render(<Hello name={'world'} />, document.getElementById('app'));

index.html:

<div id="app"></div>
<!--entry 为一个字符串,这个 chunk 的名字会是 main, 因此这里引入 main.js -->
<script src="build/main.js"></script>

启动 webpack-dev-server,打开浏览器这个时候应该能在页面上看到 hello world,这说明工作正常。

4. 拆分框架代码

为了拆分框架代码,我们需要增加一个入口,在这个入口中要包含 react react-dom

module.exports = {
  entry: {
    main: 'index.js',
    vendor: ['react', 'react-dom']
  }
  //...
}

单单像上面这样配置,打包后会得到 main.js 和 vendor.js,但会发现在 main.js 中依然包含了 react 和 react-dom 的代码,这是因为指定了入口后,webpack 就会从入口文件开始讲整个依赖打包进来,index.js 中引用了 react 和 react-dom 自然会被打包进去。要想达到之前所说的那个效果,还需要借助一个插件 —— CommonsChunkPlugin

5. 使用 CommonsChunkPlugin

这个插件的功能是将多个打包结果中公共的部分抽取出来,作为一个单独的文件。它符合目前的场景,因为 main.js vendor.js 中存在一份公共的代码,那就是 vendor.js 中的内容。(这个说法并不准确,这里只是指 react 和 react-dom 都被打包进了这两个文件)

let webpack = require('webpack');

module.exports = {
  entry: {
    main: 'index.js',
    vendor: ['react', 'react-dom']
  },
  //...

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor' // 指定一个希望作为公共包的入口
    })
  ]
}

再进行打包,这个时候会发现 main.js 中的代码不在包含 react 的代码了。看似实现了我们的需求,但真实应用下并没有这么简单,在实际项目中 js 脚本通常都会给添加一个 MD5 的 hash 在后面,形如 app@709d9850745a4c8ba1d4.js 这样每次打包后,如果文件内容变了,后面的 hash 也会变动。就以上场景,会发现当我们修改了业务代码后,得到的 hash 是不同的,因此每次都会得到两个不同的打包结果。业务代码改变了,拆分出来的框架包也变了,这显然不符合初衷 —— 利用浏览器缓存。

这是因为 webpack 在打包的时候会产生一些运行时代码,比如 __webpack_require__ webpackJsonp 等等,这些函数是用来帮助 webpack 完成模块加载等功能的,业务代码的改变会导致业务代码打包后的 hash 值改变,而在 webpack 的运行时代码中实际上是保存了打包后的结果的文件名的,因为它在异步加载模块的时候需要用到。因此,下面需要做的是将 webpack 的运行时代码拆分出来。

修改 plugins 如下,将 name 修改为 names,并增加一个 init 的包名,执行打包,会发现 webpack 的运行时代码都被入该包内。

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    names: ['vendor', 'init']
  })
]

这样以来,修改了业务代码后,vendor 因为只引用了 react 和 react-dom 因此,业务代码的改变不会改变 vendor 这个包的内容,hash 也保持不变。但,也仅仅如此 如果你引用了其他模块,webpack 收集依赖的时候会给每个模块编一个号,引入其他模块会导致模块数改变,也就会导致编号改变,这个时候打包出来的 vendor 还是会改变。

那么到底该如何解决这个问题呢?在官方文档上没有找到解决方案。后面我会继续探索这一问题,找到解决方案后会及时更新到这里,如果你有解决方案,还请不吝赐教,谢谢。

希望本文所述对你有所帮助,webpack代码拆分内容就给大家介绍到这里了。希望大家继续关注我们的网站!

Javascript 相关文章推荐
固定网页背景图同时保持图片比例的思路代码
Aug 15 Javascript
Jquery 模拟用户点击超链接或者按钮的方法
Oct 25 Javascript
JS获取计算机mac地址以及IP的实现方法
Jan 08 Javascript
两种JS实现屏蔽鼠标右键的方法
Aug 20 Javascript
js实现的简洁网页滑动tab菜单效果代码
Aug 24 Javascript
JS获取CSS样式(style/getComputedStyle/currentStyle)
Jan 19 Javascript
jQuery之简单的表单验证实例
Jul 07 Javascript
浅谈javascript中关于日期和时间的基础知识
Jul 13 Javascript
Vue2递归组件实现树形菜单
Apr 10 Javascript
bootstrap基本配置_动力节点Java学院整理
Jul 14 Javascript
基于js实现抽红包并分配代码实例
Sep 19 Javascript
vscode调试node.js的实现方法
Mar 22 Javascript
webpack学习笔记之代码分割和按需加载的实例详解
Jul 20 #Javascript
node+vue实现用户注册和头像上传的实例代码
Jul 20 #Javascript
深入探究AngularJs之$scope对象(作用域)
Jul 20 #Javascript
通过命令行创建vue项目的方法
Jul 20 #Javascript
基于BootStrap实现简洁注册界面
Jul 20 #Javascript
AngularJS实现的JSONP跨域访问数据传输功能详解
Jul 20 #Javascript
深入探究node之Transform
Jul 20 #Javascript
You might like
PHILIPS AE3805收音机的分析打磨
2021/03/02 无线电
利用static实现表格的颜色隔行显示的代码
2007/09/02 PHP
PHP 冒泡排序算法的实现代码
2010/08/08 PHP
php用数组返回无限分类的列表数据的代码
2010/08/08 PHP
phpMyAdmin出现无法载入 mcrypt 扩展,请检查PHP配置的解决方法
2012/03/26 PHP
php实现的九九乘法口诀表简洁版
2014/07/28 PHP
PHP获取数组长度或某个值出现次数的方法
2015/02/11 PHP
jQuery中与toggleClass等价的程序段 以及未来学习的方向
2010/03/18 Javascript
JavaScript栏目列表隐藏/显示简单实现
2013/04/03 Javascript
jquery封装的对话框简单实现
2013/07/21 Javascript
Javascript中克隆一个数组的实现代码
2013/12/06 Javascript
JavaScript实现广告弹窗效果
2016/08/09 Javascript
jQuery+CSS3实现四种应用广泛的导航条制作实例详解
2016/09/17 Javascript
JS使用正则截取两个字符串之间的字符串实现方法详解
2017/01/06 Javascript
为JQuery EasyUI 表单组件增加焦点切换功能的方法
2017/04/13 jQuery
vue快捷键与基础指令详解
2017/06/01 Javascript
javascript 初学教程及五子棋小程序的简单实现
2017/07/04 Javascript
JavaScript链式调用实例浅析
2018/12/19 Javascript
详解基于Vue/React项目的移动端适配方案
2019/08/23 Javascript
JS原型和原型链原理与用法实例详解
2020/02/05 Javascript
python查看微信好友是否删除自己
2016/12/19 Python
浅谈function(函数)中的动态参数
2017/04/30 Python
python3.6实现学生信息管理系统
2019/02/21 Python
Python 通过正则表达式快速获取电影的下载地址
2020/08/17 Python
Python爬虫+tkinter界面实现历史天气查询的思路详解
2021/02/22 Python
详解CSS3中的box-sizing(content-box与border-box)
2019/04/19 HTML / CSS
AmazeUI 图标的示例代码
2020/08/13 HTML / CSS
表达自我的市场:Society6
2018/08/01 全球购物
美国波西米亚风格精品店:South Moon Under
2019/10/26 全球购物
愚人节活动策划方案
2014/03/11 职场文书
读书伴我成长演讲稿
2014/05/07 职场文书
2015庆祝七一建党节94周年活动总结
2015/03/20 职场文书
2015小学音乐教师个人工作总结
2015/07/21 职场文书
2016年幼儿园教师政治学习心得体会
2016/01/23 职场文书
Python入门之使用pandas分析excel数据
2021/05/12 Python
《极主夫道》真人电影正式预告 定档6月3日上映
2022/04/05 日漫