Plop 简介

实战:用 Plop 批量生成 Vue 组件

通过一个完整的 Vue 组件生成案例,把 prompts、actions、模板语法和 helpers 串联起来。

🎯 引言

学完这篇文章,你将通过一个完整的实战案例,把前面学到的 Plop 知识串起来。我们会实现一个“Vue 组件生成器”,它可以根据你的选择,自动生成组件文件、样式文件和测试文件。


🧱 实战目标

我们要实现一个生成器,运行后能够:

  1. 询问组件名称。
  2. 询问是否需要 <script setup>
  3. 询问是否需要样式文件。
  4. 询问是否需要测试文件。
  5. 根据回答生成对应的文件。

✨ 项目结构

完成后的项目结构如下:

plop-demo/
├── plopfile.mjs
├── plop-templates/
│   ├── component.hbs
│   ├── component.scss.hbs
│   └── component.test.hbs
└── src/
    ├── components/
    └── components/__tests__/

🚀 第一步:编写 plopfile.mjs

plopfile.mjs
export default function (plop) {
    plop.setHelper('now', function () {
        const date = new Date();
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        return `${year}-${month}-${day}`;
    });

    plop.setGenerator('vue-component', {
        description: '创建一个完整的 Vue 组件',
        prompts: [
            {
                type: 'input',
                name: 'name',
                message: '请输入组件名称(英文大驼峰)',
                validate: function (value) {
                    if (value.trim() === '') {
                        return '组件名称不能为空';
                    }
                    if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) {
                        return '组件名称必须以英文大写字母开头';
                    }
                    return true;
                }
            },
            {
                type: 'confirm',
                name: 'useSetup',
                message: '是否使用 <script setup> 语法?',
                default: true
            },
            {
                type: 'confirm',
                name: 'hasStyle',
                message: '是否需要生成样式文件?',
                default: true
            },
            {
                type: 'confirm',
                name: 'hasTest',
                message: '是否需要生成测试文件?',
                default: false
            }
        ],
        actions: [
            {
                type: 'add',
                path: 'src/components/{{pascalCase name}}.vue',
                templateFile: 'plop-templates/component.hbs'
            },
            {
                type: 'add',
                path: 'src/components/__tests__/{{pascalCase name}}.test.js',
                templateFile: 'plop-templates/component.test.hbs',
                skip: function (answers) {
                    if (!answers.hasTest) {
                        return '跳过测试文件生成';
                    }
                }
            },
            {
                type: 'add',
                path: 'src/components/{{pascalCase name}}.scss',
                templateFile: 'plop-templates/component.scss.hbs',
                skip: function (answers) {
                    if (!answers.hasStyle) {
                        return '跳过样式文件生成';
                    }
                }
            }
        ]
    });
}

🛠 第二步:编写模板文件

组件模板

plop-templates/component.hbs
<!--
    组件:{{pascalCase name}}
    创建日期:{{now}}
-->
<template>
    <div class="{{kebabCase name}}">
        {{pascalCase name}} 组件
    </div>
</template>

{{#if useSetup}}
<script setup>
// {{pascalCase name}} 组件逻辑
</script>
{{else}}
<script>
export default {
    name: '{{pascalCase name}}'
};
</script>
{{/if}}

{{#if hasStyle}}
<style scoped>
@import './{{pascalCase name}}.scss';
</style>
{{/if}}

样式模板

plop-templates/component.scss.hbs
.{{kebabCase name}} {
    /* {{pascalCase name}} 组件样式 */
}

测试模板

plop-templates/component.test.hbs
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import {{pascalCase name}} from '../{{pascalCase name}}.vue';

describe('{{pascalCase name}}', () => {
    it('渲染成功', () => {
        const wrapper = mount({{pascalCase name}});
        expect(wrapper.exists()).toBe(true);
    });
});

💡 第三步:运行生成器

执行命令:

npx plop vue-component

按提示输入:

? 请输入组件名称(英文大驼峰) UserInfo
? 是否使用 <script setup> 语法? Yes
? 是否需要生成样式文件? Yes
? 是否需要生成测试文件? Yes

运行完成后,会生成以下文件:

src/components/
├── UserInfo.vue
├── UserInfo.scss
└── __tests__/
    └── UserInfo.test.js

⚡ 第四步:查看生成的组件

src/components/UserInfo.vue
<!--
    组件:UserInfo
    创建日期:2026-07-16
-->
<template>
    <div class="user-info">
        UserInfo 组件
    </div>
</template>

<script setup>
// UserInfo 组件逻辑
</script>

<style scoped>
@import './UserInfo.scss';
</style>
注意 <style> 里用了 @import './UserInfo.scss',这样样式文件和组件文件可以分开维护。如果你希望样式直接内联,也可以把 SCSS 内容写进组件模板里。

🧾 小节总结

  • 实战案例综合运用了 promptsactions、模板语法和自定义 helper。
  • 通过 confirm 类型的问题,可以控制是否生成可选文件。
  • 使用 skip 函数可以跳过不需要的动作,保持输出简洁。
  • 自定义 now helper 可以在生成的文件中加入创建日期。

❓ 知识问答

Q1:为什么把测试文件放在 __tests__ 目录下?

A:这是前端项目常见的测试文件组织方式,便于测试工具统一发现和收集测试用例。

Q2:如果用户选择不需要样式,为什么组件模板里还要写 {{#if hasStyle}}

A:因为 skip 只控制是否生成 .scss 文件,组件模板里 <style> 块是否出现由 hasStyle 决定。两者配合使用,才能保持生成结果一致。

Q3:<script setup> 和传统 <script> 写法有什么区别?

A:<script setup> 是 Vue 3 的语法糖,代码更简洁。如果项目使用 Vue 2,应该选择传统写法。

Q4:这个生成器可以直接用到真实项目里吗?

A:可以作为基础版本。真实项目中,你可能还需要根据团队的目录规范、测试框架、样式方案做进一步调整。


🧪 小练习

请在实战案例的基础上,增加一个新功能:

当组件名称输入 UserCard 时,自动生成一个 props 定义,包含 titledesc 两个属性。

提示:可以在 prompts 中增加一个 confirm 问题,在模板中用 {{#if}} 判断。

{{#if hasProps}}
<script setup>
const props = defineProps({
    title: String,
    desc: String
});
</script>
{{/if}}

🎉 恭喜你已经完成了 Plop 课程的学习!现在你可以在自己的项目里用 Plop 自动生成常用文件,大幅提升开发效率。