API 浏览器
Quasar CLI with Vite - @quasar/app-vite
启动文件

Quasar 应用的一个常见用例是在根 Vue 运行之前运行代码应用实例已实例化,例如注入和初始化你自己的依赖(例如:Vue 组件、库……)或简单地配置应用的一些启动代码。

¥A common use case for Quasar applications is to run code before the root Vue app instance is instantiated, like injecting and initializing your own dependencies (examples: Vue components, libraries…) or simply configuring some startup code of your app.

由于你无法访问任何 /main.js 文件(以便 Quasar CLI 可以无缝初始化并为 SPA/PWA/SSR/Cordova/Electron 构建相同的代码库),Quasar 通过允许用户定义所谓的启动文件来优雅地解决该问题。

¥Since you won’t have access to any /main.js file (so that Quasar CLI can seamlessly initialize and build same codebase for SPA/PWA/SSR/Cordova/Electron) Quasar provides an elegant solution to that problem by allowing users to define so-called boot files.

在早期版本的 Quasar 中,要在根 Vue 实例实例化之前运行代码,你可以修改 /src/main.js 文件并添加任何需要执行的代码。

¥In earlier Quasar versions, to run code before the root Vue instance was instantiated, you could alter the /src/main.js file and add any code you needed to execute.

这种方法存在一个主要问题:随着项目的发展,你的 main.js 文件很容易变得混乱且难以维护,这与 Quasar 鼓励开发者编写可维护且优雅的跨平台应用的理念背道而驰。

¥There is a major problem with this approach: with a growing project, your main.js file was very likely to get cluttered and challenging to maintain, which breaks with Quasar’s concept of encouraging developers to write maintainable and elegant cross-platform applications.

使用启动文件,你可以将每个依赖拆分为独立且易于维护的文件。通过 quasar.config 文件配置,禁用任何启动文件,甚至根据上下文确定哪些启动文件会进入构建过程,也非常简单。

¥With boot files, it is possible to split each of your dependencies into self-contained, easy to maintain files. It is also trivial to disable any of the boot files or even contextually determine which of the boot files get into the build through the quasar.config file configuration.

启动文件结构剖析(Anatomy of a boot file)

¥Anatomy of a boot file

启动文件是一个简单的 JavaScript 文件,可以选择导出一个函数。Quasar 将在启动应用时调用导出的函数,并额外将一个具有以下属性的对象传递给该函数:

¥A boot file is a simple JavaScript file which can optionally export a function. Quasar will then call the exported function when it boots the application and additionally pass an object with the following properties to the function:

属性名称描述
appVue 应用实例
router来自 ‘src/router/index.js’ 的 Vue Router 实例
storePinia 实例 - 如果你的项目使用 Pinia(你有 src/stores),则仅会传递 store 参数。
ssrContext如果为 SSR 构建,则仅在服务器端可用。更多信息
urlPathURL 的路径名(路径 + 搜索)部分。它还包含客户端的哈希值。
publicPath配置的公共路径。
redirect用于重定向到另一个 URL 的函数。接受字符串(完整 URL)或 Vue 路由位置字符串或对象。
import { defineBoot } from '#q-app/wrappers'
export default defineBoot(({ app, router, store }) => {
  // something to do
})

启动文件也可以异步:

¥Boot files can also be async:

import { defineBoot } from '#q-app/wrappers'
export default defineBoot(async ({ app, router, store }) => {
  // something to do
  await something()
})

注意 defineBoot 的导入。这本质上是一个无操作函数,但其​​目的是帮助提升 IDE 自动补齐体验:

¥Notice the defineBoot import. This is essentially a no-op function, but its purpose is to help with a better IDE autocomplete experience:

import { defineBoot } from '#q-app/wrappers'

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

请注意,我们正在使用 ES6 解构赋值。仅分配你实际需要/使用的内容。

¥Notice we are using the ES6 destructuring assignment. Only assign what you actually need/use.

你可能会问自己为什么我们需要导出一个函数。这实际上是可选的,但在你决定删除默认导出之前,你需要了解何时需要它:

¥You may ask yourself why we need to export a function. This is actually optional, but before you decide to remove the default export, you need to understand when you need it:

// Outside of default export:
//  - Code here gets executed immediately,
//  - Good place for import statements,
//  - No access to router, Pinia instance, ...

export default defineBoot(async ({ app, router, store }) => {
  // Code here has access to the Object param above, connecting
  // with other parts of your app;

  // Code here can be async (use async/await or directly return a Promise);

  // Code here gets executed by Quasar CLI at the correct time in app's lifecycle:
  //  - we have a Router instantiated,
  //  - we have the optional Pinia instance,
  //  - we have the root app's component ["app" prop in Object param] Object with
  //      which Quasar will instantiate the Vue app
  //      ("new Vue(app)" -- do NOT call this by yourself),
  //  - ...
})

何时使用启动文件(When to use boot files)

¥When to use boot files

警告

请确保你了解启动文件解决了什么问题以及何时适合使用它们,以避免在不需要它们的情况下使用它们。

¥Please make sure you understand what problem boot files solve and when it is appropriate to use them, to avoid applying them in cases where they are not needed.

启动文件有一个特殊用途:它们在应用的 Vue 根组件实例化之前运行代码,同时允许你访问某些变量。如果你需要初始化库、干扰 Vue Router、注入 Vue 原型或注入 Vue 应用的根实例,则需要这些变量。

¥Boot files fulfill one special purpose: they run code before the App’s Vue root component is instantiated while giving you access to certain variables, which is required if you need to initialize a library, interfere with Vue Router, inject Vue prototype or inject the root instance of the Vue app.

引导文件的合理使用示例(Examples of appropriate usage of boot files)

¥Examples of appropriate usage of boot files

  • 你的 Vue 插件有安装说明,例如需要在其上调用 app.use()

    ¥Your Vue plugin has installation instructions, like needing to call app.use() on it.

  • 你的 Vue 插件需要实例化添加到根实例的数据。 - 一个例子是 vue-i18n

    ¥Your Vue plugin requires instantiation of data that is added to the root instance - An example would be vue-i18n.

  • 你希望使用 app.mixin() 添加全局混合宏。

    ¥You want to add a global mixin using app.mixin().

  • 你希望在 Vue 应用的 globalProperties 中添加一些内容以便于访问。 - 一个例子是,在 Vue 文件中方便地使用 this.$axios(用于 Options API),而不是在每个文件中导入 Axios。

    ¥You want to add something to the Vue app globalProperties for convenient access - An example would be to conveniently use this.$axios (for Options API) inside your Vue files instead of importing Axios in each such file.

  • 你希望干扰路由 - 一个例子是使用 router.beforeEach 进行身份验证

    ¥You want to interfere with the router - An example would be to use router.beforeEach for authentication

  • 你希望干扰 Pinia

    ¥You want to interfere with Pinia

  • 配置库的各个方面 - 一个例子是创建一个带有基本 URL 的 Axios 实例;然后,你可以将其注入 Vue 原型并/或导出(这样你就可以从应用的任何其他地方导入该实例)

    ¥Configure aspects of libraries - An example would be to create an instance of Axios with a base URL; you can then inject it into Vue prototype and/or export it (so you can import the instance from anywhere else in your app)

引导文件不必要的使用示例(Example of unneeded usage of boot files)

¥Example of unneeded usage of boot files

  • 对于像 Lodash 这样的纯 JavaScript 库,它们在使用前不需要任何初始化。例如,只有当你想要将 Lodash 注入 Vue 原型时,才可能将其用作启动文件,例如能够在 Vue 文件中使用 this.$_

    ¥For plain JavaScript libraries like Lodash, which don’t need any initialization prior to their usage. Lodash, for example, might make sense to use as a boot file only if you want to inject Vue prototype with it, like being able to use this.$_ inside your Vue files.

启动文件的使用(Usage of boot files)

¥Usage of boot files

第一步始终是使用 Quasar CLI 生成一个新的启动文件:

¥The first step is always to generate a new boot file using Quasar CLI:

$ quasar new boot <name> [--format ts]

其中 <name> 应该替换为适合你的启动文件的名称。

¥Where <name> should be exchanged by a suitable name for your boot file.

此命令会创建一个新文件:/src/boot/<name>.js 包含以下内容:

¥This command creates a new file: /src/boot/<name>.js with the following content:

// import something here

// "async" is optional!
// remove it if you don't need it
export default async defineBoot(({ /* app, router, store */ }) => {
  // something to do
})

你还可以返回一个 Promise:

¥You can also return a Promise:

// import something here

export default defineBoot(({ /* app, router, store */ }) => {
  return new Promise((resolve, reject) => {
    // do something
  })
})

提示

如果你不需要默认导出,可以将其从启动文件中省略。在这些情况下,你无需访问 “app”、“router”、“store” 等。

¥The default export can be left out of the boot file if you don’t need it. These are the cases where you don’t need to access the “app”, “router”, “store” and so on.

现在,你可以根据启动文件的预期用途向该文件添加内容。

¥You can now add content to that file depending on the intended use of your boot file.

不要忘记,你的默认导出必须是一个函数。但是,如果启动文件暴露了一些内容以供以后使用,你可以拥有任意数量的命名导出。在这种情况下,你可以在应用的任何位置导入这些命名导出中的任何一个。

¥Do not forget that your default export needs to be a function. However, you can have as many named exports as you want, should the boot file expose something for later usage. In this case, you can import any of these named exports anywhere in your app.

最后一步是告诉 Quasar 使用你的新启动文件。为此,你需要在 /quasar.config 文件中添加该文件:

¥The last step is to tell Quasar to use your new boot file. For this to happen you need to add the file in the /quasar.config one:

boot: [
  // references /src/boot/<name>.js
  '<name>'
]

构建 SSR 应用时,你可能希望某些启动文件仅在服务器或客户端上运行,在这种情况下,你可以像下面这样操作:

¥When building a SSR app, you may want some boot files to run only on the server or only on the client, in which case you can do so like below:

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
  }
]

如果你想要从 node_modules 中指定启动文件,可以通过在路径前添加 ~(波浪号)来实现:

¥In case you want to specify boot files from node_modules, you can do so by prepending the path with ~ (tilde) character:

boot: [
  // boot file from an npm package
  '~my-npm-package/some/file'
]

如果你希望仅为特定构建类型将启动文件注入到你的应用中:

¥If you want a boot file to be injected into your app only for a specific build type:

boot: [
  ctx.mode.electron ? 'some-file' : ''
]

重定向到另一个页面(Redirecting to another page)

¥Redirecting to another page

警告

重定向时请多加注意,因为你可能会将应用配置为进入无限重定向循环。

¥Please be mindful when redirecting as you might configure the app to go into an infinite redirect loop.

export default defineBoot(({ urlPath, redirect }) => {
  // ...
  const isAuthorized = // ...
  if (!isAuthorized && !urlPath.startsWith('/login')) {
    redirect({ path: '/login' })
    return
  }
  // ...
})

redirect() 方法接受字符串(完整 URL)或 Vue Router 位置字符串或对象。在服务器端渲染 (SSR) 中,它可以接收第二个参数,该参数应为任何用于重定向浏览器的 HTTP 状态码(3xx 代码)的数字。

¥The redirect() method accepts a String (full URL) or a Vue Router location String or Object. On SSR it can receive a second parameter which should be a Number for any of the HTTP STATUS codes that redirect the browser (3xx ones).

// Examples for redirect() with a Vue Router location:
redirect('/1') // Vue Router location as String
redirect({ path: '/1' }) // Vue Router location as Object

// Example for redirect() with a URL:
redirect('https://quasar.nodejs.cn')

IMPORTANT!

Vue 路由位置(字符串或对象形式)不是指 URL 路径(和哈希值),而是指你定义的实际 Vue 路由路由。所以不要在其中添加 publicPath;如果你使用的是 Vue Router 哈希模式,也不要在其中添加哈希值。

¥The Vue Router location (in String or Object form) does not refer to URL path (and hash), but to the actual Vue Router routes that you have defined. So don’t add the publicPath to it and if you’re using the Vue Router hash mode then don’t add the hash to it.


假设我们定义了这个 Vue Router 路由:

¥
Let’s say that we have this Vue Router route defined:

{
  path: '/one',
  component: PageOne
}


那么,无论我们的 publicPath 如何,我们都可以像这样调用 redirect()

¥
Then regardless of our publicPath we can call redirect() like this:

// publicPath: /wiki; vueRouterMode: history
redirect('/one') // good way
redirect({ path: '/one' }) // good way
redirect('/wiki/one') // WRONG!

// publicPath: /wiki; vueRouterMode: hash
redirect('/one') // good way
redirect({ path: '/one' }) // good way
redirect('/wiki/#/one') // WRONG!

// no publicPath; vueRouterMode: hash
redirect('/one') // good way
redirect({ path: '/one' }) // good way
redirect('/#/one') // WRONG!

正如前文所述,启动文件的默认导出可以返回 Promise。如果此 Promise 因包含 “url” 属性的对象而被拒绝,则 Quasar CLI 会将用户重定向到该 URL:

¥As it was mentioned in the previous sections, the default export of a boot file can return a Promise. If this Promise gets rejected with an Object that contains a “url” property, then Quasar CLI will redirect the user to that URL:

export default defineBoot(({ urlPath }) => {
  return new Promise((resolve, reject) => {
    // ...
    const isAuthorized = // ...
    if (!isAuthorized && !urlPath.startsWith('/login')) {
      // the "url" param here is of the same type
      // as for "redirect" above
      reject({ url: '/login' })
      return
    }
    // ...
  })
})

或者更简单的等效方法:

¥Or a simpler equivalent:

export default defineBoot(() => {
  // ...
  const isAuthorized = // ...
  if (!isAuthorized && !urlPath.startsWith('/login')) {
    return Promise.reject({ url: '/login' })
  }
  // ...
})

Quasar App 流程(Quasar App Flow)

¥Quasar App Flow

为了更好地理解启动文件的工作原理及其功能,你需要了解你的网站/应用如何启动:

¥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 已初始化(组件、指令、插件、Quasar 国际化、Quasar 图标集)

    ¥Quasar is initialized (components, directives, plugins, Quasar i18n, Quasar icon sets)

  2. Quasar Extras 已导入(Roboto 字体 - 如果使用的话,还有图标、动画等)

    ¥Quasar Extras get imported (Roboto font – if used, icons, animations, …)

  3. Quasar CSS 和你应用的全局 CSS 均已导入

    ¥Quasar CSS & your app’s global CSS are imported

  4. App.vue 已加载(尚未使用)

    ¥App.vue is loaded (not yet being used)

  5. Pinia(如果使用)已注入到 Vue 应用实例中

    ¥Pinia (if using) is injected into the Vue app instance

  6. 路由已导入(在 src/router 中)

    ¥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

  10. (如果使用 Electron 模式)Electron 被导入并注入到 Vue 原型中

    ¥(if on Electron mode) Electron is imported and injected into Vue prototype

  11. (如果使用 Cordova 模式)监听 “deviceready” 事件,然后才继续执行以下步骤

    ¥(if on Cordova mode) Listening for “deviceready” event and only then continuing with following steps

  12. 使用根组件实例化 Vue 并附加到 DOM

    ¥Instantiating Vue with root component and attaching to DOM

引导文件示例(Examples of boot files)

¥Examples of boot files

Axios(Axios)

import { defineBoot } from '#q-app/wrappers'
import axios from 'axios'

const api = axios.create({ baseURL: 'https://api.example.com' })

export default defineBoot(({ app }) => {
  // for use inside Vue files (Options API) through this.$axios and this.$api

  app.config.globalProperties.$axios = axios
  // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
  //       so you won't necessarily have to import axios in each vue file

  app.config.globalProperties.$api = api
  // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
  //       so you can easily perform requests against your app's API
})

export { axios, api }

vue-i18n(vue-i18n)

import { defineBoot } from '#q-app/wrappers'
import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'

export default defineBoot(({ app }) => {
  // Create I18n instance
  const i18n = createI18n({
    locale: 'en-US',
    messages
  })

  // Tell app to use the I18n instance
  app.use(i18n)
})

路由身份验证(Router authentication)

¥Router authentication

某些启动文件可能需要干扰 Vue Router 配置:

¥Some boot files might need to interfere with Vue Router configuration:

import { defineBoot } from '#q-app/wrappers'

export default defineBoot(({ router, store }) => {
  router.beforeEach((to, from, next) => {
    // Now you need to add your authentication logic here, like calling an API endpoint
  })
})

从启动文件访问数据(Accessing data from boot files)

¥Accessing data from boot files

有时,你希望在无法访问根 Vue 实例的文件中访问你在启动文件中配置的数据。

¥Sometimes you want to access data that you configure in your boot file in files where you don’t have access to the root Vue instance.

幸运的是,由于启动文件只是普通的 JavaScript 文件,你可以根据需要向启动文件添加任意数量的命名导出。

¥Fortunately, because boot files are just normal JavaScript files you can add as many named exports to your boot file as you want.

让我们以 Axios 为例。有时,你希望在 JavaScript 文件中访问 Axios 实例,但无法访问根 Vue 实例。要解决此问题,你可以在启动文件中导出 Axios 实例,然后将其导入到其他位置。

¥Let’s take the example of Axios. Sometimes you want to access your Axios instance inside your JavaScript files, but you cannot access the root Vue instance. To solve this you can export the Axios instance in your boot file and import it elsewhere.

考虑使用以下 axios 启动文件:

¥Consider the following boot file for axios:

axios boot file (src/boot/axios.js)

import { defineBoot } from '#q-app/wrapper'
import axios from 'axios'

// We create our own axios instance and set a custom base URL.
// Note that if we wouldn't set any config here we do not need
// a named export, as we could just `import axios from 'axios'`
const api = axios.create({
  baseURL: 'https://api.example.com'
})

// for use inside Vue files through this.$axios and this.$api
// (only in Vue Options API form)
export default defineBoot(({ app }) => {
  app.config.globalProperties.$axios = axios
  app.config.globalProperties.$api = api
})

// Here we define a named export
// that we can later use inside .js files:
export { axios, api }

在任何 JavaScript 文件中,你都可以像这样导入 axios 实例。

¥In any JavaScript file, you’ll be able to import the axios instance like this.

// we import one of the named exports from src/boot/axios.js
import { api } from 'boot/axios'

语法延伸:ES6 导入, ES6 导出.

¥Further reading on syntax: ES6 import, ES6 export.