安裝vite npm create vite@latest 你還可以通過附加的命令行選項直接指定項目名稱和你想要使用的模板。例如,要構建一個 Vite + Vue + ts 項目,運行: # npm 7+,需要添加額外的 --: npm create vite@latest my-vue-app - ...
安裝vite
npm create vite@latest
你還可以通過附加的命令行選項直接指定項目名稱和你想要使用的模板。例如,要構建一個 Vite + Vue + ts 項目,運行:
# npm 7+,需要添加額外的 --:
npm create vite@latest my-vue-app -- --template vue-ts
查看 create-vite 以獲取每個模板的更多細節:vanilla,vanilla-ts, vue, vue-ts,react,react-ts,react-swc,react-swc-ts,preact,preact-ts,lit,lit-ts,svelte,svelte-ts,solid,solid-ts,qwik,qwik-ts。
你可以使用 . 作為項目名稱,在當前目錄中創建項目腳手架。
vite官網:https://cn.vitejs.dev/guide/
環境配置
在src中添加vue-shim.d.ts文件及內容:
/* eslint-disable */
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
目的是告訴TS如何處理.vue文件。
我們使用 DefineComponent 類型來註解 .vue 文件的預設導出,這是 Vue 3 中用於定義組件的類型。這個類型接受組件的 props、context 和其他選項作為泛型參數,但在這個簡單的聲明中,我們使用了空對象 {} 和 any 作為占位符,因為它們在這裡主要是為了類型註解的完整性,並不會在運行時影響組件的行為。
SDK基礎框架代碼
sdk/libApp.vue:
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
const count = ref<number>(0);
const appid = ref<string>("");
const msg = ref<string>("");
onMounted(() => {
const instance = getCurrentInstance(); // 獲取組件實例,相當於 this
if (instance) {
const optionsObj = reactive({
...instance.appContext.config.globalProperties.$options,
});
appid.value = optionsObj.appid;
msg.value = optionsObj.msg;
// 成功回調
if (optionsObj.success) {
optionsObj.success({
appid: appid.value,
msg: msg.value,
});
}
}
});
const increment = () => {
count.value++;
};
</script>
<template>
<p>msg:{{ msg }}</p>
<p>appid:{{ appid }}</p>
<div class="card">
<button type="button" @click="increment">count is {{ count }}</button>
</div>
</template>
<style scoped>
.card {
color: red;
}
</style>
sdk/main.ts:
import { createApp } from "vue";
import libApp from './libApp.vue';
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type VueApp = ReturnType<typeof createApp>;
interface IMyLib {
el: string;
success?: (...args: any[]) => any;
fail?: (...args: any[]) => any;
[key: string]: any;
}
class MyLib {
app: VueApp;
el: string = '#app';
/**
* 構造函數
*
* @param appInstance VueApp 實例對象
* @param options IMyLib 介面對象,包含可選參數 el,預設值為 '#app'
*/
constructor(appInstance: VueApp,{el = '#app'}: IMyLib) {
this.app = appInstance;
this.el = el;
this.render();
}
/**
* 渲染組件
*
* @returns 無返回值
*/
render() {
this.app.mount(this.el);
}
/**
* 為Vue應用實例添加全局配置
*
* @param app Vue應用實例
* @param options 入參選項
*/
static globalConfig(app: VueApp,options: IMyLib) {
// 為app實例添加全局屬性
app.config.globalProperties.$options = options;
}
/**
* 配置MyLib實例
*
* @param options IMyLib類型的配置項
* @returns 返回Promise<MyLib>,表示MyLib實例的Promise對象
*/
static config(options: IMyLib) {
const opts: IMyLib = {
success: () => {},
fail: () => {},
...options
}
// 下麵可以校驗參數的合理性、參數的完整性等
if(!opts.appid) {
if (typeof opts.fail === 'function') {
opts.fail('appid is required');
return;
}
}
const app = createApp(libApp);
app.use({
install(app: VueApp, opts: IMyLib) {
MyLib.globalConfig(app, opts);
}
}, opts);
const viteTest = new MyLib(app,opts);
if (typeof opts.success === 'function') {
opts.success(viteTest);
}
}
}
export default MyLib;
插件安裝、構建配置、編譯
插件安裝
安裝vite-plugin-css-injected-by-js插件,其作用:打包時把CSS註入到JS中。
npm i vite-plugin-css-injected-by-js -D
安裝vite-plugin-dts插件,其作用:生成類型聲明文件。
當然,也有人在 issue 中提出希望 Vite 內部支持在庫模式打包時導出聲明文件,但 Vite 官方表示不希望因此增加維護的負擔和結構的複雜性。
npm i vite-plugin-dts -D
vite.config.ts
vite.config.ts配置如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
import dts from "vite-plugin-dts";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
cssInjectedByJsPlugin(),
dts({
// 指定 tsconfig 文件
tsconfigPath: 'tsconfig.build.json',
rollupTypes: true
})
],
build: {
lib: {
entry: 'sdk/main.ts',
formats: ['es'],
name: 'myLib',
fileName: 'my-lib',
},
rollupOptions: {
// 確保外部化處理那些你不想打包進庫的依賴
external: ['vue'],
output: {
// 在 UMD 構建模式下為這些外部化的依賴提供一個全局變數
globals: {
vue: 'Vue',
},
},
},
}
})
添加tsconfig.build.json代碼:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"declaration": true,
"declarationDir": "./dist/types",
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["sdk/**/*.ts","sdk/**/*.vue"],
}
vite-plugin-dts地址:https://www.npmjs.com/package/vite-plugin-dts
庫包編譯
執行:
npm run build
在dist下存在如下構建文件:
- my-lib.d.ts(聲明文件)
- my-lib.js(庫文件)
生成的my-lib.d.ts文件內容如下:
import { createApp } from 'vue';
declare interface IMyLib {
el: string;
success?: (...args: any[]) => any;
fail?: (...args: any[]) => any;
[key: string]: any;
}
declare class MyLib {
app: VueApp;
el: string;
/**
* 構造函數
*
* @param appInstance VueApp 實例對象
* @param options IMyLib 介面對象,包含可選參數 el,預設值為 '#app'
*/
constructor(appInstance: VueApp, { el }: IMyLib);
/**
* 渲染組件
*
* @returns 無返回值
*/
render(): void;
/**
* 為Vue應用實例添加全局配置
*
* @param app Vue應用實例
* @param options 入參選項
*/
static globalConfig(app: VueApp, options: IMyLib): void;
/**
* 配置MyLib實例
*
* @param options IMyLib類型的配置項
* @returns 返回Promise<MyLib>,表示MyLib實例的Promise對象
*/
static config(options: IMyLib): void;
}
export default MyLib;
declare type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
declare type VueApp = ReturnType<typeof createApp>;
export { }
驗證插件包
由於是在本地構建生成的文件在本地,沒有上傳到npm,把本地生成的聲明文件可以拷貝到src下或者types目錄下。
我們把my-lib.js文件也拷貝到src目錄下進行驗證,我們調整src/App.vue下代碼如下:
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import { onMounted } from "vue";
import myLib from "./my-lib";
// import myLib from "../sdk/main";
onMounted(() => {
myLib.config({
el: "#root-app",
appid: "abcdefgxxwweridw",
msg: "Hello World",
fail: () => {
console.log("fail");
},
success: (t: any) => {
console.log(t);
},
});
});
</script>
<template>
<div class="detail-container">
<div id="root-app"></div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>