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

@quasar/app-webpack v4(@quasar/app-webpack v4)

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

¥A note to App Extensions owners

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

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

api.compatibleWith(
  '@quasar/app-webpack',
- '^3.0.0'
+ '^3.0.0 || ^4.0.0'
)

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

¥Notable breaking changes

  • 最低 Node.js 版本现在是 18.12

    ¥Minimum Node.js version is now 18.12

  • 我们已将整个 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/app-vite 移植并适配了更优的开发者服务器实现,以适用于所有 Quasar 模式。好处多多。

    ¥Ported and adapted the superior devserver implementation from @quasar/app-vite for all Quasar modes. The benefits are huge.

  • 已从 @quasar/app-vite 移植了 SSR、PWA、Electron 和 BEX 模式的更优实现。我们将在此文档页面上详细介绍 Quasar 模式的每种变化。

    ¥Ported the superior implementation of SSR, PWA, Electron & BEX modes from @quasar/app-vite. We will detail each Quasar mode changes on this docs page.

    • SSR - 一些显著的改进:

      ¥SSR - some of the noticeable improvements:

      • 可靠性提升:相同的服务器代码在开发和生产环境中运行

        ¥Improved reliability: same server code runs in dev and prod

      • 更多目标 Web 服务器选项:你可以将 express() 替换为你正在使用的其他任何组件。

        ¥More target webserver options: you can replace express() with whatever else you are using

      • 性能:在 /src-ssr 中更改代码时,客户端代码不再从头开始重新编译

        ¥Perf: client-side code no longer re-compiles from scratch when changing code in /src-ssr

      • /src-ssr 中文件的编译速度更快、更佳(现在使用 Esbuild 而非 Webpack 构建)

        ¥Faster & better compilation for files in /src-ssr (now built with Esbuild instead of Webpack)

    • PWA - 一些显著的改进:

      ¥PWA - some of the noticeable improvements:

      • 许多新的配置选项(同时删除了许多旧选项)

        ¥Many new configuration options (while removing a lot of the old ones)

      • /src-pwa 中文件的编译速度更快、更佳(现在使用 Esbuild 而非 Webpack 构建)

        ¥Faster & better compilation for files in /src-pwa (now built with Esbuild instead of Webpack)

    • Electron

      • 现在编译为 ESM(因此也利用了 ESM 格式的 Electron)

        ¥Now compiles to ESM (thus also taking advantage of the Electron in ESM format)

      • 更快、更好地编译 /src-electron 中的文件(现在使用 Esbuild 而非 Webpack 构建)

        ¥Faster & better compilation for files in /src-electron (now built with Esbuild instead of Webpack)

      • 支持多个预加载脚本

        ¥Support for multiple preload scripts

    • BEX - 一些显著的改进:

      ¥BEX - some of the noticeable improvements:

      • 已从 @quasar/app-vite 移植了更优的实现,这意味着你在生成该模式时可以在扩展 Manifest v2 和 Manifest v3 之间进行选择。

        ¥Ported the superior implementation from @quasar/app-vite, which also means that when you spawn the mode you can choose between extension Manifest v2 and Manifest v3

      • 清单文件现在保存在其自己的文件 (/src-pwa/manifest.json) 中,而不是 /quasar.config 文件中。

        ¥The manifest is now held in a file of its own (/src-pwa/manifest.json) instead of inside the /quasar.config file

  • Webpack 现在只会编译 /src 文件夹的内容,其余内容(/src-pwa、/src-electron 等)则由 Esbuild 处理。这意味着其构建速度更快,并且能够处理 Node.js 格式。

    ¥Webpack will now only compile the contents of /src folder, while the rest (/src-pwa, /src-electron, etc) are now handled by Esbuild. This translates to a superior build speed and handling of Node.js formats.

  • 由于 @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 检测基于 /tsconfig.json 的存在以及已安装的 typescript 和 ts-loader。

    ¥Typescript detection is based on the presence of /tsconfig.json and typescript & ts-loader being installed.

  • 已放弃对 Vuex 的支持。Pinia 已经成为 Vue 3 的官方应用商店一段时间了。Vuex 在 app-webpack v3 中已弃用,并且与新架构存在问题,因此现已移除。你仍然可以像使用任何 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-webpack v3 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-webpack v3,但仍在此处发布以供读者参考。

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

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

    ¥feat(app-webpack): 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)

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

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

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

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

  • 壮举(应用 webpack):升级到 Typescript v5;删除 fork-ts-checker

    ¥feat(app-webpack): upgrade to Typescript v5; drop fork-ts-checker

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

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

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

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

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

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

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

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

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

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

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

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

  • 壮举(应用 webpack):支持多种格式的 postcss 配置文件:postcss.config.cjs、.postcssrc.js、postcss.config.js、postcss.config.mjs、.postcssrc.cjs、.postcssrc.mjs

    ¥feat(app-webpack): support for postcss config file in multiple formats: postcss.config.cjs, .postcssrc.js, postcss.config.js, postcss.config.mjs, .postcssrc.cjs, .postcssrc.mjs

  • 壮举(应用 webpack):支持多种格式的 babel 配置文件:babel.config.cjs、babel.config.js、babel.config.mjs、.babelrc.js、.babelrc.cjs、.babelrc.mjs、.babelrc

    ¥feat(app-webpack): support for babel config file in multiple formats: babel.config.cjs, babel.config.js, babel.config.mjs, .babelrc.js, .babelrc.cjs, .babelrc.mjs, .babelrc

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

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

  • 壮举(应用 webpack):从 q/app-vite 移植 quasar.config 文件 > Electron > inspectPort prop

    ¥feat(app-webpack): port quasar.config file > electron > inspectPort prop from q/app-vite

  • 壮举(应用 webpack):从 q/app-vite 移植 quasar.config 文件 > 构建 > rawDefine

    ¥feat(app-webpack): port quasar.config file > build > rawDefine from q/app-vite

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

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

  • 壮举(应用 webpack):大幅提升 SSR 性能 + 内存使用率(尤其是对于生产环境);ssr-helpers 重大重构;还包括来自 q/app-vite 的 renderPreloadTag()

    ¥feat(app-webpack): highly improve SSR perf + mem usage (especially for prod); major refactoring of ssr-helpers; also include renderPreloadTag() from q/app-vite

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

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

  • 壮举(应用 webpack):SSR - 能够用任何其他类似 connect 的 Web 服务器替换 express()

    ¥feat(app-webpack): SSR - ability to replace express() with any other connect-like webserver

  • 壮举(应用 webpack):SSR - 在 /src-ssr 中更改代码时不再重新编译所有内容

    ¥feat(app-webpack): SSR - no longer recompile everything when changing code in /src-ssr

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

    ¥feat(app-webpack): upgrade deps

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

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

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

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

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

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

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

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

  • 壮举+重构(应用 webpack):能够同时运行多种模式 + dev/build

    ¥feat+refactor(app-webpack): ability to run multiple modes + dev/build simultaneously

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 壮举(应用 webpack):更智能的应用文件验证

    ¥feat(app-webpack): smarter app files validation

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

    ¥refactor(app-webpack): 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)

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

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

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

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

  • 修复(应用-webpack):触发 “未找到模块” 的 Electron 预加载脚本

    ¥fix(app-webpack): electron preload script triggering “module not found”

  • 壮举(应用 webpack):升级到 webpack-dev-server v5

    ¥feat(app-webpack): upgrade to webpack-dev-server v5

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

¥Beginning of the upgrade process

提示

如果你不确定是否不会误跳过任何建议的更改,你可以随时使用 @quasar/app-webpack v4 搭建一个新的项目文件夹,然后轻松地从那里开始移植你的应用。大部分更改都涉及不同的项目文件夹配置文件,而大部分与 /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-webpack v4 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 Webpack (v4)".

准备工作:

¥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-webpack 条目上编辑 /package.json 并将其分配给 ^4.0.0

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

    /package.json

    "devDependencies": {
    - "@quasar/app-webpack": "^3.0.0",
    + "@quasar/app-webpack": "^4.0.0"
    }

    Then yarn/npm/pnpm/bun install.

  • 手动安装 autoprefixer。它不再是开箱即用的。

    ¥Manually install autoprefixer. It is no longer supplied out of the box.

  • 如果你已安装 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
    - }

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

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

  • 如果你有 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.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"
    }

    WARNING

    Could not find "./routes". Should be import routes from './routes.js'. You will need to add the extension for all your imports. :::设置 type=module 后,如果导入时未指定文件扩展名,则可能会遇到 “文件未找到” 错误。示例:

    postcss.config.cjs 转换为 ESM 格式并重命名为 .js 扩展名:

    ¥Convert postcss.config.cjs to ESM format and rename to .js extension:

    /postcss.config.js

    // https://github.com/michael-ciniawsky/postcss-load-config
    import autoprefixer from 'autoprefixer'
    
    export default {
      plugins: [
        // to edit target browsers: use "browserslist" field in package.json
        autoprefixer
      ]
    }

    babel.config.cjs 转换为 ESM 格式并重命名为 .js 扩展名:

    ¥Convert babel.config.cjs to ESM format and rename to .js extension:

    /babel.config.js

    export default api => {
      return {
        presets: [
          [
            '@quasar/babel-preset-app',
            api.caller(caller => caller && caller.target === 'node')
              ? { targets: { node: 'current' } }
              : {}
          ]
        ]
      }
    }

  • 为了与 @quasar/app-vite 保持一致(并且能够在 @quasar/app-webpack/index.html 之间轻松切换),请将 /src/index.template.html 移至 /index.html 并进行以下更改:

    ¥For consistency with @quasar/app-vite (and easy switch between @quasar/app-webpack and it) move /src/index.template.html to /index.html and do the following changes:

    /index.html

    <body>
    - <!-- DO NOT touch the following DIV -->
    - <div id="q-app"></div>
    + <!-- quasar:entry-point -->
    </body>

  • 你可能需要将以下内容添加到你的 /.gitignore 文件中。当你的 /quasar.config 文件出现故障时,会保留此类文件以供检查(可以使用 quasar clean 命令删除):

    ¥You might want to add the following to your /.gitignore file. These kind of files 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 文件夹中自动生成。所以,你必须删除它们:

    ¥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-webpack/tsconfig-preset 已被删除,因此请更新你的 /tsconfig.json 文件以扩展新的自动生成的 .quasar/tsconfig.json 文件。除非你真正了解自己在做什么,否则请删除任何其他配置,只保留 extends 作为文件中的唯一选项。

    ¥For TypeScript projects: @quasar/app-webpack/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-webpack/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。如果你之前使用了 tsconfig-paths-webpack-plugin 之类的插件,可以将其卸载,并使用 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 tsconfig-paths-webpack-plugin, 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
      }
    }

SPA / Capacitor / Cordova modes changes(SPA / Capacitor / Cordova modes changes)

无需更改 /src/src-capacitor/src-cordova 文件夹中的任何内容。

¥No need to change anything in the /src, /src-capacitor or /src-cordova folders.

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

import { clientsClaim } from 'workbox-core'
import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'
import { registerRoute, NavigationRoute } from 'workbox-routing'

self.skipWaiting()
clientsClaim()

// Use with precache injection
precacheAndRoute(self.__WB_MANIFEST)

cleanupOutdatedCaches()

// Non-SSR fallbacks to index.html
// Production SSR fallbacks to offline.html (except for dev)
if (process.env.MODE !== 'ssr' || process.env.PROD) {
  registerRoute(
    new NavigationRoute(
      createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML),
      { denylist: [new RegExp(process.env.PWA_SERVICE_WORKER_REGEX), /workbox-(.)*\.js$/] }
    )
  )
}

创建文件 /src-pwa/manifest.json,并将 /quasar.config 文件 > pwa > manifest 从该文件移动到此文件。以下是其示例:

¥Create the file /src-pwa/manifest.json and move /quasar.config file > pwa > manifest from there to this file. Here’s an example of how it can look like:

{
  "orientation": "portrait",
  "background_color": "#ffffff",
  "theme_color": "#027be3",
  "icons": [
    {
      "src": "icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    },
    {
      "src": "icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

/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: {
- workboxPluginMode?: "GenerateSW" | "InjectManifest";
+ workboxMode?: "GenerateSW" | "InjectManifest";

  /**

   * Full option list can be found

   *  [here](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin#full_generatesw_config).
   */
- workboxOptions?: object;
  /**

   * Extend/configure the Workbox GenerateSW options
   */
+ extendGenerateSWOptions?: (config: GenerateSWOptions) => void;
  /**

   * Extend/configure the Workbox InjectManifest options
   */
+ extendInjectManifestOptions?: (config: InjectManifestOptions) => void;

- // Now the contents for this held in a new file: /src-pwa/manifest.json
- // and its replaced by extendManifestJson below:
- manifest?: PwaManifestOptions;
  /**

   * Should you need some dynamic changes to the /src-pwa/manifest.json,

   * use this method to do it.
   */
+ extendManifestJson?: (json: PwaManifestOptions) => void;

  /**

   * PWA manifest filename to use on your browser

   * @default manifest.json
   */
+ manifestFilename?: string;

  /**

   * Does the PWA manifest tag requires crossorigin auth?

   * @default false
   */
+ useCredentialsForManifestTag?: boolean;

  /**

   * Webpack config object for the custom service worker ONLY (`/src-pwa/custom-service-worker`)

   *  when pwa > workboxPluginMode is set to InjectManifest
   */
- extendWebpackCustomSW?: (config: WebpackConfiguration) => void;
  /**

   * Equivalent to `extendWebpackCustomSW()` but uses `webpack-chain` instead,

   *  for the custom service worker ONLY (`/src-pwa/custom-service-worker`)

   *  when pwa > workboxPluginMode is set to InjectManifest
   */
- chainWebpackCustomSW?: (chain: WebpackChain) => void;
  /**

   * Extend the Esbuild config that is used for the custom service worker

   * (if using it through workboxMode: 'InjectManifest')
   */
+ extendPWACustomSWConf?: (config: EsbuildConfiguration) => void;

- /**
-  * @default
-  * ```typescript
-  * {
-  *    appleMobileWebAppCapable: 'yes';
-  *    appleMobileWebAppStatusBarStyle: 'default';
-  *    appleTouchIcon120: 'icons/apple-icon-120x120.png';
-  *    appleTouchIcon180: 'icons/apple-icon-180x180.png';
-  *    appleTouchIcon152: 'icons/apple-icon-152x152.png';
-  *    appleTouchIcon167: 'icons/apple-icon-167x167.png';
-  *    appleSafariPinnedTab: 'icons/safari-pinned-tab.svg';
-  *    msapplicationTileImage: 'icons/ms-icon-144x144.png';
-  *    msapplicationTileColor: '#000000';
-  * }
-   * ```
-  */
- metaVariables?: {
-   appleMobileWebAppCapable: string;
-   appleMobileWebAppStatusBarStyle: string;
-   appleTouchIcon120: string;
-   appleTouchIcon180: string;
-   appleTouchIcon152: string;
-   appleTouchIcon167: string;
-   appleSafariPinnedTab: string;
-   msapplicationTileImage: string;
-   msapplicationTileColor: string;
- };
- metaVariablesFn?: (manifest?: PwaManifestOptions) => PwaMetaVariablesEntry[];
+ /**
+  * Auto inject the PWA meta tags?
+  * If using the function form, return HTML tags as one single string.
+  * @default true
+  */
+ 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-webpack/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()
  }
})

还有更多 /quasar.config 文件变化:

¥There are also more /quasar.config file changes:

/quasar.config file > electron

electron: {
  /** Webpack config object for the Main Process ONLY (`/src-electron/electron-main`) */
- extendWebpackMain?: (config: WebpackConfiguration) => void;
  /**

   * Equivalent to `extendWebpackMain()` but uses `webpack-chain` instead,

   *  for the Main Process ONLY (`/src-electron/electron-main`)
   */
- chainWebpackMain?: (chain: WebpackChain) => void;
  /**

   * Extend the Esbuild config that is used for the electron-main thread
   */
+ extendElectronMainConf?: (config: EsbuildConfiguration) => void;

  /** Webpack config object for the Preload Process ONLY (`/src-electron/electron-preload`) */
- extendWebpackPreload?: (config: WebpackConfiguration) => void;
  /**

   * Equivalent to `extendWebpackPreload()` but uses `webpack-chain` instead,

   *  for the Preload Process ONLY (`/src-electron/electron-preload`)
   */
- chainWebpackPreload?: (chain: WebpackChain) => void;
  /**

   * Extend the Esbuild config that is used for the electron-preload thread
   */
+ extendElectronPreloadConf?: (config: EsbuildConfiguration) => void;

  /**

   * The list of content scripts (js/ts) that you want embedded.

   * Each entry in the list should be a filename (WITHOUT its extension) from /src-electron/

   *    * @default [ 'electron-preload' ]

   * @example [ 'my-other-preload-script' ]
   */
+ preloadScripts?: string[];

  /**

   * Specify the debugging port to use for the Electron app when running in development mode

   * @default 5858
   */
+ inspectPort?: number;

  /**

   * Specify additional parameters when yarn/npm installing

   * the UnPackaged folder, right before bundling with either

   * electron packager or electron builder;
-  * Example: [ '--ignore-optional', '--some-other-param' ]
+  * Example: [ 'install', '--production', '--ignore-optional', '--some-other-param' ]
   */
  unPackagedInstallParams?: string[];
}

SSR 模式变更(SSR mode changes)

¥SSR mode changes

/src-ssr/production-export.js 的支持已被放弃(删除)。现在开发和生产环境都运行同一个 SSR 网络服务器,因此请创建一个包含以下内容的 /src-ssr/server.js

¥The support for /src-ssr/production-export.js has been dropped (delete it). The same SSR webserver now runs for both development and production, so create a /src-ssr/server.js with the following contents:

/src-ssr/server.js

/**

 * More info about this file:

 * https://v2.quasar.dev/quasar-cli-webpack/developing-ssr/ssr-webserver

 *  * Runs in Node context.
 */

/**

 * Make sure to yarn add / npm install (in your project root)

 * anything you import here (except for express and compression).
 */
import express from 'express'
import compression from 'compression'
import {
  defineSsrCreate,
  defineSsrListen,
  defineSsrClose,
  defineSsrServeStaticContent,
  defineSsrRenderPreloadTag
} from '#q-app/wrappers'

/**

 * Create your webserver and return its instance.

 * If needed, prepare your webserver to receive

 * connect-like middlewares.

 *  * Can be async: defineSsrCreate(async ({ ... }) => { ... })
 */
export const create = defineSsrCreate((/* { ... } */) => {
  const app = express()

  // attackers can use this header to detect apps running Express
  // and then launch specifically-targeted attacks
  app.disable('x-powered-by')

  // place here any middlewares that
  // absolutely need to run before anything else
  if (process.env.PROD) {
    app.use(compression())
  }

  return app
})

/**

 * You need to make the server listen to the indicated port

 * and return the listening instance or whatever you need to

 * close the server with.

 *  * The "listenResult" param for the "close()" definition below

 * is what you return here.

 *  * For production, you can instead export your

 * handler for serverless use or whatever else fits your needs.

 *  * Can be async: defineSsrListen(async ({ app, devHttpsApp, port }) => { ... })
 */
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)
    }
  })
})

/**

 * Should close the server and free up any resources.

 * Will be used on development only when the server needs

 * to be rebooted.

 *  * Should you need the result of the "listen()" call above,

 * you can use the "listenResult" param.

 *  * Can be async: defineSsrClose(async ({ listenResult }) => { ... })
 */
export const close = defineSsrClose(({ listenResult }) => {
  return listenResult.close()
})

const maxAge = process.env.DEV
  ? 0
  : 1000 * 60 * 60 * 24 * 30

/**

 * 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)
  }
})

const jsRE = /\.js$/
const cssRE = /\.css$/
const woffRE = /\.woff$/
const woff2RE = /\.woff2$/
const gifRE = /\.gif$/
const jpgRE = /\.jpe?g$/
const pngRE = /\.png$/

/**

 * Should return a String with HTML output

 * (if any) for preloading indicated file
 */
export const renderPreloadTag = defineSsrRenderPreloadTag((file/* , { ssrContext } */) => {
  if (jsRE.test(file) === true) {
    return `<script src="${file}" defer crossorigin></script>`
  }

  if (cssRE.test(file) === true) {
    return `<link rel="stylesheet" href="${file}" crossorigin>`
  }

  if (woffRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
  }

  if (woff2RE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
  }

  if (gifRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/gif" crossorigin>`
  }

  if (jpgRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/jpeg" crossorigin>`
  }

  if (pngRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/png" crossorigin>`
  }

  return ''
})

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

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

/src-ssr/server.js > listen

export const listen = ssrListen(({ 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 }
  }
})

如果你有 /src-ssr/middlewares/compression.js 文件,请将其删除,因为此代码现已嵌入到 /src-ssr/server.js 中。然后编辑你的 /quasar.config 文件以删除对旧文件的引用:

¥If you have /src-ssr/middlewares/compression.js file, delete it because this code is now embedded into /src-ssr/server.js. Then edit your /quasar.config file to remove the reference to the old file:

/quasar.config file

ssr: {
  middlewares: [
-   ctx.prod ? 'compression' : '',
    'render' // keep this as last one
  ]
}

/src-ssr/middlewares/render.js 文件内容示例:

¥Example of /src-ssr/middlewares/render.js file content:

/src-ssr/middlewares/render.js

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

// This middleware should execute as last one
// since it captures everything and tries to
// render the page with Vue

export default defineSsrMiddleware(({ app, resolve, render, serve }) => {
  // we capture any other Express route and hand it
  // over to Vue and Vue Router to render our page
  app.get(resolve.urlPath('*'), (req, res) => {
    res.setHeader('Content-Type', 'text/html')

    render(/* the ssrContext: */ { req, res })
      .then(html => {
        // now let's send the rendered html to the client
        res.send(html)
      })
      .catch(err => {
        // oops, we had an error while rendering the page

        // we were told to redirect to another URL
        if (err.url) {
          if (err.code) {
            res.redirect(err.code, err.url)
          } else {
            res.redirect(err.url)
          }
        } else if (err.code === 404) {
          // hmm, Vue Router could not find the requested route

          // Should reach here only if no "catch-all" route
          // is defined in /src/routes
          res.status(404).send('404 | Page Not Found')
        } else if (process.env.DEV) {
          // well, we treat any other code as error;
          // if we're in dev mode, then we can use Quasar CLI
          // to display a nice error page that contains the stack
          // and other useful information

          // serve.error is available on dev only
          serve.error({ err, req, res })
        } else {
          // we're in production, so we should have another method
          // to display something to the client when we encounter an error
          // (for security reasons, it's not ok to display the same wealth
          // of information as we do in development)

          // Render Error Page on production or
          // create a route (/src/routes) for an error page and redirect to it
          res.status(500).send('500 | Internal Server Error')

          if (process.env.DEBUGGING) {
            console.error(err.stack)
          }
        }
      })
  })
})

对于 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 additional changes to the /quasar.config file:

/quasar.config file

ssr: {
  // ...

  /**

   * If a PWA should take over or just a SPA.

   * When used in object form, you can specify Workbox options

   *  which will be applied on top of `pwa > workboxOptions`.

   *    * @default false
   */
- pwa?: boolean | object;
+ pwa?: boolean;

  /**

   * 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;
- // now part of the /src-ssr/server.js code

  /**

   * 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;

  /**

   * Webpack config object for the Webserver

   * which includes the SSR middleware
   */
- extendWebpackWebserver?: (config: WebpackConfiguration) => void;
  /**

   * Equivalent to `extendWebpackWebserver()` but uses `webpack-chain` instead.

   * Handles the Webserver webpack config ONLY which includes the SSR middleware
   */
- chainWebpackWebserver?: (chain: WebpackChain) => void;
  /**

   * Extend the Esbuild config that is used for the SSR webserver

   * (which includes the SSR middlewares)
   */
+ extendSSRWebserverConf?: (config: EsbuildConfiguration) => void;
}

BEX 模式变更(Bex mode changes)

¥Bex mode changes

BEX 模式的实现与 @quasar/app-vite 中的更高级实现相匹配。但这也意味着你的 /src-bex 文件夹的文件和文件夹结构发生了重大变化。最好将你的 /src-bex 文件夹临时复制到一个安全的地方,然后删除并重新添加 BEX 模式:

¥The implementation of the BEX mode has been matched with the superior implementation from @quasar/app-vite. But this also means that your /src-bex folder has suffered significant files and folders structure changes. It would be best to temporarily copy your /src-bex folder to a safe place, then remove and add back the BEX mode:

$ quasar mode remove bex
$ quasar mode add bex

然后,尝试理解新的结构并将旧的 /src-bex 移植到其中。遗憾的是,没有其他方式可以实现它。点击下方区块展开,查看新旧文件夹结构:

¥And then, try to understand the new structure and port your old /src-bex to it. There is unfortunately no other way to put it. Click on the blocks below to expand and see the old and the new folder structure:

*OLD* folder structure这
content-css.css
# CSS file which is auto injected into the consuming webpage via the manifest.json
icon-16x16.png
# Icon file at 16px x 16px
icon-48x48.png
# Icon file at 48px x 48px
icon-128x128.png
# Icon file at 128px x 128px
background.js
# Standard background script BEX file - auto injected via manifest.json
background-hooks.js
# Background script with a hook into the BEX communication layer
content-hooks.js
# Content script script with a hook into the BEX communication layer
content-script.js
# Standard content script BEX file - auto injected via manifest.json
dom-hooks.js
# JS file which is injected into the DOM with a hook into the BEX communication layer
www/
# Compiled BEX source - compiled from /src (Quasar app)
manifest.json
# Main thread code for production
*NEW* folder structure这
content.css
# CSS file which is auto injected into the consuming webpage via the manifest.json
icon-128x128.png
# Icon file at 128px x 128px
icon-16x16.png
# Icon file at 16px x 16px
icon-48x48.png
# Icon file at 48px x 48px
_locales/
# Optional BEX locales files that you might define in manifest
background.js
# (or .ts) Standard background script BEX file (auto injected via manifest.json)
manifest.json
# The browser extension manifest file
my-content-script.js
# (or .ts) Standard content script BEX file - auto injected via manifest.json (you can have multiple scripts)

改进(Improvements)

¥Improvements

有相当多的改进:

¥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 有一些额外的属性 (vueDevtoolsappPaths):

¥The ctx from /quasar.config file has some additional props (vueDevtools and appPaths):

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

ctx.vueDevtools 的定义如下:

¥The definition for ctx.vueDevtools is:

/** True if opening remote Vue Devtools in development mode. */
vueDevtools: boolean;

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

Typescript 检测基于 tsconfig.json 文件的存在以及 typescript 和 ts-loader 的安装,因此请删除以下内容:

¥The Typescript detection is based on the tsconfig.json file presence and typescript & ts-loader being installed, so please remove the following:

/quasar.config

- /**
-  * Add support for TypeScript.
-  *
-  * @default false
-  */
- supportTS?: boolean | { tsLoaderConfig: object; tsCheckerConfig: object };

/quasar.config 文件 > sourceFiles 的定义有一些变化:

¥The definition of /quasar.config file > sourceFiles has some changes:

/quasar.config > sourceFiles

sourceFiles: {
  rootComponent?: string;
  router?: string;
  store?: string;
  indexHtmlTemplate?: string;

- registerServiceWorker?: string;
- serviceWorker?: string;
+ pwaRegisterServiceWorker?: string;
+ pwaServiceWorker?: string;
+ pwaManifestFile?: string;

  electronMain?: string;
- electronPreload?: string;
- ssrServerIndex?: string;

+ bexManifestFile?: string;
}

有一个新的用于代码检查的属性:

¥There is a new prop for linting:

/quasar.config > eslint (New!)

eslint: {
  /**

   * Should it report warnings?

   * @default false
   */
  warnings?: boolean;

  /**

   * Should it report errors?

   * @default false
   */
  errors?: boolean;

  /**

   * Fix on save.

   * @default false
   */
  fix?: boolean;

  /**

   * Raw options to send to ESLint for Esbuild
   */
  rawEsbuildEslintOptions?: Omit<
    ESLint.Options,
    "cache" | "cacheLocation" | "fix" | "errorOnUnmatchedPattern"
  >;

  /**

   * Raw options to send to ESLint Webpack plugin
   */
  rawWebpackEslintPluginOptions?: WebpackEslintOptions;

  /**

   * Files to include (can be in glob format; for Esbuild ESLint only)
   */
  include?: string[];

  /**

   * Files to exclude (can be in glob format).

   * Recommending to use .eslintignore file instead.

   * @default ['node_modules']
   */
  exclude?: string[];

  /**

   * Enable or disable caching of the linting results.

   * @default true
   */
  cache?: boolean;

  /**

   * Formatter to use

   * @default 'stylish'
   */
  formatter?: ESLint.Formatter;
}
/quasar.config > build

build: {
  /**

   * Transpile JS code with Babel

   *    * @default true
   */
- transpile?: boolean;
+ webpackTranspile?: boolean;

  /**

   * Add dependencies for transpiling with Babel (from node_modules, which are by default not transpiled).

   * It is ignored if "transpile" is not set to true.

   * @example [ /my-dependency/, 'my-dep', ...]
   */
- transpileDependencies?: (RegExp | string)[];
+ webpackTranspileDependencies?: (RegExp | string)[];

  /**

   * Add support for also referencing assets for custom tags props.

   *    * @example { 'my-img-comp': 'src', 'my-avatar': [ 'src', 'placeholder-src' ]}
   */
- transformAssetsUrls?: Record<string, string | string[]>;
  // use vueLoaderOptions instead

  /** Show a progress bar while compiling. */
- showProgress?: boolean;
+ webpackShowProgress?: boolean;

  /**

   * Source map [strategy](https://webpack.js.org/configuration/devtool/) to use.
   */
- devtool?: WebpackConfiguration["devtool"];
+ webpackDevtool?: WebpackConfiguration["devtool"];

  /**

   * Sets [Vue Router mode](https://router.vuejs.org/guide/essentials/history-mode.html).

   * History mode requires configuration on your deployment web server too.

   *    * @default 'hash'
   */
+ vueRouterMode?: "hash" | "history";
  /**

   * Sets Vue Router base.

   * Should not need to configure this, unless absolutely needed.
   */
+ vueRouterBase?: string;

  /**

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

   * PWA index html file.

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

   *    * @default 'offline.html'
   */
- ssrPwaHtmlFilename?: string;
- // Moved to ssr > pwaOfflineHtmlFilename

  /** Options to supply to `ts-loader` */
+ tsLoaderOptions?: object;

  /**

   * Esbuild is used to build contents of /src-pwa, /src-ssr, /src-electron, /src-bex

   * @example

   *    {

   *      browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],

   *      node: 'node20'

   *    }
   */
+ esbuildTarget?: EsbuildTargetOptions;
+ // please check below for the EsbuildTargetOptions interface

  /**

   * Defines constants that get replaced in your app.

   * Unlike `env`, you will need to use JSON.stringify() on the values yourself except for booleans.

   * Also, these will not be prefixed with `process.env.`.

   *    * @example { SOMETHING: JSON.stringify('someValue') } -> console.log(SOMETHING) // console.log('someValue')
   */
+ rawDefine?: { [index: string]: string | boolean | undefined | null };

  /**

   * 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[];
}

interface EsbuildTargetOptions {
  /**

   * @default ['es2022', 'firefox115', 'chrome115', 'safari14']
   */
  browser?: string[];
  /**

   * @example 'node20'
   */
  node?: string;
}

由于 @quasar/app-webpack v4.0.0-beta.3 中已升级到 webpack-dev-server v5:

¥Due to the upgrade to webpack-dev-server v5 in @quasar/app-webpack v4.0.0-beta.3:

/quasar.config > devServer

devServer: {
- proxy: {
-   "/api": {
-     target: "http://localhost:3000",
-     changeOrigin: true,
-   },
- }
+ proxy: [
+   {
+     context: ["/api"],
+     target: "http://localhost:3000",
+     changeOrigin: true,
+   },
+ ]
}

环境点文件支持(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-webpack v4.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-webpack v4.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
  }
}