vue按需加载组件-webpack require.ensure(转)

vue按需加载组件-webpack require.ensure

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27626333/article/details/76228578

使用 vue-cli构建的项目,在 默认情况下 ,执行 npm run build 会将所有的js代码打包为一个整体,

打包位置是 dist/static/js/app.[contenthash].js 
类似下面的路由代码

router/index.js 路由相关信息,该路由文件引入了多个 .vue组件

import Hello from '@/components/Hello'
import Province from '@/components/Province'
import Segment from '@/components/Segment'
import User from '@/components/User'
import Loading from '@/components/Loading'
  • 1
  • 2
  • 3
  • 4
  • 5

  执行 npm run build 会打包为一个整体 app.[contenthash].js ,这个文件是非常大,可能几兆或者几十兆,加载会很慢

这里写图片描述

  所以我们需要分模块打包,把我们想要组合在一起的组件打包到一个 chunk块中去,分模块打包需要下面这样使用 webpack的 require.ensure,并且在最后加入一个 chunk名,相同 chunk名字的模块将会打包到一起。

webpack中利用require.ensure()实现按需加载

1、require.ensure()

  webpack 在编译时,会静态地解析代码中的 require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp 来按需加载。

语法如下:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)
  • 1
  • 依赖 dependencies

  这是一个字符串数组,通过这个参数,在所有的回调函数的代码被执行前,我们可以将所有需要用到的模块进行声明。

  • 回调 callback

  当所有的依赖都加载完成后,webpack会执行这个回调函数。require 对象的一个实现会作为一个参数传递给这个回调函数。因此,我们可以进一步 require() 依赖和其它模块提供下一步的执行。

  • chunk名称 chunkName

  chunkName 是提供给这个特定的 require.ensure() 的 chunk 的名称。通过提供 require.ensure() 不同执行点相同的名称,我们可以保证所有的依赖都会一起放进相同的 文件束(bundle)。

让我们来看以下的项目

\\ file structure
    |
    js --|
    |    |-- entry.js
    |    |-- a.js
    |    |-- b.js
    webpack.config.js
    |
    dist
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
\\ entry.js

require('a');
require.ensure([], function(require){
    require('b');
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
\\ a.js
console.log('***** I AM a *****');
  • 1
  • 2
\\ b.js
console.log('***** I AM b *****');
  • 1
  • 2
\\ webpack.config.js
var path = require('path');

module.exports = function(env) {
    return {
        entry: './js/entry.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  通过执行这个项目的 webpack 构建,我们发现 webpack 创建了2个新的文件束, bundle.js 和 0.bundle.js。

entry.js 和 a.js 被打包进 bundle.js.

b.js 被打包进 0.bundle.js.

2、require.ensure() 的坑点

(1)、空数组作为参数

require.ensure([], function(require){
    require('./a.js');
});
  • 1
  • 2
  • 3

以上代码保证了拆分点被创建,而且 a.js 被 webpack 分开打包。

(2)、依赖作为参数

require.ensure(['./a.js'], function(require) {
    require('./b.js');
});
  • 1
  • 2
  • 3

  上面代码, a.js 和 b.js 都被打包到一起,而且从主文件束中拆分出来。但只有 b.js 的内容被执行。a.js 的内容仅仅是可被使用,但并没有被输出。

  想去执行 a.js,我们需要异步地引用它,如 require(‘./a.js’),让它的 JavaScritp 被执行。

(3)、单独打包成自己写的名字配置 
  需要配置chunkFilename,和publicPath。publicPath是按需加载单独打包出来的chunk是以publicPath会基准来存放的,chunkFilename:[name].js这样才会最终生成正确的路径和名字

module.exports={
    entry:'./js/entry.js',
    output:{
        path:path.resolve(__dirname,"./dist"),
        filename:'js/a.bundle.js',
        publicPath:"./",
        chunkFilename:'js/[name].js'
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所以router/index.js 修改为懒加载组件:

const Province = r => require.ensure([], () => r(require('@/components/Province.vue')), 'chunkname1')
const Segment = r => require.ensure([], () => r(require('@/components/Segment.vue')), 'chunkname2')
const Loading = r => require.ensure([], () => r(require('@/components/Loading.vue')), 'chunkname3')
const User = r => require.ensure([], () => r(require('@/components/User.vue')), 'chunkname3')
  • 1
  • 2
  • 3
  • 4

  根据 chunkame的不同, 上面的四个组件, 将会被分成3个块打包,最终打包之后与组件相关的js文件会分为3个 (除了app.js,manifest.js, vendor.js)

  分模块打包之后在 dist目录下是这样的, 这样就把一个大的 js文件分为一个个小的js文件了,按需去下载,其他的使用方法和import的效果一样

这里写图片描述

参考:http://blog.csdn.net/yangbingbinga/article/details/61417689 
   http://www.css88.com/doc/webpack2/guides/code-splitting-require/

 
 
 

在vue中使用import()来代替require.ensure()实现代码打包分离

 

一、require.ensure() 方法来实现代码打包分离

require.ensure() 是 webpack 特有的,已经被 import() 取代。

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

给定 dependencies 参数,将其对应的文件拆分到一个单独的 bundle 中,此 bundle 会被异步加载。 
当使用 CommonJS 模块语法时,这是动态加载依赖的唯一方法。 
意味着,可以在模块执行时才运行代码,只有在满足某些条件时才加载依赖项。 
这个特性依赖于内置的 Promise。如果想在低版本浏览器使用 require.ensure, 
记得使用像 es6-promise 或者 promise-polyfill 这样 polyfill 库, 来预先填充(shim) Promise 环境。
var a = require('normal-dep');
if ( module.hot ) {
  require.ensure(['b'], function(require) {
    var c = require('c');
    // Do something special...
  });
}

按照上面指定的顺序,webpack 支持以下参数:

  • dependencies:字符串构成的数组,声明 callback 回调函数中所需的所有模块。
  • callback:只要加载好全部依赖,webpack 就会执行此函数。require 函数的实现,作为参数传入此函数。当程序运行需要依赖时,可以使用 require() 来加载依赖。函数体可以使用此参数,来进一步执行 require() 模块。
  • errorCallback:当 webpack 加载依赖失败时,会执行此函数。
  • chunkName:由 require.ensure() 创建出的 chunk 的名字。通过将同一个 chunkName 传递给不同的 require.ensure() 调用,我们可以将它们的代码合并到一个单独的 chunk 中,从而只产生一个浏览器必须加载的 bundle。
虽然我们将 require 的实现,作为参数传递给回调函数,然而如果使用随意的名字,
例如 require.ensure([], function(request) { request('someModule'); }) 
则无法被 webpack 静态解析器处理,所以还是请使用 require,例如 require.ensure([], function(require) { require('someModule'); })。

二、在vue中使用import()来代替require.ensure()实现代码打包分离 

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。 
只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。 

例:require.ensure()实现
const notFound = r => require.ensure([], () => r(require('@views/common/404')), 'index')
const login = r => require.ensure([], () => r(require('@views/common/login')), 'index')
const register = r => require.ensure([], () => r(require('@views/common/register')), 'index')
const main = r => require.ensure([], () => r(require('@views/main')), 'index')
const myWorks = r => require.ensure([], () => r(require('@views/my/index')), 'my')
const myAccountSetting = r => require.ensure([], () => r(require('@views/my/account-setting')), 'my')
const makeIndex = r => require.ensure([], () => r(require('@views/make/index')), 'make')
例:import()实现
const notFound = () => import(/* webpackChunkName: "index" */ '@views/common/404')
const login = () => import(/* webpackChunkName: "index" */ '@views/common/login')
const register = () => import(/* webpackChunkName: "index" */ '@views/common/register')
const main = () => import(/* webpackChunkName: "index" */ '@views/main')
const myWorks = () => import(/* webpackChunkName: "my" */ '@views/my/index')
const myAccountSetting = () => import(/* webpackChunkName: "my" */ '@views/my/account-setting')
const makeIndex = () => import(/* webpackChunkName: "make" */ '@views/make/index')
 
分类: Vue,Webpack
标签: Webpackvue
 
 
 

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

96 
吴高亮 
2017.09.29 11:49* 字数 1558 阅读 1830评论 0

文章转载地址[转载github地址[https://github.com/wugaoliang1116/webpa_ensure]];

webpack异步加载的原理

webpack ensure相信大家都听过。有人称它为异步加载,也有人说做代码切割,那这
个家伙到底是用来干嘛的?其实说白了,它就是把js模块给独立导出一个.js文件的,然后使用这个
模块的时候,webpack会构造script dom元素,由浏览器发起异步请求这个js文件。

场景分析:

比如应用的首页里面有个按钮,点击后可以打开某个地图。打开地图的话就要利用百度地图的js,于是
我们不得不在首页中把百度地图的js一起打包进去首页,一个百度地图的js文件是非常大的,假设为
1m,于是就造成了我们首页打包的js非常大,用户打开首页的时间就比较长了。

有没有什么好的解决方法呢?

解决1

既然打包成同一个js非常大的话,那么我们完全可以把百度地图js分类出去,利用浏览器的并发请求
js文件处理,这样的话,会比加载一个js文件时间小得多。嗯,这也是个不错的方案。为baidumap.js
配置一个新的入口就行了,这样就能打包成两个js文件,都插入html即可(如果baidumap.js被多个
入口文件引用的话,也可以不用将其设置为入口文件,而且直接利用CommonsChunkPlugin,导出到一个
公共模块即可)可以参考我之前文章
webpack模块打包

那还有没有更好的解决方案呢?

解决2

当然还是有的!我们细想,百度地图是用户点击了才弹出来的,也就是说,这个功能是可选的。那么解决
方案就来了,能不能在用户点击的时候,我在去下载百度地图的js.当然可以。那如何实现用户点击的时候
再去下载百度地图的js呢?于是,我们可以写一个按钮的监听器

mapBtn.click(function() {
  //获取 文档head对象
  var head = document.getElementsByTagName('head')[0];
  //构建 <script>
  var script = document.createElement('script');
  //设置src属性
  script.async = true;
  script.src = "http://map.baidu.com/.js"
  //加入到head对象中
  head.appendChild(script);
})

上面的几行代码对大家来说都不难。可以在点击的时候,才加载百度地图,等百度地图加载完成后,在
利用百度地图的对象去执行我们的操作。ok,讲到这里webpack.ensure的原理也就讲了一大半了。
它就是 把一些js模块给独立出一个个js文件,然后需要用到的时候,在创建一个script对象,加入
到document.head对象中即可,浏览器会自动帮我们发起请求,去请求这个js文件,在写个回调,去
定义得到这个js文件后,需要做什么业务逻辑操作。

ok,那么我们就利用webpack的api去帮我们完成这样一件事情。点击后才进行异步加载百度地图js,上面
的click加载js时我们自己写的,webpack可以轻松帮我们搞定这样的事情,而不用我们手写

mapBtn.click(function() {
  require.ensure([], function() {
    var baidumap = require('./baidumap.js') //baidumap.js放在我们当前目录下
  })
})

搞定!当然还是分析一下。require.ensure这个函数是一个代码分离的分割线,表示 回调里面的require
是我们想要进行分割出去的,即require('./baidumap.js'),把baidumap.js分割出去,形成一个
webpack打包的单独js文件。当然ensure里面也是可以写一些同步的require的,比如


var sync = require('syncdemo.js')   //下面ensure里面也用到

mapBtn.click(function() {
  require.ensure([], function() {
    var baidumap = require('./baidumap.js') //baidumap.js放在我们当前目录下
    var sync = require('syncdemo.js')  //这个不会独立出去,因为它已经加载到模块缓存中了
  })
})

也就是说,ensure会把没有使用过的require资源进行独立分成成一个js文件. require.ensure的
第一个参数是什么意思呢?[], 其实就是 当前这个 require.ensure所依赖的其他 异步加载的模块。你想啊?如果A 和 B都是异步加载的,B中需要A,那么B下载之前,是不是先要下载A啊?,所以ensure的第一个参数[]
也是请求下载的模块,如果想加载A require.ensure(['A.js'],function) 即可

说完了上面的原理。下面就实践一下

entry.js 依赖三个 js。

  • Abtn-work.js 是封装了 abtn按钮点击后,才执行的业务逻辑
  • Bbtn-work.js 是封装了 bbtn按钮点击后,才执行的业务逻辑
  • util.js 是封装了 entry.js需要利用的工具箱

针对上面的需求,优化方案

假设 Abtn-work.js Bbtn-work.js util.js都是非常大的文件
因为 Abtn-work.js Bbtn-work.js 都不是entry.js必须有的,即可能发生的操作,那么我们把
他们利用异步加载,当发生的时候再去加载就行了

util.js是entry.js立即马上依赖的工具箱。但是它又非常的大,所以将其配置打包成一个公共模块,
利用浏览器的并发加载,加快下载速度。ok,构思完成,开始实现

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>
  <body>
    <div id="aBtn">Abtn</div>
    <div id="bBtn">Bbtn</div>
  </body>
</html>

定义了两个buttom

然后看看 entry.js


var util_sync = require('./util-sync.js')

alert(util_sync.data)

document.getElementById("aBtn").onclick = function() {

  require.ensure([], function() {
    var awork = require('./workA-async.js')
    alert(awork.data)
    //异步里面再导入同步模块--实际是使用同步中的模块
    var util1 = require('./util-sync.js')
  })
}

document.getElementById("bBtn").onclick = function() {

  require.ensure([], function() {
    var bwork = require('./workB-async.js')
    alert(bwork.data)
  })
}

可以看到,workA-async.js, workB-async.js 都是点击后才ensure进来的。什么时候加载完成呢?
就是 require.ensure() 第二个函数参数,即回调函数,它表示当下载js完成后,发生的因为逻辑

webpack打包后,形成

 
image.png

其实, 1.1.... 2.2...就是我们ensure导出来的js文件

我们看看代码是如何加载的执行的,点击打包插入js后的html

 
image.png

可以看到,并没有加载 ensure导出来的 1.1...js 2.2....js

点击 abtn,

 
image.png

发现浏览器下载并加载了 1.1....js

点击 bbtn

 
image.png


发现浏览器下载并加载了 2.2....js

ok 全部完成

todo: 异步公用模块的代码抽离

喜欢的话 欢迎star

:源码地址:https://github.com/yongningfu/webpa_ensure

posted @ 2018-12-03 16:30  yasepix  阅读(686)  评论(0编辑  收藏  举报