# 3-3 Vue CLI 環境設定與打包部署

前一個小節介紹了 Vue SFC 的檔案結構後,相信讀者對 Vue CLI 與 SFC 已經有了基本的認識。 在本章最後一個小節中,就繼續為讀者介紹 Vue.js 的相關開發工具、開發環境設定,以及最後上線前的打包與部署。

# package.json 專案與套件相依設定檔

很多前端開發的入門者經常會有的疑問,文件裡面只有提到 Vue CLI 建立專案後, 在終端機輸入 npm run serve 啟動開發伺服器,或執行 npm run build,但是卻不知道這些指令由來為何。

事實上,這些指令被定義在我們專案內的 package.json 檔案裡頭,打開 package.json,並找到 "scripts" 物件:

"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "lint": "vue-cli-service lint"
},
1
2
3
4
5

可以看到分別已經預先定義好 servebuild 以及 lint 對應的行為,而這樣的指令,就被稱作 npm script

進一步探究,裡面的 vue-cli-service 其實是在專案目錄內的 ./node_modules/.bin/ 底下可以找到它。 換句話說,上面那段 npm script 改回完整的寫法其實是:

"scripts": {
  "serve": "./node_modules/.bin/vue-cli-service serve",
  "build": "./node_modules/.bin/vue-cli-service build",
  "lint": "./node_modules/.bin/vue-cli-service lint"
},
1
2
3
4
5

只是在 package.json 裡面,它允許我們省略掉前面的 ./node_modules/.bin/ 路徑。

# build 將專案打包

所以,當我們專案開發完成時,就可以利用這裏的 build 指令:

$ npm run build
# 或 yarn build
1
2

打包專案

這時候,Vue CLI 就會透過 @vue/compiler-sfc 與各種 Loader 將 SFC 的 .vue 檔案轉譯成瀏覽器看得懂的 JavaScript .js 檔。

打包後的檔案會出現在 dist/ 目錄下,同時 Vue CLI 也會提示檔案的大小:

 DONE  Compiled successfully in 5759ms

  File                                 Size              Gzipped

  dist/js/chunk-vendors.ebd601cc.js    167.96 KiB        59.78 KiB
  dist/js/app.2ad45894.js              4.58 KiB          1.65 KiB
  dist/css/app.66156f72.css            0.33 KiB          0.23 KiB

  Images and other types of assets omitted.

 DONE  Build complete. The dist directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

✨  Done in 9.48s.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

為確保前端靜態檔案不被瀏覽器暫存,Vue CLI 預設會將打包後的檔名加上檔案的雜湊值,所以當我們執行了 build 指令進行打包後, Vue CLI 就會在 dist/ 目錄下新增 chunk-vendors.ebd601cc.jsapp.2ad45894.jsapp.66156f72.css 三個檔案。

小提醒

雜湊值顧名思義就是透過雜湊演算法產生,所以讀者在進行打包後的檔名與上面範例不同屬正常現象。

# 相依套件的版本管理

而除了 script 區塊外,我們也可以從 package.json 觀察到這個專案所使用的套件,如:

"dependencies": {
  "core-js": "^3.6.5",
  "vue": "^3.0.0"
},
"devDependencies": {
  "@vue/cli-plugin-babel": "~4.5.0",
  "@vue/cli-plugin-eslint": "~4.5.0",
  "@vue/cli-service": "~4.5.0",
  "@vue/compiler-sfc": "^3.0.0",
  "babel-eslint": "^10.1.0",
  "eslint": "^6.7.2",
  "eslint-plugin-vue": "^7.0.0-0"
},
1
2
3
4
5
6
7
8
9
10
11
12
13

裡面的 dependencies 代表的是當專案進行打包建置 (build) 時,要跟著一起被包裝進去,並發佈到線上環境, 而 devDependencies 裡面的套件僅用於開發階段時使用,並不會跟著一起被打包出去。

而相依套件版本號前面的 ^~ 代表的意義是:

  • ~ 會找到目前最新的小版本號來安裝,如 "@vue/cli-service": "~4.5.0" 在安裝時,如果已經有 4.5.94.6.1 則會選擇 4.5.9 進行安裝。
  • ^ 會找到目前最新的中版本號來安裝,如 "eslint": "^6.7.2" 在安裝時,如果已經有 7.0.16.8.16.7.3,則會選擇在 6.x.x 裡面最新的那個。 以前面例子來說,也就是 6.8.1 進行安裝。

當然其他還有像是 eslintConfig 用來處理 ES Lint 相關設定,以及 browserslist 用來指定 babel 轉換的目標版本, 這裡就不多說明,有興趣的讀者可自行參閱 ES Lint 與 Babel 的相關文件。

像這樣,透過 package.json 進行專案相依套件的管理,當專案要進入版本控制系統的時候,只需要寫入 package.json 檔案, 團隊裡的其他成員也能透過 npm installyarn 指令取得專案所需的相依套件了。

# vue.config.js 設定檔

由於 Vue CLI 希望減少開發者的負擔,透過 Vue CLI 建立專案後就馬上可以投入開發,所以預設是採取零配置 (zero configuration) 的設定策略。

但是在實際開發時,偶爾我們還是會因應不同場景,會有需要調整設定的時候, 像是要調整 webpack 的參數時,這個時候我們就必須自行建立 vue.config.js 檔案了。

vue.config.js 需要建立在整個專案的根目錄下,也就是這個位置:







 


├── node_modules/          # node 相關的套件 (隱藏目錄)
├── dist/                  # 用來存放打包後的檔案
├── public/                # 公開檔案目錄
├── src/                   # 原始碼目錄,主要都在這裡進行開發
│
├── README.md 
├── vue.config.js          # vue.config.js 設定檔
└── package.json           # 專案、相依套件設定檔
1
2
3
4
5
6
7
8

像是若不希望打包後的檔名出現雜湊值,就可以在 vue.config.js 加入 filenameHashing: false

// vue.config.js
module.exports = {
  filenameHashing: false,
}
1
2
3
4

存檔後再重新執行 npm run build 指令打包,這樣生成的檔案名稱就不會再帶有亂數產生的雜湊值了。

# 開發時的跨網域存取 - proxy-devServer

在專案的開發階段,相信讀者們一定省不了與後端 API 溝通的機會。 但是由於開發的網頁伺服器建立在自己的機器上 (localhost),在呼叫遠端 API 的時候難免會遇到跨域 (cross-domain) 的限制, 這時候,Vue CLI 內建提供的 proxy-devServer 就是很實用的功能了。

這裏我們以 新北市公共自行車租賃系統 (俗稱 ubike) 的開放資料作為範例。

新北市公共自行車租賃系統

新北市公共自行車租賃系統說明文件: https://data.ntpc.gov.tw/datasets/71CD1490-A2DF-4198-BEF1-318479775E8A (opens new window)

當我們嘗試在元件的 mounted 階段透過 fetch 取得遠端資料時:

mounted() {   
  fetch('https://data.ntpc.gov.tw/api/datasets/71CD1490-A2DF-4198-BEF1-318479775E8A/json/preview')
    .then(res => res.json())
    .then(data => console.log(data));
}
1
2
3
4
5

由於 API 未啟用 CORS ,加上瀏覽器的預設的跨域限制,在呼叫 API 時 console 主控台會出現像這樣的訊息:

CORS跨域限制的錯誤

表示因為網域不同的限制,我們無法在 localhost 直接呼叫這個 API 將資料寫入網頁。

不過還好,Vue CLI 整合了 http-proxy-middleware (opens new window), 這個工具替我們在 localhost 建立一個暫時性的後端服務作為代理伺服器,讓我們在開發時期就可以很方便地直接呼叫遠端所提供的 API。

讓我們再回到 vue.config.js 並新增 devServerproxy 選項:

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://data.ntpc.gov.tw/api/',
        pathRewrite: { '^/api': '' },
        changeOrigin: true,
        ws: true
      },
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

這裏的 target 代表遠端 API 的網域路徑,我們將 https://data.ntpc.gov.tw/api/ 指向 localhost/api, 這樣我們就可以透過 http://localhost/api/ 來取得原本在 https://data.ntpc.gov.tw/api/ 的資訊了。

接著重新啟動 dev-server,並修改程式:

mounted() {   
  fetch('/api/datasets/71CD1490-A2DF-4198-BEF1-318479775E8A/json/preview')
    .then(res => res.json())
    .then(data => console.log(data));
}
1
2
3
4
5

由於 https://data.ntpc.gov.tw/api/ 已經被本機的 dev-server 所代理,所以我們就可以在 fetch 直接寫成相對路徑 /api/datasets/71CD1490-A2DF-4198-BEF1-318479775E8A/json/preview

devServer與proxy示範

小提醒

devServer-proxy 只是一個暫時性的後端代理服務,僅在開發階段時有效。 當專案打包上線後,若原本的跨域問題未能解決,那麼限制依然存在,請讀者務必多加注意。

# 多頁式應用 (MPA) 入口設定

前面介紹過,Vue CLI 建立好的專案預設只有單一進入點,也就是我們前面說的 main.js

但在實際開發時,我們通常不一定每個專案都會做成 SPA (Single-Page Application, 單頁式應用程式) 的形式, 也可能會採用傳統的 MPA (Multiple-Page Application, 多頁式應用程式) 來進行開發。

這時候,我們就需要自行調整專案裡的 vue.config.js 檔案來使環境符合開發的需求。

小提醒

SPA 與 MPA 的比較,我們會在第四章介紹 Vue Router 的時候有更詳細的說明。

既然要變成多頁式應用程式,那麼就需要把原本的入口網頁 public/index.html 換成其他入口。

這裡以兩個獨立頁面來示範,分別是 list.htmlproduct.html, 那麼我們就在專案的 src/ 目錄下新增一個 pages 子目錄,並新增這兩個 HTML 檔案:

│
├── src/            
│   ├── App.vue
│   ├── assets/     
│   ├── components/ 
│   ├── pages/              # 入口網頁目錄換成這個
│   │   ├── list.html
│   │   └── product.html
│   │ 
│   ├── list.js             # list.html 的進入點
│   └── product.js          # product.html 的進入點
│
└──  # 以下略
1
2
3
4
5
6
7
8
9
10
11
12
13

這個 list.js 的內容與原本的 main.js 基本上相同, 讀者可以依照實際所需自行修改掉引入的 vue 檔案,以及 mount 的目標。

// list.js
import { createApp } from 'vue';
import App from './App.vue';

// 這裡我故意將 mount 的目標改成 #list-app
createApp(App).mount('#list-app');
1
2
3
4
5
6

那麼與 list.js 對應的 list.html 裡面就應該要有個 id="list-app" 的 DOM 節點:

<div id="list-app"></div>
1

接著修改 vue.config.js,新增 pages 物件,並加入:

// vue.config.js
module.exports = {
  pages: {
    list: {
      entry: `./src/list.js`,
      template: `./src/pages/list.html`,
      filename: `/list.html`
    },
    product: {
      entry: `./src/product.js`,
      template: `./src/pages/product.html`,
      filename: `/product.html`
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

pages 物件的 key 代表我們的路由,這裏設定 list: { ... }product: { ... } , 代表我們可以透過 http://localhost:8080/list.html 以及 http://localhost:8080/product.html 連接到目標的網頁。

物件裡面的 entry 指的是這頁所在的程式進入點,也就是啟動 Vue.createApp({...}) 的檔案位置。 而 template 指的是網頁的所在路徑, filename 指的是當我們執行 build 指令後,網頁存放的位置。

如果覺得一頁一頁手動設定太麻煩的話,當然也有更快速的作法。

由於 Vue-CLI 的開發環境是 Node.js,所以我們可以利用 Node 所提供的 pathglob 工具, 自行撰寫程式來掃描專案內檔案路徑:

// vue.config.js
const path = require('path');
const glob = require('glob');
const resolve = dir => path.join(__dirname, dir);

const getPagesEntry = () => {
  const entry = {};

  // 搜尋專案內 /src/pages/ 所有的 HTML 檔案
  const fileNameArr = glob
    .sync(path.join(__dirname, './src/pages/**/*.html'))
    .map(p => p.split('/src/pages/')[1])
    .map(p => p.replace('.html', ''));

  // 建立 pages 物件內容,存放到 entry 物件內
  fileNameArr.forEach(e => {
    entry[e] = {
      entry: `./src/${e}.js`,
      template: `./src/pages/${e}.html`,
      filename: `${e}.html`,
    };
  });

  return entry;
};

// 最後將 getPagesEntry() 的結果回傳給 page
// 就可以取得所有檔案的路徑了
module.exports = {
  pages: getPagesEntry()
}
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

以上面範例來說,最終打包的結果就會同時有 list.jsproduct.js, 我們只需分別將它們在各自的網頁上透過 <script> 標籤引入即可。

像這樣,我們就可以改造原本預設的 SPA 架構,誰說 Vue CLI 不能做多頁式應用呢?

# vue.config.js 與 webpack

由於 Vue CLI 內建整合了 webpack,所以我們可以在剛剛建立好的 vue.config.js, 新增 configureWebpack 這個物件選項進行相關的設定:



 







// vue.config.js
module.exports = {
  configureWebpack: {
    plugins: { ... },         // 放置 webpack plugin 相關設定
    performance: { ... },     // 設定打包後檔案大小限制與提示
    resolve: { ... }          // 模組的解析相關設定
    // ... 還有更多
  }
}
1
2
3
4
5
6
7
8
9

當然 webpack 能做的絕對不只這些,有興趣了解更多的讀者可至 webpack 官方文件 (https://webpack.js.org/concepts/ (opens new window)) 查閱。

# Vue CLI 整合第三方函式庫

如果我們希望在 Vue 專案裡頭整合外部第三方的函式庫 (如 jQuery):

首先透過 npm 安裝 jQuery:

$ npm install jquery
1

安裝後時候打開 package.json

"dependencies": {
  "core-js": "^3.6.5",
  "jquery": "^3.5.1",
  "vue": "^3.0.0"
},
1
2
3
4
5

確認過眼神, jQuery 已經正確安裝至專案當中。

這個時候,我們修改 App.vue,透過 import $ from 'jquery' 將 jQuery 引入至元件中:

<script>
import HelloWorld from './components/HelloWorld.vue'
import $ from 'jquery';

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  mounted() {
    // 在 mounted 階段使用 jQuery ($) 確保 DOM 已經渲染完成
    console.log( $('img').attr('src') );
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

但是前面說過,放在 dependencies 的相依套件在打包的時候,會跟著一起被包裝進去,無形中也增加了檔案的大小。

  File                        Size              Gzipped

  dist/js/chunk-vendors.js    167.96 KiB        59.78 KiB
  dist/js/app.js              4.58 KiB          1.65 KiB
  dist/css/app.css            0.33 KiB          0.23 KiB
1
2
3
4
5

若我們希望減少打包後的檔案大小,將 jQuery 改用外部 script 的方式引入,又該怎麼設定呢?

此時我們只需要修改 public/index.html,加入 <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> 以便讓 jQuery 透過 CDN 引入網頁,接著再回頭修改 vue.config.js,加入 externals 設定:

// vue.config.js
module.exports = {
  configureWebpack: {
    externals: { 'jquery': '$' }
  }
}
1
2
3
4
5
6

存檔後重新執行 build 指令:

  File                        Size              Gzipped

  dist/js/chunk-vendors.js    79.46 KiB         29.75 KiB
  dist/js/app.js              4.58 KiB          1.65 KiB
  dist/css/app.css            0.33 KiB          0.23 KiB
1
2
3
4
5

從生成的檔案就可以觀察到,這時 webpack 並未將 jQuery 打包進專案中,打包後的檔案大小也就減少了許多。


本章節我們大致介紹了 Vue CLI 的特色與常用的功能, 當然 Vue CLI 能做到的功能眾多,礙於篇幅不可能完全列出,有興趣的讀者可以自行參考官方文件 https://cli.vuejs.org/zh/config/#vue-config-js (opens new window)

備註

在本書撰寫的同時 (2021/01) ,Vue CLI 的下一個版本 (v5) 已經進入 v5.0.0-alpha.0 的階段了, 此版本除了將內部整合的 webpack 從原本的 v4 更新至 v5 的重大更新之外,也針對周邊相關 Plugins 進行錯誤的修復。

完整的更新說明,讀者可以自行參考 https://next.cli.vuejs.org/migrations/migrate-from-v4.html#breaking-changes (opens new window)

Last Updated: 1/3/2021, 6:10:34 PM