kuitos / kuitos.github.io

📝Kuitos's Blog https://github.com/kuitos/kuitos.github.io/issues
https://kuitos.github.io/
MIT License
1.13k stars 81 forks source link

前端工程化知识要点回顾&思考 #29

Open kuitos opened 8 years ago

kuitos commented 8 years ago

前端工程化知识要点回顾&思考

本文是近期对一系列 前端工程化&架构 文章的观点的整理及总结,特此鸣谢:
2015前端组件化框架之路 张云龙系列blog

编程技术及生态发展的三个阶段

  • 最初的时候人们忙着补全各种API,代表着他们拥有的东西还很匮乏,需要在语言跟基础设施上继续完善
  • 然后就开始各种模式,标志他们做的东西逐渐变大变复杂,需要更好的组织了
  • 然后就是各类分层MVC,MVP,MVVM之类,可视化开发,自动化测试,团队协同系统等等,说明重视生产效率了,也就是所谓工程化

    前端工程是软件工程的一个子类别

软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。

前端是一种GUI软件

从本质上讲,所有Web应用都是一种运行在网页浏览器中的软件,这些软件的图形用户界面(Graphical User Interface,简称GUI)即为前端。

前端又不同于传统的客户端软件/后端,因为前端应用具备“免安装”、“增量安装”等特性。也“得益”于这些特性,前端应用会遭遇客户端应用不可能碰到的资源管理问题,这也是前端最容易引起工程问题的点。

一个符合工程化要求的软件系统(前端)需要包含的要素

  1. 开发规范
  2. 模块化开发
  3. 组件化开发
  4. 组件仓库
  5. 性能优化
  6. 项目部署
  7. 开发流程
  8. 开发工具

1-3是技术业务相关的开发需求,4是技术沉淀及共享需求,5-8是工程优化需求

大部分时候我们谈的“工程化”其实只是“工具化”。

每一个单独的点或许都比较容易实现,但是把这8条串联起来则是一个很大的挑战,而且这8个点相互之间又互有联系

举三个案例:

  1. 最基本的资源合并,我们应该采取哪种策略?全部打包成一个还是分开打包?如何最高效的利用缓存?如何在降低请求数的同时提高缓存利用率?移动终端又应该采取哪种策略?
  2. 发布的时候我们到底是应该先部署页面还是静态资源?如何实现平滑升级?如果我还想玩个灰度发布呢?
  3. 如果采用模块化按需加载的方式开发,每次发布资源文件都会有不同的md5值,如何在不影响开发体验的前提下确保能引用到正确的模块?

    相关工具?

  4. 构建工具 gulp
    task-based的方式使得gulp无法(难以)处理资源嵌套的递归场景。如 a.js -> b.scss -> md5(d.img) -> md5(b.scss) -> md5(a.js)
  5. 基于 资源表+资源管理框架 策略的fis
    其实已经能处理大部分场景了,但是侵入式代码实在是无法接受。因为它是一个框架。
  6. 静态分析工具 webpack
    webpack依赖其可配置的loader使其拥有强大的打包能力,但是依然无法实现动态按需加载的需求。类似:

    if(browser){
       require('browser.js');
    } else {
       require('node.js');
    }

    出路

ES6 Module + ES6 Module Loader + HTTP/2.0 + Others

ES6 Module提供了一个原生的模块化语法,ES6 Module Loader则能提供一个原生的模块加载器。对于前端工程而言,资源管理是最核心的问题,而资源管理中加载又是重点更是难点。
可是ES6 Module Loader从ES6草案中移除了现在由WHATWG组织还在维护制定标准,pending状态。。
现在有一个基于这个草案实现的api polyfill Module Loader。可是你不是规范我这种教条主义者是不会用的😂

HTTP/2.0是HTTP/1.1的升级版(非革命版,前身是Google的SPDY协议),2015年5月以RFC 7540正式发表,新增了几个关键特性:

  1. 多路复用
  2. HEAD压缩
  3. 服务端推送

其中多路复用是对前端感知最明显的特性,基于此特性,HTTP/2.0时代需要淘汰的优化方式:

  1. 域名散列(突破单一域名请求连接数限制)
  2. 资源合并(多路复用带来了跟资源合并同样的效果,相反资源会造成的缓存利用率降低)
  3. 资源内联(server push)

ps:目前的各种bundle方案(如browserify&webpack)可能会在http2.0时代被淘汰(替代),有测试表明在http2.0环境下多文件请求会比单请求大文件更快。移动终端的意义更大,你无法想象移动端创建一个连接开销有多大。。多路复用才是未来!

总结

前端工程化相关问题是随之前端的发展越来越受到重视的问题,一套好的工程化解决方案能在提高开发效率(包括代码编写的舒适度及多人协作)的同时确保整个系统的伸缩性(各种不同的部署环境)及健壮性(安全),同时在性能上又能有一个很优异的表现(主要上各种缓存策略加载策略等),而且这套方案又应该是对工程师无感知(或感知很小)趋于自动化的一套方案。总知要达到这个目的前端工程化还有很长一段路要走。

拓展阅读

  1. 国内工程化第一人系列文章 https://github.com/fouber/blog/issues
  2. 大公司是如何部署前端代码的
  3. 相关工具
    • 百度:fis (资源表+资源管理框架 策略)
    • UC:scrat
    • 腾讯:mtjs (可以实现字节增量发布)
luqin commented 8 years ago
if(browser){
    require('browser.js');
} else {
    require('node.js');
}

可以实现按场景打包,通过 webpack 的环境变量

kuitos commented 8 years ago

@luqin 没明白你的意思?按场景打包是只按需加载对应的打包方案?

luqin commented 8 years ago

按平台只加载对应的代码

kuitos commented 8 years ago

@luqin 你说的这个是打包阶段吧。如果我要实现运行时的按需加载的需求呢?

luqin commented 8 years ago

运行时动态确实有点麻烦了,目前的做法都是定义多端的 main 文件入口实现

kuitos commented 8 years ago

@luqin 我猜测你想说的是webpack的require.ensure api吧。其实我想表达的是,我更倾向的是基于标准的module loader方案而不是与构建工具耦合的方式,谁让我是个唯标准论的教条主义者呢😂

luqin commented 8 years ago

我也希望标准能落地,不过短期内…… webpack 的编译时分包满足不了所有的需求,你那前端是什么架构?

kuitos commented 8 years ago

System.js我是在考虑用了,尽管现在还不能确定他就是未来标准的module loader方案。工具这一块基本的打包需求就webpack+自己写的nodejs脚本,复杂的就webpack+gulp。

luqin commented 8 years ago

webpack 分模块打包成独立 js 文件,然后 System.js 配置加载?

luqin commented 8 years ago

使用 webpack 编译整站时,修改任意模块都必须全部构建,这点有点坑了

javie007 commented 8 years ago

写的挺好

luqin commented 8 years ago

@kuitos System.js方案已经开始用了吗?

kuitos commented 8 years ago

@luqin 1.webpack会有cache,不会整站重新构建 2.对于System.js而言每一个模块文件可以直接System.import,不需要webpack再打包,在这之前你还得将es6 module转换成其他常用的module type(CommonJs/AMD/UMD)。当然你也可以配置在浏览器运行时做转译。 3.目前我在给公司的组件库开发中是采用System.js(jspm),采用System.js的原因是因为我不喜欢整站打包的方案,所以如果你们要用需要确定站点是否能升级http2.0做支持,否则还是应该选择打包方案。但是目前我碰到一个问题就是,我希望每个独立模块在发布时都能压缩并且带上md5值,如 a.js -> a-xxxx.js,但是如果做了这个事情后必须生成一个资源配置表来告知loader,我源码中System.import(a.js)等同于System.import(a-xxxx.js),我还没找到jspm是否支持这个构建动作。最近比较忙也没时间再深入研究所以目前还是采用bundle的方式,后面如果解决了这个问题会随时切到System.js。

luqin commented 8 years ago

@kuitos 我已经完成了一个System.js示例,包含 Gulp, Babel, SystemJS, React, react-router, echarts 源码地址:https://github.com/luqin/systemjs-es6-react-demo

确实完全不需要webpack,但是遇到了两个坑:

kuitos commented 8 years ago

@luqin 页面路径是指?当前js路径是?

luqin commented 8 years ago

目录结构:

/dist/
    demo/
            components/
                        App.js
            truck.js
            main.js

我的system的配置:

System.config({
  baseURL: "./dist",
  paths: {
    "*": "*.js"
  },
  defaultJSExtensions: true
});

然后components/App.js中使用System.import动态加载truck.js,不是基于当前文件的相对路径../trunk,而是基于基于baseURL的路径demo/truck

        setTimeout(()=> {
            // FIXME ???
            System.import('demo/truck')
                .then(m => {
                    let {Truck} = m;

                    let truck = new Truck({
                        price: 40000, make: 'Ford', model: 'F150',
                        year: 2014, is4by4: true
                    });

                    this.setState({
                        truck
                    });
                });
        }, 2000);
luqin commented 8 years ago

贴下源码的地址:https://github.com/luqin/systemjs-es6-react-demo 包含 Gulp, Babel, SystemJS, React, react-router, react-bootstrap, ECharts

kuitos commented 8 years ago

@luqin 看这里 https://github.com/systemjs/systemjs/blob/master/docs/system-api.md#systemimportmodulename--normalizedparentname---promisemodule 不过用起来感觉还是繁琐。。 另外,你这个还只是开发阶段,发布的时候我要压缩每个js并加上md5戳怎么弄?我没找到jspm能生成资源表的配置。。

luqin commented 8 years ago

应该可以的吧,我暂时就只研究到这,我目前还是决定用webpack打md5 hash之类的。jspm应该最终也会有一个所有资源的描述文件,类似webpack的stats.json

luqin commented 8 years ago

System.import 如果要使用相对文件的路径只能用构建工具了,另外估计babel也有这种插件,如果没有自己实现一个也是有可能的。

kuitos commented 8 years ago

我发的那个链接就是System.import api说明啊,支持相对路径的,只不过不够友好。

luqin commented 8 years ago

第二个参数需要构建工具,我用babel转commonjs的方式好像不支持, 没测试直接用ES6模块的方式

kuitos commented 8 years ago

不用构建工具,在System.config中配置好module就行。目前非标准,但是看描述未来会进入标准

luqin commented 8 years ago

有没有例子可以瞧瞧的?

guguji5 commented 3 years ago

路过 5年过去了 请问system.js 用起来了么 ?