基於webpack和vue.js搭建開發環境

前言

在對着產品高舉中指怒發心中之憤後,真正能夠解決問題的是自身上的改變,有句話說的好:你雖然改變不了全世界,但是你有機會改變你自己。秉承着“不聽老人言,吃虧在眼前”的優良作風,我還是決定玩火自焚。

問題所在

之前的項目總結爲以下內容:

1、AMD模塊規範開發,使用requirejs實現,使用rjs打包,最終導致的結果是,輸出的項目臃腫,腫的就像一坨狗不理……不忍直視
2、使用gulp進行打包,這一點貌似沒有可吐槽的地方,畢竟都是被grunt折磨過來的……
3、數據的渲染使用模板引擎,這就意味着你要手動管理DOM,這樣,你的業務代碼參雜着你的數據處理、DOM管理,滿屏幕的毛線……
4、模塊化不足,雖然使用require進行了模塊管理,但是大部分業務邏輯還是充斥在一個文件裏,這與最近流行的組件化概念冰火不容,拒絕落後……
5、諸如 擴展性 、 維護性 我想早已不言而喻,不需贅述,再述就真TM是累贅了。

新框架要解決的問題:

1、要使構建輸出的項目像你鄰家小妹妹一樣、瘦的皮包骨。(也許是營養不良)
2、要實現真正的模塊化、組件化的開發方式,真正去解決維護難、擴展難的問題。(從此不怕產品汪)
3、業務邏輯專注數據處理,手動管理DOM的年代就像……像什麼呢?(畢竟成人用品也越來越自動化了)
4、等等…….(其實好處無需贅述,來,往下看)

爲了達成以上目標,我們探討一下解決方案:

1、老項目的構建輸出爲什麼臃腫?

答:因爲使用的是require的rjs進行構建打包的,瞭解rjs的都知道,它會把項目所有依賴都打包在一個文件裏,如果項目中有很多頁面依賴這個模塊,那麼rjs並不會把這個模塊提取出來作爲公共模塊,所以就會有很多複製性的內容,所以項目自然臃腫。

解決方案:使用webpack配合相應的loader,來完成模塊加載和構建的工作。

2、老項目爲什麼模塊化的不足?

答:老項目的模塊化,僅僅體現在js層面,解決了模塊引用的問題,但在開發方式上,依然可以看做是過程式的,這樣的結果就導致了項目的難擴展和難維護,讓開發人員在與產品汪的對峙中,並不從容。

解決方案:Vue.js能夠很好的解決組件化的問題,配合 Vue.js 官方提供的 vue-loader 能夠很好的結合webpack做組件化的開發架構。

3、如何避免手動管理DOM?

答:如果你在做數據展示這一塊的開發工作,相信你一定體會頗深,發送http請求到服務端,拿到返回的數據後手動渲染DOM至頁面,這是最原始的開發方式,無非再加一個模板引擎之類的,但最終還是避免不了手動渲染,如果頁面邏輯複雜,比如給你來一個翻頁的功能,再來一個篩選項,估計你會覺得世界並不那麼美好。

解決方案:MVVM模式能夠很好的解決這個問題,而Vue.js的核心也是MVVM。

webpack

你肯定聽說過webpack,如果直接對你描述什麼是webpack你可能感受不到他的好處,那麼在這之前,我相信你肯定使用過gulp或者grunt,如果你沒使用過也可以,至少你要聽說過並且知道gulp和grunt是幹什麼的,假如這個你還不清楚,那麼你並不是一個合格的前端開發人員,這篇文章也不適合你,你可以從基礎的地方慢慢學起。

gulp和grunt對於每一個前端開發人員應該是不陌生的,它們爲前端提供了自動化構建的能力,並且有自己的生態圈,有很多插件,使得我們告別刀耕火種的時代,但是它們並沒有解決模塊加載的問題,比如我們之前的項目是使用gulp構建的,但是模塊化得工作還是要靠require和rjs來完成,而gulp除了完成一些其他任務之外,就變成了幫助我們免除手動執行命令的工具了,別無它用。

而webpack就不同了,webpack的哲學是一切皆是模塊,無論是js/css/sass/img/coffeejs/ttf….等等,webpack可以使用自定義的loader去把一切資源當做模塊加載,這樣就解決了模塊依賴的問題,同時,利用插件還可以對項目進行優化,由於模塊的加載和項目的構建優化都是通過webpack一個”人“來解決的,所以模塊的加載和項目的構建優化並不是無機分離的,而是有機的結合在一起的,是一個組合的過程,這使得webpack在這方面能夠完成的更出色,這也是webpack的優勢所在。

如果你看不懂上面的描述,沒關係,你只需要知道一下幾點:

1、過去使用require和rjs等進行模塊加載的方式,可以替換爲webpack提供的指定loader去完成,你也可以自己開發加載特定資源的loader。
2、過去使用gulp和grunt完成項目構建優化的方式,可以替換成webpack提供的插件和特定的配置去完成。
3、由於模塊的加載和項目的構建優化有機的結合,所以webpack能夠更好的完成這項工作
4、並不是說有了webpack就淘汰的gulp等,有些特定的任務,還是要使用gulp去自定義完成的。但是不保證webpack的未來發展趨勢會怎麼樣。

最後,給大家分享一個官方的教程,這個教程的最開始有坑的地方,如果讀者遇到了坑,可以在這裏給我留言,我會爲大家解答,不過總體來講,這個教程適合入門,唯一不足的就是教程是英文的,英文的也不用怕,本人的英語沒過四級,但是現在依然能夠看得懂英文技術文章。教程鏈接:http://blog.madewithlove.be/post/webpack-your-bags/

Vue.js

Vue.js是一個MVVM模式的框架,如果讀者有angular經驗,一定能夠很快入門Vue的,那麼問題來了,爲什麼使用Vue而不用angular,
首先,Vue的體積小,輕量在移動端開發始終是一個不可忽略的話題,其次,Vue在實現上與angular有本質的區別,讀者可以通過下面兩個鏈接來了解:

1、Vue的變化追蹤和計算屬性的區別等

2、Vue 與 angular 及 react 等框架的對比

3、第三點就是Vue提供了webpack的loader —-> [vue-loader],使用它可以讓項目的組件化思想更加清晰

綜上所述,這就是選用Vue的原因

npm 和 nodejs

npm 的全稱是 nodejs包管理,現在越來越多的項目(包)都可以通過npm來安裝管理,nodejs是js運行在服務器端的平臺,它使得js的能力進一步提高,我們還要使用nodejs配合 webpack 來完成熱加載的功能。所以讀者最好有nodejs的開發經驗,如果有express的經驗更好。

讓我們一步一步從零搭建這個項目

首先新建一個目錄,名爲 myProject ,這是我們的項目目錄。然後執行一些基本的步驟,比如 npm init 命令,在我們的項目中生成 package.json 文件,這幾乎是必選的,因爲我們的項目要有很多依賴,都是通過npm來管理的,而npm對於我們項目的管理,則是通過package.json文件:

1
npm init

執行npm init之後,會提示你填寫一些項目的信息,一直回車默認就好了,或者直接執行 npm init -y 直接跳過詢問步驟
然後我們新建一個叫做 app 的目錄,這個是我們頁面模塊的目錄,再在app目錄下建立一個index目錄,假設這個是首頁模塊的目錄,然後再在index目錄下建立一個 index.html 文件和 index.js 文件,分別是首頁入口html文件和主js文件,然後再在index目錄下建立一個components目錄,這個目錄用作存放首頁組件模塊的目錄,因爲我們最終要實現組件化開發。這樣,當你完成上面的步驟後,你的項目看上去應該是這樣的:

接下來通過npm安裝項目依賴項:

1
npm install\
  webpack webpack-dev-server\
  vue-loader vue-html-loader css-loader vue-style-loader vue-hot-reload-api\
  babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015\
  babel-runtime@5\
  --save-dev

npm install vue --save

這個時候,你的package.json文件看起來應該是這樣的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"devDependencies": {
"babel-core": "^6.3.17",
"babel-loader": "^6.2.0",
"babel-plugin-transform-runtime": "^6.3.13",
"babel-preset-es2015": "^6.3.13",
"babel-runtime": "^5.8.34",
"css-loader": "^0.23.0",
"vue-hot-reload-api": "^1.2.2",
"vue-html-loader": "^1.0.0",
"vue-style-loader": "^1.0.0",
"vue-loader": "^7.2.0",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
},
"dependencies": {
"vue": "^1.0.13"
},

我們安裝了 babel 一系列包,用來解析ES6語法,因爲我們使用ES6來開發項目,如果你不瞭解ES6語法,建議你看一看阮老師的教程,然後我們安裝了一些loader包,比如css-loader/vue-loader等等,因爲webpack是使用這些指定的loader去加載指定的文件的。

另外我們還使用 npm install vue –save 命令安裝了 vue ,這個就是我們要在項目中使用的vue.js,我們可以直接像開發nodejs應用一樣,直接require(‘vue’);即可,而不需要通過script標籤引入,這一點在開發中很爽。

安裝完了依賴,編輯以下文件並保存到相應位置:

1、index.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="zh">
<head>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>首頁</title>
</head>
<body>
<!-- vue的組件以自定義標籤的形式使用 -->
<favlist></favlist>
</body>
</html>

2、index.js文件:

1
2
3
4
5
6
7
import Vue from 'Vue'
import Favlist from './components/Favlist'

new Vue({
el: 'body',
components: { Favlist }
})

3、在components目錄下新建一個 Favlist.vue 文件,作爲我們的第一個組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div v-for="n in 10">div</div>
</template>

<script>
export default {
data () {
return {
msg: 'Hello World!'
}
}
}
</script>

<style>
html{
background: red;
}
</style>

要看懂上面的代碼,你需要了解vue.js,假如你看不懂也沒關係,我們首先在index.html中使用了自定義標籤(即組件),然後在index.js中引入了Vue和我們的Favlist.vue組件,Favlist.vue文件中,我們使用了基本的vue組件語法,最後,我們希望它運行起來,這個時候,我們就需要webpack了。

在項目目錄下新建 build 目錄,用來存放我們的構建相關的代碼文件等,然後在build目錄下新建 webpack.config.js 這是我們的webpack配置文件,webpack需要通過讀取你的配置,進行相應的操作,類似於gulpfile.js或者gruntfile.js等。

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
// nodejs 中的path模塊
var path = require('path');

module.exports = {
// 入口文件,path.resolve()方法,可以結合我們給定的兩個參數最後生成絕對路徑,最終指向的就是我們的index.js文件
entry: path.resolve(__dirname, '../app/index/index.js'),
// 輸出配置
output: {
// 輸出路徑是 myProject/output/static
path: path.resolve(__dirname, '../output/static'),
publicPath: 'static/',
filename: '[name].[hash].js',
chunkFilename: '[id].[chunkhash].js'
},
module: {

loaders: [
// 使用vue-loader 加載 .vue 結尾的文件
{
test: /\.vue$/,
loader: 'vue'
}
]
}
}

上例中,相信你已經看懂了我的配置,入口文件是index.js文件,配置了相應輸出,然後使用 vue-loader 去加載 .vue 結尾的文件,接下來我們就可以構建項目了,我們可以在命令行中執行:

1
webpack --display-modules --display-chunks --config build/webpack.config.js

通過webpack命令,並且通過 –config 選項指定了我們配置文件的位置是 ‘build/webpack.config.js’,並通過 –display-modules 和 –display-chunks 選項顯示相應的信息。如果你執行上面的命令,可能得到下圖的錯誤:

錯誤提示我們應該選擇合適的loader去加載這個 ‘./app/index/index.js’ 這個文件,並且說不期望index.js文件中的標識符(Unexpected token),這是因爲我們使用了ES6的語法 import 語句,所以我們要使用 babel-loader 去加載我們的js文件,在配置文件中添加一個loaders項目,如下:

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
// nodejs 中的path模塊
var path = require('path');

module.exports = {
// 入口文件,path.resolve()方法,可以結合我們給定的兩個參數最後生成絕對路徑,最終指向的就是我們的index.js文件
entry: path.resolve(__dirname, '../app/index/index.js'),
// 輸出配置
output: {
// 輸出路徑是 myProject/output/static
path: path.resolve(__dirname, '../output/static'),
publicPath: 'static/',
filename: '[name].[hash].js',
chunkFilename: '[id].[chunkhash].js'
},
module: {

loaders: [
// 使用vue-loader 加載 .vue 結尾的文件
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel?presets=es2015',
exclude: /node_modules/
}
]
}
}

現在再運行構建命令 : ‘webpack –display-modules –display-chunks –config build/webpack.config.js’

sorry,不出意外,你應該得到如下錯誤:

它說沒有發現 ‘./components/Favlist’ 模塊,而我們明明有 ./components/Favlist.vue 文件,爲什麼它沒發現呢?它瞎了?其實是這樣的,當webpack試圖去加載模塊的時候,它默認是查找以 .js 結尾的文件的,它並不知道 .vue 結尾的文件是什麼鬼玩意兒,所以我們要在配置文件中告訴webpack,遇到 .vue 結尾的也要去加載,添加 resolve 配置項,如下:

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
// nodejs 中的path模塊
var path = require('path');

module.exports = {
// 入口文件,path.resolve()方法,可以結合我們給定的兩個參數最後生成絕對路徑,最終指向的就是我們的index.js文件
entry: path.resolve(__dirname, '../app/index/index.js'),
// 輸出配置
output: {
// 輸出路徑是 myProject/output/static
path: path.resolve(__dirname, '../output/static'),
publicPath: 'static/',
filename: '[name].[hash].js',
chunkFilename: '[id].[chunkhash].js'
},
resolve: {
extensions: ['', '.js', '.vue']
},
module: {

loaders: [
// 使用vue-loader 加載 .vue 結尾的文件
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel?presets=es2015',
exclude: /node_modules/
}
]
}
}

這樣,當我們去加載 ‘./components/Favlist’ 這樣的模塊時,webpack首先會查找 ./components/Favlist.js 如果沒有發現Favlist.js文件就會繼續查找 Favlist.vue 文件,現在再次運行構建命令,我們成功了,這時我們會在我們的輸出目錄中看到一個js文件:

之所以會這樣輸出,是因爲我們的 webpack.config.js 文件中的輸出配置中指定了相應的輸出信息,這個時候,我們修改 index.html ,將輸出的js文件引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="zh">
<head>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>首頁</title>
</head>
<body>
<!-- vue的組件以自定義標籤的形式使用 -->
<favlist></favlist>

<script src="../../output/static/main.ce853b65bcffc3b16328.js"></script>
</body>
</html>

然後用瀏覽器打開這個頁面,你可以看到你寫的代碼正確的執行了。

那麼問題來了,難道我們每次都要手動的引入輸出的js文件嗎?因爲每次構建輸出的js文件都帶有 hash 值,如 main.ce853b65bcffc3b16328.js,就不能更智能一點嗎?每次都自動寫入?怎麼會不可能,否則這東西還能火嗎,要實現這個功能,我們就要使用webpack的插件了,html-webpack-plugin插件,這個插件可以創建html文件,並自動將依賴寫入html文件中。

首先安裝 html-webpack-plugin 插件:

1
npm install html-webpack-plugin --save-dev

然後在修改配置項:

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
// nodejs 中的path模塊
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
// 入口文件,path.resolve()方法,可以結合我們給定的兩個參數最後生成絕對路徑,最終指向的就是我們的index.js文件
entry: path.resolve(__dirname, '../app/index/index.js'),
// 輸出配置
output: {
// 輸出路徑是 myProject/output/static
path: path.resolve(__dirname, '../output/static'),
publicPath: 'static/',
filename: '[name].[hash].js',
chunkFilename: '[id].[chunkhash].js'
},
resolve: {
extensions: ['', '.js', '.vue']
},
module: {

loaders: [
// 使用vue-loader 加載 .vue 結尾的文件
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel?presets=es2015',
exclude: /node_modules/
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: '../index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
]
}

然後再次執行構建命令,成功之後,看你的輸出目錄,多出來一個index.html文件,雙擊它,代碼正確執行,你可以打開這個文件查看一下,webpack自動幫我們引入了相應的文件。

問題繼續來了,難道每次我們都要構建之後才能查看運行的代碼嗎?那豈不是很沒有效率,別擔心,webpack提供了幾種方式,進行熱加載,在開發模式中,我們使用這種方式來提高效率,這裏要介紹的,是使用 webpack-dev-middleware中間件和webpack-hot-middleware中間件,首先安裝兩個中間件:

1
npm install webpack-dev-middleware webpack-hot-middleware --save-dev

另外,還要安裝express,這是一個nodejs框架

1
npm install express --save-dev

在開始之前,我先簡單介紹一下這兩個中間件,之所以叫做中間件,是因爲nodejs的一個叫做express的框架中有中間件的概念,而這兩個包要作爲express中間件使用,所以稱它們爲中間件,那麼他們能幹什麼呢?

1、webpack-dev-middleware

我們之前所面臨的問題是,如果我們的代碼改動了,我們要想看到瀏覽器的變化,需要先對項目進行構建,然後才能查看效果,這樣對於開發效率來講,簡直就是不可忍受的一件事,試想我僅僅修改一個背景顏色就要構建一下項目,這尼瑪坑爹啊,好在有webpack-dev-middleware中間件,它是對webpack一個簡單的包裝,它可以通過連接服務器服務那些從webpack發射出來的文件,它有一下幾點好處:

1、不會向硬盤寫文件,而是在內存中,注意我們構建項目實際就是向硬盤寫文件。

2、當文件改變的時候,這個中間件不會再服務舊的包,你可以直接帥新瀏覽器就能看到最新的效果,這樣你就不必等待構建的時間,所見即所得。

下面我們在build目錄中創建一個 dev-server.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
// 引入必要的模塊
var express = require('express')
var webpack = require('webpack')
var config = require('./webpack.config')

// 創建一個express實例
var app = express()

// 調用webpack並把配置傳遞過去
var compiler = webpack(config)

// 使用 webpack-dev-middleware 中間件
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true,
chunks: false
}
})

// 註冊中間件
app.use(devMiddleware)

// 監聽 8888端口,開啓服務器
app.listen(8888, function (err) {
if (err) {
console.log(err)
return
}
console.log('Listening at http://localhost:8888')
})

此時,我們在項目根目錄運行下面的命令,開啓服務:

1
node build/dev-server.js

如果看到下圖所示,證明你的服務成功開啓了:

接下來打開瀏覽器,輸入:

1
http://localhost:8888/app/index/index.html

回車,如果不出意外,你應該得到一個404,如下圖:

我們要對我們的 webpack.config.js 配置文件做兩處修改:

1、將 config.output.publicPath 修改爲 ‘/‘:

1
2
3
4
5
6
7
output: {
// 輸出路徑是 myProject/output/static
path: path.resolve(__dirname, '../output/static'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[chunkhash].js'
},

2、將 plugins 中 HtmlWebpackPlugin 中的 filename 修改爲 ‘app/index/index.html’

1
2
3
4
5
6
7
plugins: [
new HtmlWebpackPlugin({
filename: 'app/index/index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
]

重啓服務,再刷新頁面,如果看到如下界面,證明你成功了:

但是這樣開發模式下的確是成功了,可是我們直接修改了 webpack.config.js 文件,這就意味着當我們執行 構建命令 的時候,配置變了,那麼我們的構建也跟着變了,所以,一個好的方式是,不去修改webpack.config.js文件,我們在build目錄下新建一個 webpack.dev.conf.js文件,意思是開發模式下要讀取的配置文件,並寫入一下內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var HtmlWebpackPlugin = require('html-webpack-plugin')
var path = require('path');
// 引入基本配置
var config = require('./webpack.config');

config.output.publicPath = '/';

config.plugins = [
new HtmlWebpackPlugin({
filename: 'app/index/index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

module.exports = config;

這樣,我們在dev環境下的配置文件中覆蓋了基本配置文件,我們只需要在dev-server.js中將

1
var config = require('./webpack.config')

修改爲:

1
var config = require('./webpack.dev.conf')

即可,然後,重啓服務,刷新瀏覽器,你應該得到同樣的成功結果,而這一次當我們執行構建命令:

1
webpack --display-modules --display-chunks --config build/webpack.config.js

並不會影響構建輸出,因爲我們沒有直接修改webpack.config.js文件。

現在我們已經使用 webpack-dev-middleware 搭建基本的開發環境了,但是我們並不滿足,因爲我們每次都要手動去刷新瀏覽器,所謂的熱加載,意思就是說能夠追蹤我們代碼的變化,並自動更新界面,甚至還能保留程序狀態。要完成熱加載,我們就需要使用另外一箇中間件 webpack-hot-middleware

2、webpack-hot-middleware

webpack-hot-middleware 只配合 webpack-dev-middleware 使用,它能給你提供熱加載。

它的使用很簡單,總共分4步:

1、安裝,我們上面已經安裝過了
2、在 webpack.dev.conf.js 配置文件中添加三個插件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

var HtmlWebpackPlugin = require('html-webpack-plugin')
var path = require('path');
var webpack = require('webpack');
// 引入基本配置
var config = require('./webpack.config');

config.output.publicPath = '/';

config.plugins = [
// 添加三個插件
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),

new HtmlWebpackPlugin({
filename: 'app/index/index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

module.exports = config;

3、在 webpack.config.js 文件中入口配置中添加 ‘webpack-hot-middleware/client’,如下:

1
entry: ['webpack-hot-middleware/client', path.resolve(__dirname, '../app/index/index.js')],

4、在 dev-server.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
// 引入必要的模塊
var express = require('express')
var webpack = require('webpack')
var config = require('./webpack.dev.conf')

// 創建一個express實例
var app = express()

// 調用webpack並把配置傳遞過去
var compiler = webpack(config)

// 使用 webpack-dev-middleware 中間件
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true,
chunks: false
}
})

// 使用 webpack-hot-middleware 中間件
var hotMiddleware = require('webpack-hot-middleware')(compiler)

// 註冊中間件
app.use(devMiddleware)
// 註冊中間件
app.use(hotMiddleware)

// 監聽 8888端口,開啓服務器
app.listen(8888, function (err) {
if (err) {
console.log(err)
return
}
console.log('Listening at http://localhost:8888')
})

ok,現在重啓的服務,然後修改 Favlist.vue 中的頁面背景顏色爲 ‘#000’:

1
2
3
4
5
<style>
html{
background: #000;
}
</style>

然後查看你的瀏覽器,是不是你還沒有刷新就已經得帶改變了?

那麼這樣就完美了嗎?還沒有,如果你細心,你會注意到,我們上面在第2步中修改了 webpack.config.js 這個基本配置文件,修改了入口配置,如下:

1
entry: ['webpack-hot-middleware/client', path.resolve(__dirname, '../app/index/index.js')],

這也會導致我們之前討論過的問題,就是會影響構建,所以我們不要直接修改 webpack.config.js 文件,我們還是在 webpack.dev.conf.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

var HtmlWebpackPlugin = require('html-webpack-plugin')
var path = require('path');
var webpack = require('webpack');
// 引入基本配置
var config = require('./webpack.config');

config.output.publicPath = '/';

config.plugins = [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
filename: 'app/index/index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

// 動態向入口配置中注入 webpack-hot-middleware/client
var devClient = 'webpack-hot-middleware/client';
Object.keys(config.entry).forEach(function (name, i) {
var extras = [devClient]
config.entry[name] = extras.concat(config.entry[name])
})

module.exports = config;

但是我們還是要講 webpack.config.js 文件中的入口配置修改爲多入口配置的方式,這個修改不會影響構建,所以無所謂:

1
2
3
entry: {
index: path.resolve(__dirname, '../app/index/index.js')
},

重啓你的服務,刷新一下瀏覽器,然後修改 Favlist.vue 中的背景色爲 green:

1
2
3
4
5
<style>
html{
background: green;
}
</style>

再次查看瀏覽器,發現可以熱加載。但是這樣就結束了嗎?還沒有,不信你修改 index.html 文件,看看會不會熱加載,實際上不會,你還是需要手動刷新頁面,爲了能夠當 index.html 文件的改動也能夠觸發自動刷新,我們還需要做一些工作。

第一步:在 dev-server.js 文件中監聽html文件改變事件,修改後的 dev-server.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
// 引入必要的模塊
var express = require('express')
var webpack = require('webpack')
var config = require('./webpack.dev.conf')

// 創建一個express實例
var app = express()

// 調用webpack並把配置傳遞過去
var compiler = webpack(config)

// 使用 webpack-dev-middleware 中間件
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true,
chunks: false
}
})

var hotMiddleware = require('webpack-hot-middleware')(compiler)

// webpack插件,監聽html文件改變事件
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
// 發佈事件
hotMiddleware.publish({ action: 'reload' })
cb()
})
})

// 註冊中間件
app.use(devMiddleware)
// 註冊中間件
app.use(hotMiddleware)

// 監聽 8888端口,開啓服務器
app.listen(8888, function (err) {
if (err) {
console.log(err)
return
}
console.log('Listening at http://localhost:8888')
})

從上面的代碼中可以看到,我們增加了如下代碼:

1
2
3
4
5
6
7
8
// webpack插件,監聽html文件改變事件
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
// 發佈事件
hotMiddleware.publish({ action: 'reload' })
cb()
})
})

這段代碼可能你看不懂,因爲這涉及到webpack插件的編寫,讀者可以參閱下面的連接:

webpack 插件doc1

webpack 插件doc2

在這段代碼中,我們監聽了 ‘html-webpack-plugin-after-emit’ 事件,那麼這個事件是從哪裏發射的呢?我們通過名字可知,這個事件應該和html-webpack-plugin這個插件有關,在npm搜索 html-webpack-plugin 插件,在頁面最底部我們可以發現如下圖:

我們可以看到,html-webpack-plugin 這個插件的確提供了幾個可選的事件,下面也提供了使用方法,這樣,我們就能夠監聽到html文件的變化,然後我們使用下面的代碼發佈一個事件:

1
hotMiddleware.publish({ action: 'reload' })

第二步:修改 webpack.dev.conf.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

var HtmlWebpackPlugin = require('html-webpack-plugin')
var path = require('path');
var webpack = require('webpack');
// 引入基本配置
var config = require('./webpack.config');

config.output.publicPath = '/';

config.plugins = [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
filename: 'app/index/index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

// var devClient = 'webpack-hot-middleware/client';
var devClient = './build/dev-client';
Object.keys(config.entry).forEach(function (name, i) {
var extras = [devClient]
config.entry[name] = extras.concat(config.entry[name])
})

module.exports = config;

我們修改了devClient變量,將 ‘webpack-hot-middleware/client’ 替換成 ‘./build/dev-client’,最終會導致,我們入口配置會變成下面這樣:

1
2
3
4
5
6
entry: {
index: [
'./build/dev-client',
path.resolve(__dirname, '../app/index/index.js')
]
},

第三步:新建 build/dev-client.js 文件,並編輯如下內容:

1
2
3
4
5
6
7
8
var hotClient = require('webpack-hot-middleware/client')

// 訂閱事件,當 event.action === 'reload' 時執行頁面刷新
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

這裏我們除了引入 ‘webpack-hot-middleware/client’ 之外訂閱了一個事件,當 event.action === ‘reload’ 時觸發,還記得我們在 dev-server.js 中發佈的事件嗎:

1
hotMiddleware.publish({ action: 'reload' })

這樣,當我們的html文件改變後,就可以監聽的到,最終會執行頁面刷新,而不需要我們手動刷新,現在重啓服務,去嘗試能否對html文件熱加載吧。答案是yes。
好了,開發環境終於搞定了,下面我們再來談一談生產環境,也就是構建輸出,我們現在可以執行一下構建命令,看看輸出的內容是什麼,爲了不必每次都要輸入下面這條長命令:

1
webpack --display-modules --display-chunks --config build/webpack.config.js

我們在 package.js 文件中添加 “scripts” 項,如下圖:

這樣,我們就可以通過執行下面命令來進行構建,同時我們還增加了一條開啓開發服務器的命令:

1
2
3
4
// 構建
npm run build
// 開啓開發服務器
npm run dev

回過頭來,我們執行構建命令: npm run build,查看輸出內容,如下圖:

現在我們只有一個js文件輸出了,並沒有css文件輸出,在生產環境,我們希望css文件生成單獨的文件,所以我們要使用 extract-text-webpack-plugin 插件,安裝:

1
npm install extract-text-webpack-plugin --save-dev

然後在build目錄下新建 webpack.prod.conf.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

var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var path = require('path');
var webpack = require('webpack');
// 引入基本配置
var config = require('./webpack.config');

config.vue = {
loaders: {
css: ExtractTextPlugin.extract("css")
}
};

config.plugins = [
// 提取css爲單文件
new ExtractTextPlugin("../[name].[contenthash].css"),

new HtmlWebpackPlugin({
filename: '../index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

module.exports = config;

上面的代碼中,我們覆蓋了 webpack.config.js 配置文件的 config.plugins 項,並且添加了 config.vue 項,補血藥知道爲什麼,就是這麼用的,如果一定要知道爲什麼也可以,這需要你多去了解vue以及vue-loader的工作原理,這裏有連接點擊這裏
然後修改 package.json 文件中的 script 項爲如下:

1
2
3
4
"scripts": {
"build": "webpack --display-modules --display-chunks --config build/webpack.prod.conf.js",
"dev": "node ./build/dev-server.js"
},

我們使用 webpack.prod.conf.js 爲配置去構建,接下來執行:

1
npm run build

查看你的輸出內容,如下圖,css文件未提取出來了:

另外我們還可以添加如下插件在我們的 webpack.prod.conf.js 文件中,作爲生產環境使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
config.plugins = [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
// 壓縮代碼
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurenceOrderPlugin(),
// 提取css爲單文件
new ExtractTextPlugin("../[name].[contenthash].css"),
new HtmlWebpackPlugin({
filename: '../index.html',
template: path.resolve(__dirname, '../app/index/index.html'),
inject: true
})
];

大家可以搜索這些插件,瞭解他的作用,這篇文章要介紹的太多,所以我一一講解了。

到這裏實際上搭建的已經差不多了,唯一要做的就是完善,比如公共模塊的提取,如何加載圖片,對於第一個問題,如何提取公共模塊,我們可以使用 CommonsChunkPlugin 插件,在 webpack.prod.conf.js 文件中添加如下插件:

1
2
3
4
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
}),

然後在 webpack.config.js 文件中配置入口文件:

1
2
3
4
5
6
entry: {
index: path.resolve(__dirname, '../app/index/index.js'),
vendors: [
'Vue'
]
},

上面代碼的意思是,我們把Vue.js當做公共模塊單獨打包,你可以在這個數組中增加其他模塊,一起作爲公共模塊打包成一個文件,我們執行構建命令,然後查看輸出,如下圖,成功提取:

對於加載圖片的問題,我們知道,webpack的哲學是一切皆是模塊,然後通過相應的loader去加載,所以加載圖片,我們就需要使用到 url-loader,在webpack.config.js 文件中添加一個loader配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
loaders: [
// 使用vue-loader 加載 .vue 結尾的文件
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel?presets=es2015',
exclude: /node_modules/
},
// 加載圖片
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url',
query: {
limit: 10000,
name: '[name].[ext]?[hash:7]'
}
}
]

轉:http://hcysun.me/  適合新手研究這個。很不錯,涉及到的知識自己去看。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章