vue-ts-ui
vue3+ts实现一个简单组件库
目的
主要是使用momorepo的方式来写一个vue3组件库,再把ts加进去,不考虑完善组件的功能
工具
- vue3
- webpack
- lerna(实现monorepo)
- ts
- gulp (打包静态资源,处理css 和图片等)
- rollup (生成iife,cjs,amd,esm,umd)多格式文件
- yarn(包管理,产生软链接)
1.monorepo项目初始化
yarn global add lerna lerna init
这个时候会生成
lerna.json 和package.json{ "packages": ["packages/*"], "npmClient": "yarn", "version": "0.0.0", "useWorkspaces": true // 使用workspace,需要配置package.json,实现多个组件库共用相同依赖 }
{ "name": "root", "private": true, "workspaces": [ "packages/*" ], "devDependencies": { "lerna": "^4.0.0" } }
2.初始化组件
lerna create @h-ui/button lerna create @h-ui/icon
在填写组件名称时最好是 @组件库名称/组件名称 例如:@h-ui/button
然后在生成的单个组件目录下新增src目录和index.ts入口
就像:├─button │ │ package.json │ │ README.md │ ├─src | ├─ button.vue │ ├─index.ts # 组件入口 │ └─__tests__ # 测试相关 └─icon │ package.json │ README.md ├─src ├─ icon.vue ├─index.ts # 组件入口 └─__tests__
3.使用ts
yarn add typescript npx tsc --init
然后更改tsconfig.json如下
{ "compilerOptions": { "target": "ESNext", // 打包的目标语法 "module": "ESNext", // 模块转化后的格式 "esModuleInterop": true, // 支持模块转化 "skipLibCheck": true, // 跳过类库检测 "forceConsistentCasingInFileNames": true, // 强制区分大小写 "moduleResolution": "node", // 模块解析方式 "jsx": "preserve", // 不转化jsx "declaration": true, // 生成声明文件 "sourceMap": true // 生成映射文件 } }
二.组件初始化
在button.vue编写源代码
<template> <button :class="classs" @click="handleClick"> <i :class="icon"></i> <span><slot></slot></span> </button> </template> <script lang="ts"> import { computed, defineComponent, PropType } from "vue"; type ButtonType = | "primary" | "wraning" | "danger" | "default" | "info" | "sucess"; export default defineComponent({ props: { type: { type: String as PropType<ButtonType>, default: "primary", vaildator: (val: string) => { return [ "primary", "wraning", "danger", "default", "info", "sucess", ].includes(val); }, }, icon: { type: String, default: "", }, disabled: Boolean, loading: Boolean, round: Boolean, }, emits: ["click"], name: "HButton", setup(props, ctx) { const classs = computed(() => [ "h-button", "h-button--" + props.type, { "is-disabled": props.disabled, "is-loading": props.loading, "is-round": props.round, }, ]); console.log(classs); const handleClick = (e) => { ctx.emit("click", e); }; return { classs, handleClick, }; }, }); </script>
组件入口index.ts声明install方法并导出组件
import { App } from "@vue/runtime-core"; import Button from "./src/button.vue"; Button.install = (app: App): void => { app.component(Button.name, Button); }; type WithInstall<T> = T & { install(app: App): void }; const _Button: WithInstall<typeof Button> = Button; export default _Button;
同时.vue文件后缀的文件默认无法解析,增加typings
根目录下新建typings/vue-shim.d.tsdeclare module "*.vue" { import { defineComponent } from "@vue/runtime-core"; const component: ReturnType<typeof defineComponent> & { install(app: APP): void; }; export default component; }
2.整合所有组件
lerna create h-ui
在h-ui/index.ts里import Button from "@h-ui/button"; import Icon from "@h-ui/icon"; import { App } from "@vue/runtime-core"; import ButtonGroup from "../button-group"; const components = [Button, Icon, ButtonGroup]; const install = (app: App): void => { components.forEach((component) => { console.log(component.name); //注册组件 app.component(component.name, component); }); }; export default { install, };
三.搭建文档环境
yarn add webpack webpack-cli webpack-dev-server vue-loader@next @vue/compiler-sfc -WD yarn add babel-loader @babel/core @babel/preset-env @babel/preset-typescript babel-plugin-module-resolver url-loader file-loader html-webpack-plugin css-loader sass-loader style-loader sass -WD
并且根目录新建babel.config.js
module.exports = { presets: ["@babel/preset-env", "@babel/preset-typescript"], overrides: [ { test: /\.vue$/, plugins: ["@babel/transform-typescript"], }, ], env: { utils: { plugins: [ [ "babel-plugin-modu-resolver", { root: "h-ui", }, ], ], }, }, };
根目录下新建website文件夹用于起个服务预览组件效果,在这个目录下新建
App.vue main.ts template.html webpack.config.js
webpack.config.js配置如下const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); const { VueLoaderPlugin } = require("vue-loader"); module.exports = { mode: "development", devtool: "source-map", entry: path.resolve(__dirname, "main.ts"), output: { path: path.resolve(__dirname, "../website-dist"), filename: "bundles.js", }, resolve: { //解析模块 extensions: [".ts", ".tsx", ".js", ".vue"], }, module: { rules: [ { test: /\.(ts|js)x?$/, exclude: /node_modules/, loader: "babel-loader", }, { test: /\.vue$/, loader: "vue-loader", }, { // 识别图标... test: /\.(svg|otf|ttf|woff|eot|gif|png)$/, loader: "url-loader", }, { // 识别样式 test: /\.(scss|css)$/, use: ["style-loader", "css-loader", "sass-loader"], }, ], }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: path.resolve(__dirname, "template.html"), }), ], };
main.ts 如下
import App from "./App.vue"; import { createApp } from "@vue/runtime-dom"; import HUI from "h-ui"; // import HUI from "../lib/index.esm.js"; // import Icon from "../lib/icon/index"; import "theme-chalk/src/index.scss"; //创建应用,并使用组件库 createApp(App).use(HUI).mount("#app");
app.vue 和tempate.html就和正常的项目写法一样,在app.vue 直接使用组件,预览效果
根目录下package.json配置运行命令
"scripts": { "website-dev": "webpack serve --config ./website/webpack.config.js" }
四.组件库打包
1.打包umd格式组件库(兼容 IIFE, AMD, CJS 三种模块规范)
根目录下新建builds/webpack.config.js,配置如下
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
mode: "development",
entry: path.resolve(__dirname, "../packages/h-ui/index.ts"),
output: {
path: path.resolve(__dirname, "../lib"),
filename: "index.js",
libraryTarget: "umd",
library: "h-ui",
},
externals: {
vue: {
// 忽略组件引用的vue变量
root: "Vue",
commonjs: "vue",
commonjs2: "vue",
},
},
resolve: {
//解析模块
extensions: [".ts", ".tsx", ".js", ".vue"],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
loader: "babel-loader",
},
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
plugins: [new VueLoaderPlugin()],
};
根目录下的package.json新增脚本运行
"build": "webpack --config ./build/webpack.config.js"
打包esModule格式组件库
使用rollup进行打包,安装所需依赖
yarn add rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve rollup-plugin-vue -WD
打包分为两种,单个组件打包和全量打包
全量打包
新增builds/rollup.config.bundle.js
配置如下
import typescript from 'rollup-plugin-typescript2';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import path from 'path';
import vue from 'rollup-plugin-vue'
export default {
input: path.resolve(__dirname, <code>../packages/h-ui/index.ts</code>),
output: {
format: 'es',
file: <code>lib/index.esm.js</code>,
},
plugins: [
nodeResolve(),
vue({
target: 'browser'
}),
typescript({ // 默认调用tsconfig.json 帮我们生成声明文件
tsconfigOverride:{
exclude: [
'node_modules',
'website'
]
}
})
],
external(id) { // 排除vue本身
return /^vue/.test(id)
},
}
单个组件打包
新建builds/rollup.config.js,配置如下
import typescript from "rollup-plugin-typescript2";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import path from "path";
import { getPackagesSync } from "@lerna/project";
import vue from "rollup-plugin-vue";
// 获取package.json 找到名字 以@h-ui 开头的
const inputs = getPackagesSync()
.map((pck) => pck.name)
.filter((name) => name.includes("@h-ui"));
export default inputs.map((name) => {
const pckName = name.split("@h-ui")[1]; // button icon
return {
input: path.resolve(__dirname, <code>../packages/${pckName}/index.ts</code>),
output: {
format: "es",
file: <code>lib/${pckName}/index.js</code>,
},
plugins: [
nodeResolve(),
vue({
target: "browser",
}),
typescript({
tsconfigOverride: {
compilerOptions: {
// 打包单个组件的时候不生成ts声明文件
declaration: false,
},
exclude: ["node_modules"],
},
}),
],
external(id) {
// 对vue本身 和 自己写的包 都排除掉不打包
return /^vue/.test(id) || /^@h-ui/.test(id);
},
};
});
同时新增两个脚本运行命令
"scripts": {
"build": "webpack --config builds/webpack.config.js",
"build:esm-bundle": "rollup -c ./builds/rollup.config.bundle.js",
"build:esm": "rollup -c ./builds/rollup.config.js"
}
组件样式打包
使用gulp打包样式
安装gulp
yarn add gulp gulp-autoprefixer gulp-cssmin gulp-dart-sass gulp-rename -D
样式命名规范采用bem规范 https://baike.baidu.com/item/BEM/23772830
scss的复杂使用比如方法参考 https://www.sass.hk/docs/
lerna create theme-chalk
之后内容如下
src--│ button.scss
│ icon.scss
├─common
│ var.scss # 提供scss变量
├─fonts # 字体
└─mixins
config.scss # 提供名字
mixins.scss # 提供mixin方法
index.scss # 整合所有scss
gulpfile.js
gulpfile.js配置如下
const { series, src, dest } = require('gulp')
const sass = require('gulp-dart-sass')
const autoprefixer = require('gulp-autoprefixer')
const cssmin = require('gulp-cssmin')
function compile() { // 处理scss文件
return src('./src/*.scss')
.pipe(sass.sync())
.pipe(autoprefixer({}))
.pipe(cssmin())
.pipe(dest('./lib'))
}
function copyfont(){ // 拷贝字体样式
return src('./src/fonts/**').pipe(cssmin()).pipe(dest('./lib/fonts'))
}
exports.build = series(compile,copyfont)