在Vue组件库中新增组件的流程+脚本实现

135 阅读1分钟

在Vue组件库中新增组件的流程 + 脚本实现

简介

要在vue组件库中新增一个组件,通常会创建这几个文件:

  • MyComponent.vue
  • index.ts
  • MyComponent.types.ts
  • MyComponent.scss

文件内容是固定的,制作一个模板用来生成它们可以减少重复的工作。

基本文件内容

MyComponent.vue

<template>
  <div class="my-component">
  </div>
</template>

<script setup lang="ts">
import { myComponentProps } from './MyComponent.types';

const props = defineProps(myComponentProps);
const emits = defineEmits<MyComponentEmits>();

</script>

index.ts

import type { App } from 'vue';
import MyComponent from './MyComponent.vue';

import './MyComponent.scss';

MyComponent.install = (app: App) => {
  app.component('PrefixMyComponent', MyComponent);
};

export { MyComponent };

MyComponent.types.ts

export const myComponentProps = {

}

export interface MyComponentEmits {

}

MyComponent.scss

可以在头部导入全局样式的文件

.my-component {

}

整理为模板

命名格式

注意到文件中出现了多种命名:

  • MyComponent upperCamel
  • my-component kebab
  • myComponent lowerCamel
  • Prefix 会遇到项目的组件需要前缀的情况,如组件名为Input,但是导出叫McInput

所以需要编写函数获取多种命名

function getNames(upperCase) {
  const kebabCase = upperCase.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
  const lowerCase = upperCase.charAt(0).toUpperCase() + upper.slice(1);
  
  return {
    upper: upperCase,
    kebab: kebabCase,
    lower: lowerCase,
  };
}

模板

将MyComponent替换为模板内容即可

MyComponent.vue -> vue.template
<template>
  <div class="{{kebab}}">
  </div>
</template>

<script setup lang="ts">
import { {{lower}}Props } from './{{upper}}.types';

const props = defineProps({{lower}}Props);
const emits = defineEmits<{{upper}}Emits>();

</script>

index.ts -> index.template

import type { App } from 'vue';
import {{upper}} from './{{upper}}.vue';

import './{{upper}}.scss';

{{upper}}.install = (app: App) => {
  app.component('{{prefix}}{{upper}}', {{upper}});
};

export { {{upper}} };

MyComponent.types.ts -> types.template

export const {{lower}}Props = {

}

export interface {{upper}}Emits {

}

MyComponent.scss -> scss.template

可以在头部导入全局样式的文件

.{{kebab}} {

}

脚本文件

这里的add-component.js文件放在项目根目录中的scripts文件夹中,模板文件的路径为root/scripts/templates/*.template,输出路径为root/src/components

import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
import { join } from 'path';

const templatesDir = join(__dirname, './templates');
const componentsDir = join(__dirname, '../src/components');

function getNames(upperCase) {
  const kebabCase = upperCase.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
  const lowerCase = upperCase.charAt(0).toUpperCase() + upper.slice(1);
  
  return {
    upper: upperCase,
    kebab: kebabCase,
    lower: lowerCase,
  };
}

function generateTemplates(upper, kebab, lower){
  const files = [
    ['vue.template', `${upper}.vue`],
    ['index.template', 'index.ts'],
    ['scss.template', `${upper}.scss`],
    ['types.template', `${upper}.types.ts`],
  ];
  
  for (const [template, target] of files){
    const src = join(templatesDir, template);
    const dst = join(componentsDir, target);
    
    const content = readFileSync(src, 'utf8')
      .replace(/\{\{pascal\}\}/g, pascal)
      .replace(/\{\{kebab\}\}/g, kebab)
      .replace(/\{\{lower\}\}/g, lower);
    
    writeFileSync(dst, content, 'utf8');
    console.log(`创建文件${dst}`);
  }
}

function createComponent(componentName){
  const {upper, kebab, lower} = getNames(componentName);
  
  const outputDir = join(componentsDir, upper);
  
  if (existsSync(outputDir)) {
    console.error(`组件${upper}已存在!`);
    process.exit(1);
  }
  
  mkdirSync(outputDir, { recursive: true });
  
  generateTemplates(upper, kebab, lower);
}

function main(){
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.error('❌ 请提供组件名称!');
    process.exit(1);
  }

  const componentName = args[0];
  
  createComponent(componentName);
}

main();

全局使用

不太可能为所有项目都配置一个这样的脚本和模板文件夹,毕竟有的项目不是自己的,所以在全局中配置一个这样的脚本很有必要。编写自己的javascript脚本库辅助开发讲述了如何配置项目,下面给出全局的创建vue组件的代码部分。

./index.js

#!/usr/bin/env node

import yargs from 'yargs/yargs';
import { hideBin } from 'yargs/helpers';

yargs(hideBin(process.argv))
  .command(
    'add-vue-component <componentName>',
    'Create a new Vue component',
    (yargs) => {
      return yargs.positional('componentName', {
        describe: 'Name of the Vue component',
        type: 'string',
      });
    },
    async (argv) => {
      const { default: createVueComponent } = await import('./create-vue-component/index.js');
      createVueComponent(argv.componentName, process.cwd());
    }
  )
  .alias('add-vue-component', 'vc')
  .demandCommand(1, 'You need at least one command before moving on')
  .help()
  .argv;

./create-vue-component/index.js

import fs from 'fs';
import path from 'path';
import getVueComponentTemplate from './template.js';

/**
 * Creates a Vue component file in a specified directory.
 * 
 * @param {string} componentName - The name of the component (e.g., 'my-component').
 * @param {string} targetDir - The directory where the component should be created.
 */
function createVueComponent(componentName, targetDir) {
  if (!componentName) {
    console.error('Error: Component name is required.');
    process.exit(1);
  }

  const componentDir = path.join(targetDir, componentName);
  const componentFilePath = path.join(componentDir, 'index.vue');

  if (fs.existsSync(componentDir)) {
    console.error(`Error: Directory '${componentDir}' already exists.`);
    return;
  }

  try {
    fs.mkdirSync(componentDir, { recursive: true });

    const componentTemplate = getVueComponentTemplate(componentName);

    fs.writeFileSync(componentFilePath, componentTemplate);
    console.log(`Successfully created component '${componentName}' at: ${componentFilePath}`);

  } catch (error) {
    console.error(`Error creating component '${componentName}':`, error);
    process.exit(1);
  }
}

export default createVueComponent;

./create-vue-component/template.js

/**
 * Generates the content for a new Vue component file.
 * @param {string} componentName - The name of the component.
 * @returns {string} The Vue component template string.
 */
function getVueComponentTemplate(componentName) {
  return `<template>
  <div class="${componentName}">
    <h1>${componentName} Component</h1>
  </div>
</template>

<script>
export default {
  name: '${componentName}',
  props: {},
  data() {
    return {};
  },
  mounted() {},
  methods: {}
};
</script>

<style scoped>
.${componentName} {
  /* Add your component styles here */
}
</style>
`;
}

export default getVueComponentTemplate;

./create-vue-component/templates/

文件夹中的模板内容与上一节中的模板内容一样