注意:该文件比较长,写的是从没有项目起到项目上线的一些事,从大的方面约束规范到环境的配置、代码的模式、静态文件管理都有些概括,希望对你有帮助~

环境和域名

项目的开发基本是开发->测试->线上的流程,那么项目开发前,约定好环境和域名,首先我们已知如下条件:

  • 使用git版本控制,前、后端分2个仓库(repository),会有master线上develop开发release发布3大分支
  • 后端使用php语言,Yii2
  • 项目域名fe.com
  • 项目静态域名cdn-fe.com,线上启用CDN服务
  • 分本地开发、联调测试、线上3个环境,每天环境都能独立的运行,当然前端fe可以不装数据库,但后端环境得有一套
  • 后端代码目录:/home/wwwroot/fe.com/
  • 前端代码目录:/home/wwwroot/cdn-fe.com/
  • 全是本地开发,fe本地有静态服务开发,rd本地有后端服务+数据库服务

代码目录只是个例子

本地环境

域名为:fe.mecdn-fe.me

前端fe维护前端仓库,当前如果前端也要"套模板"的话,本地也得有一套后端服务,当前数据库可以连接测试环境的

本地环境主要是前端fe开发静态页面使用,为的是方便快速开发,在develop分支开发并每天下班前推送到联调环境,以供后端和团队其他小伙伴及时应用最新版本

联调、测试、发布环境

域名为:fe.devcdn-fe.dev

该环境大多数在内网,测试服务器具有前、后端独立环境,并且具有数据库,联调环境fe.dev迁出develop分支,并在每天开发者push的时候自动更新最新代码,后端开发静态资源连接该环境的代码cdn-fe.dev

在每次上线前,把要上线的版本合并到release分支,并在测试服务器迁出release分支为fe.releasecdn-fe.release域名,让qa测试,测试通过后直接更新到线上

也就是说在这个测试服务器上具有4个站点:

  • fe.dev - 项目最新代码,用来开发功能
  • cdn-fe.dev - 静态项目最新代码,用来开发功能,主要是后端rd本地连接该资源
  • fe.release - 每次版本要上线的代码(其实也可以说是沙盒)
  • cdn-fe.release - 静态项目上线版本代码

这样的环境不冲突,不至于rd1和qa1在测试上线代码,而导致rd2的开发不正常,理论来说rd跟fe也一样,都是本地开发,只是rd连接的是测试环境(cdn-fe.dev)的静态文件,因为这个静态文件可以保证实时更新

线上

域名fe.comcdn-fe.com,当release版本测试通过,直接发布到线上

环境配置的问题

看着上面感觉乱乱的,其实都是配置文件,开发个Yii2模块,让配置为用户配置 > 环境配置 > 默认配置这样的规则去覆盖,用户配置可以用电脑主机名为key去设置,比如:

// 后端环境/config/默认配置.php
{
    // 配置
    "config": {
        // 线上默认配置
        "production": {
            "数据库": "阿里云",
            "域名": "fe.com",
            "静态域名": "cdn-fe.com"
        },

        // 开发环境配置
        "develop": {
            "数据库": "内网测试机:dev",
            "域名": "fe.dev",
            "静态域名": "cdn-fe.dev"
        },

        // 发布/测试配置
        "test/release": {
            "数据库": "内网测试机:release",
            "域名": "fe.release",
            "静态域名": "cdn-fe.release"
        }
    },

    // 当前环境
    "env": "production"
}

然后我fe本地的是:

// 后端环境/config/fe1.php
{
    // 配置
    "config": {
        "develop": {
            "数据库": "测试机:dev",
            "域名": "fe.me",
            "静态域名": "cdn-fe.me"
        }
    },
    // 当前环境
    "env": "develop"
}

可见我本地有环境,但数据库我连接的是测试机的dev,而rd的本地可以是这样:

// 后端环境/config/rd1.php
{
    // 配置
    "config": {
        "develop": {
            "数据库": "本地",
            "域名": "fe.rd",
            "静态域名": "cdn-fe.dev"
        }
    },
    // 当前环境
    "env": "develop"
}

整个配置的思路感谢 @阿旭、@消寒 大神的指导,非常好用

可见rd本地域名是自己、数据库是自己,因为这样很方便修改,而由于rd不改静态文件,那么又想保证链接最新的静态文件,那就连接测试机的dev吧,因为不管前端后端,只要往develop分支push,测试的dev版本就会自动更新。

当然配置式覆盖只是个思路,具体如何编写我想肯定难不倒你~

ps: 有次rd发现我个小问题,正好我也看到了,我顺手就改好了,然而rd刷新他的本地页面想稳定复现后找我来报bug,但发现我push后他那么好了,哈哈

多域名的好处

  1. 域名后缀使用语义化,比如.me就是本地、.dev就是测试机开发分支、.release就是要发布啊、.com就是线上
  2. 多个域名可以达到cookie不共享,避免线上出问题,本地想查却发现cookie共享了

规范

目录规范

前端仓库目录:

# 静态文件源码
./src/
    # 一些公用模块,包括js、css、和图片
    ./common/
        ./jquery.js
        ./zepto.js
        ./dialog/
            ./dialog.js
            ./alert.js
            ./close.png
        ./popup/
            ./select.js
            ./select.css
            ./base.js
            ./base.css

        # 单独的公用样式
        ./css/
            ./reset.css
            ./markdown.css

        # 单独的公用图片
        ./img/
            ./loading-16.gif
            ./loading-32.gif

    # 其他的以业务/方向名命名,比如:user、home、login
    ./{module}/
        ./img/
            ./login.png
            ./user.png
        ./page.js
        ./page.css

# 编译后的目录,里面目录结构同src一致,方便2个版本之间的切换
dist/

# 模板文件
./tpl/
    # 公用的基础样式演示
    ./commom/
        # 弹出层演示
        ./dialog/index.html

    # 其他的以业务/方向名命名,比如:user、home、login
    ./{module}/
        ./index.html
        ./login.html
        ./xxoo.html

# 单元测试
./test/

为什么模板文件单独存放,而不是跟src/静态文件在一起呢?是因为模板文件最终会给后端rd并套到php Yii2框架里运行,前端只是本地开发方便而已

样式、交互规范

同ue、pm约定好基础样式、交互的规范,比如:

  • psd确认稿单位、比例、标注
  • 定义基础样式:栅格、颜色、间距、字号等
  • 定义组件:幻灯、Tab、筛选、手风琴、输入框、下拉框等
  • 数据加载:加载中、加载超时、加载出错、数据为空
  • 样式规范抽离成公用组件文档

前、后端联调规范

  1. 接口规范、数据规范 - 因为约定好这些前端就可以使用mock数据而不依赖后端开发,达到并行开发
    • 接口参数类型和值
    • 返回值结构和类型(强制类型)
    • 统一返回状态码
    • 接口路径(版本、语义化)
  2. 模板代码注释,比如告诉套模板的人如何使用页面上的元素状态(登录前用xx,使用后用xx)、如何循环列表
  3. 约定整个项目的公用模块,比如公用的layout、商家中心的layout
  4. 文档

静态文件引用规范

本地开发时在/tpl/*.html里引用静态文件是:

<script src="/src/xxoo.js"></script>
<link rel="stylesheet" type="text/css" href="/src/xxoo.css">

这样是可以快速开发,但由于前、后端不是一个项目目录,又不想每次修改个静态文件还需要上线 静态文件+后端文件,但不编译后端如何处理静态文件版本号呢?

f点击这里查看静态文件版本号的处理

如果在后端项目里使用前端的一些编译语法,这样导致修改个静态文件后端就得编译,不然版本号对不上,当然你会说把后端的View层也放在前端项目里,做到前、后端模板分离,其实也可以,但模板里就会有些数据相关的问题,我们这里使用这样的方法处理静态文件:

  1. 后端扩展一个php的方法,启名为STATIC_FILE,后端模板(View层)里所有引用静态文件都使用该方法
  2. 前端在release的时候把代码编译并生成一个md5映射文件存放在前端项目/static_map.php
  3. 模板里使用<script src="<?php echo STATIC_FILE('common/login.js')?>"></script>,文件路径基于src/开始,因为这样可以很好的在src dist两个版本切换

再配置里添加个是否使用md5版本和使用src还是dist的参数,如:

{
    // 配置
    "config": {
        // 线上默认配置
        "production": {
            "static_model": "dist",
            "static_md5": true
        },

        // 开发环境配置
        "develop": {
            "static_model": "src",
            "static_md5": false
        },

        // 发布/测试配置
        "test/release": {
            "static_model": "dist",
            "static_md5": true
        }
    }
}

这样就是线上使用压缩版本dist并且开启md5功能,而开发环境既使用开发代码src也不开启md5功能,由于测试\发布环境就是沙盒所以必须跟线上一致。

前端生成的static_map.php大概长这样:

<?php
$STATIC_FILE_MD5 = array(
    'common/login.js' => '123456',
    'user/index.css' => '123456',
);

STATIC_FILE方法大概长这样:

<?php
# 假如 $CONFIG 就是配置变量,是由 【用户配置 > 项目配置 > 默认配置】 合并过来

/**
 * 获取静态文件路径
 *
 * @param {string} $uri 文件uri,以静态项目里的src为起始目录
 * @return {string}     文件的绝对路径
 */
function STATIC_FILE ($uri) {
    # 静态域名 + 静态模式(src,dist) + 路径
    $url = $CONFIG['静态域名'] . '/' . $CONFIG['static_model'] . '/' . $uri;

    # 如果没有开启md5则直接返回路径
    if (isset($CONFIG['static_md5']) && $CONFIG['static_md5'] === false) {
        return $url;
    }

    # 处理md5版本号
    # 加载前端项目编译后的md5文件
    include_once(前端项目 . '/static_map.php');
    $version = '';

    # 如果md5版本里有该文件
    if (!empty($STATIC_FILE_MD5[$uri])) {
        $version = '?' . $STATIC_FILE_MD5[$uri];
    }

    # 把版本号追加上返回,当前如果不存在md5也就忽略
    return $url . $version;
}

这里有个问题就是说,前端项目/static_map.php这个配置文件,他只能跟在前端项目仓库里,如果把她放在后端仓库也那也就成了前端改静态文件也需要上后端项目的问题。

这样比如前端要改个样式,只需要改好测好后编译下,把前端代码一上线即可,线上影射文件一更新就会生成新的md5文件路径,比如:

前端项目里开发静态页面
<script src="/src/xx.js"></script>

后端项目里
<script src="<?php echo STATIC_FILE('xxoo.js')?>"></script>

STATIC_FILE('xxoo.js')会根据环境的配置生成不同的,比如:

测试环境关闭了md5并且引用开发代码src
http://cdn-fe.dev/src/xxoo.js

上线开启了md5并引用压缩代码dist
http://cdn-fe.com/dist/xxoo.js?qwerty

发布/测试环境同线上一致
http://cdn-fe.release/dist/xxoo.js?qwerty

你可能会说为啥不是xxoo_qwerty.js呢?我想说你随便啊,改这个不过就是改下编译脚本和STATIC_FILE方法而已~

前端项目

开发模式

前端使用requirejs开发,页面中始终加载一个入口文件,这个入口文件在src里只是个加载器和项目配置,而在dist环境就是一些公用某块打包合并之后的,这样可以减少请求,通常这个引用是在公用的layout里,比如:

    <body>
        这里是其他的block

        <script src="<?php echo STATIC_FILE('common/main.js')?>"></script>

        ...

        <script>
            require(['home/index', 'common/xxoo']);
        </script>
    </body>
</html>

src/common/main.js文件内容是:

// requirejs代码
// ...

//requirejs配置
requirejs.config({
    // 默认主路径是src/
    baseUrl: '../',

    // 其他的路径映射
    paths: {},

    //...
});

dist/common/main.js文件是这样:

// requirejs代码
// requirejs配置代码

// jquery.js
// util.js
// ...

当然你可以有很多公用文件,比如频道的公用文件、某模块集合的配置文件,我们在开发时全使用异步加载,在发布、测试时前端使用脚本编译处理下,当然编译前\后的模块路径不换,也方便文件定位

项目配置

前端项目配置在/package.json里,大概如:

{
    "name": "前端项目",
    "version": "1.0.0",
    "description": "xx网前端项目",
    "srcipts": {
        "server": "http-server -p 8080",
        "start": "npm run server",
        "test": "mocha --reporter spec --timeout 5000 --recursive test/",
        "test-cov": "istanbul cover _mocha -- -t 5000 --recursive  -R spec test/",
        "fecs": "fecs check src/ test/",
        "release": "",
        "watch": "",
        "debug": ""
    },
    "devDependencies": {
        "http-server": "*",
        "mocha": "*",
        "istanbul": "*",
        "fecs": "*"
    }
}

说下几个命名:

  • npm run server, npm run start, npm start - 本地开启web server功能,用来开发静态页
  • npm run test - 使用mocha运行单元测试
  • npm run test-cov - 使用istanbul运行测试覆盖率
  • npm run fecs - 使用fecs检查代码规范
  • npm run release - 编译发布代码,把src/文件编译、混淆、压缩、合并到dist/目录里,把dist/目录里的所有文件生成md5并生成static_map.php
  • npm run watch - 本地监听静态文件修改并实时刷新浏览器
  • npm run debug - 本地开启web server,配置host修改把线上域名cdn-fe.com域名到本地环境,并可配置dist->src或者src->dist的映射,方便调试线上代码

至于watchdebugrelease这些脚本,我想你可以根据你的项目写出来,但不使用全局的包,目前webpackgulp都支持非全局使用

开发流程

这里的开发流程只是说前端的一些流程工作,不包括诸如跟pm对需求这些

制作静态页面

develop分支:根据psd确认稿开发成本地的.html文件,可以使用npm run *的一些命令更好的本地开发,必须在文件内写好相关注释,开发完成(自测通过)后可在测试静态环境(cdn-fe.dev)让ue、pm确认效果,当然这里也只能确认下效果,逻辑还得出整个测试环境才可以测,在这里确认效果方便fe修改,不然套完模板告诉你有问题你就哭吧

套模板

develop分支:把静态项目里的.html文件代码片段根据注释编写出后端View层代码,并写相关的逻辑处理,某些功能告一段落后可以让联调

发布版本

在准备上线时把前端代码编译处理,再把开发分支代码合并入release分支,推送到测试机,让qa测试该版本的release代码,通过后给该版本打上标签,并合并入masterdevelop

上线

线上永远是master,只要合并入master分支表示代码没有问题,上线可使用线上机器拉分支,或者.tar包上线,当然这些看你如何处理了,线上只要一更新到master代码,就表示项目上线

修复线上问题

前端项目上如果发现线上问题,可在本地把master迁出一个fix-*分支,可以使用npm run debug命令把线上压缩代码重写到本地开发代码以方便调试,代码没问题后,合并release,并测试,通过后合并到master上线并打上标签,并合并到develop

整个架构的特点

优点

  1. 本地开发,开发代码不依赖于编译才能查看
  2. 合理的使用npm run功能,杜绝使用全局包(开发只需要克隆仓库和npm install安装依赖即可
  3. url规则重写方便调试线上问题
  4. 静态文件自动md5,而不用担心上线需要编译后端模板
  5. 多环境配置不冲突,如果你们是session单点服务器,可以相应的调整下配置的方案

缺点

  1. 线上后端机器也需要迁出一份前端代码,因为需要前端的文件md5映射

总结

你会不会说为啥感觉这么多额,其实当你整个走下来也没什么,至于说不用requirejs而用commonjs或者es6啥的,亦或者说用fis3,使用react不使用smarty之类我认为大的流程上都是大同小异,开发模式很多,好的不一定适合自己项目~

当然可能考虑的还不够多,欢迎吐槽~