仓库地址
先把仓库地址贴上:dxx-mobile-ui。
上周遗留的问题已解决,现已支持在Vue SFC
中引入外部类型定义。
创建项目
第一次搭建项目看再多文档都不如参考官方文档:项目搭建。
简单来说就以下三个步骤:
-
全局安装 vue-cli
pnpm install -g @vue/cli
-
创建以 typescript 开发的工程
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
-
更新到最新正式版
npx @dcloudio/uvm@latest
项目就创建好了
插件化准备
接下来的步骤在 HBuilderX 里操作比较保险,万一以后它文件目录又变了呢,是吧。
为什么叫
插件化准备
,因为我们要开发一个公用的组件库,让大家通过npm install xxx
就可以使用,那我们就要发布到npm
上面去,那么在uniapp
上我们就要开发插件来实现,最后把插件上传到npm
。插件化开发官方文档参考:https://uniapp.dcloud.net.cn/plugin/uni_modules.html#%E5%BC%80%E5%8F%91-uni-modules-%E6%8F%92%E4%BB%B6
1.打开 HBuilderX
,导入刚才创建的项目
2.右键根目录,选择 新建uni_modules目录(X)
,新建后就会在 src
下生成一个 uni_modules
文件
3.右键 uni_modules
,选择 新建uni_modules插件(H)
,会弹出一个弹窗。
4.填写插件 ID,就相当于我们组件库的名字,我这儿就取名dxx-mobile-ui
(插件 ID 最好唯一,如果你要发布到插件市场的话),点击创建。
5.创建后,会在 uni_modules
下自动生成一个文件,文件下的目录就是这样子。
6.接下来我们就可以在components
下开发自己的组件了。先把compenents
下自动生成的组件改造一下,组件名字请不要乱取,建议规范一下,后面使用的时候好设置easycom
。
代码如下:
<template>
<view class="content">{{ title }}, 我是d-test组件 </view>
</template>
<script lang="ts">
export default {
name: "d-test", // 重要!引用组件的名字,必须导出
};
</script>
<script setup lang="ts">
import { ref } from "vue";
const title = ref("Hello");
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: #8f8f94;
}
</style>
此时你已经可以在 src/pages/index/index.vue
里调试此组件了(不管你是在vscode
终端输入pnpm run dev:h5
,还是直接从HBuilderX
里点击运行到浏览器
,都可以调试,运行到其他端也可以),只需要在里面写上如下代码即可,无需手动引入。
<template>
<d-test></d-test>
</template>
<script setup lang="ts">
</script>
<style>
</style>
7.但是我们是要发布到npm上,那就需要一个统一的入口,所以我们在src/uni_modules/dxx-mobile-ui
下新建一个index.ts文件,代码如下:
const install = (App: any) => {
Object.values(
import.meta.glob("./components/*/index.ts", { eager: true }) // 动态引入components下的index.ts文件
).forEach((item: any) => {
App.component(item.name, item);
});
};
export default {
install,
};
2.要在src/uni_modules/dxx-mobile-ui/components/d-test
文件下新增一个入口文件index.ts
,以后就按这种规范来开发组件了,代码如下:
import { ref } from "vue";
import type DTest from "./d-test.vue";
const getDTestRef = () => ref<InstanceType<typeof DTest> | null>(null);
export { getDTestRef };
export default { name: "DTest", title: "测试组件" };
到这里我们的准备工作已经做完了,接下来我们就可以在components
下开发我们的组件了。我又在下面添加了一个组件,等会看看是否两个组件都能使用正常。
发布到npm
发布到npm
需要切换镜像,有时候会遇到ETIMEDOUT 443
错误,这需要我们翻墙才行,我的建议是单独开一个终端来代理,终端代理,是单独的,不会影响到其他地方。如果你还没有代理服务器,恐怕需要你自行查阅了,我的有时候会抽风,不知道什么原因。
1.完善 src/uni_modules/dxx-mobile-ui
下的 package.json
,新增这两行:
- name必须唯一
- private必须是false
- 以后每次发布的时候版本号都需要修改
"name": "dxx-mobile-ui",
"private": false,
如果想了解其他字段什么意思,请查看该文:package.json配置。
2.打开文件src/uni_modules/dxx-mobile-ui
,在这个文件下开一个cmd
终端,我们要把这个插件上传到npm
。
3.切换镜像
npm config set registry https://registry.npmjs.org
4.输入代理服务器,登录npm,接下来会叫你输入npm用户名、npm密码、邮箱,稍后会发一个验证码到你的邮箱,输入验证码就登录成功。(没有npm账号的去注册一个)
set https_proxy=http://127.0.0.1:7890
set http_proxy=http://127.0.0.1:7890
pnpm login
5.发布,接着执行:
pnpm publish
发布成功
然后你就可以去npm
搜到你发的这个包了。接下来就是如何使用的问题了。
使用
另起一个项目,方法一样,安装
pnpm add dxx-mobile-ui
在入口文件src/main.ts
引入
import { createSSRApp } from 'vue'
import App from './App.vue'
import DxxMobileUI from 'dxx-mobile-ui'
export function createApp() {
const app = createSSRApp(App)
app.use(DxxMobileUI)
return {
app,
}
}
在src/pages.json中添加配置,这样就可以不用每次使用都导入了。
"easycom": {
"autoscan": true,
"custom": {
"^d-(.*)": "dxx-mobile-ui/components/d-$1/d-$1"
}
}
然后在页面中直接使用即可
<d-button></d-button>
<d-test></d-test>
效果如下
好了,到这里就基本结束了,但是开发过程中,我们难免会使用 typescript ,有意思的是,vue
并不支持什么复杂类型从外部引入,意思就是你这样写会报错:
你只能在内部写类型,不能从外部引入
const emits = defineEmits<{...}>();
既然有这个问题,就让我们来解决一下吧。
支持在Vue SFC中引入外部类型定义
- 安装,记得先切换回淘宝镜像
npm config set registry https://registry.npmmirror.com
pnpm add prettier decomment -D
- 修改
vite.config.ts
文件
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import fs from "fs";
import path from "path";
import prettier from "prettier";
import decomment from "decomment";
import type { Plugin } from "vite";
// 支持在Vue SFC中引入外部类型定义
const vueSfcImportType = (): Plugin => {
return {
name: "vue-sfc-import-type",
enforce: "pre",
async transform(code, id) {
// 不是vue文件 返回
if (!/\.(vue)$/.test(id)) return;
const typePath = path.resolve(id, "../type.sfc.ts");
// 不存在type.sfc.ts文件 返回
if (!fs.existsSync(typePath)) return;
// 文件内容
let typeFileContent = decomment(fs.readFileSync(typePath).toString());
typeFileContent = await prettier.format(typeFileContent, {
semi: true,
parser: "typescript",
});
// 将文件内容去掉换行符 变成一行
typeFileContent = typeFileContent.replace(/[\r\n]/g, "");
// 替换的正则表达式
const regex = /import .* from "\.\/type\.sfc"/g;
// 将导入type.sfc的那一行进行替换
const res = code.replace(regex, typeFileContent);
return res;
},
};
};
// https://vitejs.dev/config/
export default defineConfig(async ({ command, mode }) => {
return {
plugins: [uni(), vueSfcImportType()],
};
});
此时就不报错了,原理就是将导出的内容变成一行代码,然后与vue
文件中import...type.sfc
的那一行进行替换。
总结
移动端插件化与web端不同,且网上不易搜索,明白了原理你就会发现,其实就是在uni_modules下写插件而已,最后把这个插件发布就行了。