最近 vite2 非常火,它基於瀏覽器原生ES模組載入的現代化構建工具,主要由兩部分組成:
- 一個開發伺服器,它利用 原生 ES 模組 提供了 豐富的內建功能,如速度快到驚人的 模組熱更新(HMR)。
- 一套構建指令,它使用 Rollup 打包你的程式碼,預配置輸出高度優化的靜態資源用於生產。
為什麼選用vite?
隨著我們開始構建越來越多的雄心勃勃的應用程式,我們處理的 JavaScript 數量也呈指數級增長。大型專案包含數千個模組的情況並不少見。我們開始遇到基於 JavaScript 的工具的效能瓶頸:通常需要很長時間(有時甚至是幾分鐘!)才能啟動開發伺服器,即使使用 HMR,檔案編輯也需要幾秒鐘才能在瀏覽器中反映出來。緩慢的反饋會極大地影響開發人員的生產力和幸福感。
Vite 旨在利用生態系統中的新進展解決上述問題:瀏覽器支援原生模組,越來越多 JavaScript 工具使用編譯型語言編寫。
公司內部OA系統,基於vue2+webpack2開發,目前頁面有上百個,每次啟動開發或者編譯都需要至少5分鐘,非常慢。由於 vite 官方沒有原生支援vue2.0,需要依賴於第三方外掛,而且對於編譯的穩定性和風險沒辦法保證,因此本次引入vite優先保證本地開發伺服器的執行,儘量避免修改程式碼,編譯還是由原來的webpack來執行。
首先安裝 vite、vite-plugin-vue2
$ npm i vite vite-plugin-vue2 sass --save-dev
新建配置檔案 vite.config.js
import {defineConfig} from 'vite'
import {createVuePlugin} from 'vite-plugin-vue2'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
createVuePlugin({
jsx: true,
}),
],
resolve: {
extensions: ['.vue', '.js', '.json'],
alias: [
{find: "@", replacement: path.resolve(__dirname, './src')},
],
},
server: {
proxy: {}, // 原本專案的後端介面代理
base: '/index-vite.html', // 保留原本的 index.html,新建一個 index-vite.html
open: '/index-vite.html'
},
})
index-vite.html 並增加入口
<!DOCTYPE html>
<html>
<head>
<!-- 頁面字符集 -->
<meta charset="utf-8" />
<!-- 360標籤,頁面需預設用極速核-->
<meta name="renderer" content="webkit" />
<!-- 禁止移動端點選輸入框自動放大-->
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> -->
<!-- 使用Chrome核心來做渲染-->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 網頁標題-->
<title>document</title>
</head>
<body>
<div id="app"></div>
<!-- 入口 -->
<script type="module" src="./src/index.js"></script>
</body>
</html>
為了儘量不對原始碼進行修改,保證能正常編譯專案,因此對程式碼的修改使用 vite-plugin transform
鉤子來實現,能夠在引入程式碼之前對程式碼進行修改。
參照專案中的舊程式碼,我們的外掛主要需要提供以下幾種對程式碼的轉換:
module.exports
->export default
。- 懶載入
require
轉成import('...')
。 - 程式碼中使用
require(...)
,需替換成臨時變數script_inject_var__0
,然後在指令碼作用域頂部引入變數:import script_inject_var__0 from '...'
。 - template中引入的require資源,例如
<img :src="require(...)"
-><img :src="tpl_inject_var__1"
、在 script 塊中import tpl_inject_var__1 from '...'
,然後在元件計算屬性/data屬性中引入computed: {tpl_inject_var__1}
。 require.context
轉換成import.meta.globEager
首先建立 ./vite/plugins/replace.js
import path from 'path'
import url from 'url'
const dirname = path.dirname(url.fileURLToPath(import.meta.url))
const srcDir = path.resolve(dirname, '../../src')
const enterDir = path.resolve(dirname, '../../enter')
function getPath(filepath) {
let sps = filepath.split('?', 2)
return sps[0]
}
function getExtension(filepath) {
return path.extname(getPath(filepath))
}
function replaceCode(code, filename, ext) {
let n = 0
let script, template, style
if (ext === '.vue') {
try {
template = code.match(/<template(?:.*?)>(?<template>.+)<\/template>/is).groups.template
} catch (ignore) {
}
try {
script = code.match(/<script(?:.*?)>(?<script>.+)<\/script>/is).groups.script
} catch (ignore) {
}
try {
style = code.match(/<style(?:.*?)>(?<style>.+)<\/style>/is).groups.style
} catch (ignore) {
}
} else if (ext === '.js') {
script = code
} else {
return {n, code}
}
let tplInjectVar = []
if (template) {
template = template.replace(/require\((.+?)\)/igm, function (_, match) {
n++
tplInjectVar.push(match)
return `tpl_inject_var__${tplInjectVar.length - 1}`
})
}
if (style) {
style = style.replace(/\/deep\//ig, function (match) {
n++
return '::v-deep'
})
}
let scriptInjectVar = []
if (script) {
// 替換 resolve => require(['@ resolve => require(['@/guide/guidePage.vue'], resolve)
// () => import('../src/guide/guidePage.vue'),
script = script.replace(/resolve\s*?=>\s*?require\(\[['"`]@(\/.*?)(["'`])],\s*?resolve\s*?\)/img, function (matched, m1, m2) {
n++
return `() => import(${m2}../src${m1}${m2})`
})
// 替換 resolve => require([
script = script.replace(/resolve\s*?=>\s*?require\(\[(['"`]@\/.*?["'`])],\s*?resolve\s*?\)/img, function (matched, m1) {
n++
return `() => import(${m1})`
})
script = script.replace(/require\((.+?)\)(.default)?/img, function (_, match) {
n++
scriptInjectVar.push(match)
return `script_inject_var__${scriptInjectVar.length - 1}`
})
scriptInjectVar.forEach((vvar, i) => {
n++
script = `import script_inject_var__${i} from ${vvar}\n${script}`
})
// 替換 module.exports =
script = script.replace(/module\.exports\s*?=/, function () {
n++
return 'export default '
})
}
if (ext === '.js') {
code = script
return {n, code}
}
if (ext === '.vue') {
tplInjectVar.forEach((vvar, i) => {
n++
script = `import tpl_inject_var__${i} from ${vvar}\n${script}`
})
if (tplInjectVar.length) { // 新增計算屬性
let computedN = 0
script = script.replace(/(computed\s*?:\s*?){/is, function (match) {
n++
computedN++
let vvars = ''
tplInjectVar.forEach((vvar, i) => {
vvars += `tpl_inject_var__${i},`
})
return `${match}${vvars}`
})
if (!computedN) { // 沒有計算屬性,就加一個
script = script.replace(/\s*?export\s+?default\s+?{/is, function (match) {
n++
computedN++
let vvars = ''
tplInjectVar.forEach((vvar, i) => {
vvars += `tpl_inject_var__${i},`
})
return `${match}\ncomputed:{${vvars}},`
})
}
if (!computedN) {
console.warn('沒有注入 computed!!')
}
}
let ret = code
if (template) {
ret = ret.replace(/(<template(?:.*?)>)(.+)(<\/template>)/is, function (_, m1, m2, m3) {
return `${m1}${template}${m3}`
})
}
if (script) {
ret = ret.replace(/(<script(?:.*?)>)(.+)(<\/script>)/is, function (_, m1, m2, m3) {
return `${m1}${script}${m3}`
})
}
if (style) {
ret = ret.replace(/(<style(?:.*?)>)(.+)(<\/style>)/is, function (_, m1, m2, m3) {
return `${m1}${style}${m3}`
})
}
return {n, code: ret}
}
}
export default function replacement() {
return {
enforce: 'pre',
transform(code, rawId) {
// 由於就這一個地方,因此就直接用路徑判斷,直接返回原始碼,就不做正則匹配替換了
if (rawId.endsWith('/views/liveMonitor/icons/index.js')) {
return {
code: `
import Vue from 'vue'
import SvgIcon from '../components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
let all = import.meta.globEager('./svg/*.svg')
`,
map: null,
}
}
let filepath = path.resolve(getPath(rawId))
let ext = getExtension(getPath(rawId))
if ((filepath.startsWith(srcDir) || filepath.startsWith(enterDir)) && ['.vue', '.js'].includes(ext)) {
let ret = replaceCode(code, filepath, ext)
if (ret.n === 0) {
return null
}
return {
code: ret.code,
map: null,
}
}
return null
}
}
}
在 vite.config.js
中引入外掛
import replacement from './vite/plugins/replacement.js'
...
export default defineConfig({
plugins: [
replacement(),
createVuePlugin({
jsx: true,
}),
],
...
在 package.json scripts 中新增兩項
"vite-dev": "vite"
至此可以啟動 vite 了。
npm run vite-dev
以上就是為專案引入 vite2,並且對原本程式碼0修改的過程記錄。這裡僅做拋磚引玉,讀者可以自定義外掛來對程式碼進行替換,對其他不相容的語法打補丁。
本作品採用《CC 協議》,轉載必須註明作者和本文連結