Webpack与AngularJS整合之代码逻辑与架构设计

前言

公司项目的前端使用Ionic开发,但是代码的组织方式略显臃肿,代码的书写规范性不佳。一方面造成了严重的性能瓶颈,另一方面也无法应对未来频繁的需求变更,所以准备结合构建工具对前端做设计重构。考察了各个构建工具包括Glup、Grunt、Webpack之后还是决定使用Webpack(最新的主流一般都不会有错的)。查阅了不少资料,大部分是关注的技术细节,对于如何进行代码的组织设计较少,所以本文更多的关注的可能是代码的组织方式,探索一个比较好的组织方案

预备知识

  • AMD/CMD
  • npm
  • AngularJS
  • Webpack

Webpack

Webpack通过一个入口文件(当然可以多入口,不是重点),将所有依赖文件打包到一个文件中,从而减少SAP应用初次加载时http请求到个数。并且可以通过各种loader去处理优化不同的文件,如js、css、svg等。一般一个通行的做法是将webpack构建时的配置信息写入到一个名为 “webpack.config.js”的文件中。这部分的基础知识以及demo一般稍作学习便可掌握。官网文档本身很不错。

所以目录可以这样组织:


  • app:存放源代码
  • build: 存放webpack构建完成的代码
  • node_modules: npm管理的包库
  • webpack.config.js: webpack构建配置
    这个是webpack视角下大的框架

组织结构

1. Module

关于module,angular的官方Developer Guide是这样写的:

You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.

所以在angular中module是一个层次相对较高的聚合,把相关的controller、service等封装到一个module,以实现一组特定的相对完备的功能。
对一个中大型的项目理想情况下是拆分为几个相对比较独立的module。

以passport模块为例,包含登录注册等功能,组织形式如下:


  • index.js:将该模块内各个部分联系在一起
angular = require('angular');
uirouter = require('uirouter');

service = require('../services');

//define the app.passport module
module.exports = angular.module('app.passport', [uirouter, service.AuthService, service.Request])
    .config(require('./passport.routes'))
    .controller('SigninController', require('./signin.controller'))
    .controller('SignupController', require('./signup.controller'))
    .service('passportService', require('./passport.service'))
    .name;
  • routes.js:定义该模块的路由
module.exports = ['$stateProvider',
function($stateProvider) {
    $stateProvider
        .state('signup', {
            url: '/signup',
            name: 'signup',
            template: require('./signup.html'),
            controller: 'SignupController'
        })
        .state('signin', {
            url: '/signin',
            name: 'signin',
            template: require('./signin.html'),
            controller: 'SigninController'
        });
}];
  • signin.controller.js: 登录控制器,这里export controller的定义函数,在index中完成最终定义
module.exports = ['$scope','authService','request','$state',
function($scope, authService,request,$state) {

    $scope.user = {
        phone : "",
        password:""
    };

    //执行用户登录操作
    $scope.signin = function() {
        request.post('signin', $scope.user)
            .then(
                function(data) {
                    authService.setToken(data.token, data.expire_in * 1000);
                    $state.go('home');
                },
                function(error) {
                    console.log(error);
                }
            );
    };
}];
  • signin.html 登录页面模版
<ion-view>
    <ion-content class="padding">
        <div style="margin-top: 120px;">
            <form name="myForm" novalidate="">
                <div class="list list-inset" style="background-color:transparent;">
                    <label class="item item-input">
                        <input name="phone" ng-maxlength="11" ng-minlength="11" ng-model="user.phone" ng-pattern="/^(((1[0-9]{2})|159|153)+\d{8})$/" placeholder="输入手机号" required="" type="number"/>
                    </label>
                    <label class="item item-input">
                        <input name="password" ng-maxlength="32" ng-minlength="6" ng-model="user.password" placeholder="6-32位字母数字组合" required="" type="password">
                        </input>
                    </label>
                    <input class="button button-block button-small button-positive" ng-click="signin()" ng-disabled="myForm.$invalid" type="submit" value="登录" />
                    <div class="row">
                        <div class="col button button-clear button-positive" ui-sref="register" style="color:#7b7b7b;">
                            用户注册
                        </div>
                        <button class="col button button-clear button-positive" style="color:#7b7b7b;">
                            重置密码
                        </button>
                    </div>
                </div>
            </form>
        </div>
    </ion-content>
</ion-view>

主要思想就是对于模块内的无论是controller、config也好,将它们的定义放到单独文件中,通过index.js文件,将它们组装定义成目标module

** * 这边的定义都采用数组的方式进行,是为了以后进行代码混淆,函数的参数名即使被替换,依然不影响正常的inject * **

通过这种模式我们就完成了一个模块的定义。

2.项目

完成了一个模块的定义,那么整个项目的integration也就比较明了了。大致结构如下图:



首先是各个模块的定义,然后将跨模块的复用代码以service的形式提取出来,作为通用模块。这里我是放置在services目录下。整合时,定义app module作为项目的主模块,然后将其他module作为依赖的方式注入到app module中,从而完成整合。

  • app.module.js
angular = require('angular');
uirouter = require('uirouter');
ionic    = require('ionic');

ngCache = require('angular-cache');
config = require('./app.config');
home = require('./home');
passport = require('./passport');

angular.module('app', [ionic,uirouter, ngCache,home,passport])
    .config(config.routing)
    .config(config.providerConfig)
    .constant('ENV', require('./app.env'));
  • app.ENV.js 环境配置
module.exports = {
    version: 1.0,
    api: 'http://xxx.com/v1/',
    appPath: 'http://localhost:8080/'
};
  • app.config.js 配置信息
module.exports.providerConfig = ['$httpProvider', 'CacheFactoryProvider',
    function($httpProvider, CacheFactoryProvider) {

        var param = function(obj) {
            var query = '',
                name, value, fullSubName, subName, subValue, innerObj, i;
            for (name in obj) {
                value = obj[name];
                if (value instanceof Array) {
                    for (i = 0; i < value.length; ++i) {
                        subValue = value[i];
                        fullSubName = name + '[' + i + ']';
                        innerObj = {};
                        innerObj[fullSubName] = subValue;
                        query += param(innerObj) + '&';
                    }
                } else if (value instanceof Object) {
                    for (subName in value) {
                        subValue = value[subName];
                        fullSubName = name + '[' + subName + ']';
                        innerObj = {};
                        innerObj[fullSubName] = subValue;
                        query += param(innerObj) + '&';
                    }
                } else if (value !== undefined && value !== null) query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
            }
            return query.length ? query.substr(0, query.length - 1) : query;
        };

        $httpProvider.defaults.transformRequest = function(obj) {
            return angular.isObject(obj) && String(obj) !== '[object File]' ? param(obj) : obj;
        };
        $httpProvider.defaults.headers.post = {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        $httpProvider.defaults.headers.put = {
            'Content-Type': 'application/x-www-form-urlencoded'
        }

        angular.extend(CacheFactoryProvider.defaults, {
            maxAge: 15 * 60 * 1000,

        });
    }
];

module.exports.routing = ['$urlRouterProvider', '$locationProvider',
    function($urlRouterProvider, $locationProvider) {
        $locationProvider.html5Mode(true);

        $urlRouterProvider.otherwise('/signin');
    }
];

More

这篇文章主要讲个大致思路,项目webpack-angular的完整代码已经放到github上面,有需要可以参考。代码的组织方式本身就是仁者见仁,智者见智,没有最好一说,所以还是希望可以一起探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容

  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 8,157评论 7 35
  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,437评论 1 32
  • 最近在学习 Webpack,网上大多数入门教程都是基于 Webpack 1.x 版本的,我学习 Webpack 的...
    My_Oh_My阅读 8,175评论 40 247
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,685评论 7 110
  • 《新余志不约》 余良天日在东方, 绝路不悔吾愁棱。 志悔无醋幻物亏, 傲气凛然固画国。 2017年11月10日作
    春城怡景阅读 161评论 4 8