# 从0到大论前端持续集成
# 什么是持续集成。
# CI
- 在持续集成环境中,开发人员将会频繁的提交 代码到主干。这些新提交在终合并到主线之 前,都需要通过编译和自动化测试流进行验 证。这样做是基于之前持续集成过程中很重视 自动化测试验证结果,以保障所有的提交在合 并主干之后的质量问题,对可能出现的一些问 题进行预警。
# 持续交付(CONTINUOUS DELIVERY)
- 持续交付就是讲我们的应用发布出去的过程。这个过程 可以确保我们尽可能快的实现交付。这就意味着除了自 动化测试,我们还需要有自动化的发布流,以及通过一 个按键就可以随时随地实现应用的部署上线。
- 通过持续交付,您可以决定每天,每周,每两周发布一 次,这完全可以根据自己的业务进行设置。
- 但是,如果您真的希望体验持续交付的优势,就需要先 进行小批量发布,尽快部署到生产线,以便在出现问题 时方便进行故障排除。
# 持续部署(CONTINUOUS DEPLOYMENT)
- 如果我们想更加深入一步的话,就是持续部署了。通过这 个方式,任何修改通过了所有已有的工作流就会直接和客 户见面。没有人为干预(没有一键部署按钮),只有当一 个修改在工作流中构建失败才能阻止它部署到产品线。
- 持续部署是一个很优秀的方式,可以加速与客户的反馈循 环,但是会给团队带来压力,因为不再有“发布日”了。开 发人员可以专注于构建软件,他们看到他们的修改在他们 完成工作后几分钟就上线了。基本上,当开发人员在主分 支中合并一个提交时,这个分支将被构建、测试,如果一 切顺利,则部署到生产环境中。
# 持续集成需求
- 持续集成是通过平台串联各个开发环节,实现和沉淀 工作自动化的方法。
- 线上代码和代码仓库不同步,影响迭代和团队协 作。
- 静态资源发布依赖人工,浪费开发人力。
- 缺少自动化测试,产品质量得不到保障。
- 文案简单修改上线,需要技术介入。
# 121齐步走
- 统一代码仓库通过分支管理合并主干SVN。
- 自动化构建工具,编译、部署、测试、监控、本机 开发上线环境。FIS3/Webpack/jdists/package.json/ chai/supertest/mocha/selenium-webdriver
- 持续集成平台。Jenkins、Travis CI
- 部署工具。rsync、shelljs、yargs
- 运营同学有权限操作运营页面保存即可上线。
# 统⼀一代码仓库多分⽀支开发 ( Subversion Git)
# 合成步骤
- svn checkout svn地址 --username 用户名
- svn branch 分支名(add/commit)。
- svn merge 主干svn地址 分支svn地址。
- Beyond Compare -> svn resolved。
- svn copy 主干SVN地址 /tags/2017
# 前端⼯工程化
# 前端工程化目标
- 自动化编译。
- 前端模块化。
- 定位静态资源。
- 前端开发组件化。
- 自动化部署测试配合版本库。
- 自动化性能优化(前端架构开发下)
# 自动化编译
- -> 读入foo.es的文件内容,编译成js内容
- -> 分析js内容,找到资源定位标记 'foo.scss'
- -> 对foo.scss进行编译:
-> 读入foo.scss的文件内容,编译成css内容
-> 分析css内容,找到资源定位标记url(foo.png)
-> 对 foo.png 进行编译:
-> 读入foo.png的内容
-> 图片压缩
-> 返回图片内容
-> 根据foo.png的终内容计算md5戳,替换url(foo.png)为url(/static/img/foo_2af0b.png)
-> 替换完毕所有资源定位标记,对css内容进行压缩
-> 返回css内容 - -> 根据foo.css的终内容计算md5戳,替换'foo.scss'为 '/static/scss/foo_bae39.css'
- -> 替换完毕所有资源定位标记,对js内容进行压缩
- -> 返回js内容
- -> 根据终的js内容计算md5戳,得到foo.coffee的资源url为 '/static/scripts/foo_3fc20.js'
# 前端模块化
- 前端模块化框架肩负着 模块管理、资源加载 两项重要的功能,这 两项功能与工具、性能、业务、部署等工程环节都有着非常紧密的联 系。因此,模块化框架的设计应该高优先级考虑工程需要。
- CommonJS API定义很多普通应用程序(主要指非浏览器的应用) 使用的API,从而填补了这个空白。它的终极目标是提供一个类似 Python,Ruby和Java标 准库。
- 根据这个规范,每个文件就是一个模块,有自己的作用域。在一个 文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 4. CMD和AMD都是CommonJS的一种规范的实现定义,RequireJS和 SeaJS是对应的实践。
优缺点
- CMD 依赖是就近声明,通过内部require方法进行声明。但是因 为是异步模块,加载器需要提前加载这些模块,所以模块真正使用 前需要提取模块里面所有的依赖。
- 不能直接压缩,require局部变量如果替换无法加载资源
- CMD路径参数不能进行字符串运算。
- AMD的依赖是提前声明。这种优势的好处就是依赖无需通过静态 分析,无论是加载器还是自动化工具都可以很直接的获取到依赖。
- AMD依赖提前声明在代码书写上不是那么友好。
- AMD模块内部与 NodeJS 的 Modules 有一定的差异。
依赖后置
- requirejs和seajs二者在加载上都有缺陷,就是模块的依赖要等到模块加载完 成后,通过静态分析(seajs)或者deps参数(requirejs)来获取,这就为 合 并请求 和 按需加载 带来了实现上的矛盾:要么放弃按需加载,把所有js合成一个文件。要么放弃请求合并,请求独立的 模块文件,从而满足按需加载。
- AMD规范在执行callback的时候,要初始化所有依赖的模块,而CMD只有执 行到require的时候才初始化模块。所以用AMD实现某种if-else逻辑分支加载不 同的模块的时候,就会比较麻烦了。 require(['page/index', 'page/detail'], function(index, detail){ switch(location.hash){ case '#index': index(); break; }});
- 以纯前端方式实现模块化框架 不能 同时满足 按需加载,请求合并 和 依赖 管理 三个需求。
总结
- 传统模块化的意义,是让开发者不再需要写一堆 script 标签 引入js 了,让编码更爽。但是对于浏览器那边,没啥大的变化。
- require.js只是采取了某种“方法”,让你在写代码时只写一个 script,运行html文档时却是多个script
- AMD: 3.js,2.js,1.js,,,即如果模块以及该模块的依赖都加载完 了,那么就执行。。。 比如 3.js 加载完后,发现自己也没有依赖啊, 那么直接执行3.js的回调了,,,2.js加载完后探查到依赖的3.js也加载 完了,那么2.js就执行自己的回调了。。。。 主模块一定在后执行
- CMD: 1.js,2.js,3.js,,,即先执行主模块1.js,碰到 require('2.js')就执行2.js,2.js中碰到require('3.js')就执行3.js
# 静态资源定位
- 配置超长时间的本地缓存 — 节省带宽,提高 性能
- 采用内容摘要作为缓存更新依据— 精确的缓 存控制
- 静态资源CDN部署—— 优化网络请求
- 更资源发布路径实现非覆盖式发布 — 平滑升 级
# 开发组件化
- 每一个前端模块都是一个小项目,配合mock.js 可以进行本地的开发测试,package.json是标 配产物。经过webpack的环境配置统一进行本 地环境、上线环境的编译过程。
- 由page组装widget,由widget组装Web Components(X-TAG)。
- 能够根据路由快速抉择配置SPA或者直出。
# Web Components
- Custom Elements
- HTML Imports
- HTML Templates
- Shadow DOM
# Custom Elements
提供⼀一种⽅方式让开发者可以⾃自定义 HTML 元素,包括特定 的组成,样式和⾏行行为。
class ButtonHelloElement extends HTMLButtonElement {
constructor() {
super()
this.addEventListener('click', () => {
alert('hello world')
})
}
}
customElements.define('button-hello', ButtonHelloElement, { extends: 'button' })
<button is="button-hello">hello world</button>
<button-hello>hello</button-hello>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# HTML Imports
HTML Imports 是⼀一种在 HTMLs 中引⽤用以及复⽤用其他的 HTML ⽂文档的⽅方式。这个 Import 很漂亮,可以简单理理解为 我们常⻅见的模板中的 include 之类的作⽤用。
<link rel="import" href=“/components/header.html">
const link = document.querySelector('link[rel=import]') const header = link.import;
const pulse = header.querySelector(‘div.logo'); //获取 import 的 HTML 的 document const d = document.currentScript.ownerDocument
1
2
3
2
3
# HTML Templates
⽤用过 handlebars 的⼈人都知道有这么⼀一个东⻄西: 那么 HTML Templates 便便是把这个东⻄西官⽅方标准 化,提供了了⼀一个 template 标签来存放以后需要但是暂时不不 渲染的 HTML 代码。
<template id="template"><p>Smile!</p></template>
<script>
let num = 3;
const fragment = document.getElementById('template').content.cloneNode(true);
while (num-- > 1) {
fragment.firstChild.before(fragment.firstChild.cloneNode(true));
fragment.firstChild.textContent += fragment.lastChild.textContent;
}
document.body.appendChild(fragment);
</script>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Shadow DOM
Shadow DOM 最本质的需求是需要⼀一个隔离组件代码作⽤用域的 东⻄西,例例如我组件代码的 CSS 不不能影响其他组件之类的,⽽而 iframe ⼜又太重并且可能有各种奇怪问题。旨在提供⼀一种更更好地 组织⻚页⾯面元素的⽅方式,来为⽇日趋复杂的⻚页⾯面应⽤用提供强⼤大⽀支 持,避免代码间的相互影响。
const div = document.getElementById('id')
const shadowRoot = div.createShadowRoot()
const span = document.createElement('span')
span.textContent = 'hello world' shadowRoot.appendChild(span)
1
2
3
4
2
3
4
# Shadow DOM CSS
<x-foo>
<"shadow tree">
<div>
<span id="not-top">...</span>
</div>
<span id="top">...</span>
</>
</x-foo>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
x-foo::shadow > span 可以匹配到 #top 元素 x-foo /deep/ span 可以匹配到 #not-top 和 #top 元素 :host(.foo) 匹配 x-foo 元素
# Web Components
<template id="">
<style>
::content li {
display: inline-block;
padding: 20px 10px;
}
</style>
<content select="ul"></content>
</template>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<head>
<link rel="import" href="components/header.html">
</head>
<body>
<test-header>
<ul>
<li>Home</li>
<li>About</li>
</ul>
</test-header>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 自动化部署
jenkins travis CI