大家好!
文字是為了提升開發效率及體驗實踐誕生的。
專案背景:
- 腳手架:vue-cli3,具體為
"@vue/cli-service": "^3.4.1"
- 庫:vue2,具體為:
"vue": "2.6.12"
- 備註:沒有 typescript , 非 ssr
痛點:隨著時間的推移、業務的不斷迭代,依賴、功能、程式碼越來越多,專案本地啟動比較慢、開發熱更新比較慢。
改進目標:保留原來的 webpack, 支援 vite。並且儘可能的減少改動,減少維護成本。
考慮:
- vite 生產環境用的是 rollup,rollup 更適合打包庫。
- vite 打包提效並沒有提供太多。
- 保留原來的 webpack 方式,儘可能的保證生產的穩定與安全。
實踐
主要是處理三方面:
- 配置檔案需根據
vue.config.js
增加vite.config.js
- 入口檔案
index.html
支援 vite 的方式 - 配置 vite 啟動命令
- 可能需要
- 路由懶載入,vite 需要特殊處理
- 解決 commonjs 與 esModule 的引入與混用
增加 vite.config.js
在專案根目錄建立一個 vite.config.js
安裝所需依賴
npm i vite vite-plugin-vue2 -D
根據 vue.config.js
在 vite.config.js
中增加對應配置
// 若改了該檔案邏輯,請對照著改一下 vue.config.js
import path from 'path'
import fs from 'fs'
import { defineConfig } from 'vite'
import config from './config'
import { createVuePlugin } from 'vite-plugin-vue2'
import { injectHtml } from 'vite-plugin-html'
const resolve = dir => path.join(__dirname, dir)
const alias = {
vue: 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
const publicPath = '/'
const mode = 'development'
// https://vitejs.dev/config/
export default defineConfig({
base: publicPath,
plugins: [
createVuePlugin(),
],
// 這些是注入專案中的變數,若有 process 開頭的,都需要在這兒注入,vite 預設不會注入 process 相關的變數及值
define: {
'process.env.NODE_ENV': JSON.stringify(mode),
'process.env.publicPath': JSON.stringify(publicPath),
},
resolve: {
// 配置別名
alias,
// 匯入時想要省略的副檔名列表,必須加入.vue,因為 vite 預設不支援省略 .vue 字尾
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
server: {
// 允許跨域
cors: true,
proxy: {
// 可直接拷貝 vue.config.js 中對應的 proxy
}
}
})
sass 相關
配置特殊說明:若專案中有用是 sass
前處理器, 且用到變數了。
需要安裝 sass@1.32.13,不能安裝比 1.32
更高的版本,跟高版本存在這個問題Sass報錯: Using / for division is deprecated
npm i sass@1.32.13 -D
export default defineConfig({
...
css: {
// 給 sass 傳遞選項
preprocessorOptions: {
scss: {
charset: false, // 最新版sass才支援
additionalData: `@import "src/sass/common/var.scss";`,
}
},
},
...
})
備註:如果生產環境用 vite 打包,採用 sass@1.32.13
將會遇到這個問題vite2打包出現警告,"@charset" must be the first,該如何消除呢?,但是 sass@1.32.13
不支援文件中的配置。所以這可以算是生產環境不用 vite 的一個原因。
還需全域性替換 /deep/
為 ::v-deep
入口檔案 index.html
支援 vite 的方式
由於 vite 的專案要求 index.html 檔案在根目錄,且有入口檔案配置。
所以將 index.html 從 public 目錄移到根目錄。並加入
<% if (typeof isVite === 'boolean' && isVite) { %>
<script type="module" src="/src/main.js"></script>
<% } %>
這樣配置是為了能讓 webpack、vite 方式都都支援,共用一個檔案
而為了在 index.html 檔案注入 isVite
變數,需要安裝
npm i vite-plugin-html -D
需要在 vite.config.js
中配置
...
import { injectHtml } from 'vite-plugin-html'
export default defineConfig({
...
plugins: [
...
injectHtml({
data: {
title: 'vite-plugin-html-example',
isVite: true
},
}),
],
define: {
'process.env.isVite': JSON.stringify(true)
},
...
})
配置 vite 啟動命令
最後在 package.json
中增加指令碼
"scripts": {
"vite-start": "vite"
}
路由懶載入,vite 需要特殊處理
vue 實現路由懶載入的方式是這樣的
const Home = import(/* webpackChunkName: "[home]" */ `@/page/home.vue`)
const routes = [
{
path: `/home`,
name: 'home',
component: Home
},
]
但是 vite 不支援,解決
const modules = import.meta.glob('@/page/*.vue')
const Home = modules['@/page/home.vue']
const modules = import.meta.glob('@/page/*.vue')
// vite 會生成程式碼
const modules = {
'@/page/home.vue': () => import('@/page/home.vue'),
'@/page/page1.vue': () => import('@/page/page1.vue'),
'@/page/page2.vue': () => import('@/page/page2.vue'),
...
}
參考:vite-Glob 匯入
所以可封裝一下:
function loadPage (view) {
if (process.env.isVite) {
const modules = import.meta.glob('../pages/*.vue')
return modules[`../pages/${view}.vue`]
}
return () => import(/* webpackChunkName: "[request]" */ `@/pages/${view}`)
}
// 使用:
const routes = [
{
path: `/home`,
name: 'home',
component: loadPage('home'),
},
{
path: `/404`,
name: '404',
component: loadPage('404'),
},
]
但是 webpack 不支援 import.meta,需要 loader 處理。解決:在本地建一個檔案 webpack-import-meta-loader.js。
// 來源:https://github.com/KmjKoishi/webpack-import-meta-loader-fixed
// 是對 @open-wc/webpack-import-meta-loader 這個的修復
// 主要是修復 當 this.rootContext 不存在的判斷處理。構建生產環境時不存在
/* eslint-disable */
// @ts-nocheck
const path = require('path');
function toBrowserPath(filePath, _path = path) {
return filePath.replace(new RegExp(_path.sep === '\\' ? '\\\\' : _path.sep, 'g'), '/');
};
const regex = /import\.meta/g;
/**
* Webpack loader to rewrite `import.meta` in modules with url data to the source code file location.
*
* @example
* return import.meta;
* // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` });
*
* return import.meta.url;
* // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` }).url;
*/
module.exports = function (source) {
const path = require('path');
const relativePath = this.context.substring(
this.context.indexOf(this.rootContext) + (this.rootContext && this.rootContext.length >= 0 ? (this.rootContext.length + 1) : 0),
this.resource.lastIndexOf(path.sep) + 1,
);
const browserPath = toBrowserPath(relativePath);
const fileName = this.resource.substring(this.resource.lastIndexOf(path.sep) + 1);
let found = false;
let rewrittenSource = source.replace(regex, () => {
found = true;
return `({ url: getAbsoluteUrl('${browserPath}/${fileName}') })`;
});
if (found) {
return `
function getAbsoluteUrl(relativeUrl) {
const publicPath = __webpack_public_path__;
let url = '';
if (!publicPath || publicPath.indexOf('://') < 0) {
url += window.location.protocol + '//' + window.location.host;
}
if (publicPath) {
url += publicPath;
} else {
url += '/';
}
return url + relativeUrl;
}
${rewrittenSource}`;
} else {
return source;
}
};
vue.config.js 修改配置:
const resolve = dir => require('path').join(__dirname, dir)
module.exports = {
...
configureWebpack: {
...
module: {
rules: {
...
{
test: /index.js$/,
use: [
resolve('webpack-import-meta-loader'),
'babel-loader'
],
include: [resolve('src/router')]
}
}
}
}
...
}
解決 commonjs 與 esModule 的引入與混用
混用
webpack 方式下,若你的 src 專案原始碼中存在混用 commonjs 與 esModule。
方案一:不改原始碼,在 vite.config.js 中加配置,把 commonjs 轉換為 esModule
安裝 npm i cjs2esmodule -D
在 vite.config.js 中加配置
export default defineConfig({
plugins: [cjs2esmVitePlugin()]
})
如果這個方案,能讓你的專案執行正常。否則,可能需要採用方案二。
方案二:把 src 程式碼中的 commonjs 語法自己手動改為 esModule
引入
如果你的專案在有一個 config.js, 被 vue.config.js 中使用。那麼你可能需要處理。
vue.config.js 必須是 commonjs 語法的檔案,才能被使用,否則會報錯。
vite.config.js 既可以 esModule 語法,也可以 commonjs 語法。預設是 esModule 語法。
若上面混用,你是採用方案二,而且 src 程式碼中也用了 config.js。那麼你只能把 config.js 改成 esModule 的方式。此時 vue.config.js 不支援了,採用的方案是根據 config.js 自動生成一個 config-cjs.js。
目的是減少後期維護成本。
// transformConfig2cjs.js
// 執行專案的時候,會根據config.js自動生成檔案:config-cjs.js, 以便讓 vue-config.js 能直接使用
const {transformAsync} = require('@babel/core');
const plugin = require('@babel/plugin-transform-modules-commonjs')
const fs = require('fs')
const resolve = dir => require('path').join(__dirname, dir)
async function transfrom () {
const inputPath = resolve('config.js')
const input = fs.readFileSync(inputPath, 'utf-8')
const {code} = await transformAsync(input, {
sourceType: 'module',
plugins: [ plugin ]
});
fs.writeFileSync(resolve('./config-cjs.js'), code);
}
transfrom()
然後在 vue.config.js 原來引入的 config.js 的地方改為 config-cjs.
最後在 package.json
中改變下指令碼,每次都重新生成一個最新的配置。
"scripts": {
"transformConfig2cjs": "node transformConfig2cjs.js",
"serve": "npm run transformConfig2cjs && vue-cli-service serve",
"build": "npm run transformConfig2cjs && vue-cli-service build",
}
總結
遇到很多坑,都是語法相關的,最終都 11 解決了!
也嘗試過一些其他方案,但是不能用,我的專案沒有生效:
支援了之後,開發是真的很高效!
希望這篇文章對有需要的有所幫助。