API 浏览器
Quasar CLI with Vite - @quasar/app-vite
Quasar CLI 与 Vite 升级指南

@quasar/app-vite v2(@quasar/app-vite v2)

应用扩展所有者须知(A note to App Extensions owners)

¥A note to App Extensions owners

你可能想要发布 Quasar 应用扩展的新版本,以支持新的 @quasar/app-vite。如果你没有修改 quasar.config 配置,那么只需更改以下内容即可:

¥You might want to release new versions of your Quasar App Extensions with support for the new @quasar/app-vite. If you are not touching the quasar.config configuration, then it will be as easy as just changing the following:

api.compatibleWith(
  '@quasar/app-vite',
- '^1.0.0'
+ '^1.0.0 || ^2.0.0'
)

显著的重大变化(Notable breaking changes)

¥Notable breaking changes

  • 最低 Node.js 版本现在是 18(主要是因为 Vite 6)

    ¥Minimum Node.js version is now 18 (mainly due to Vite 6)

  • 我们已将整个 Quasar 项目文件夹转换为 ESM 样式,因此许多默认项目文件现在需要 ESM 代码(尽管支持使用 .cjs 作为这些文件的扩展名,但如果你不想更改任何内容,则很可能需要重命名扩展名)。一个例子是 /quasar.config.js 文件,现在它也被假定为 ESM(因此,如果你仍然需要 CommonJs 文件,请从 .js 更改为 .cjs)。

    ¥We have shifted towards an ESM style for the whole Quasar project folder, so many default project files now require ESM code (although using .cjs as an extension for these files is supported, but you will most likely need to rename the extension should you not wish to change anything). One example is the /quasar.config.js file which now it’s assumed to be ESM too (so change from .js to .cjs should you still want a CommonJs file).

  • 由于 @quasar/testing-* 软件包的最新更新,“test” 命令已被移除。参见 此处

    ¥The “test” cmd was removed due to latest updates for @quasar/testing-* packages. See here

  • “clean” 命令已重新设计。在升级后的 Quasar 项目文件夹中输入 “quasar clean -h” 以获取更多信息。

    ¥The “clean” cmd has been re-designed. Type “quasar clean -h” in your upgraded Quasar project folder for more info.

  • TypeScript 检测基于 TS 格式的 quasar.config 文件 (quasar.config.ts) 和 tsconfig.json 文件的存在。

    ¥TypeScript detection is based on the quasar.config file being in TS form (quasar.config.ts) and tsconfig.json file presence.

  • TypeScript tsconfig.json 预设已被自动生成的 .quasar/tsconfig.json 文件取代。这更加灵活,并带来了新功能,更多内容见下文。

    ¥TypeScript tsconfig.json presets have been replaced by an auto-generated .quasar/tsconfig.json file. This is more flexible and brings new features, more on this below.

  • 壮举+重构(app-vite):能够同时运行多种模式 + dev/build(非常耗时!)

    ¥feat+refactor(app-vite): ability to run multiple modes + dev/build simultaneously (huge effort!)

  • SSR 和 Electron 模式现在以 ESM 格式构建。

    ¥SSR and Electron modes now build in ESM format.

  • 新的 BEX 模式,具有重要的新功能和易用性(现在包括 Chrome 的 HMR!)。

    ¥New BEX mode with significant new capabilities and ease of use (includes HMR for Chrome now!).

  • 不再支持我们内部的 linting 系统(quasar.config 文件 > eslint)。应改用 vite-plugin-checker

    ¥Dropped support for our internal linting system (quasar.config file > eslint). Should use vite-plugin-checker instead.

  • 已放弃对 Vuex 的支持。Pinia 已经成为 Vue 3 的官方应用商店一段时间了。Vuex 在 app-vite v1 中已弃用,并且与新架构存在问题,因此现已移除。你仍然可以像使用任何 Vue 插件一样使用 Vuex,但你必须自行管理所有内容(安装 store、hydration、启动文件中不包含 store 参数等),并且不会获得 Quasar CLI 的任何支持。你可能需要修补 Vuex 才能使其与 TypeScript 兼容。我们建议迁移到 Pinia。

    ¥Dropped support for Vuex. Pinia has been the official store for Vue 3 for a while now. Vuex was deprecated in app-vite v1 and it had problems with the new structure, so it’s now removed. You can still use Vuex as any Vue plugin, but you will have to manage everything(installing the store, hydration, no store parameter in boot files, etc.) yourself and will not receive any support from Quasar CLI. You will likely have to patch Vuex in order to get it working with TypeScript. We recommend migrating to Pinia.

  • 我们将在下文中详细介绍每种 Quasar 模式的更多重大变化。

    ¥We will detail more breaking changes for each of the Quasar modes below.

新功能亮点(Highlights on what’s new)

¥Highlights on what’s new

以下部分工作已反向移植到旧版 @quasar/app-vite v1,但仍在此处发布以供读者参考。

¥Some of the work below has already been backported to the old @quasar/app-vite v1, but posting here for reader’s awareness.

  • 壮举(应用 vite):升级到 Vite 6

    ¥feat(app-vite): upgrade to Vite 6

  • 壮举(应用 vite):能够同时运行多个 quasar dev/build 命令(例如:可以同时运行 “quasar dev -m 电容”、“quasar dev -m ssr” 和 “quasar dev -m 电容 -T ios”)

    ¥feat(app-vite): ability to run multiple quasar dev/build commands simultaneously (example: can run “quasar dev -m capacitor” and “quasar dev -m ssr” and “quasar dev -m capacitor -T ios” simultaneously)

  • 壮举(应用 vite):整体更佳的 TS 类型

    ¥feat(app-vite): Better TS typings overall

  • refactor(app-vite):将 CLI 移植到 ESM 格式(这需要很大的努力!尤其要支持 Vite 6 和 SSR)

    ¥refactor(app-vite): port CLI to ESM format (major effort! especially to support Vite 6 and SSR)

  • 壮举(应用 vite):支持多种格式的 quasar.config 文件(.js、.mjs、.ts、.cjs)

    ¥feat(app-vite): support for quasar.config file in multiple formats (.js, .mjs, .ts, .cjs)

  • 壮举(应用 vite):改进 quasarConfOptions,为其生成类型,改进文档(修复:#14069)(#15945)

    ¥feat(app-vite): Improve quasarConfOptions, generate types for it, improve docs (fix: #14069) (#15945)

  • 壮举(应用 vite):如果从 quasar.config 文件导入的内容发生更改,则重新加载应用

    ¥feat(app-vite): reload app if one of the imports from quasar.config file changes

  • 壮举(应用 vite):TS 检测也应考虑 quasar.config 文件格式 (quasar.config.ts)。

    ¥feat(app-vite): TS detection should keep account of quasar.config file format too (quasar.config.ts)

  • 壮举(应用 vite):简写 CLI 命令 “quasar dev/build -m ios/android” 现在针对 Capacitor 模式,而不是 Cordova (2.0.0-beta.12+)。

    ¥feat(app-vite): The shorthand CLI command “quasar dev/build -m ios/android” is now targeting Capacitor mode instead of Cordova (2.0.0-beta.12+)

  • 壮举(应用 vite):支持使用 HTTPS 的 SSR 开发

    ¥feat(app-vite): support for SSR development with HTTPS

  • 壮举(应用 vite):env dotfiles 支持 #15303

    ¥feat(app-vite): env dotfiles support #15303

  • 壮举(应用 vite):新的 quasar.config 文件属性:build > envFolder(字符串)和 envFiles(字符串[])

    ¥feat(app-vite): New quasar.config file props: build > envFolder (string) and envFiles (string[])

  • 壮举(应用 vite):通过 quasar.config 文件更改应用 URL 时,重新打开浏览器(如果已配置)

    ¥feat(app-vite): reopen browser (if configured so) when changing app url through quasar.config file

  • 壮举与性能(应用 vite):更快更准确的算法来确定要使用的 Node 包管理器

    ¥feat&perf(app-vite): faster & more accurate algorithm for determining node package manager to use

  • 壮举(应用 vite):升级依赖

    ¥feat(app-vite): upgrade deps

  • 壮举(应用 vite):移除针对 Electron 6-8 命令行模板中错误的解决方法 (#15845)

    ¥feat(app-vite): remove workaround for bug in Electron 6-8 in cli templates (#15845)

  • 壮举(应用 vite):移除 Capacitor v5+ 的 bundleWebRuntime 配置

    ¥feat(app-vite): remove bundleWebRuntime config for Capacitor v5+

  • 壮举(应用 vite):默认使用 Workbox v7

    ¥feat(app-vite): use workbox v7 by default

  • 壮举(应用 vite):quasar.config > pwa > injectPwaMetaTags 现在也可以是一个函数:(({ pwaManifest, publicPath }) => 字符串);

    ¥feat(app-vite): quasar.config > pwa > injectPwaMetaTags can now also be a function: (({ pwaManifest, publicPath }) => string);

  • 壮举(应用 vite):quasar.config > build > htmlMinifyOptions

    ¥feat(app-vite): quasar.config > build > htmlMinifyOptions

  • 壮举(应用 vite):使用时查找 vue devtools 的开放端口;能够使用 vue devtools 运行多个 cli 实例

    ¥feat(app-vite): lookup open port for vue devtools when being used; ability to run multiple cli instances with vue devtools

  • perf(app-vite):根据宿主项目,SSR 渲染模板采用特定的 esm 或 cjs 格式;通过变量插值

    ¥perf(app-vite): SSR render-template in specific esm or cjs form, according to host project; interpolation by variable

  • perf(app-vite):仅验证 “dev” 命令的 quasar.conf 服务器地址

    ¥perf(app-vite): only verify quasar.conf server address for “dev” cmd

  • 壮举(应用 vite):为每个实例选择新的 Electron 检查端口

    ¥feat(app-vite): pick new electron inspect port for each instance

  • 壮举(应用 vite):Electron - 现在可以加载多个预加载脚本

    ¥feat(app-vite): Electron - can now load multiple preload scripts

  • refactor(app-vite):AE 支持 - 更好更高效的算法

    ¥refactor(app-vite): AE support - better and more efficient algorithms

  • 壮举(应用 vite):AE 支持 ESM 格式

    ¥feat(app-vite): AE support for ESM format

  • 壮举(应用 vite):AE 支持 TS 格式(通过构建步骤)

    ¥feat(app-vite): AE support for TS format (through a build step)

  • 壮举(应用 vite):AE API 新方法 -> hasTypescript() / hasLint() / getStorePackageName() / getNodePackagerName()

    ¥feat(app-vite): AE API new methods -> hasTypescript() / hasLint() / getStorePackageName() / getNodePackagerName()

  • 壮举(应用 vite):AE -> Prompts API(以及将提示默认导出的函数设置为异步的功能)

    ¥feat(app-vite): AE -> Prompts API (and ability for prompts default exported fn to be async)

  • refactor(app-vite):“clean” 命令现在的工作方式有所不同,因为 CLI 可以在同一个项目文件夹中以多个实例运行(开发或构建模式下的多种模式)。

    ¥refactor(app-vite): the “clean” cmd now works different, since the CLI can be run in multiple instances on the same project folder (multiple modes on dev or build)

  • 壮举(应用 vite):支持 Bun 作为包管理器 #16335

    ¥feat(app-vite): Support for Bun as package manager #16335

  • 壮举(应用 vite):默认 /src-ssr 模板 -> prod ssr -> on 错误,如果启用了调试,则打印错误堆栈

    ¥feat(app-vite): for default /src-ssr template -> prod ssr -> on error, print err stack if built with debugging enabled

  • 壮举(应用 vite):扩展 build > vitePlugins 表单(附加 { server?: boolean, client?: boolean } 参数)

    ¥feat(app-vite): extend build > vitePlugins form (additional { server?: boolean, client?: boolean } param

  • 壮举+重构(app-vite):BEX -> 完全重写并重新设计了 Quasar Bridge(包含大量新功能);从 BEX 清单本身自动推断后台脚本文件和内容脚本文件;能够编译你可能需要动态加载/注入的其他 js/ts 文件;打开弹窗时不再有 3 秒延迟;不再需要 “dom” 脚本(直接使用内容脚本);桥接器在应用 (/src) 中全局可用,可通过 $q 对象或 window.QBexBridge 访问。

    ¥feat+refactor(app-vite): BEX -> Completely rewrote & redesigned the Quasar Bridge (with a ton of new features); Automatically infer the background script file & the content script files from the bex manifest itself; Ability to compile other js/ts files as well that you might need to dynamically load/inject; No more 3s delay when opening the popup; No more “dom” script (use content script directly); The bridge is available globally in App (/src) through the $q object or window.QBexBridge

  • 壮举(应用 vite):适用于 Chrome 的带有 HMR(热模块重载)的 BEX

    ¥feat(app-vite): BEX with HMR (hot module reload) for Chrome

  • 壮举(应用 vite):支持从 build > extendsViteConf 返回覆盖

    ¥feat(app-vite): support returning overrides from build > extendViteConf

升级过程开始(Beginning of the upgrade process)

¥Beginning of the upgrade process

提示

如果你不确定是否不会误跳过任何建议的更改,你可以随时使用 @quasar/app-vite v2 搭建一个新的项目文件夹,然后轻松地从那里开始移植你的应用。大部分更改都涉及不同的项目文件夹配置文件,而大部分与 /src 文件无关。

¥If you are unsure that you won’t skip by mistake any of the recommended changes, you can scaffold a new project folder with the @quasar/app-vite v2 at any time and then easily start porting your app from there. The bulk of the changes refer to the different project folder config files and mostly NOT to your /src files.


$ yarn create quasar

When asked to "Pick Quasar App CLI variant", answer with: "Quasar App CLI with Vite 6 (v2)".

准备工作:

¥Preparations:

  • 如果使用 Quasar CLI (@quasar/cli) 的全局安装,请确保你使用的是最新版本。这是因为 quasar.config 文件支持多种格式。

    ¥If using the global installation of Quasar CLI (@quasar/cli), make sure that you have the latest one. This is due to the support of quasar.config file in multiple formats.

  • 再次强调,Node.js 的最低支持版本现在为 v18(请始终使用 Node.js 的 LTS 版本 - 版本越高越好)。

    ¥Again, we highlight that the minimum supported version of Node.js is now v18 (always use the LTS versions of Node.js - the higher the version the better).

  • @quasar/app-vite 条目上编辑 /package.json 并将其分配给 ^2.0.0

    ¥Edit your /package.json on the @quasar/app-vite entry and assign it ^2.0.0:

    /package.json

    "devDependencies": {
    - "@quasar/app-vite": "^1.0.0",
    + "@quasar/app-vite": "^2.0.0"
    }

    Then yarn/npm/pnpm/bun install.

  • /quasar.config.js 文件转换为 ESM 格式(推荐使用 ESM 格式,否则请将文件扩展名重命名为 .cjs 并使用 CommonJs 格式)。另请注意封装器导入的变化,稍后会详细介绍。

    ¥Convert your /quasar.config.js file to the ESM format (which is recommended, otherwise rename the file extension to .cjs and use CommonJs format). Also notice the wrappers import change, more on that later.

    /quasar.config.js file

    - const { configure } = require('quasar/wrappers')
    + import { defineConfig } from '#q-app/wrappers'
    
    - module.export = configure((ctx) => {
    + export default defineConfig((ctx) => {
        return {
          // ...
        }
      })

    Tip on TypeScript

    You can now write this file in TS too should you wish (rename /quasar.config.js to /quasar.config.ts – notice the .ts file extension).

  • /package.json 中将 type 设置为 module。不要忽略这一步!

    ¥Set type to module in your /package.json. Do not overlook this step!

    /package.json

    {
    + "type": "module"
    }

    如果 postcss.config.js 尚未采用 ESM 格式,则将其转换为 ESM。

    ¥Convert postcss.config.js to ESM, if it’s not already in ESM format.

    /postcss.config.js

    import autoprefixer from 'autoprefixer'
    // import rtlcss from 'postcss-rtlcss'
    
    export default {
      plugins: [
        // https://github.com/postcss/autoprefixer
        autoprefixer({
          overrideBrowserslist: [
            'last 4 Chrome versions',
            'last 4 Firefox versions',
            'last 4 Edge versions',
            'last 4 Safari versions',
            'last 4 Android versions',
            'last 4 ChromeAndroid versions',
            'last 4 FirefoxAndroid versions',
            'last 4 iOS versions'
          ]
        }),
    
        // https://github.com/elchininet/postcss-rtlcss
        // If you want to support RTL css, then
        // 1. yarn/pnpm/bun/npm install postcss-rtlcss
        // 2. optionally set quasar.config.js > framework > lang to an RTL language
        // 3. uncomment the following line (and its import statement above):
        // rtlcss()
      ]
    }

  • 你可能需要将以下内容添加到你的 /.gitignore 文件中。/quasar.config.*.temporary.compiled* 条目指的是当你的 /quasar.config 文件出现故障时为检查目的而留下的文件(可以通过 quasar clean 命令删除):

    ¥You might want to add the following to your /.gitignore file. The /quasar.config.*.temporary.compiled* entry refers to files that are left for inspection purposes when something fails with your /quasar.config file (and can be removed by the quasar clean command):

    /.gitignore

    .DS_Store
    .thumbs.db
    node_modules
    
    # Quasar core related directories
    .quasar
    /dist
    /quasar.config.*.temporary.compiled*
    
    # local .env files
    .env.local*
    
    # Cordova related directories and files
    /src-cordova/node_modules
    /src-cordova/platforms
    /src-cordova/plugins
    /src-cordova/www
    
    # Capacitor related directories and files
    /src-capacitor/www
    /src-capacitor/node_modules
    
    # Log files
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Editor directories and files
    .idea
    *.suo
    *.ntvs*
    *.njsproj
    *.sln

  • 请确保使用最新规范更新你的 /quasar.config 文件,以满足类型要求。检查以下所有部分。

    ¥Make sure to update your /quasar.config file with the newest specs in order to satisfy the types. Check all following sections.

  • 如果你已安装 dotenv 包并在 quasar.config 文件中使用它,请卸载它并使用我们 CLI 原生的 dotenv 支持

    ¥If you’ve installed the dotenv package and are using it in your quasar.config file then uninstall it and use our CLIs native dotenv support.

    /quasar.config file

    - build: {
    -  env: require('dotenv').config().parsed
    - }

  • 如果你有 linting,请前往 Linter 页面 检查你的设置。你需要:

    ¥If you have linting, please review your setup by going to Linter page. You will need to:

    1. 卸载所有当前的 linting 软件包

      ¥Uninstall all your current linting packages

    2. /.eslintrc.cjs 重命名为 /eslint.config.js(查看上面的链接了解新文件的外观)

      ¥Rename /.eslintrc.cjs to /eslint.config.js (check link above on how the new file should look)

    3. /.eslintignore 移植到新的 /eslint.config.js

      ¥Port /.eslintignore to the new /eslint.config.js

    4. 删除 /.eslintignore

      ¥Delete /.eslintignore

    5. 安装新的依赖(查看上面的链接)。

      ¥Install the new dependencies (check the link above).

    6. 编辑 /package.json > 脚本 > lint:

      ¥Edit your /package.json > scripts > lint:

    /package.json

    "scripts": {
    -  "lint": "eslint --ext .js,.ts,.vue ./"
    
    // for non-TS projects:
    +  "lint": "eslint -c ./eslint.config.js \"./src*/**/*.{js,cjs,mjs,vue}\""
    // for TS projects:
    +  "lint": "eslint -c ./eslint.config.js \"./src*/**/*.{ts,js,cjs,mjs,vue}\""
    }

  • 类型功能标志文件现在将在 .quasar 文件夹中自动生成。所以,你必须删除它们:

    ¥The types feature flag files will now be auto-generated in the .quasar folder. So, you must delete them:


    # in project folder root:
    $ npx rimraf -g ./src*/*-flag.d.ts
    $ quasar prepare

  • 我们已弃用所有来自 quasar/wrappers 的导入。你仍然可以使用它们,但我们强烈建议你切换到新的 #q-app/wrappers,如下所示:

    ¥We have deprecated all the imports coming from quasar/wrappers. You can still use them, but we highly recommend switching to the new #q-app/wrappers, as shown below:

    The wrapper functions

    - import { configure } from 'quasar/wrappers'
    + import { defineConfig } from '#q-app/wrappers'
    
    - import { boot } from 'quasar/wrappers'
    + import { defineBoot } from '#q-app/wrappers'
    
    - import { preFetch } from 'quasar/wrappers'
    + import { definePreFetch } from '#q-app/wrappers'
    
    - import { route } from 'quasar/wrappers'
    + import { defineRouter } from '#q-app/wrappers'
    
    - import { store } from 'quasar/wrappers'
    + import { defineStore } from '#q-app/wrappers'
    
    - import { ssrMiddleware } from 'quasar/wrappers'
    + import { defineSsrMiddleware } from '#q-app/wrappers'
    
    - import { ssrCreate } from 'quasar/wrappers'
    + import { defineSsrCreate } from '#q-app/wrappers'
    
    - import { ssrListen } from 'quasar/wrappers'
    + import { defineSsrListen } from '#q-app/wrappers'
    
    - import { ssrClose } from 'quasar/wrappers'
    + import { defineSsrClose } from '#q-app/wrappers'
    
    - import { ssrServeStaticContent } from 'quasar/wrappers'
    + import { defineSsrServeStaticContent } from '#q-app/wrappers'
    
    - import { ssrRenderPreloadTag } from 'quasar/wrappers'
    + import { defineSsrRenderPreloadTag } from '#q-app/wrappers'

  • 对于非 TS 项目,请更新你的 /jsconfig.json 文件。是的,它包含 tsconfig,并且安装正确。

    ¥For non-TS projects, update your /jsconfig.json file. Yes, it contains tsconfig in it and it’s correct.

    /jsconfig.json

    {
      "extends": "./.quasar/tsconfig.json"
    }

  • 面向 TypeScript 项目:@quasar/app-vite/tsconfig-preset 已被删除,因此请更新你的 /tsconfig.json 文件以扩展新的自动生成的 .quasar/tsconfig.json 文件。除非你真正了解自己在做什么,否则请删除任何其他配置,只保留 extends 作为文件中的唯一选项。

    ¥For TypeScript projects: @quasar/app-vite/tsconfig-preset has been dropped, so update your /tsconfig.json file to extend the new auto-generated .quasar/tsconfig.json file. Unless you really know what you are doing, drop any other configuration and just keep extends as the only option in the file.

    /tsconfig.json

    {
    +  "extends": "./.quasar/tsconfig.json"
    -  "extends": "@quasar/app-vite/tsconfig-preset",
    -  "compilerOptions": {
    -    "baseUrl": "."
    -  },
    - "include": [ ... ],
    - "exclude": [ ... ]
    }

    底层配置现在有所不同,因此请查看生成文件中的新选项,以确定是否需要对 tsconfig.json 文件进行进一步调整。以下是生成的 tsconfig(非严格)示例,用于审核:

    ¥The underlying configuration is different now, so please review the new options in the generated file to see if you need further adjustments to your tsconfig.json file. Here is an example of the generated tsconfig (non strict) for reviewing purposes:

    /.quasar/tsconfig.json

    {
      "compilerOptions": {
        "esModuleInterop": true,
        "skipLibCheck": true,
        "target": "esnext",
        "allowJs": true,
        "resolveJsonModule": true,
        "moduleDetection": "force",
        "isolatedModules": true,
        "module": "preserve",
        "noEmit": true,
        "lib": [
          "esnext",
          "dom",
          "dom.iterable"
        ],
        "paths": { ... }
      },
      "exclude": [ ... ]
    }

    如果你使用的是 ESLint,我们建议你在 ESLint 配置中启用 @typescript-eslint/consistent-type-imports 规则。如果你没有设置 linting,我们建议你在 tsconfig.json 文件中使用 verbatimModuleSyntax 作为替代方案(与 ESLint 规则不同,它不能自动修复)。这些更改将帮助你统一常规导入和仅类型导入。请阅读 typescript-eslint 博客 - 一致的类型导入和导出:原因和方法 了解更多信息以及如何设置。以下是示例:

    ¥If you are using ESLint, we recommend enabling @typescript-eslint/consistent-type-imports rules in your ESLint configuration. If you don’t have linting set up, we recommend using verbatimModuleSyntax in your tsconfig.json file as an alternative (unlike ESLint rules, it’s not auto-fixable). These changes will help you unify your imports regarding regular and type-only imports. Please read typescript-eslint Blog - Consistent Type Imports and Exports: Why and How for more information about this and how to set it up. Here is an example:

    /eslint.config.js

    rules: {
      // ...
      '@typescript-eslint/consistent-type-imports': [
        'error',
        { prefer: 'type-imports' },
      ],
      // ...
    }

    你可以使用 quasar.config file > build > typescript 属性来控制与 TypeScript 相关的行为。将此部分添加到你的配置中:

    ¥You can use quasar.config file > build > typescript to control the TypeScript-related behavior. Add this section into your configuration:

    /quasar.config.ts

    build: {
    +  typescript: {
    +    strict: true, // (recommended) enables strict settings for TypeScript
    +    vueShim: true, // required when using ESLint with type-checked rules, will generate a shim file for `*.vue` files
    +    extendTsConfig (tsConfig) {
    +      // You can use this hook to extend tsConfig dynamically
    +      // For basic use cases, you can still update the usual tsconfig.json file to override some settings
    +    },
    +  }
    }

    大多数严格选项已在上一个预设中启用。所以,你应该能够轻松地将 strict 选项设置为 true。但是,如果你遇到任何问题,你可以更新代码以满足更严格的规则,或者在 tsconfig.json 文件中将 “problematic” 选项设置为 false,至少在你修复这些问题之前是这样。

    ¥Most of the strict options were already enabled in the previous preset. So, you should be able to set the strict option to true without facing much trouble. But, if you face any issues, you can either update your code to satisfy the stricter rules or set the “problematic” options to false in your tsconfig.json file, at least until you can fix them.

    src/quasar.d.tssrc/shims-vue.d.ts 文件现在将在 .quasar 文件夹中自动生成。所以,你必须删除这些文件:

    ¥src/quasar.d.ts and src/shims-vue.d.ts files will now be auto-generated in the .quasar folder. So, you must delete those files:


    # in project folder root:
    $ npx rimraf src/quasar.d.ts src/shims-vue.d.ts

    如果你使用带有类型检查规则的 ESLint,请启用 vueShim 选项以保留 shim 文件的先前行为。如果你的项目在没有该选项的情况下也能正常运行,则无需启用它。

    ¥If you are using ESLint with type-check rules, enable the vueShim option to preserve the previous behavior with the shim file. If your project is working fine without that option, you don’t need to enable it.

    /quasar.config.ts

    build: {
      typescript: {
    +    vueShim: true // required when using ESLint with type-checked rules, will generate a shim file for `*.vue` files
      }
    }

    得益于此更改,Capacitor 依赖现在已正确链接到项目的 TypeScript 配置。这意味着你不必两次安装依赖,一次在 /src-capacitor 中,一次在根文件夹中。你可以从根 package.json 文件中删除 Capacitor 依赖。从现在开始,仅在 /src-capacitor 文件夹中安装 Capacitor 依赖就足够了。

    ¥Thanks to this change, Capacitor dependencies are now properly linked to the project’s TypeScript configuration. That means you won’t have to install dependencies twice, once in /src-capacitor and once in the root folder. So, you can remove the Capacitor dependencies from the root package.json file. From now on, installing Capacitor dependencies only in the /src-capacitor folder will be enough.

    此更改的另一个好处是 TypeScript 会自动识别文件夹别名 (quasar.config file > build > alias)。你可以删除 tsconfig.json > compilerOptions > paths。如果你之前使用了 vite-tsconfig-paths 之类的插件,可以将其卸载,并使用 quasar.config file > build > alias 作为数据源。

    ¥Another benefit of this change is that folder aliases(quasar.config file > build > alias) are automatically recognized by TypeScript. So, you can remove tsconfig.json > compilerOptions > paths. If you were using a plugin like vite-tsconfig-paths, you can uninstall it and use quasar.config file > build > alias as the source of truth.

    正确运行类型检查和 linting 需要 .quasar/tsconfig.json 存在。运行 quasar devquasar build 命令时,该文件将自动生成。但是,作为一种轻量级的替代方案,有一个新的 CLI 命令 quasar prepare,它将生成 .quasar/tsconfig.json 文件和一些类型文件。它对于 CI/CD 流水线尤其有用。

    ¥Properly running typechecking and linting requires the .quasar/tsconfig.json to be present. The file will be auto-generated when running quasar dev or quasar build commands. But, as a lightweight alternative, there is a new CLI command quasar prepare that will generate the .quasar/tsconfig.json file and some types files. It is especially useful for CI/CD pipelines.

    $ quasar prepare

    你可以将其添加为 postinstall 脚本,以确保它在安装依赖后运行。当有人第一次拉取项目时,这将很有帮助。

    ¥You can add it as a postinstall script to make sure it’s run after installing the dependencies. This would be helpful when someone is pulling the project for the first time.

    /package.json

    {
      "scripts": {
        "postinstall": "quasar prepare"
      }
    }

    如果你使用的是 Pinia,我们现在会自动在 .quasar/pinia.d.ts 中扩充 router 属性。你可以从 src/stores/index.ts 文件中的 PiniaCustomProperties 接口删除 router 属性。它将继续像以前一样工作,但建议将其删除以避免混淆。

    ¥If you are using Pinia, we are now augmenting the router property inside .quasar/pinia.d.ts automatically. So, you can remove the router property from the PiniaCustomProperties interface in the src/stores/index.ts file. It will continue to work as before, but it’s recommended to remove it to avoid confusion.

    /src/stores/index.ts

    import { defineStore } from '#q-app/wrappers'
    import { createPinia } from 'pinia'
    - import { type Router } from 'vue-router';
    
    /*
    
     * When adding new properties to stores, you should also
    
     * extend the `PiniaCustomProperties` interface.
    - * @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
    + * @see https://pinia.vuejs.org/core-concepts/plugins.html#Typing-new-store-properties
     */
    declare module 'pinia' {
      export interface PiniaCustomProperties {
    -    readonly router: Router;
    +    // add your custom properties here, if any
      }
    }

Capacitor / Cordova 模式变更(Capacitor / Cordova modes changes)

¥Capacitor / Cordova modes changes

UI 代码 (/src) 现在可以使用 process.env.TARGET(即 “ios” 或 “android”)。

¥The UI code (/src) can now use process.env.TARGET (which will be “ios” or “android”).

PWA 模式变更(PWA mode changes)

¥PWA mode changes

CLI 不再提供 register-service-worker 依赖。你必须自行将其安装在项目文件夹中。

¥The register-service-worker dependency is no longer supplied by the CLI. You will have to install it yourself in your project folder.


$ yarn add register-service-worker@^1.0.0

编辑你的 /src-pwa/custom-service-worker.js 文件:

¥Edit your /src-pwa/custom-service-worker.js file:

/src-pwa/custom-service-worker.js

if (process.env.MODE !== 'ssr' || process.env.PROD) {
  registerRoute(
    new NavigationRoute(
      createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML),
-     { denylist: [/sw\.js$/, /workbox-(.)*\.js$/] }
+     { denylist: [new RegExp(process.env.PWA_SERVICE_WORKER_REGEX), /workbox-(.)*\.js$/] }
    )
  )
}

/quasar.config 文件也有一些细微的变化:

¥There are some subtle changes in /quasar.config file too:

/quasar.config file

sourceFiles: {
- registerServiceWorker: 'src-pwa/register-service-worker',
- serviceWorker: 'src-pwa/custom-service-worker',
+ pwaRegisterServiceWorker: 'src-pwa/register-service-worker',
+ pwaServiceWorker: 'src-pwa/custom-service-worker',
+ pwaManifestFile: 'src-pwa/manifest.json',
  // ...
},

pwa: {
- workboxMode?: "generateSW" | "injectManifest";
+ workboxMode?: "GenerateSW" | "InjectManifest";

- // useFilenameHashes: false,
+ // Moved to quasar.config > build > useFilenameHashes

  /**

   * Auto inject the PWA meta tags?

   * If using the function form, return HTML tags as one single string.

   * @default true
   */
- injectPwaMetaTags?: boolean;
+ injectPwaMetaTags?: boolean | ((injectParam: InjectPwaMetaTagsParams) => string);
+ // see below for the InjectPwaMetaTagsParams interface

  // ...
}

// additional types for injectPwaMetaTags
interface InjectPwaMetaTagsParams {
  pwaManifest: PwaManifestOptions;
  publicPath: string;
}
interface PwaManifestOptions {
  id?: string;
  background_color?: string;
  categories?: string[];
  description?: string;
  // ...
}

Electron 模式变更(Electron mode changes)

¥Electron mode changes

警告

可分发文件(你的生产代码)将被编译为 ESM 格式,从而也能利用 ESM 格式的 Electron。

¥The distributables (your production code) will be compiled to ESM form, thus also taking advantage of Electron in ESM form.

提示

你可能想要将 electron 软件包升级到最新版本,以便它可以处理 ESM 格式。

¥You might want to upgrade the electron package to the latest so it can handle the ESM format.

大多数更改都涉及编辑你的 /src-electron/electron-main.js 文件:

¥Most changes refer to editing your /src-electron/electron-main.js file:

Icon path

+import { fileURLToPath } from 'node:url'

+const currentDir = fileURLToPath(new URL('.', import.meta.url))

function createWindow () {
  mainWindow = new BrowserWindow({
-   icon: path.resolve(__dirname, 'icons/icon.png'), // tray icon
+   icon: path.resolve(currentDir, 'icons/icon.png'), // tray icon
    // ...
  })
Preload script

import { fileURLToPath } from 'node:url'

const currentDir = fileURLToPath(new URL('.', import.meta.url))

function createWindow () {
  mainWindow = new BrowserWindow({
    // ...
    webPreferences: {
-     preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD)
+     preload: path.resolve(
+       currentDir,
+       path.join(process.env.QUASAR_ELECTRON_PRELOAD_FOLDER, 'electron-preload' + process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION)
+     )
    }
  })

危险

编辑 /quasar.config.js 以指定预加载脚本:

¥Edit /quasar.config.js to specify your preload script:

/quasar.config file

sourceFiles: {
- electronPreload?: string;
},

electron: {
+ // Electron preload scripts (if any) from /src-electron, WITHOUT file extension
+ preloadScripts: [ 'electron-preload' ],
}

As you can see, you can now specify multiple preload scripts should you need them.
- function createWindow () {
+ async function createWindow () {
   // ...
-  mainWindow.loadURL(process.env.APP_URL)
+  if (process.env.DEV) {
+    await mainWindow.loadURL(process.env.APP_URL)
+  } else {
+    await mainWindow.loadFile('index.html')
+  }

最终,新文件应如下所示:

¥Finally, the new file should look like this:

The new /src-electron/electron-main.js

import { app, BrowserWindow } from 'electron'
import path from 'node:path'
import os from 'node:os'
import { fileURLToPath } from 'node:url'

// needed in case process is undefined under Linux
const platform = process.platform || os.platform()

const currentDir = fileURLToPath(new URL('.', import.meta.url))

let mainWindow

async function createWindow () {
  /**

   * Initial window options
   */
  mainWindow = new BrowserWindow({
    icon: path.resolve(currentDir, 'icons/icon.png'), // tray icon
    width: 1000,
    height: 600,
    useContentSize: true,
    webPreferences: {
      contextIsolation: true,
      // More info: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/electron-preload-script
      preload: path.resolve(
        currentDir,
        path.join(process.env.QUASAR_ELECTRON_PRELOAD_FOLDER, 'electron-preload' + process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION)
      )
    }
  })

  if (process.env.DEV) {
    await mainWindow.loadURL(process.env.APP_URL)
  } else {
    await mainWindow.loadFile('index.html')
  }

  if (process.env.DEBUGGING) {
    // if on DEV or Production with debug enabled
    mainWindow.webContents.openDevTools()
  } else {
    // we're on production; no access to devtools pls
    mainWindow.webContents.on('devtools-opened', () => {
      mainWindow.webContents.closeDevTools()
    })
  }

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.whenReady().then(createWindow)

app.on('window-all-closed', () => {
  if (platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})

SSR 模式变更(SSR mode changes)

¥SSR mode changes

警告

可分发文件(你的生产代码)将被编译为 ESM 格式。

¥The distributables (your production code) will be compiled to ESM form.

/src-ssr/middlewares/*

- import { ssrMiddleware } from 'quasar/wrappers'
+ import { defineSsrMiddleware } from '#q-app/wrappers'

- export default ssrMiddleware({
+ export default defineSsrMiddleware(({
  app,
  port,
  resolve,
  publicPath,
  folders,
  render,
  serve
}) => {
  // something to do with the server "app"
})

其他更改涉及编辑你的 /src-ssr/server.js 文件。由于你现在也可以在开发应用时使用 HTTPS,因此你需要对文件进行以下更改:

¥The other changes refer to editing your /src-ssr/server.js file. Since you can now use HTTPS while developing your app too, you need to make the following changes to the file:

/src-ssr/server.js > listen

- import { ssrListen } from 'quasar/wrappers'
+ import { defineSsrListen } from '#q-app/wrappers'

- export const listen = ssrListen(async ({ app, port, isReady }) => {
+ // notice: devHttpsApp param which will be a Node httpsServer (on DEV only) and if https is enabled
+ // notice: no "isReady" param (starting with 2.0.0-beta.16+)
+ // notice: defineSsrListen() param can still be async (below it isn't)
+ export const listen = defineSsrListen(({ app, devHttpsApp, port }) => {
-   await isReady()
-   return app.listen(port, () => {
+   const server = devHttpsApp || app
+   return server.listen(port, () => {
      if (process.env.PROD) {
        console.log('Server listening at port ' + port)
      }
    })
  })

最终,它现在应如下所示:

¥Finally, this is how it should look like now:

/src-ssr/server.js > listen

import { defineSsrListen } from '#q-app/wrappers'
export const listen = defineSsrListen(({ app, devHttpsApp, port }) => {
  const server = devHttpsApp || app
  return server.listen(port, () => {
    if (process.env.PROD) {
      console.log('Server listening at port ' + port)
    }
  })
})

对于无服务器方法,“listen” 部分应如下所示:

¥For a serverless approach, this is how the “listen” part should look like:

/src-ssr/server.js > listen

export const listen = defineSsrListen(({ app, devHttpsApp, port }) => {
  if (process.env.DEV) {
    const server = devHttpsApp || app;
    return server.listen(port, () => {
      console.log('Server listening at port ' + port)
    })
  }
  else { // in production
    // return an object with a "handler" property
    // that the server script will named-export
    return { handler: app }
  }
})

接下来,serveStaticContent 功能已更改:

¥Next, the serveStaticContent function has changed:

/src-ssr/server.js > serveStaticContent

- import { serveStaticContent }
+ import { defineSsrServeStaticContent } from '#q-app/wrappers'

- export const serveStaticContent = ssrServeStaticContent((path, opts) => {
-  return express.static(path, { maxAge, ...opts })
- })

+ /**
+ * Should return a function that will be used to configure the webserver
+ * to serve static content at "urlPath" from "pathToServe" folder/file.
+ *
+ * Notice resolve.urlPath(urlPath) and resolve.public(pathToServe) usages.
+ *
+ * Can be async: defineSsrServeStaticContent(async ({ app, resolve }) => {
+ * Can return an async function: return async ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
+ */
+ export const serveStaticContent = defineSsrServeStaticContent(({ app, resolve }) => {
+  return ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
+    const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts })
+    app.use(resolve.urlPath(urlPath), serveFn)
+  }
+ })

此外,renderPreloadTag() 函数现在可以接受一个附加参数 (ssrContext):

¥Also, the renderPreloadTag() function can now take an additional parameter (ssrContext):

/src-ssr/server.js

- import { ssrRenderPreloadTag } from 'quasar/wrappers'
+ import { defineSsrRenderPreloadTag } from '#q-app/wrappers'

+ export const renderPreloadTag = defineSsrRenderPreloadTag((file, { ssrContext }) => {
+  // ...
+ })

对于 TS 开发者,你还应该对 /src-ssr/middlewares 文件进行一些小改动,如下所示:

¥For TS devs, you should also make a small change to your /src-ssr/middlewares files, like this:

For TS devs

+ import { type Request, type Response } from 'express';
// ...
- app.get(resolve.urlPath('*'), (req, res) => {
+ app.get(resolve.urlPath('*'), (req: Request, res: Response) => {

/quasar.config 文件也新增了一些内容:

¥There are some additions to the /quasar.config file too:

/quasar.config file

ssr: {
  // ...

  /**

   * When using SSR+PWA, this is the name of the

   * PWA index html file that the client-side fallbacks to.

   * For production only.

   *    * Do NOT use index.html as name as it will mess SSR up!

   *    * @default 'offline.html'
   */
- ssrPwaHtmlFilename?: string;
+ pwaOfflineHtmlFilename?: string;

  /**

   * Tell browser when a file from the server should expire from cache

   * (the default value, in ms)

   * Has effect only when server.static() is used
   */
- maxAge?: number;

  /**

   * Extend/configure the Workbox GenerateSW options

   * Specify Workbox options which will be applied on top of

   *  `pwa > extendGenerateSWOptions()`.

   * More info: https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
   */
+ pwaExtendGenerateSWOptions?: (config: object) => void;

  /**

   * Extend/configure the Workbox InjectManifest options

   * Specify Workbox options which will be applied on top of

   *  `pwa > extendInjectManifestOptions()`.

   * More info: https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
   */
+ pwaExtendInjectManifestOptions?: (config: object) => void;
}

BEX 模式变更(Bex mode changes)

¥Bex mode changes

有相当多的改进:

¥There are quite a few improvements:

  • BEX 模式现在支持 HMR(热模块重载)!!!(仅限 Chrome)

    ¥The BEX mode now has HMR (hot module reload)!!! (Chrome only)

  • 完全重写并重新设计了 Quasar Bridge,以实现:

    ¥Completely rewrote & redesigned the Quasar Bridge to allow for:

    • 在 bex 的任何部分(应用、内容脚本、后台)之间直接发送/接收消息

      ¥Sending/receiving messages directly between any part of your bex (app, content scripts, background)

    • 能够完全跳过使用桥接器

      ¥Ability to skip using the bridge altogether

    • 通过桥接发送和接收消息的错误处理

      ¥Error handling for sending & receiving messages through the bridge

    • 更好地处理内部资源以避免内存泄漏(之前的实现中存在一些特殊情况)

      ¥Better handling of internal resources to avoid memory leaks (there were some edge cases in the previous implementation)

    • 调试模式(所有桥接通信都将输出到浏览器控制台)

      ¥Debug mode (where all the bridge communication will be outputted to the browser console)

    • 重大变更亮点:桥接器的后台和内容脚本初始化;响应时调用 bride.on();bridge.send() 调用

      ¥Breaking changes highlights: background & content scripts initialization of the bridge; bride.on() calls when responding; bridge.send() calls

    • 现在,通过访问 $q objectwindow.QBexBridge,桥接器可在 /src/ 中的整个应用中使用(无论使用什么文件:启动文件、路由初始化文件、App.vue、任何 Vue 组件等)。

      ¥The bridge is now available throughout the App in /src/ (regardless of the file used: boot files, router init, App.vue, any Vue component, …) by accessing the $q object or window.QBexBridge

  • 一个单独的清单文件,可以从中提取 Chrome 和 Firefox 的清单文件。

    ¥One single manifest file from which both chrome & firefox ones can be extracted.

  • 从 BEX 清单文件自动推断后台脚本文件和内容脚本文件。

    ¥Automatically infer the background script file & the content script files from the BEX manifest file.

  • 能够编译你可能需要动态加载/注入的其他 js/ts 文件。

    ¥Ability to compile other js/ts files as well that you might need to dynamically load/inject.

  • 打开弹窗时不再有 3 秒的延迟。

    ¥No more 3s delay when opening the popup.

  • “dom” 脚本支持已被移除。只需将你的逻辑从那里移到你的一个内容脚本中即可。

    ¥The “dom” script support was removed. Simply move your logic from there into one of your content scripts.

  • 用于背景/内容脚本的新的、更简单的 API。

    ¥New, easier API for the background/content scripts.

依赖(Dependencies)

¥Dependencies

不再需要 events 依赖。如果你已安装 Quasar,请将其卸载:

¥The events dependency is no longer required. If you have it installed, uninstall it:


$ yarn remove events

CLI 命令(CLI commands)

¥CLI commands

quasar devquasar build 命令现在需要明确的目标(chrome 或 firefox)。如果你希望同时开发两者,则可以生成两个 quasar dev 命令。

¥The quasar dev and quasar build commands now require an explicit target (chrome or firefox). Should you wish to develop for both simultaneously, then you can spawn two quasar dev commands.

$ quasar dev -m bex -T <chrome|firefox>
$ quasar dev -m bex --target <chrome|firefox>

$ quasar build -m bex -T <chrome|firefox>
$ quasar build -m bex --target <chrome|firefox>

请注意,/src/src-bex 中的代码现在可以使用 process.env.TARGET(即 “chrome” 或 “firefox”)。

¥Note that the code in /src and /src-bex can now use process.env.TARGET (which will be “chrome” or “firefox”).

Chrome 的 HMR(HMR for Chrome)

¥HMR for Chrome

DX 的重大改进:

¥Significant improvements to the DX:

  • devtools/options/popup 页面的完整 HMR

    ¥Full HMR for devtools/options/popup page

  • 更改后台脚本时,扩展程序将自动重新加载。

    ¥When changing the background script, the extension will automatically reload.

  • 更改内容脚本时,扩展程序将自动重新加载,并且使用这些内容脚本的标签页将自动刷新。

    ¥When changing a content script, the extension will automatically reload & the tabs using those content scripts will auto-refresh.

quasar.config 文件(The quasar.config file)

¥The quasar.config file

/quasar.config file

sourceFiles: {
+ bexManifestFile: 'src-bex/manifest.json',
  // ...
},
bex: {
- contentScripts: [] // no longer needed as scripts are
-                    // now extracted from the manifest file
+ extraScripts: []
}

BEX 清单文件(The BEX manifest file)

¥The BEX manifest file

我们现在提供一种方法来区分每个目标(Chrome 和 Firefox)的清单。

¥We are now supplying a way to differentiate the manifest for each target (chrome and firefox).

请注意,清单文件现在包含三个根属性:all, chrome & firefox.Chrome 的清单文件与 all+chrome 深度合并,而 Firefox 的清单文件则由 all+firefox 生成。你甚至可以为每个目标使用不同的清单版本。

¥Notice that the manifest file now contains three root props: all, chrome & firefox. The manifest for chrome is deeply merged from all+chrome, while the firefox one is generated from all+firefox. You could even have different manifest versions for each target.

{
  "all": {
    "manifest_version": 3,

    "icons": {
      "16": "icons/icon-16x16.png",
      "48": "icons/icon-48x48.png",
      "128": "icons/icon-128x128.png"
    },

    "permissions": [
      "storage",
      "tabs",
      "activeTab"
    ],

    "host_permissions": [ "*://*/*" ],
    "content_security_policy": {
      "extension_pages": "script-src 'self'; object-src 'self';"
    },
    "web_accessible_resources": [
      {
        "resources": [ "*" ],
        "matches": [ "*://*/*" ]
      }
    ],

    "action": {
      "default_popup": "www/index.html"
    },

    "content_scripts": [
      {
        "matches": [ "<all_urls>" ],
        "css": [ "assets/content.css" ],
        "js": [ "my-content-script.js" ]
      }
    ]
  },

  "chrome": {
    "background": {
      "service_worker": "background.js"
    }
  },

  "firefox": {
    "background": {
      "scripts": [ "background.js" ]
    }
  }
}

面向 TS 开发者

你的背景和内容脚本都带有 .ts 扩展名。在 manifest.json 文件中也使用该扩展名!示例:“background.ts”, “my-content-script.ts”.虽然浏览器供应商仅支持 .js 扩展名,但 Quasar CLI 会自动转换文件扩展名。

¥Your background and content scripts have the .ts extension. Use that extension in the manifest.json file as well! Examples: “background.ts”, “my-content-script.ts”. While the browser vendors do support only the .js extension, Quasar CLI will convert the file extensions automatically.

脚本文件(The script files)

¥The script files

Background script

/**

 * Importing the file below initializes the extension background.

 *  * Warnings:

 * 1. Do NOT remove the import statement below. It is required for the extension to work.

 *    If you don't need createBridge(), leave it as "import '#q-app/bex/background'".

 * 2. Do NOT import this file in multiple background scripts. Only in one!

 * 3. Import it in your background service worker (if available for your target browser).
 */
import { createBridge } from '#q-app/bex/background'

/**

 * Call useBridge() to enable communication with the app & content scripts

 * (and between the app & content scripts), otherwise skip calling

 * useBridge() and use no bridge.
 */
const bridge = createBridge({ debug: false })
Content script

/**

 * Importing the file below initializes the content script.

 *  * Warning:

 *   Do not remove the import statement below. It is required for the extension to work.

 *   If you don't need createBridge(), leave it as "import '#q-app/bex/content'".
 */
import { createBridge } from '#q-app/bex/content'

// The use of the bridge is optional.
const bridge = createBridge({ debug: false })
/**

 * bridge.portName is 'content@<path>-<number>'

 *   where <path> is the relative path of this content script

 *   filename (without extension) from /src-bex

 *   (eg. 'my-content-script', 'subdir/my-script')

 *   and <number> is a unique instance number (1-10000).
 */

// Attach initial bridge listeners...

/**

 * Leave this AFTER you attach your initial listeners

 * so that the bridge can properly handle them.

 *  * You can also disconnect from the background script

 * later on by calling bridge.disconnectFromBackground().

 *  * To check connection status, access bridge.isConnected
 */
bridge.connectToBackground()
  .then(() => {
    console.log('Connected to background')
  })
  .catch(err => {
    console.error('Failed to connect to background:', err)
  })
App (/src/...) vue components

<template>
  <div />
</template>

<script setup>
import { useQuasar } from 'quasar'
const $q = useQuasar()

// Use $q.bex (the bridge)
// $q.bex.portName is "app"
</script>

请注意,devtools/popup/options 页面的 portName 将是 app

¥Please note that the devtools/popup/options page portName will be app.

新的 BEX 桥接器(The new BEX bridge)

¥The new BEX bridge

Bex Bridge messaging

// Listen to a message from the client
bridge.on('test', message => {
  console.log(message)
  console.log(message.payload)
  console.log(message.from)
})

// Send a message and split payload into chunks
// to avoid max size limit of BEX messages.
// Warning! This happens automatically when the payload is an array.
// If you actually want to send an Array, wrap it in an object.
bridge.send({
  event: 'test',
  to: 'app',
  payload: [ 'chunk1', 'chunk2', 'chunk3', ... ]
}).then(responsePayload => { ... }).catch(err => { ... })

// Send a message and wait for a response
bridge.send({
  event: 'test',
  to: 'background',
  payload: { banner: 'Hello from content-script' }
}).then(responsePayload => { ... }).catch(err => { ... })

// Listen to a message from the client and respond synchronously
bridge.on('test', message => {
  console.log(message)
  return { banner: 'Hello from a content-script!' }
})

// Listen to a message from the client and respond asynchronously
bridge.on('test', async message => {
  console.log(message)
  const result = await someAsyncFunction()
  return result
})
bridge.on('test', message => {
  console.log(message)
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ banner: 'Hello from a content-script!' })
    }, 1000)
  })
})

// Broadcast a message to app & content scripts
bridge.portList.forEach(portName => {
  bridge.send({ event: 'test', to: portName, payload: 'Hello from background!' })
})

// Find any connected content script and send a message to it
const contentPort = bridge.portList.find(portName => portName.startsWith('content@'))
if (contentPort) {
  bridge.send({ event: 'test', to: contentPort, payload: 'Hello from background!' })
}

// Send a message to a certain content script
bridge
  .send({ event: 'test', to: 'content@my-content-script-2345', payload: 'Hello from a content-script!' })
  .then(responsePayload => { ... })
  .catch(err => { ... })

// Listen for connection events
// (the "@quasar:ports" is an internal event name registered automatically by the bridge)
// --> ({ portList: string[], added?: string } | { portList: string[], removed?: string })
bridge.on('@quasar:ports', ({ portList, added, removed }) => {
  console.log('Ports:', portList)
  if (added) {
    console.log('New connection:', added)
  } else if (removed) {
    console.log('Connection removed:', removed)
  }
})

// Current bridge port name (can be 'background', 'app', or 'content@<name>-<xxxxx>')
console.log(bridge.portName)

警告!发送大量数据

所有浏览器扩展程序对可作为通信消息传递的数据量都有硬性限制(例如:50MB)。如果你的有效载荷超出了该数量,你可以发送分块(payload 参数应为数组)。

¥All browser extensions have a hard limit on the amount of data that can be passed as communication messages (example: 50MB). If you exceed that amount on your payload, you can send chunks (payload param should be an Array).


bridge.send({
  event: 'some.event',
  to: 'app',
  payload: [ chunk1, chunk2, ...chunkN ]
})

计算负载大小时,请记住负载被封装在 Bridge 构建的消息中,该消息还包含一些其他属性。这也会占用一些字节。因此,你的块大小应该比浏览器的阈值低几个字节。

¥When calculating the payload size, have in mind that the payload is wrapped in a message built by the Bridge that contains some other properties too. That takes a few bytes as well. So your chunks’ size should be with a few bytes below the browser’s threshold.

警告!发送数组时的性能

正如我们在上面的警告中看到的,如果 payload 是数组,则桥接器将为数组的每个元素发送一条消息。当你实际想要发送数组(而不是将有效负载拆分成块)时,这将非常低效。

¥Like we’ve seen on the warning above, if payload is Array then the bridge will send a message for each of the Array’s elements. When you actually want to send an Array (not split the payload into chunks), this will be VERY inefficient.


解决方案是将数组封装在一个对象中(这样只会发送一条消息):

¥The solution is to wrap your Array in an Object (so only one message will be sent):


bridge.send({
  event: 'some.event',
  to: 'background',
  payload: {
    myArray: [ /*...*/ ]
  }
})

如果你在 BEX 部分之间发送消息时遇到问题,你可以为你感兴趣的桥接器启用调试模式。这样做时,通信内容也会输出到浏览器控制台:

¥If you encounter problems with sending messages between the BEX parts, you could enable the debug mode for the bridges that interest you. In doing so, the communication will also be outputted to the browser console:

Bridge debug mode

// Dynamically set debug mode
bridge.setDebug(true) // boolean

// Log a message on the console (if debug is enabled)
bridge.log('Hello world!')
bridge.log('Hello', 'world!')
bridge.log('Hello world!', { some: 'data' })
bridge.log('Hello', 'world', '!', { some: 'object' })
// Log a warning on the console (regardless of the debug setting)
bridge.warn('Hello world!')
bridge.warn('Hello', 'world!')
bridge.warn('Hello world!', { some: 'data' })
bridge.warn('Hello', 'world', '!', { some: 'object' })

其他 /quasar.config 文件更改(Other /quasar.config file changes)

¥Other /quasar.config file changes

/quasar.config 文件中的 ctx 有一个额外的属性 (appPaths):

¥The ctx from /quasar.config file has an additional prop (appPaths):

import { defineConfig } from '#q-app/wrappers'
export default defineConfig((ctx) => ({
  // ctx.appPaths is available

ctx.appPaths 的定义使用 QuasarAppPaths TS 类型,如下所示:

¥The definition for ctx.appPaths is defined with QuasarAppPaths TS type as below:

export interface IResolve {
  cli: (dir: string) => string;
  app: (dir: string) => string;
  src: (dir: string) => string;
+ public: (dir: string) => string;
  pwa: (dir: string) => string;
  ssr: (dir: string) => string;
  cordova: (dir: string) => string;
  capacitor: (dir: string) => string;
  electron: (dir: string) => string;
  bex: (dir: string) => string;
}

export interface QuasarAppPaths {
  cliDir: string;
  appDir: string;
  srcDir: string;
+ publicDir: string;
  pwaDir: string;
  ssrDir: string;
  cordovaDir: string;
  capacitorDir: string;
  electronDir: string;
  bexDir: string;

  quasarConfigFilename: string;
+ quasarConfigInputFormat: "esm" | "cjs" | "ts";
+ quasarConfigOutputFormat: "esm" | "cjs";

  resolve: IResolve;
}
/quasar.config > sourceFiles

sourceFiles: {
+ bexManifestFile?: string;
}
/quasar.config > framework

framework: {
  /**

   * Auto import - how to detect components in your vue files

   *   "kebab": q-carousel q-page

   *   "pascal": QCarousel QPage

   *   "combined": q-carousel QPage

   * @default 'kebab'
   */
  autoImportComponentCase?: "kebab" | "pascal" | "combined";

  /**

   * Auto import - which file extensions should be interpreted as referring to Vue SFC?

   * @default [ 'vue' ]
   */
+ autoImportVueExtensions?: string[];

  /**

   * Auto import - which file extensions should be interpreted as referring to script files?

   * @default [ 'js', 'jsx', 'ts', 'tsx' ]
   */
+ autoImportScriptExtensions?: string[];

  /**

   * Treeshake Quasar's UI on dev too?

   * Recommended to leave this as false for performance reasons.

   * @default false
   */
+ devTreeshaking?: boolean;
+ // was previously under /quasar.conf > build
}
/quasar.config > build

build: {
  /**

   * Treeshake Quasar's UI on dev too?

   * Recommended to leave this as false for performance reasons.

   * @default false
   */
- devTreeshaking?: boolean;
- // moved under /quasar.conf > framework

  /**

   * Should we invalidate the Vite and ESLint cache on startup?

   * @default false
   */
- rebuildCache?: boolean;

  /**

   * Automatically open remote Vue Devtools when running in development mode.
   */
+ vueDevtools?: boolean;

  /**

   * Folder where Quasar CLI should look for .env* files.

   * Can be an absolute path or a relative path to project root directory.

   *    * @default project root directory
   */
+ envFolder?: string;
  /**

   * Additional .env* files to be loaded.

   * Each entry can be an absolute path or a relative path to quasar.config > build > envFolder.

   *    * @example ['.env.somefile', '../.env.someotherfile']
   */
+ envFiles?: string[];
}

其他注意事项(Other considerations)

¥Other considerations

你可能想要从 @intlify/vite-plugin-vue-i18n 升级/切换到更新的 @intlify/unplugin-vue-i18n

¥You might want to upgrade/switch from @intlify/vite-plugin-vue-i18n to the newer @intlify/unplugin-vue-i18n.

删除旧软件包并安装新软件包后,请按如下方式更新你的 /quasar.config 文件:

¥After removing the old package and installing the new one then update your /quasar.config file as follows:

/quasar.config

- import path from 'node:path'
+ import { fileURLToPath } from 'node:url'

export default defineConfig((ctx) => {
  return {
    build: {
      vitePlugins: [
-       ['@intlify/vite-plugin-vue-i18n', {
+       ['@intlify/unplugin-vue-i18n/vite', {
-         include: path.resolve(__dirname, './src/i18n/**')
+         include: [ fileURLToPath(new URL('./src/i18n', import.meta.url)) ],
+         ssr: ctx.modeName === 'ssr'
        }]
      ]
    }
  }
})

环境点文件支持(The env dotfiles support)

¥The env dotfiles support

稍微扩展了环境点文件的支持。这些文件将被检测和使用(顺序很重要):

¥Expanding a bit on the env dotfiles support. These files will be detected and used (the order matters):

.env                                # loaded in all cases
.env.local                          # loaded in all cases, ignored by git
.env.[dev|prod]                     # loaded for dev or prod only
.env.local.[dev|prod]               # loaded for dev or prod only, ignored by git
.env.[quasarMode]                   # loaded for specific Quasar CLI mode only
.env.local.[quasarMode]             # loaded for specific Quasar CLI mode only, ignored by git
.env.[dev|prod].[quasarMode]        # loaded for specific Quasar CLI mode and dev|prod only
.env.local.[dev|prod].[quasarMode]  # loaded for specific Quasar CLI mode and dev|prod only, ignored by git

…其中 “被 git 忽略” 假定在发布此包后创建了一个默认项目文件夹,否则,请将 .env.local* 添加到你的 /.gitignore 文件中。

¥…where “ignored by git” assumes a default project folder created after releasing this package, otherwise add .env.local* to your /.gitignore file.

你还可以配置从其他文件夹中获取上述文件,甚至可以将更多文件添加到列表中:

¥You can also configure the files above to be picked up from a different folder or even add more files to the list:

/quasar.config file

build: {
  /**

   * Folder where Quasar CLI should look for .env* files.

   * Can be an absolute path or a relative path to project root directory.

   *    * @default project root directory
   */
  envFolder?: string;

  /**

   * Additional .env* files to be loaded.

   * Each entry can be an absolute path or a relative path to quasar.config > build > envFolder.

   *    * @example ['.env.somefile', '../.env.someotherfile']
   */
  envFiles?: string[];

  /**

   * Filter the env variables that are exposed to the client

   * through the env files. This does not account also for the definitions

   * assigned directly to quasar.config > build > env prop.

   *    * Requires @quasar/app-vite v2.0.3+
   */
  envFilter?:
    (env: { [index: string]: string | boolean | undefined | null })
      => { [index: string]: string | boolean | undefined | null };
}

请记住,你可以使用 build > envFilter 过滤掉不需要的键,甚至可以更改键的值:

¥Remember that you can filter out unwanted keys, or even change values for keys by using build > envFilter:

/quasar.config file

build: {
  // @quasar/app-vite v2.0.3+
  envFilter (originalEnv) {
    const newEnv = {}
    for (const key in originalEnv) {
      if (/* ...decide if it goes in or not... */) {
        newEnv[ key ] = originalEnv[ key ]
      }
    }

    // remember to return your processed env
    return newEnv
  }
}