API 浏览器
入门套件等效

本指南适用于你需要创建一个本质上是 “入门套件” 的项目,它在官方入门套件的基础上添加一些内容(/quasar.config 文件配置、文件夹、文件、CLI 钩子)。这允许你让多个项目共享一个通用结构/逻辑(并且只需一个包来管理它们,而不必单独更改所有项目以匹配你的通用模式),并且还允许你与社区分享所有这些。

¥This guide is for when you want to create what essentially is a “starter kit” that adds stuff (/quasar.config file configuration, folders, files, CLI hooks) on top of the official starter kit. This allows you to have multiple projects sharing a common structure/logic (and only one package to manage them rather than having to change all projects individually to match your common pattern), and also allows you to share all this with the community.

提示

为了创建 App Extension 项目文件夹,请首先阅读 开发指南 > 简介

¥In order for creating an App Extension project folder, please first read the Development Guide > Introduction.

完整示例

要查看我们将要构建的示例,请前往 MyStarterKit 完整示例,这是包含此应用扩展程序的 GitHub 仓库。

¥To see an example of what we will build, head over to MyStarterKit full example, which is a github repo with this App Extension.

我们将创建一个示例应用扩展,其功能如下:

¥We’ll be creating an example App Extension which does the following:

  • 它提示用户希望此应用扩展安装哪些功能

    ¥it prompts the user what features it wants this App Extension to install

  • 根据他给出的答案,将文件渲染(复制)到托管文件夹

    ¥renders (copies) files into the hosting folder, according to the answers he gave

  • 它扩展了/quasar.config 文件

    ¥it extends the /quasar.config file

  • 它扩展了 Webpack 配置

    ¥it extends the Webpack configuration

  • 它使用应用扩展钩子 (onPublish)

    ¥it uses an App Extension hook (onPublish)

  • 它在应用扩展卸载时删除添加的文件

    ¥it removes the added files when the App Extension gets uninstalled

  • 它使用提示来定义应用扩展的功能

    ¥it uses the prompts to define what the App Extension does

结构(The structure)

¥The structure

出于本示例的目的,我们将创建以下文件夹结构:

¥For the intents of this example, we’ll be creating the following folder structure:

README.md
package.json
my-starter-kit-boot.js
README.md
tasks.md
serviceA.js
serviceB.js
index.js
# Described in Index API
install.js
# Described in Install API
prompts.js
# Described in Prompts API
uninstall.js
# Described in Uninstall API

安装脚本(The install script)

¥The install script

以下安装脚本仅将文件渲染到托管应用中。请注意上面的 src/templates 文件夹,我们决定将模板保存在那里。

¥The install script below is only rendering files into the hosted app. Notice the src/templates folder above, where we decided to keep these templates.

src/install.js

export default function (api) {
  // (Optional!)
  // Quasar compatibility check; you may need
  // hard dependencies, as in a minimum version of the "quasar"
  // package or a minimum version of Quasar App CLI
  api.compatibleWith('quasar', '^2.0.0')

  if (api.hasVite === true) {
    api.compatibleWith('@quasar/app-vite', '^2.0.0')
  }
  else { // api.hasWebpack === true
    api.compatibleWith('@quasar/app-webpack', '^4.0.0')
  }

  // We render some files into the hosting project

  if (api.prompts.serviceA) {
    api.render('./templates/serviceA')
  }

  if (api.prompts.serviceB) {
    // we supply interpolation variables
    // to the template
    api.render('./templates/serviceB', {
      productName: api.prompts.productName
    })
  }

  // we always render the following template:
  api.render('./templates/common-files')
}

请注意,我们使用提示来决定要渲染到托管项目中的内容。此外,如果用户选择了 “服务 B”,那么我们还会在渲染服务 B 的文件时使用 “productName”。

¥Notice that we use the prompts to decide what to render into the hosting project. Furthermore, if the user has selected “service B”, then we’ll also have a “productName” that we can use when we render the service B’s file.

索引脚本(The index script)

¥The index script

我们在索引脚本中做了一些事情,例如扩展 /quasar.config 文件,挂接到众多 Index API 钩子之一(在本例中为 onPublish),以及链接 Webpack 配置:

¥We do a few things in the index script, like extending the /quasar.config file, hooking into one of the many Index API hooks (onPublish in this case), and chaining the Webpack configuration:

src/index.js

export default function (api) {
  // (Optional!)
  // Quasar compatibility check; you may need
  // hard dependencies, as in a minimum version of the "quasar"
  // package or a minimum version of Quasar App CLI
  api.compatibleWith('quasar', '^2.0.0')

  if (api.hasVite === true) {
    api.compatibleWith('@quasar/app-vite', '^2.0.0')
  }
  else { // api.hasWebpack === true
    api.compatibleWith('@quasar/app-webpack', '^4.0.0')
  }

  // Here we extend the /quasar.config file;
  // (extendQuasarConf() will be defined later in this tutorial, continue reading)
  api.extendQuasarConf(extendQuasarConf)

  // Here we register the onPublish hook,
  // only if user answered that he wants the publishing service
  if (api.prompts.publishService) {
    // onPublish() will be defined later in this tutorial, continue reading
    api.onPublish(onPublish)
  }

  if (api.hasVite === true) {
    api.extendViteConf(extendVite)
  }
  else { // api.hasWebpack === true
    // we add/change/remove something in the Webpack configuration
    // (chainWebpack() will be defined later in this tutorial, continue reading)
    api.chainWebpack(chainWebpack)
  }

  // there's lots more hooks that you can use...
}

以下是 extendQuasarConf 定义的示例:

¥Here’s an example of extendQuasarConf definition:

function extendQuasarConf (conf, api) {
  conf.extras.push('ionicons-v4')
  conf.framework.iconSet = 'ionicons-v4'

  //
  // We register a boot file. User does not need to tamper with it,
  // so we keep it into the App Extension code:
  //

  // make sure my-ext boot file is registered
  conf.boot.push('~quasar-app-extension-my-starter-kit/src/boot/my-starter-kit-boot.js')

  // @quasar/app-vite does not need this
  if (api.hasVite !== true) {
    // make sure boot file get transpiled
    conf.build.webpackTranspileDependencies.push(/quasar-app-extension-my-starter-kit[\\/]src/)
  }
}

onPublish 函数:

¥The onPublish function:

function onPublish (api, { arg, distDir }) {
  // this hook is called when "quasar build --publish" is called

  // your publish logic here...
  console.log('We should publish now. But maybe later? :)')

  // are we trying to publish a Cordova app?
  if (api.ctx.modeName === 'cordova') {
    // do something
  }
}

extendVite 函数:

¥The extendVite function:

function extendVite (viteConf, { isClient, isServer }, api) {
  // viteConf is a Vite config object generated by Quasar CLI
}

chainWebpack 函数:

¥The chainWebpack function:

function chainWebpack (cfg, { isClient, isServer }, api) {
  // cfg is a Webpack chain Object;
  // docs on how to use it: webpack-chain docs (https://github.com/neutrinojs/webpack-chain)
}

卸载脚本(The uninstall script)

¥The uninstall script

当应用扩展被卸载时,我们需要进行一些清理。但请注意从应用空间中删除的内容!可能仍然需要一些文件。如果你决定使用卸载脚本,请务必谨慎操作。

¥When the App Extension gets uninstall, we need to do some cleanup. But beware what you delete from the app-space! Some files might still be needed. Proceed with extreme care, if you decide to have an uninstall script.

// we yarn added it to our App Extension,
// so we can import the following:
import rimraf from 'rimraf'

export default function (api) {
  // Careful when you remove folders!
  // You don't want to delete files that are still needed by the Project,
  // or files that are not owned by this app extension.

  // Here, we could also remove the /src/services folder altogether,
  // but what if the user has added other files into this folder?

  if (api.prompts.serviceA) {
    // we added it on install, so we remove it
    rimraf.sync(api.resolve.src('services/serviceA.js'))
  }

  if (api.prompts.serviceB) {
    // we added it on install, so we remove it
    rimraf.sync(api.resolve.src('services/serviceB.js'))
  }

  // we added it on install, so we remove it
  rimraf.sync(api.resolve.app('some-folder'))
  // warning... we've added this folder, but what if the
  // developer added more files into this folder???
}

请注意,我们正在请求 rimraf npm 包。这意味着我们已通过 yarn/npm 将其添加到我们的应用扩展项目中。

¥Notice that we are requesting rimraf npm package. This means that we yarn/npm added it into our App Extension project.