@quasar/app-vite v2 (beta)
CLI 目前处于 beta 版本
- 请帮助测试 CLI,以便我们将其从
beta
状态中移除。我们提前感谢您的帮助! - 虽然我们不打算添加任何进一步的重大更改,但仍然有很小的可能性,根据您的反馈,我们可能被迫进行一项更改。
警告
所有其他文档页面将引用旧的 @quasar/app-vite 版本 (v1) 规范。只有此页面(目前)提到了如何使用 v2 beta 版本。
致应用扩展所有者的说明
您可能希望发布支持新的 @quasar/app-vite 的 Quasar 应用扩展的新版本。如果您没有接触 quasar.config 配置,那么它将像更改以下内容一样容易
api.compatibleWith(
'@quasar/app-vite',
- '^1.0.0'
+ '^1.0.0 || ^2.0.0-beta.1'
)
值得注意的重大更改
- 最小 Node.js 版本现在为 18(主要是由于 Vite 5)
- 我们已转向整个 Quasar 项目文件夹的 ESM 风格,因此许多默认项目文件现在需要 ESM 代码(虽然可以使用
.cjs
作为这些文件的扩展名,但您很可能需要重命名扩展名,如果您不希望更改任何内容)。一个例子是/quasar.config.js
文件,它现在也被认为是 ESM(因此,如果您仍然想要 CommonJs 文件,请将.js
更改为.cjs
)。 - 由于最新的 @quasar/testing-* 包的更新,“test” 命令已被删除。见 此处
- “clean” 命令已重新设计。在您的升级的 Quasar 项目文件夹中键入“quasar clean -h”以获取更多信息。
- Typescript 检测基于 quasar.config 文件是否采用 TS 格式 (quasar.config.ts) 和 tsconfig.json 文件的存在。
- feat+refactor(app-vite): 能够同时运行多个模式 + dev/build(巨大的努力!)
- SSR 和 Electron 模式现在以 ESM 格式构建。
- 放弃了对我们内部 linting 系统的支持 (quasar.config 文件 > eslint)。应使用 vite-plugin-checker 代替。
- 我们将在下面详细介绍每个 Quasar 模式的更多重大更改.
新功能的亮点
以下某些工作已回溯到旧的 @quasar/app-vite v1,但此处发布供读者了解。
- feat(app-vite): 升级到 Vite 5
- feat(app-vite): 能够同时运行多个 quasar dev/build 命令(例如:可以同时运行“quasar dev -m capacitor”和“quasar dev -m ssr”以及“quasar dev -m capacitor -T ios”)
- feat(app-vite): 更好的 TS 类型整体
- refactor(app-vite): 将 CLI 移植到 ESM 格式(重大努力!尤其是支持 Vite 5 和 SSR)
- feat(app-vite): 支持多种格式的 quasar.config 文件 (.js, .mjs, .ts, .cjs)
- feat(app-vite): 改进 quasarConfOptions,为其生成类型,改进文档 (fix: #14069) (#15945)
- feat(app-vite): 如果 quasar.config 文件中的一个导入发生更改,则重新加载应用
- feat(app-vite): TS 检测应考虑 quasar.config 文件的格式 (quasar.config.ts)
- feat(app-vite): 简写 CLI 命令“quasar dev/build -m ios/android”现在针对 Capacitor 模式,而不是 Cordova (2.0.0-beta.12+)
- feat(app-vite): 支持使用 HTTPS 进行 SSR 开发
- feat(app-vite): env 点文件支持 #15303
- feat(app-vite): 新的 quasar.config 文件属性:build > envFolder (string) 和 envFiles (string[])
- feat(app-vite): 通过 quasar.config 文件更改应用 URL 时,重新打开浏览器(如果配置如此)
- feat&perf(app-vite): 更快、更准确的算法,用于确定要使用的节点包管理器
- feat(app-vite): 升级依赖项
- feat(app-vite): 删除 Electron 6-8 中 CLI 模板的错误解决方法 (#15845)
- feat(app-vite): 删除 Capacitor v5+ 的 bundleWebRuntime 配置
- feat(app-vite): 默认使用 workbox v7
- feat(app-vite): quasar.config > pwa > injectPwaMetaTags 现在也可以是函数:(({ pwaManifest, publicPath }) => string);
- feat(app-vite): quasar.config > build > htmlMinifyOptions
- feat(app-vite): 查找正在使用时用于 vue devtools 的开放端口;能够使用 vue devtools 运行多个 CLI 实例
- perf(app-vite): 根据主机项目以特定 esm 或 cjs 格式渲染模板;通过变量插值
- perf(app-vite): 只为“dev”命令验证 quasar.conf 服务器地址
- feat(app-vite): 为每个实例选择新的 electron 检查端口
- feat(app-vite): Electron - 现在可以加载多个预加载脚本
- refactor(app-vite): AE 支持 - 更好、更高效的算法
- feat(app-vite): AE 对 ESM 格式的支持
- feat(app-vite): AE 对 TS 格式的支持(通过构建步骤)
- feat(app-vite): AE API 新方法 -> hasTypescript() / hasLint() / getStorePackageName() / getNodePackagerName()
- feat(app-vite): AE -> 提示 API(以及提示默认导出函数为异步的能力)
- refactor(app-vite): “clean” 命令现在工作方式不同,因为 CLI 可以在同一个项目文件夹中运行多个实例(dev 或 build 上的多个模式)
- feat(app-vite): 支持 Bun 作为包管理器 #16335
- feat(app-vite): 对于默认的 /src-ssr 模板 -> prod ssr -> 发生错误时,如果使用调试功能构建,则打印错误堆栈
- feat(app-vite): 扩展构建 > vitePlugins 表单(附加 { server?: boolean, client?: boolean } 参数
升级过程的开始
推荐
如果您不确定是否会不小心跳过任何推荐的更改,您可以随时使用 @quasar/app-vite v2 beta 构建一个新的项目文件夹,然后从那里轻松开始移植您的应用程序。大多数更改指的是不同的项目文件夹配置文件,而主要不是指您的 /src 文件。
$ yarn create quasar
当被要求“选择 Quasar App CLI 变体”时,回答“带有 Vite 5 的 Quasar App CLI (BETA | 下一个主要版本 - v2)”。
准备工作
如果您使用的是 Quasar CLI 的全局安装(
@quasar/cli
),请确保您拥有最新的版本。这是由于 quasar.config 文件支持多种格式。再次强调,现在支持的 Node.js 最低版本为 v18(始终使用 Node.js 的 LTS 版本 - 版本越高越好)。
在
@quasar/app-vite
条目中编辑您的/package.json
并为其分配^2.0.0-beta.1
"devDependencies": { - "@quasar/app-vite": "^1.0.0", + "@quasar/app-vite": "^2.0.0-beta.1" }
content_paste
然后 yarn/npm/pnpm/bun install。将您的
/quasar.config.js
文件转换为 ESM 格式(推荐使用,否则将文件扩展名重命名为.cjs
并使用 CommonJs 格式)。import { configure } from 'quasar/wrappers' export default configure((/* ctx */) => { return { // ... } })
content_paste关于 Typescript 的提示
如果您愿意,您现在也可以用 TS 编写此文件(将
/quasar.config.js
重命名为/quasar.config.ts
- 注意.ts
文件扩展名)。我们强烈建议您在您的
/package.json
中将type
设置为module
。根据它,Quasar CLI 将对它构建的发布文件做出决定(例如:ESM 或 CJS 格式的 Electron)。{ + "type": "module" }
content_paste
请记住,如果您使用它,将 `postcss.config.js` 文件转换为 ESM。此外,将 `.eslintrc.js` 重命名为 `.eslintrc.cjs`(内容不变)。您可能希望将以下内容添加到您的
/.gitignore
文件中。/quasar.config.*.temporary.compiled*
条目指的是当您的/quasar.config
文件出现问题时(并且可以通过quasar clean
命令删除)为了检查目的而保留的文件。.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
content_paste如果您使用 Typescript,请确保您的
/tsconfig.json
文件如下所示{ "extends": "@quasar/app-vite/tsconfig-preset", "compilerOptions": { "baseUrl": "." }, "exclude": [ "./dist", "./.quasar", "./node_modules", "./src-capacitor", "./src-cordova", "./quasar.config.*.temporary.compiled*" ] }
content_paste必须从您的项目文件夹中删除特性标志文件。它们需要重新生成(会自动发生)。
# in project folder root: $ npx rimraf -g ./**/*-flag.d.ts $ quasar build # or dev
content_paste
代码整理(TS 或 JS)
我们放弃了对内部代码整理(quasar.config 文件 > eslint)的支持,转而支持 vite-plugin-checker 包。我们将在下面详细介绍根据您使用 TS 或 JS 的情况需要做出的更改。
Typescript 项目代码整理
$ yarn add --dev vite-plugin-checker vue-tsc@2 typescript
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.cjs
/quasar.config.*.temporary.compiled*
在项目文件夹的根目录中创建一个名为 tsconfig.vue-tsc.json
的新文件
{
"extends": "./tsconfig.json",
"compilerOptions": {
"skipLibCheck": true
}
}
- eslint: {
- // ...
- },
build: {
vitePlugins: [
+ ['vite-plugin-checker', {
+ vueTsc: {
+ tsconfigPath: 'tsconfig.vue-tsc.json'
+ },
+ eslint: {
+ lintCommand: 'eslint "./**/*.{js,ts,mjs,cjs,vue}"'
+ }
+ }, { server: false }]
]
}
Javascript 项目代码整理
$ yarn add --dev vite-plugin-checker
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.cjs
/quasar.config.*.temporary.compiled*
- eslint: {
- // ...
- },
build: {
vitePlugins: [
+ ['vite-plugin-checker', {
+ eslint: {
+ lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"'
+ }
+ }, { server: false }]
]
}
SPA / Capacitor / Cordova 模式更改
无需在 /src
、/src-capacitor
或 /src-cordova
文件夹中更改任何内容。
PWA 模式更改
CLI 不再提供 register-service-worker
依赖项。您必须在项目文件夹中自己安装它。
$ yarn add register-service-worker@^1.0.0
编辑您的 /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
文件中也有一些细微的更改
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 模式更改
警告
发布文件(您的生产代码)将被编译为 ESM 格式,从而也利用 ESM 格式的 Electron。
提示
您可能希望将 electron
包升级到最新版本,以便它可以处理 ESM 格式。
大多数更改指的是编辑您的 /src-electron/electron-main.js
文件
+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
// ...
})
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
以指定您的预加载脚本
sourceFiles: {
- electronPreload?: string;
},
electron: {
+ // Electron preload scripts (if any) from /src-electron, WITHOUT file extension
+ preloadScripts: [ 'electron-preload' ],
}
如您所见,您现在可以根据需要指定多个预加载脚本。
function createWindow () {
// ...
- mainWindow.loadURL(process.env.APP_URL)
+ if (process.env.DEV) {
+ mainWindow.loadURL(process.env.APP_URL)
+ } else {
+ mainWindow.loadFile('index.html')
+ }
最后,新文件应该如下所示
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
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) {
mainWindow.loadURL(process.env.APP_URL)
} else {
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 模式更改
警告
发布文件(您的生产代码)将被编译为 ESM 格式。
大多数更改指的是编辑您的 /src-ssr/server.js
文件。由于您现在也可以在开发应用程序时使用 HTTPS,因此您需要对文件进行以下更改
- 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: ssrListen() param can still be async (below it isn't)
+ export const listen = ssrListen(({ 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)
}
})
})
最后,它应该如下所示
export const listen = ssrListen(({ app, devHttpsApp, port }) => {
const server = devHttpsApp || app
return server.listen(port, () => {
if (process.env.PROD) {
console.log('Server listening at port ' + port)
}
})
})
对于无服务器方法,这是“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 }
}
})
接下来,serveStaticContent
函数已更改
- 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: ssrServeStaticContent(async ({ app, resolve }) => {
+ * Can return an async function: return async ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
+ */
+ export const serveStaticContent = ssrServeStaticContent(({ app, resolve }) => {
+ return ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
+ const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts })
+ app.use(resolve.urlPath(urlPath), serveFn)
+ }
+ })
此外,renderPreloadTag()
函数现在可以接受一个额外的参数(ssrContext
)
export const renderPreloadTag = ssrRenderPreloadTag((file, { ssrContext }) => {
// ...
})
对于 TS 开发者,您还应该对您的 /src-ssr/middlewares 文件进行一些小的更改,如下所示
+ import { Request, Response } from 'express';
// ...
- app.get(resolve.urlPath('*'), (req, res) => {
+ app.get(resolve.urlPath('*'), (req: Request, res: Response) => {
在 /quasar.config
文件中也有一些补充内容
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 模式更改
无需更改任何内容,但是我们在此强调对 /quasar.conf
文件的补充内容
sourceFiles: {
+ bexManifestFile: 'src-bex/manifest.json',
// ...
},
其他 /quasar.config 文件更改
来自 /quasar.config
文件的 ctx
有一个额外的属性(appPaths
)
import { configure } from 'quasar/wrappers'
export default configure((ctx) => ({
// ctx.appPaths is available
如下使用 QuasarAppPaths TS 类型定义 ctx.appPaths
的定义
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;
}
eslint: {
/**
* Enable or disable caching of the linting results.
* @default true
*/
+ cache?: boolean;
/**
* Formatter to use
* @default 'stylish'
*/
+ formatter?: ESLint.Formatter;
}
sourceFiles: {
+ bexManifestFile?: string;
}
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
}
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[];
}
其他注意事项
您可能希望从 @intlify/vite-plugin-vue-i18n
升级/切换到更新的 @intlify/unplugin-vue-i18n
。
删除旧包并安装新包后,请更新您的 /quasar.config
文件,如下所示
- import path from 'node:path'
+ import { fileURLToPath } from 'node:url'
export default configure((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'
}]
]
}
}
})
env 点文件支持
稍微扩展一下 env 点文件支持。这些文件将被检测到并使用(顺序很重要)
.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
文件中。
您还可以配置上面的文件以从不同的文件夹中提取,甚至可以将更多文件添加到列表中
build: {
envFolder: './' // absolute or relative path to root project folder
envFiles: [
// Path strings to your custom files --- absolute or relative path to root project folder
]
}