Vue npm JavaScript JS Vue-CLI 軟體工程

軟體工程:更新 Vue 2 的 Vue Cli

張証寓 Ted Chang 2023/06/05 11:10:24
1243

一、更新原因

現今,大部分的前端框架都有使用 CLI 工具,來幫助我們進行開發測試與打包的工作。在開發的過程中,常常會因為 CLI 的版本,所相依的套件版本,而限制我們能否使用其它的第三方套件

以我曾經進行某個專案為例,由於早期 Vue 2 所使用的 Vue CLI 4 使用的 PostCSS-loader 不支援 PostCSS 8,導致 Vue 2 無法完美的整合 Tailwind CSS 的 JIT,造成每次存檔後,Vue CLI 會重新編譯兩次,甚至無法支援新版的 Tailwind CSS 3。

以下是我將專案的 Vue CLI 從 4.5 更新到 5 的過程中,在更新前端框架的 CLI 時,需要注意的流程與細節,以及會踩到的坑,應用在未來的前端專案開發。

二、更新目標

  1. 讓現有的 Vue 2 專案可以支援 Tailwind CSS 3
  2. 解決先前每次存檔後,會編譯兩次的問題。

三、開始更新前: 確認要更新的套件

我們要先知道,是什麼造成目前的 Vue CLI 不支援新版的 Tailwind CSS 3。

原因的根源是,目前的 Vue CLI 4.5,使用的是 PostCSS-loader 3,相依 PostCSS 7
https://www.jianshu.com/p/be1f9f162889

接著,我們在專案底下,確認有哪些套件用到 PostCSS-loader 3

npm list postcss-loader

目前使用 postcss-loader 3 的套件有兩個:

  • @ckeditor/ckeditor5-dev-utils@25.4.5
  • @vue/cli-service@4.5.17

檢查有哪些套件使用 PostCSS 7

npm list postcss
  • @ckeditor/ckeditor5-dev-utils@25.4.5
  • @ckeditor/ckeditor5-dev-webpack-plugin@25.4.5
  • @vue/cli-service@4.5.17
  • autoprefixer@9.8.8
  • postcss-loader@3.0.0
  • tailwindcss@npm:@tailwindcss/postcss7-compat@^2.2.16

四、更新 Vue CLI

Vue CLI 本身有提供升級的指令

vue upgrade

五、更新其它相依套件

更新完 Vue CLI 後,由於 Vue CLI 本身內建 PostCSS & autoprefixer

所以可以移除上面這兩個套件,並且重新安裝最新版的 Tailwind。

npm uninstall tailwindcss postcss autoprefixer

npm install tailwindcss@latest

更新 @ckeditor/ckeditor5-dev-utils, @ckeditor/ckeditor5-dev-webpack-plugin

npm install @ckeditor/ckeditor5-dev-utils@latest @ckeditor/ckeditor5-dev-webpack-plugin@latest

如果有安裝 postcss-loader,則移除 postcss-loader (Vue CLI 本身已內建)

npm uninstall postcss-loader

全部結束後,檢查目前使用到 PostCSS 的套件版本

除了 @vue/component-compiler-utils@3.3.0,其它都更新上去了。

然而,@vue/component-compiler-utils@3.3.0 目前已經是最新版本了,我們直接忽略,試試看 npm run serve。

npm run serve

六、更新後的問題排除

(一) 重新安裝 npm 套件

執行完 dev server 沒多久,出現了這個錯誤訊息:

(node:16788) UnhandledPromiseRejectionWarning: TypeError: The 'compilation' argument must be an instance of Compilation
    at Function.getCompilationHooks (C:\project\ted.chang\Vue\cfh_d-talk_frontend_tailwind3\node_modules\webpack\lib\NormalModule.js:227:10)
    at getCompilationHooks (C:\project\ted.chang\Vue\cfh_d-talk_frontend_tailwind3\node_modules\@ckeditor\ckeditor5-dev-webpack-plugin\lib\servetranslations.js:207:22)

這時候 google 一下,看到 Vue CLI 文件的升級指南有提到:

UnhandledPromiseRejectionWarning: TypeError: The 'compilation' argument must be an instance of Compilation 

after upgrading, please remove the lockfile (yarn.lock or package-lock.json) and node_modules in the project and reinstall all the dependencies.

這時候,要先刪掉 package-lock.json 與 node_modules
刪掉後,重新在終端機下 npm install,安裝所有的套件。

npm install

完成後,重新執行 dev server

npm run serve

(二) 重新設定 CKEditor loader

跑起來之後,出現了一段錯誤訊息:

Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
ValidationError: Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'plugins'. These properties are valid:
   object { postcssOptions?, execute?, sourceMap?, implementation? }
    at validate (C:\project\ted.chang\Vue\cfh_d-talk_frontend_tailwind3\node_modules\schema-utils\dist\validate.js:105:11)  
    at Object.loader (C:\project\ted.chang\Vue\cfh_d-talk_frontend_tailwind3\node_modules\postcss-loader\dist\index.js:43:29)
    @ ./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css 4:14-310 15:3-20:5 16:22-318
 @ ./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js 34:0-60
 @ ./node_modules/@ckeditor/ckeditor5-ui/src/index.js 37:0-81 37:0-81
 @ ./node_modules/ckeditor5/src/ui.js 10:0-39 10:0-39
 @ ./node_modules/@ckeditor/ckeditor5-editor-balloon/src/ballooneditor.js 19:0-50 83:17-31
 @ ./src/plugins/cKEditor.js 20:0-85 21:0-83 96:2-19 110:2-17
 @ ./src/main.js 12:0-28

看起來應該是 CKEditor 的 loader 設定有誤。
對照 CKEditor 官網的文件,在 vue.config.js 裡面,重新貼上 loader 的設定:

config.module
  .rule('cke-css')
  .test(/ckeditor5-[^/\\]+[/\\].+\.css$/)
  .use('postcss-loader')
  .loader('postcss-loader')
  .tap(() => {
    return {
      postcssOptions: styles.getPostCssConfig({
        themeImporter: {
          themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
        },
        minify: true
      })
    }
  })

完成後,重新執行 dev server

npm run serve

(三) Webpack 5 預設不再自動載入 polyfill (連結)

繼續處理其它錯誤訊息:

Can't resolve 'net' in 'C:\project\ted.chang\Vue\cfh_d-talk_frontend_tailwind3\node_modules\stompjs\lib'

參考這則 stackoverflow,會得知新的 Webpack 5 以前會自動載入 polyfill,而專案當中的 stompjs 套件有使用到。

我們現在如果有在瀏覽器用到,必須自行設定。

  1. 安裝套件
npm install assert-browserify url util browserify-fs tls-browserify net-browserify stream-http https-browserify path-browserify crypto-browserify stream-browserify browserify-zlib os-browserify
  1. 在 vue.config.js 的 configureWebpack 加入下面的設定:
{
    externals: [{ 'express': { commonjs: 'express' } }],
    resolve: {
      fallback: {
          "child_process": 'empty',
          "assert": require.resolve("assert-browserify"),
          "url": require.resolve("url"), 
          "fs": require.resolve("browserify-fs"), 
          "util": require.resolve("util"), 
          "http": require.resolve("stream-http"),
          "https": require.resolve("https-browserify"),
          "tls": require.resolve("tls-browserify"),
          "net": require.resolve("net-browserify"),
          "crypto": require.resolve("crypto-browserify"), 
          "path": require.resolve("path-browserify"),
          "os": require.resolve("os-browserify"), 
          "stream": require.resolve("stream-browserify"),
          "zlib": require.resolve("browserify-zlib")
      }
    }
}

完成後,重新執行 dev server,可以正常使用

npm run serve

但是,當我們打包放到伺服器上,會出現 Cannot read property of undefined (reading 'prototype'),後來發現 stompjs 使用到後端的 express 上的變數,加上此套件原作者已經不再維護,因此更換為新團隊維護的 stompjs。

更新完後的套件,也不再需要用到 Webpack 的 Polyfill,因此可以移除所有的 polyfill。

npm uninstall assert-browserify url util browserify-fs tls-browserify net-browserify stream-http https-browserify path-browserify crypto-browserify stream-browserify browserify-zlib os-browserify

(四) 重構專案程式碼

剩下的錯誤訊息,主要分為兩類:

  1. vue/no-mutating-props: 在元件裡面直接修改到 prop,這是不允許的(可參考這篇),需要重構。

  2. vue/multi-word-component-names: 元件的命名不符合 Vue 預設的 Coding Style。由於不影響功能,先暫時到 .eslintrc.js 關掉,有需要再回來重構。

rules: {
   ...,
    'vue/multi-word-component-names': 'off'
}

(五) CKEditor 和 Tailwind 的 PostCSS-Loader 設定合併再一起

修改到目前後,專案可以正常跑起來了。
但是出現了一段警告:

Module Warning (from ./node_modules/postcss-loader/dist/cjs.js):
Warning

(24:3) Nested CSS was detected, but CSS nesting has not been configured correctly.
Please enable a CSS nesting plugin *before* Tailwind in your configuration.
See how here: https://tailwindcss.com/docs/using-with-preprocessors#nestin

原因是,CKEditor 的 CSS 預設使用的 postcss-loader,和 vue.config.js 的 postcss-loader 不一樣。導致 CKEditor 的 CSS 重複被兩個 postcss-loader 處理到。

因此,我們要統一使用 Vue-cli 的 Postcss-loader,將原本 vue.config.js 設定 CKEditor 的 postcss-loader 的部分,合併到 postcss.config.js。

const { styles } = require('@ckeditor/ckeditor5-dev-utils')

module.exports = (api) => {
  if (/ckeditor5-[^/\\]+[/\\].+\.css$/.test(api.file)) {
    return styles.getPostCssConfig({
      themeImporter: {
        themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
      },
      minify: true
    })
  }

  return {
    plugins: [
      'postcss-import',
      'tailwindcss/nesting',
      'tailwindcss',
      'autoprefixer'
    ]
  }
}

原先設定在 vue.config.js 的程式碼就可以直接刪掉了。

(六) 修改 Tailwind 的設定檔

由於我們已經更新到 Tailwind CSS 3,所以我們每次執行 dev server,會出現下面的錯誤訊息

warn - The `purge`/`content` options have changed in Tailwind CSS v3.0.
warn - Update your configuration file to eliminate this warning.
warn - https://tailwindcss.com/docs/upgrade-guide#configure-content-sources

warn - The glob pattern ./public/**/*.{html} in your Tailwind CSS configuration is invalid.
warn - Update it to ./public/**/*.html to silence this warning.

warn - The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.
warn - Change `darkMode` to `media` or remove it entirely.

只要根據錯誤訊息,改成 V3 的設定檔格式及可

module.exports = {
  content: ['./public/**/*.html', './src/**/*.{vue,js,ts,html}'],
  darkMode: 'media', // or 'media' or 'class'
  theme: {
    extend: {
      colors: {
        black: {
          DEFAULT: '#303032'
        },
        gray: {
          black: '#252525',
          dark: '#656565',
          DEFAULT: '#8B8B8C',
          light: '#C5C4CA',
          lighter: '#DFDEE0',
          concrete: '#F2F2F2',
          'white-smoke': '#F6F6F6'
        },
        primary: {
          blue: '#56ADFF',
          'dark-blue': '#4567F3',
          'darker-blue': '#2B4AC6'
        },
        secondary: {
          light: '#B8DAFA',
          lighter: '#E1F0FF',
          lightest: '#EDF6FF'
        },
        alert: {
          DEFAULT: '#D9415A'
        }
      },
      borderRadius: {
        DEFAULT: '8px'
      },
      boxShadow: {
        blur: '0 0 10px 0 rgba(0, 0, 0, 0.25)',
        DEFAULT: '1px 1px 4px 1px rgb(0 0 0 / 25%)',
        inner: 'inset 1px 1px 4px 1px rgb(0 0 0 / 25%)'
      },
      zIndex: {
        '-1': '-1',
        '-10': '-10',
        999: 999
      }
    },
    fontSize: {
      // name: ['font-size', 'line-height']
      xs: ['12px', '17px'],
      sm: ['14px', '20px'],
      base: ['16px', '22px'],
      'lg-content': ['18px', '28px'],
      'lg-title': ['18px', '25px'],
      xl: ['22px', '30px'],
      '2xl': ['24px', '33px'],
      '3xl': ['36px', '50px']
    }
  },
  important: 'body'
}

(七) 修改 package.json 的指令

新版的 Vue CLI 已經完全支援 Tailwind 3 的 JIT,所以不需要使用 Tailwind CLI 的 Watch 模式,因此我們可以拿掉相關的指令。

修改後的結果如下:

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build --mode production",
    "lint": "vue-cli-service lint",
    "build:ci": "vue-cli-service build --mode uat --dest=./dist_uat & vue-cli-service build --mode prod --dest=./dist_prod",
    "build:dev": "vue-cli-service build --mode development",
    "build:prod": "vue-cli-service build --mode prod",
    "build:uat": "vue-cli-service build --mode uat",
    "compiler": "npx tailwindcss -i ./src/assets/styles/tailwind.css -o ./src/assets/styles/style.css",
    "dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "mocks": "mocks-server"
  }

七、已知 Issue

(一) 新版 autoPrefixer 出現的警告 (連結)。

Module Warning (from ./node_modules/@vue/cli-service/node_modules/postcss-loader/dist/cjs.js):
Warning

(3053:3) autoprefixer: Replace color-adjust to print-color-adjust. The color-adjust shorthand is currently deprecated.

原因:在 App.vue 引入的 bootstrap 4.6.1 有用到 color-adjust 的 CSS 屬性,經過新版的 autoPrefixer 處理後造成的

解法: 可不處理,或者在 App.vue 的 <style></style>上方加上 /* autoprefixer: off */

(二) Vue CLI 無法用新寫法啟用 Https (連結)

Webpack 5 Dev Server 有一個新的啟用 Https 的寫法:

devServer: {
    host: 'localhost',
    server: 'https',
    port: 8080
},
  

如果用先前的 https:true,執行 npm run serve 會出現以下錯誤

(node:34152) [DEP_WEBPACK_DEV_SERVER_HTTPS] DeprecationWarning: 'https' option is deprecated. Please use the 'server' option.
(Use `node --trace-deprecation ...` to show where the warning was created)

然而,如果改成 server: ‘https’,執行 npm run serve,出來的網址會是 http。

解法: 維持 server: ‘https’ 的寫法,稍早已經有人發 PR 修正,等待發布新版本後,再修改為 server: ‘https’ 的寫法。

全部都修改完成之後,我們看到已經沒有任何錯誤訊息跟警告了

八、結語:檢查版本→進行更新→除錯調整

版本更新在軟體工程當中,一直是一項難度極高的工作。
從開始更新前,到更新完後的檢查,需要注意的重點包括:

  1. 檢查需要更新的套件資訊(版本、相依性)。
    可以使用 npm list <套件名稱> ,以及 package.json、package-lock.json 得知
  2. 更新後的開源套件,是否有相關的 Breaking Change、設定檔修改,影響到目前的功能運作。
    可以從錯誤訊息、官方文件、Github 專案的 CHANGELOG 得知
  3. 更新後的開源套件,是否目前有相關的 Issue 等待處理。
    可以從官方文件、Stackoverflow、Github 專案的 Issue 得知
  4. 調整完執行後,先前的程式功能是否都一如往常。

由於版本更新的工程的流程相對繁瑣,所以要不要進行更新,通常都會考慮這些原因:

  1. 安全性的必要。
    是否出現安全性漏洞,這次 D-Talk 專案並非此原因。
  2. 套件穩定版本所存在的問題 & 支援性
    套件新版本剛出來的時間,通常會有一些問題需要時間之後才會被發現,又或者此版本所使用的相依套件,不支援新版本,而這些問題如果沒被解決,是否會影響到開發工作。
  3. 新功能/效能改善,對於目前開發的效益與必要性
    經過更新之後,降低了 50% 的開發編譯時間、記憶體溢位的問題、需依賴舊版的 node.js & npm 等底層的問題。
張証寓 Ted Chang