PJCHENder 未整理筆記

[Unit2] Webpack 處理 JS, CSS, 圖片和分檔載入

2017-09-26

[Unit2] Webpack 處理 JS, CSS, 圖片和分檔載入

@([Udemy] Webpack 2: The Complete Developer’s Guide)

Babel 處理 JS 轉譯問題

在 webpack 中,loaders 通常是用來對程式進行前處理(preprocessing)或編譯的。

Babel 可以用來幫我們將 ES6 或更之後的語法轉譯成 ES5 或其他版本的 JS 語法,其中包含了三個模組:

  • babel-loader:用來告訴 babel 如何和 webpack 合作。
  • babel-core:知道如何載入程式碼、解析和輸出檔案(但不包含編譯)。
  • babel-preset-env:讓 babel 知道如何將不同版本的 ES 語法進行轉譯。

STEP 1: 安裝 babel

1
npm install --save-dev babel-loader babel-core babel-preset-env

STEP 2: webpack config

在 webpack2 中把 loader 稱做 rule。在 webpack.config.js 中,我們建立 module,接著在 rules 裡面透過 use 來告訴 webpack 我們要使用哪個 loader/rule(這裡要稍微留意一下大小寫,modulerules

接著在 test 屬性中,我們要放入正則表達式 /\.js$/ 意思是要找到所有副檔名為 .js 的檔案,只有這些檔案要套用 bable-loader(我們不希望 babel loader 處理到其他的 css 或圖檔。

1
2
3
4
5
6
7
8
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/
}
]
}

STEP 3: .babelrc

接著我們要在根目錄資料夾中建立另一個檔案,這個檔案不需要名稱,直接命名為 .babelrc 。在 .babelrc 中,我們目前只需要告訴它我們要用哪個 presets 來告訴 babel 我們的檔案要用什麼樣的方式將 JavaScript 做轉譯:

1
2
3
{
"presets": ["babel-preset-env"]
}

STEP 4: 透過 babel 將檔案轉譯

寫好了上面的部分後,我們就可以透過 npm run build 來執行 webpack ,這時候你會發現雖然我們在 sum.js 中用的是 ES6 箭頭函式

1
2
// ./src/sum.js
const sum = (a, b) => a + b

但在 bundle 後的 build.js 中則會幫我們轉譯成 ES5 可以理解的版本:

1
2
3
4
// ./dist/bundle.js
var sum = function sum(a, b) {
return a + b;
};

將 CommonJS 的寫法轉成 ES6

接著,我們可以試著將 commonJS 的模組系統改成用 ES6 模組系統的寫法,這兩者是可以直接相互對應的:

img

css-loader 使用 CSS

STEP 1: 使用 JS 建立一張圖片檔

接著我們要來學習使用 CSS loader,我們先在 ./src 中新增一個檔案 image-viewer.js,在這裡面我們只是要建立一張圖片

1
2
3
4
5
6
7
// ./src/image-viewer.js

const img = document.createElement('img')
img.src = 'http://lorempixel.com/400/400'
document.body.appendChild(img)

// 在這裡我們沒有 export 任何的東西

由於在 image-viewer.js 中並沒有 export 任何內容,在 index.js 中我們 import 時,會幫我們執行 image-viewer 內的程式

1
2
3
4
5
6
7
// ./src/index.js

// const sum = require('./sum') // CommonJS
import sum from './sum' // ES6
import './image-viewer'

const total = sum(10, 5)

STEP 2: 建立 css 檔案並在 JS 中 import

接著我們在 ./src/ 這個資料夾底下在建立一個名為 styles 的資料夾,裡面建立一個名為 image-viewer.css 的檔案,裡面寫入簡單的樣式

1
2
3
4
5
/* ./src/styles/image-viewer.css */

img{
border: 10px solid blue;
}

接著在我們剛剛建立的 imger-viewer.js 檔中去匯入這支 css 檔

1
2
3
4
5
6
7
// ./src/image-viewer.js

import './styles/image-viewer.css'

const img = document.createElement('img')
img.src = 'http://lorempixel.com/400/400'
document.body.appendChild(img)

STEP 3: 安裝 css-loader 和 style-loader

接著我們要安裝 css-loaderstyle-loader

  • css-loader: 告訴 webpack 如何匯入和解析 css 檔案
  • style-loader: 將匯入的 css 內容注入 HTML 的 <style></style>

因此我們執行

1
npm install --save-dev css-loader style-loader

STEP 4: webpack config

接著我們要修改 webpack.config.js ,多一道 rule 來處理 css 檔案。

use 中我們可以使用陣列的方式讓同類型的檔案經過多個不同的 loader,其中它會由陣列的右邊執行到左邊,因此會先執行 css-loader 再來才是 style-loader

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/
},
{
use: ['style-loader', 'css-loader'], // 會由右往左執行,因此先執行 css-loader 再來 style-loader
test: /\.css$/
}
]
}

STEP 5: 執行 webpack

1
npm run build

透過這種作法,webpack 並不會幫我們額外的產生 CSS 檔案,而是從 bundle.js 這支檔案,透過 JS 的方式直接注入 CSS 到 HTML 的 <style></style> 當中

img

產生 css 檔案

在 webpack 中 pluginloader 是不同的,loader 是用來將匯入的檔案進行前處理,然後再打包進去 bundle.jsplugin 的執行則比較不在 webpack 的 pipeline 中。

STEP 1:安裝外掛

1
npm install --save-dev extract-text-webpack-plugin@2.0.0-beta.4

STEP 2:修改 webpack config

接著我們要回來修改 webpack.config.js 的檔案,在這裡我們使用 loader 而不是使用 useloader 是比較舊的 webpack 中的寫法,但是因為我們的外掛只認得 loader,所以我們要用 loader。

在 module 的後面,我們要在多一個 plugins 屬性,裡面用來放匯出後的 css 檔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// webpack.config.js
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/
},
{
// use: ['style-loader', 'css-loader'], // 會由右往左執行
loader: ExtractTextPlugin.extract({ // loader 是比較舊的用法,但因為外掛需要,所以寫成 loader
loader: 'css-loader'
}),
test: /\.css$/
}
]
},
plugins: [
new ExtractTextPlugin('style.css') // 將檔案匯出成 style.css
]

STEP 3: 執行 webpack

1
npm run build

接著在我們的 ./build/ 資料夾中會多一支 style.css,現在因為 CSS 不會透過 Style 的方式直接注入 HTML 中,所以我們要把這支 style.css 載到 HTML 中:

1
2
3
<head>
<link rel="stylesheet" href="./build/style.css">
</head>

如此就能達到和剛剛一樣載入 CSS 的效果。

處理圖片

接著我們要來使用另外兩個 loader 幫我們處理圖片,分別是 image-webpack-loaderurl-loader,其中 image-webpack-loader 用來幫我們壓縮圖片;url-loader 則幫我判斷要把圖片放在 bundle.js 中還是放在輸出的資料夾中。

img

STEP 1: 安裝 image-webpack-loader 和 url-loader

1
npm install --save-dev image-webpack-loader url-loader file-loader

STEP 2: webpack.config

在 webpack.config.js 中的 module 中在多一個 loader:

1
2
3
4
5
6
7
8
// 這樣寫會先執行 'image-webpack-loader',接著再執行 'url-loader'
{
use: [
'url-loader',
'image-webpack-loader'
],
test: /\.(jpe?g|png|gif|svg)$/
}

對 url-loader 進行設定

因為我們要對 url-loader 進行更多的設定,告訴它我圖片大小多少時要進行壓縮,因此我們會把原本的 use 改成下面這樣:

1
2
3
4
5
6
7
8
9
10
{
use: [
{
loader: 'url-loader',
options: { limit: 40000 } // 以 40 kb 當作檔案大小的分界
},
'image-webpack-loader'
],
test: /\.(jpe?g|png|gif|svg)$/
}

STEP 3: 下載圖檔並匯入 image-viewer.js 中

lorempixel 的網站下載圖片,新增一個 assets 資料夾,並把圖片放在裡面(big: 1200, small: 200)。

接著我們把下載的圖檔匯入 image-viewer.js 中:

1
2
3
//  ./src/image-viewer.js
import big from '../assets/big.jpeg'
import small from '../assets/small.jpeg'

接著我們可以執行 webpack

1
npm run build

我們會發現在 build 的資料夾中多了一支被壓縮過的圖檔,這支檔案是 big.jpeg 壓縮後的結果;至於原本 small 的圖片,則是以原始檔的方式直接被注入 bundle.js 中。

STEP 4: 使用圖檔

剛剛的步驟只會幫我們把圖片壓縮或注入 bundle.js 中,我們要在 image-viewer.js 中使用這些圖檔,才能看得到這些圖片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  ./src/image-viewer.js

import './styles/image-viewer.css'
import bigJPG from '../assets/big.jpeg' // 這會是檔案名稱
import smallJPG from '../assets/small.jpeg' // 這在 bundle.js 中,可以直接用

// smallJPG 是被注入在 bundle.js 中,可以直接使用
const smallImg = document.createElement('img')
smallImg.src = smallJPG
document.body.appendChild(smallImg)

// bigJPG 會是被壓縮過的檔案名稱
const bigImg = document.createElement('img')
bigImg.src = bigJPG
document.body.appendChild(bigImg)

// 在這裡我們沒有 export 任何的東西

當我們執行 webpack 後 ,會發現小圖可以正確顯示,大圖無法,這是因為 webpack 預設會在根目錄找這張圖檔(js_modules/xxx.jpeg),因此他找不到這張圖,為了解決這樣的問題,我們要設定 publicPath

STEP 5: 在 webpack config 中設定 publicPath

我們需要在 webpack.config.js 中的 output 屬性進一步設定 publicPath

1
2
3
4
5
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
publicPath: 'build/' // 建立 public path
},

在設定了 publicPath 之後,url-loader 就會把圖片名稱的前面加上 publicPath 所給的路徑,因此當我們再執行 webpack 就可以正確看到圖片了。

Webpack CodeSplitting (Class4)

透過 webpack 的 codesplitting 可以讓使用者在不同頁面只載入不同部分的 JS 程式碼片段。

在接下來的練習中,index.js 是我們的 entry point ,其中頁面上有一個按鈕,當這個按鈕被點擊的時候,我們才會 import image-viewer.js 這支檔案,並且載入圖片。

使用 System.import

為了要達到 codesplitting 的效果,我們要用到 ES6 中的 System.import('module') 這個方法,當滑鼠點擊的時候,它會以非同步的方式載入特定的 module(在這裡是 image-viewer.js)以及和這個 module 相依的其他 modules。

img

透過 System.import 它會回給我們一個 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//   ./src/index.js

const button = document.createElement('button')
button.innerText = 'Click Me'
button.onclick = () => {
// 點擊時才載入 image-viewer.js
// System.import 會回傳一個 Promise 物件
System.import('./image-viewer')
.then(module => {
console.log('module', module)
})
}

document.body.appendChild(button)

image-viwer.js 中,我們要把這要執行的程式 export 出來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  ./src/image-viewer.js

import './styles/image-viewer.css'
import bigJPG from '../assets/big.jpeg' // 這會是檔案名稱
import smallJPG from '../assets/small.jpeg' // 這在 bundle.js 中,可以直接用

// smallJPG 是被注入在 bundle.js 中,可以直接使用
const smallImg = document.createElement('img')
smallImg.src = smallJPG
document.body.appendChild(smallImg)

// bigJPG 會是被壓縮過的檔案名稱
const bigImg = document.createElement('img')
bigImg.src = bigJPG
document.body.appendChild(bigImg)

export {smallImg, bigImg}

修改完檔案後,我們可以在一次執行 webpack (npm run build),這時候到 chrome 的 network 視窗,當我們載入頁面的時候,只會先載入 bundle.js 這支,當我按下按鈕時,才會載入 0.bundle.js 這支,並且顯示出圖片。

img

檔案內容

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

const config = {

entry: './src/index',
output: {
path: path.resolve(__dirname, 'build'), // 這裡要填絕對路徑,用 node module 幫助產生,慣例上存在 build 或 dist
filename: 'bundle.js', // 慣例上用 bundle.js
publicPath: 'build/'
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/
},
{
// use: ['style-loader', 'css-loader'], // 會由右往左執行
loader: ExtractTextPlugin.extract({ // loader 是比較舊的用法,但因為外掛需要,所以寫成 loader
loader: 'css-loader'
}),
test: /\.css$/
},
{
use: [
{
loader: 'url-loader',
options: { limit: 40000 } // 以 40000 byte 當作檔案的分界
},
'image-webpack-loader'
],
test: /\.(jpe?g|png|gif|svg)$/
}
]
},
plugins: [
new ExtractTextPlugin('style.css') // 將檔案匯出成 style.css
]
}

module.exports = config

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"name": "js_modules",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-env": "^1.4.0",
"css-loader": "^0.28.1",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"file-loader": "^0.11.1",
"image-webpack-loader": "^3.3.1",
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"webpack": "^2.2.0-rc.0"
}
}

.babelrc

1
2
3
{
"presets": ["babel-preset-env"]
}

./src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
const button = document.createElement('button')
button.innerText = 'Click Me'
button.onclick = () => {
// 點擊時才載入 image-viewer.js
// System.import 會回傳一個 Promise 物件
System.import('./image-viewer')
.then(module => {
console.log('module', module)
})
}

document.body.appendChild(button)

./src/sum.js

1
2
3
4
5

const sum = (a, b) => a + b

// module.exports = sum // CommonJS
export default sum // ES6

./src/image-viewer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import './styles/image-viewer.css'
import bigJPG from '../assets/big.jpeg' // 這會是檔案名稱
import smallJPG from '../assets/small.jpeg' // 這在 bundle.js 中,可以直接用

// smallJPG 是被注入在 bundle.js 中,可以直接使用
const smallImg = document.createElement('img')
smallImg.src = smallJPG
document.body.appendChild(smallImg)

// bigJPG 會是被壓縮過的檔案名稱
const bigImg = document.createElement('img')
bigImg.src = bigJPG
document.body.appendChild(bigImg)

export {smallImg, bigImg}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="./build/style.css">
</head>
<body>
<script src="./build/bundle.js"></script>
</body>
</html>

掃描二維條碼,分享此文章