webpack4.6.0打包包含oclazyload组件(懒加载)的angular1.x工程

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

当前能百度到的webpack打包包含oclazyload组件的angular1.x工程的文章大多用的是webpack1.x版本,但实际上webpack.config里很多1.x版本的配置项在最新的4.6.0里已经废弃了(或名称、位置发生了调整),这意味着我们直接拿用webpack1.x版本可以成功打包的源码工程过来用最新版本的webpack打包是会报错的,出错信息可以看这里了解一下:webpack打包时提示Invalid configuration object错误

下面介绍一下在webpack4.6.0版本下如何打包包含oclazyload组件的angular1.x工程。

首先给出笔者的一些环境信息:
node v8.10.0
npm 5.6.0
webpack 4.6.0
angular 1.4.6
angular-ui-router 0.2.18
oclazyload 1.0.1

接下来来简单分析一下如果要用webpack来打包工程要做哪些调整:

webpack的基础功能就是把工程里多个有依赖关系的javascript文件打包压缩成一个文件,但使用了oclazyload组件即意味着工程里的javascript文件并不是同时加载的(不是同时加载的文件肯定不能打包压缩在同一个文件中),这意味着webpack也要支持懒加载,好在webpack早就支持了,这个懒加载功能可以通过require.ensure来完成,接口文档可以看这里:require.ensure接口

ps:有个坏消息,笔者写这篇博客的时候webpack的接口文档貌似已经更新到4.8.1了,请各位同学注意(没错,接口可能又变了)。

首先看一下在不使用webpack打包时懒加载是怎么玩的:

$stateProvider.state("default",{
     url:"/default",
     //本地直接在浏览器中运行index.html则不能使用templateUrl,否则会有跨域问题
     // templateUrl:"html/default.html",
     template: "<div><span>{{default}}</span></div>",
     controller: "defaultCtrl",
     resolve:{
        'default': function($ocLazyLoad) {
            let mod = require('./controller/defaultCtrl');
            return $ocLazyLoad.load({
                name: mod
            });
        }
    }
});

直接把需要懒加载的文件load进来就可以了(有兴趣的同学戳这里看基本用法:angularjs1.4懒加载的基本用法)。

然后按正常人的脑回路来说一般会尝试这么写:

$stateProvider.state("default",{
     url:"/default",
     //本地直接在浏览器中运行index.html则不能使用templateUrl,否则会有跨域问题
     // templateUrl:"html/default.html",
     template: "<div><span>{{default}}</span></div>",
     controller: "defaultCtrl",
     resolve:{
        'default': ["$ocLazyLoad", function($ocLazyLoad) {
            require.ensure(['./controller/defaultCtrl'], function() {
                let mod = require('./controller/defaultCtrl');
                return $ocLazyLoad.load({
                    name: mod
                });
            }, 'defaultCtrl');
        }]
    }
});

不过按一般的套路来说这么容易就搞定的话笔者就没什么好写的了,实际上按上面的写法运行时会提示路由所需的controller不存在,并抛出异常终止程序运行:
controller不存在

推测是require.ensure内部有异步行为,resolve对应的回调函数在执行完成时require.ensure的回调函数其实还没有执行,下面尝试用angular的promise把require.ensure包起来:

$stateProvider.state("default",{
    url:"/default",
    //本地直接在浏览器中运行index.html则不能使用templateUrl,否则会有跨域问题
    // templateUrl:"html/default.html",
    template: "<div><span>{{default}}</span></div>",
    controller: "defaultCtrl",
    resolve:{
        'default': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
            let deferred = $q.defer();
            require.ensure(['./controller/defaultCtrl'], function() {
                let mod = require('./controller/defaultCtrl');
                $ocLazyLoad.load({
                    name: mod
                });
                deferred.resolve();
            }, 'defaultCtrl');
            return deferred.promise;
        }]
    }
});

重新用webpack打包后看一下运行结果:
默认路由

再手动切换一下路由看看其他路由所需的controller是否可以正常加载:
其他路由

嗯,简单验证了一下基本没什么大问题了。

下面贴一下路由模块的完整代码:

"use strict";
let configModule = angular.module("config", []);
let config = ["$stateProvider", "$urlRouterProvider", "$controllerProvider",
    function($stateProvider, $urlRouterProvider){

        $urlRouterProvider.otherwise("/default");

        $stateProvider.state("default",{
            url:"/default",
            //本地直接在浏览器中运行index.html则不能使用templateUrl,否则会有跨域问题
            // templateUrl:"html/default.html",
            template: "<div><span>{{default}}</span></div>",
            controller: "defaultCtrl",
            resolve:{
                'default': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
                    let deferred = $q.defer();
                    require.ensure(['./controller/defaultCtrl'], function() {
                        let mod = require('./controller/defaultCtrl');
                        $ocLazyLoad.load({
                            name: mod
                        });
                        deferred.resolve();
                    }, 'defaultCtrl');
                    return deferred.promise;
                }]
                // 'default': ["$ocLazyLoad", function($ocLazyLoad) {
                //     require.ensure(['./controller/defaultCtrl'], function() {
                //         let mod = require('./controller/defaultCtrl');
                //         return $ocLazyLoad.load({
                //             name: mod
                //         });
                //     }, 'defaultCtrl');
                // }]
            }
        });

        $stateProvider.state("routerA",{
            url:"/routerA",
            // templateUrl:"html/routerA.html",
            template: "<div>{{say}}</div>`",
            controller: "routerACtrl",
            resolve:{
                'routerA': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
                    let deferred = $q.defer();
                    require.ensure(['./controller/routerACtrl'], function() {
                        let mod = require('./controller/routerACtrl');
                        $ocLazyLoad.load({
                            name: mod
                        });
                        deferred.resolve();
                    }, 'routerACtrl');
                    return deferred.promise;
                }]
            }
        });
    }];
configModule.config(config);
module.exports = configModule.name;

可以看到有2个路由模块,那么使用webpack打包后应该会生成三个文件:
打包生成文件

额外生成的0.bundle.js和1.bundle.js对应的就是懒加载的2个路由对应的controller文件。

下面再贴一下默认路由的controller代码:

"use strict";
let defaultCtrl = ["$rootScope", "$scope", function ($rootScope, $scope) {
    console.log("enter default controller");
    $scope.default = "default page."
}];

module.exports = angular.module("config", [])
    .controller("defaultCtrl", defaultCtrl).name;

另外要注意的一点是export default和module.exports导出的模块require时的写法是不同的(export default如果用import就没问题,但是要想混用require就必须注意写法),详情可以参考在路由的resolve中使用lazyload加载控制器无效这篇文章。

上面的例子已经简单说明该如何打包包含oclazyload组件(懒加载)的angular1.x工程,但深入使用的话还有坑要踩。之前的路由模块代码只给出了一层路由的情况,一旦配置成多层路由就会有下面这样的问题:

包含多层路由的路由模块代码:

"use strict";
let configModule = angular.module("config", []);
let config = ["$stateProvider", "$urlRouterProvider", "$controllerProvider",
    function($stateProvider, $urlRouterProvider){

        // $urlRouterProvider.otherwise("/default/subPage");
        $urlRouterProvider.otherwise("/default");

        $stateProvider.state("default",{
            url:"/default",
            //本地直接在浏览器中运行index.html则不能使用templateUrl,否则会有跨域问题
            // templateUrl:"html/default.html",
            template: "<div><span>{{default}}</span><div ui-view></div></div>",
            controller: "defaultCtrl",
            resolve:{
                'default': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
                    let deferred = $q.defer();
                    require.ensure(['./controller/defaultCtrl'], function() {
                        let mod = require('./controller/defaultCtrl');
                        $ocLazyLoad.load({
                            name: mod
                        });
                        deferred.resolve();
                    }, 'defaultCtrl');
                    return deferred.promise;
                }]
                // 'default': ["$ocLazyLoad", function($ocLazyLoad) {
                //     require.ensure(['./controller/defaultCtrl'], function() {
                //         let mod = require('./controller/defaultCtrl');
                //         return $ocLazyLoad.load({
                //             name: mod
                //         });
                //     }, 'defaultCtrl');
                // }]
            }
        });

        $stateProvider.state("default.subPage",{
            url:"/subPage",
            // templateUrl:"html/routerA.html",
            template: "<div>{{subPageData}}</div>`",
            controller: "subPageCtrl",
            resolve:{
                'subPageCtrl': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
                    let deferred = $q.defer();
                    require.ensure(['./controller/subPageCtrl'], function() {
                        let mod = require('./controller/subPageCtrl');
                        $ocLazyLoad.load({
                            name: mod
                        });
                        deferred.resolve();
                    }, 'subPageCtrl');
                    return deferred.promise;
                }]
            }
        });

        $stateProvider.state("routerA",{
            url:"/routerA",
            // templateUrl:"html/routerA.html",
            template: "<div>{{say}}</div>`",
            controller: "routerACtrl",
            resolve:{
                'routerA': ["$q", "$ocLazyLoad", function($q, $ocLazyLoad) {
                    let deferred = $q.defer();
                    require.ensure(['./controller/routerACtrl'], function() {
                        let mod = require('./controller/routerACtrl');
                        $ocLazyLoad.load({
                            name: mod
                        });
                        deferred.resolve();
                    }, 'routerACtrl');
                    return deferred.promise;
                }]
            }
        });
    }];
configModule.config(config);
module.exports = configModule.name;

subPage路由的controller代码:

"use strict";
let subPageCtrl = ["$rootScope", "$scope", function ($rootScope, $scope) {
    console.log("enter subPage controller");
    $scope.subPageData = "subPage page."
}];

module.exports = angular.module("config", [])
    .controller("subPageCtrl", subPageCtrl).name;

执行webpack后访问/default/subPage路由:

这里写图片描述

笔者的开发代码(不是github里给出的代码)里这似乎是个偶现问题,不是每次都会报错,且提示未定义的controller有可能是父路由也有可能是子路由。

导致这个问题发生的根因是路由模块路由controller的加载方式及路由controller的定义方式。

首先来看一下路由controller的定义方式:

default路由controller:

"use strict";
let defaultCtrl = ["$rootScope", "$scope", function ($rootScope, $scope) {
    console.log("enter default controller");
    $scope.default = "default page."
}];

module.exports = angular.module("config", [])
    .controller("defaultCtrl", defaultCtrl).name;

subPage路由controller:

"use strict";
let subPageCtrl = ["$rootScope", "$scope", function ($rootScope, $scope) {
    console.log("enter subPage controller");
    $scope.subPageData = "subPage page."
}];

module.exports = angular.module("config", [])
    .controller("subPageCtrl", subPageCtrl).name;

父子路由的controller都定义在config这个module上,再看一下路由模块路由controller的加载方式(这里只截取了路由模块default路由的片段):

let deferred = $q.defer();
require.ensure(['./controller/defaultCtrl'], function() {
    let mod = require('./controller/defaultCtrl');
    $ocLazyLoad.load({
        name: mod
    });
    deferred.resolve();
}, 'defaultCtrl');

可以看到ocLazyLoad.load的是export出来的module名称,如果打印出来的话可以看到父子路由export出来的module名称都是config,而ocLazyLoad.load以name的方式来完成module的懒加载属于覆盖式加载,即意味着我们最终load进来的config这个module要么少了defaultCtrl,要么少了subPageCtrl(因为defaultCtrl这个文件export出来的config module没有subPageCtrl,subPageCtrl这个文件export出来的config module没有defaultCtrl),这样报错就不难理解了。

问题改起来也很简单,定义的controller不挂在config这个module上就没问题了,以default路由controller为例(当然所有的controller都要调整):

"use strict";
let defaultCtrl = ["$rootScope", "$scope", function ($rootScope, $scope) {
    console.log("enter default controller");
    $scope.default = "default page."
}];

module.exports = angular.module("defaultCtrl", [])
    .controller("defaultCtrl", defaultCtrl).name;

把module的名称调整为defaultCtrl(其实随便什么名字都行,只要确保module名称不重复即可),然后执行webpack后访问/default/subPage路由(再说一遍,其他controller挂的module名称都要调整):

这里写图片描述

这样即使刷新再多次也不会报错了。也许有同学有疑问:为什么controller挂的这些module都没有被config或app这两个module声明依赖,但实际上确可以使用这些module上挂的controller?

答案很明显,ocLazyLoad.load以name的方式来完成module的懒加载默认是作为执行ocLazyLoad.load所在module(在这里是config)的依赖项load进来,所以直接使用完全没问题。同理,在这些controller也可以随便使用在config或app上定义的service、factory甚至是directive。

完整工程代码:https://github.com/liqing-taoyanzoukaila/angular-lazyload-webpack

猜你喜欢

转载自blog.csdn.net/lqlqlq007/article/details/80280411