使用 Webpack5 創(chuàng)建 Vue3 + TS 項目
點擊上方 前端瓶子君,關(guān)注公眾號
回復(fù)算法,加入前端編程面試算法每日一題群

作者:suporka
https://segmentfault.com/a/1190000039934387
1. 前言
筆者兩年前曾寫過一篇文章《Webpack4 搭建 Vue 項目》,后來隨著 webpack5 和 vue3 的面世,一直想升級下我這個 createVue 項目,但是苦于沒有時間(其實是因為懶),一直拖延至今。搗鼓了好幾天,終于搭建好整個項目,因此僅以此文記錄升級搭建的過程。
PS: 其實也可以用官方腳手架搭建的,為何要自己從頭做起呢?有腳手架我不用,我就折騰。哎,就是玩兒~??
2. 準備工作
為何升級?除了折騰外,便是享受新版本帶給我們的新特性體驗。
Webpack5 的新特性
-
持久化緩存 -
moduleIds & chunkIds 的優(yōu)化 -
更智能的 tree shaking -
Module Federation -
...
Vue3 的新特性
-
更小 -
更快 -
加強 TypeScript 支持 -
加強 API 設(shè)計一致性 -
提高自身可維護性 -
開放更多底層功能
確定項目技術(shù)棧
-
編程語言:TypeScript 4.2.4 -
構(gòu)建工具:[Webpack 5.33.2]() -
前端框架:Vue 3.0.11 -
路由工具:Vue Router 4.0.6 -
狀態(tài)管理:Vuex 4.0.0 -
CSS 預(yù)編譯:Sass / Less -
HTTP 工具:Axios -
Git Hook 工具:Husky + Lint-staged -
代碼規(guī)范:EditorConfig + Prettier + ESLint -
提交規(guī)范:Commitlint -
構(gòu)建部署:Travis
3. 項目搭建
此文并不是從零搭建,而是在 [email protected] 的基礎(chǔ)上修改搭建,如若看不懂,可以先看《Webpack4 搭建 Vue 項目》,跟著一步步搭建,后再看此文升級
創(chuàng)建 createVue 文件夾,進入該文件夾, npm init 初始化項目
老規(guī)矩,安裝 webpack 四件套npm i webpack webpack-cli webpack-dev-server webpack-merge --save-dev
當(dāng)前使用版本:
"webpack": "^5.33.2",
"webpack-bundle-analyzer": "^4.4.1",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
webpack5 啟動開發(fā)服務(wù)器命令與之前有所變化,從 webpack-dev-server 轉(zhuǎn)變?yōu)?nbsp;webpack serve, 因此 package.json 中 script 的 start 修改為: "start": "webpack serve --progress --hot --inline --config build/webpack.dev.js"
-
創(chuàng)建相應(yīng)文件
與之前沒有太大差異。增加變動的有一下幾點:
1). 持久化緩存,增加 cache 配置. v5 中緩存默認是 memory,修改設(shè)置"filesystem"寫入硬盤
// webpack.dev.js
module.exports = merge(common, {
cache: {
type: 'filesystem',
}
//...
}
2). 去除插件 clean-webpack-plugin(v5支持),webpack.HashedModuleIdsPlugin(v5更好的 moduleIds & chunkIds),HardSourceWebpackPlugin(v5支持),happypack(v5不兼容)
安裝 vue 核心解析插件
解析插件有所不同,從 vue-template-compiler 變成了 @vue/compiler-sfc, vue-loader 保持不變。 npm i vue-loader @vue/compiler-sfc --save-dev
// 當(dāng)前我使用版本
"vue-loader": "^16.2.0",
"@vue/compiler-sfc": "^3.0.11",
安裝 vue3 及相關(guān)庫,添加 vue 類型文件
npm i vue@next [email protected] vue-router --save
src 文件夾下添加 shims-vue.d.ts 文件,解決 vue 類型報錯
// shims-vue.d.ts
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
安裝 html 模板解析插件
npm i html-webpack-plugin --save-dev
安裝 typescript 及解析插件
npm i typescript ts-loader --save-dev
配置ts-loader解析:
// webpack.base.js
// rules
{
test: /\.(t|j)s$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
// 指定特定的ts編譯配置,為了區(qū)分腳本的ts配置
configFile: path.resolve(__dirname, '../tsconfig.loader.json'),
// 對應(yīng)文件添加個.ts或.tsx后綴
appendTsSuffixTo: [/\.vue$/],
},
},
],
}
ts-loader 為單進程執(zhí)行類型檢查和轉(zhuǎn)譯,因此效率有些慢,可以用多進程方案:即關(guān)閉ts-loader的類型檢查,類型檢查由 fork-ts-checker-webpack-plugin 插件執(zhí)行。npm i fork-ts-checker-webpack-plugin --save-dev
// webpack.base.js
// rules
{
test: /\.(t|j)s$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
// 指定特定的ts編譯配置,為了區(qū)分腳本的ts配置
configFile: path.resolve(__dirname, '../tsconfig.loader.json'),
// 對應(yīng)文件添加個.ts或.tsx后綴
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true, // ? 關(guān)閉類型檢查,即只進行轉(zhuǎn)譯
},
},
],
}
// plugins push
new ForkTsCheckerWebpackPlugin()
至此項目基本可以跑起來了,那么有個問題了:Ts 可以編譯為指定版本的 js,那么還需要 babel 么?
tsc 的 target 只轉(zhuǎn)譯語法,不集成 polyfill,所以還是得要 babel。
比如把箭頭函數(shù)轉(zhuǎn)成普通 function、aysnc + await 變成 Promise.then,這是語法轉(zhuǎn)譯;
但你運行環(huán)境里如果沒有 Promise.prototype.finally,那沒有就還是沒有。
因此我們項目里還是需要 babel.
Webpack 轉(zhuǎn)譯 Typescript 現(xiàn)有方案:

綜合考慮性能和擴展性,目前比較推薦的是 babel+fork-ts-checker-webpack-plugin 方案。
在 babel7 之前,是需要同時使用 ts-loader 和 babel-loader 的,其編譯過程 TS > TS 編譯器 > JS > Babel > JS 。可見編譯了兩次js,效率有些低下。但是 babel7 出來之后有了解析 typescript 的能力,有了這一層面的支持,我們就可以只使用 babel,而不用再加一輪 ts 的編譯流程了。
在 babel 7 中,我們使用新的 @babel/preset-typescript 預(yù)設(shè),結(jié)合一些插件便可以解析大部分的 ts 語法。
那么,Babel 是如何處理 TypeScript 代碼的呢?
Babel 刪除了所有 TypeScript,將其轉(zhuǎn)換為常規(guī)的 JavaScript,并繼續(xù)以它自己的方式處理。刪除了 typescript 則不需要進行類型檢查,不會有煩人的類型錯誤提醒,因此編譯速度提升,開開心心編程??
當(dāng)然,類型安全性檢查必不可少,我們可以統(tǒng)一在某個時間集中處理,增加 script:
"check-types": "tsc --watch",
添加 babel 解析 typescript
# 安裝以下依賴 --save-dev
# webpack loader
babel-loader
# babel 核心
@babel/core
# 智能轉(zhuǎn)換成目標(biāo)運行環(huán)境代碼
@babel/preset-env
# 解析 typescript 的 babel 預(yù)設(shè)
@babel/preset-typescript
# polyfill
@babel/plugin-transform-runtime
# 支持 ts 類的寫法
@babel/plugin-proposal-class-properties
# 支持三點展開符
@babel/plugin-proposal-object-rest-spread
# 安裝以下依賴 --save
@babel/runtime
@babel/runtime-corejs3
"core-js": "^3.11.0",
刪除 ts-loader, 添加 babel-loader
{
test: /\.(t|j)s$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
},
],
}
項目根目錄添加 babel 配置文件 babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // 按需引入 polyfill
corejs: 3,
},
],
[
'@babel/preset-typescript', // 引用Typescript插件
{
allExtensions: true, // 支持所有文件擴展名,否則在vue文件中使用ts會報錯
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
},
],
'@babel/proposal-class-properties',
'@babel/proposal-object-rest-spread',
],
}
4. 代碼規(guī)范
項目中代碼規(guī)范集成了 EditorConfig, Prettier, ESLint, Husky, Lint-staged,以及如何解決 Prettier 和 ESLint 的沖突的問題,具體實現(xiàn)可以參考 《從 0 開始手把手帶你搭建一套規(guī)范的 Vue3.x 項目工程環(huán)境》這篇文章,講的很詳細這里不再贅述。
5. 提交規(guī)范
利用 inquirer 選擇配置好的提交類型,以及配合 commitlint 實現(xiàn) commit 檢查
npm i inquirer shelljs @commitlint/{cli,config-conventional} -D
添加 package.json 的 script :
"commitlint": "commitlint -e",
"commit": "node commit/git-commit.js"
創(chuàng)建 commit/git-commit.js 文件
const shell = require('shelljs')
const inquirer = require('inquirer')
const prompsConfig = {
ciType: [
{
type: 'list',
name: 'type',
message: '請選擇本次提交的類型:',
choices: [
{
name: '引入新特性',
value: 'feat',
},
{
name: '改進代碼的結(jié)構(gòu)格式/樣式',
value: 'style',
},
{
name: '修復(fù) bug',
value: 'fix',
},
{
name: '提升性能',
value: 'perf',
},
{
name: '刪除代碼或文件',
value: 'delete',
},
{
name: '其他修改, 比如改變構(gòu)建流程、或者增加依賴庫、工具等',
value: 'chore',
},
{
name: '重構(gòu)',
value: 'refactor',
},
{
name: '撰寫文檔',
value: 'docs',
},
{
name: '增加測試',
value: 'test',
},
{
name: '更新打包文件',
value: 'build',
},
{
name: '初次提交',
value: 'init',
},
{
name: '發(fā)布/版本標(biāo)簽',
value: 'release',
},
{
name: '部署功能',
value: 'deploy',
},
{
name: '代碼回滾',
value: 'revert',
},
{
name: 'CI持續(xù)集成修改',
value: 'ci',
},
],
},
],
ciMsg: {
type: 'input',
name: 'msg',
message: '請輸入提交文本:',
validate: function (value) {
if (value) {
return true
}
return '文本必須輸入!'
},
},
}
async function gitCommit() {
let { type } = await inquirer.prompt(prompsConfig.ciType)
let { msg } = await inquirer.prompt(prompsConfig.ciMsg)
shell.exec(`git commit -m "${type}: ${msg}"`, function () {
console.log(`\n提交腳本: git commit -m "${type}: ${msg}"`)
})
}
gitCommit()
配置 commitlint 類型,創(chuàng)建 commitlint.config.js 文件:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'build', 'chore', 'ci', 'feat', 'docs', 'fix', 'perf', 'revert', 'refactor', 'style', 'test', 'init', 'build', 'release', 'delete'
]],
}
};
完成上述操作后,git add 相關(guān)文件,執(zhí)行 npm run commit 即可執(zhí)行 commit 校驗
6. 構(gòu)建部署 Travis CI
Travis CI 是一款構(gòu)建和測試的自動化工具,不僅可以提高效率,還能使開發(fā)流程更可靠和專業(yè)化,從而提高軟件的價值。而且,它對于開源項目是免費的,不花一分錢,就能幫你做掉很多事情。詳細介紹可以查看 阮一峰——《持續(xù)集成服務(wù) Travis CI 教程》
首先,訪問官方網(wǎng)站 travis-ci.org,點擊右上角的個人頭像,使用 Github 賬戶登入 Travis CI。
找到對應(yīng)的倉庫,打開開關(guān)添加倉庫

在 github 上 setting/Developer settings/Personal access token 處生成 travis token
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69ea45bccd674eaeb7023ea850aa2be1~tplv-k3u1fbpfcp-watermark.image" width="700"/>
點擊對應(yīng)倉庫的 setting

設(shè)置環(huán)境變量 GITHUB_TOKEN 為剛才 github 處生成的 token
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a1594556dfc3449698070dfb99c18f59~tplv-k3u1fbpfcp-watermark.image" width="700"/>
創(chuàng)建 .travis.yml 配置文件:
language: node_js
node_js:
- 12
branchs:
only:
- master
cache:
directories:
- node_modules
install:
- yarn install
scripts:
- yarn build
deploy:
provider: pages
local_dir: dist
skip_cleanup: true
# 在 GitHub 上生成的令牌,允許 Travis 推送代碼到你的倉庫。
# 在倉庫對應(yīng)的 Travis 設(shè)置頁面中配置,用于安全控制。
github_token: $GITHUB_TOKEN
keep_history: true
on:
branch: master
這樣,當(dāng)你 push 到 master 或者 pr 合并到 master 的時候,就會觸發(fā)部署腳本的執(zhí)行,將生成的 dist 推送至 gh-pages 分支
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1710d33f1a1c4da18ad28ef2c4805f34~tplv-k3u1fbpfcp-watermark.image" width="700"/>
7. 存在問題及總結(jié)
-
構(gòu)建時間比 webpack4 長,可能是由于 ts 的引入,以及 happypack 多進程構(gòu)建的移除造成時間略長 -
dev server 不會自增 port -
fork-ts-checker-webpack-plugin 無法檢測 vue 中的 ts 類型錯誤
搗鼓了挺長一段時間,也了解了蠻多工程化的東西,雖然不一定能用于實際項目中,但還是算有所收獲吧!
