iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
1
Modern Web

Quasar CLI Framework 邪教:整合分散的前端技術和工具、常見的開發需求系列 第 14

第十四天:專案的初始化 - JS 程式 (feat. ESModule)

※ 今天的內容

昨天談完了Quasar 框架 CSS初始化的部分
今天來談Quasar 框架 js初始化的部分

一、Quasar 初始化的流程
二、Boot 在專案的初始化流程應用示例
三、ESModule 常用的 import 和 export 方式
四、總結
五、延伸閱讀

一、Quasar 初始化的流程

Boot在Quasar框架的初始化流程中,是非常重要也非常強大的架構
只要你有ESModule的概念,以及了解Quasar框架的網站初始化流程
你可以很方便的自訂額外的初始化邏輯

在Quasar 的官方文件有提到
使用者一開始進到一個Quasar專案開發的網站
依序的網站初始化流程如下:

In order to better understand how a boot file works and what it does, you need to understand how your website/app boots:

1. Quasar is initialized (components, directives, plugins, Quasar i18n, Quasar icon sets)
2. Quasar Extras get imported (Roboto font – if used, icons, animations, …)
3. Quasar CSS & your app’s global CSS are imported
4. App.vue is loaded (not yet being used)
5. Store is imported (if using Vuex Store in src/store)
6. Router is imported (in src/router)
7. Boot files are imported
8. Router default export function executed
9. Boot files get their default export function executed
(if on Electron mode) Electron is imported and injected into Vue prototype
(if on Cordova mode) Listening for “deviceready” event and only then continuing with following steps
10. Instantiating Vue with root component and attaching to DOM

這邊注意兩個重點:

  1. Boot 的所有程式檔會在 App.vue、Vuex 和 Vue-Router 引入後接著自動引入
    換句話說,你可以在Boot存取Vue、Vuex 跟 Vue-Router

  2. Boot 的 export default function 會自動被執行,任何初始化的流程都可以寫在裡面

如同官方文件上的這段程式碼

export default ({ app, router, store, Vue }) => {
  // something to do
}

因此,我們可以在Boot做的事情非常多,包括:

  1. 前後端分離的架構,會將Token存在Cookie或localStorage
    在Boot將Token從localStorage塞到Vuex,離開網站的時候再將Token塞回localStorage
  2. 處理某些套件的初始化流程,例如Axios、Vee-Validate、CKEditor
  3. Vue全域的Component、Mixin、Plugin、Directive
  4. SSR Server side 和 client side 的初始化

二、Boot 在專案的初始化流程應用示例

(一) Token 與 localStorage的讀寫 (Vuex的初始化)

我們用Quasar 建立專案時 src/store/module example來示範這個需求

  1. 打開src/store/index.js,將圖中兩處原本的註解拿掉,變成如圖所示:
    https://ithelp.ithome.com.tw/upload/images/20200929/201203312HdN4mSt0P.png

  2. 打開 src/store/module-example/state.js,在裡面宣告一個token的屬性

export default function () {
  return {
    token: ''
  }
}
  1. 打開 src/store/module-example/getters.js,在裡面寫一個getToken,將Token從Vuex讀取
export function getToken (state) {
  return state.token
}

  1. 打開 src/store/module-example/mutation.js,在裡面寫一個readToken,用來將Token 存到 example store
export function readToken (state, token) {
  state.token = token
}
  1. src/boot 新增一個localStorage.js並加到quasar.config.js的boot:[]
    https://ithelp.ithome.com.tw/upload/images/20200929/201203310izruQaQq6.png

https://ithelp.ithome.com.tw/upload/images/20200929/20120331aUsSkisz3Y.png

localStorage.js撰寫下面的程式碼

export default ({ app, router, store, Vue }) => {
  // 在頁面載入時讀取localStorage裡的token,塞到Vuex
  if (localStorage.getItem('token')) {
    store.commit('example/readToken', localStorage.getItem('token'))
  } else {
    store.commit('example/readToken', 'Test001')
  }
  localStorage.removeItem('token')

  // 在頁面重新整理時將vuex裡的token儲存到localStorage裡
  window.addEventListener('beforeunload', () => {
    localStorage.setItem('token', store.getters['example/getToken'])
  })
}
  1. 最後打開src/pages/Index.vue,在mounted()加入程式碼
<script>
export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    console.log(this.$store.getters['example/getToken'])
    this.$store.commit('example/readToken', 'Test002')
  }
}
</script>

第一次打開網頁時,你會看到console 輸出Test001
https://ithelp.ithome.com.tw/upload/images/20200929/20120331gKjsT5oqmE.png

把網頁關掉再重開後,會出現Test002
示範了每一次網站打開時,Boot會從localstorage取得上一次的最新值
https://ithelp.ithome.com.tw/upload/images/20200929/20120331MZGEeJvkJY.png

(二) Axios的初始化

我們可以在boot設定axios的base url、interceptors

Quasar 專案的src/boot裡面已經有一個axios.js
可以改寫成如下:

import axios from 'axios'

export default ({ app, router, store, Vue }) => {
  axios.defaults.baseURL = 'http://api.test.com.tw/'
    
  axios.interceptors.request.use((config) => {
    return config
  }) 
  Vue.prototype.$axios = axios
}

然後在Index.vue測試axios,看到baseUrl成功的被修改了

<script>
export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    this.$axios.post('login', {
      username: 'username',
      password: 'password'
    })
  }
}
</script>

https://ithelp.ithome.com.tw/upload/images/20200929/20120331THHPIEXyj8.png

(三) 第三方套件的初始化

例如:Vee Validate

import { ValidationProvider, ValidationObserver, extend, required, email, alpha_num } from 'vee-validate'

export default ({ app, router, store, Vue }) => {
  Vue.component('ValidationProvider', ValidationProvider)
  Vue.component('ValidationObserver', ValidationObserver)
  
  extend('email', {
    ...email,
    message: '無效的電子郵件格式'
  })

  extend('required', {
    ...required,
    message: '必填'
  })

  extend('alpha_num', {
    ...alpha_num,
    message: '只能包含英文字母跟數字'
  })
}

例如:CKEditor

import CKEditor from '@ckeditor/ckeditor5-vue'
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'

const editorConfig: {
  // 圖文編輯器的預設參數  
}

export default ({ app, router, store, Vue }) => {
  Vue.use(CKEditor)
}

export { ClassicEditor, editorConfig }

(四) SSR 模式存取Cookie

在quasar 的 SSR模式
可以使用在export default function 的參數傳入 ssrContext 操作 Cookie
https://quasar.dev/quasar-cli/developing-ssr/ssr-frequently-asked-questions#Why-doesn%E2%80%99t-importing-Platform-and-Cookies-work%3F

import { Cookies } from 'quasar'

let cookies

export default function ({ app, router, store, Vue, ssrContext }) {
  cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies
  
  if (cookies.has('token')) {
    store.commit('example/readToken', localStorage.getItem('token'))
  }
  cookies.remove('store')
}

export { cookies }

要注意的是,有些第三方套件只能在Client端使用
你可以個別設定哪些boot要在server或client時執行:

boot: [
  {
    server: false, // run on client-side only!
    path: '<name>' // references /src/boot/<name>.js
  },
  {
    client: false, // run on server-side only!
    path: '<name>' // references /src/boot/<name>.js
  }
]

三、ESModule 常用的 import 和 export 方式

我們在src底下建立一個lib資料夾,並且建立一個test.js
當中包含了這些程式碼

const variable = '安安'

console.log('ㄤㄤ')

export { variable }
export function myFunction () {
  console.log(`myFunction 叫${variable}`)
}

export default () => {
  console.log(`default 叫${variable}`)
}

以下是在src/pages/Index.vue用不同方式引入:

(一)不管有沒有export,import 進來執行所有程式碼

import test.js並執行test.js裡面,export之外的程式碼
但是無法直接從外部存取test.js的任何變數、外部呼叫test.js的任何函式

//src/pages/Index.vue
<script>
import 'src/lib/test.js'

export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    if (variable) {
      console.log('Variable', variable)
    } else {
      console.log('無法存取Variable')
    }
  }
}
</script>

存檔後,ES Lint 會告訴你,無法存取variable
https://ithelp.ithome.com.tw/upload/images/20200929/20120331t4CKbOcys3.png

當你拿掉mounted() 裡面的所有程式碼,會看到輸出了一個ㄤㄤ
https://ithelp.ithome.com.tw/upload/images/20200929/20120331HjHJ8EP8yR.png

(二)export { something } 或 export function myFunction () {} 後,import { something } (指定的export 對象)**

import 在 test.js export 的 指定函式變數
提供外部使用test.js裡面的指定export的函式、變數或物件
並執行test.js裡面,export之外的程式碼

export 的時候叫什麼名子
import 的時候就要用什麼名子

//src/pages/Index.vue
<script>
import { variable, myFunction } from 'src/lib/test.js'

export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    console.log('Variable', variable)
    myFunction()
  }
}
</script>

這時候你會看到輸出了ㄤㄤVariable 安安myFunction 叫安安
https://ithelp.ithome.com.tw/upload/images/20200929/20120331zt6wbWmq2j.png

(三)export default 無名氏 後,import 隨便稱呼啦 (預設的export對象)**

import 在test.js 當中 export default 的 函式變數物件
並執行test.js裡面,export之外的程式碼

import 時的 名子自行定義,例如:songla
但是無法直接從外部存取test.js export default 以外的函式、變數 或 物件

//src/pages/Index.vue
<script>
import songla from 'src/lib/test.js'

export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    if (variable) {
      console.log('Variable', variable)
    } else {
      console.log('無法存取Variable')
    }
    console.log(songla)
  }
}
</script>

存檔後,ES Lint 會告訴你,無法存取variable
https://ithelp.ithome.com.tw/upload/images/20200929/20120331PLcXObLLUP.png

當你拿掉mounted() 裡面的所有程式碼,留下console.log(songla),會看到輸出了一個ㄤㄤf () {}
https://ithelp.ithome.com.tw/upload/images/20200929/20120331xzKHl0rwyb.png

這是因為test.js export default 的,是一個函式
console.log(songla)改成console.log(songla()),會看到原本輸出的f () {}變成安安
https://ithelp.ithome.com.tw/upload/images/20200929/20120331lVWcQT9W1D.png

如果將test.js 的 export default () => {} 改成export {}

export default {
  property: 'property value',
  someMethod () {
    console.log(`call someMethod ${this.property}`)
  }
}
<script>
import songla from 'src/lib/test.js'

export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    console.log(songla)
    console.log(songla.property)
    songla.someMethod()
  }
}
</script>

https://ithelp.ithome.com.tw/upload/images/20200929/20120331jKXI5eU1q2.png

你也可以一次import所有被export的變數與函式
一樣會執行test.js裡面,export之外的程式碼

<script>
import * as obj from 'src/lib/test.js'
export default {
  name: 'DemoPage',

  data () {
    return {
    }
  },
  mounted () {
    console.log(obj)
  }
}
</script>

import之後會以一個物件包住所有被export的變數與函式:
https://ithelp.ithome.com.tw/upload/images/20200929/20120331o1xrsIKeA9.png

總結來說,不管你用任何方式import
都會執行被export以外的函式處理,也就是範例中的console.log('ㄤㄤ')
但是無法從外部直接存取被import的檔案變數,以及直接呼叫被import的檔案的函式
只能先從外部import 有被export的資料後才能存取使用

四、總結

理解Quasar框架CSS和JS初始化的方式
在架構上可以很方便的規劃和使用
跟Nuxt相比,Quasar並沒有Middleware,可以直接在boot撰寫router.beforeEach
Quasar保留了routes.js的設定彈性,相對的也沒辦法像Nuxt可以產生所有靜態的網頁檔案

五、延伸閱讀

完全解析 JavaScript import、export


上一篇
第十三天:專案的初始化 - CSS 樣式
下一篇
第十五天:專案的初始化 - 拆分環境變數 (feat. process.env、CommonJS)
系列文
Quasar CLI Framework 邪教:整合分散的前端技術和工具、常見的開發需求31

尚未有邦友留言

立即登入留言