Quasar CLI with Webpack - @quasar/app-webpack
启动文件

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

由于您无法访问/main.js文件(因此Quasar CLI可以无缝初始化并为SPA/PWA/SSR/Cordova/Electron构建相同的代码库)Quasar提供了该问题的优雅解决方案——允许用户定义所谓的启动文件。

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

这种方法存在一个主要问题。 随着项目的不断发展,你的main.js文件非常容易混乱,难以维护,这与Quasars鼓励开发人员编写可维护且优雅的跨平台应用程序的理念相违背。

使用启动文件,可以将您的每个依赖项分解为自包含的、易于维护的文件。 可以轻易禁用任何启动文件,甚至可以通过quasar.config.js配置来确定哪些启动文件可以进入构建。

解剖一个启动文件

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

属性名称说明
appVue应用实例
router来自’src/router/index.js’的Vue路由器实例
store应用Pinia或Vuex存储的实例 - 只有当您的项目使用Pinia(您有src/stores)或Vuex(您有src/store)时才会传递store
ssrContext如果为SSR构建,则仅在服务器端可用。更多信息
urlPathURL的路径名(路径+搜索)部分;在客户端(仅在客户端),它也包含哈希值。
publicPath配置的公共路径。
redirect重定向到另一个URL的调用函数。接受字符串(完整的URL)或Vue路由器位置对象。
export default ({ app, router, store }) => {
  // something to do
}

启动文件也可以是异步的:

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

您可以使用boot帮助程序包装返回的函数,以获得更好的IDE自动完成体验(通过Typescript):

import { boot } from 'quasar/wrappers'

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

注意我们正在使用ES6解构赋值。只分配你实际需要/使用的东西。

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

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

export default 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 Vuex store instantiated,
  //  - 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),
  //  - ...
}

何时使用启动文件

WARNING

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

启动文件实现了一个特殊的目的:它们在应用程序的Vue根组件被实例化之前运行代码,同时允许您访问某些变量,如果需要初始化库、干预Vue路由器、注入Vue原型或注入Vue应用程序的根实例。

适当使用启动文件的示例

  • 你的Vue插件有安装说明,就像需要调用Vue.use()一样。
  • 你的Vue插件需要实例化添加到根实例的数据 - 一个例子是vue-i18n
  • 您想使用app.mixin()添加全局mixin。
  • 您想添加一些东西到Vue应用globalProperties以方便访问 - 一个例子是在Vue文件中方便地使用this.$axios (Options API) 而不是在每个这样的文件中导入Axios。
  • 你想干涉路由器 - 一个例子是使用router.beforeEach进行认证
  • 你想干涉Pinia或Vuex存储实例 - 一个例子是使用vuex-router-sync软件包
  • 配置库的方面 - 一个例子是创建一个带有基本URL的Axios实例;你可以将它注入到Vue原型中和/或导出它(这样你就可以从应用程序中的任何其他地方导入实例)

不需要使用启动文件的示例

  • 对于像Lodash这样的普通JavaScript库,在使用之前不需要任何初始化。例如,Lodash可能只有在你想注入Vue原型时(例如可以在你的Vue文件中使用this.$_)用作启动文件才有意义。

使用启动文件

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

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

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

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

// import something here

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

You can also return a Promise:

// import something here

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

TIP

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

您现在可以将内容添加到该文件,具体取决于您的启动文件的预期用途。

不要忘记,您的默认导出需要是一个功能。 但是,如果启动文件公开某些内容以供日后使用,则你可以根据需要拥有任意数量的命名导出。 在这种情况下,您可以在应用程序的任何位置导入任何这些命名导出。

最后一步是告诉Quasar使用你的新启动文件。 为了做到这一点,你需要在/quasar.config.js中添加启动文件

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

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

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指定启动文件,可以通过在路径前加上~(波浪号)字符来实现:

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

如果您希望仅针对特定的构建类型将启动文件注入您的应用程序:

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

重定向到另一个页面

WARNING

在重定向时请谨慎,因为你可能会配置应用程序进入无限重定向循环。

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

redirect()方法接受一个字符串(完整的URL)或一个Vue Router的位置字符串或对象。在SSR上,它可以接受第二个参数,它应该是一个数字,用于重定向浏览器的任何HTTP STATUS代码(3xx代码)。

// 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.dev')

重要提示!

Vue Router位置(字符串或对象形式)不是指URL路径(和哈希),而是指您定义的实际Vue Router路由。 因此不要将publicPath添加到其中,如果您使用的是Vue Router哈希模式,则不要将哈希添加到其中。


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

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


然后不管我们的publicPath是什么,我们都可以这样调用redirect()

// 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:

export default ({ 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
    }
    // ...
  })
}

或更简单的等效代码:

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

Quasar应用程序流程

为了更好地理解启动文件的功能和用途,您需要了解您的网站/应用程序是如何启动的:

  1. Quasar已初始化(组件、指令、插件、Quasar i18n、Quasar图标集)
  2. Quasar Extras被导入(Roboto字体 - 如果使用,图标,动画…)
  3. Quasar CSS和您的应用程序的全局CSS已导入
  4. App.vue被加载(尚未被使用)
  5. Store被导入(如果在src/store中使用Vuex或在src/stores中使用Pinia)
  6. Pinia(如果使用)被注入到Vue应用实例中
  7. Router已导入(在src/router中)
  8. 启动文件已导入
  9. Router默认导出功能已执行
  10. 启动文件会执行其默认导出功能
  11. (如果在Electron模式下)Electron 被导入并注入Vue原型
  12. (如果在Cordova模式下)收听“deviceready”事件,然后继续执行以下步骤
  13. 使用根组件实例化Vue并附加到DOM

启动文件的例子

Axios

import { boot } from 'quasar/wrappers'
import axios from 'axios'

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

export default boot(({ app }) => {
  // 通过this.$axios和this.$API在Vue文件(Options API)内使用

  app.config.globalProperties.$axios = axios
  // ^ ^ ^ 这将允许你使用this.$axios(Vue Options API形式)
  //       所以你不需要在每个vue文件中导入axios

  app.config.globalProperties.$api = api
  // ^ ^ ^ 这将允许您使用this.$api(Vue Options API形式)
  //       这样您就可以轻松地根据应用程序的api执行请求
})

export { axios, api }

vue-i18n

import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'

export default boot(({ app }) => {
  // 创建I18n实例
  const i18n = createI18n({
    locale: 'en-US',
    messages
  })

  // 告诉应用程序使用I18n实例
  app.use(i18n)
})

路由验证

一些插件可能需要干涉Vue路由器配置:

import { boot } from 'quasar/wrappers'

export default boot(({ router, store }) => {
  router.beforeEach((to, from, next) => {
    // 现在您需要在这里添加验证逻辑,比如调用一个API
  })
})

从启动文件访问数据

有时,您想访问您在启动文件中配置的数据,这些数据在您无权访问根Vue实例的文件中。

幸运的是,因为启动文件只是普通的JavaScript文件,所以您可以根据需要将任意数量的导出添加到您的启动文件。

以Axios为例。 有时候你想要在你的JavaScript文件中访问你的Axios实例,但是你不能访问根Vue实例。 为了解决这个问题,你可以在你的启动文件中导出Axios实例并将其导入到别处。

考虑下面的axios启动文件:

// axios启动文件(src/boot/axios.js)

import axios from 'axios'

// 我们创建我们自己的axios实例并设置一个自定义的基本URL。
// 请注意,如果我们不在这里设置任何配置,我们不需要
// 一个命名的导出,因为我们可以`import axios from 'axios'`
const api = axios.create({
  baseURL: 'https://api.example.com'
})

// 在Vue文件中通过this.$axios和this.$api来使用
// (仅在Vue Options API形式下)
export default ({ app }) => {
  app.config.globalProperties.$axios = axios
  app.config.globalProperties.$api = api
}

// 这里我们定义一个命名的导出,
// 然后我们后面可以使用这个内部的.js文件:
export { axios, api }

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

// 我们从src/boot/axios.js中导入一个命名的导出
import { api } from 'boot/axios'

进一步阅读语法:ES6导入ES6导出