:::

Node.js前後端CORS網頁應用試作:AdonisJs聊天室 / Practice of Build a CORS Full Stack Web Application in Node.js: AdonisJs Chat

14-Node_js_CORS_AdonisJs_Practice_of.png

啊囉哈~~這裡是寫程式寫到整個人都變得優雅起來的布丁。

作為網站工程師,用JavaScript開發前端就像是日常便飯一樣。但前端都用JavaScript開發了,那後端為何不用Node.js一起開發呢?這個想法是從兩年前開始,這期間跌跌撞撞不斷摸索,直到最近才找到比較理想的方案,那就是後端使用AdonisJS作為伺服器與保存資料、前端使用Vue.js控制介面與邏輯,最後搭配Webpack編譯。

為了測試我需要的功能都能正常運作,我試作了一個跨網域資源共用(Cross-Origin Resource Sharining, CORS)情境下使用的網站外掛:AdonisJS聊天室。我寫這篇,是為了將到目前為止開發的心得告一個段落。以下將說明AdonisJS聊天室的功能、系統架構與技術,最後講講開發時遭遇的問題與想法。希望能夠為其他開發者提供一些解決問題的線索。


AdonisJS聊天室 / AdonisJS Chat

2019-1010-193317-SKHub-mn-RE-CRT.png

2019-1010-193409-OOO-pulipulichenggthub-pudding.png

AdonisJs聊天室是一個以JavaScript程式語言為主所開發的網頁應用。它的主要功能是用於外掛到任何網站上,在網站的指定區塊內呈現完整的聊天室功能。這種提供給網站添增上去的外掛功能,就是一種跨網域資源共用(Cross-Origin Resource Sharining, CORS)情境下的應用。

2019-1010-193443-Lorem-ipsum-or-sit-amet-consectetur.png

讓我們用這個網站來說明網站外掛的安裝。這是架設在 http://127.0.0.1:3000/ 的純HTML網頁。這個網頁只有兩段文字,文字本身是用假文章產生器製作,本身並沒有後端伺服器的功能。

為了讓這樣單純的HTML網頁添增聊天室的功能,我們先啟用AdonisJs聊天室的伺服器,然後在兩段文字中間插入來自 http://127.0.0.1:3333/ 伺服器的AdonisJS聊天室引用程式碼:

<script src="http://127.0.0.1:3333/spa/client-loader.js" async></script>

2019-1010-193542-Lorem-ipsum-or-sit-amet-consectetur.png

插入程式碼後,再重新讀取 http://127.0.0.1:3000/ 的純HTML網頁,這時候你就會像上圖一樣,兩段文字中間出現了聊天室的功能。

anime.gif

聊天室支援的功能包括了:登入、註冊、OAuth網站登入(GoogleGitHubInstagramFoursquare)、傳送訊息、傳送圖片、登出。此外,也可以連接到AdoniJs聊天室的管理功能。

2019-1010-201558-xz-HOME-SITEMAP-ABOUT-MENU-so-ii.png

因為AdonisJs聊天室是一種外掛形式的應用,它可以掛載到任何網頁上。上圖就是我把它掛在Blogger的畫面。但是因為我預設沒有申請有效的HTTPS憑證,所以AdonisJs聊天室只能掛在http的網頁上,在https網頁上就不能運作了。

AdonisJs聊天室的功能就這樣子而已。畢竟這只是我練習技術做出來的東西,並不是真的要拿來用的工具。儘管如此,AdonisJs聊天室是一套包含了前後端在內多種技術的綜合體。如果未來有遇到類似問題需要克服的話,這裡面有很多程式值得參考。

程式碼下載與安裝 / Download and Setup

2019-1010-204916-Pull-requests-Issues-Marketplace.png

AdonisJs聊天室的程式碼放在GitHub保存庫中。如果你也想要試著安裝的話,請參考README.md裡面的說明架設。

順利配置完成之後,你會啟動兩臺伺服器:

  • http://127.0.0.1:3333 : AdonisJs聊天室主要運作的伺服器
  • http://127.0.0.1:3000 :純HTML網頁,提供AdonisJs聊天室外掛使用。

2019-1010-205021-Lorem-ipsum-or-sit-amet-consectetur.png

配置完成之後,在瀏覽器打開  http://127.0.0.1:3000,你看到上圖畫面的話,就代表AdonisJs聊天室正常運作了。


系統架構與技術 / System architecture

2019-1010-210941-Client-Side-Server-Side-Front-End.png

AdonisJs聊天室的系統架構分成後端與前端。在上面的系統架構圖中,藍色的部分是AdonisJs聊天室主要的功能,綠色的部分則是要外掛上去的純HTML網頁。

AdonisJs聊天室的後端部分是以Node.js的後端伺服器AdonisJs框架為基礎,它是用來製作REST形式的Client API、對資料庫的控制、以及Admin管理介面的部分。

AdonisJs聊天室中也提供Client JS的靜態JavaScript程式碼給前端使用,不過它並不是由AdonisJs框架製作,而是由Vue.js為基礎搭配Webpack編譯而成。Client JS的工作是畫出前端介面、控制介面的操作邏輯,並且與後端的Client API互通資料,或是開啟後端的Admin管理介面。

雖然AdonisJs聊天室具備OAuth認證的功能,但這張系統架構圖並沒有畫出來。這是因為AdonisJs聊天室的預設安裝中,OAuth的功能並無法使用,需要額外設定才行。這點待會會聊到。

以下我們就從後端技術開始,一一聊聊我在AdonisJs聊天室開發過程中的一些想法吧。


後端:AdonisJs / Back end: AdonisJs

2019-1010-212545-JETS-Quick-Start-Guides-Recipes.png

(網頁截圖:AdonisJs)

AdonisJs是一套Node.js中的網頁框架。跟早期的Express.js相比,AdonisJs將許多功能優雅地包裝起來,並提供多種指令,方便開發者產生鷹架(scaffold)、資料庫的遷移(migration)等等。AdonisJs整個框架吸收了Ruby on Rails的精華,運作架構採用明確易懂的MVC模式呈現。

儘管AdonisJs框架也有許多約定優於配置的設計,一些小細節需要先學習那些單數、複數的約定才能使用。但好在AdonisJs的包裝做得很好,使得檔案目錄架構和程式碼十分容易理解,也易於調整和擴充。

2019-1011-050346.png

雖然整個伺服器的後端是用AdonisJs框架所架設的http伺服器提供服務,但我認為AdonisJs框架最派得上用場的地方,是上圖紅框中Client API的路由,以及Client API跟資料庫之間的互動。

底下讓我們更進一步來聊聊AdonisJs的特色吧。

簡單與漸進 / Simple and progressive

在談AdonisJs之前,我想先講一下我之前用過Express.js跟Feathers JS的感想。

express.jpg

(圖片來源:寫點科普,請給指教。)

在接觸Node.js開發網站應用時,我一開始是用Express.js。Express.js是很老牌且有豐富文件的框架,但是實在是太過自由,跟我之前在PHP時使用的CodeIgniterFat-Free Framework相比,用Express.js框架開發的程式碼混亂許多。

這可能是因為Express.js只專注於處理路由的邏輯問題(controller),但實際上我們在網頁應用中,還需要處理網頁介面(view)和管理資料(model)的問題。雖然許多人也有為Express.js開發出各種功能,但是各種功能各自兜起來的結果,造成程式碼更加的混亂。

key-image-horizontal.png

(圖片來源:Feathers)

後來繼續要用Node.js開發網頁應用的時候,我就在想:都已經2019年了,總有比較新的框架出現了吧。花時間研究了一下,我找來了這個看似相當新穎的框架:Feathers。Feathers號稱輕量框架,主打即時同步應用和開發REST API為主,這正符合我想要開發的系統需求。

generate-service.e4513510.png

(圖片來源:Feathers: Generating a service)

Feathers的起步文件中以架設聊天室作為入門教材,單以該文件架設的話,會覺得Feathers的確看起來很簡單。Feathers主打路由邏輯service的部分,它將管理資料庫的model包裝在service中,讓開發者專注於開發service。相較於AdonisJs,Feathers更強調用指令建立鷹架。它的指令還有多種選項搭配,這樣就能夠建立起具有不同基本功能的鷹架。

然而,這種做法也帶來了各種困擾。最主要的問題,就是鷹架所產生的程式碼有太多看不懂的地方。跟著起步文件的教學去修改,系統當然是能夠順利運作。不過一旦想要自己修改的話,各種問題立刻層出不窮。

其中一個相當基本、但是卻在官方文件找不到方法的問題,就是資料表之間的關聯(relationship或association)。起步文件中使用的NeDB是一種NoSQL資料庫,資料表的建立和修改不必特別宣告資料庫綱要(schema),而起步文件也省略了這一步。當我想要改用PostgreSQL這種關聯式資料庫的時候,起步文件的資料庫設定就會發生錯誤。最後我是在「Painless file upload using FeathersJs service」這篇文章找到了資料庫設定關聯的做法。改到最後雖然能夠運作,但改起來也不像文章說的無痛啊。

Feathers太多功能都需要花時間研究。我花了很多時間嘗試作出CORS (不要用預設的socketio-client,改用rest-client)跟HTTPS之後,最後決定放棄繼續深入研究Feathers,它真的太難用了。

2019-1010-224421-npm-adonisjs-cli-adonts-new-yardstick.png

(網頁截圖:AdonisJs: Quick Start)

捨棄Feathers後,我繼續研究其他後端框架,最後就找到了現在這個AdonisJs。上圖是AdonisJs快速上手的入門,這樣子就能建立起一個基本的網站。不過這種入門誰都會,倒也不是什麼特別厲害的地方。

2019-1010-225542-standard-AdonisJs-installation-looks.png

(網頁截圖:AdonisJs: Directory Structure)

讓我一開始就有好感的是它的目錄結構。AdonisJs的目錄結構區分得很容易理解,一開始主要修改的地方在 start 底下,應用程式的內容就在 app 底下,而設定檔就在 config 底下。

const Config = use('Config')
const appSecret = Config.get('app.appSecret')

這些程式碼擺在可以容易理解的位置,而且也是以容易理解的方式讓人使用。以設定 Configuration 為例子來說,上面這段程式碼就是在讀取「config/app.js」檔案中的appSecret屬性。

// app/Controllers/Http/Admin/UserController -> store()
Route.post(url, 'Admin/UserController.store')

這是Controller的用法,我們可以在 routes.js 裡面設定從什麼路徑對應到特定的 Controller 檔案中的方法 (method),用起來很直覺。

AdonisJs框架裡面的各個檔案所需要的程式碼看起來都不多,而且大部分都是不需要特別講解就能夠理解的功能。其中讓我最覺得優雅的,大概就是AdonisJs的資料庫管理吧。

資料庫管理ORM:Lucid / Object-Relational Mapping: Lucid

在AdonisJs裡面,資料庫綱要的建立是使用遷移功能(migration)。以下是users資料表中建立username、email、password三個欄位的做法:

'use strict'

const Schema = use('Schema')

class UsersSchema extends Schema {
  up () {
    this.create('users', (table) => {
      table.increments() table.string('username', 80).notNullable().unique() table.string('email', 254).notNullable().unique() table.string('password', 60).notNullable()
      table.timestamps()
    })
  }

  down () {
    this.drop('users')
  }
}

module.exports = UsersSchema

這種以程式碼控制資料庫綱要的做法,在很久以前就有不少開發者極力倡導。這樣的做法最主要的好處是,開發者只需要在程式裡面維護資料庫,不必特別在個別資料庫中用SQL語法進行設定。要知道各個資料庫的SQL語法略有不同,有時候還會遇到不同版本資料庫語法不同的問題,實在令人煩心。

我以前常常在維護系統不同版本的時候遇到資料表無法對應的問題。少了一個欄位、欄位名字變更,整個系統就無法使用。以前資料庫綱要的備份還原都只能依賴SQL指令,那實在是很麻煩的事情。因此看到AdonisJs這種用遷移功能管理資料庫的方法,就特別深得我心。

資料表建立之後,我們就能在AdonisJs裡面使用它的物件關聯對應工具:Lucid ORM。在這之前我用過Sequelize ORM,我得說Sequelize ORM的確很強大,但使用上卻相當繁瑣。相較之下,Lucid ORM採用了約定優於配置的做法,這讓很多時候我們不用預先設定就能直接使用,整個程式碼看起來相當簡潔。

HasMany_kkbac9.png

(圖片來源:AdonisJs: Relationships)

我以在Feathers遇到最痛苦的資料表關聯為例,上面的ERM中可以看到目前有兩個資料表:users跟posts。其中users中每一位user有許多的posts,可以看到posts資料表裡面有user_id這個外鍵(Foreign Key, FK)用來對應它是那位user。

如果我們要從user找到他撰寫的posts的話,要用hasMany寫法,對應這樣的資料表關係,在Lucid ORM的寫法是:

const Model = use('Model')

class User extends Model {
  posts () {
    return this.hasMany('App/Models/Post')
  }

}

module.exports = User

相反的,如果我們要從post找到它撰寫的user的話,要用belongsTo的寫法,在Lucid ORM裡面的寫法是:

const Model = use('Model')

class Post extends Model {
  user () {
    return this.belongsTo('App/Models/User')
  }

}

module.exports = Post

透過hasMany跟belongsTo這樣子語義的語法,讓可讀性提高了許多。

另一方面,Lucid ORM在使用上也很容易理解。如果我們要查詢user,條件是他所發表的post_title包含「hello」這串文字的話,語法如下:

let users = await User
  .query()
  .whereHas('posts', (builder) => {
    builder.where('post_title', 'like', '%hello%')
  })
  .fetch()

這樣就能優雅地處理資料表之間的關聯,而不必煩惱各種複雜的join用法。

此外,AdonisJs大量採用了ES2017的async/await寫法,這讓整個以非同步函數主宰的Node.js世界變得非常簡單明瞭,這樣子看起來才真的有2019年程式碼的現代感。關於async和await的用法,我在下面的axios會再繼續聊到它。

實用的認證系統 / Authentication

作為一個網站應用,使用者的登入和登出是基本的功能。AdonisJs特別將認證系統 Authentication 分離成為一個獨立功能,我們可以這樣子實作登入功能:

class UserController {

  async login ({ auth, request }) {
    const { email, password } = request.all()
    await auth.attempt(email, password)

    return 'Logged in successfully'
  }
}

真的是簡單易懂到一種境界。這個auth背後是直接對應到User Model,而設定檔都在 config/auth.js 裡面。相較之下,Feathers的認證系統就跟謎一樣複雜。

2019-1011-001534.png

除了網站內的登入之外,AdonisJs也有支援OAuth登入,它是使用Ally套件來實作這種社群認證 Social Authentication 的功能。Ally套件提供了Facebook、Github、Google、LinkedIn、Twitter、Instagram、Foursquare的OAuth認證整合功能。這七種網站試下來,我最後只有成功整合四種:

  • GitHub:申請與設定最簡單,從「Register a new OAuth application」網頁可以直接建立OAuth,然後在「OAuth Apps」這裡可以管理已經建立的服務。
  • Foursquare:申請時要從Foursquare Developers進入。註冊帳號似乎要用Facebook的身份,不過也能夠獨立申請帳號。註冊成功之後,剩下的申請都很簡單了。(註:我本來因為沒有成功在Foursqure註冊帳號而放棄它,所以前面截圖都沒出現Foursquare的按鈕。不過後來換了無痕視窗註冊新帳號後,Foursqure就能使用了。)
  • Instagram:申請與設定比GitHub複雜一點,但也夠簡單了。申請請從Instagram Developer介面進入,做法可以參考「[30apis] Day 28 : Instagram Platform API」這篇,但其實直接看介面摸索也不難啦。
  • Google:Google申請OAuth要從Google APIs進入,裡面手續蠻複雜的,但申請Google OAuth的人很多,網路上教學也不少,例如「使用Google OAuth 2.0 存取Google API」這篇。不過因為Google APIs持續有在改版,介面不會完全長得一樣,真的要申請的時候還是要自己摸索一下才行。

其他的服務我都沒能成功整合:

  • Facebook:申請時要從facebook for developers進入。申請設定是不難,但它要求必須要用https才行使用OAuth。我沒有購買憑證的打算,所以就略過Facebook。關於AdonisJs的HTTPS服務,下面會再聊到。
  • LinkedIn:申請時要從Linkedin Developers進入。申請憑證的過程很順利,不過實際上它跟AdonisJs的介接會失敗,出現了「Response code 410 (Gone)」的錯誤。我花了一些時間找不到解決方法,所以就放棄了LinkedIn。
  • Twitter:申請時要從Twitter Developer進入。不過要註冊成為Twitter的開發者,必須填寫相當多的資料。我填到一半就放棄了。

Ally似乎就只有提供這七種服務,目前沒有新增其他服務的途徑。我本來也想註冊Plurk OAuth來試試看,不過似乎Ally並沒有提供自行擴充的方法。

話說回來,儘管AdonisJs有提供OAuth認證整合的功能,不過實際上這不是我系統會用到的需求。這部分我就是試著做做看而已,並沒有深入鑽研。

檔案上傳 / Upload files

前面有提到Feathers無痛實作檔案上傳,但是實作起來卻還是很複雜的例子。這邊讓我們來看看AdonisJs是怎麼實作檔案上傳 File Uploads 的吧:

const Helpers = use('Helpers')

Route.post('upload', async ({ request }) => {
  const profilePic = request.file('profile_pic', {
    types: ['image'],
    size: '2mb'
  })

  await profilePic.move(Helpers.tmpPath('uploads'), {
    name: 'custom-name.jpg',
    overwrite: true
  })

  if (!profilePic.moved()) {
    return profilePic.error()
  }
  return 'File moved'
})

呃,就這樣?

我本來以為PHP檔案上傳已經夠簡單了。但PHP容易上傳的檔案功能,卻因為需要克服資安漏洞而必須做相當多的檢查。做完之後反而變得很複雜。

沒想到AdonisJs的檔案上傳不僅做好了檢查、檔案大小,連搬移檔案、重新命名、複寫等設定都如此清楚易懂。我把它跟認證系統結合後的程式碼寫在MessageController.upload()當中,實際上做起來也很簡單,出乎意料之外的簡單。

這也是我覺得AdonisJs寫起來很優雅的特點之一。

CORS的支援 / Enable Cross-Origin Resource Sharing 

AdonisJs對CORS的支援有獨立的設定。最主要是修改 config/cors.js 中設定。這邊要調整的參數有點多,請直接參考我的 cors.js 檔案即可。

再來要關閉 config/shield.js 中的CSRF Protection,設定檔請看shield.js。因為AdonisJs聊天室的運作機制就是會從不同網站上傳送資料到伺服器,而CSRF會擋掉POST等請求,所以我就將CSRF關閉不使用。關於CSRF攻擊手法,請參考「讓我們來談談 CSRF」這篇。

最後客戶端也要有對應的設定。我使用客戶端請求資料的函式庫axios中,必須讓它帶有cookie等憑證資料。這個做法是開啟「withCredentials: true」的設定,最推薦的做法是直接宣告它預設包含憑證,寫法如下:

import axios from 'axios'
axios.defaults.withCredentials = true

詳細做法可以參考我寫的AxiosHelper.js檔案。

https的支援 / https server

AdonisJs預設是用http伺服器,並不是啟用加密的https伺服器。如果要啟用https的話,可以參考「Using Https」這篇。我將https伺服器的啟動方法額外寫成了server-https.js檔案,我們可以用「npm run start-https」指令啟動https伺服器,它預設會以連結埠4444啟動,可以用 https://127.0.0.1:4444 連線到https伺服器。

值得注意的是,我們其實通常會需要同時啟動http和https兩種伺服器。為此我使用了concurrently套件來同時啟動兩個不同的AdonisJs伺服器,這部分的寫法請看package.json的script

HTTPS憑證 / HTTPS certificate

如果只是要測試的話,可以用pem套件製作自簽憑證(self-signed certificate),以此直接啟動https伺服器。這種做法不像使用OpenSSL開發使用自簽憑證這樣麻煩,使用上簡單許多。我將使用自簽憑證啟動https伺服器的做法寫在server-https.js檔案中。

此外,http和https兩種伺服器必須走不同的連結埠(port)。在AdonisJs裡面,連結埠設定是放在.env檔案之中。為了讓server-https.js伺服器使用不同的設定檔,我額外建立了.env.https檔案,然後用以下程式讀取它:

require('dotenv').config({ path: '.env.https' })

這部分的程式碼也在server-https.js裡面。

自簽憑證的https伺服器終究只能拿來測試用。如果要長久服務的話,則必須要申請有效的HTTPS憑證才行。現在是有像是SSL for Free的免費服務,可以用來申請Let's Encrypt的HTTPS憑證,不過這個90天就會失效。

2019-1011-014244.png

如果你有申請好的HTTPS憑證,請放在./config/cert資料夾中,並將憑證檔案命名為「certificate.crt」、私密金鑰檔案命名為「private.key」命名。這樣子server-https.js就會以這份HTTPS憑證啟動HTTPS伺服器。

豐富的文件 / Documents

AdonisJs的文件相當完整。網站開發會用到的功能,在文件中幾乎都找得到。此外,它的討論區也有相當多提問和解答,我有不少文件中沒找到的做法,在Google到它討論區中找到了解答。

擁有大量且活躍的使用者,是挑選框架的重要參考依據之一。相較之下,我在研究Feathers的時候,常常很難找到想要的功能,研究起來很令人洩氣。

儘管如此,多虧AdonisJs大部分程式都相當容易理解和修改,實際上開發時許多時候都不需要仰賴文件,可以直接嘗試自己想要的修改方式,而大多時候它都能如我預期般的運作。這種感覺真的很不錯。

其他沒用到的功能 / Other feathers

AdonisJs還有大量我沒用到的功能,其中一個大概就是前端views的部分。AdonisJs似乎也有好用的模板引擎,不過因為我主要是在CORS情境下使用Vue.js處理前端,所以這部分功能我都沒用到。

這次AdonisJs聊天室只是將AdonisJs作為REST API伺服器,它的功能並不多。未來如果要開發複雜應用的話,我會想要嘗試AdonisJs的單元測試 Testing。看起來似乎沒有很複雜,可以用它來測試單一REST API功能是否正常運作。

小結 / Before next

好啦,寫到這裡,字數居然已經破萬字了。不過我們才聊完後端的AdonisJs框架而已呢。接下來讓我們轉換心情,來看看前端的部分吧。


前端:Vue.js / Front end: Vue.js

Vue.JS_Logo_transparent_PNG.png

(圖片來源:StickPNG)

Vue.js是知名的前端MVVM框架。它提供了資料綁定、HTML自訂屬性、單一頁面的hash路由、翻譯功能i18n等等好用的功能。跟AngularJS相比,Vue.js可說是簡單易懂、架構清晰。另一方面,Vue.js又比幾乎無框架的Svelte容易理解。如果要開發單一頁面應用程式的話,強力推薦一定要使用Vue.js這種MVVM框架。

2019-1011-050309.png

在AdonisJs聊天室應用中,就如上圖系統架構圖的紅框所示,Vue.js主要是載入到前端純HTML網頁裡面運作的功能。

雖然我在「工具開發閒聊:從AutoIT到Electron」中提到我逐漸採用Vue.js的事情,不過似乎還沒有深入聊聊我使用至今對它的各種看法。底下我就挑幾個印象深刻的特點來跟大家聊聊吧。

簡單好用的MVVM資料綁定 / Data binding

2019-1011-025205.png

(網頁截圖:Vue.js Introduction)

Vue.js最重要的特色就是它MVVM框架著重的資料綁定功能。在Vue.js中,資料是最核心的一塊。頁面如何呈現、邏輯如何判斷,全部都是以資料為主。當採用了Vue.js這種MVVM框架之後,我們就可以以資料為核心來開發程式。這種開發方法比傳統的循序流程操弄元素比起來還要簡單許多。

2019-1011-025557.png

(網頁截圖:Vue.js Introduction)

在上圖的例子中,資料seen是整個程式的核心。我們只要調整seen,就能控制網頁元素中<span>Now you see me</span>是否呈現。

以資料為核心的做法,可以更進一步延伸到整合多個資料計算出的computed資料,以及監控資料是否改變的watch。更特別的是,watch還能監控物件資料中的屬性,例如監控"status.username" (請看我寫的client.js)。這樣讓整個邏輯控制變得很靈活。

雖然Vue.js的教學大多是從data跟methods開始,不過實際開發下來,我深深地覺得可以在watch中放入更多邏輯控制,而methods就把它當作簡單的setter就好。畢竟以資料為核心推動整個程式的運作,這才是符合MVVM框架的邏輯。

資料綁定跟傳統操弄元素的比較 / Compare traditional element to data binding

說到這裡,讓我來批評一下傳統jQuery操控元素的做法。在用JavaScript或jQuery操控元素的時候,如果有資料變更,我們必須一一造訪所有顯示該資料的元素,一一指定它的顯示內容。反之,如果要取得特定<input type="text" />設定的值,傳統做法也是選擇該元素,取出它的value。

這樣的做法在非同步的網站應用中,常常會遇到很多難以偵錯的問題。最基本的錯誤就是在開發過程中挪動了元素的位置,導致jQuery照順序找元素的時候找不到它。相較之下,Vue.js的資料跟樣板本身是脫鉤的,請看「Template Syntax」的做法。

除了這個小問題,在非同步環境中,我們最怕的就是資料之間的不同步。有時候我們要取得元素上的資料,那個元素卻在另一段程式碼中非同步地請求伺服器資料來設定。結果兩段不同的程式碼的先後順序難以釐清,邏輯錯誤往往就發生在這種小細節上。這時候在Vue.js裡面的watch就是很好的做法,它會在資料改變的時候作出反應,我們不必煩惱不同程式碼之間先後的問題。

在Vue.js之中,這一切都很簡單。我們可以在很多不同的程式碼中設定資料,也可以設定資料改變之後要怎麼反應。而這之間的誰先誰後、介面如何調整,全部都交給Vue.js負責即可。這樣子才是資料(model)和顯示(view)脫鉤的好做法。

可分離的元件架構 / Components

Vue.js另一個令我激賞的特點,那就是它靈活的元件架構 Components。在寫大型的應用時,將程式依照邏輯區分成不同檔案,避免單一檔案中塞入過多程式碼,這是很重要的習慣。以前我在寫Vue.js的時候不知道元件的好用,直到在開發Electron Sticky Notes時好好遵循Vue.js的寫法使用元件之後,我才感受到它的強大。

雖然很多人對於元件的著眼點是在於可重複使用,不過我認為它最重要的加值,還是將大型的程式分割成許多容易維護的小型元件。每個元件裡面有自己的資料、邏輯,而各元件之間又能用資料綁定和props同步。將大型、厚重的程式分割之後,我們可以專注於開發小巧、完整的元件。

Vue.js的單一檔案元件Single File Components也能夠將邏輯、樣板、樣式、語系分割成不同的檔案。以我在Login.vue單一檔案元件為例子來說,它的程式碼如下:

<template src="./Login.html"></template>
<style src="./Login.less" lang="less" scoped></style>
<i18n src="./Login.json"></i18n>
<script src="./Login.js"></script>

它將單一元件分割成樣板Login.html樣式Login.less (而且還是less語法)、語系Login.json、以及邏輯Login.js。將程式碼分割之後,我們可以個別專注維護各自的檔案,也方便我們在多檔案之間交互參照。

然而,這種做法會讓單一系統變成相當多的檔案,並不適合直接使用。後續我們還有賴編譯器Webpack把這些檔案打包、壓縮,才能成為真正適合給網頁引用的JavaScript程式碼。

適用於單一元件的語系、樣式、以及錯誤處理 / I18n, scoped style and error handling in a component

講到可分割的元件,那就不得不提分割元件後帶來的另外三個好處:多國語系翻譯i18n、區域性的樣式、以及錯誤處理。

多國語系i18n / Internationalization

大部分的網站開發框架都會將語系翻譯集中在某一處管理,包含AdonisJs的Internationalization也是這樣處理。但是Vue.js的多國語系翻譯i18n支援單一元件,因此我們可以在Login.vue元件裡面直接設定Login.json。這讓我們在維護單一元件時,不必再跟全網站超多的翻譯檔打交道,只需要專心維護自己這個元件中的語系檔即可。

當然,我們依然可以設定全域的語系檔,我把它放在i18n-global.conf.js裡面,然後在最上層Vue.js載入i18n的時候設定進去。詳細做法請參考我寫的i18n.js

區域性樣式 / Scoped style

再來談談樣式。Vue.js支援less語法,我們可以用上下層包裹CSS選取器,以巢狀結構呈現CSS,提高CSS的可讀性和寫作效率。關於LESS跟CSS的比較,可以看「用 LESS 寫 CSS ( 入門、Import、變數 )」這篇。但實務開發時,因為我們可以將元件拆分得很細,單一元件中樣板的元素可能不會這麼多,所以實際上好像也用不太到複雜的巢狀結構。

比起支援less語法,Vue.js元件的區域性樣式更有實用價值。在前面宣告單一檔案元件Login.vue時,我在<style>標籤裡面加入了scoped屬性,這就表示這個樣式表只適用於這個元件中的樣板。

我們可以看看官方例子「有作用域的 CSS」中,區域性樣式的實際轉換結果:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

紅色字所添加的屬性,就是Vue.js宣告這個樣式只適用於這個樣板的意思。區域性樣式使得我們更容易專注開發各自的元件,不用擔心元件和元件之間的樣式會彼此打架,更不用一直仰賴「!important」覆蓋別人的樣子。

不過值得注意的是,有時候我們會在樣板裡面建立樣板之外的元素,這時候要搭配使用深度作用選取器,才能選到樣板之中額外建立的元素。許多文件都將樣板選取器寫成「 >>> 」,例如「.a >>> .b { /* ... */}」,不過我的情況是只有使用「::v-deep」才能發生作用,例如「.extra.text ::v-deep a > img { /* ... */ }」。詳細請看Chat.less檔案的內容。

錯誤處理 / Error handle

Vue.js有多種錯誤處理的做法,其中跟元件比較密切的錯誤處理就是errorCaptured。errorCaptured主要是捕捉這個元件以及下層子元件所發生的錯誤,因此我們可以明確知道這個錯誤可能是發生在那些程式裡面。關於Vue.js的錯誤處理可以看「Error/Exception handling in Vue.js application」這篇,我在client.js裡面也用到errorCaptured。

Vue.js還有用於全域的errorHandler功能,不過這個我就沒用到了。

強大的路由和動態元件 / Route and dynamic component

當我們將網頁的功能拆開成許多小元件之後,我們會需要些方法來決定何時要用什麼功能。如果要讓使用者可以透過網址來存取,那需要用到的是Vue Router。如果是要全部由程式邏輯來控制所顯示的內容,那就是用動態元件 Dynamic component。雖然這兩者很少被拿在一起講,但在我摸索之後,其實它們還挺像的。

Vue路由器 / Vue Router

2019-1011-041700.png

(圖片來源:TutorialsPoint: VueJS - Routing)

在Vue Router中,預設是用hash作為網站路由。hash的值會決定現在要顯示什麼元件。Vue.js的樣板中可以用以下語法來建立設定路由路徑的連結:

<router-link to = "/route1">Router Link 1</router-link>

它也可以用JavaScript程式碼來控制路由路徑:

router.push({ path: 'route1' })

此外,Vue Router也可以透過路由路徑取得參數。這點在接收來自不同網頁的資料時非常實用。

動態元件 / Dynamic component

相較於Vue Router的強大功能,動態元件看起來就簡單許多。它在樣板中的呈現方式如下:

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

紅字currentTabComponent這個資料決定了這個位置要呈現的元件,其他使用方式就跟一般元件一樣。<keep-alive>是用來保存不同元件的運作狀況。當元件被切換成其他元件時,前一個元件仍然保有它運作時的狀態。關於是否使用<keep-alive>的用法,可以看看「Vue.js: 動態元件 Dynamic Components」這篇。

雖然動態元件看起來很簡單,但對於只是要切換現在要顯示那個元件的系統需求來說,這樣就已經足夠了。

延遲與非同步載入 / Lazy and async loading

Vue.js的元件架構能夠讓我們把複雜的網站拆開成多個小型元件。但當網站非常複雜、開發的元件數量一多,就算能用Webpack打包、壓縮成一個檔案,這樣的檔案依然是大到難以接收。更重要的是,在複雜的網站上,使用者不見得會用到所有的元件。這樣的話,將所有元件打包成一個檔案的做法,顯然不是好事。

Vue Router跟動態元件兩者都有支援一項特別的功能:延遲和非同步載入。簡單來說,就是只有在會用到該元件的時候,Vue.js才會即時去取得該元件的程式碼、執行並編譯該元件的內容。在Vue Router裡面,這種做法稱之為Lazy Loading Routes。在動態元件裡面則是稱之為Async Components

原本Vue.js的延遲與非同步載入只是做到了「比較慢執行程式碼」的效果,但搭配Webpack的分割檔案功能,我們就能將某些元件分割並打包成獨立檔案,讓使用者需要的時候才動態下載並載入它。

我在客戶端components.js裡面就是用動態元件的非同步載入功能,並且搭配Webpack將元件分割成login.bundle.jschat.bundle.js兩個檔案。寫法如下:

import Loading from './../components/Loading/Loading.vue'
import ErrorHandler from './../components/ErrorHandler/ErrorHandler.vue'
import Auth from './components/Auth/Auth.vue'

let components = {
  Loading: Loading,
  'error-handler': ErrorHandler,
  Auth: Auth,
  Login: () => import(/* webpackChunkName: "client-components/Login" */ './components/Login/Login.vue'),
  Chat: () => import(/* webpackChunkName: "client-components/Chat" */ './components/Chat/Chat.vue'),
}
export default components

Login和Chat這兩個元件只有在需要顯示的時候,Vue.js才會即時去下載對應的檔案、並且執行元件的內容,這樣就能確保使用者不必下載用不到的多餘元件檔案,降低客戶端的負擔。

小結 / Before next

好,Vue.js的特色講到這裡就差不多了。這邊寫一寫又發現Vue.js還有幾個實用的特色,它真的是一款深奧的MVVM框架。

目前Vue最新版的Vue 3.0提出了Vue 3.0 Function-based API,我們可以看看「初探 Vue 3.0 Function-based API」這篇,其中setup的寫法很像是我在Svelte看到的做法,也許這樣可以讓程式開發更為簡潔。不過大致上看了Vue Composition API之後,我覺得這個做法似乎還在變動中。也許再等一段時間,等它穩定之後再來嘗試吧。

現在寫著寫著居然也超過一萬六千字了,但該講的事情還有三分之一呢。

我在前端用到的工具,除了Vue.js之外,還有畫面呈現的Semantic UI、處理日期的DAY.JS,以及從前端到後端傳輸資料的axios。其中值得一提的就是這個axios。


前後端資料傳輸:axios / Data request: axios

fetch_data_with_axios.jpg

(圖片來源:DUSTIN'S MURMUR)

axios是一套發出HTTP客戶端請求的函式庫。它的工作專注於跟伺服器請求資料、取得資料。axios採用了Promise架構,能夠支援ES2017的async/await的寫法,這讓我們開發AJAX應用時不必再跟層層包覆的callback纏鬥,程式寫起來精簡許多。而且axios把許多細節都優雅地包裝起來,開發者可以用最直覺的方式使用它。

2019-1011-050251.png

在AdonisJs聊天室中,axios是由前端的Vue.js使用,負責跟後端AdonisJs框架架設的Client API互通資料。我為了AdonisJs聊天室CORS需求,又特別將axios封裝成AxiosHelper.js獨立檔案,進一步統一get()與post()在傳遞資料和錯誤處理的方式,並且加上了上傳檔案的方法。

底下我們就來聊聊axios的幾個特色吧。

HTTP客戶端的選擇 / Why select axios?

要在Vue.js中跟伺服器請求資料,要選擇什麼工具比較合適呢?這似乎是許多開發者都有的共同問題。

以前我主要使用jQuery的$.ajax(),它將複雜的XMLHTTPRequest封裝成易用的工具。但它不是採用Promise框架,必須要用大量的回呼函數處理每一個回傳的資料,這種開發方式已經過時。

說到Vue.js,就會想到Vue.js生態系中的vue-resource。但因為它不能用於伺服器的Node.js,後來Vue.js比較推薦的是axios。在「Day19 純、手工系列 JS(Vue Axios篇)」跟「Vue-resource vs Axios?」這篇都有提到這點。

相較於axios,很多人主張應該使用原生功能fetch,例如「Axios or fetch(): Which should you use?」這篇。但就可讀性和封裝程度來看,axios很明顯是容易使用的得多。

舉例來說,如果我們要跟「/users/list」請求使用者列表資料,axios只要這樣寫:

let result = await axios.get('/users/list')
let users = result.data

axios預設會將回傳的結果轉換成JSON,這使得開發者省下資料轉換的手續,整個程式碼看起來就優雅許多。如果不仔細看,你可能還以為你在跟同一個頁面裡面的某個物件取得資料,忘記它其實是一個跟遠端伺服器非同步取得資料的AJAX做法呢。

簡潔的async與await語法 / Easier and readable asynchronous: async/await

從前面的AdonisJs框架到axios,我一再提到了ES2017的async/await寫法。這個寫法大幅度簡化了非同步架構下程式的複雜度。之前JavaScript提出許多改進,像是Promise或是yield,我在看的時候都覺得只是程式的另一種寫法。但直到ES 2017的async/await,我才真正有種JavaScript新時代來臨的感動。

舉例來說,我們要在Vue掛載的時候,從依序取得 /user/list 跟 /message/list 的資料的話,做法如下:

VueConfig.mounted = async function () {
  let result
  result = await axios.get('/user/list')
  this.users = result.data

  result = await axios.get('/message/list')
  this.messages = result.data
}

這樣子程式就會先跟伺服器取得 /user/list 的資料,再來取得 /message/list 的資料,兩者循序進行。值得注意的是,await關鍵字必須包在async宣告的函數裡面使用,我們可以直接在Vue.js的函數前加上async關鍵字即可,不會影響Vue.js的其他功能運作。此外,值得注意的是,如果忘記加上await,程式並不會出錯。不過這是axios會回傳Promise物件,它的表現就不是如上面的方式運作了。

如果想要簡單使用await的話,我們可以用匿名函數搭配async來使用,例如:

;(async () => {
  let result
  result = await axios.get('/user/list')
  result = await axios.get('/message/list')
})()

雖然現在Chrome跟Firefox已經預設支援await/async的寫法,但是早期的瀏覽器並沒有不支援。各個瀏覽器的相容性請看MDN web docs的await

不過這個事情好解決,我們可以請Webpack打包的時候,一併把@babel/plugin-transform-runtime套件打包進來,這樣子就能夠讓早期的瀏覽器兼容async和await的用法了。詳情請看我在webpack.config.js裡面的寫法。

靈活的擴充性:支援CORS跟檔案上傳 / CORS and file upload support

axios的簡單易用是它的主要特色,但它同時也具備因應不同使用情境的靈活性。網路上許多人看重的是它的可取消功能 (在options裡面設定timeout),不過我在AdonisJs聊天室用到比較多的地方,是CORS跟檔案上傳這兩個功能。

為了在傳送HTTP請求時配合身份認證,我們必須為axios的請求加上cookie等憑證資訊。最簡單的做法是直接在一開始就宣告設定:

import axios from 'axios'
axios.defaults.withCredentials = true

這個在前面講AdonisJs框架的時候也有提到過。

axios另一個好用的地方在於能夠使用檔案上傳。寫法如下:

let imagefile = document.querySelector('#file').files[0]

let formData = new FormData()
formData.append("image", imagefile)
let result = await axios.post('/upload_file', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
})
console.log(result.data)

這樣子就能上傳檔案,配合後端AdonisJs框架的Client API運作。

在AdonisJs聊天室的程式碼中,前端的部分我寫在AxiosHelper.js裡面,後端的部分則是寫在MessageController.js裡面。

令我訝異是,即使是在CORS跨網域的情境下,我們依然能夠使用這種方式上傳檔案,並且取得伺服器回覆的資料。有這個方法之後,我在很久以前寫的「JSONP跨網域傳送檔案:以POST方法實作」就完全不需要使用了呢。

小結 / Before next

axios只是個負責前端到後端傳輸資料的小工具。但它簡潔的寫法、優雅的包裝,這些特色都大大簡化了非同步資料請求的複雜度。

在使用async/await,也許人們很快就會忘了被callback統治下的恐怖,也許大家也不會仔細探究它背後的Promise跟yeild是如何運作,但開發者能夠省下這些鑽研細節的時間,投注更多心力在系統的主要邏輯上,這就是一件好事。

不過,要讓async/await順利運作,抑或是要打包與壓縮前面Vue.js中的龐大元件,一個好的編譯器是不可或缺的存在。接下來就讓我們來聊聊Webpack吧。


前端編譯:Webpack / Front end compiler: Webpack

webpack.png

(圖片來源:維基百科)

Webpack是將前端使用的程式碼打包、壓縮、甚至是分割的編譯工具。它不僅可以將大量零散的JavaScript程式碼整合在一起,還能直接編譯less、載入CSS。更重要的是,它作為編譯器的工作中,能夠將上述Vue.js等框架的許多功能發揮到極致。我從「閒聊Blogger範本程式碼的管理」這篇就有聊到我開始使用Webpack,但是直到最近在「工具開發閒聊:從AutoIT到Electron」中,我才深刻感受到Webpack不可或缺的價值。

2019-1011-050418.png

在AdonisJs聊天室中,Webpack負責將前端會使用的程式原始碼打包、編譯成數個發佈檔。程式原始碼放在 /webpack-app 資料夾裡面,編譯後的發佈檔放在 /public/spa 裡面。Wepback最主要的設定檔則是寫在webpack.config.js 之中。

雖然我以前有講過為什麼要用Webpack編譯程式碼的理由了,前面在Vue.js和axios提到的許多特色也都跟Wepback有所重疊,不過這邊還是挑出幾個我最近覺得比較有意思的特色出來聊聊吧。

合併與壓縮 / Bundle and compress

Webpack最基本的就是大量檔案合併在一起的編譯功能。前面有提到過Vue.js能夠將複雜系統分割成大量小型元件的功能,這特別需要搭配Webpack把眾多小型元件合併成少量的發佈檔,以免使用者使用系統還要下載大量檔案。

接著,在生產模式「production」底下,Webpack也會對JavaScript和CSS進行壓縮。在AdonisJs聊天室中Webpack所打包的發佈檔client.js,在開發模式「development」下的檔案大小是902KB,但是在生產模式中則可以壓縮到356KB。壓縮效率驚人。

相容性轉換 / Convert for browser compatibility

我在webpack.config.js的設定檔裡面也為生產模式加入了Babel轉換。Babel能將較新的JavaScript程式語言寫法,例如箭頭函數() => {}以及ES2017的async / await,轉換為舊型瀏覽器也能執行的程式碼。

值得一提的是,為了要讓瀏覽器兼容async/await,Webpack的設定檔有很多不同的方式,請看「一文读懂babel-loader、babel-polyfill、babel-transform-runtime的区别和联系」這篇。最後我選擇的是在載入babel-loader的時候加上plugins "@bable/plugin-transform-runtime",這樣執行時就不會遭遇「Uncaught ReferenceError: regeneratorRuntime is not defined」錯誤,而且也比直接「import "@babel/polyfill"」所產生的檔案還要小。在webpack.config.js的寫法請看這裡

自動分割程式碼與手動載入 / Code splitting automatically and loading chunks manually

再來是講講Webpack的分割功能。我將它的分割分成自動分析的分割,以及手動分割的設定兩個部分。這邊先來聊聊基本的自動分割的部分。

Webpack能使用SplitChunksPlugin將打包的程式碼分割成數個檔案。這主要是將不同程式碼中會用到的相同函式庫、元件或程式碼抽取出來成為獨立檔案,避免重複程式碼增肥了發佈檔的檔案大小。這部分的介紹可以看「webpack 二三事:如何讓 CommonsChunkPlugin() 發揮最大效益」這篇,但CommonsChunkPlugin已經是過時的寫法,現在要用SplitChunksPlugin。

Webpack能夠用很多方式分析共用的程式碼,並把它分離成為獨立的chunk檔案。我參考了「Reduce JavaScript Payloads with Code Splitting」這篇,將node_modules裡面的檔案分離成vendors.js,不同程式會用到的共同程式碼分離成commons.js。這部分的程式碼寫在webpack.config.js的splitChunks裡面,程式碼如下:

webpackConfig.optimization = {
  splitChunks: {
    cacheGroups: {
      // Split vendor code to its own chunk(s)
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: "all"
      },
      // Split code common to all chunks to its own chunk
      commons: {
        name: "commons", // The name of the chunk containing all common code
        chunks: "initial", // TODO: Document
        minChunks: 2 // This is the number of modules
      }
    }
  }
},

然而,這樣的做法也造成了另一個問題。原本我期望透過Webpack打包成一個檔案,只需要載入一個client.js就好了。現在卻是用程式碼分割成多個檔案:vendors.jscommons.js以及client.js。有沒有方法可以一口氣載入這三個檔案呢?為此,我另外寫了client-loader.js來依序載入這三個檔案,讓客戶端依然只需引用單一檔案,抱持使用上的簡潔。

但老實說這不是很漂亮的做法,我本來以為Webpack應該要有這方面相對應的做法,不過我找了老半天都找不到相關資訊。大家大多都在討論怎麼分割程式碼比較好,卻很少文章在討論怎麼讀取它們比較好,例如「The 100% correct way to split your chunks with Webpack」這篇。

手動分割程式碼與自動動態載入 / Code splitting manually and dynamic loading chunks automatically

除了自動分析程式碼並將程式碼進行之外,Webpack也能夠讓讀取程式中手動設定的分割檔案,並支援動態載入。舉例來說,因為semantic-ui太過龐大,我希望它能夠分割成獨立檔案,然後自動動態載入該檔案的話,寫法如下:

let semantic = () => import(/* webpackChunkName: "vendors/semantic-ui" */ './vendors/semantic-ui/semantic-ui.js')

這時候Wepback會自動讀取webpackChunkName的設定,把semantic-ui.js這個檔案及其引用的所有程式都獨立成vendors/semantic-ui.js。並且在程式執行到該段落時,動態去載入該檔案。

在CORS的情境中,Webpack還必須特別指定發佈路徑,這樣自動載入才能找到後端伺服器上的正確檔案。做法是在webpack.config.js的output裡面設定publicPath,寫法請看這裡

Wepback的動態載入能夠手動分割程式碼並且在需要的時候載入該檔案,這樣的做法能不能應用到前面自動分析共用程式碼的寫法呢?我看網路上的做法大多都是將原本「import Vue from 'vue'」的靜態載入寫法改為前面的「import('vue')」動態載入寫法,再搭配Webpack的魔術註解(magic comments)設定分割檔案的名稱,例如「Webpack 4 course: what are dynamic imports?」這篇的教學。但是我覺得大量改寫原本import的寫法並不是好事,因為它並非基本JavaScript語法中的常識。

總之,雖然手動分割與動態載入的寫法用在Vue.js的元件分割上非常好用,但Webpack自動分析與分割的功能,目前好像還是得要自己寫個client-loader.js載入器才行。不知道有沒有更好的解決方法呢?

對於Vue.js和axios的支援 / Expend abilities of Vue.js and axios

Vue.js實際上可以不用編譯,直接就能在網頁上執行,例如這個Markdown Editor Example範例。不過如果加上Webpack編譯的支援,Vue.js跟axios就能做到一些額外的功能:

  • Vue.js中的大量元件檔案可以用Webpack合併、壓縮成單一檔案,也能夠用Webpack手動分割與動態載入,讓元件只有在需要用到的時候才讀取與執行。
  • Vue.js的單一檔案元件不僅可以用src將樣板、樣式分離成獨立檔案,還能夠支援語系i18nless樣式語法
  • Wepback加上相容性轉換後,就能支援ES2017的async/await語法。

我一直有想過要試試熱重載 Hot Reload的功能,不過不知道是因為動態載入呢,還是因為CORS甚麼的因素,我一直沒能成功啟用熱重載。未來有機會再來試試看好了。

設定檔與版本差異問題 / Configuration and version difference of Webpack

Webpack在合併、壓縮、轉換、分割的做法上非常靈活,但只要系統專案的架構穩定之後,Webpack就是一個放著背後跑、不用管它的小精靈。因此,我們很容易可以從其他人類似的專案架構上,找到值得學習的Wepback設定檔寫法。我這份webpack.config.js在不同專案之間也是一直在改良、精進,這種踏實的成長感令人覺得充實。

Webpack設定檔是在Node.js環境下運作,因此它又能配合dotenv套件讀取.env設定的內容。這就可以讓我們在後端AdonisJs框架與前端的Vue.js與Webpack中使用共通的設定檔。在AdonisJs聊天室中,我在Webpack設定檔裡面讀取AdonisJs聊天室伺服器的網址,這樣在CORS動態載入的時候,Webpack就能正確載入到後端伺服器上的檔案。寫法請參考webpack.config.js

值得注意的是,在學習別人Webpack設定檔寫法的時候,請務必注意Webpack版本差異。不同版本之間,設定檔的寫法有不小的差距。目前我使用的是Webpack 4。該系統使用的Webpack版本可以從package.json裡面查詢,請務必先確認Webpack版本之後再來研究設定喔。

小結 / Before ending

Webpack是支撐Vue.js跟axios等各種前端技術的重要角色。如果缺少Webpack,Vue.js跟axios可能就沒辦法這麼好用了。


結語 / Wrap up

adonis.jpg

(圖片來源:Ancient History Encyclopedia - Adonis)

好久以前我期望用JavaScript開發前後端網頁應用程式的想法,終於在這次的AdonisJs聊天室中實現了。這個網頁應用中所使用的後端AdonisJs框架、前端Vue.js和編譯器Webpack等技術的組合,使得網頁應用開發變得十分優雅。

以前在改寫程式時,總是會覺得像是在做黑手的工作,老是在一團混亂的程式碼中,把程式搞得更混亂。現在使用AdonisJs框架之後,總算讓我覺得寫程式是一種優雅的創作,不用為了太多小細節煩心。

Adonis這個名字來自於希臘神話中的非常英俊的神,它也可以用來代稱非常有魅力的年輕男子。接下來我就是以AdonisJs聊天室為基礎,繼續開發下一個系統。希望未來做出來的系統也能跟Adonis一樣,充滿魅力。


那麼這次對AdonisJs聊天室及其使用技術的閒聊就到這裡了。寫到最後,我有些問題想問問大家:

  • 你有用過Node.js開發網頁應用程式嗎?你選擇的是什麼框架呢?
  • 你有用過JavaScript開發前端嗎?你選擇的是什麼框架呢?
  • 你對於這篇所提到的AdonisJs、Vue.js、axios、Webpack有什麼想法呢?還是你覺得有更好的工具可以使用呢?

歡迎在下面的留言處跟我們分享你的想法。大家的意見是我繼續分享的動力喔!如果你覺得我這篇實用的話,請幫我在AddThis分享工具按讚、將這篇分享到Facebook等社群媒體吧!

感謝你的耐心閱讀,我是布丁,讓我們下一篇見。