Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
kuitos opened this issue Nov 13, 2015 · 25 comments
Open

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

kuitos opened this issue Nov 13, 2015 · 25 comments

Comments

@kuitos
Copy link
Owner

kuitos commented Nov 13, 2015

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

本文是近期对一系列 前端工程化&架构 文章的观点的整理及总结,特此鸣谢:
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个点相互之间又互有联系

  • 模块化开发涉及到性能优化,对构建工具有一定的配套实现要求,同时也会影响开发规范的制定
  • 组件化开发应该基于模块化框架来加载其他依赖的组件,如果组件化框架自带模块管理功能,那么就可能导致工程的性能优化实现困难(我们可以直接使用ES6的module语法及loader)
  • 组件库应该与组件化开发配套,组件仓库中的组件应该按照相同的标准实现
  • 开发规范工具必须容易实现,如果部署上有特殊要求,工具是否能很容易的做出调整而不是修改规范。
  • 工具是否能提供接入公司已有流程的接口,是否能与公司的ci工具相互融合

为什么都说前端目前正遭遇前所未有的工程问题

  1. 前端在第1、2阶段耗费了十多年的时间,然后近几年才井喷式的爆发
  2. 由于整个生态的发展缓慢、门栏低、构建应用成本低,前端开发长时间停留在刀耕火种、茹毛饮血的阶段
  3. 以前大部分前端工作都是切页面加特效,还不能算得上一个真正意义上的webapp,自然很少有公司能遭遇到工程化问题
  4. 前端不同于 客户端/后端 的特性(比如增量安装),导致遭遇的工程会很特殊,很难直接从别的领域套用已有的解决方案
  5. 我们自己完全意识不到那是问题😂

工程化到底要解决哪些问题

  1. 合理的开发流程及开发规范,包括代码规范、模块化组件化规范(分治)等(提高生产力)
  2. 一套自动化代码质量检测方案(提高系统可靠性)
  3. 一套自动化及高度适应性的项目 发布/部署 方案(提高系统的伸缩性及灵活性)
  4. 极致的性能优化,包括减少冗余的接口请求及资源请求、提高缓存命中率等,简言之就是站点的打开及运行速度(更好的用户体验)

举三个案例:

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

相关工具?

  1. 构建工具 gulp
    task-based的方式使得gulp无法(难以)处理资源嵌套的递归场景。如 a.js -> b.scss -> md5(d.img) -> md5(b.scss) -> md5(a.js)

  2. 基于 资源表+资源管理框架 策略的fis
    其实已经能处理大部分场景了,但是侵入式代码实在是无法接受。因为它是一个框架。

  3. 静态分析工具 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
Copy link

luqin commented Dec 24, 2015

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

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

@kuitos
Copy link
Owner Author

kuitos commented Dec 24, 2015

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

@luqin
Copy link

luqin commented Dec 24, 2015

按平台只加载对应的代码

@kuitos
Copy link
Owner Author

kuitos commented Dec 26, 2015

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

@luqin
Copy link

luqin commented Dec 26, 2015

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

@kuitos
Copy link
Owner Author

kuitos commented Dec 26, 2015

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

@luqin
Copy link

luqin commented Dec 26, 2015

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

@kuitos
Copy link
Owner Author

kuitos commented Dec 26, 2015

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

@luqin
Copy link

luqin commented Dec 26, 2015

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

@luqin
Copy link

luqin commented Dec 26, 2015

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

@javie007
Copy link

写的挺好

@luqin
Copy link

luqin commented Jan 6, 2016

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

@kuitos
Copy link
Owner Author

kuitos commented Jan 7, 2016

@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
Copy link

luqin commented Jan 7, 2016

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

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

  • System.import是根据页面的路径查找JS,能否根据当前JS路径来查找?
  • 暂时还没考虑合并文件之类,这部分有什么好的方案?

@kuitos
Copy link
Owner Author

kuitos commented Jan 7, 2016

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

@luqin
Copy link

luqin commented Jan 7, 2016

目录结构:

/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
Copy link

luqin commented Jan 8, 2016

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

@kuitos
Copy link
Owner Author

kuitos commented Jan 11, 2016

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

@luqin
Copy link

luqin commented Jan 11, 2016

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

@luqin
Copy link

luqin commented Jan 11, 2016

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

@kuitos
Copy link
Owner Author

kuitos commented Jan 11, 2016

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

@luqin
Copy link

luqin commented Jan 11, 2016

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

@kuitos
Copy link
Owner Author

kuitos commented Jan 11, 2016

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

@luqin
Copy link

luqin commented Jan 11, 2016

有没有例子可以瞧瞧的?

@guguji5
Copy link

guguji5 commented Nov 24, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants