Vue + webpack 專案實踐
Vue.js 是一款極簡的 mvvm 框架,如果讓我用一個詞來形容它,就是 “輕·巧” 。如果用一句話來描述它,它能夠集眾多優秀逐流的前端框架之大成,但同時保持簡單易用。廢話不多說,來看幾個例子:
<script src="vue.js"></script> <div id="demo"> {{message}} <input v-model="message"> </div> <script> var vm = new Vue({ el: '#demo', data: { message: 'Hello Vue.js!' } }) </script>
首先,程式碼分兩部分,一部分是 html,同時也是檢視模板,裡面包含一個值為 message
的文字何一個相同值的輸入框;另一部分是 script,它建立了一個 vm 物件,其中繫結的 dom 結點是 #demo
,繫結的資料是 {message: 'Hello Vue.js'}
,最終頁面的顯示效果就是一段 Hello Vue.js
文字加一個含相同文字的輸入框,更關鍵的是,由於資料是雙向繫結的,所以我們修改文字框內文字的同時,第一段文字和被繫結的資料的 message
欄位的值都會同步更新——而這底層的複雜邏輯,Vue.js 已經全部幫你做好了。
再多介紹一點
我們還可以加入更多的 directive,比如:
<script src="vue.js"></script> <div id="demo2"> <img title="{{name}}" alt="{{name}}" v-attr="src: url"> <input v-model="name"> <input v-model="url"> </div> <script> var vm = new Vue({ el: '#demo2', data: { name: 'taobao', url: 'https://www.taobao.com/favicon.ico' } }) </script>
這裡的檢視模板加入了一個 <img>
標籤,同時我們看到了 2 個特性的值都寫作了 。這樣的話,圖片的
title
和 alt
特性值就都會被繫結為字串 'taobao'
。
如果想繫結的特性是像 img[src]
這樣的不能在 html 中隨意初始化的 (可能預設會產生預期外的網路請求),沒關係,有 v-attr="src: url"
這樣的寫法,把被繫結的資料裡的 url
同步過來。
沒有介紹到的功能還有很多,推薦大家來我(發起並)翻譯的Vue.js 中文文件
web 元件化
最後要介紹 Vue.js 對於 web 元件化開發的思考和設計
如果我們要開發更大型的網頁或 web 應用,web 元件化的思維是非常重要的,這也是今天整個前端社群長久不衰的話題。
Vue.js 設計了一個 *.vue
格式的檔案,令每一個元件的樣式、模板和指令碼集合成了一整個檔案, 每個檔案就是一個元件,同時還包含了元件之間的依賴關係,麻雀雖小五臟俱全,整個元件從外觀到結構到特性再到依賴關係都一覽無餘 :
並且支援預編譯各種方言:
這樣再大的系統、在複雜的介面,也可以用這樣的方式庖丁解牛。當然這種元件的寫法是需要編譯工具才能最終在瀏覽器端工作的,下面會提到一個基於 webpack 的具體方案。
小結
從功能角度,template, directive, data-binding, components 各種實用功能都齊全,而 filter, computed var, var watcher, custom event 這樣的高階功能也都洋溢著作者的巧思;從開發體驗角度,這些設計幾乎是完全自然的,沒有刻意設計過或欠考慮的感覺,只有個別不得已的地方帶了自己框架專屬的 v-
字首。從效能、體積角度評估,Vue.js 也非常有競爭力!
介紹 webpack
webpack 是另一個近期發現的好東西。它主要的用途是通過 CommonJS 的語法把所有瀏覽器端需要釋出的靜態資源做相應的準備,比如資源的合併和打包。
舉個例子,現在有個指令碼主檔案 app.js
依賴了另一個指令碼 module.js
// app.js
var module = require('./module.js')
... module.x ...
// module.js
exports.x = ...
則通過 webpack app.js bundle.js
命令,可以把 app.js
和 module.js
打包在一起並儲存到 bundle.js
同時 webpack 提供了強大的 loader 機制和 plugin 機制,loader 機制支援載入各種各樣的靜態資源,不只是 js 指令碼、連 html, css, images 等各種資源都有相應的 loader 來做依賴管理和打包;而 plugin 則可以對整個 webpack 的流程進行一定的控制。
比如在安裝並配置了 css-loader 和 style-loader 之後,就可以通過 require('./bootstrap.css')
這樣的方式給網頁載入一份樣式表。非常方便。
webpack 背後的原理其實就是把所有的非 js 資源都轉換成 js (如把一個 css 檔案轉換成“建立一個 style
標籤並把它插入 document
”的指令碼、把圖片轉換成一個圖片地址的 js 變數或 base64 編碼等),然後用 CommonJS 的機制管理起來。一開始對於這種技術形態我個人還是不太喜歡的,不過隨著不斷的實踐和體驗,也逐漸習慣並認同了。
最後,對於之前提到的 Vue.js,作者也提供了一個叫做 vue-loader 的 npm 包,可以把 *.vue
檔案轉換成 webpack 包,和整個打包過程融合起來。所以有了 Vue.js、webpack 和 vue-loader,我們自然就可以把它們組合在一起試試看!
專案實踐流程
回到正題。今天要分享的是,是基於上面兩個東西:Vue.js 和 webpack,以及把它們串聯起來的 vue-loader
Vue.js 的作者以及提供了一個基於它們三者的專案示例 (連結已失效)。而我們的例子會更貼近實際工作的場景,同時和團隊之前總結出來的專案特點和專案流程相吻合。
目錄結構設計
<components>
元件目錄,一個元件一個.vue
檔案a.vue
b.vue
<lib>
如果實在有不能算元件,但也不來自外部 (tnpm) 的程式碼,可以放在這裡foo.css
bar.js
<src>
主應用/頁面相關檔案app.html
主 htmlapp.vue
主 vueapp.js
通常做的事情只是var Vue = require('vue'); new Vue(require('./app.vue'))
<dist>
(ignored)<node_modules>
(ignored)gulpfile.js
設計專案打包/監聽等任務package.json
記錄專案基本資訊,包括模組依賴關係README.md
專案基本介紹
打包
通過 gulpfile.js
我們可以設計整套基於 webpack 的打包/監聽/除錯的任務
在 gulp-webpack 包的官方文件裡推薦的寫法是這樣的:
var gulp = require('gulp');
var webpack = require('gulp-webpack');
var named = require('vinyl-named');
gulp.task('default', function() {
return gulp.src(['src/app.js', 'test/test.js'])
.pipe(named())
.pipe(webpack())
.pipe(gulp.dest('dist/'));
});
我們對這個檔案稍加修改,首先加入 vue-loader
tnpm install vue-loader --save
.pipe(webpack({
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
}
}))
其次,把要打包的檔案列表從 gulp.src(...)
中抽出來,方便將來維護,也有機會把這個資訊共享到別的任務
var appList = ['main', 'sub1', 'sub2']
gulp.task('default', function() {
return gulp.src(mapFiles(appList, 'js'))
...
})
/**
* @private
*/
function mapFiles(list, extname) {
return list.map(function (app) {return 'src/' + app + '.' + extname})
}
現在執行 gulp
命令,相應的檔案應該就打包好並生成在了 dist
目錄下。然後我們在 src/*.html
中加入對這些生成好的 js
檔案的引入:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main</title>
</head>
<body>
<div id="app"></div>
<script src="../dist/main.js"></script>
</body>
</html>
用瀏覽器開啟 src/main.html
這時頁面已經可以正常工作了
加入監聽
監聽更加簡單,只要在剛才 webpack(opt)
的引數中加入 watch: true
就可以了。
.pipe(webpack({
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
},
watch: true
}))
當然最好把打包和監聽設計成兩個任務,分別起名為 bundle
和 watch
:
gulp.task('bundle', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig()))
.pipe(gulp.dest('dist/'))
})
gulp.task('watch', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig({watch: true})))
.pipe(gulp.dest('dist/'))
})
/**
* @private
*/
function getConfig(opt) {
var config = {
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
}
}
if (!opt) {
return config
}
for (var i in opt) {
config[i] = opt
}
return config
}
現在你可以不必每次修改檔案之後都執行 gulp bundle
才能看到最新的效果,每次改動之後直接重新整理瀏覽器即可。
除錯
打包好的程式碼已經不那麼易讀了,直接在這樣的程式碼上除錯還是不那麼方便的。這個時候,webpack + vue 有另外一個現成的東西:source map 支援。為 webpack 加入這個配置欄位 devtool: 'source-map'
:
var config = {
module: {
loaders: [
{ test: /.vue$/, loader: ‘vue’}
]
},
devtool: ‘source-map’
}
再次執行 gulp bundle
或 gulp watch
試試看,是不是開發者工具裡 debug 的時候,可以追蹤斷點到原始碼了呢:)
完整的 javascript 程式碼如下:
var gulp = require('gulp')
var webpack = require('gulp-webpack')
var named = require('vinyl-named')
var appList = ['main']
gulp.task('default', ['bundle'], function() {
console.log('done')
})
gulp.task('bundle', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig()))
.pipe(gulp.dest('dist/'))
})
gulp.task('watch', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig({watch: true})))
.pipe(gulp.dest('dist/'))
})
/**
* @private
*/
function getConfig(opt) {
var config = {
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
},
devtool: 'source-map'
}
if (!opt) {
return config
}
for (var i in opt) {
config[i] = opt[i]
}
return config
}
/**
* @private
*/
function mapFiles(list, extname) {
return list.map(function (app) {return 'src/' + app + '.' + extname})
}
最後,杜拉拉不如紫羅蘭
做出一個 vue + webpack 的 generator,把這樣的專案體驗分享給更多的人。目前我基於團隊內部在使用的輕量級腳手架工具寫了一份名叫 just-vue
的 generator,目前這個 generator 還在小範圍試用當中,待比較成熟之後,再分享出來
總結
其實上面提到的 just-vue
腳手架已經遠不止文章中介紹的東西了, 我們在業務落地的“最後一公里”做了更多的沉澱和積累,比如自動圖片上傳與畫質處理、rem單位自動換算、服務端/客戶端/資料埋點介面的梳理與整合、自動化 htmlone 打包與 awp 釋出等等。它們為支援業務的開發者提供了更簡單高效的工作體驗。 篇幅有限,更多內容我也希望將來有機會再多分享出來。
最後再次希望大家如果有興趣的話可以來玩一下,無線前端組內的同學我都願意提供一對一入門指導:)
Just Vue!