@quasar/app-webpack v4(测试版)
CLI 目前处于测试阶段
- 请帮助测试 CLI,以便我们能够将其从
beta
状态中移除。感谢您的帮助! - 虽然我们不打算添加任何进一步的重大更改,但根据您的反馈,我们仍然可能会被迫进行一些细微的更改。
警告
所有其他文档页面都将参考旧版 @quasar/app-webpack 版本 (v3) 的规范。目前只有此页面提到了如何使用 v4 测试版。
致应用扩展所有者
您可能希望发布新版本的 Quasar 应用扩展,以支持新的 @quasar/app-webpack。如果您没有修改 quasar.config 配置,那么只需更改以下内容即可
api.compatibleWith(
'@quasar/app-webpack',
- '^3.0.0'
+ '^3.0.0 || ^4.0.0-beta.1'
)
显著的重大更改
- Node.js 最小版本现已更新至 18.12。
- 我们已将整个 Quasar 项目文件夹转向 ESM 样式,因此许多默认项目文件现在需要 ESM 代码(尽管可以使用
.cjs
作为这些文件的扩展名,但如果您不希望更改任何内容,则很可能需要重命名扩展名)。一个示例是/quasar.config.js
文件,现在也假设它是 ESM(因此如果您仍然想要一个 CommonJs 文件,请从.js
更改为.cjs
)。 - 从 @quasar/app-vite 移植并适配了更优秀的 devserver 实现,用于所有 Quasar 模式。好处巨大。
- 从 @quasar/app-vite 移植了 SSR、PWA、Electron 和 BEX 模式的更优秀实现。我们将在本文档页面上详细说明每个 Quasar 模式的更改。
- SSR - 一些显著的改进
- 改进的可靠性:相同的服务器代码在开发和生产环境中运行
- 更多目标 Web 服务器选项:您可以用您正在使用的任何其他内容替换 express()。
- 性能:当更改 /src-ssr 中的代码时,客户端代码不再需要从头重新编译。
- /src-ssr 中文件的编译速度更快且更好(现在使用 Esbuild 而不是 Webpack 构建)。
- PWA - 一些显著的改进
- 许多新的配置选项(同时删除了许多旧的选项)
- /src-pwa 中文件的编译速度更快且更好(现在使用 Esbuild 而不是 Webpack 构建)。
- Electron
- 现在编译为 ESM(从而也利用了 ESM 格式的 Electron)。
- /src-electron 中文件的编译速度更快且更好(现在使用 Esbuild 而不是 Webpack 构建)。
- 支持多个预加载脚本。
- BEX - 一些显著的改进
- 移植了 @quasar/app-vite 中更优秀的实现,这也意味着当您启动此模式时,可以在扩展清单 v2 和清单 v3 之间进行选择。
- 清单现在保存在它自己的文件中(/src-pwa/manifest.json),而不是在 /quasar.config 文件中。
- SSR - 一些显著的改进
- Webpack 现在只会编译
/src
文件夹的内容,而其余部分(/src-pwa、/src-electron 等)现在由 Esbuild 处理。这转化为更快的构建速度和对 Node.js 格式的处理。 - 由于 @quasar/testing-* 包的最新更新,已删除“test”命令。请参阅 此处。
- “clean”命令已重新设计。在您升级的 Quasar 项目文件夹中键入“quasar clean -h”以获取更多信息。
- Typescript 检测基于 quasar.config 文件是否为 TS 格式(quasar.config.ts)以及 tsconfig.json 文件是否存在。
- 我们将在下面详细说明每个 Quasar 模式的更多重大更改。.
新增功能亮点
以下某些工作已回传到旧的 @quasar/app-webpack v3,但在此处发布以供读者了解。
- feat(app-webpack):能够同时运行多个 quasar dev/build 命令(例如:可以同时运行“quasar dev -m capacitor”和“quasar dev -m ssr”以及“quasar dev -m capacitor -T ios”)。
- feat(app-webpack):支持多种格式的 quasar.config 文件(.js、.mjs、.ts、.cjs)。
- feat(app-webpack):总体上更好的 TS 类型定义。
- feat(app-webpack):升级到 Typescript v5;删除 fork-ts-checker。
- feat(app-webpack):改进 quasarConfOptions,为其生成类型,改进文档(修复:#14069)(#15945)
- feat(app-webpack):如果 quasar.config 文件中的某个导入发生更改,则重新加载应用程序。
- feat(app-webpack):TS 检测也应考虑 quasar.config 文件的格式(quasar.config.ts)。
- feat(app-webpack):简写 CLI 命令“quasar dev/build -m ios/android”现在目标是 Capacitor 模式而不是 Cordova(4.0.0-beta.13+)。
- feat(app-webpack):env dotfiles 支持 #15303
- feat(app-webpack):新的 quasar.config 文件属性:build > envFolder(字符串)和 envFiles(字符串[])。
- feat(app-webpack):支持多种格式的 postcss 配置文件:postcss.config.cjs、.postcssrc.js、postcss.config.js、postcss.config.mjs、.postcssrc.cjs、.postcssrc.mjs。
- feat(app-webpack):支持多种格式的 babel 配置文件:babel.config.cjs、babel.config.js、babel.config.mjs、.babelrc.js、.babelrc.cjs、.babelrc.mjs、.babelrc。
- feat(app-webpack):通过 quasar.config 文件更改应用程序 URL 时重新打开浏览器(如果已配置)。
- feat(app-webpack):从 q/app-vite 移植 quasar.config 文件 > electron > inspectPort 属性。
- feat(app-webpack):从 q/app-vite 移植 quasar.config 文件 > build > rawDefine。
- feat&perf(app-webpack):更快且更准确的确定要使用的节点包管理器算法。
- feat(app-webpack):高度提高 SSR 性能 + 内存使用率(尤其是在生产环境中);ssr-helpers 的主要重构;还包括来自 q/app-vite 的 renderPreloadTag()。
- feat(app-webpack):支持使用 HTTPS 进行 SSR 开发。
- feat(app-webpack):SSR - 能够用任何其他类似 connect 的 Web 服务器替换 express()。
- feat(app-webpack):SSR - 更改 /src-ssr 中的代码时不再重新编译所有内容。
- feat(app-webpack):升级依赖项。
- feat(app-webpack):删除 CLI 模板中 Electron 6-8 中错误的解决方法(#15845)。
- feat(app-webpack):删除 Capacitor v5+ 的 bundleWebRuntime 配置。
- feat(app-webpack):默认使用 workbox v7。
- feat(app-webpack):quasar.config > build > htmlMinifyOptions。
- feat+refactor(app-webpack):能够同时运行多个模式 + dev/build。
- feat(app-webpack):查找正在使用的 vue devtools 的开放端口;能够使用 vue devtools 运行多个 cli 实例。
- perf(app-webpack):仅为“dev”命令验证 quasar.conf 服务器地址。
- feat(app-webpack):为每个实例选择新的 electron inspect 端口。
- refactor(app-webpack):AE 支持 - 更好、更高效的算法。
- feat(app-webpack):对 ESM 格式的支持。
- feat(app-webpack):对 TS 格式的支持(通过构建步骤)。
- feat(app-webpack):AE API 新方法 -> hasTypescript() / hasLint() / getStorePackageName() / getNodePackagerName()。
- feat(app-webpack):AE -> 提示 API(以及提示默认导出函数为异步的能力)。
- feat(app-webpack):更智能的应用程序文件验证。
- refactor(app-webpack):“clean”命令现在的工作方式有所不同,因为 CLI 可以在同一项目文件夹中以多个实例运行(开发或构建中的多个模式)。
- feat(app-webpack):支持 Bun 作为包管理器 #16335
- feat(app-webpack):对于默认的 /src-ssr 模板 -> 生产 ssr -> 发生错误时,如果使用调试功能构建,则打印错误堆栈。
- fix(app-webpack):electron 预加载脚本触发“模块未找到”。
- feat(app-webpack):升级到 webpack-dev-server v5。
升级过程的开始
建议
如果您不确定是否不会错误地跳过任何建议的更改,您可以随时使用 @quasar/app-webpack v4 beta 构建一个新的项目文件夹,然后轻松地从那里开始移植您的应用程序。大部分更改是指不同的项目文件夹配置文件,主要不是指您的 /src 文件。
$ yarn create quasar
当被要求“选择 Quasar App CLI 变体”时,请回答:“带有 Webpack 的 Quasar App CLI(BETA | 下一个主要版本 - v4)”。
准备工作
如果使用 Quasar CLI 的全局安装(
@quasar/cli
),请确保您拥有最新版本。这是由于支持多种格式的 quasar.config 文件。再次强调,Node.js 的最低支持版本现在是 v16(始终使用 Node.js 的 LTS 版本 - 版本越高越好)。
在
/package.json
上的@quasar/app-webpack
条目中进行编辑,并将其赋值为^4.0.0-beta.1
。"devDependencies": { - "@quasar/app-webpack": "^3.0.0", + "@quasar/app-webpack": "^4.0.0-beta.1" }
内容粘贴
然后 yarn/npm/pnpm/bun install。将您的
/quasar.config.js
文件转换为 ESM 格式(建议这样做,否则将文件扩展名重命名为.cjs
并使用 CommonJs 格式)。import { configure } from 'quasar/wrappers' export default configure((/* ctx */) => { return { // ... } })
内容粘贴关于 Typescript 的提示
如果您愿意,您现在也可以用 TS 编写此文件(将
/quasar.config.js
重命名为/quasar.config.ts
- 注意.ts
文件扩展名)。为了与 @quasar/app-vite 保持一致(以及在 @quasar/app-webpack 和它之间轻松切换),将
/src/index.template.html
移动到/index.html
并进行以下更改。<body> - <!-- DO NOT touch the following DIV --> - <div id="q-app"></div> + <!-- quasar:entry-point --> </body>
内容粘贴(可选,但建议)为了将来证明某些工具配置文件,请重命名以下文件(在根项目文件夹中)。
旧名称 新名称 postcss.config.js postcss.config.cjs .eslintrc.js .eslintrc.cjs babel.config.js babel.config.cjs 您可能希望将以下内容添加到您的
/.gitignore
文件中。当您的/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
内容粘贴如果您有 linting,请查看您的
/.eslintignore
文件。/dist /src-capacitor /src-cordova /.quasar /node_modules .eslintrc.cjs babel.config.cjs /quasar.config.*.temporary.compiled*
内容粘贴如果您使用 Typescript,请确保您的
/tsconfig.json
文件如下所示。{ "extends": "@quasar/app-webpack/tsconfig-preset", "compilerOptions": { "baseUrl": "." }, "exclude": [ "./dist", "./.quasar", "./node_modules", "./src-capacitor", "./src-cordova", "./quasar.config.*.temporary.compiled*" ] }
内容粘贴功能标志文件必须从您的项目文件夹中删除。它们需要重新生成(将自动发生)。
# in project folder root: $ npx rimraf -g ./**/*-flag.d.ts $ quasar build # or dev
内容粘贴
SPA / Capacitor / Cordova 模式更改
无需更改 /src
、/src-capacitor
或 /src-cordova
文件夹中的任何内容。
PWA 模式更改
register-service-worker
依赖项不再由 CLI 提供。您需要在您的项目文件夹中自己安装它。
$ yarn add register-service-worker@^1.0.0
编辑您的 /src-pwa/custom-service-worker.js
文件。
- import { precacheAndRoute } from 'workbox-precaching'
- // Use with precache injection
- precacheAndRoute(self.__WB_MANIFEST)
+ 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 从那里移动到此文件。以下是如何显示示例。
{
"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 文件中也有一些细微的更改。
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 模式更改
警告
可分发文件(您的生产代码)将编译为 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-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) {
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()
}
})
/quasar.config 文件中也有一些更改。
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 模式更改
对 /src-ssr/production-export.js
的支持已被删除(删除它)。相同的 SSR Web 服务器现在同时用于开发和生产,因此使用以下内容创建一个 /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 {
ssrClose,
ssrCreate,
ssrListen,
ssrServeStaticContent,
ssrRenderPreloadTag
} from 'quasar/wrappers'
/**
* Create your webserver and return its instance.
* If needed, prepare your webserver to receive
* connect-like middlewares.
*
* Can be async: ssrCreate(async ({ ... }) => { ... })
*/
export const create = ssrCreate((/* { ... } */) => {
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: ssrListen(async ({ app, devHttpsApp, 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)
}
})
})
/**
* 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: ssrClose(async ({ listenResult }) => { ... })
*/
export const close = ssrClose(({ 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: 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)
}
})
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 = ssrRenderPreloadTag((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”部分的外观。
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
文件以删除对旧文件的引用。
ssr: {
middlewares: [
- ctx.prod ? 'compression' : '',
'render' // keep this as last one
]
}
/src-ssr/middlewares/render.js 文件内容示例
import { ssrMiddleware } from 'quasar/wrappers'
// This middleware should execute as last one
// since it captures everything and tries to
// render the page with Vue
export default ssrMiddleware(({ 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 文件进行一些小的更改,如下所示。
+ import { Request, Response } from 'express';
// ...
- app.get(resolve.urlPath('*'), (req, res) => {
+ app.get(resolve.urlPath('*'), (req: Request, res: Response) => {
/quasar.config 文件中还有一些其他更改。
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 模式的实现已从 @quasar/app-vite 移植,因此当您启动此 Quasar 模式时,它现在会询问您想要哪种扩展清单版本(v2 或 v3)。
但这同时也意味着您的 /src-bex
文件夹的文件夹和文件结构发生了重大变化。最好先将您的 /src-bex 文件夹临时复制到安全位置,然后删除并重新添加 BEX 模式
$ quasar mode remove bex
$ quasar mode add bex
然后,尝试理解新的结构并将您的旧 /src-bex 移植到其中。很遗憾,没有其他方法。
但首先,您需要注意一些 /quasar.config
文件的更改
sourceFiles: {
+ bexManifestFile: 'src-bex/manifest.json',
// ...
},
bex: {
- builder: {
- directories: {
- input: cfg.build.distDir,
- output: path.join(cfg.build.packagedDistDir, 'Packaged')
- }
- }
}
一些更改,例如将后台脚本从 /js/background.js
直接移动到根文件夹,是由于外部因素为了扩展结构的未来发展而必需的。
提示
临时,直到此版本的 @quasar/app-webpack 退出测试版状态,最好查看 Quasar CLI with Vite 文档中关于 BEX 的内容,因为它们现在大多会匹配。
点击下面的块展开并查看旧的和新的文件夹结构
旧的文件夹结构
新的文件夹结构
其他 /quasar.config 文件更改
/quasar.config
文件中的 ctx
具有一些额外的属性(vueDevtools
和 appPaths
)
import { configure } from 'quasar/wrappers'
export default configure((ctx) => ({
// ctx.vueDevtools & ctx.appPaths is available
ctx.vueDevtools
的定义如下
/** True if opening remote Vue Devtools in development mode. */
vueDevtools: boolean;
ctx.appPaths
的定义使用 QuasarAppPaths TS 类型,如下所示
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 检测基于 quasar.config 文件是否为 TS 格式(quasar.config.ts)以及 tsconfig.json 文件是否存在,因此请删除以下内容
- /**
- * Add support for TypeScript.
- *
- * @default false
- */
- supportTS?: boolean | { tsLoaderConfig: object; tsCheckerConfig: object };
/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;
}
有一个用于代码风格检查的新属性
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;
}
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.cn/configuration/devtool/) to use.
*/
- devtool?: WebpackConfiguration["devtool"];
+ webpackDevtool?: WebpackConfiguration["devtool"];
/**
* Sets [Vue Router mode](https://router.vuejs.ac.cn/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
devServer: {
- proxy: {
- "/api": {
- target: "http://localhost:3000",
- changeOrigin: true,
- },
- }
+ proxy: [
+ {
+ context: ["/api"],
+ target: "http://localhost:3000",
+ changeOrigin: true,
+ },
+ ]
}
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
]
}