簡介
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
| 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" }, { loader: "css-loader" }, { loader: "less-loader" }] }] } }
|
使用方式:
1 2
| 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]', publicPath: "/src/", outputPath: "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 } }] }] } }
|
使用方式:
1 2 3 4 5 6
| 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
| $ npm install --save-dev webpack webpack-cli
$ npm install --save-dev css-loader style-loader url-loader file-loader less less-loader
npm install --save-dev babel-core babel-loader babel-preset-es2017
|
安裝 webpack-dev-middleware、webpack-hot-middleware
1 2 3 4 5
| $ npm install --save-dev webpack-dev-middleware webpack-hot-middleware
$ 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 webpack --watch webpack -d 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', './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" }, { loader: "css-loader" }, { loader: "less-loader" }] }, { 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 } }]
}] }, devtool: 'cheap-module-eval-source-map', plugins: [ new webpack.HotModuleReplacementPlugin(), ] };
|
搭配 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);
app.set('trust proxy', 1) 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(); });
const compiler = webpack(webpackConfig); compiler.apply(new DashboardPlugin());
app.use(webpackDev(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }));
app.use(webpackHot(compiler));
app.use((req, res, next) => { let err = new Error('Not Found'); err.status = 404; next(err); });
app.use((err, req, res, next) => { res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500); res.render('error'); });
let httpServer = app.listen(app.get('port'), (err) => { if(err) { console.error(err); } else { console.log(`Listening at port ${httpServer.address().port}.`); } });
|
啟動 Server
依照上述步驟設定好 Webpack,並撰寫好 app.js
後,要記得在 HTML 中加入打包好的 JS:
1
| <script src="/src/bundle.js"></script>
|
最後就可以啟動 Server:
1 2 3 4 5
| $ npm run dev
$ npm run deploy
|
這樣就完成了使用 Webpack 來打包我們的網站囉~
參考資料