如何不編譯直接在前端瀏覽器使用Vue.js插件?以vue-slim-tabs為例 / How to use Vue.js Plugins in Browser Without Module Bundler?
嗨~呦~~ 這裡是煩惱要不要用Vue.js插件的布丁。
說到網頁前端MVVM框架Vue.js,大部分時候都是以搭配後端編譯工具 (像是Webpack之類) 為前提來安裝、配置。不過實際上,很多時候Vue.js、單檔案組件(single file component)、以及它們的插件可以不需透過編譯,直接在網頁瀏覽器上執行。
這邊我以「vue-slim-tabs」為例子,它文件中的說明必須要靠編譯器才能運作。我分析vue-slim-tabs插件的原始碼,重新撰寫了使用該插件的index.html跟引用的單檔案組件vue-slim-tabs.vue。你可以點此看到實際運作的網頁。以下我來說明改寫的過程,希望對Vue.js的使用者有所幫助。
結果展示 / Demo
做完之後會產生以下三個檔案:
- index.html 主要網頁
- vue-slim-tabs.vue 使用vue-slim-tabs插件的單檔案組件
- default.css 單檔案組件引用的樣式檔
流程 / Process
我這篇是準備寫給完全不懂Vue.js的入門者閱讀的。操作這篇只需要有HTML跟JavaScript的基本知識,不會用到Node.js或編譯器Webpack之類的工具。整個編寫過程也只用純文字編輯器(例如Notepad++)、Google Chrome瀏覽器,以及架設一個簡單的http伺服器來展示網頁而已。
必須注意的是,在瀏覽器直接開啟網頁檔案是不能運作的。請準備一個http伺服器,例如XAMPP或其他可以呈現靜態網頁的伺服器都可以。
要在瀏覽器裡面使用Vue.js、它的插件、以及Vue.js的單檔案組件的話,從無到有會有以下幾個步驟:
- 建立網頁
- 引用Vue.js
- 安裝Vue.js的插件
- 建立單檔案組件
- 引用單檔案組件
讓我們一步一步慢慢來看吧。
STEP 1. 建立網頁 / Make a empty webpage
先讓我們建立一個簡單的網頁,命名為「index.html」:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
</body>
</html>
這個網頁內容什麼都沒有,就是一個起點。我們從這個起點開始一一加入Vue.js跟它的擴充套件。
STEP 2. 引用Vue.js / Include Vue.js
接著我們先決定要在網頁何處擺放Vue.js,以下紅字的程式碼是額外加入的部分:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
</body>
</html>
注意id="app",待會我們會用CSS選取器「#app」來設定Vue.js,請它把要顯示的元素擺在這裡。
再來我們引用Vue.js的程式碼。簡單練習的話,直接使用Vue.js提供直連的CDN就好了:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</body>
</html>
然後我們加入建立Vue的設定:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var app = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
其中「el: "#app"」這個設定就是請Vue.js把顯示元素擺在<div id="app"></div>裡面的意思。這時候這個<div>就會成為Vue的模板(template),可以使用Vue.js的語法。
到這邊為止,因為我們實際上並沒有在模板裡面寫上任何東西,所以網頁看起來還是空空如也。對Vue.js稍微有點認識的人來說,做到這一步應該不會有任何問題。讓我們繼續做下去吧。
STEP 3. 安裝Vue.js的插件 / Install Vue.js plugin
接下來我們要引用Vue.js的擴充套件,這邊以「vue-slim-tabs」為例子來做示範。
vue-slim-tabs的說明主要是寫給搭配編譯器的情境,對很多只做前端、不碰後端編譯器的人來說,做到這裡就會卡關。
分析插件的文件 / Explain plugin's document
我們先來看看vue-slim-tabs的文件中的使用方法吧。它在Usage裡面寫了兩段程式碼,第一段的架構是:
<template>
...
</template>
<script>
...
</script>
<style src="vue-slim-tabs/themes/default.css"></style>
這種包含<template>、<script>、以及<style>的寫法,表示它是Vue.js的單檔案組件(single file component)。它的寫法是給後端編譯器用的,我們不能在前端瀏覽器直接使用它。
後面講到單檔案組件的改寫時,我們會再回來看這段程式碼。現在我們先跳過這邊,看下一段。
import * as Tabs from 'vue-slim-tabs'
Vue.use(Tabs)
這邊用到的import是後端Node.js的語法,意思是引用「vue-slim-tabs」插件的程式碼,然後把它重新命名為「Tabs」,最後在「Vue.use(Tabs)」中把插件加入到Vue裡面。
Vue.use(Tabs)是前端瀏覽器也可以執行的語法,但前面的「import * as Tabs form 'vue-slim-tabs'」可就不是了。
前端瀏覽器基本上只接受「<script src="..."></script>」的語法,那我們可以這樣引用套件嗎?答案是可以的。
分析插件的原始碼 / Analyze source code of plugin
首先,我們要先找到插件未壓縮的原始碼。
在「vue-slim-tabs」套件文件中雖然提到了yarn的安裝法,但那是給後端Node.js用的,我們前端要用的是CDN。我試了一下,能夠提供直接連線的CDN是jsDelivr。我們要用裡面的vue-slim-tabs.js。
先讓我們來看看vue-slim-tabs.js原始碼的開頭,裡面有段語法是在處理插件對外呈現的方式,也就是要如何讓其他程式碼找到它的方法:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.VueSlimTabs = {}));
}(this, function (exports) { 'use strict';
注意到「global」屬性,在前端前端裡面,global通常就是指「window」。而這邊有提到「global.VueSlimTabs」,那通常就等於是「window.VueSlimTabs」,在前端網頁裡面可以省略window、更簡化為「VueSlimTabs」。
再來我們看到vue-slim-tabs.js最下面有提到install方法的地方:
exports.Tab = Tab;
exports.Tabs = Tabs;
exports.install = install;
這表示該插件可以用「Vue.use(VueSlimTabs)」的語法安裝。
其中Install的方法又會註冊兩個元件:
function install(Vue) {
Vue.component(Tabs.name, Tabs);
Vue.component(Tab.name, Tab);
}
這就表示我們安裝vue-slim-tabs之後,就能直接使用「tab」跟「tabs」元件。
真的是如此嗎?讓我們實際引用看看就知道了。
用<script>標籤引用插件 / Include plugin with script tag
讓我們回到剛剛編輯到一半的網頁,加入以下紅字的程式碼:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slim-tabs@0.4.0/dist/vue-slim-tabs.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
這段<script>就是引用前面找到的vue-slim-tabs插件CDN的網址。
接著我們要來確認插件是否有正確引用,到底是不是如上面所分析的,可以直接使用「VueSlimTabs」變數即可呢。
我們可以在開發人員工具的Console控制檯裡面,試著輸入「VueSlimTabs」看看。結果的確有回傳資料,沒有顯示錯誤訊息「VueSlimTabs is not defined」,就表示VueSlimTabs的確是我們要找的對象。
將插件安裝到Vue / Install plugin to Vue
確定vue-slim-tabs插件在用<script>加入網頁、並能夠成功用「VueSlimTabs」取得變數後,我們就可以來讓Vue.js使用該插件。請為原本的網頁加上以下紅字的語法:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slim-tabs@0.4.0/dist/vue-slim-tabs.js"></script>
<script>
Vue.use(VueSlimTabs)
var app = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
值得注意的是,根據黑暗執行緒「輕前端札記 - HTML + JS 引用 CDN Vue.js 套件」這篇的說明,不是所有插件都可以用這種方式安裝。有些插件需要自行手動註冊為組件,例如vuejs-datepicker。
到這邊為止,我們雖然初步引用插件的程式碼,不過我們的網頁依然一片空白。這是因為Vue只有做到準備使用插件的環境,但我們還沒告訴Vue要怎麼使用這個插件。
我們其實已經能夠在模板<div id="app"></div>直接使用<tabs>跟<tab>元件。不過大部分插件都會寫成單檔案組件的形式供人使用,那我們也來以元件(component)的形式來使用插件吧。
STEP 4. 建立單檔案組件 / Make a single file component (*.vue)
接下來我們要建立一個Vue.js的單檔案組件,裡面會定義模板<template>、邏輯<script>跟樣式<style>。我們先來看看vue-slim-tabs的文件裡面是怎麼寫得,挑出裡面是寫給後端的程式碼,把它改寫為給前端網頁瀏覽器使用的單檔案組件。
後端用的單檔案組件 / Single file component for Node.js
在vue-slim-tabs的文件中有提到單檔案組件的寫法:
<template>
<tabs>
<tab title="Vue">
This is Vue
</tab>
<tab title="React">
This is React
</tab>
<tab title="Svelte">
This is Svelte
</tab>
</tabs>
</template>
<script>
import { Tabs, Tab } from 'vue-slim-tabs'
export default {
components: {
Tabs, Tab
}
}
</script>
<!-- optionally use our default style -->
<style src="vue-slim-tabs/themes/default.css"></style>
在這個單檔案組件中,<template>並沒有前後端之分,這邊我們可以直接使用。但紅字的<script>跟藍字的<style>都是配合編譯器才能運作的寫法,我們不能直接使用。讓我們一步一步來改寫看看吧。
建立空白的單檔案組件 / Make a empty single file component
先讓我們建立一個空白的單檔案組件,檔案名稱叫做「vue-slim-tabs.vue」,檔案內容如下:
<template>
</template>
<script>
module.exports = {
}
</script>
<style>
</style>
請注意到<script>裡面我用的是「module.exports = {}」,這是給前端的寫法。
再來我們一步一步把vue-slim-tabs文件中的單檔案組件搬過來。
設定模板 / Setup <template>
前面有提到,vue-slim-tabs文件中單檔案組件的範本<template>是可以直接使用的,所以我們直接把它的<template>內容移到我們的檔案裡。請加入以下紅字的語法:
<template>
<tabs>
<tab title="Vue">
This is Vue
</tab>
<tab title="React">
This is React
</tab>
<tab title="Svelte">
This is Svelte
</tab>
</tabs>
</template>
<script>
module.exports = {
}
</script>
<style>
</style>
設定邏輯 / Setup <script>
在要處理<script>這一塊之前,我們先來看看原本單檔案組件裡面的<script>寫了什麼:
import { Tabs, Tab } from 'vue-slim-tabs'
export default {
components: {
Tabs, Tab
}
}
「import { Tabs, Tab } from 'vue-slim-tabs'」這行是為了從插件取得Tabs跟Tab兩個元件,以提供給下面components裡面的Tabs跟Tab使用。
不過我們在前面引用插件時已經取得VueSlimTabs,所以不需要在這裡使用「import」。而該插件在「Vue.use(VueSlimTabs)」的時候,已經就把「Tabs」跟「Tab」這兩個組件註冊成全域組件 (關於套件註冊請看Vue.js的Component Registration)。我們也不用在單檔案組件裡面設定components。如果加入components的話,反而會讓組件無法正常運作。因此,這整段程式碼其實可以完全省略。
在這一步中,我們的單檔案組件並沒有修改,綠字的<script>依然維持預設寫法:
<template>
<tabs>
<tab title="Vue">
This is Vue
</tab>
<tab title="React">
This is React
</tab>
<tab title="Svelte">
This is Svelte
</tab>
</tabs>
</template>
<script>
module.exports = {
}
</script>
<style>
</style>
設定樣式 / Setup <script>
最後我們要來處理<style>。原本單檔案組裡面的<style>是這樣寫的:
<style src="vue-slim-tabs/themes/default.css"></style>
看來,它是引用自己組件裡面的「vue-slim-tabs/themes/default.css」這個檔案。
在vue-slim-tabs的CDN裡面並沒有看到該檔案,不過從組件文件中我們可以找到它的在GitHub原始碼儲存庫egoist/vue-slim-tabs,從裡面就可以找到「vue-slim-tabs/themes/default.css」。
讓我們直接儲存這個檔案為「default.css」,然後在我們的單檔案組件中引用它。請把<style>改成以下紅字的語法:
<template>
<tabs>
<tab title="Vue">
This is Vue
</tab>
<tab title="React">
This is React
</tab>
<tab title="Svelte">
This is Svelte
</tab>
</tabs>
</template>
<script>
module.exports = {
}
</script>
<style src="default.css"></style>
到這邊為止,我們的單檔案組件「vue-slim-tabs.vue」就準備好了。
STEP 5. 使用單檔案組件 / Use single file component
讓我們回到index.html網頁上。我們雖然建立了單檔案組件vue-slim-tabs.vue,但要如何使用它呢?
引用http-vue-loader套件 / Include http-vue-loader
如果要在網頁引用單檔案組件,則是要透過http-vue-loader套件,我們很容易就可以找到http-vue-loader提供直接連線使用的CDN。關於http-vue-loader套件的介紹可以看「不需編譯也能載入 .vue 元件檔: 使用 http-vue-loader」這篇。
讓我們先讓網頁加入http-vue-loader套件,請加入以下紅字的語法:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slim-tabs@0.4.0/dist/vue-slim-tabs.js"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
<script>
Vue.use(VueSlimTabs)
var app = new Vue({
el: '#app',
data: {
},
})
</script>
</body>
</html>
註冊單檔案組件 / Register single file component
有了http-vue-loader之後,我們就能在Vue裡面設定components組件時,直接引用單檔案組件vue-slim-tabs.vue。請加入以下紅字的語法:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slim-tabs@0.4.0/dist/vue-slim-tabs.js"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
<script>
Vue.use(VueSlimTabs)
var app = new Vue({
el: '#app',
data: {
},
components: {
'vue-slim-tabs': httpVueLoader('./vue-slim-tabs.vue')
}
})
</script>
</body>
</html>
我們用httpVueLoader讀取單檔案組件vue-slim-tabls.vue,並且把它註冊成名為「vue-slim-tabs」的元件。
再來就是要在模板裡面插入「<vue-slim-tabs></vue-slim-tabs>」元件,請加入以下紅字的語法:
<!DOCTYPE html>
<html>
<head>
<title>Vue vue-slim-tabs DEMO</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
<vue-slim-tabs></vue-slim-tabs>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-slim-tabs@0.4.0/dist/vue-slim-tabs.js"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
<script>
Vue.use(VueSlimTabs)
var app = new Vue({
el: '#app',
data: {
},
components: {
'vue-slim-tabs': httpVueLoader('./vue-slim-tabs.vue')
}
})
</script>
</body>
</html>
這樣子就能在模板裡面,顯示單檔案組件vue-slim-tabs.vue的內容了。
在瀏覽器裡面重新讀取index.html網頁,你會發現vue-slim-tabs.vue的內容已經顯示在網頁上,而且可以正常運作。
接下來就可以修改vue-slim-tabs.vue的內容,讓它符合你的需求吧。
結語 / Wrap up
為什麼會寫這篇呢?這是因為早上看到黑暗執行緒寫了一篇「輕前端札記 - HTML + JS 引用 CDN Vue.js 套件」,裡面提到使用vue-slim-tab插件的時候卡關的問題。我就試著只用前端來使用Vue.js插件看看,一併把做法記錄一下。
這邊一併說明其他可能的問題:
我能直接使用Wepback編譯好的JavaScript嗎? / Could I use bundle JavaScript?
vue-slim-tabs有提供Demo網頁,做前端的人通常會覺得前端是沒有祕密的,我們可以分析網頁的內容,從中取得自己要的資訊。可惜的是,vue-slim-tabs的Demo網頁在編譯之後,似乎是無法直接使用的。
首先是這個網頁是用html-webpack-plugin套件產生,裡面可以看到它用了Webpack程式切割功能,把檔案分割成多個chunk。
它的JavaScript跟CSS都沒有提供Source Map,我們看到的都是編譯後的程式碼,完全無法瞭解它編譯前是什麼樣子。關於 Source Map,請看「JavaScript Source Map 详解」這篇。
雖然這種別人無法使用編譯結果的做法算是一種保護,不過對於要學習的人來說,確實是不太友善。
既然前端可以用了,那還需要後端編譯嗎? / Do we still need module bundle?
透過本篇的做法,我們可以將原本用於後端搭配編譯器的Vue.js插件直接搬到前端使用。那既然都可以直接在前端用了,我們還需要後端編譯嗎?
以最近我的使用感想來看,後端編譯還是能夠帶來許多好處:
- 能用較多語法:比起純CSS,我現在比較喜歡寫LESS。可是這好像不能配合http-vue-loader在瀏覽器直接編譯。
- 編譯後檔案數量較少:Webpack之類的編譯器主要的工作是把眾多檔案打包成單一檔案,此外還能做到壓縮、混淆、分割等功能。從前端處理檔案雖然比較簡單,但是卻不適合生產環境。
- 模組化開發:在前端最麻煩的問題是很容易受到其他引用檔案的汙染。這可能是重複宣告了相同名稱的函式,或是jQuery引用擴充功能後卻被其他的jQuery覆蓋。在後端編譯的時候,通常我們比較不用擔心這種問題。
後端編譯和前端直接使用兩者有不小的差別。透過這篇的改寫,你也會發現這兩者的寫法有不少的出入。因此,從一開始就得要決定你的專案是要用後端編譯,還是直接放在前端使用。不太能夠前期開發先用前端先寫來測試,後期部署生產環境時就直接拿到後端用。
考量到效率問題,最後大家還是會選擇用後端編譯。這可能是因為如此,這就造成了儘管Vue.js可以直接在前端執行,但大家仍然是配合Webpack之類的編譯器為主。
要用Vue.js的插件嗎? / Should I use Vue.js plugin?
老實說,我對Vue.js的插件其實瞭解的很少,也很少使用。這篇講Vue插件,我把它當作是一種學習。
我之前大多是直接使用Semantic UI,因為我覺得找一個統一的解決方案,比起個別找不同插件兜起來,好像看起來比較一致。不過Semantic UI有許多功能都會在元素上綁定事件,例如它的Tab若要順利運作,必須要先執行「$('.tabular.menu .item').tab();」這段程式碼。但Vue.js的模板會一直複寫,複寫後原本Semantic UI綁定的事件就會消失。
Semantic UI也有推出Vue插件版本Semantic UI Vue,原本用class來指定的樣式,在插件版本中則是改用組件替代,例如插件版本的Tab。我看了看覺得好像要重學很多東西,而且我也對原本的Semantic UI有些改寫,不太想要重新來過。所以目前還沒有深入瞭解Vue插件的用法。
也許繼續用Vue.js開發之後會有不同的想法吧?讓我們一步一步慢慢看下去。
那麼這次使用在前端使用Vue.js插件的教學就到這裡了。寫到最後,我有些問題想問問大家:
- 你最常用Vue.js的什麼插件呢?
- 什麼情況下你會使用插件?什麼情況下你會自己直接寫呢?
- 要把component翻譯做「組件」好呢?還是「元件」好呢?
歡迎在下面的留言處跟我們分享你的想法。大家的意見是我繼續分享的動力喔!如果你覺得我這篇實用的話,請幫我在AddThis分享工具按讚、將這篇分享到Facebook等社群媒體吧!
感謝你的耐心閱讀,我是布丁,讓我們下一篇見。