为什么捐赠
API 浏览器
联系站长
App Extension Install API

本页面介绍 /ae/src/install.js|ts 文件,它仅在 App Extension 安装时执行。并非所有 App Extension 都需要 install 脚本——这是一个可选步骤。

文件的基本结构示例:

/ae/src/install.js (or .ts)

import { defineInstallScript } from '#q-app'

// can be async
export default defineInstallScript((/* api */) => {})

API 参数

api.ctx

/quasar.config 文件中的 ctx 相同。

api.ctx example:

{
  appPaths: {
    cliDir: '...absolute path of it',
    appDir: '...absolute path of it',
    srcDir: '...absolute path of it',
    publicDir: '...absolute path of it',
    pwaDir: '...absolute path of it',
    ssrDir: '...absolute path of it',
    cordovaDir: '...absolute path of it',
    capacitorDir: '...absolute path of it',
    electronDir: '...absolute path of it',
    bexDir: '...absolute path of it',
    quasarConfigFilename: '...absolute path of the quasar.config file',
    quasarConfigInputFormat: 'js', // or 'ts'
    resolve: {
      cli: (...paths) => theAbsolutePathToCliDir,
      app: (...paths) => theAbsolutePathToAppDir,
      src: (...paths) => theAbsolutePathToAppSrcDir,
      public: (...paths) => theAbsolutePathToPublicDir,
      pwa: (...paths) => theAbsolutePathToAppSrcPwaDir,
      ssr: (...paths) => theAbsolutePathToAppSrcSsrDir,
      cordova: (...paths) => theAbsolutePathToAppSrcCordovaDir,
      capacitor: (...paths) => theAbsolutePathToAppSrcCapacitorDir,
      electron: (...paths) => theAbsolutePathToAppSrcElectronDir,
      bex: (...paths) => theAbsolutePathToAppSrcBexDir
    }
  }
}

api.extId

包含此 App Extension 的 ext-id(字符串)。

api.prompts

一个对象,包含此 App Extension 安装时用户对提示问题的回答。更多关于 prompts 的信息,请参考 Prompts API

api.resolve

解析运行此 App Extension 的应用中的路径。无需自己导入 path 并解析路径。

// resolves to root of app
api.resolve.app('src/my-file.js')

// resolves to root/src of app
api.resolve.src('my-file.js')

// resolves to root/public of app
api.resolve.public('my-image.png')

// resolves to root/src-pwa of app
api.resolve.pwa('some-file.js')

// resolves to root/src-ssr of app
api.resolve.ssr('some-file.js')

// resolves to root/src-cordova of app
api.resolve.cordova('config.xml')

// resolves to root/src-electron of app
api.resolve.electron('some-file.js')

// resolves to root/src-bex of app
api.resolve.bex('some-file.js')

api.appDir

包含运行此 App Extension 的应用根目录的完整路径(字符串)。

api.logger

一个作用域为当前 App Extension 的日志工具。每个方法的输出都会标记 AE (<extId>),方便用户识别哪行日志来自哪个扩展。

api.logger.log('hello') // 绿色横幅行
api.logger.warn('careful') // 黄色横幅警告
api.logger.fatal('boom') // 红色横幅错误;以退出码 1 退出
api.logger.tip('try foo') // TIP 标签提示行
api.logger.info('synced') // INFO 标签行
api.logger.info('synced', 'SYNC') // 自定义标签文本替代 INFO
api.logger.success('built')
api.logger.error('oh no')
api.logger.warning('hmm')

const finish = api.logger.progress({
  tool: 'ssg',
  waitAction: 'building',
  doneAction: 'built'
})
// ...稍后
finish() // 打印 DONE 行及耗时

api.logger.dot // 辅助方法打印的圆点字符

api.hasTypescript

/**
 * @return {Promise<boolean>} host project has TypeScript active or not
 */
await api.hasTypescript()

api.getStorePackageName

/**
 * @return {Promise<string|undefined>} 'pinia' | 'vuex' | undefined
 */
await api.getStorePackageName()

api.getNodePackagerName

/**
 * @return {Promise<'npm' | 'yarn' | 'pnpm' | 'bun'>}
 */
await api.getNodePackagerName()

api.compatibleWith

通过 semver 条件确保 App Extension 与宿主应用中安装的包兼容。

如果 semver 条件不满足,Quasar CLI 将报错并终止执行。

semver 条件示例:'1.x || >=2.5.0 || 5.0.0 - 7.2.3'

/**
 * @param {string} packageName
 * @param {string} semverCondition
 */
api.compatibleWith(packageName, '3.x')
A more complex example:

api.compatibleWith('@quasar/app-vite', '^3.0.0-rc.1')

api.hasPackage

通过 semver 条件判断宿主应用中是否安装了某个包。

semver 条件示例:'1.x || >=2.5.0 || 5.0.0 - 7.2.3'

/**
 * @param {string} packageName
 * @param {string} (optional) semverCondition
 * @return {boolean} package is installed and meets optional semver condition
 */
if (api.hasPackage('vuelidate')) {
  // hey, this app has it (any version of it)
}
if (api.hasPackage('quasar', '^2.0.0')) {
  // hey, this app has Quasar UI v2 installed
}

api.hasExtension

检查另一个 app extension 是否已通过 npm 安装且 Quasar CLI 已调用它。

/**
 * Check if another app extension is installed
 *
 * @param {string} extId
 * @return {boolean} has the extension installed & invoked
 */
if (api.hasExtension(extId)) {
  // hey, we have it
}

api.getPackageVersion

获取宿主应用中某个包的版本。

/**
 * @param {string} packageName
 * @return {string|undefined} version of app's package
 */
console.log(api.getPackageVersion(packageName))
// output examples:
//   1.1.3
//   undefined (when package not found)

api.extendPackageJson

用于扩展 package.json 的辅助方法。如果指定了已存在的属性,将会覆盖它们。

/**
 * @param {object|string} extPkg - Object to extend with or relative path to a JSON file
 */
api.extendPackageJson({
  scripts: {
    electron: 'quasar dev -m electron'
  }
})

上面的示例向应用的 package.json 中添加了一个 npm 脚本,之后您就可以执行 yarn electron(或等效的 npm run electron)。

api.extendJsonFile

用新属性扩展一个 JSON 文件(深度合并)。如果指定了已存在的属性,将会覆盖它们。

/**
 * @param {string} file (relative path to app root folder)
 * @param {object} newData (Object to merge in)
 */
api.extendJsonFile('src/some.json', {
  newProp: 'some-value'
})

api.render

将 App Extension 模板中的文件夹渲染(复制)到应用的根目录中。保持与模板文件夹相同的目录结构。

如果某些文件在应用中已存在,则会询问用户是否覆盖。

需要一个相对于调用 render() 的文件所在目录的相对路径。

/**
 * Render a folder from extension templates into devland
 * Needs a path (to a folder) relative to the path of the file where render() is called
 *
 * @param {string} templatePath (relative path to folder to render in app)
 * @param {object} scope (optional; rendering scope variables)
 */
api.render('./path/to/a/template/folder')

文件名边界情况

如果您想渲染一个以点号开头的模板文件(如 .env),需要遵循特定的命名约定,因为 dotfile 在发布到 npm 时会被忽略:

# templates containing dotfiles must use an
# underscore instead of the dot in their names:

some-folder/_env

# When calling api.render('./some-folder'), this will be
# rendered in the project folder as:

/.env

如果您想渲染一个文件名确实以下划线开头的文件,则文件名必须以 __(两个下划线)开头:

some-folder/__my.css

# When calling api.render('./template'), this will be
# rendered in the project folder as:

/_my.css

使用 scope

您还可以通过 lodash/template 语法向待渲染的文件中注入决策逻辑。

示例:

src/install.js

// (my-folder is located in same folder as
// the file in which following call takes place)
api.render('./my-folder', {
  prompts: api.prompts
})

假设我们同时使用了 Prompts API 文件,它询问用户是否需要"Feature X"并将答案存储在名为"featureX"的变量中。

我们可以在渲染文件时根据条件决定文件内容。这样就不需要创建两个文件夹再根据条件选择渲染哪个了。

src/my-folder/some-file.js

<% if (prompts.featureX) { %>
const message = 'This is content when "Feature X" exists'
<% } else { %>
const message = 'This is content when we don\'t have "Feature X"'
<% } %>

可能性仅受限于您的想象力。

api.renderFile

与 api.render() 类似,区别在于此方法渲染单个文件。

/**
 * Render a file from extension template into devland
 * Needs a path (to a file) relative to the path of the file where renderFile() is called
 *
 * @param {string} relativeSourcePath (file path relative to the folder from which the install script is called)
 * @param {string} relativeTargetPath (file path relative to the root of the app -- including filename!)
 * @param {object} scope (optional; rendering scope variables)
 */
api.renderFile(
  './path/to/a/template/filename',
  'path/relative/to/app/root/filename',
  {
    prompts: api.prompts
  }
)

api.renderFile('./my-file.json', 'src/my-file.json')

api.getPersistentConf

获取此扩展的内部持久化配置。如果没有则返回空对象。

/**
 * @return {object} cfg
 */
api.getPersistentConf()

api.setPersistentConf

设置此扩展的内部持久化配置。如果已存在则会被覆盖。

/**
 * @param {object} cfg
 */
api.setPersistentConf({
  // ....
})

api.mergePersistentConf

深度合并到此扩展的内部持久化配置中。如果扩展还没有设置任何配置,这本质上等同于首次设置。

/**
 * @param {object} cfg
 */
api.mergePersistentConf({
  // ....
})

api.onExitLog

添加一条消息,在 App CLI 完成安装 App Extension 即将退出时打印。可以多次调用以注册多条退出日志。

/**
 * @param {string} msg
 */
api.onExitLog('Thanks for installing my awesome extension')