webpack多页应用架构系列(十四):No复制粘贴!多项目共用基础设施

本文首发于 Array_Huang的技术博客—— 实用至上,非经做者赞成,请勿转载。
原文地址: http://www.javashuo.com/article/p-xwrwoszw-bz.html
若是您对本系列文章感兴趣,欢迎关注订阅这里: https://segmentfault.com/blog/array_huang

前言

本文介绍如何在多项目间共用同一套基础设施,又或是某种层次的框架javascript

基础设施是什么?

一个完整的网站,不可能只包含一个jQuery,或是某个MVVM框架,其中一定包含了许多解决方案,例如:如何上传?如何兼容IE?如何跨域?如何使用本地存储?如何作用户信息反馈?又或者具体到如何选择日期?等等等等……这里面一定包含了UI框架、JS框架、各类小工具库,不管是第三方的仍是本身团队研发的。而以上所述的种种,就构成了一套完整的解决方案,也称基础设施css

基础设施有个重要的特征,那就是与业务逻辑无关,不管是OA仍是CMS又或是CRM,只要总体产品形态相似,咱们就可使用同一套基础设施。html

框架

框架这个概念很泛,泛得让人心生困惑,但抽象出来讲,框架就是一套定义代码在哪里写、怎么写的规则。不能说咱们要怎么去框架,反却是框架控制咱们怎么去代码。前端

本系列前面的十来篇文章,分开来看是不一样的,但若是全部文章合起来,并连同示例项目(Array-Huang/webpack-seed),实际上阐述的就是一套完整的多页应用框架(或称架构)。这套框架规定了整个应用的方方面面,举几个例子:java

  • 每一个页面的文件放在哪一个目录?
  • 页面的HTML、入口文件、css、图片等等应该怎么放?
  • 编码规范(由ESLint来保证)。

固然,这只是个人框架,我但愿大家能够看懂了,而后根据本身的需求来调整,变成大家的框架。甚至说,我本身在作不一样类型的项目时,总体架构也都会有很多的变化。jquery

为何要共用基础设施/框架/架构?

缘起

数月前,我找同事要了一个他本身写的地区选择器,拉回来一看遍地都是ESLint的报错(他负责的项目没有用ESLint,比较随意),我这人有强迫症的怎么看得过眼,卷起袖子就开始改,改好也就正常使用了。过了一段时间,来了新需求,同事在他那改好了地区选择器又发了一份给我,我一看头都大了,又是满地报错,这不是又要我再改一遍吗?当时我就懵了,只好按着他的思路,对个人版本作了修改。今后,也确立了咱们公司会有两份外观功能都一致,可是实现却不同的地区选择器。webpack

很坑爹是吧?git

多项目共享架构变更

上面说的是组件级的,下面咱们来讲架构级别的。github

我在公司主要负责的项目有两个,在个人不懈努力下,已经作到跟个人脚手架项目Array-Huang/webpack-seed大致上同构了。但维持同构显然是要付出代价的,我在脚手架项目试验过的改进,小至改个目录路径,大至引入个plugin啊loader啊什么的,都要分别在公司的两个项目里各作一遍,超烦哒(嫌弃脸web

试想只是两个项目就已经这样了,若是是三个、四个,甚至六个、七个呢?堪忧啊堪忧啊!

快速建立新项目

不知道大家有没有这样子的经验:接到新项目时,灵机一动“这不就是个人XX项目吗?”,而后赶忙搬出XX项目的源码,而后删掉业务逻辑,保留可复用的基础设施。

也许你会说,这不已经比从零开始要好多了吗?整体上来讲,是吧,但还不够好:

  • 你须要花时间重温整个项目的架构,搞清楚哪些要删、哪些要留。
  • 毕竟是快刀斩乱麻,清理好的架构比不上原先的思路那么清晰。
  • 清理完代码想着跑跑看,结果一大堆报错,一个一个来调烦的要命,并且还极可能是删错了什么了不起的东西,还要去原先额项目里搬回来。

以上这些问题,你每建立一个新项目都要经历一遍,我问你怕了没有。

脚手架不是能够帮助快速建立新项目吗?

是的没错,脚手架自己就算是一整套基础设施了,但依然有下列问题:

  • 维护一套脚手架你知道有多麻烦吗?公司项目一忙起来,加班都作不完,哪顾得上脚手架啊。最后新建项目的时候发现脚手架已经落后N多了,你究竟是用呢仍是不用呢?
  • 甭跟我提Github上开源的脚手架,像我这么有个性的人,会直接用那些妖艳贱货吗?
  • 不一样类型的项目技术选型不同,好比说:需不须要兼容低版本IE;是web版的仍是Hybrid App的;是前台仍是后台。每一套技术选型就是一套脚手架,难道你要维护这么多套脚手架吗?

上述问题,经过共用基础设施,都能解决

  • 既然共用了基础设施,要怎么改确定都是全部项目一块儿共享的了,不管是组件层面的仍是架构自己。
  • 假设你每一个不一样类型的项目都已经准备好了与其它项目共用基础设施,那么,你根本不须要花费多余的维护成本,建立新项目的时候看准了跟以前哪一个项目是属于同一类型的,凑一脚就好了呗,轻松。

怎么实现多项目共用一套基础设施呢?

示例项目

在以前的文章里,我使用的一直都是Array-Huang/webpack-seed这个脚手架项目做为示例,而为了实践多项目共用基础设施,我对该项目的架构作了较大幅度的调整,升级为2.0.0版本。为免你们看前面的文章时发现示例项目货不对板,感到困惑,我新开了一个repo来存放调整后的脚手架:Array-Huang/webpack-seed-v2(https://github.com/Array-Huang/webpack-seed-v2),而且,我在两个项目的README里我都注明了相应的内容,你们可不要混淆了哈。

下面就以从Array-Huang/webpack-seedArray-Huang/webpack-seed-v2的改造过程来介绍如何实现多项目共用基础设施。

改造思路

改造思路其实很简单,就是把预想中多个项目都能用得上的部分从现有项目里抽离出来

如何抽离

抽离的说法是针对原项目的,若是单纯从文件系统的角度来讲,只不过是移动了某些文件和目录。

移动到哪里了呢?天然是移动到与项目目录同级的地方,这样就方便多个项目引用这个核心了。

若是你跟我同样,在原项目中定义了大量路径和alias的话,移动这些文件/目录就只是个改变量的活了:

选自webpack-seed/webpack-config/base/dir-vars.config.js

var path = require('path');
var moduleExports = {};

// 源文件目录
moduleExports.staticRootDir = path.resolve(__dirname, '../../'); // 项目根目录
moduleExports.srcRootDir = path.resolve(moduleExports.staticRootDir, './src'); // 项目业务代码根目录
moduleExports.vendorDir = path.resolve(moduleExports.staticRootDir, './vendor'); // 存放全部不能用npm管理的第三方库
moduleExports.dllDir = path.resolve(moduleExports.srcRootDir, './dll'); // 存放由各类不常改变的js/css打包而来的dll
moduleExports.pagesDir = path.resolve(moduleExports.srcRootDir, './pages'); // 存放各个页面独有的部分,如入口文件、只有该页面使用到的css、模板文件等
moduleExports.publicDir = path.resolve(moduleExports.srcRootDir, './public-resource'); // 存放各个页面使用到的公共资源
moduleExports.logicDir = path.resolve(moduleExports.publicDir, './logic'); // 存放公用的业务逻辑
moduleExports.libsDir = path.resolve(moduleExports.publicDir, './libs');  // 与业务逻辑无关的库均可以放到这里
moduleExports.configDir = path.resolve(moduleExports.publicDir, './config'); // 存放各类配置文件
moduleExports.componentsDir = path.resolve(moduleExports.publicDir, './components'); // 存放组件,能够是纯HTML,也能够包含js/css/image等,看本身须要
moduleExports.layoutDir = path.resolve(moduleExports.publicDir, './layout'); // 存放UI布局,组织各个组件拼起来,因应须要能够有不一样的布局套路

// 生成文件目录
moduleExports.buildDir = path.resolve(moduleExports.staticRootDir, './build'); // 存放编译后生成的全部代码、资源(图片、字体等,虽然只是简单的从源目录迁移过来)

module.exports = moduleExports;

选自webpack-seed/webpack-config/resolve.config.js

var path = require('path');
var dirVars = require('./base/dir-vars.config.js');
module.exports = {
  // 模块别名的配置,为了使用方便,通常来讲全部模块都是要配置一下别名的
  alias: {
    /* 各类目录 */
    iconfontDir: path.resolve(dirVars.publicDir, 'iconfont/'),
    configDir: dirVars.configDir,

    /* vendor */
    /* bootstrap 相关 */
    metisMenu: path.resolve(dirVars.vendorDir, 'metisMenu/'),

    /* libs */
    withoutJqueryModule: path.resolve(dirVars.libsDir, 'without-jquery.module'),
    routerModule: path.resolve(dirVars.libsDir, 'router.module'),

    libs: path.resolve(dirVars.libsDir, 'libs.module'),

    /* less */
    lessDir: path.resolve(dirVars.publicDir, 'less'),

    /* components */

    /* layout */
    layout: path.resolve(dirVars.layoutDir, 'layout/html'),
    'layout-without-nav': path.resolve(dirVars.layoutDir, 'layout-without-nav/html'),

    /* logic */
    cm: path.resolve(dirVars.logicDir, 'common.module'),
    cp: path.resolve(dirVars.logicDir, 'common.page'),

    /* config */
    configModule: path.resolve(dirVars.configDir, 'common.config'),
    bootstrapConfig: path.resolve(dirVars.configDir, 'bootstrap.config'),
  },

  // 当require的模块找不到时,尝试添加这些后缀后进行寻找
  extentions: ['', 'js'],
};

抽离对象

抽离的方法很简单,那么关键就看究竟是哪些部分能够抽离、须要抽离了,这一点看我抽离后的成果就比较清晰了:

先来看根目录:

├─ core # 抽离出来的基础设施,或称“核心”
├─ example-admin-1 # 示例项目1,被抽离后剩下的
├─ example-admin-2 # 示例项目2,嗯,简单起见,直接复制了example-admin-1,不过仍是要作一点调整的,好比说配置
├─ npm-scripts # 没想到npm-scripts也能公用吧?
├─ vendor # 没法在npm上找到的第三方库
├─ .eslintrc # ESLint的配置文件
├─ package.json # 全部的npm库依赖建议都写到这里,不建议写到具体项目的package.json里

再来看看core目录

├─ _webpack.dev.config.js # 整理好公用的开发环境webpack配置,以备继承
├─ _webpack.product.config.js # 整理好公用的生产环境webpack配置,以备继承
├─ webpack-dll.config.js # 用来编译Dll文件用的webpack配置文件
├─ manifest.json # Dll文件的资源目录
├─ package.json # 没有什么实质内容,我这里就放了个编译Dll用的npm script
├─components # 各类UI组件
│  ├─footer
│  ├─header
│  ├─side-menu
│  └─top-nav
├─config # 公共配置,有些是提供给具体项目的配置来继承的,有些自己就有用(好比说“核心”部分自己须要的配置)
├─dll # 以前的文章里就说过,我建议把各类第三方库(包括npm库也包括非npm库)都打包成Dll来加速webpack编译过程,这部分明显就属于基础设施了
├─iconfont # 字体图标能不能公用,这点我也是比较犹豫的,看项目实际须要吧,不折腾的话仍是推荐公用
├─layout # 布局,既然是同类型项目,布局确定是基本同样的
│  ├─layout
│  └─layout-without-nav
├─less # 样式基础,在我这项目里就是针对bootstrap的SB-Admin主题作了修改
│  ├─base-dir
│  └─components-dir
├─libs # 本身团队研发的一些公共的方法/库,又或是针对第三方库的适配器(好比说对alert库封装一层,后面要更换库的时候就方便了)
├─npm-scripts # 与根目录下的npm-scripts目录不同,这里的不是用来公用的,而是“核心”使用到的script,好比我在这里就放了编译dll的npm script
└─webpack-config # 公用的webpack配置,尤为是关系到“核心”部分的配置,好比说各第三方库的alias。这里的配置是用来给具体项目来继承的,老实说我如今继承的方法也比较复杂,回头看看有没有更简单的方法。
    ├─base
    ├─inherit
    └─vendor

最后总结一下,是哪些资源被抽离出来了:

  • webpack配置中属于架构的部分,好比说各类loader、plugin、“核心”部分的alias。
  • “核心”部分所需的配置,好比我这项目里为了定制bootstrap而建的配置。
  • 各类与UI相关的资源,好比UI框架/样式、UI组件、字体图标。
  • 第三方库,以Dll文件的形式存在。
  • 自研库/适配器。

结构图

上传上来之后发现图被压小了,请到这里看原图

Array-Huang-webpack-seed-v2 结构图

附系列文章目录(同步更新)

本文首发于 Array_Huang的技术博客—— 实用至上,非经做者赞成,请勿转载。
原文地址: http://www.javashuo.com/article/p-xwrwoszw-bz.html
若是您对本系列文章感兴趣,欢迎关注订阅这里: https://segmentfault.com/blog/array_huang
相关文章
相关标签/搜索