@quasar/app-webpack v4 (beta)
CLI 目前處於 Beta 測試階段
- 請協助測試 CLI,以便我們可以讓它脫離 Beta 測試階段。 我們在此預先感謝您的協助!
- 雖然我們不打算新增任何進一步的重大變更,但根據您的回饋,仍然有很小的機率我們會被迫進行變更。
警告
所有其他文件頁面將參考舊版的 @quasar/app-webpack 版本 (v3) 規格。 只有此頁面(目前)提及如何使用 v4 Beta 版。
給 App Extensions 擁有者的一則注意事項
您可能需要發布新版本的 Quasar App Extensions,以支援新的 @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 模式移植並改編了來自 @quasar/app-vite 的更優越的 devserver 實作。 優點非常顯著。
- 從 @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 移植了更優越的實作,這也表示當您產生模式時,您可以選擇 extension Manifest v2 和 Manifest v3
- manifest 現在保存在自己的檔案 (/src-pwa/manifest.json) 中,而不是在 /quasar.config 檔案內
- SSR - 一些值得注意的改進
- Webpack 現在將只編譯
/src
資料夾的內容,而其餘部分 (/src-pwa、/src-electron 等) 現在由 Esbuild 處理。 這轉化為更優越的建置速度和 Node.js 格式的處理。 - 由於 @quasar/testing-* 套件的最新更新,"test" cmd 已被移除。 請參閱 此處
- "clean" cmd 已重新設計。 在您升級後的 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 點檔案支援 #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 檔案變更應用程式網址時,重新開啟瀏覽器 (如果已設定)
- feat(app-webpack):從 q/app-vite 移植 quasar.config 檔案 > electron > inspectPort 屬性
- feat(app-webpack):從 q/app-vite 移植 quasar.config 檔案 > build > rawDefine
- feat&perf(app-webpack):更快且更精確的演算法,用於判斷要使用的 node 套件管理器
- feat(app-webpack):大幅提升 SSR 效能 + 記憶體使用量 (尤其針對生產環境);主要重構 ssr-helpers;同時包含來自 q/app-vite 的 renderPreloadTag()
- feat(app-webpack):支援使用 HTTPS 進行 SSR 開發
- feat(app-webpack):SSR - 能夠將 express() 替換為任何其他類似 connect 的網路伺服器
- 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):AE 支援 ESM 格式
- feat(app-webpack):AE 支援 TS 格式 (透過建置步驟)
- feat(app-webpack):AE API 新方法 -> hasTypescript() / hasLint() / getStorePackageName() / getNodePackagerName()
- feat(app-webpack):AE -> Prompts API (以及 prompts 預設匯出函式能夠為非同步)
- feat(app-webpack):更聰明的應用程式檔案驗證
- refactor(app-webpack):由於 CLI 可以在同一個專案資料夾中的多個實例上執行 (dev 或 build 的多個模式),“clean” 命令現在運作方式不同
- feat(app-webpack):支援 Bun 作為套件管理器 #16335
- feat(app-webpack):對於預設 /src-ssr 範本 -> prod ssr -> 發生錯誤時,如果使用偵錯功能建置,則列印錯誤堆疊
- fix(app-webpack):electron preload script 觸發 “找不到模組”
- feat(app-webpack):升級至 webpack-dev-server v5
升級流程開始
建議
如果您不確定是否會不小心跳過任何建議的變更,您可以隨時使用 @quasar/app-webpack v4 beta 建立新的專案資料夾,然後輕鬆地從那裡開始移植您的應用程式。大部分的變更都與不同的專案資料夾設定檔有關,而且主要**不是**針對您的 /src 檔案。
$ yarn create quasar
當被詢問「選擇 Quasar App CLI 變體」時,回答:「Quasar App CLI with Webpack (BETA | next major version - 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" }
content_paste
然後執行 yarn/npm/pnpm/bun install。將您的
/quasar.config.js
檔案轉換為 ESM 格式 (建議使用此格式,否則請將檔案副檔名重新命名為.cjs
並使用 CommonJs 格式)。import { configure } from 'quasar/wrappers' export default configure((/* ctx */) => { return { // ... } })
content_pasteTypescript 提示
如果您願意,現在也可以使用 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>
content_paste(選用,但建議) 為了讓某些工具設定檔具有未來的前瞻性,請重新命名以下檔案 (在專案根資料夾中)
舊名稱 新名稱 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
content_paste如果您有使用 linting,請同時檢查您的
/.eslintignore
檔案/dist /src-capacitor /src-cordova /.quasar /node_modules .eslintrc.cjs babel.config.cjs /quasar.config.*.temporary.compiled*
content_paste如果使用 Typescript,請確保您的
/tsconfig.json
檔案看起來像這樣{ "extends": "@quasar/app-webpack/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
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
以指定您的 preload script
sourceFiles: {
- electronPreload?: string;
},
electron: {
+ // Electron preload scripts (if any) from /src-electron, WITHOUT file extension
+ preloadScripts: [ 'electron-preload' ],
}
如您所見,如果您需要,現在可以指定多個 preload script。
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 網路伺服器,因此請建立一個包含以下內容的 /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 模式時,現在會詢問您想要哪個擴充功能 Manifest 版本 (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 脫離 beta 狀態之前,最好查看 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;
}
有一個新的 linting 屬性
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.dev.org.tw/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
devServer: {
- proxy: {
- "/api": {
- target: "https://127.0.0.1:3000",
- changeOrigin: true,
- },
- }
+ proxy: [
+ {
+ context: ["/api"],
+ target: "https://127.0.0.1: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
]
}