0%

[Webpack] Webpack 設定及使用

Webpack

簡介

Webpack 是一個 module bundler,把許多不同類型的模組 build 成靜態資源。

主要功能:

  • 將 CSS、圖片與其他資源打包
  • Pre-processing Less、CoffeeScript、JSX、ES6 等檔案
  • 可依 entry 文件不同,把 .js 打包成多個 .js 檔案
  • 可以搭配許多 Loaders

這一篇文章主要是紀錄 Webpack 搭配 Express、Hot module replacement 、 Babel 和許多常用的 Lodaers 的設定及使用方式.

Loaders 介紹

在使用 Webpack 時,常常會搭配各種 loaders 來做靜態資源的打包,接下來我們先介紹一下常用到的 lodaers:

babel-loader

Babel 是一個JavaScript compiler,可以將新的 JS 語法轉譯為瀏覽器支援的 ES5,因為 JavaScript 幾乎是每年會提出一個新的規格草案,但是瀏覽器沒辦法很快就能夠支援新的 JS,所以我們需要先轉譯成瀏覽器支援的 ES5。

設定方式:

.babelrc:

1
2
3
{
"presets": ["es2017", "react"]
}

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
{
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: '/node_modules/',
use: {
loader: 'babel-loader'
}
}]
}
}

css-loader and style-loader

css-loader: 載入 CSS file.
style-loader: Add CSS to the DOM by injecting a <style> tag. 通常搭配css-loader一起使用。

webpack.config.js:

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

使用方式:

1
2
// in index.js
import './css/style.css';

less-loader

將 Less 轉為 CSS.

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
module: {
rules: [{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}]
}
}

使用方式:

1
2
// in index.js
import './css/style.less';

file-loader

載入檔案,回傳 public URL.

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
module: {
rules: [{
test: /\.(jpe?g|JPE?G|png|PNG|gif|GIF|svg|SVG|woff|woff2|eot|ttf)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[hash].[ext]', //[hash].[ext] or [name].[ext]
publicPath: "/src/",
outputPath: "img/"
}
}]
}]
}
}

使用方式:

  • If options.name == "[name].[ext]":

    1
    2
    // in index.js
    import './img/user-icon.png';
    1
    2
    3
    4
    <!-- main.html -->
    <div id="userIcon">
    <img src="/src/img/user-icon.png"></img>
    </div>
  • else:

    1
    2
    3
    4
    5
    import user_icon from './img/user-icon.png';

    let img = document.createElement("img");
    img.src = user_icon;
    $("#userIcon").append(img);

url-loader

file-loader 類似, 但在檔案大小小於 limit 時, 會回傳一個 base64 url.

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
module: {
rules: [{
test: /\.(jpe?g|JPE?G|png|PNG|gif|GIF|svg|SVG|woff|woff2|eot|ttf)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192 // 當圖片大小小於 8k 時使用 base64 URL, 其餘使用直接連接到圖片的 URL
}
}]
}]
}
}

使用方式:

1
2
3
4
5
6
//index.js
import user_icon from './img/user-icon.png';

let img = document.createElement("img");
img.src = user_icon;
$("#userIcon").append(img);

Plugins

Webpack 也提供了許多方便的 plugins, 這裡介紹一下常用的 plugins:

uglifyjs-webpack-plugin

uglifyjs-webpack-plugin 主要功能是 minify JavaScript, 減少 js file size.

webpack-dev-middleware

webpack-dev-middleware 可以讓 webpack 被 Express app 或是其他可使用 middleware 的框架所使用。

webpack-dev-server

webpack-dev-server 本身就是小型的 Express server,整合了 Express 和 webpack-dev-middleware。
雖然方便架設,但彈性較少,無法跟現有的 Express app 結合。

Hot Module Replacement

Hot Module Replacement (簡稱 HMR),主要功能是在修改任意檔案並儲存後,會自動編譯打包 (等同於 webpack --watch 的功能),接著更新瀏覽器畫面,只會 reload 更新的部分,而不會全部 reload。

HMR 有兩種套件:

  • react-hot-loader: webpack-dev-server 的衍生套件。
  • webpack-hot-middleware: webpack-dev-middleware 的衍生套件。

請依照選擇的環境安裝。

這裡我們選擇的環境是 webpack-dev-middleware + Express + webpack-hot-middleware, Dashboard 則是使用webpack-dashboard.

安裝

安裝 Webpack and Loaders

1
2
3
4
5
6
7
8
# webpack
$ npm install --save-dev webpack webpack-cli

# webpack loaders
$ npm install --save-dev css-loader style-loader url-loader file-loader less less-loader

# babel
npm install --save-dev babel-core babel-loader babel-preset-es2017

安裝 webpack-dev-middleware、webpack-hot-middleware

1
2
3
4
5
# webpack-dev-middleware and HMR
$ npm install --save-dev webpack-dev-middleware webpack-hot-middleware

# uglifyjs-webpack-plugin
$ npm install --save-dev uglifyjs-webpack-plugin

安裝 webpack-dashboard: A friendly UI for webpack.

1
$ npm install --save-dev webpack-dashboard

Webpack 相關指令

1
2
3
4
5
webpack                       # 會在開發模式下開始一次性的建置
webpack -p # 會建置production的程式碼
webpack --watch # 會監聽程式碼的修改,當儲存時有修改時會更新檔案
webpack -d # 加入source maps檔案
webpack --progress --color # 加上處理進度和顏色

設定 Webpack

這裡的設定範例是依照我們所選擇的環境: webpack-dev-middleware + Express + webpack-hot-middleware, 並使用以下 loaders:

  • babel-loaders
  • css-loader
  • style-loader
  • less-loader
  • url-loader

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
43
44
45
46
47
48
49
50
51
52
const path = require('path');
const webpack = require('webpack');
const DashboardPlugin = require('webpack-dashboard/plugin');

const env = process.env.NODE_ENV;
module.exports = {
mode: env || 'development',
entry: [
'webpack-hot-middleware/client?reload=true', // HMR
'./public/js/index.js' // 進入點
],
output: {
path: path.join(__dirname, '/dist/'),
filename: 'bundle.js',
publicPath: '/src/'
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: '/node_modules/',
use: {
loader: 'babel-loader'
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}, {
test: /\.(jpe?g|JPE?G|png|PNG|gif|GIF|svg|SVG|woff|woff2|eot|ttf)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192 // 當圖片大小小於 8k 時使用 base64 URL, 其餘使用直接連接到圖片的 URL
}
}]

}]
},
devtool: 'cheap-module-eval-source-map', // 設置 eval 或 SourceMap 屬性,debug 用
plugins: [
new webpack.HotModuleReplacementPlugin(),
// new DashboardPlugin() //如果沒有使用express, 將plugin加到webpack.config.js
]
};

搭配 Babel, 需安裝 babel-loader 並另外再設定 .babelrc:

1
2
3
{
"presets": ["es2017"]
}

可以在 package.json 加上以下 script, 方便之後執行:

1
2
3
4
5
6
7
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "NODE_ENV=development webpack-dashboard -p 2458 -- node app.js",
"deploy": "NODE_ENV=production webpack -p --config webpack.config.js"
}
}

其中,dev指令中的-p可換為指定的port.

撰寫 app.js

再來,我們先撰寫一個簡單的 app.js, 並在裡面加上 Webpack、webpack-dev-middleware、HMR 和 webpack-dashboard 的相關設定:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const path = require('path');
const logger = require('morgan');
const helmet = require('helmet');
const http = require('http');
const https = require('https');
const webpack = require('webpack');
const webpackDev = require('webpack-dev-middleware');
const webpackHot = require('webpack-hot-middleware');
const DashboardPlugin = require('webpack-dashboard/plugin');
const webpackConfig = require('./webpack.config');
const config = require('./config');
const routes = require('./routes');

// ====== 基本設定 ======
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.set('port', process.env.PORT || 3000);
app.set('httpsport', process.env.HTTPSPORT || 3001);

app.use(helmet());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use('/', routes);

// ====== Session ======
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: config.server.secret,
cookie: {
maxAge: 86400 * 1000
},
resave: true,
saveUninitialized: true
}));
app.use((req, res, next) => {
res.locals.login = req.session.login;
res.locals.account = req.session.account;
next();
});

// ====== Webpack (增加此段code) ======
const compiler = webpack(webpackConfig);
compiler.apply(new DashboardPlugin());

// 將 webpack 傳入 webpack-dev-middleware 並套用至 app,同時傳入屬性,webpack 就可以被加載進來
app.use(webpackDev(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath
}));

// 將 webpack 傳入 webpack-hot-middleware 並套用至 app,就可達到 HMR 的效果
app.use(webpackHot(compiler));

// ====== Error handler ======
// catch 404 and forward to error handler
app.use((req, res, next) => {
let err = new Error('Not Found');
err.status = 404;
next(err);
});

app.use((err, req, res, next) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

// ====== Server ======
// HTTP
let httpServer = app.listen(app.get('port'), (err) => {
if(err) {
console.error(err);
} else {
console.log(`Listening at port ${httpServer.address().port}.`);
}
});

// HTTPS
/*let options = {
key: fs.readFileSync(config.serverKey),
cert: fs.readFileSync(config.serverCrt)
}

https.createServer(options, app).listen(app.get('httpsport'), (err) => {
if (err) {
console.error(err);
}
console.log(`Listening at port ${app.get('httpsport')}.`);
});*/

啟動 Server

依照上述步驟設定好 Webpack,並撰寫好 app.js 後,要記得在 HTML 中加入打包好的 JS:

1
<script src="/src/bundle.js"></script>

最後就可以啟動 Server:

1
2
3
4
5
# develop
$ npm run dev

# deploy
$ npm run deploy

這樣就完成了使用 Webpack 來打包我們的網站囉~

參考資料