VUE3 UI造轮子总结

272 阅读3分钟

一、技术细节

  • 使用create-vite-app搭建官网
  • 使用vue3 + typescript实现
  • 支持markdown语法高亮展示代码
  • 使用rollup进行项目库打包,并发布npm
  • 使用shell脚本自动化部署

二、效果预览

  1. 源码链接
  2. 效果预览

首页 image.png

文档 image.png

三、vite搭建过程

1. 准备工作

  • 安装nodejs稳定版
  • 安装npm,使用淘宝源加速,运行命令:npm config set registry registry.npm.taobao.org
  • 安装yarn
  • 安装vscode 或webstorm用于项目开发

2. 使用vite搭建

  • 全局安装create-vite-app
yarn global add create-vite-app 或
npm i -g create-vite-app
  • 创建项目
cva bobo-ui  或
create-vite-app bobo-ui //bobo-ui可以改成自己的项目名 
  • 打开项目
cd bobo-ui
  • 运行以下命令,打开本地网址,开始开发
yarn dev 

四、知识总结

1. vue-router

  • 安装: yarn add vue-router
  • 初始化:新建 history对象;新建router对象
//main.ts 文件
import { createApp } from 'vue';
import App from './App.vue';
import {createWebHashHistory,createRouter} from 'vue-router';
const history = createWebHashHistory();
const router = createRouter({
    history,
    routes:[
        {path:'/',component:Bobo},
        {path:'/xxx',component:Bobo2}
    ]
});

const app = createApp(App);
app.use(router);
app.mount('#app')
  • router-view & router-link
//App.vue 文件
<template>
  <div>导航栏|
    <router-link to="/">BOBO</router-link>|
    <router-link to="/xxx">BOBO2</router-link></div>
  <hr />
  <router-view />
</template>

<script>
export default {
  name: 'App',
}
</script>

2. provide 和 inject

//父组件做标记
<script lang="ts">
import {ref ,provide} from 'vue';
export default {
  name: 'App',
  setup(){
    const asideVisible = ref(false);
    provide('asideVisible',asideVisible);
  }

}
</script>
//子组件接收值
<template>
    <aside v-if="asideVisible">
        <h2>组件列表</h2>
    </aside>
</template>

<script lang="ts">
import {inject, Ref} from "vue";
export default {
  setup(){
    const asideVisible = inject<Ref<boolean>>('asideVisible')
    return {asideVisible}
  }
}
</script>

3. props传值和v-model

//父组件
<template>
  <div>
    <Switch :value = "y"  @update:value="y=$event"/>
  </div>
</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}"></button>
</template>

<script lang="ts">
export default {
  props:{
    value:Boolean
  },
  setup(props,context){
    const toggle = () =>{
      context.emit('update:value',!props.value)
    }
    return {toggle};
  }
}
</script>
//v-model
//父组件
<Switch v-model:value = "y" />

//子组件
context.emit('update:value',!props.value)

4. vue3属性绑定(Attrs)

<template>
  //父元素传过来的所有属性默认绑定到根元素上,可以不继承,并且拆分属性只绑定部分属性 
  <div :size = "size">
    <!-- <button v-bind="$attrs"> 把父元素传过来的所有属性绑定到button上 -->
    <button v-bind="rest"> <!-- 只绑定除size外的剩余属性 -->
      <slot/>
    </button>
  </div>
</template>

<script lang="ts">
export default {
  inheritAttrs:false,//模板根元素不继承父元素传过来的事件
  setup(props,context){
    const{onClick,onMouseOver,size,...rest}=context.attrs;//拿到父元素传的属性解构赋值
    return{onClick,onMouseOver,size,rest};
  }
}

</script>

image.png

5. 具名插槽

//父组件
<Dialog>
    <template v-slot:title>
      <strong>标题</strong>
    </template>
    <template v-slot:content>
      <strong>内容</strong>
    </template>
 </Dialog>
//子组件
<header>
 <slot name="title"/> 
</header>
<main>
  <slot name="content"/>
</main>

6. 渲染嵌套插槽

//父组件
<template>
  <Tabs>
    <Tab title="导航1">内容1</Tab>
    <Tab title="导航2">内容2</Tab>
  </Tabs>
</template>
//嵌套子组件 Tabs.vue
<template>
  <div>
    <component v-for="(c,index) in defaults" :is="c" :key="index"/>
  </div>
</template>

<script lang="ts">
import Tab from "./Tab.vue";
export default {
  setup(props,context){
    const defaults = context.slots.default(); //可以取到所有的Tab
    return {defaults};
}
</script>
//嵌套子组件 Tab.vue,父组件的内容1、内容2,会插到slot
<template>
  <div>
    <slot/>  
  </div>
</template>

7. Teleport

任意传送门:Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术

//直接将这一块传送到Dom节点里面的body
<Teleport to = "body">
<div>
一大块代码
</div>
</Teleport>

8. ref 和 getgetBoundingClientRect

<template>
<div class="gulu-tabs-nav-indicator " ref="indicator">aaa</div>  
</template>

<script lang="ts">
    const indicator = ref<HTMLDivElement>(null);
    const {width,height,top,left} = indicator.getBoundingClientRect();//获取indicator引用的div元素的宽高等
    return {indicator};
</script>

9. 引入SVG图标

  • iconfont官网选中图标加入购物车项目,打开购物车该,点击symbol,点击生成代码
  • 将生成的地址链接复制到项目中index.htmlheader
 <script src="//at.alicdn.com/t/font_2426847_zhxe2xwikqs.js"></script>
  • 点击旁边的帮助,搜索symbol引用,按文档指示把css复制到项目的index.css文件
.icon {
  width: 1em; height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
  • 在项目中加入svg图标
<svg class="icon" >
   <use xlink:href="#icon-Vue"></use> //icon-Vue为图标在iconfont的名字
</svg>

10. clip-path和border-radious都可以用于画圆弧

11. 支持github的markdown样式

markdown github地址

yarn add github-markdown-css //安装
//使用
import 'github-markdown-css' //在mian.ts引入,就可以直接使用
<template>
  <article class="markdown-body" >//加入class="markdown-body"就可以显示为markdown样式
   <h1>标题</h1>
   <p>扩大放大</P>
  </article>
</template>

12. 支持项目中引入markdown文件

  • src同级目录下新建plugins,添加文件md.ts
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)
        }]
    }
}
  • 安装marked:yarn add --dev marked
  • src同级目录下创建vite.config.ts,引入plugins/md.ts,传给plugins
import { md } from "./plugins/md";

export default {
    plugins: [md()]
};
  • 使用实例

md文件 image.png

<template>
  <article class="markdown-body" v-html="md">
  </article>
</template>

<script lang="ts">
import md from '../markdown/intro.md';
export default {
  data() {
    return {
      md
    }
  }
}
</script>

13. createApp和 h 函数

  • 使用createApp这个 API返回一个应用实例,并且可以通过链条的方式继续调用其他的方法

image.png

//openDialog.ts
import Dialog from './Dialog.vue';
import {createApp,h} from 'vue';

export const openDialog = (options) =>{
    const {title,content,ok,cancel,closeOnClickOverlay}= options;
    const div = document.createElement('div');
    document.body.appendChild(div);

    const close=()=>{
        app.unmount(div);
        div.remove();
    }

    const app =createApp({
        render(){
            return h(
                Dialog,
                {
                    visible:true,
                    "onUpdate:visible":(newVisible)=>{
                        if (newVisible===false){
                            close();
                        }
                    },
                    ok,
                    cancel,
                    closeOnClickOverlay
                },
                {title,content}
            )
        }
    });
    app.mount(div);
}

14.显示源代码

  • 修改vite.config.ts文件配置
import fs from 'fs'
import {baseParse} from '@vue/compiler-core'

export default {
    vueCustomBlockTransforms: {
        demo: (options) => {
            const { code, path } = options
            const file = fs.readFileSync(path).toString()
            const parsed = baseParse(file).children.find(n => n.tag === 'demo')
            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()
        }
    }
};
  • 在 switch1demo中的template前加上<demo>常规用法</demo> image.png

  • 在SwitchDemo.vue父组件引用Switch1Demo,添加预览

<div class="demo">
      <h2>常规用法</h2>
      <div class="demo-component">
        <component :is="Switch1Demo"/> //此处是组件
      </div>
      <div class="demo-actions">
        <Button>查看代码</Button>
      </div>
      <div class="demo-code">
        <pre>{{Switch1Demo.__sourceCode}}</pre> //此处展示组件的源代码
      </div>
</div>

15.高亮源代码

1、安装prismjs yarn add prismjs

2、在使用Demo的地方SwitchDemo.vue里import

import 'prismjs'
const Prism = (window as any) .Prism

3、查看node_modules里的prismjs里面的themes中和prism.css同级的其他css,就是可以引用的css样式,在使用Demo的地方SwitchDemo.vue里import

import 'prismjs/themes/prism.css'

4、使用Prism.highlight就可以对源代码进行高亮显示

<pre class="language-html" v-html="Prism.highlight(Switch2Demo.__sourceCode, Prism.languages.html, 'html')" />

五、rollup打包库文件,发布npm

1. 创建sr/lib/index.ts文件,导出组件

image.png

2. src同级目录创建rollup.config.js,告诉rollup怎么进行打包

// 请先安装 rollup-plugin-esbuild rollup-plugin-vue rollup-plugin-scss sass rollup-plugin-terser
// 把 name 改成你的库名
import esbuild from 'rollup-plugin-esbuild'
import vue from 'rollup-plugin-vue'
import scss from 'rollup-plugin-scss'
import dartSass from 'sass';
import { terser } from "rollup-plugin-terser"

export default {
    input: 'src/lib/index.ts',
    output: [{
        globals: {
            vue: 'Vue'
        },
        name: 'Bobo',
        file: 'dist/lib/bobo.js',
        format: 'umd',
        plugins: [terser()]
    }, {
        name: 'Bobo',
        file: 'dist/lib/bobo.esm.js',
        format: 'es',
        plugins: [terser()]
    }],
    plugins: [
        scss({ include: /\.scss$/, sass: dartSass }),
        esbuild({
            include: /\.[jt]s$/,
            minify: process.env.NODE_ENV === 'production',
            target: 'es2015'
        }),
        vue({
            include: /\.vue$/,
        })
    ],
}

3. 安装rollup(全局或局部安装),项目根目录运行rollup -c

 yarn global add rollup //安装

4. 更新package.json文件

image.png

5. 发布npm

  • 修改npm源为官方源
npm config get registry //查看npm源
npm config set registry https://registry.npmjs.org/  //修改npm源

小技巧:nrm快速切换源

image.png

  • 登录npm官网,注册用户
  • 项目终端运行npm login,输入用户名,密码,邮箱登录(使用npm logout可以登出)
  • 项目终端运行npm publish,就可以把打包好的库文件发布到npm
  • 登录npm官网,登录账号,点击右上角头像,查看packages,就可以看到自己发布的包
  • 下次改动重新rollup -c,修改package.json里的version版本号,重新npm publish

七、自动化部署

1. 修改vite.config.ts配置(没有该配置文件可新增,放在src同级目录下)

image.png

2. 在自己github上创建源码仓库,过程略

3. 创建自动化脚本deploy.sh,实现一键部署

image.png

注:orgin源需修改为自己的github仓库地址

八、问题解决

待补充...