webpack
webpack 是一個打包器。
什麼是打包器?就是爲了把 node.js 引入的模塊方案讓前端也可以用上,作爲打包器的 webpack 存在的目的,就是把模塊方案編譯爲前端瀏覽器可以識別的格式。
webpack模塊化方案
文件 b 引出一個函數 b:
// b.js
exports.b = function b() {
console.log("b");
}
文件 a 引入此模塊,並調用模塊的引出函數 b:
// a.js
var b = require("./b.js");
b.b();
調用並查看輸出:
$node a.js
b
這樣的開發套路(創建並引入模塊)很常見。然而,這麼簡單好用的模塊方案在前端卻並不存在!但是有了 webpack 就可以了,要做的就是使用 webpack 對以上代碼做一次編譯:
$webpack a.js magic.js
然後,使用 html
引入它:
<html>
<body>
<script type="text/javascript" src="magic.js"></script>
</body>
</html>
打開瀏覽器訪問此文件,就可以看到在瀏覽器的控制檯內輸出了 b
。
模塊是一個古老的分而治之的技術,從結構化編程範式開始就有了。然而:
- 一方面,js 在語言層面,在客戶端是不支持的,它必須靠外在的 html 標籤
<script>
來實現粗淺的、僅僅能用的模塊。 - 另外一個方面,js 因爲語言的柔性,卻是有可能實現自己的相對更好的模塊,包括變量和函數的局部化等。
當然,在使用命令行指定編譯參數之外,更好的習慣是做一個配置文件:
// webpack.config.js
module.exports = {
entry: './a.js',
output: {
filename: 'bundle.js'
}
};
有了它,程序員就不必每次敲入 webpack a.js magic.js
,而只要 webpack
即可。配置文件略囉嗦,但是可以看出來就是替代了本有的 webpack 的命令行參數,然後各就各位。當執行 webpack
時:
$ webpack
Hash: ed9f2c850698ca3d8863
Version: webpack 1.13.1
Time: 51ms
Asset Size Chunks Chunk Names
bundle.js 1.55 kB 0 [emitted] main
[0] ./a.js 31 bytes {0} [built]
[1] ./b.js 45 bytes {0} [built]
輸出表明 a
文件,和它引入的 b
文件,都已經被轉譯完畢。轉譯到 bundle
文件內。
加載css
<!-- c.html -->
<html>
<body>
<div>Hello css</div>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
我們希望通過 css
來讓 div 變成紅色的字體,文件爲:
/* b.css */
div {
color:red;
}
我們只需要在 js
的入口文件內引用此 css:
require("./b.css")
並修改 webpack
的配置文件,以便通知 css
文件由 css-loader
加載,並由 style-loader
插入到 html
文件內:
// webpack.config.js
module.exports = {
entry: './a.js',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
{ test: /\.css$/, loader: 'style-loader!css-loader' },
]
}
};
因爲需要引入模塊 css-loader
和 style-loader
,我們需要安裝一下:
npm i css-loader style-loader --save-dev
隨後是熟悉的編譯命令:
webpack
現在工作全部做完,可以用瀏覽器打開文件 c.html
,發現 html 內的文字變紅,說明 css 生效了。
加載svg
<!-- main.html -->
<html>
<body>
<div>Hello svg</div>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
svg 文件就是繪製了一個填充了黑色的圓(文件名爲100.svg
):
<svg xmlns="http://www.w3.org/2000/svg"><circle cx="10" cy="10" r="10" fill="#000"/></svg>
依然在 js
的入口文件內引用此 svg:
var img1 = document.createElement("img");
img1.src = require("./100.svg")
document.body.appendChild(img1);
並修改 webpack
的配置文件,加入一個新的 svg-url-loader
(文件名 webpack.config.js
):
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
{test: /\.svg/, loader: 'svg-url-loader?limit=1'},
]
}
};
此
svg-url-loader
參數limit
指明再小也得使用外部引用文件形式。
因爲需要引入模塊 svg-url-loader
,安裝:
npm i svg-url-loader --save-dev
轉譯:
webpack
現在工作全部做完,可以用瀏覽器打開文件 main.html
,發現圖片已經加入到頁面內了。
加載圖片
加載圖片也可以使用模塊方案,也就是 require
函數方式。
// main.js
var img1 = document.createElement("img");
img1.src = require("./small.png");
document.body.appendChild(img1);
<!-- index.html -->
<html>
<body>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
]
}
};
要想打包這個圖片,webpack 需要一個 loader
來轉換 png 文件。承擔此責任的就是 url-loader
。
它的參數
limit=8192
指明,如果圖片大小小於8192,就直接使用Data URL,否則就是正常的URL。
Data URL無需引入外部文件,而是把內容直接編碼在src屬性內,編碼格式爲base64。
安裝:
npm i url-loader --save-dev
打包:
webpack
隨後打開文件 index.html
。你可以看到瀏覽器內已經顯示了此圖片。說明打包成功。
對於 Data URL 有些好奇的人,可以看看生成的
bundle.js
文件的最後幾行,最後賦值給img.src
屬性的,是類似這樣的數據:
"data:image/png;base64,iVBORw0....."
創建api-server
問題:服務端 api 代碼應該放置於何處?纔可以:
- 在開發階段,繼續利用
webpack
的熱加載。 - 在發佈階段,可以不必改變任何
api
代碼就可以繼續使用。 - 這些代碼不應該在
dev-server.js
內修改或者添加,而最好獨立於dev-server.js
存在。
答案是使用腳手架代碼中的 config/index.js
內的 proxyTable
屬性的配置,把到達 dev-server.js
的 api
訪問轉發給我的 api server
。
🌰 一個 hello 組件,從服務器的 api/who
提取消息,並綁定到客戶端組件內。使用的技術如下:
- vue-cli
- express
- vue-resource
首先,我們創建腳手架代碼:
vue init webpack helloapi #此爲3.0腳手架前的版本
cd helloapi
npm i
npm run dev
此時可以看到瀏覽器打開,顯示我特別熟悉的 vue 默認的 html 頁面:
Welcome to Your Vue.js App
我們現在提供一個 api 實現(api server),爲默認的 vue 的歡迎頁面消息做一個修改,服務器端來提供它:
var express = require('express');
var app = express();
app.get('/api', function (req, res) {
var j = {msg:'Hello From Server'}
res.end(JSON.stringify(j));
})
var server = app.listen(8181, function () {
var host = server.address().address
var port = server.address().port
console.log("listening at http://%s:%s", host, port)
})
客戶端需要安裝 vue-resource
:
npm i vue-resource --save
並在把 src/components/Hello.vue
替換爲如下代碼,以便實際發起 GET 請求:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'hello',
data () {
return {
msg: 'Welcome'
}
},
mounted(){
this.$http.get('/api').then((response) => {
var j = JSON.parse(response.body)
this.msg = j.msg
}, (response) => {
console.log('error',response)
});
}
}
</script>
在 src/main.js
內插入如下代碼,以便引入 vue-resource
:
import r from 'Vue-Resource'
Vue.use(r)
在 config/index.js
內添加代理轉發,把本來發給 dev-server.js
的 api rul
轉發給我們的 api server
。
module.exports = {
..
dev: {
...
proxyTable: {
'/api': 'http://localhost:8181',
},
}
}
啓動 api server:node server.js
啓動 dev-server.js: npm run dev
客戶端看到:
Hello From Server
這樣,開發階段我們已經做到了 apiserver
和 dev-server.js
的代碼分離,並且繼續利用本有的熱加載能力。現在,我們需要驗證:如果發佈了此代碼,api server 代碼中和 api 有關的代碼,是否可以無絲毫修改就可以繼續複用。
首先,發佈當前代碼:
npm run build
該命令會創建一個 dist
目錄,內有編譯打包好的全部 js
代碼和資源代碼。儘管其中有 index.html
,但是直接用瀏覽器打開是無效的。首先要啓動一個服務器,所有的資源文件必須通過瀏覽器發起,有服務器服務纔可以正常運行。我們可以稍稍修改api server
,引入插件,讓此服務器除了提供 api 服務外,也可以對整個 dist
目錄提供服務。只要添加代碼:
var path = require('path')
var dist = path.join(__dirname, 'dist')
app.use('/',express.static(dist))
然後啓動服務:
node server.js
打開瀏覽器,訪問 http://localhost:8181,可以看到和 dev-server.js
下一樣的結果。
這說明,api server
可以在發佈後不做修改(修改時爲了提供服務靜態內容的能力,對於 api 提供者的代碼是不做修改的)繼續使用。
熱加載
使用 webpack 的模塊熱加載可以加快開發的速度。它無需刷新,只要修改了文件,客戶端就立刻做熱加載。
- dev-middleware 的利用方法
- HMR(webpack-hot-middleware) 的利用方法
這次提供熱加載的代碼共兩個文件(放置於src
內),a
依賴於 b
,並調用 b
的引出函數:
// a.js
var b = require("./b.js")
b.b()
// b.js
exports.b = function b(){
console.log("h")
}
首先我需要使用 dev-middleware
讓使用 require 函數成爲可能,其次我希望使用 HMR
,當 b 文件內修改時,可以自動熱加載,而不是必須完整 reload 纔可以。
<html>
<body>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>
希望熱加載的代碼就是這樣了。目錄結構如下:
├── output
│ └── index.html
├── server.js
└── src
├── a.js
└── b.js
其中的 server.js
在隨後創建。現在我們創建環境,讓它可以熱加載: 創建目錄環境的命令爲:
mkdir src
touch src/a.js
touch src/b.js
mkdir output
touch output/index.html
touch server.js
創建環境:
npm init -y
npm install express --save
npm install webpack webpack-dev-middleware webpack-hot-middleware --save-dev
創建服務器文件:
此服務器文件使用 express
創建服務器監聽,使用 dev
中間件,HMR
中間件:
var express = require('express')
var webpack = require('webpack')
var path = require('path')
var app = express()
var webpackMiddleware = require("webpack-dev-middleware");
var compiler = webpack({
entry:
["./src/a.js",
'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
],
output: {
path: path.resolve(__dirname, './output/'),
filename:'bundle.js',
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
]
})
var options ={
publicPath: "/",
}
app.use(webpackMiddleware(compiler, options));
app.use(require("webpack-hot-middleware")(compiler));
app.use(express.static('output'))
app.listen(8080, function () {
console.log('Example app listening on!')
})
其中,dev
中間件中涉及到的入口文件的做法,和一般的 webpack
做法一樣,但是多出一個 webpack-hot-middleware/client
文件,此文件用來傳遞到客戶端,並和服務器的 HMR
插件聯絡,聯絡的 URL 爲 path=/__webpack_hmr&timeout=20000
,其中 path
有HMR服務監聽,timeout
:知道失聯的話,達到20000毫秒就算超時,不必再做嘗試。
爲了讓 HMR
知道 a、b
文件是可以熱加載的, a.js
得修改爲:
// a.js
var b = require("./b.js")
b.b()
if (module.hot) {
module.hot.accept();
}
現在執行服務: node server.js
。
打開瀏覽器,訪問 localhost:8080 ,並打開 Chrome devtools,看到:
bundle.js:1916 h
bundle.js:1626 [HMR] connected
現在修改 b.js
內的字符串爲 hello HMR
,看到 Console
輸出:
Hello HMR
bundle.js:1847 [HMR] Updated modules:
bundle.js:1849 [HMR] - ./src/b.js
bundle.js:1849 [HMR] - ./src/a.js
bundle.js:1854 [HMR] App is up to date.
就是說 HMR 已經激活。
ref : https://github.com/ahfarmer/webpack-hmr-starter-middleware
🔗: