懒加载JS脚本的方式

  • CommonJS:require.ensure;引入但需要手动执行相关js代码。
  • ES6:动态import(目前还没有原生支持,需要babel转换)--推荐使用。引入并且自动执行相关js代码。

如何动态import?

// 安装babel插件
yarn add @babel/plugin-syntax-dynamic-import -D
1
2
{
    "plugins": [
        "@babel/plugin-syntax-dynamic-import"
    ]
}
1
2
3
4
5

如何动态引入?

通过jsonp请求从服务器获取对应的文件。动态的创建script标签,将对应的脚本插入到页面中。

动态import

// 查看当前代码目录结构
tree -L 2 -I "node_modules"
1
2

代码目录解构如下:

├── dist
│   ├── index.html
│   ├── main.bundle.js
│   ├── pageA.chunk.js
│   ├── pageB.chunk.js
│   └── vendors~jquery.chunk.js
├── package.json
├── src
│   ├── index.js // 入口文件
│   ├── module.js
│   ├── pageA.js
│   └── pageB.js
├── webpack.config.js
└── yarn.lock
1
2
3
4
5
6
7
8
9
10
11
12
13
14

module.js:

export default 'module';
1

pageA.js:

import './module';
console.log('this is pageA');
export default 'pageA';
1
2
3

pageB.js:

import './module';
console.log('this is pageB');
export default 'pageB';
1
2
3

其中,pageA.js和pageB.js共同引用module.js。注意:pageA.js和pageB.js两个文件中都执行了console.log语句。之后将会看到import()和require()不同的表现形式:是否会自动执行js的代码?

webpack配置如下:

const path = require('path');

module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        chunkFilename: '[name].chunk.js'
    }
};
1
2
3
4
5
6
7
8
9
10
11

index.js:

// import可以通过注释的方法来指定打包后的chunk的名称
import(/* webpackChunkName: 'pageA'*/'./pageA').then(pageA => {
    console.log(pageA);
});

import(/* webpackChunkName: 'pageB'*/'./pageB').then(pageB => {
    console.log(pageB);
});

document.querySelector('#btn').onclick = () => {
    import(/* webpackChunkName: 'jquery'*/'jquery').then($ => {
        $.default('body').css('background-color', 'red');
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用过vue-router的朋友应该知道,其路由懒加载的配置也是通过import()语法来书写的。

在命令行中运行yarn run build打包,结果如下:

在dist目录下新建index.html文件,通过script标签引入我们打包结果,需要注意的是:因为是单页应用,所以只要引用入口文件即可(即是上图中的`main.bundle.js`)。

index.html主要内如如下:

<body>
    <button id="btn">按需加载</button>
    <script src="./main.bundle.js"></script>
</body>
1
2
3
4

打开浏览器控制台,刷新界面,结果如下图所示:

上图结果说明import()会自动运行pageA.js和pageB.js中的代码。

点击按需加载的按钮也会动态加载对应的chunk:页面背景变成红色,说明动态加载成功。

demo

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import logo from './images/logo.png';
import './search.less';

class Search extends Component {
    constructor() {
        super();
        this.state = {
            Text: null
        };
        this.loadComponent = this.loadComponent.bind(this);
    }

    loadComponent() {
        import('./Text.js').then(Text => {
            this.setState({
                Text: Text.default
            });
        });
    }
    render() {
        const {Text} = this.state;
        console.log(Text);
        return <div className="search">
            hello react666
            <img
                src={logo}
                onClick={this.loadComponent}
            />
            {
                Text ? <Text /> : null
            }
        </div>;
    }
}

ReactDOM.render(
    <Search />,
    document.querySelector('#root')
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

打包结果如下:1_b9fee4d1.js即动态import的脚本。

                Asset       Size  Chunks                         Chunk Names
        1_b9fee4d1.js  647 bytes       1  [emitted] [immutable]
img/logo_bd62f047.png   8.54 KiB          [emitted]
           index.html  293 bytes          [emitted]
    index_af0e9452.js   3.99 KiB       2  [emitted] [immutable]  index
          search.html   1.44 KiB          [emitted]
  search_1d9de098.css  127 bytes       0  [emitted] [immutable]  search
   search_5f7d4376.js   13.2 KiB       0  [emitted] [immutable]  search
1
2
3
4
5
6
7
8

通过上下两图对比,动态import的脚本是通过创建动态创建script标签的形式引入的,即jsonp的方式引入。

require.ensure方式

require.ensure()是 webpack特有的,已经被import()取代。require.ensure()不会自动执行js代码,请注意注释。

require.ensure(
  dependencies: String[],
  callback: function(require),
  errorCallback: function(error),
  chunkName: String
)
1
2
3
4
5
6
require.include('./module.js'); // 将pageA和pageB共用的module.js打包在此page中
require.ensure(
    ['./pageA.js', './pageB.js'], // js文件或者模块名称
    function() {
      var pageA = require('./pageA'); // 引入后需要手动执行,控制台才会打印
      var pageB = require('./pageB');
    },
    'subPage' // chunkName
  );

  require.ensure(
    ['jquery'],
    function() {
      var $ = require('jquery');
      $('body').css('background-color', 'red');
    },
    'jquery'
  );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18