前端该不该碰业务?

前端还是不要碰业务逻辑,围绕着交互做就好了
将“前端工程体系”定义成一种服务,而非一种工程模型

示例代码

https://github.com/boijs/boi

前端工程师的基本素养

前端工程化体系设计与实践 - 图1

前端革命

AJAX

A New Approach to Web Applications
实现异步请求和局部刷新

Node

Node 集成了 V8 引擎、事件驱动 和 底层 I/O API,并且可使用 JS 语言开发服务器端应用的运行环境

1、服务器端开发

实时应用、微服务、前端工程化等应用场景最佳技术选型之一

2、同构JS

  • 服务器端渲染:浏览器主动发送请求,然后服务器端生成 HTML 文档后发送响应给浏览器,浏览器接到响应后将 HTML 文档渲染为可视网页

    • 节省客户端资源

    • 有利于SEO

    • 缺点:每访问一个页面都要发起请求

  • SPA:服务端 RESTFul API 只提供渲染 HTML 所需的 JSON 数据

    • 减轻了服务器的资源消耗

    • 与HTML 文档比起来,JSON 数据的体积小很多,减少了网络请求的事件消耗

    • 页面路由控制更快速灵活

    • 可以离线使用

    • 缺点:

      • “白屏时间”:浏览器要等JS文件加载完后才可以渲染后续的 HTML 文档内容

      • 不利于SEO( google 已经对SPA 进行了 SEO 优化,但国内浏览器仍有该问题)

      • 数据格式问题及路由逻辑冲突

  • 同构JS:在 浏览器 和 Node 环境都可以渲染 ,性能、SEO、可维护性都有更好的支持

3、前端工具

Grunt | Gulp | webpack 等等

前后端分离

好处

  • 开发:实现前后端并行开发,缩短开发周期

  • 测试:令前后端工程师更快速、精准地对问题进行定位

  • 部署:将静态文件和动态文件分离部署并结合回滚策略,简化了部署流程,增强了应用程序的健壮性

模式

开发

HTML模版的处理方案

  • SPA项目:不存在HTML 模版的概念,所有 HTML 实体内容均由 JS 在浏览器下生成。所以可将 html 作为静态文件处理

  • HTML 模板由服务器部署的项目:最终的HTML模版需要与服务器端代码一起打包部署,由于静态文件必须由HTML 引入,为了避免套模板,开发阶段前端直接编写 HTML 模板

    • HTML 模板引擎的支持

    • Mock:写代码前约定好接口的请求规范和数据结构

      • HTML 模板的初始数据

      • 各种异步数据接口的数据

  • 大前端项目:前端负责与客户端相关的所有文件,包括静态文件和 HTML 模板

部署

静态资源和动态资源的分离部署

  • SPA项目

    • 不能令浏览器将 html 文件强制缓存到本地

      • 方案一: 专业服务器一般都支持针对文件拓展类型设置不同的缓存策略

        • html 文件:使用协商缓存(浏览器HTTP请求返回304)

        • 其他静态资源:强缓存策略 (浏览器HTTP请求返回200)

      • 方案二:都使用协商缓存

        • 静态资源部署到静态文件服务器

        • HTML 模板 与 服务器端代码一同部署

注意:大部分公司供测试用的静态文件服务器是不设置客户端缓存的,这样可以保证测试环境下每次访问网站都能拿到最新的资源

  • HTML 模板由服务器部署的项目:最终的HTML模版需要与服务器端代码一起打包部署,由于静态文件必须由HTML 引入,为了避免套模板,开发阶段前端直接编写 HTML 模板

    • HTML 模板引擎的支持

    • Mock:写代码前约定好接口的请求规范和数据结构

      • HTML 模板的初始数据

      • 各种异步数据接口的数据

  • 大前端项目

    • 将 JS、CSS、图片等静态资源部署到静态文件服务器

    • HTML 模板文件与中间层的 Node.js 代码一同部署到 Web 服务器

前端工程化

工具为实现

  • 开发

    • ES 规范与浏览器兼容性不一致

    • CSS 弱编程能力

    • 资源定位

    • 图片压缩|base64 内嵌| CSS Sprites

    • 模块依赖分析和压缩打包

  • 协作

    • JS部分逻辑依赖接口API
  • 部署(动静态资源分离部署)

    • 部分资源需要借助后端工程师部署

规范为蓝本

  • 项目文件的组织结构:

  • 源码的开发范式

  • 工具的使用规范

  • 各阶段的环境依赖

进化形态——集中管理的云平台

  • 淡化环境差异性,保证构建产出的一致性

  • 权限集中管理,提高安全性

  • 项目版本集中管理,便于危机处理,比如版本回滚等

工程化方案架构

  • 工具流管理工具:Grunt | Gulp

  • 构建工具:webpack | rollup

  • 整体解决方案:FIS | WeFlow

命令行工具

1、package.json 设置入口文件

  1. "bin": {
  2. "dbox": "bin/dbox.js"
  3. }

2、在 bin/dbox.js 顶部声明此文件需要调用 Node.js 执行

  1. #!usr/bin/env/ node

3、命令行交互模块 commander.js

  1. const commander = require('commander')

环境区分

  • dev: 借助 Mock 服务进行前端逻辑开发

  • testing:测试环境与生产环境的异步数据接口地址不同,构建出的代码需要便于浏览器调试(不混淆)

  • production: 控制静态文件的体积

集成 Yeoman 封装脚手架方案

  • 通过命令行收集用户的配置信息

  • 将这些动态的配置信息转化成静态的文件内容:通常使用 Boilerplate(样板文件,HTML模板引擎)执行,Yeoman 默认使用 EJS 引擎,动态内容转化完成后可将“.ejs” 文件后缀修改为 “.js “ 、”.css” 、”.html”

  • 将生成的文件复制到目标文件夹

封装一个脚手架方案实质上是创建一个 Yeoman Generator 实例

  1. class extends Generators {
  2. constructor(args, opts) {
  3. super(args, opts);
  4. // ...
  5. }
  6. initializing() {
  7. // ...
  8. }
  9. prompting() {
  10. // 负责用户提示和配置收集
  11. let prompts = [].concat(require('./_prompts/_js.js'))
  12. .concat(require('./_prompts/style.js'))
  13. .concat(require('./_prompts/_html.js'))
  14. return this.prompt(prompts).then((res) => {
  15. let appname = res.appname || this.options.appname;
  16. let options = Object.assign({}, res, {appname});
  17. });
  18. // dependencies,提供给自动安装依赖模块功能使用
  19. this.pkg = options.nodeModules;
  20. // 渲染配置项
  21. this.renderOpts = options;
  22. }
  23. writing() {
  24. // 负责文件操作
  25. }
  26. install() {
  27. // 负责依赖模块安装
  28. }
  29. }

构建

开发者直接编写的源代码并不能在宿主浏览器中正确无误地运行,构建(编译)承担着从源代码到可执行代码的转换者角色。

  • ES 规范的转译

  • CSS 预编译语法转译

  • HTML 模板渲染

  • 依赖打包:分析文件依赖关系,将同步依赖的文件打包在一起,减少 HTTP 请求数量

  • 资源嵌入:比如小于10KB 的图片编译为 base64 格式嵌入文档,减少一次 HTTP 请求

  • 文件压缩:减小文件体积,缩短请求时间

  • hash 指纹:通过给文件名加入 hash 指纹,以应对浏览器缓存策略

  • 域名/路径改变:开发环境与线上环境的域名肯定是不同的,不同类型的资源甚至部署于不同的 CDN 服务器上

  • 文件名改变:经过构建之后文件名被加上 hash 指纹,内容的改动导致 hash 指纹的改变

资源定位本质上是由于测试环境与生产环境的差异性

Babel

ES6 对开发效率和源代码可维护性的提升是非常客观的。ES6 几行代码通常需要几十行 ES5 代码才可以实现。

Babel 将 class 转化为两部分:

  • 工厂函数代码

  • 创建 class 本身的代码

构建系统:支持模块化规范并能够将散列的模块构建为利于部署的整合文件

模块化开发的价值:

  • 避免命名冲突

  • 便于依赖管理

  • 利于性能优化

  • 提高可维护性

  • 利于代码复用

commonJS

  • 针对服务器端或桌面应用开发等非浏览器环境下的 JS 开发

  • 静态模块化规范

  • 均是同步阻塞式加载,无法实现按需异步加载

AMD/CMD

  • 可以处理 JS 以外的资源

  • 源码无须编译便可在浏览器环境下运行

  • 按需异步加载、并行加载

  • 插件系统

CommonJS 和 ES6 Module 虽然自身不支持异步加载,但是提供了两种方式:

  • require.ensure(): 是 webpack 的特有 API,可移植性差

  • import():未来可期,但是需要借助特殊注释定义异步文件的名称

增量更新与缓存

  • 构建产出文件 hash 指纹,这是实现增量更新的必要条件

  • 构建更新 html 文件对其他静态资源的引用 URL


强缓存根据过期时间决定使用本地缓存还是请求新资源
Expires 的致命缺陷:它所指定的时间点是以服务器为准的时间,但是客户端进行过期判断时是将本地的时间与此时间点对比。

Cache-control:

  • no-cache 和 no-store:

    • no-cache: 并非禁止缓存,而是需要先与服务器确认返回的响应是否发生了变化,如果资源未发生变化,则可使用缓存副本从而避免下载

    • no-store: 是真正意义上的禁止缓存,禁止浏览器以及所有中间缓存存储任何版本的返回响应。每次用户都会向服务器发送请求,并下载完整的响应

  • public 和 private:

    • public:表示此响应可以被浏览器以及中间缓存器无限期缓存,此信息并不常用,常规方案是使用 max-age 指定精确的缓存时间

    • private: 表示此响应可以被用户浏览器缓存,但是不允许任何中间缓存器对其进行缓存。例如用户的浏览器可以缓存包含用户私人信息的 HTML 网页,但 CDN 却不能缓存

  • max-age: 指定从请求的时刻开始计算,此响应的缓存副本有效的最长时间。

Cache-control 比 Expires 有更高的优先级

协商缓存每次都会发出请求,经过服务器进行对比后决定采用本地缓存还是新资源
Etag 作为响应首部信息返回给浏览器。浏览器在 Cache-control 指定 no-cache 或者 max-age 和 Expires 均过期后,将 Etag 值通过 If-none-match 作为请求首部信息发送给服务器。服务器接收到请求之后,对比所请求资源的 Etag 值是否改变,如果未改变将返回 304 Not Modified,并且根据既定的缓存策略分配新的 Cache-control 信息;如果资源发生了改变,则会返回最新的资源以及重新分配的 Etag 值。

对于非服务器端渲染项目中的 HTML 文档,由于它是所有其他静态资源的引用者,所以必须保证每次请求到的资源都是最新的。同时,为了便于服务器解析和保证网站地址的唯一性, html 文件不能应用 hash 指纹。这种场景下只能使用协商缓存。

覆盖更新:在引用资源的 URL 后添加请求参数,比如添加时间戳参数,浏览器会将参数不同的 URL 视为全新的 URL,所以下面的改动可以保证浏览器向服务器请求并下载最新的资源。

  1. <script type="text/javascript" src="main.home.js?v=1.0.0">

有两个致命缺陷:

  • 必须保证 html 文件与改动的静态文件同步更新,否则会出现资源不同步的情况

  • 不利于版本回滚

增量更新

  1. <script type="text/javascript" src="main.home.bbcdaf73.js">