【温故】前端工程化思考与实践

随着业务不断发展,产品规模不断壮大。愈来愈多应用创建起立,业务逻辑各有不一样且日趋复杂,团队人员愈加庞大,代码维护成本愈来愈高。 不断实践并不断思考,前端开发其实就是要推进前端工程化。javascript

  • 平常开发中,咱们为何须要前端工程化?php

  • webpack、npm、gulp等它们有什么关系?css

  • 有多种构建工具,好比npm、yarn、browserify、gulp、grunt等,你知道他们之间的区别吗?html

  • 实际开发中却常常遇到第三方包报错,有时候咱们能够经过 rm -rf node_modules && npm i 暴力地将问题解决,有时候却不能。你知道是什么缘由吗?前端

本文阐述前端工程化的一些思考和实践。java

什么是前端工程化

工程化是指node

将系统化的、严格约束的、可量化的方法应用于软件的开发、运行和维护,即将工程化应用于软件。———— 清华大学出版社《软件工程》webpack

软件工程与其说是计算机中的一门科学,不如说是偏向于提升系统生产效率的一整套方法论。git

前端工程化可以有效 提升 需求开始,需求开发,发布部署,测试阶段 的生产效率,提升代码质量,下降开发成本。github

前端工程化是为开发者服务的。简而言之,就是将平常开发的重复步骤整化为固定流程,把前端开发工做带入到更加系统和规范体系的一系列过程

为何要进行前端工程化

用户的需求从最开始简单的页面在向复杂的应用发展。前端须要作的事情更多,同时也要追求更友好的用户体验。

第一阶段:石器时代

适合小项目,不分先后端,页面由jsp、php、tornado template等在服务端生成,浏览器负责展示。

第二阶段:后端MVC

为了下降复杂度,有了Web Server层的框架升级,好比Structs、Spring MVC等。

以上两个阶段存在的问题

  • 前端开发依赖开发环境
  • 先后端没分离,可维护性差

第三阶段:SPA

2005年推出Ajax后,前端进入SPA阶段。 Single Page Application 单页面应用。

Ajax须要面临的问题

  • 先后端接口约定,开发沟通效率低
  • 大量的js来知足复杂的交互,维护成本高

前端工程化的主要目的是让开发更加规范化、流程化、自动化,实现敏捷开发。

以上三个阶段,开发者想要添加一些逻辑,最简单的作法是在HTML中插入一个script标签,而后直接在里面书写业务逻辑代码。这样作比较简单直接了当,可是也存在其余问题:

  • 不一样script标签之间命名冲突,容易形成全局做用域污染
  • 代码重用性差

针对这些问题,能够将HTML中内联的JavaScript提取出来成单独的JS文件,和使用当即执行函数表达式IIFE包起来,只把接口暴露到全局。

(function() {
	  // 经过当即执行函数表达式将做用域隔离
	  // 逻辑代码...
	})();
复制代码

可是会随着页面逻辑的复杂度增长了其余问题:

  • 页面JS文件引用顺序。
    HTML页面引用和处理js文件只能是顺序的(不考虑async等),所以js之间的依赖关系也是顺序的。简单的顺序依赖关系没法知足需求。一个大型工程内部的模块依赖关系一般是树状的。

  • 页面引用js文件的长度与数量。

文件愈来愈长,愈来愈难维护,一个页面的单个js文件可能有几千行代码,若是按功能切割成多个小文件,就会致使页面请求过多,每一个HTTP请求都须要单独创建链接,致使页面渲染速度降低

第四阶段:前端MVC/MVP/MVVC

为了下降前端开发复杂度,引入了模块化概念。AngularJS、React、Vue三分天下,还有Ember、Knockout、Polymer、Riot等大量前端框架涌现。

前端工程化的主要目的是让开发更加规范化、流程化、自动化,实现敏捷开发。

第五阶段:Node全栈

Nodejs兴起,前端语言可用于后端开发,带来一种新的开发模式。

后两个阶段的好处是

  • 先后端分离,开发和维护职责分明
  • 有利于重复代码模块化,减小迭代成本
  • 部署相对独立,项目合理分层,更好维护

前端工程化的基本概念

工具和语言虽然差别大,可是解决的都是类似的问题,概括为:

  • 扩展 javascript 、html、css 自己的语言能力
  • 解决重复工做
  • 模板化、模块化
  • 解决功能复用和变动问题
  • 解决开发和产品环境差别问题
  • 解决发布流程问题

开发同窗不用担忧不一样模块间的框架和依附系统差别,只要知道以上命令就可以完成全部开发流程。优化倾向于底层,脱离业务,实现更高层级的复用

预编译语言、模块热加载等技术能够提高开发效率,而利用自动化测试、lint 工具等能够保证代码的功能和质量;本地环境下还要生成 source-map、配置模块热加载等等便于调试代码;而到了生产环境下则要对资源进行压缩,生成版本号等。

工程化基本思想:模块化

模块化的系统,当需求改动时,开发者能够更快速地将问题定位到相应模块中,模块逻辑清晰不耦合。所以模块化具有更强的可维护性。

早期设计的js并不具有这一特性,CommonJS 以及 AMD 的出现,为前端定义了模块的标准。也有了实现这些模块化的库,好比 RequireJS 以及 Browserify。

模块化设计的特色:

  • 做用域封装
  • 重用性
  • 解除耦合

模块化带来的正面做用:

  • 可让开发者将本身工程中的代码按模块进行划分,模块之间也再也不仅仅是简单的顺序依赖关系。
  • 对于客户端来讲,接受打包后的单一文件,解决文件过多致使的HTTP请求耗时长问题
  • 并根据模块之间的依赖和实际需求,按需加载。

发布下载包工具:包管理器

包管理器是一个可让开发者便捷地获取代码和发布代码的工具。JavaScript 应用中,最主流的包管理器是 npm 和 Yarn。(发布本身的npm包教程

js没有强大的标准库,可是有不少小型的开源框架库知足经常使用的功能,好比日期处理、url处理、异步流程控制等,不须要人手工编写。一些具备特定功能的代码(框架、库等)按照特性形式被封装成包,开发者能够经过包管理器安装这些包,避免重复造轮子,也能够把本身的代码经过包的方式分享给别人使用。

npm 特性

npm 是node包管理器,经过npm获取项目须要的依赖,而且经过打包工具和业务代码打包在一块儿。其仓库是一个遵循 npm 特定包规范的站点,提供 API 来让用户上传和下载包、获取包信息、以及管理用户帐号。

npm init初始化npm项目,并根据终端输入的基本信息,生成配置文件package.json npm install 远程或者从目标路径获取npm包。(--save 和--save-dev参数区别

yarn的冲击

npm 存在 版本号锁定问题和性能问题,Yarn 这个竞争对手的出现能够说给 npm 带来了改进的动力。

Yarn 是 Facebook 公司在 2016 年 10 月 11 日开源的模块管理器,它宣称比 npm 更快、更安全、更可靠。Yarn 并不重头创建一个新的 Javascript 模块仓库,而只是替代 npm 客户端来管理原有的 node_modules 中的模块,并弥补 npm 的缺陷。 相对于npm的优势是:

  • 会帮助开发者自动生成和维护版本号描述文件。 初次执行 Yarn 时会在项目中自动生成一个名为 yarn.lock 的文件,它与 npm shrinkwrap 的内容形式很相近,而且会随着模块的更新自动同步。

  • 性能比当时 npm 更优。 Yarn 为了解决当时 npm 安装模块速度慢问题,在拉取包时采用并行操做,优化了请求队列,更高效地利用当前的网络资源。同时默认的 yarn.lock 也无形中减小了解析 semver 与获取模块最新版本的时间。

预编译语言是什么?

使用预编译语言的主要目的是为了实现 HTML、CSS、JavaScript自己语言所不具有的特性。好比最多见的 SASS,它是CSS的预编译语言,经过它开发者可使用模块、定义变量、编写嵌套规则等等来提升开发效率。另外还有 Babel 预编译 JavaScript 来实现新的 ES 特性,以及使用 TypeScript 去作类型检查。

Gulp 和 Grunt 有什么做用?

经过Gulp 和 Grunt 等构建流程管理工具,使得构建变得更加简单化,经过项目中的一些配置,开发者可使用简单的一行命令启动本地开发环境或者构建和发布整个工程。

构建流程优化是什么?能够作什么?

开发者修改代码并保存,构建工具从新打包刷新浏览器,完整构建一遍须要好几分钟,甚至更长,工程越庞大,须要的耗时越长。另外客户端资源体积过大也是问题,须要针对项目特色进行按需加载、异步加载、长效缓存等。

为何使用webpack

  • 拆分依赖树成块并按需加载
  • webpack有着丰富的插件接口,知足不一样的业务需求
  • webpack支持AMDCommonJs模块样式
  • 它巧妙的在你代码的AST中进行静态分析
  • 能处理简单的表达式,容许支持更多的类库
  • 支持SourceUrlsSourceMaps进行简单的调试.经过development middleware来监控文件和development server来自动刷新

如何实现前端工程化

AMD

Asynchronous Module Definition(异步模块定义)的缩写。下面的代码使用 AMD 规范定义了一个模块:

// 定义一个求和的模块
define('getSum', ['math'], function(math) {
	//第一个参数是当前模块的 ID,至关于给这个模块起一个名字
	//第二个参数是当前模块的依赖,好比上面咱们定义的 getSum 模块须要 math 模块的依赖
	//第三个参数能够是函数或者对象。
  return function(a, b) {
    console.log('sum: ' + math.sum(a, b));
  }
});
复制代码

经过这种形式定义模块的好处在于,它** 显式 **地表达出了每一个模块所依赖的其它模块。而且模块定义也再也不绑定到全局对象上,没必要担忧其在别的地方被篡改。

CommonJS 与 Node.js 模块系统

近两年来对于开发者来讲遵循 CommonJS 标准来编写和使用模块已经成为了一个基本通识。 CommonJS 是于 2009 年提出的 JavaScript 规范,它最开始是为了定义服务端标准,而非用于浏览器环境。

在 CommonJS 中每一个文件是一个模块,而且拥有属于本身的做用域和上下文。模块的依赖经过 require 函数来引入。

const math = require('./math');
复制代码

若是想把模块的接口暴露给外部,则要经过 exports 将其导出,如:

exports.getSum = function(a, b) {
  return a + b;
}
复制代码

缺点:

  • 阻塞调用在网络中调用并非很好,网络请求是异步的
  • 多个模块无平行加载

AMD 和 CommonJS 具备一样的特性——模块的依赖必须显式引入,这样就解决了以前维护复杂模块引入时的顺序问题。可是AMD编码开销大,阅读和编写都更加困难

ES6 Module

之因此在过去咱们有各类不一样的模块化标准是由于 JavaScript 这门语言自己不具有模块化的特性,而如今 ES6 中已经具有了。ES6 Module 的模块语法和 CommonJS 很像,它经过 import 和 export 来进行模块的导入和导出。

import math from './math';

export function sum(a, b) {
  return a + b;
}
复制代码

在 ES6 Module 中也是每一个文件做为一个模块。和 CommonJS 不一样的是,ES6 Module 的模块的依赖是静态的,或者说是在编译时肯定的,而不是运行时肯定的。

举个例子,咱们能够在 CommonJS 中的 if 语句中 require 模块,根据代码运行时 if 的判断条件决定是否要引入该模块。

// 根据运行时条件肯定是否引入
if(Date.now() > new Date('2019-01-01')) {
  require('./my_module');
}
复制代码

而在 ES6 Module 中则不容许这样作,import 必须在代码的顶层做用域,这意味着你不能把它放在 if 等代码块中。ES6 Module 这样规定的缘由在于可使编译器在编译阶段就能够获取到整个依赖树,从而进行代码静态分析层面的优化,好比检测出哪些模块是历来没有被使用过的,而后从打包结果中优化掉等等。可是如今ES6还不算彻底普及,很多浏览器不兼容。

模块打包原理简述

Webpack 以及其它的一些打包工具最基本的功能就是按照咱们定义好的依赖树将模块合并成单一的文件,让浏览器可以按照预想的依赖顺序去执行。这个过程咱们一般将它叫作模块打包

源码地址

Webpack 进行打包:

# Webpack 版本须要大于等于 2,这里使用的版本是 3.5.5
webpack app.js dist/bundle.js
复制代码

app.js 是咱们的打包入口文件,dist/bundle.js 是最终的打包合并结果文件。Webpack 会在打包的过程当中从入口 app.js 开始查找全部依赖的模块,并最终包装和合并这些模块放在 bundle.js 中

总结

以上只是鄙人对于前端工程化的一些简单看法,想要真正熟悉工程化及其相关工具,仍是须要多实践多踩坑。 编程是一种修行,应用修行的产物,也是咱们与世界交流的方式。将来在哪里并不重要,重要的是以空杯心态持续学习和实践,用心写下每行代码。

相关文章
相关标签/搜索