你在一家做 B 端 SaaS 的公司,产品迭代节奏极快。某天,老板突然甩来一句:"客户要私有化部署,包体必须 < 500 KB,脚手架里那些没用到的依赖全给我砍掉!"
那一刻,你深刻体会到:脚手架是"通用解",而私有化场景需要"定制解"。于是,你决定从零手搓一个 Vue 应用,既能极致瘦身,又能随时插拔功能。
解决方案:30 分钟搭出可交付的"裸奔" Vue3 项目
1. 环境准备(2 分钟)
bash
# 🔍 用 volta 锁 Node 版本,避免"在我电脑能跑"
volta install node@20
mkdir vue-lite && cd vue-lite
npm init -y
2. 最小依赖安装(3 分钟)
bash
# 只装运行时 + 编译时刚需
npm i vue@next
npm i -D vite @vitejs/plugin-vue
为什么选 Vite?
对比 Webpack5(webpage 3 的做法),Vite 在 dev 阶段用 esbuild 做预构建,冷启动 < 300 ms,正适合我们"边改边看"的私有化调试场景。
3. 目录结构(5 分钟)
csharp
vue-lite
├─ public
│ └─ favicon.ico
├─ src
│ ├─ main.ts # 应用入口
│ ├─ App.vue
│ └─ components
│ └─ Hello.vue
├─ index.html # Vite 的"钩子"
└─ vite.config.ts
4. 核心文件代码(10 分钟)
index.html
html
<!doctype html>
<html>
<head>
<title>Vue-Lite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
vite.config.ts
ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/main.ts',
name: 'VueLite',
fileName: 'vue-lite',
},
rollupOptions: {
external: ['vue'], // 🔍 把 vue 打成 external,私有化时再外链 CDN
},
},
})
src/main.ts
ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
src/App.vue
vue
<template>
<Hello />
</template>
<script setup lang="ts">
import Hello from './components/Hello.vue'
</script>
src/components/Hello.vue
vue
<template>
<h1>{{ msg }}</h1>
</template>
<script setup lang="ts">
const msg = 'Hello, 私有化客户!'
</script>
原理剖析:从"裸文件"到"可交付产物"的三层视角
层级 | 表面用法 | 底层机制 | 设计哲学 |
---|---|---|---|
Dev 阶段 | npm run dev 秒开浏览器 |
Vite 把 .vue 文件实时编译成 ESModule,浏览器直接 import |
浏览器原生能力优先,工具链只做"翻译" |
Build 阶段 | npm run build 生成 dist/vue-lite.umd.js |
Rollup 做 tree-shaking + 代码分割,external vue 减少 80 KB | 私有化场景下,业务代码与框架解耦 |
Runtime 阶段 | 客户页面 <script src="vue-lite.umd.js"></script> |
UMD 格式自动判断宿主环境(CommonJS / AMD / 全局) | 不侵入客户构建体系,即插即用 |
应用扩展:把"裸奔"项目武装到牙齿
1. 插拔式路由(不打包路由库)
ts
// src/router/index.ts
import { ref, computed } from 'vue'
const routes = {
'/': () => import('../pages/Home.vue'),
'/about': () => import('../pages/About.vue'),
}
export const path = ref(location.pathname)
window.addEventListener('popstate', () => (path.value = location.pathname))
export const currentView = computed(() => routes[path.value] || routes['/'])
原理:利用浏览器原生
popstate
+ Vue3 的响应式,实现 0 依赖路由。场景:后台管理系统只有 3-4 个页面,无需整包 vue-router。
2. 按需引入 UI 组件(以 ElementPlus 为例)
ts
// vite.config.ts 新增
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
plugins: [
vue(),
Components({ resolvers: [ElementPlusResolver()] }),
]
结果:按钮、表格用到多少就打包多少,未使用的组件 0 成本。
3. 私有化环境变量注入
ts
// src/env.ts
export const API_BASE = import.meta.env.VITE_API_BASE || 'https://saas.example.com'
客户部署时只需在 Nginx 加一行:
bash
location / {
sub_filter 'https://saas.example.com' 'https://customer.internal';
}
举一反三:3 个变体场景实现思路
场景 | 关键差异 | 实现思路 |
---|---|---|
微前端子应用 | 需要生命周期钩子 | 在 main.ts 导出 bootstrap / mount / unmount ,用 Vite 的 library 模式打包成 systemjs 格式 |
低代码平台渲染器 | 动态组件量巨大 | 用 defineAsyncComponent + import.meta.glob 做运行时加载,配合 CDN 缓存 |
Chrome 插件 popup | 包体 < 100 KB | 关闭 Vite 的代码分割,用 build.minify='terser' + pure_funcs=['console.log'] 删除所有日志 |
可复用配置片段
ts
// vite.config.ts(私有化专用)
export default defineConfig(({ command }) => ({
base: command === 'build' ? '/static/vue-lite/' : '/', // 🔍 适配客户子路径
plugins: [vue()],
build: {
rollupOptions: {
external: ['vue'],
output: {
globals: { vue: 'Vue' }, // 告诉 UMD 外部依赖叫 Vue
},
},
},
}))
小结
不用脚手架,不是"为了酷",而是"为了活"。当你面对包体、合规、私有化这些真实约束时,手搓项目就像给自己开了一条逃生通道:
- 想砍依赖?直接删
package.json
一行。 - 想换构建?Vite 换 Rollup 换 esbuild,5 分钟搞定。
下次老板再提"极限瘦身",你就可以淡定地打开这篇文章,30 分钟交差。