軟體工程:更新 Vue 2 的 Vue Cli
一、更新原因
現今,大部分的前端框架都有使用 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 時,需要注意的流程與細節,以及會踩到的坑,應用在未來的前端專案開發。
二、更新目標
- 讓現有的 Vue 2 專案可以支援 Tailwind CSS 3
- 解決先前每次存檔後,會編譯兩次的問題。
三、開始更新前: 確認要更新的套件
我們要先知道,是什麼造成目前的 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 套件有使用到。
我們現在如果有在瀏覽器用到,必須自行設定。
- 安裝套件
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
- 在 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
(四) 重構專案程式碼
剩下的錯誤訊息,主要分為兩類:
-
vue/no-mutating-props: 在元件裡面直接修改到 prop,這是不允許的(可參考這篇),需要重構。
-
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’ 的寫法。
全部都修改完成之後,我們看到已經沒有任何錯誤訊息跟警告了
八、結語:檢查版本→進行更新→除錯調整
版本更新在軟體工程當中,一直是一項難度極高的工作。
從開始更新前,到更新完後的檢查,需要注意的重點包括:
- 檢查需要更新的套件資訊(版本、相依性)。
可以使用 npm list <套件名稱> ,以及 package.json、package-lock.json 得知 - 更新後的開源套件,是否有相關的 Breaking Change、設定檔修改,影響到目前的功能運作。
可以從錯誤訊息、官方文件、Github 專案的 CHANGELOG 得知 - 更新後的開源套件,是否目前有相關的 Issue 等待處理。
可以從官方文件、Stackoverflow、Github 專案的 Issue 得知 - 調整完執行後,先前的程式功能是否都一如往常。
由於版本更新的工程的流程相對繁瑣,所以要不要進行更新,通常都會考慮這些原因:
- 安全性的必要。
是否出現安全性漏洞,這次 D-Talk 專案並非此原因。 - 套件穩定版本所存在的問題 & 支援性
套件新版本剛出來的時間,通常會有一些問題需要時間之後才會被發現,又或者此版本所使用的相依套件,不支援新版本,而這些問題如果沒被解決,是否會影響到開發工作。 - 新功能/效能改善,對於目前開發的效益與必要性。
經過更新之後,降低了 50% 的開發編譯時間、記憶體溢位的問題、需依賴舊版的 node.js & npm 等底層的問題。