部署项目并且发布源代码至 npm

[toc]


部署官网到 GitHub

需要大致完成两件事

  • 部署官网
    • 让官网上线,有文档层能直观地被看到,才会有人去使用
  • 发布 Vueel UI
    • 让其他开发者可以使用 npm install vueel3-ui安装源码

发布官网

就是把 dist 目录上传到网上

  • yarn buil时要注意设置build path

发布官网的步骤

注意要关闭网站的自动翻译功能,会打乱代码

  • 1.如果有 dist 目录,则删除 dist 目录
  • 2.在 .gitignore添加一行 /dist/然后提交代码
  • 3.运行yarn build/vite build创建出最新的dist
  • 4.运行hs dist -c-1在本地测试网站是否成功运行
  • 5.在部署到GitHub
    • 1.运行 cd dist 到 dist 目录下
    • 2.运行git init;git add .;git commit -m 'init';
    • 3.注意现在 dist 是一个套娃的git仓库
    • 4.新建远程仓库vueel-website,并关联到 dist 目录
    • 5.注意不是关联到上层仓库的目录
      1. 使用git协议的 点击SHH按钮,不要使用https
    • 2.开启项目的Pages功能
1
2
3
4
5
6
7
8
cd dist
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:xmasuhai/vueel3-demo-website.git
git push -f -u origin main
cd ..
  • 注意强制覆盖 git push -f -u origin main

部署 GitHub Pages 发现 404

  • 安装yarn global add http-server
  • 运行hs dist -c-1在本地测试网站是否成功运行
  • 点击进入http://127.0.0.1:8080
  • 出现页面说明目前项目时可以部署的
  • 但按照以上步骤部署发现页面一片空白
  • 打开浏览器,开发者面板的ntework查看请求发现js和css资源都是404,而html请求正确
  • 请求路径错误导致的404
  • 需要重新设置vite.config.ts中的正确的路径build path

设置build path

对比html文件和其他文件的请求路径

  • html 请求成功
    • Request URL: https://xmasuhai.xyz/vueel3-demo-website/index.html
  • js 请求失败
    • Request URL: https://xmasuhai.xyz/assets/index.96031f0b.js
  • css 请求失败
    • Request URL: https://xmasuhai.xyz/assets/index.98fa0c4b.css
  • 发现路径中仓库名不同

GitHub 会删掉以下划线开头的目录,例如_assets/

  • 参考查看官方的配置vite.config.js
  • 目前vite版本已经去默认掉assetsDir下划线

仓库名不同

  • 需要在vite.config.ts中设置base: './', ...,以./开头而不是以/开头
  • 重新部署即可正确显示页面

部署到码云

提升访问速度

  • 直接使用 github 账号登录码云
  • 拉取 github 对应仓库
  • 打开服务 -> 部署 Gitee Pages

脚本化半自动部署

根目录下创建deploy.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
rm -rf dist &&
vite build &&
cd dist &&
git init &&
git add . &&
git commit -m "update" &&
git branch -M main &&
git remote add origin git@github.com:xmasuhai/vueel3-demo-website.git &&
git push -f -u origin main &&
cd -
  • 运行sh deploy.sh即可实现半自动部署
  • 有时github部署会延迟,需要在浏览器network中开启禁用缓存,勾上 Disable cache,刷新网页几次即可

使用GitHub Actions持续集成

根目录下创建.github/workflows/deploy-website.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
name: Deploy GitHub Pages

on:
  # 每当 push 到 main 分支时触发部署
  push:
    branches: [main]

# 任务
jobs:
  build-and-deploy:
    # 服务器环境:最新版 Ubuntu
    runs-on: ubuntu-latest

    steps:
      # 拉取代码
      - name: Checkout
        uses: actions/checkout@v2
        with:
          persist-credentials: false

      # 1、生成静态文件
      - name: Build
        run: yarn install && yarn vite-bd

      # 2、部署到 GitHub Pages
      # 预先设置好仓库的 secrets.ACCESS_TOKEN
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          REPOSITORY_NAME: xmasuhai/vueel3-demo-website
          BRANCH: gh-pages
          FOLDER: dist


发布 Vueel UI 至 npm

打包库文件

vite不支持打包,需要自行配置rollup

  • 创建 lin/index.ts,将所有需要导出的东西导出
  • 创建 rollup.config.js,放在根目录下,告诉rollup怎么打包
  • 安装rollup相关依赖
    • yarn global add rollup
    • yarn add -D rollup-plugin-esbuild rollup-plugin-vue rollup-plugin-scss sass rollup-plugin-terser @vue/compiler-sfc
  • 运行rollup -c 编译库文件

lin/index.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// import Switch from './Switch';
// export {Switch};
// 可以合并成依据
export {default as Switch} from 'Switch.vue';
export {default as Button} from 'Button.vue';
export {default as Tabs} from 'Tabs.vue';
export {default as Tab} from 'Tab.vue';
export {default as Dialog} from 'Dialog.vue';
export {useDialog as useDialog} from './hooks/useDialog';
export {useCheckSlots as useCheckSlots} from './hooks/useCheckSlots';
export {useGetSlots as useGetSlots} from './hooks/useGetSlots';

rollup.config.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 请先安装 rollup-plugin-esbuild rollup-plugin-vue rollup-plugin-scss sass rollup-plugin-terser
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'
import alias from '@rollup/plugin-alias'

module.exports = {
  input: 'src/lib/index.ts',
  output: {
    globals: {
      vue: 'Vue'
    },
    name: 'Vueel3-demo',
    file: 'dist/lib/gulu.js',
    format: 'umd', // format: 'cjs',
    plugins: [terser()],
    // dir: 'output',
  },
  plugins: [
    alias({
      entries: [
        {find: '@', replacement: './src'},
        {'vue-types': 'vue-types/shim'}
      ]
    }),
    scss({include: /\.scss$/, sass: dartSass}),
    esbuild({
      include: /\.[jt]s$/,
      minify: process.env.NODE_ENV === 'production',
      target: 'es2015'
    }),
    vue({
      include: /\.vue$/,
    })
  ]
}

rollup -c 编译库文件

运行rollup -c发现报错

  • 逐个删除插件尝试
  • 改变插件顺序

rollup.config.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 请先安装 rollup-plugin-esbuild rollup-plugin-vue rollup-plugin-scss sass rollup-plugin-terser
// rollup-plugin-esbuild
// rollup-plugin-vue
// rollup-plugin-scss
// sass
// rollup-plugin-terser
import dartSass from 'sass'
import vuePlugin from 'rollup-plugin-vue'
import esbuildPlugin from 'rollup-plugin-esbuild'
import scssPlugin from 'rollup-plugin-scss'
import {terser} from 'rollup-plugin-terser' // 压缩 js 代码,包括 ES6 代码压缩

export default {
  input: 'src/lib/index.ts',
  output: {
    // exports: 'vueel3',
    globals: {
      vue: 'vue' // 告诉rollup全局变量Vue即是vue
    },
    name: 'vueel3-ui',
    file: 'dist/lib/vueel3.js',
    format: 'umd', // format: 'cjs',
    plugins: [terser()],
  },
  external: ['vue'],
  plugins: [
    // .vue -> .js
    vuePlugin({
      include: /\.vue$/,
      // 把单文件组件中的样式,插入到html中的style标签
      css: true,
      // 把组件转换成 render 函数
      compileTemplate: true
    }),
    // .ts -> .js
    esbuildPlugin({
      include: /\.[jt]s$/,
      minify: process.env.NODE_ENV === 'production',
      target: 'es2015'
    }),
    // .scss -> css
    scssPlugin({include: /\.scss$/, sass: dartSass})
  ]
}

忽略警告

  • (!) Unresolved dependencies...
  • (!) Missing global variable names...

编译src/lib/index.ts → dist/lib/vueel3.js...

  • 生成dist/lib/vueel3.cssdist/lib/vueel3.js

解释 rollup.config.js

新增几个插件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 请先安装 
// rollup-plugin-esbuild
// rollup-plugin-vue
// sass
// rollup-plugin-scss
// rollup-plugin-terser
// @rollup/plugin-alias
// rollup-plugin-filesize
import path from 'path'
import dartSass from 'sass' // 支持 'rollup-plugin-scss'
import vuePlugin from 'rollup-plugin-vue' // 编译vue为js
import esbuildPlugin from 'rollup-plugin-esbuild' // 编译ts为js
import scssPlugin from 'rollup-plugin-scss' // 编译scss为js
import bundleSize from 'rollup-plugin-filesize' // 显示打包完成的体积
import alias from '@rollup/plugin-alias' // 替换别名
import {terser} from 'rollup-plugin-terser' // 压缩 js 代码,包括 ES6 代码压缩 作用和uglify相同 import { uglify } from 'rollup-plugin-uglify';
import pkg from './package.json'

const pathResolve = p => path.resolve(__dirname, p)

export default {
  input: 'src/lib/index.ts',
  output: {
    // exports: 'vueel3',
    globals: {
      vue: 'Vue' // 告诉rollup全局变量Vue即是vue 不用打包
    },
    name: 'vueel3-ui', // 影响输出时的全局变量名
    file: 'dist/lib/vueel3.js', // 自动创建css,不用重复添加
    format: 'umd', // 相较于format: 'cjs', `umd`既适用于浏览器,用适用于node环境
    plugins: [terser()], // 丑化代码
  },
  external: [Object.keys(pkg.dependencies)],
  plugins: [
    // .vue -> .js
    vuePlugin({
      include: /\.vue$/,
      // 把单文件组件中的样式,插入到html中的style标签
      css: true,
      // 把组件转换成 render 函数
      compileTemplate: true,
      template: {
        isProduction: !process.env.ROLLUP_WATCH,
        compilerOptions: {preserveWhitespace: false}
      }
    }),
    // .ts -> .js
    esbuildPlugin({
      include: /\.[jt]s$/,
      minify: process.env.NODE_ENV === 'production',
      target: 'es2015' // 支持ES6
    }),
    // .scss -> css
    scssPlugin({include: /\.scss$/, sass: dartSass}),
    alias({
      '@': pathResolve('src')
    }),
    bundleSize(),
  ]
}

发布 dist/lib 目录,上传到npm服务器

package.json中添加filesmain

package.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
  "name": "vue3-demo-ui-1",
  "version": "0.0.0",
  "author": "Xu Shuai",
  "license": "MIT",
  "files": [
    "dist/lib/*"
  ],
  "main": "dist/lib/vueel3.js",
  "scripts": {
    "rpc": "rollup -c",
    "dev": "vite",
    "vite-bd": "vite build",
    "build": "vue-tsc --noEmit && vite build"
  },
  "resolutions": {
    "node-sass": "npm:sass@^1.39.2"
  },
  "dependencies": {
    "github-markdown-css": "^5.0.0",
    "mitt": "^3.0.0",
    "prismjs": "^1.25.0",
    "vite": "^2.5.7",
    "vue-router": "4.0.11"
  },
  "devDependencies": {
    "@rollup/plugin-alias": "^3.1.8",
    "@types/marked": "^3.0.2",
    "@types/node": "^16.9.1",
    "@types/prismjs": "^1.16.6",
    "@vitejs/plugin-vue": "^1.6.1",
    "@vue/compiler-sfc": "^3.2.21",
    "marked": "^3.0.7",
    "postcss": "^8.3.11",
    "rollup-plugin-esbuild": "^4.6.0",
    "rollup-plugin-filesize": "^9.1.1",
    "rollup-plugin-scss": "^3.0.0",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-vue": "^6.0.0",
    "sass": "1.39.2",
    "sass-loader": "8.0.2",
    "typescript": "^4.4.3",
    "vite": "^2.5.7",
    "vite-plugin-components": "^0.13.3",
    "vite-plugin-md": "^0.11.2",
    "vue": "^3.2.20",
    "vue-loader": "^16.8.2",
    "vue-tsc": "^0.2.2",
    "vue-types": "^4.1.1"
  },
  "peerDependencies": {
    "vue": "^3.2.6"
  }
}

  • "files": [ "dist/lib/*" ] 写上所有需要上传的文件
  • "main": "dist/lib/vueel3.js", 说明主文件

npm login

确保已切换至npm官方源,而没有使用淘宝源

  • 查看npm源npm config get registry
  • 切换至npm官方源npm config set registry https://registry.npmjs.org
  • 切换回npm淘宝源npm config set registry https://registry.npm.taobao.org/

注册npm,之后在命令行中使用npm login登录

  • npm login
  • 按提示输入UsernameEmailPassword
  • 注意只有使用了官方源,才能正确登录

npm publish

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
卐 npm publish
npm notice
npm notice package: vue3-demo-ui-1@0.0.0
npm notice === Tarball Contents ===
npm notice 1.1kB LICENSE
npm notice 2.4kB README.md
npm notice 2.2kB dist/lib/vueel3.css
npm notice 1.6kB dist/lib/vueel3.js
npm notice 1.3kB package.json
npm notice === Tarball Details ===
npm notice name:          vue3-demo-ui-1
npm notice version:       0.0.0
npm notice filename:      vue3-demo-ui-1-0.0.0.tgz
npm notice package size:  4.4 kB
npm notice unpacked size: 8.6 kB
npm notice shasum:        bfdc2825cf74db0e25f89c8f0fc2d925c5279d3b
npm notice integrity:     sha512-KIJx0x4g0Eu9t[...]Htcv7IaAHffdw==
npm notice total files:   5
npm notice
+ vue3-demo-ui-1@0.0.0

  • 完成发布后,就可以通过 yarn add vue3-demo-ui-1来使用了

一些细节

  • name
    • packagename必须是小写字母,可用-_连接
    • packagename不能和npm上现有的name重名
  • version
    • 每次 publish 的版本不能和之前任何一次的相同
    • 之后的更改中,必须先改 versionnpm publish
  • 注册 npmjs.com 账号
    • 使用网页注册,然后再命令行使用npm login登录
    • 登陆之后才能npm publish
    • 使用 npm logout可以退出登录

小技巧

使用 nrm 快速切换源

  • 安装npm i -g nrm --registry https://registry/npm.taobao.org
  • nrm ls可以查看所有源
  • nrm use taobao可以切换到淘宝源
  • nrm use npm可以切换回npm官方源

修复本地运行不出现而部署dist后出现的 bug,完善功能

模拟别人使用自己的组件

测试自己的 package

  • 在空白目录中使用@vue/cli新建一个vue3项目
  • 安装自己的包yarn add vue3-demo-ui-1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
info Direct dependencies
└─ vue3-demo-ui-1@0.0.12
info All dependencies
├─ @vue/devtools-api@6.0.0-beta.20
├─ github-markdown-css@5.0.0
├─ mitt@3.0.0
├─ prismjs@1.25.0
├─ vue-router@4.0.11
└─ vue3-demo-ui-1@0.0.12


重构组件名

  • 统一改为以Vl开头的驼峰模式

修复 Tabs 组件的 bug

使用hs dist -c-1运行打包后的项目时,tab组件报错VlTabs 子标签必须是 VlTabItem

  • 分别打出本地服务运行和打包后运行信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
...
const {defaults} = useGetSlots();
console.log("-> defaults", defaults);
console.log("-> TabItem", TabItem);
const checkTabItem = () => {
  defaults.forEach((tab: VNode) => {
  console.log("-> tab.type", tab.type);
    if (tab.type !== TabItem) {
      console.error(new Error('VlTabs 子标签必须是 VlTabItem'));
    }
  });
};
...
  • 在生产环境下,原始组件和tab.type是不严格相等的
  • 改为判断组件的name属性,需设置属性

VlTabItem.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<template>
  <div>
    <slot>
      Tab内容
    </slot>
  </div>
</template>

<script lang="ts">
export default {
  name: 'VlTabItem'
};
</script>

抽出判断组件名的逻辑useCheckSlots.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import {Component, VNode} from 'vue';

export const useCheckSlots = (defaults: VNode[], component: Component) => {
  defaults.forEach((tab: VNode) => {
    if (tab.type !== component) {
      console.error(new Error(`VlTabs 子标签必须是 ${component.name}`));
    }
  });
};

VlTabs.vue

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<script setup lang="ts">
import VlTabItem from './VlTabItem.vue';
import {useGetSlots} from './hooks/useGetSlots';
import {useCheckSlots} from './hooks/useCheckSlots';
import {onBeforeUpdate, computed, onMounted, ref, VNode, watchEffect, Ref} from 'vue';

// 获取slotsDefaults
const {defaults} = useGetSlots();

// 检查子标签名方法hooks: useCheckSlots
/*
const checkTabItem = () => {
  defaults.forEach((tag: VNode) => {
    if (tag.type !== TabItem) {
      console.error(new Error('Tabs 子标签必须是 TabItem'));
    }
  });
};
*/
const checkTabItem = useCheckSlots.bind(null, defaults, VlTabItem);

// 获取子组件VNode中对应title属性组成的数组 titles: string[]
const titles = defaults?.map((tag: VNode) => {
  return tag?.props?.title;
});

// 声明外部数据 获取 props.selected 属性
const props = defineProps({
  selected: String
});

// 对比所有项目的title和当前选中项的title 获取当前选中项currentTab
// filter 性能不如 find,改用find
/*
const getCurrentTab = computed(() => {
  return defaults?.filter((tag: VNode) => {
    return tag?.props?.title === props.selected;
  })[0];
});
*/
const getCurrentTab = computed(() => {
  return defaults.find((tag: VNode) => {
    return tag?.props?.title === props.selected;
  });
});

const getCurrentTitle = computed(() => {
  return defaults.find((tag: VNode) => {
    return tag?.props?.title === props.selected;
  })!.props!.title;
});

// 声明 发布方法名
const emits = defineEmits(['update:selected']);

// 点击选中项目时执行的方法 通知父组件当前的选中项
const select = (title: string) => {
  emits('update:selected', title);
};

// 获取导航标签项目引用
const div = document.createElement('div');
let selectedItem: Ref = ref<HTMLDivElement>(div);
// 获取导航标签指示横线引用
const indicator = ref<HTMLDivElement>(div);
// 获取导航外部div引用
const container = ref<HTMLDivElement>(div);

// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
  selectedItem.value = div;
});

onMounted(() => {
  checkTabItem();
});

// Tab指示下划线跟踪TabItem位置
const indicatorTraceTab = () => {
  const {width, left} = selectedItem.value!.getBoundingClientRect();
  const {left: containerLeft} = container.value!.getBoundingClientRect();
  const leftPos = left - containerLeft;
  indicator.value!.style.width = `${width}px`;
  indicator.value!.style.transform = `translate3D(${leftPos}px, 0, 0)`;
};

// 追踪 变更,执行回调
watchEffect(() => {
  indicatorTraceTab();
});

</script>

<template>
  <div class="vue-tabs">
    <nav class="vue-tabs-nav" ref="container">
      <div v-for="(title, index) in titles"
           :key="index"
           :ref="(el) => { if (el && (title === selected)) selectedItem = el }"
           :class="{selected: title === selected}"
           class="vue-tabs-nav-item"
           @click="select(title)">
        {{ title }}
      </div>
      <div class="vue-tabs-nav-indicator"
           ref="indicator">
      </div>
    </nav>
    <div class="vue-tabs-content">
      <keep-alive>
        <component :is="getCurrentTab"
                   :key="getCurrentTitle"
                   class="vue-tabs-content-item">
        </component>
      </keep-alive>
    </div>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Tabs'
};
</script>

<style lang="scss">
@import 'styles/var';

.vue-tabs {
  &-nav {
    display: flex;
    color: $tab-color;
    border-bottom: 1px solid $border-color;
    position: relative;

    &-item {
      padding: 8px 0;
      margin: 0 16px;
      cursor: pointer;

      &:first-child {
        margin-left: 0;
      }

      &.selected {
        color: $blue-underscore;
      }
    }

    &-indicator {
      position: absolute;
      height: 3px;
      background-color: $blue-underscore;
      left: 0;
      bottom: -1px;
      transition: all .25s;
    }

  }

  &-content {
    padding: 8px 0;
  }
}
</style>

显示或隐藏代码

Demo.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<script setup lang="ts">
import Prism from 'prismjs';
import 'prismjs/themes/prism-okaidia.css';
import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
import 'prismjs/plugins/line-numbers/prism-line-numbers.min.js';
import VlButton from '@/lib/VlButton.vue';
import {computed, ref} from 'vue';

const codeVisible = ref(false);
const props = defineProps({
  component: Object
});
const toggle = () => {
  codeVisible.value = !codeVisible.value;
};
const html = computed(() => {
  return Prism.highlight(props.component?.__sourceCode, Prism.languages.html, 'html');
});

</script>

<template>
  <div class="vue-demo">
    <h2>{{ component.__sourceCodeTitle }}</h2>
    <div class="vue-demo-component">
      <keep-alive>
        <component :is="component"></component>
      </keep-alive>
    </div>
    <div class="vue-demo-actions">
      <VlButton @click="toggle">{{ codeVisible ? '隐藏代码' : '查看代码' }}</VlButton>
    </div>
    <div class="vue-demo-code" v-show="codeVisible">
      <pre class="language-html">
        <code class="language-html" v-html="html"></code>
      </pre>
    </div>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Demo'
};
</script>

<style lang="scss" scoped>
$border-color: #d9d9d9;

.vue-demo {
  border: 1px solid $border-color;
  margin: 16px 0 32px;

  > h2 {
    font-size: 20px;
    padding: 8px 16px;
    border-bottom: 1px solid $border-color;
  }

  &-component {
    padding: 16px;
  }

  &-actions {
    padding: 8px 16px;
    border-top: 1px dashed $border-color;
  }

  &-code {
    padding: 8px 16px;
    border-top: 1px dashed $border-color;

    > pre {
      line-height: 1.1;
      font-family: Consolas, 'Courier New', Courier, monospace;
      margin: 0;
      padding-left: 70px;

      @media(max-width: 500px) {
        padding-left: 5px;
      }

      > code {
        &:first-child {
          margin-left: -70px;
        }
      }
    }
  }
}
</style>

  • <VlButton @click="toggle">{{ codeVisible ? '隐藏代码' : '查看代码' }}</VlButton>

修复 watchEffect

排除所有可能性,剩下的就是真相

VlTabs.vue的实现思路

  • 当用户点击vue-tabs-nav-item时,执行select(title)
  • select(title)会向外发布自定义事件emits('update:selected', title);
    • title传到外部,让外部组件更新自己的外部属性selected
  • selected更新了,selectedItem也更新
    • selectedItem = ref<HTMLDivElement>(div)
    • <div v-for="(title, index) in titleListOfDefaultSlots" ... :ref="(el) => { if (el && (title === selected)) selectedItem = el }" @click="select(title)">...</div>
  • 如果当selected更新了,selectedItem未更新,就会出现bug
    • watchEffect追踪的依赖是selectedItem
    • selected变了,去渲染DOM
    • RC watchEffect post 在渲染DOM后,selectedItem变了
    • 正式版 watchEffect pre 在渲染DOM前,selectedItem没变
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
...
// Tab指示下划线跟踪TabItem位置
const indicatorTraceTab = () => {
  const {width, left} = selectedItem.value!.getBoundingClientRect();
  const {left: containerLeft} = container.value!.getBoundingClientRect();
  const leftPos = left - containerLeft;
  indicator.value!.style.width = `${width}px`;
  indicator.value!.style.transform = `translate3D(${leftPos}px, 0, 0)`;
};

// 追踪 变更,执行回调
watchEffect(() => {
  indicatorTraceTab();
});

...

watchEffect

  • watchEffect依赖的selectedItemcontainerindicator都不变的话,就不会执行watchEffect回调逻辑
  • 需配置第二个参数, {flush: post}表示等到渲染DOM后再执行

再次完善官网

install.md

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 安装

打开终端运行下列命令:

\\```
npm install vue3-demo-ui-1
\\```

或

\\```
yarn add vue3-demo-ui-1
\\```

下一节:[开始使用](#/docs/get-started)

getStarted.md

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 开始使用

请先[安装](#/docs/install)本组件库。

然后在你的代码中写入下面的代码:

!```
import * as vueel3 from 'vue3-demo-ui-1'
import 'vue3-demo-ui-1/dist/lib/style.css'
const {VlButton, VlTabs, VlSwitch, VlDialog, useDialog} = vueel3
!```

就可以使用我提供的组件了。

## Vue 单文件组件 使用时不必使用 ts 语法

代码示例:

!```
<template>
  <div>
    <VlButton>按钮</VlButton>
  </div>
</template>

<script>
import * as vueel3 from 'vue3-demo-ui-1'
import 'vue3-demo-ui-1/dist/lib/style.css'
const {VlButton, VlTabs, VlSwitch, VlDialog, useDialog} = vueel3

export default {
  components: {VlButton, VlTabs, VlSwitch, VlDialog, useDialog}
}
</script>
!```


总结

这样就完成了

  • 部署官网到 GitHub
  • 发布官网页面
  • 部署到码云
  • 脚本化部署
  • 使用 GitHub Actions 持续集成
  • 发布组件库至 npm

初步完成的轮子

把项目加到简历,Vue 2 如何升级到 Vue 3

更多

  • 添加其他组件
    • Popover
    • Datepicker
    • Tooltip
  • 使用 JSXTSX
  • 使用 defineComponent
    • 支持泛型,能更好地与 TypeScript 配合使用
  • 通读Vue3 中文文档