# 前端项目持续集成(一)

# 什么是持续集成

# 百度百科

  • 是一种软件开发实践,每个开发人员每天至少要集成一次代码,每次集成都通过自动化构建「包括编译、发布、自动化测试」,从而尽早的发现集成错误。
  • 持续集成一般和持续部署一起被提及【CI/CD】
  • 减少风险
  • 减少重复过程
  • 任何时间任何地点都可部署软件
  • 增强项目的可见性
  • 建立团队对开发产品的信心
  • 持续集成要遵从的原则
    1. 所有开发人员需要在本地机器上做本地构建,在提交版本控制库中,从而确保变更不会导致持续集成失败
    2. 开发人员每天至少向版本控制库提交一次代码
    3. 开发人员每天至少从版本控制库中更新一次代码到本地机器
    4. 需要有专门的集成服务器来执行集成构建,每天要执行多次构建
    5. 每次构建都要 100%通过
    6. 每次构建都可以生成可发布的产品
    7. 修复失败的构建是优先级最高的事情
    8. 测试是未来,未来是测试

# CI/CD 流程

  1. 本地机器写代码
  2. 提交代码,push 到 git 远程仓库
  3. git hook 触发 jenkins 的构建 job(自动)
  4. jenkins job 中拉取项目代码,运行npm run unitnpm run build,如果失败,发邮件通知相关的人。(自动)
  5. jenkins job 中执行测试服务器的部署脚本(自动)

当代码发生变更到 gitlab 的时候,就可以通过钩子将代码变更事件提交到 CI 系统,CI 系统也就是构建平台,一些插件组成的系统,可以在上面写脚本,这是一个服务器,上面有很多软件,如 jenkins 等(还有一个发布服务器,专门负责发布的),这台服务器也有引用代码仓库的钩子,检测到一些变更事件后就自动构建部署到内网,自动测试,自动提测。【最在 jenkins 上利用 webpack/grunnt 等进行项目构建并自动化测试,若成功,交付给 QA,若失败,打回开发】

# 前端工程化

# 自动化编译里都发生了什么

-> 读取foo.es文件内容,babel编译成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_2afob.png)
  -> 替换完毕所有资源定位标记,对css内容进行压缩
  -> 返回css内容
-> 根据foo.css最终内容计算md5戳,替换foo.scss为 /static/scss/foo_base39.css
-> 替换完毕所有资源定位标记,对js内容进行压缩
-> 返回js内容
-> 根据最终的js内容计算md5戳,得到foo.coffee的资源url为 /static/scripts/foo_3fc20.js

# 模块化

每个文件就是一个模块,有自己的作用域,这个文件里所定义的变量、函数、类都是私有的,CMD 和 AMD 都是 CommonJS 的一种规范实现定义,RequireJS 和 SeaJS 是对应的实践。

  1. AMD=> requirejs(预加载)提前读取并加载 1.1 AMD 推崇依赖前置
    define(['dep1', 'dep2'], function(dep1, dep2){
      // 内部只能使用指定模块
      return function(){}
    })
    
    1.2 AMD 对加载的模块是提前读取并加载 1.3 模块化原理
// 文件加载/文件运行 顺序: 1.js 2.js 3.js
// 模块运行 顺序 3.js 2.js 1.js
// 1.js中 入口用require,其他用define
require(['2.js'], function(A){
  // A是2.js的返回值
  // 2.js, 3.js都加载完,才执行1.js的这个回调函数
})
// 2.js中
define(['3.js', 'xxx.js'], functionA(B, c){
  return 2; // 2.js模块的返回值
})
// 3.js 中
define([], functionA(){
  return {} // 3.js的返回值
})
// 子模块优先执行,主模块最后执行

1.4 优缺点 1.4.1 AMD 的依赖是提前声明的,依赖无需静态分析,无论是加载器还是自动化工具都可以直接获取到依赖 1.4.2 AMD 在代码书写上不怎么友好 1.4.3 AMD 模块内部与 NodeJS 的 modules 有一点差异

var onload = function(){
  clearTimeout(tid);
}
if('onload' in script){
  sript.onload = onload;
} else {
  script.onreadystatechange=function(){
    if(this.readyState === 'load' || this.readyState === 'complete'){
      onload();
    }
  }
}
)(script, id);
  1. CMD => SeaJS(懒加载)提前读取文件,但在需要在加载 2.1 CMD 推崇就近依赖 2.2 CMD 对加载的模块是提前读取并不加载,而是需要时加载(依赖模块 CMD 是延迟执行) 2.3 加载原理
// CMD
define(finction(require, exports, module){
  // 此处如果需要加载某模块,就声明加载
  var xx = require("xxx");
})

2.4 模块化原理

// 只有define,没有require
// 1.js
define(function(){
  var a = require("2.js");
  console.log(22222);
  var b = require("4.js")
})
// 2.js
define(function(){
  var b=require("3.js")
})
....

先把 require 那部分用正则解析出来,需要什么模块就加载什么模块(先把所需要的包下载下来)。全部加载完之后再执行。先执行主模块 1.js,碰到 require("2.js")就执行 2.js,2.js 中碰到 require('3.js')就执行 3.js... 2.5 优缺点 2.5.1 CMD 依赖是就近声明,通过内部 require 方法进行声明。但因为是异步模块,加载器需要加载这些模块,所以模块真正使用前需要提取模块里的所有依赖。 2.5.2 不能直接压缩,require 局部变量如果替换无法加载资源 2.5.3 CMD 路径参数不能进行字符串运算(用正则解析)

  1. 为什么要使用 webpack 3.1 Webpack 执行 CommonJS 标准,解决了依赖配置和请求流量。 3.2 对于 Webpack 来讲,万物都可是模块,所有文件都被合并到 JS 中,最终在浏览器。 3.3 兼容 AMD,CMD 3.4 js 模块化不仅仅为了提高代码复用性,更是为了让资源文件更合理的进行缓存。

# 前端开发组件化

  1. 每一个前端模块都是一个小项目,配合 mock.js 可进行本地开发测试。package.json 是标配,通过 webpack 对环境配置进行本地环境,线上环境的统一编译。
  2. 有 page 组装 widget,由 widget 组装 WebComponents(X-TAG)。
  3. 能够根据路由快速选择配置 SPA

# WebComponents

WebComponents 标准能够使开发者将 HTML 页面功能封装成 CustomElements(自定义标签),而不需要 React, Vue 等来帮助了。

# custom element

CustomElemenntRegistry接口实例用来处理 web 文档中的 custom elements->注册一个 custom element,并返回已注册的信息。

# CustomElementRegistry.define()方法用来注册一个 custom element,该方法接受以下参数
  • 表示所创建元素名称符合 DOMString 标准的字符串【custom element 名称不能是一个单词,且其中必须要有短横线】
  • 用于定义元素行为的类
  • 可选参数,一个包含 extends 属性的配置对象,是可选参数,他指定所创建的元素继承自哪个内置元素,可继承任何内置元素。
// main.js
class WordCount extends HTMLParagraphElement{
  constructor(){
    // 必须首先调用super方法
    super();
    // 元素功能写在这
    // 计算器指向元素父级
    var wcParent = this.parentNode;
    function countWords(node){
      var text = node.innerText || node.textContent;
      return text.trim().split(/\s+/g).length;
    }
    var count = "Words: "+countWords(wcParent);

    // 创建一个shadowRoot
    var shadow = this.attachShadow({mode: "open"});
    var text = document.createElement("span");
    text.textContext = count;
    // 将span 添加到shadow root上
    shadow.appendChild(text);
    setInterval(function(){
      var count = "Words: "+ countWords(wcParent);
      text.textCountent = count;
    }, 200)

    this.addEventListener("click", ()=>{
      alert("hello world");
    })
  }
  // 当custom element首次被插入文档DOM时,被调用
  connectedCallback(){
    console.log("load");
  }
  // 当custom element从文档删除时,被调用
  disconnectedCallback(){};
  // 当custom element被移动到新文档时,被调用
  adoptedCallback(){}
  // 当customelement增加、删除、修改自身属性时,被调用
  attributeChangedCallback(){}
}
customElements.define("word-count", WordCount, {extends: "p"});
// index.html
...
<p>weeee</p>
<p>weeee</p>
<p is="word-count">1111</word-count>
...

# 共有两种 customElements

  • Autonomous custom elements 是独立的元素,它不继承其他内建的 HTML 元素,可以直接写成 HTML 标签形式,在页面上用如<popup-info>,或者document.createElement("popup-info")这样。
  • Customized built-in elementts 继承自基本的 HTML 元素,在创建时,需要制定扩展元素,如上面例子的<p>元素,使用时,用is属性指定 custom element 的名称,例如<p is="word-count">document.createElement("p", {is: "word-count"}) tips: shadow root【实验属性】 具体看 MDN 文档,会更新

AST

js 堵塞 MVC esjs router

支持 MVC (MVVM 双向数据绑定) 沙箱环境

Web Components

Custom Elements Html Imports Html Templates Shadow Dom

X-Tag