【溫故】前端工程化思考與實踐

隨着業務不斷髮展,產品規模不斷壯大。愈來愈多應用創建起立,業務邏輯各有不一樣且日趨複雜,團隊人員愈加龐大,代碼維護成本愈來愈高。 不斷實踐並不斷思考,前端開發其實就是要推進前端工程化。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 中

總結

以上只是鄙人對於前端工程化的一些簡單看法,想要真正熟悉工程化及其相關工具,仍是須要多實踐多踩坑。 編程是一種修行,應用修行的產物,也是咱們與世界交流的方式。將來在哪裏並不重要,重要的是以空杯心態持續學習和實踐,用心寫下每行代碼。

相關文章
相關標籤/搜索