实况天气预报新版
48.24MB · 2025-10-14
你是否曾想过,除了开发复杂的单页应用(SPA),我们还能用 Vue 做些什么?比如,开发一个像地图 SDK、在线客服或数据统计脚本那样的独立 JS 库?用户只需在页面上引入一个 <script>
标签,就能立即使用你提供的功能,而无需关心其内部实现。
今天,我们就来探讨如何使用 Vue 和 Vite,从零开始构建一个这样的 JS 库。
我们的目标非常明确,创建一个名为 phpreturn
的库,它需要满足以下条件:
phpreturn.js
。<script src="phpreturn.js"></script>
引入,无需安装任何依赖或额外引入 Vue。window
对象上挂载一个全局变量(如 phpreturn
),通过 phpreturn.chooseUser()
等方法调用功能。要满足上述需求,我们必须解决两个核心问题:打包 和 隔离。
让我们通过具体的代码来实现这个想法。
首先,我们需要一个基础的 Vue + Vite 项目。打开你的终端,运行以下命令来创建项目:
npm create vite@latest phpreturn -- --template vue
进入项目目录并安装依赖:
cd phpreturn
npm install
这会创建一个名为 phpreturn
的新目录。为了保持项目整洁,我们可以清理一下 Vite 生成的默认文件:
src/assets
目录。src/components/HelloWorld.vue
和 src/App.vue
文件。src/main.js
的内容,准备编写我们的库入口逻辑。现在,我们的项目已经准备就绪。
接下来,修改项目根目录下的 vite.config.js
,告诉 Vite 我们要构建一个库(library),而不是一个应用。
// File: vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
// 新增:在开发模式下,模拟 UMD 挂载到 window
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/main.js'),
name: 'phpreturn', // 挂在 window 上的全局变量名
formats: ['umd'], // 输出 UMD 格式,兼容 <script> 标签
fileName: () => `phpreturn.js` // 固定输出文件名
},
// 注意:这里没有 rollupOptions.external,意味着所有依赖(包括Vue)都会被打包进去
}
})
src/main.js
是我们库的灵魂。它负责创建 Shadow DOM 环境,定义全局 API,并将 Vue 组件挂载进去。
// File: src/main.js
import { createApp } from 'vue';
import ChooseUser from './components/ChooseUser.vue';
// 核心函数:在 Shadow DOM 中挂载组件
function mountInShadowDom(component, props) {
const hostElement = document.createElement('div');
document.body.appendChild(hostElement);
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
const appContainer = document.createElement('div');
shadowRoot.appendChild(appContainer);
// 将组件的样式作为 <style> 标签注入到 Shadow DOM 中
// 这是实现样式隔离的关键
if (component.style) {
const styleEl = document.createElement('style');
styleEl.textContent = component.style;
shadowRoot.appendChild(styleEl);
}
const app = createApp(component, props);
app.mount(appContainer);
// 返回一个清理函数,用于关闭弹窗后销毁实例和 DOM
return () => {
app.unmount();
document.body.removeChild(hostElement);
};
}
// 暴露给用户的全局 API
export function chooseUser() {
return new Promise((resolve, reject) => {
let unmount;
const props = {
onSelect: (user) => {
resolve(user);
if (unmount) unmount();
},
onClose: () => {
reject(new Error('User cancelled.'));
if (unmount) unmount();
}
};
unmount = mountInShadowDom(ChooseUser, props);
});
}
// 在开发模式下,将 API 挂载到 window 上,方便调试
if (process.env.NODE_ENV === 'development') {
window.phpreturn = { chooseUser };
}
由于样式需要被手动注入到 Shadow DOM,我们需要对 .vue
单文件组件做一点小改造。直接从 .vue
文件自身导入样式会导致循环引用问题,因此最佳实践是<strong>将样式分离到单独的 .css 文件中</strong>
。
首先,创建样式文件 src/components/ChooseUser.css
。
/* File: src/components/ChooseUser.css */
/* 样式不再需要 scoped,因为 Shadow DOM 本身就是作用域 */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999; /* 确保在最顶层 */
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
min-width: 300px;
}
.modal-content ul {
list-style: none;
padding: 0;
margin: 10px 0;
}
.modal-content li {
padding: 8px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.modal-content li:hover {
background: #f0f0f0;
}
然后,修改 src/components/ChooseUser.vue
,从新的 CSS 文件导入样式,并移除 <style>
块。
<!-- File: src/components/ChooseUser.vue -->
<template>
<div v-if="visible" class="modal-backdrop" @click.self="close">
<div class="modal-content">
<h2>Choose a User</h2>
<ul>
<li v-for="user in users" :key="user.id" @click="select(user)">
{{ user.name }}
</li>
</ul>
<button @click="close">Close</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
// 关键:将 CSS 作为字符串导入
// `?inline` 是 Vite 的一个特性,它会将文件内容作为字符串导入
import styles from './ChooseUser.css?inline';
export default {
name: 'ChooseUser',
props: { onSelect: Function, onClose: Function },
// 关键:导出样式,供 main.js 使用
style: styles,
setup(props) {
const visible = ref(true);
const users = ref([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]);
const select = (user) => {
props.onSelect(user.name);
};
const close = () => {
visible.value = false;
props.onClose();
};
return { visible, users, select, close };
}
}
</script>
现在,只需运行 npm run build
,你就会在 dist
目录下得到一个独立的 phpreturn.js
文件。
在任何 HTML 页面中这样使用它:
<html>
<head>
<title>Test phpreturn</title>
</head>
<body>
<h1>My Awesome Page</h1>
<button id="my-button">Choose a User</button>
<!-- 1. 引入你的库,就这么简单 -->
<script src="./dist/phpreturn.js"></script>
<script>
// 2. 直接调用全局 API
document.getElementById('my-button').onclick = async () => {
try {
const user = await phpreturn.chooseUser();
alert(`You selected: ${user}`);
} catch (error) {
console.warn(error.message);
}
};
</script>
</body>
</html>
我们已经完成了库的构建,但如何在开发过程中实时看到效果,而不是每次修改都执行 npm run build
呢?Vite 强大的开发服务器(Dev Server)可以轻松解决这个问题。
创建测试页面
在项目根目录下创建一个 index.html
文件。这个文件将作为我们开发时的宿主页面。
<!-- File: index.html -->
<html>
<head>
<title>Test phpreturn Dev</title>
</head>
<body>
<h1>My Awesome Page (Dev Mode)</h1>
<button id="my-button">Choose a User</button>
<!-- 关键:直接引入库的入口文件 -->
<script type="module" src="/src/main.js"></script>
<script>
document.getElementById('my-button').onclick = async () => {
try {
// 直接调用挂在 window 上的 API
const user = await window.phpreturn.chooseUser();
alert(`You selected: ${user}`);
} catch (error) {
console.warn(error.message);
}
};
</script>
</body>
</html>
启动开发服务器
现在,运行 npm run dev
。Vite 会启动一个开发服务器,并自动打开 index.html
。当你修改 src
目录下的任何文件(例如 ChooseUser.vue
的样式或逻辑)时,页面会热更新(HMR),让你能立即看到修改后的效果,极大地提升了开发效率。
通过将 Vue 打包进库文件并利用 Shadow DOM 实现完美隔离,我们成功地创建了一个真正独立、即插即用的 JS
库。这种模式极大地扩展了 Vue 的应用场景,使其不再局限于构建大型应用,而是可以作为“功能组件”嵌入到任何 Web
环境中。希望这篇文章能为你打开一扇新的大门。