yarn global add create-vite-app
cva wheel-ui
// 等价于
npm init vite-app <
cd wheel-ui
yarn install
yarn dev
// 查看 vue-router 所有版本号
npm info nue-router versions
// 装置 vue-router
yarn add vue-router
yarn add -D sass
vite 文档给出的命令是
npm init vite-app 我的项目名
yarn create vite-app 我的项目名
等价于
yarn global creat-vite-app
cva 我的项目名
也等价于
npx create-vite-app 我的项目名
即 npx 会帮你全局装置用到的包
初始化 vue-router
新建 history 对象
新建 router 对象
app.use(router)
const history = createWebHashHistory();
const router = createRouter({
history: history,
routes: [{path: "/", component: HelloWorld}],
});
const app = createApp(App);
app.use(router);
app.mount("#app");
增加 <router-view>
<router-link>
<template>
<div>
导航栏 |
<router-link to="/"> frank </router-link> |
<router-link to="/xxx"> frank2</router-link>
</div>
<hr />
<router-view />
</template>
<script>
export default {name: "App",};
</script>
provide inject
需要:点击子组件 A,暗藏子组件 B 某一内容。
App.vue
<script lang="ts">
import {provide, ref} from "vue";
export default {
name: "App",
setup() {const asideVisible = ref(false); // set
provide("xxx", asideVisible);
},
};
</script>
子组件 A
<!-- Topnav.vue -->
<template>
<div class="topnav">
<div class="logo" @click="toggleAside">LOGO</div>
</div>
</template>
<script lang="ts">
import Vue, {inject, Ref} from "vue";
export default {setup() {const asideVisible = inject<Ref<boolean>>("xxx");
const toggleAside = () => {asideVisible.value = !asideVisible.value;};
return {toggleAside};
},
};
</script>
子组件 B
<!-- Doc.vue -->
<template>
<div>
<Topnav />
<div class="content">
<aside v-if="asideVisible">
...
</aside>
</div>
</div>
</template>
<script lang="ts">
import Topnav from "../components/Topnav.vue";
import {inject, Ref} from "vue";
export default {components: { Topnav},
setup() {const asideVisible = inject<Ref<boolean>>("xxx");
return {asideVisible};
},
};
</script>
TS 引入 .vue 文件
找不到模块“./App.vue”或其相应的类型申明。ts(2307)
src 目录下创立以 .d.ts 为结尾的文件
// shims-vue.d.ts
declare module '*.vue' {import { ComponentOptions} from "vue";
const componentOptions:ComponentOptions
export default componentOptions
}
error Command failed with exit code 1.
<center><img src=”https://cdn.jsdelivr.net/gh/Drwna/image//images/Snipaste_2022-02-08_15-42-14.png” alt=”Snipaste_2022-02-08_15-42-14″ width=”800px”></center>
<center><img src=”https://cdn.jsdelivr.net/gh/Drwna/image//images/Snipaste_2022-02-08_15-42-14.png” alt=”Snipaste_2022-02-08_15-42-14″ width=”800px”></center>
解决:
rm -rf node_modules/
yarn cache clean
yarn install
父子组件通信
父组件
<template>
<Switch :value="y" @input="y = $event" />
</template>
<script lang="ts">
import Switch from "../lib/Switch.vue";
import {ref} from "vue";
export default {components: { Switch},
setup() {const y = ref(false);
return {y};
},
};
</script>
子组件
<template>
<button @click="toggle" :class="{checked: value}">
<span></span>
</button>
</template>
<script lang="ts">
import {ref} from "vue";
export default {
props: {value: Boolean,},
setup(props, context) {const toggle = () => {
// 外围
context.emit("input", !props.value);
};
return {toggle};
},
};
</script>
或者应用 v-model
父组件
<Switch v-model:value="y" />
子组件
context.emit("update:value", !props.value);
属性绑定
默认所有属性都绑定到根元素。
应用 inheritAttrs: false
能够勾销默认绑定。
应用 $attrs
或者 context.attrs
获取所有属性。
应用 v-bind=“$attrs”
批量绑定属性。
应用 cons {size, ...rest} = context.attrs
将属性放离开。
父组件
// ButtonDemo.vue
<template>
<div>Button 示例 </div>
<h1> 示例 1 </h1>
<div>
<Button @click="onClick"
@mouseover="onClick"
size="small"> 你好 </Button>
</div>
</template>
<script lang="ts">
import Button from "../lib/Button.vue";
export default {components: { Button},
setup() {const onClick = () => {alert("hello");
};
return {onClick};
},
};
</script>
子组件
// Button.vue
<template>
// 将 size 属性绑定到根元素
<div :size="size">
// 将其余的属性绑定到指标元素
<button v-bind="rest">
<slot />
</button>
</div>
</template>
<script lang="ts">
export default {
// 勾销默认绑定到根元素
inheritAttrs: false,
setup(props, context) {const { size, ...rest} = context.attrs;
return {size, rest};
},
};
</script>
开发 UI 库的 CSS 注意事项
不能应用 scoped
因为 data-v-xxx 中的 xxx 不能保障每次运行都雷同,必须输入稳固不变的 class 选择器,不便使用者笼罩。
必须加前缀
.button 不行,很容易被使用者笼罩,.wheel-button 行,不太容易被笼罩。
.theme-link 不行,很容易被使用者笼罩,.wheel-theme-link 行,不太容易被笼罩。
插槽
具名插槽
<template>
<div class="wheel-dialog">
<header>
<slot name="title"/>
<span @click="close"></span>
</header>
<main>
<slot name="content"/>
</main>
</template>
<template>
<div>
<template v-slot:content>
<div> 你好 </div>
<div>hello</div>
</template>
<template v-slot:title>
<strong>xxx</strong>
</template>
</div>
</template>
Teleport
将 xxx 传送到 body 下
<Teleport to="body">
xxx
</Teleport>
openDialog
openDialog.js
import Dialog from './Dialog.vue';
import {createApp, h} from 'vue';
export const openDialog = (options) => {const {title, content, closeOnClickOverlay, ok, cancel} = options;
const div = document.createElement('div');
document.body.appendChild(div);
const close = () => {
//@ts-ignore
app.unmount(div);
div.remove();};
const app = createApp({render() {
return h(Dialog, {
visible: true,
closeOnClickOverlay,
'onUpdate:visible': (newVisible) => {if (newVisible === false) close();},
ok,
cancel
}, {title, content});
}
});
app.mount(div);
};
vue 组件应用
const showDialog = () => {
openDialog({
title: '题目',
content: '你好啊',
closeOnClickOverlay: false,
ok() {console.log('ok');},
cancel() {console.log('cancel');}
});
};
查看子组件的类型
获取插槽内容 const defaults = context.slots.default()
<template>
<div>
Tabs 组件
</div>
<component :is="defaults[0]"/>
<component :is="defaults[1]"/>
</template>
<script lang="ts">
import Tab from '../lib/Tab.vue';
export default {setup(props, context) {
// 调试 查看子组件是否为 Tab 组件
// console.log({...context.slots.default()[0]});
// console.log({...context.slots.default()[1]});
// const defaults = context.slots.default();
// console.log(defaults[0].type === Tab);
const defaults = context.slots.default();
defaults.forEach(tag => {if (tag.type !== Tab) {throw new Error('Tabs 子标签必须是 Tab');
}
});
return {defaults};
}
};
</script>
获取 el 的 width、left const {width, height, left, top} = el.getBoundingClientRect()
CSS 最小影响准则
css 相对不能影响库的使用者。
markdown
yarn add --dev marked
新建 plugins/md.ts
// @ts-nocheck
import path from 'path';
import fs from 'fs';
import {marked} from 'marked';
const mdToJs = str => {const content = JSON.stringify(marked(str));
return `export default ${content}`;
};
export function md() {
return {
configureServer: [ // 用于开发
async ({app}) => {app.use(async (ctx, next) => { // koa
if (ctx.path.endsWith('.md')) {
ctx.type = 'js';
const filePath = path.join(process.cwd(), ctx.path);
ctx.body = mdToJs(fs.readFileSync(filePath).toString());
} else {await next();
}
});
},
],
transforms: [{ // 用于 rollup // 插件
test: context => context.path.endsWith('.md'),
transform: ({code}) => mdToJs(code)
}]
};
}
新建 vite.config.ts
import {md} from './plugins/md';
export default {plugins: [md()]
};
应用
<template>
<article class="markdown-body" v-html="md"></article>
</template>
<script lang="ts">
// intro.md 为写好的 markdown 文件
import md from '../markdown/intro.md';
export default {data() {return {md}; }
};
</script>
打消反复 markdown
应用动静引入 import()
== 异步操作 ==
Markdown.vue
<template>
<article class="markdown-body" v-html="content"></article>
</template>
<script lang="ts">
import {ref} from 'vue';
export default {
props: {
path: {
type: String,
required: true
}
},
setup(props) {
// 外围
const content = ref<string>();
import(props.path).then(result => {console.log(result.default);
// 动静援用 3s 后才会呈现
setTimeout(() => {content.value = result.default;}, 3000);
});
return {content};
}
};
</script>
应用
Intro.vue
<template>
<Markdown path="../markdown/intro.md"/>
</template>
<script lang="ts">
import Markdown from '../components/Markdown.vue';
export default {components: {Markdown},};
</script>
或者间接全局引入 Markdown 组件,就不须要每个组件独自引入了。
main.ts app.component('Markdown', Markdown)
进一步优化:
间接在 router.js 里渲染,删除无用的 intro.vue
const history = createWebHashHistory();
// 外围
const md = filename => h(Markdown, {path: `../markdown/${filename}.md`, key: filename});
export const router = createRouter({
history: history,
routes: [{path: '/', component: Home},
{
path: '/doc',
component: Doc,
children: [{path: '', component: DocDemo},
// 外围
{path: 'intro', component: md('intro')},
{path: 'install', component: md('install')},
{path: 'get-started', component: md('get-started')},
//
{path: 'switch', component: SwitchDemo},
...
],
},
],
});
展现源代码
配置 vite.config.ts
import {md} from './plugins/md';
import * as fs from 'fs';
import {baseParse} from '@vue/compiler-core';
export default {plugins: [md()],
vueCustomBlockTransforms: {demo: (options) => {const {code, path} = options;
const file = fs.readFileSync(path).toString();
//@ts-ignore
const parsed = baseParse(file).children.find(n => n.tag === 'demo');
//@ts-ignore
const title = parsed.children[0].content;
const main = file.split(parsed.loc.source).join('').trim();
return `export default function (Component) {
Component.__sourceCode = ${JSON.stringify(main)
}
Component.__sourceCodeTitle = ${JSON.stringify(title)}
}`.trim();}
}
};
SwitchDemo.vue
<template>
<div>
<div class="demo-code">
<!-- 用法 -->
<pre>{{Switch1Demo.__sourceCode}}</pre>
</div>
</div>
</template>
<script lang="ts">
export default {components: {Switch2Demo, Switch1Demo,Switch,Button},
setup() {const bool = ref(false);
return {bool, Switch1Demo, Switch2Demo};
}
};
</script>
Switch1Demo.vue 组件结尾增加上 <demo>xxx</demo>
高亮源代码
应用 prismjs
yarn add prismjs
导入
import Prism from 'prismjs';
import '../../node_modules/prismjs/themes/prism.min.css';
...
set(){return (Prism)
}
...
应用
<pre class="language-css" v-html="Prism.highlight(Switch1Demo.__sourceCode, Prism.languages.html,'html')"/>
Vue2 和 Vue3 的区别
- Vue3 的 template 反对 == 多个跟标签 ==,Vue2 不反对。
- Vue3 有
createApp()
,而 Vue2 的是new Vue()
- Vue3
createApp(组件)
,Vue2new Vue({template, render})
v-model 代替以前的 v-model,.sync。
新增 context.emit 和 this.$emit 作用雷同。
部署上线注意事项
配置 vite.config.ts
增加以下代码,
base: './',
assetsDir: 'assets',
报错
runtime-core.esm-bundler.js?5c40:38 [Vue warn]: Invalid VNode type: Symbol(Comment) (symbol)
解决:
vue.config.js
const path = require(`path`);
module.exports = {
configureWebpack: {
resolve: {
symlinks: false,
alias: {vue: path.resolve(`./node_modules/vue`)
}
}
}
};
打包部署
rollup -c
{
"name": "whl-ui",
"version": "0.0.2",
"files": ["dist/lib/*"],
"main": "dist/lib/whl.js",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"resolutions": {"node-sass": "npm:sass@^1.26.11"},
"dependencies": {
"github-markdown-css": "4.0.0",
"marked": "4.0.12",
"prismjs": "1.21.0",
"vue": "3.0.0",
"vue-router": "4.0.0-beta.3"
},
"devDependencies": {
"@types/prismjs": "^1.26.0",
"@vue/compiler-sfc": "3.0.0",
"rollup-plugin-esbuild": "2.5.0",
"rollup-plugin-scss": "2.6.0",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-vue": "6.0.0-beta.10",
"sass": "1.26.11",
"vite": "1.0.0-rc.1"
}
}
yarn build
{
"name": "whl-ui",
"version": "0.0.2",
"files": ["dist/lib/*"],
"main": "dist/lib/whl.js",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"resolutions": {"node-sass": "npm:sass@^1.26.11"},
"dependencies": {
"github-markdown-css": "4.0.0",
"marked": "4.0.12",
"prismjs": "1.21.0",
"vue": "^3.0.0",
"vue-router": "4.0.0-beta.3"
},
"devDependencies": {
"@types/prismjs": "^1.26.0",
"@vue/compiler-sfc": "^3.0.0",
"rollup-plugin-esbuild": "2.5.0",
"rollup-plugin-scss": "2.6.0",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-vue": "6.0.0-beta.10",
"sass": "1.26.11",
"vite": "1.0.0-rc.13"
}
}