為何捐款
API 瀏覽器
升級指南
NEW!
quasar.config 檔案
轉換為搭配 Webpack 的 CLI
瀏覽器相容性
支援 TypeScript
目錄結構
命令列表
CSS 預處理器
路由
懶載入 - 代碼分割
處理資源
啟動檔案
預取功能
API 代理
處理 Webpack
處理 process.env
使用 Pinia 的狀態管理
使用 Vuex 的狀態管理
Linter
測試與稽核
開發行動應用程式
Ajax 請求
開放 Dev Server 給公眾
Quasar CLI with Webpack - @quasar/app-webpack
Quasar CLI with Webpack 升級指南

@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 檔案內
  • 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

    /package.json

    "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 格式)。

    /quasar.config.js 檔案

    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 並進行以下變更

    /index.html

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

  • (選用,但建議) 為了讓某些工具設定檔具有未來的前瞻性,請重新命名以下檔案 (在專案根資料夾中)

    舊名稱新名稱
    postcss.config.jspostcss.config.cjs
    .eslintrc.js.eslintrc.cjs
    babel.config.jsbabel.config.cjs

  • 您可能會想要將以下內容新增至您的 /.gitignore 檔案。當您的 /quasar.config 檔案發生問題時,會保留這些檔案以供檢查用途 (quasar clean 命令可以移除這些檔案)

    /.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

  • 如果您有使用 linting,請同時檢查您的 /.eslintignore 檔案

    /.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 檔案

/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 檔案中也有一些細微的變更

/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
    // ...
  })
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 以指定您的 preload script

/quasar.config 檔案

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')
+  }

最後,新檔案應如下所示

新的 /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

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 檔案變更

/quasar.config 檔案 > 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 模式變更

已捨棄對 /src-ssr/production-export.js 的支援 (請刪除它)。現在開發和生產環境都執行相同的 SSR 網路伺服器,因此請建立一個包含以下內容的 /src-ssr/server.js

/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” 部分應有的樣子

/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 檔案以移除對舊檔案的參考

/quasar.config 檔案

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

/src-ssr/middlewares/render.js 檔案內容範例

/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 檔案進行小幅變更,如下所示

對於 TS 開發人員

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

/quasar.config 檔案還有一些額外的變更

/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 檔案的一些變更

/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 的文件,因為它們現在將大致相符。

按一下下方的區塊以展開並查看舊的和新的資料夾結構

*舊的*資料夾結構
content-css.css
# CSS 檔案,透過 manifest.json 自動注入到使用中的網頁
icon-16x16.png
# 16px x 16px 的圖示檔案
icon-48x48.png
# 48px x 48px 的圖示檔案
icon-128x128.png
# 128px x 128px 的圖示檔案
background.js
# 標準背景腳本 BEX 檔案 - 透過 manifest.json 自動注入
background-hooks.js
# 具有 BEX 通訊層掛鉤的背景腳本
content-hooks.js
# 具有 BEX 通訊層掛鉤的內容腳本
content-script.js
# 標準內容腳本 BEX 檔案 - 透過 manifest.json 自動注入
dom-hooks.js
# 注入到 DOM 中的 JS 檔案,具有 BEX 通訊層的掛鉤
www/
# 已編譯的 BEX 來源 - 從 /src 編譯 (Quasar 應用程式)
manifest.json
# 生產環境的主執行緒程式碼
*新的*資料夾結構
content.css
# CSS 檔案,透過 manifest.json 自動注入到使用中的網頁
background.js
# 標準背景腳本 BEX 檔案 (透過 manifest.json 自動注入)
dom.js
# 注入到 DOM 中的 JS 檔案,具有 BEX 通訊層的掛鉤
icon-128x128.png
# 128px x 128px 的圖示檔案
icon-16x16.png
# 16px x 16px 的圖示檔案
icon-48x48.png
# 48px x 48px 的圖示檔案
_locales/
# 您可能在 manifest 中定義的可選 BEX 地區設定檔
manifest.json
# 瀏覽器擴充功能 manifest 檔案
my-content-script.js
# 標準內容腳本 BEX 檔案 - 透過 manifest.json 自動注入 (您可以有多個腳本)

其他 /quasar.config 檔案變更

來自 /quasar.config 檔案的 ctx 有一些額外的屬性 (vueDevtoolsappPaths)

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 檔案是否存在,因此請移除以下內容

/quasar.config

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

/quasar.config 檔案 > sourceFiles 的定義有一些變更

/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 屬性

/quasar.config > eslint (新的!)

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.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

/quasar.config > devServer

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 檔案。

您也可以設定從不同的資料夾中選取上述檔案,甚至將更多檔案新增至清單

/quasar.config 檔案

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