Boison
百日轉職前端工程師:第十三週現代前端工具復盤 《DAY 20》

大家好,這是百日轉職前端工程師的 Day20,也是 11/17(二),這週的主題會講的是一些現代前端工具諸如 SaSS、Babel、Gulp、Webpack,能夠幫助工程師更方便的開發以及與團隊協作,這些工具的精神也被運用到現代框架中,因此了解這些工具的本身,也能夠在未來對於學習前端框架有一個更全面的瞭解。也會帶到在發送 request 常用的函式 promise 和 fetch。

復盤系列將會回答我正在上的課程 Huli 的程式導師實驗計畫每一週學習上的自我檢測目標:


 一、WEBPACK 功用和原理是什麼?如何用來模組化開發?

Webpack 是一套模組整合工具(Webpack is a module bundler.)。可將零散的 JavaScript 模組打包,然後在瀏覽器上運行,解決舊瀏覽器不支援部分新語法的問題,也利於後續管理與維護。

  1. index.js, utils.js, templates.js → 經過 webpack 打包 → main.js
  2. index.html 用 src 引入 main.js 模組。

由於 Webpack 在現代前端開發上相對重要,內容也份量十足,因此獨立為一篇詳細請見 百日轉職前端工程師:Webpack, gulp 現代前端工具

註:
主要參考節錄資料:Webpack 打包工具入門(由於此篇內容詳盡,內容多有重疊節錄,僅作個人筆記使用。)
次要參考資料:Webpack 官方網站文件


二、如何使用 Promise?

1. Promise 要解決什麼問題?

簡單來說,Promise 是一個包含很多函式的物件,而Promise 裡的 callback function 都是非同步。

Promise 的誕生是要用來解決非同步的多個 Callback function 造成的 callback hell 問題,也就是 Callback function 內部又包覆著很多個一層包一層的 Callback function,其會造成程式易讀性降低,也由於高度的複雜性和連動性導致接手原本專案的人難以更動修改程式碼,但且讓我們先熟悉 Promise 的特性,要真正解決 callback hell 的問題,還要搭配上我們待會要聊的另一個 fetch 函式 。

2. Promise 物件的特性?

Promise 的使用很間單,其內建兩個函式 resolve 和 reject。可以用其中的 then 來接收回傳的結果(resolve),catch 來接收回傳的錯誤(reject),所以要偵錯時只要塞入一個 .catch,若是有錯誤則 .catch 底下的 .then 就不會繼續被執行。

promise
.then(result => result + ‘!’);
.then(result2 => result2 + ‘?’
.catch(()=>console.log(‘error!’))
.then(result3 => { console.log(result3 + ‘!’)
})

資料來源:JavaScript 的同步與非同步 – 從 Callback function 到 Promise

3. Promise 如何使用?

先宣告一個 Promise 的 callback function 然後再做使用:

function sleep() {
const myPromise = new Promise(resolve => {
setTimeout(resolve, 3000)
})
return myPromise;
}

sleep()
.then((data) => { console.log(‘myPromise Data’, data); })
.catch(err => { console.log(‘err’, err);
})

.sleep 可簡化成

function sleep(ms) {
return new Promise(
resolve => { setTimeout(resolve, ms) })
}

.sleep 再簡化

const sleep = ms => {
return new Promise(
resolve => { setTimeout(resolve, ms) })
}

.sleep 可再簡化

const sleep = ms => new Promise(
resolve => {
setTimeout(resolve, ms)
})

.sleep 可再再簡化成

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))


三、如何使用 fetch?

1. fetch 要解決什麼問題?

fetch 是 ES6 的新語法,主要是搭配 Promise (Promise 的基本用法)來執行請求網站和請求後獲取 Response 的處理方式。在 JavaScript 中,網路請求(Request)其實主要分為兩種,在古代的時候只有 XMLHttpRequest (jQuery 中有名的 ajax 就是針對此做封裝),以及後來的 fetch (後來的標準,但也參考了 ajax 的做法),使用 fetch 的好處是可以避開 Callback hell 的問題。

2. fetch 函式的特性?

而利用 fetch() 這個 ajax 方法來發出 request,會回傳是一個 promise 的物件。裡面的內容如下:

  • arrayBuffer()
  • blob()
  • formData()
  • json()
  • text()

我們真正想要知道的是其中 text() 的內容,因此用 .then 將其取出如下:

const result = fetch(‘https://www.example_api.php’)
.then((res)=> {
console.log(res.text());
})

然而印出的內容還是一個 Promise 物件,因此我們只好不死心的再用 .then 一次如下,總算拿到資料了。

const result = fetch(‘https://www.example_api.php’)
.then((res)=> {
res.text().then (text => {
console.log(text);
});
})

3. fetch 函式的 Chaining 特性

const result = fetch(‘https://www.example_api.php’) .then((res)=> {
res.json().then (text => {
console.log(text);
});
})

原本寫法

因為 .then 裡面回傳的還是一個 promise 物件,我們可以一直使用 .then 來對回傳的 promise 做處理。簡單來說,.then return 的值會是下一個 .then 的值,而這個巧妙的特性設計也剛好為我們解決了層層包覆的 Callback hell 問題。

const result = fetch(‘https://www.example_api.php’) .then(res => {
return res.json() })
.then ( value => {
console.log(value)
})

運用 fetch 特性後的寫法,有效減少層數。

4. Fetch 使用時的注意事項

  • content type:注意 content type 是什麼,看 API 要接收的是什麼格式資料?
  • credential:發 request 給不同來源 domain 的 API 時,加上 credentials: ‘includes’,才會把 cookie 帶上去。

const data = { hello: ‘world’ }
const result = fetch(api400, {
method: ‘POST’,
body: JSON.stringify(data),
credentials: ‘includes’,
headers: new Headers({ ‘Content-Type’: ‘application/json’ })
}) .then() .then() .catch()

  •  mode: ‘no-cors’
    • 不能突破 CORS 限制,只是當發生時請瀏覽器回傳一個空的 response 不報錯。
  • Async / Await:用看起像是同步的語法,做到非同步的事情。
    • 用 async 宣告一個非同步的 function,await 接一個 Promise 物件,會等到執行完 await 裡面的 promise 才往下執行,await 是逐一執行的概念。

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
async function() {
console.log(‘hello’);
await sleep(1000);
console.log(‘world’);
}

會等到 await 裡面的 function 執行完才執行下一段

註:
參考資料 1:從 ES6 開始的 JavaScript 學習生活
參考資料 2:JavaScript 的同步與非同步 – 從 Callback function 到 Promise
參考資料 3:一起來把 setTimeout 封裝成 Promise 吧!
參考資料 4:JavaScript Fetch API 使用教學(含 Fetch 詳細參數)


四、gulp 的目的以及原理?

gulp 是一個前端自動化構建工具(Task Mananger),開發者可使用它來建構自動化工作流程,但通常在比較小的網站才會看到 gulp,如果在大型專案網站用到前端框架,任務又更多而且方向不太一樣,比較會用 webpack。


五、使用 gulp 建構自動化工作流程

各種常用的 gulp plugin 有 gulp-babel、gulp-uglify、gulp-rename、gulp-sass、gulp-clean-css,和 gulp-imagemin,在 src 和 dest 之間加入這些 gulp plugin,可以幫助我們建構自動化工作流程。

關於 gulp 內容,與 Webpack 獨立為一篇做筆記,詳細請見 百日轉職前端工程師:Webpack, gulp 現代前端工具


六、什麼是 uglify 與 minify?

在撰寫 CSS 程式碼時,有兩種方式可以讓 CSS 檔案變得更小,uglify 是將其變數名稱代換,並且重新編碼,盡量的簡化但也會讓程式碼變得不同。而 minify 則只是將程式碼去除所有不必要的空格,把它轉成越短越好,變成一整串連續不換行的程式碼。基本上這兩種壓縮都可以透過一些工具(Webpack, gulp……的 Plugin)去完成,詳細請見參考資料。


參考資料 1:Plugins – 示範內建的 webpack.optimize.UglifyJsPlugin
參考資料 2:前端也需要編譯?Transpile、Compile、Minify、Uglify 基本介紹
參考資料 3:Gulp 學習 2 – 打包壓縮 CSS 與 JS
參考資料 4:gulp-uglify


七、CSS Sprites 與 Data URI 的優缺點

CSS Sprite 是什麼?

CSS Sprite 技術將網站中使用的許多較少更動的小圖匯集成一張大圖,稱為 Sprite ,又稱為雪碧圖或者精靈圖等。透過這個處理可以節省網頁請求數量,加快網頁載入速度。比較常見的工具是使用 SpriteSmith 來合併圖片,並產生合併圖片後每個小圖的對應座標。SpriteSmith 可以跟許多建構工具結合使用,像是 Grunt Gulp 以及今天要介紹的 Sprite-Smith-Plugin。

其優點有以下:

  • 減少圖片的體積,加快速度。
  • 減少網頁的 HTTP Request 次數,加快速度。
  • 解決了每週圖片命名上困擾,加快開發效率。
  • 更換全部圖片風格方便,加快維護。

但也有一些缺點,如開發上要注意合併格式之類較麻煩之處,因此通常會搭配 SpriteSmith 等輔助工具使用,且用在較少更換的小圖如 logo 等上面。

如何使用 Sprite-Smith-Plugin

1. CLI 先安裝 Plugin

npm i webpack-spritesmith -D

2. webpack.common.js 設定檔調整如下:

var path = require(‘path’)
var SpritesmithPlugin = require(‘webpack-spritesmith’)
module.exports = {
plugins: [
new SpritesmithPlugin({
// 定義來源圖片資料夾,可以透過glob定義那些圖片要被合併 ex: .png src: { cwd: path.join(__dirname, ‘Resource/Images/sprite’), glob: ‘‘,},
// 合併完成後,圖片及scss的儲存路徑
target: {
image: path.join(__dirname, ‘Resource/Images/sprite.png’),
css: path.join(__dirname, ‘Resource/Sass/_sprite.scss’),},
// 可以設定一些圖片參數
spritesmithOptions: {
padding: 10,},
// 自動產生的 scss 要使用的 sprite 圖路徑
apiOptions: {
cssImageRef: ‘../Images/sprite.png’,},
})]}

3. 設定好之後,將原本網站上面的 ICON 放在步驟 2 的 src 來源資料夾
4. 執行 webpack 打包:npm start

註:
參考資料 1:使用 Sprite-Smith-Plugin 產生 CSS Sprite

Data URI 是什麼?

除了 Sprite,Data URI 是另外一種減少 HTTP 的請求(Request)數量的方式。Data URI 是一種檔案格式,其資料全經過 base64 編碼後以文字的方式儲存,以文字儲存的好處是可以直接寫進 HTML 或 CSS 中,不需要透過外部的檔案儲存。其效果不錯,但有資料顯示在手機上的效果較差。

但也有以下缺點:

  • 無法存快取:使用 Data URI,所有的資料都是在網頁中(圖片和網頁沒有分開),無法使用快取,每次開網頁都要重新渲染一次。
  • 原始碼讀取不易: base64 編碼過後的資料很長,原始碼讀取不易,搜尋速度也較慢,但可用 SASS partial 的方式,將 Data URI 與一般的網頁分開,使用變數的方式引入解決。
  • Data URI 是編碼後存在 HTML 或 CSS 中的,若每次資料要變更時要重新編碼,但可使用 SASS 與 Compass 來處理解決。

將資料編碼的方式

1. 線上編碼工具:使用線上編碼工具如 dataurl.net 等。
2. 使用 PHP 內建的 base64 編碼函數 base64_encode()

<?php
function create_data_uri($source_file, $mime_type) {
$encoded_string = base64_encode(file_get_contents($source_file));
echo (‘data:’ . $mime_type . ‘;base64,’ . $encoded_string);
} ?>

3. 使用 Compass 與 SASS:Compass 提供了一個很有用的函數 inline-image,如果你有使用 Compass 的話,就可以直接在 SASS 檔中使用這個函數:body { background: inline-image(“../images/logo.png”) }。因為這樣的方式是直接指向一個外部的檔案,所以如果該圖檔需要更動, Compass 也會自動將新的檔案編譯成 CSS。
4. 使用 Ruby And Rails:引入函式庫 base64,使用函式 create_data_uri() 。



八、CSS 優化的一些小技巧

1. 資源大小優化

  • Minify:把所有空格都去除掉。
  • gzip*:壓縮成不同編碼,需要解碼瀏覽器才能讀懂。

2. 載入方式優化

  • CSS Sprites: 把所有小的東西(如圖片)打包起來成一份,零零散散的 Request 整合起來的。
  • Critical CSS:把一大包東西分批載入,很大的話可以把一些重要的部分先載入。
  • Cache*: 當你今天不是第一次進入這個網站,有些東西可以先被瀏覽器暫存起來,就不用再重新要一次資源。

3. 執行方式優化

  • 選擇器:Selector 能否寫得更漂亮,來減少瀏覽器在語法解析上的時間。
  • 屬性渲染:你選擇的屬性對於瀏覽器來說是否不需要涉及比較早期的渲染機制,舉例用 CSS 去切換 JavaScript,是從排版期就開始重新去 render 還是在合成的階段再重新去 render。

註:* 意思為前端要知道,但主要是後端伺服器在做的事情。

九、CSS Selector 的權重

假設 CSS 對同一個區塊有不同的風格,要以何者為準?又如何計算權重呢?

  • CSS 屬性是會繼承到底下的,但越底下的屬性優先次序越高。
  • CSS 同一階層越後面寫的優先。
  • !important > inline style > id > class > tag
  • 若是同階層數量(權重)一樣,則往下一階層比!
  • !Important 實務上很少會去用。

十、結論

這週內容比較多,考驗自行上網找資料的能力,也要自行融會貫通,
關於 Scss / Sass 和 babel 內容請參考 Sass 入門,和 babel 入門,前者是對 CSS 做預處理讓其在編譯上可以更加結構化,後者則是讓新版本的 ES6 功能也能轉換後在舊版的瀏覽器上正確的運行,這週就這樣啦!


作者介紹 - Boison

Boison

台大 MBA 畢業的筆記術達人,在臉書上經營近 5,000 人的閱讀社群「書旅」,平日熱愛將職場所學做筆記分享商業精華,因此又有「筆記狂 Boison」之稱, 歡迎你追蹤我的臉書發落第一手消息:「 Boison 臉書」;出版社正式合作可透過 dragoncres@gmail.com 聯絡。

發表迴響