如何将代码打包成 npm 包并通过 GitHub 私有仓库发布使用

205 阅读7分钟

将自己的代码封装成 npm 包并分享给他人使用,是开发者常用的协作方式。但 npm官方: www.npmjs.com/ 的私有包是付费的,对于自己使用和小团队使用就不太友好了。所以这里输出一个免费私有包替代方案:并通过 GitHub 免费发布和使用(免费额度有限)。

1. 准备工作

首先,确保你的开发环境中已经安装了:

  • Node.js (推荐 v14 及以上版本)
  • npm (通常随 Node.js 一起安装)
  • Git
  • 一个 GitHub 账号

检查安装情况:

node -v 
npm -v 
git --version

image.png

2. 创建项目结构(如果已经有对应项目了,可以直接跳过)

这里以 VUE3+TS+ElementUI为例:

2.1. 新建项目文件夹并初始化

2.1.1. 创建项目的配置文件:

注意这里的name配置格式为 @github用户名/包名。我的用户名是 carrieryu,打包的仓库是 yld-ui

package.json

{
    "name": "@carrieryu/yld-ui",
    "version": "1.0.0",
    "description": "基于Vue3+TypeScript的通用组件库",
    "main": "dist/index.js",
    "module": "dist/index.esm.js",
    "types": "dist/index.d.ts",
    "bin": {
        "yld-ui": "./cli/index.js"
    },
    "files": [
        "dist",
        "cli"
    ],
    "scripts": {
        "dev": "vite --config vite.dev.config.ts",
        "build": "vite build",
        "build:lib": "vite build --config vite.lib.config.ts",
        "preview": "vite preview",
        "type-check": "vue-tsc --noEmit",
        "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
        "cli": "node cli/index.js"
    },
    "keywords": [
        "vue3",
        "typescript",
        "component-library",
        "ui-components",
        "element-ui",
        "cli"
    ],
    "author": "Your Name",
    "license": "MIT",
    "devDependencies": {
        "@types/node": "^20.19.10",
        "@vitejs/plugin-vue": "^4.5.0",
        "@vue/tsconfig": "^0.4.0",
        "typescript": "~5.2.0",
        "vite": "^5.0.0",
        "vite-plugin-dts": "^3.7.0",
        "vue-tsc": "^1.8.0"
    },
    "dependencies": {
        "element-plus": "^2.10.6",
        "vue": "^3.5.18"
    }
}

2.1.2. TS配置文件

tsconfig.json

{
  "include": [
    "src/**/*",
    "src/**/*.vue",
    "src/**/*.d.ts",
    "examples/**/*",
    "examples/**/*.vue"
  ],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist",
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "node",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noFallthroughCasesInSwitch": true,
    "types": ["vite/client", "element-plus/global"]
  }
}

2.1.3. TypeScript 环境声明文件

src\env.d.ts

/// <reference types="vite/client" />
/// <reference types="element-plus/global" />

declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}

// 扩展 Vue 全局类型
declare module '@vue/runtime-core' {
    export interface GlobalComponents {
        ElButton: typeof import('element-plus')['ElButton']
        ElInput: typeof import('element-plus')['ElInput']
        ElMessage: typeof import('element-plus')['ElMessage']
    }
} 

2.1.4. Vite 配置文件

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3000,
    open: true
  },
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'YldUI',
      fileName: (format) => `yld-ui.${format}.js`
    },
    rollupOptions: {
      external: ['vue', 'element-plus'],
      output: {
        globals: {
          vue: 'Vue',
          'element-plus': 'ElementPlus'
        }
      }
    }
  }
}) 

vite.dev.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  root: 'examples', // 设置根目录为examples
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3000,
    open: true
  },
  build: {
    outDir: '../dist-examples',
    emptyOutDir: true
  }
}) 

vite.lib.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'

export default defineConfig({
    plugins: [
        vue(),
        dts({
            insertTypesEntry: true,
            include: ['src/**/*'],
            exclude: ['examples/**/*']
        })
    ],
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src')
        }
    },
    build: {
        lib: {
            entry: resolve(__dirname, 'src/index.ts'),
            name: 'YldUI',
            fileName: (format) => `yld-ui.${format}.js`
        },
        rollupOptions: {
            external: ['vue', 'element-plus'],
            output: {
                globals: {
                    vue: 'Vue',
                    'element-plus': 'ElementPlus'
                }
            }
        },
        sourcemap: true,
        minify: false
    }
}) 

2.1.5. 组件库入口文件

src\index.ts

import { ElButton, ElInput, ElMessage } from 'element-plus'
// Vue 3 类型定义
type VueApp = {
    component: (name: string, component: any) => void;
    use: (plugin: any) => void;
}

// 导入样式
import './style/index.css'

// 导入组件
import YldButton from './components/YldButton/index.vue'
import YldCard from './components/YldCard/index.vue'

// 组件列表
const components = [
    YldButton,
    YldCard
]

// 安装函数
const install = (app: VueApp): void => {
    // 注册组件
    components.forEach(component => {
        const componentName = component.name || component.__name || 'UnknownComponent'
        app.component(componentName, component)
    })

    // 注册Element Plus组件
    app.use(ElButton)
    app.use(ElInput)
    app.use(ElMessage)
}

// 自动安装(移除window.Vue检查,因为Vue3不支持这种方式)
// if (typeof window !== 'undefined' && window.Vue) {
//   install(window.Vue)
// }

export default {
    install,
    version: '1.0.0'
}

// 导出组件
export {
    YldButton,
    YldCard
}

// 导出类型
export type { YldButtonProps, YldCardProps } from './types' 

2.1.6. 类型定义文件

src\types\index.ts

// 按钮组件属性类型
export interface YldButtonProps {
    type?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
    size?: 'large' | 'default' | 'small'
    disabled?: boolean
    loading?: boolean
    round?: boolean
    circle?: boolean
    icon?: string
    text?: string
}

// 卡片组件属性类型
export interface YldCardProps {
    header?: string
    shadow?: 'always' | 'hover' | 'never'
    bodyStyle?: Record<string, any>
    headerStyle?: Record<string, any>
}

// 全局组件类型
export interface GlobalComponents {
    YldButton: typeof import('../components/YldButton/index.vue').default
    YldCard: typeof import('../components/YldCard/index.vue').default
} 

src\types\vue-shims.d.ts

declare module '*.vue' {
    import { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}

declare module '@vue/runtime-core' {
    export interface GlobalComponents {
        ElButton: typeof import('element-plus')['ElButton']
        ElInput: typeof import('element-plus')['ElInput']
        ElMessage: typeof import('element-plus')['ElMessage']
    }
} 

src\types\vue.d.ts

declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}

// 扩展Vue组件类型
declare module '@vue/runtime-core' {
    export interface GlobalComponents {
        YldButton: typeof import('../components/YldButton/index.vue').default
        YldCard: typeof import('../components/YldCard/index.vue').default
    }
} 

2.1.7. 示例组件

src\components\YldButton\index.vue

<template>
  <el-button
    :type="type"
    :size="size"
    :disabled="disabled"
    :loading="loading"
    :round="round"
    :circle="circle"
    :icon="icon"
    @click="handleClick"
  >
    <slot>{{ text }}</slot>
  </el-button>
</template>

<script setup lang="ts">
import { ElButton } from "element-plus";
import type { YldButtonProps } from "@/types";

// 定义组件名称
defineOptions({
  name: "YldButton",
});

// 定义props
interface Props extends YldButtonProps {
  onClick?: (event: MouseEvent) => void;
}

const props = withDefaults(defineProps<Props>(), {
  type: "default",
  size: "default",
  disabled: false,
  loading: false,
  round: false,
  circle: false,
  text: "按钮",
}) as Props;

// 定义emits
const emit = defineEmits<{
  click: [event: MouseEvent];
}>();

// 点击事件处理
const handleClick = (event: MouseEvent) => {
  if (props.onClick) {
    props.onClick(event);
  }
  emit("click", event);
};
</script>

<style scoped>
/* 自定义样式 */
.yld-button {
  transition: all 0.3s ease;
}

.yld-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
</style>

src\components\YldCard

<template>
  <el-card
    :header="header"
    :shadow="shadow"
    :body-style="bodyStyle"
    :header-style="headerStyle"
    class="yld-card"
  >
    <template #header v-if="$slots.header">
      <slot name="header"></slot>
    </template>

    <div class="yld-card__body">
      <slot></slot>
    </div>

    <template #footer v-if="$slots.footer">
      <slot name="footer"></slot>
    </template>
  </el-card>
</template>

<script setup lang="ts">
import { ElCard } from "element-plus";
import type { YldCardProps } from "@/types";

// 定义组件名称
defineOptions({
  name: "YldCard",
});

// 定义props
const props = withDefaults(defineProps<YldCardProps>(), {
  header: "",
  shadow: "hover",
  bodyStyle: () => ({}),
  headerStyle: () => ({}),
});

// 使用props变量避免未使用警告
const { header, shadow, bodyStyle, headerStyle } = props;
</script>

<style scoped>
.yld-card {
  border-radius: 8px;
  transition: all 0.3s ease;
}

.yld-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}

.yld-card__body {
  padding: 16px;
}

:deep(.el-card__header) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: 8px 8px 0 0;
  padding: 16px 20px;
}

:deep(.el-card__body) {
  padding: 20px;
}
</style>

2.1.8. 示例页面:负责展示组件

examples\App.vue

<template>
  <div class="app">
    <header class="app-header">
      <h1>YldUI 组件库示例</h1>
      <p>基于 Vue3 + TypeScript + Element Plus 的通用组件库</p>
    </header>

    <main class="app-main">
      <!-- 按钮组件示例 -->
      <section class="component-section">
        <h2>YldButton 按钮组件</h2>
        <div class="component-demo">
          <div class="demo-row">
            <yld-button type="primary">主要按钮</yld-button>
            <yld-button type="success">成功按钮</yld-button>
            <yld-button type="warning">警告按钮</yld-button>
            <yld-button type="danger">危险按钮</yld-button>
          </div>

          <div class="demo-row">
            <yld-button size="large">大按钮</yld-button>
            <yld-button size="default">默认按钮</yld-button>
            <yld-button size="small">小按钮</yld-button>
          </div>

          <div class="demo-row">
            <yld-button round>圆角按钮</yld-button>
            <yld-button circle icon="el-icon-star">圆形按钮</yld-button>
            <yld-button loading>加载中</yld-button>
          </div>
        </div>
      </section>

      <!-- 卡片组件示例 -->
      <section class="component-section">
        <h2>YldCard 卡片组件</h2>
        <div class="component-demo">
          <div class="card-grid">
            <yld-card header="基础卡片" shadow="hover">
              <p>这是一个基础的卡片组件,支持自定义头部、内容和底部。</p>
              <template #footer>
                <yld-button type="primary" size="small">操作按钮</yld-button>
              </template>
            </yld-card>

            <yld-card shadow="always">
              <template #header>
                <div style="display: flex; align-items: center">
                  <span>自定义头部</span>
                  <yld-button
                    type="text"
                    size="small"
                    style="margin-left: auto"
                  >
                    更多
                  </yld-button>
                </div>
              </template>
              <p>支持自定义头部内容的卡片组件。</p>
            </yld-card>
          </div>
        </div>
      </section>
    </main>
  </div>
</template>

<script setup lang="ts">
// 直接导入组件,不使用@/components路径
import YldButton from "../src/components/YldButton/index.vue";
import YldCard from "../src/components/YldCard/index.vue";
</script>

<style scoped>
.app {
  min-height: 100vh;
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}

.app-header {
  text-align: center;
  padding: 40px 20px;
  background: white;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.app-header h1 {
  margin: 0 0 10px 0;
  color: #2c3e50;
  font-size: 2.5rem;
}

.app-header p {
  margin: 0;
  color: #7f8c8d;
  font-size: 1.1rem;
}

.app-main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 20px;
}

.component-section {
  background: white;
  border-radius: 12px;
  padding: 30px;
  margin-bottom: 30px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}

.component-section h2 {
  margin: 0 0 20px 0;
  color: #2c3e50;
  font-size: 1.8rem;
  border-bottom: 2px solid #3498db;
  padding-bottom: 10px;
}

.component-demo {
  padding: 20px 0;
}

.demo-row {
  display: flex;
  gap: 12px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

.card-grid .yld-card {
  height: fit-content;
}
</style>

示例页面入口文件: examples\main.ts

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import YldUI from '../src/index'

const app = createApp(App)

// 使用Element Plus
app.use(ElementPlus)

// 使用YldUI组件库
app.use(YldUI)

app.mount('#app') 

HTML 模板 examples\index.html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>YldUI 组件库示例</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/main.ts"></script>
  </body>
</html>

2.1.9. 样式文件:需要我们添加专门用于构建样式的脚本,那样就能自动构建css文件了

package.json

"dev": "npm run build:style && vite --config vite.dev.config.ts",
"dev:watch": "concurrently "npm run watch:style" "vite --config vite.dev.config.ts"",
"build": "npm run build:style && vite build",
"build:lib": "npm run build:style && vite build --config vite.lib.config.ts",
"build:style": "npx sass src/style/index.scss:src/style/index.css --style compressed",
"watch:style": "npx sass src/style/index.scss:src/style/index.css --watch",

npm install --save-dev concurrently

  1. 修改vite.dev.config.ts文件
  css: {
        preprocessorOptions: {
            scss: {
                additionalData: `@import "${resolve(__dirname, 'src/style/index.scss')}";`
            }
        }
    },

添加 scss 文件

/* YldUI 组件库样式 */

// 导入Element Plus样式
@import 'element-plus/dist/index.css';

// 自定义变量
:root {
  --yld-primary-color: #409eff;
  --yld-success-color: #67c23a;
  --yld-warning-color: #e6a23c;
  --yld-danger-color: #f56c6c;
  --yld-info-color: #909399;
  
  --yld-border-radius: 8px;
  --yld-box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  --yld-transition: all 0.3s ease;
}

// 全局样式
.yld-ui {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

// 按钮组件样式
.yld-button {
  transition: var(--yld-transition);
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: var(--yld-box-shadow);
  }
}

// 卡片组件样式
.yld-card {
  border-radius: var(--yld-border-radius);
  transition: var(--yld-transition);
  
  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
  }
  
  .el-card__header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border-radius: var(--yld-border-radius) var(--yld-border-radius) 0 0;
    padding: 16px 20px;
  }
  
  .el-card__body {
    padding: 20px;
  }
} 

可用的样式构建命令:

  1. npm run build:style - 一次性构建 CSS 文件
  2. npm run watch:style - 监听 SCSS 文件变化,自动重新构建
  3. npm run dev - 开发服务器
  4. npm run build:lib - 打包

等等~具体看package scripts 配置。

执行以上其中一个命令后,自动创建了 index.css 和 index.css.map 文件

src\style\index.css

/* YldUI 组件库样式 */

/* 自定义变量 */
:root {
  --yld-primary-color: #409eff;
  --yld-success-color: #67c23a;
  --yld-warning-color: #e6a23c;
  --yld-danger-color: #f56c6c;
  --yld-info-color: #909399;

  --yld-border-radius: 8px;
  --yld-box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  --yld-transition: all 0.3s ease;
}

/* 全局样式 */
.yld-ui {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* 按钮组件样式 */
.yld-button {
  transition: var(--yld-transition);
}

.yld-button:hover {
  transform: translateY(-2px);
  box-shadow: var(--yld-box-shadow);
}

/* 卡片组件样式 */
.yld-card {
  border-radius: var(--yld-border-radius);
  transition: var(--yld-transition);
}

.yld-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}

.yld-card .el-card__header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: var(--yld-border-radius) var(--yld-border-radius) 0 0;
  padding: 16px 20px;
}

.yld-card .el-card__body {
  padding: 20px;
} 

src\style\index.css.map

{"version":3,"sourceRoot":"","sources":["index.scss"],"names":[],"mappings":"AAGQ,qCAGR,MACE,6BACA,6BACA,6BACA,4BACA,0BAEA,yBACA,gDACA,gCAIF,QACE,iGAIF,YACE,iCAEA,kBACE,2BACA,iCAKJ,UACE,uCACA,iCAEA,gBACE,2BACA,qCAGF,2BACE,6DACA,WACA,oEACA,kBAGF,yBACE","file":"index.css"}

2.1.10. README.md

# YldUI 组件库

基于 Vue3 + TypeScript + Element Plus 的通用组件库

## 特性

- 🚀 基于 Vue3 Composition API
- 📝 完整的 TypeScript 支持
- 🎨 基于 Element Plus 设计系统
- 📦 支持按需引入
- 🔧 支持自定义主题
- 📱 响应式设计

## 安装

### NPM 安装

```bash
npm install yld-ui
```

### Yarn 安装

```bash
yarn add yld-ui
```

### PNPM 安装

```bash
pnpm add yld-ui
```

## 使用

### 完整引入

```typescript
import { createApp } from "vue";
import YldUI from "yld-ui";
import "yld-ui/dist/style.css";

const app = createApp(App);
app.use(YldUI);
app.mount("#app");
```

### 按需引入

```typescript
import { createApp } from "vue";
import { YldButton, YldCard } from "yld-ui";
import "yld-ui/dist/style.css";

const app = createApp(App);
app.component("YldButton", YldButton);
app.component("YldCard", YldCard);
app.mount("#app");
```

## 组件

### YldButton 按钮

增强的按钮组件,基于 Element PlusElButton

```vue
<template>
  <yld-button type="primary" size="large" round> 主要按钮 </yld-button>
</template>
```

#### Props

| 参数     | 说明       | 类型    | 可选值                                      | 默认值  |
| -------- | ---------- | ------- | ------------------------------------------- | ------- |
| type     | 类型       | string  | primary / success / warning / danger / info | default |
| size     | 尺寸       | string  | large / default / small                     | default |
| disabled | 是否禁用   | boolean | —                                           | false   |
| loading  | 是否加载中 | boolean | —                                           | false   |
| round    | 是否圆角   | boolean | —                                           | false   |
| circle   | 是否圆形   | boolean | —                                           | false   |
| icon     | 图标类名   | string  | —                                           | —       |
| text     | 按钮文本   | string  | —                                           | 按钮    |

### YldCard 卡片

增强的卡片组件,基于 Element PlusElCard

```vue
<template>
  <yld-card header="卡片标题" shadow="hover">
    <p>卡片内容</p>
    <template #footer>
      <yld-button type="primary">操作按钮</yld-button>
    </template>
  </yld-card>
</template>
```

#### Props

| 参数        | 说明         | 类型   | 可选值                 | 默认值 |
| ----------- | ------------ | ------ | ---------------------- | ------ |
| header      | 卡片标题     | string | —                      | —      |
| shadow      | 阴影效果     | string | always / hover / never | hover  |
| bodyStyle   | 内容区域样式 | object | —                      | {}     |
| headerStyle | 头部区域样式 | object | —                      | {}     |

## 开发

### 安装依赖

```bash
npm install
```

### 开发模式

```bash
npm run dev
```

### 构建库

```bash
npm run build:lib
```

### 类型检查

```bash
npm run type-check
```

## 项目结构

```
yld-ui/
├── src/                    # 源码目录
│   ├── components/         # 组件目录
│   │   ├── YldButton/     # 按钮组件
│   │   └── YldCard/       # 卡片组件
│   ├── types/             # 类型定义
│   └── index.ts           # 入口文件
├── examples/               # 示例应用
├── dist/                   # 构建输出
├── package.json            # 包配置
├── tsconfig.json           # TypeScript配置
├── vite.config.ts          # Vite配置
└── README.md               # 说明文档
```

## 许可证

MIT License

## 贡献

欢迎提交 IssuePull Request

2.1.11. .gitignore文件

# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Build outputs
dist/
build/
*.tsbuildinfo

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# nyc test coverage
.nyc_output

# Dependency directories
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt

# Storybook build outputs
.out
.storybook-out

# Temporary folders
tmp/
temp/ 

2.2. 项目结构

yld-ui/
├── src/                    # 源码目录
│   ├── components/         # 组件目录
│   │   ├── YldButton/     # 按钮组件
│   │   └── YldCard/       # 卡片组件
│   ├── types/             # 类型定义
│   └── index.ts           # 入口文件
├── examples/               # 示例应用
│   ├── App.vue            # 示例页面
│   ├── main.ts            # 示例入口
│   └── index.html         # HTML模板
├── package.json            # 包配置
├── tsconfig.json           # TypeScript配置
├── vite.config.ts          # Vite配置
├── .gitignore              # Git忽略文件
└── README.md               # 说明文档

2.2.1. 主要特性

  • Vue3 + TypeScript: 使用最新的Vue3 Composition API和完整的TS支持
  • Element Plus: 基于Element Plus设计系统,提供美观的UI组件
  • 组件化: 创建了YldButton和YldCard两个示例组件
  • 类型安全: 完整的TypeScript类型定义
  • 构建系统: 支持开发、构建和类型检查
  • CLI工具: 提供项目初始化和组件生成功能
  • 样式系统: 支持CSS变量和主题定制

2.2.2. 创建的组件

  1. YldButton 按钮组件
  • 支持多种类型:primary、success、warning、danger、info
  • 支持多种尺寸:large、default、small
  • 支持圆角、圆形、加载状态等
  • 基于Element Plus的ElButton,增强用户体验
  1. YldCard 卡片组件
  • 支持自定义头部、内容和底部
  • 多种阴影效果:always、hover、never
  • 美观的渐变头部设计
  • 响应式布局

2.3. 开发流程

2.3.1. 开发模式

npm i         	   # 安装依赖
npm run dev          # 启动开发服务器
npm run build:lib    # 构建组件库
npm run type-check   # 类型检查
  1. npm i

先安装好依赖 npm i,如果安装完成后发现部分文件有ts提示,例如:element-plus 模块没有安装。问题可能是 TypeScript 编译器缓存或者需要重启 TypeScript 服务。

重启 TypeScript 服务:在 VS Code 或 Cursor 中按 Ctrl+Shift+P,输入 "TypeScript: Restart TS Server"。

或者关闭 VS Code 或 Cursor 程序重新打开。

  1. npm run dev

2.3.2. CLI 工具

npm run cli init     # 初始化项目
npm run cli add      # 添加组件
npm run cli help     # 查看帮助

2.4. 在其他项目中的使用

  1. npm 安装

确保设定了正确的 registry (如果前面能发布成功,这一步应该是没问题的)

到自己的paackages下可以看到对应安装命令

npm install @carrieryu/kjy-ui@1.0.0

  1. 完整引入
import { createApp } from 'vue'
import YldUI from 'yld-ui'
import 'yld-ui/dist/style.css'

const app = createApp(App)
app.use(YldUI)
app.mount('#app')
  1. 按需引入
import { YldButton, YldCard } from 'yld-ui'

3. 创建 GitHub 仓库并上传代码

  1. 在 GitHub 上创建一个新仓库
  • 登录 GitHub
  • 点击右上角 "+" 图标,选择 "New repository"

  • 填写仓库名称 (建议与包名一致)
  • 选择 "Private"
  • 点击 "Create repository"
  1. 将本地代码推送到 GitHub
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/carrieryu/yld-ui.git
git push -u origin main

4. 设置私有仓库访问权限

记得 package.json 中名称对应正确

到github中创建一个 Personal access token(个人访问令牌),注意访问令牌只可查看一次,记得复制保存后再关闭

  1. 进入设置 Settings

  2. 点击左侧菜单最底下的 Developer settings

  1. 点击左侧菜单最底下的 Tokens(classic),新建一个 token。 验证密码

  2. 填写基本信息,配置权限。

因为我自己用,所以我开启了全部权限,并且note填写为:yldui-admin,方便自己区分权限级别。

  1. 复制token,等下要用到

刷新后

  1. 可以在项目中添加 .npmrc 文件保存token
registry=https://npm.pkg.github.com/你的用户名
//npm.pkg.github.com/:_authToken=前面复制的token(个人访问令牌)

  1. 执行打包命令 npm run build:lib,然后执行 npm publish 发布。可以看到 dist 文件夹就被打包发布到线上了。

  2. 至此发布成功,刷新下 github packages页签下内容

4.1. 问题1:npm publish 命令行显示发布成功了,但是github packages 列表没有对应包

  1. 检查一下你当前的 npm registry 配置:

如果显示的是 registry.npmjs.org/,说明你发布到了公共 npm,而不是 GitHub Packages。

要发布到 GitHub Packages,需要:

  • 设置正确的 registry:
npm config set @你的用户名:registry https://npm.pkg.github.com

比如我的用户名是 carrieryu

npm config set @carrieryu:registry https://npm.pkg.github.com
  • 确保 .npmrc 文件配置正确:
echo "@carrieryu:registry=https://npm.pkg.github.com" > .npmrc
echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> .npmrc
  1. 检查是否发到了公共包

访问:www.npmjs.com/package/@ca…

www.npmjs.com/package/@你的…

如果不小心发布到了错误的 registry,可以执行以下命令进行删除:

npm unpublish @carrieryu/yld-ui@1.0.1
  1. 重新发布
# 设置 GitHub Packages registry
npm config set @carrieryu:registry https://npm.pkg.github.com

# 重新发布
npm publish

5. 更新流程

  1. 更新版本号:有以下3中方式,任选其一
  • 修改 package.json 中的版本号,然后重新发布
{
    "name": "@carrieryu/yld-ui",
    "version": "1.0.1",  // 从 1.0.0 改为 1.0.1
    // ... existing code ...
}
  • 使用 npm version 命令自动更新
# 推荐:更新补丁版本 (1.0.0 -> 1.0.1)
npm version patch

# 或者更新次要版本 (1.0.0 -> 1.1.0)
npm version minor

# 或者更新主要版本 (1.0.0 -> 2.0.0)
npm version major
  • 强制覆盖(不推荐)
npm publish --force
  1. 打包
npm run build:lib

3. 发布到 npm

npm publish

附录

登录 npm 官方账号:

  1. 官方注册表:全球开发者都能访问
  2. 稳定性更好:官方服务更可靠
  3. 功能完整:支持所有 npm 功能
npm login --registry=https://registry.npmjs.org/

登录 cnpm 官方账号

npm login --registry=https://registry.npmmirror.com/

查看当前使用的注册表

npm config get registry

切换注册表

# 切换到官方注册表
npm config set registry https://registry.npmjs.org/

# 切换到淘宝镜像(仅用于安装依赖)
npm config set registry https://registry.npmmirror.com/

发布到官方 npm

# 确保使用官方注册表
npm config set registry https://registry.npmjs.org/

# 登录
npm login

# 发布
npm publish