自己的vue前端脚手架搭建

1,135 阅读6分钟

起因

因为工作中遇到了需要在多个市场创建新的管理端权限管控(基于Vue全家桶)项目。
目前开源的vue脚手架一般都是使用vue-cli,但是在实际使用过程中自己已经在原有项目上编写好了很多通用且可复用的公共组件。若使用vue-cli 免不了进行ctrl+c和ctrl+v操作,十分繁琐。所以有了搭建一个专属于这类型项目的前端脚手架的想法。一番查阅资料之后选择了Yeoman-genarator来搭建脚手架项目,并成功实现且在多个市场进行复用。 一是学习了新的东西,二来确实相当方便,新建工程爽爽的。所以说也不算是教程,只是自己成功搞定generator的思路,欢迎大家指教(wuhaoxiangfau@163.com)。该脚手架已上传至npmgenerator-manage-cli),欢迎大家下载。 [git链接]:(github.com/wuhaoxiangf…)

了解Yeoman

为什么使用yeoman

Yeoman是Google的团队和外部贡献者团队合作开发的,他的目标是通过Grunt(一个用于开发任务自动化的命令行工具)和Bower(一个HTML、CSS、Javascript和图片等前端资源的包管理器)的包装为开发者创建一个易用的工作流。

Yeoman的目的不仅是要为新项目建立工作流,同时还是为了解决前端开发所面临的诸多严重问题,例如零散的依赖关系。

Yeoman主要有三部分组成:yo(脚手架工具)、grunt(构建工具)、bower(包管理器)。这三个工具是分别独立开发的,但是需要配合使用,来实现我们高效的工作流模式。

这是官网的简介,由于我习惯于使用webpack(构建工具),npm(包管理工具),所以在这个项目中是使用这个两个辅助工具搭建脚手架。

yeoman的简单使用

需要用到npm 安装yo generator-generator

npm install -g yo
npm install -g generator-generator

之后运行generator-generator来创建我们自己需要的generator的基础框架

yo generator

在一系列设置问题之后,注意脚手架的名字一定要以generator开头

? Your generator name generator-manegewhx
Your generator must be inside a folder named generator-manegewhx
I'll automatically create this folder.
? Description a demo for yeoman generator
? Project homepage url
? Author's Name whx
? Author's Email 422249525@qq.com
? Author's Homepage
? Package keywords (comma to split) generator
? Send coverage reports to coveralls No
? GitHub username or organization wuhaoxiangfau
? Which license do you want to use? (Use arrow keys)
? Which license do you want to use?
? Which license do you want to use? MIT

我们得到了generator的目录:

├── generators/
│   └── app/
│       ├── index.js
│       └── templates/
│           └── dummyfile.txt
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .eslintrc
├── .travis.yml
├── .yo-rc.json
├── package.json
├── gulpfile.js
├── README.md
├── LICENSE

开始行动

准备模板

首先我是准备了我将要生成基础工程的一些模版文件,放在 templates 文件夹中,删掉了默认生成的 dummyfile.txt。这是Yeoman默认的放模版文件的文件夹,当然你也可以通过templatePath去设置其他文件夹。这里我就不弄复杂了,详情可以参考官网的API文档,欢迎一起探讨。

因为是demo文件,我就将一些简单的模板文件放入,不再去做复杂化处理了,允许偷懒一下。

└─templates
    ├─build //webpack配置文件
    ├─config //webpack配置文件
    ├─src //资源文件
    │  ├─components //组件
    │  │  ├─basic //基础组件
    │  │  └─frame //可复用组件
    │  ├─pages //页面
    │  │  └─basic //基础页面
    │  ├─router  //vue-router
    │  ├─store //vuex
    │  └─tool //工具类
    │      ├─pubService
    │      └─resource //vue-resource
    └─static 

编写index.js

我们的generator如何生成什么样的基础工程,目录结构,是否自动安装依赖模块等等都是在这一步完成。会贴上重点代码并加以说明。


prompting块

prompting() {
    const prompts = [
      {
        type: 'input',
        name: 'projectName',
        message: 'Please input project name (manage_app):',
        default: 'manage_app'
      },
      {
        type: 'input',
        name: 'description',
        message: 'Please input project description:'
      },
      {
        type: 'input',
        name: 'projectMain',
        message: 'Main file (main.js):',
        default: 'main.js'
      },
      {
        type: 'input',
        name: 'projectAuthor',
        message: 'Author :',
      },
    ];

    return this.prompt(prompts).then(props => {
      this.props = props;
    });
  }

我们需要通过问题的方式采集用户建立工程的数据。promts是问题集合,在调用this.promt使其在运行yo的时候提出来,最后将用户输入的数据存在this.props中,以方便后面调用。


defaults块

defaults() {
    if (path.basename(this.destinationPath()) !== this.props.projectName) {
      mkdirp(this.props.projectName);
      this.destinationRoot(this.destinationPath(this.props.projectName));
    }
  }

mkdirp是我们引用的模块,用来创建文件夹。this.destinationRoot则是设置要创建的工程的根目录。


writing块

writing() {
    //创建readme
    let readmeTpl = _.template(this.fs.read(this.templatePath('README.md')));
    this.fs.write(this.destinationPath('README.md'), readmeTpl({
      generatorName: 'generator-managecli',
      yoName: 'managecli'
    }));

    //创建packageJson
    let pkg = this.fs.readJSON(this.templatePath('package_tmpl.json'),{});
    extend(pkg,{
      dependencies:{
        "babel-polyfill": "^6.26.0",
        "element-ui": "^2.2.1",
        "font-awesome": "^4.7.0",
        "vue": "^2.5.2",
        "vue-resource": "^1.5.0",
        "vue-router": "^3.0.1",
        "vuex": "^3.0.1"
      },
      devDependencies:{
        "autoprefixer": "^7.1.2",
        "babel-core": "^6.22.1",
        "babel-helper-vue-jsx-merge-props": "^2.0.3",
        "babel-loader": "^7.1.1",
        "babel-plugin-syntax-jsx": "^6.18.0",
        "babel-plugin-transform-runtime": "^6.22.0",
        "babel-plugin-transform-vue-jsx": "^3.5.0",
        "babel-preset-env": "^1.3.2",
        "babel-preset-stage-2": "^6.22.0",
        "chalk": "^2.0.1",
        "copy-webpack-plugin": "^4.0.1",
        "css-loader": "^0.28.0",
        "extract-text-webpack-plugin": "^3.0.0",
        "file-loader": "^1.1.4",
        "friendly-errors-webpack-plugin": "^1.6.1",
        "html-webpack-plugin": "^2.30.1",
        "node-notifier": "^5.1.2",
        "optimize-css-assets-webpack-plugin": "^3.2.0",
        "ora": "^1.2.0",
        "portfinder": "^1.0.13",
        "postcss-import": "^11.0.0",
        "postcss-loader": "^2.0.8",
        "postcss-url": "^7.2.1",
        "rimraf": "^2.6.0",
        "semver": "^5.3.0",
        "shelljs": "^0.7.6",
        "uglifyjs-webpack-plugin": "^1.1.1",
        "url-loader": "^0.5.8",
        "vue-loader": "^13.3.0",
        "vue-style-loader": "^3.0.1",
        "vue-template-compiler": "^2.5.2",
        "webpack": "^3.6.0",
        "webpack-bundle-analyzer": "^2.9.0",
        "webpack-dev-server": "^2.9.1",
        "webpack-merge": "^4.1.0"
      }
    });

    pkg.keywords = pkg.keywords || [];
    pkg.keywords.push('generator-manage-cli');

    pkg.name = this.props.projectName;
    pkg.description = this.props.description;
    pkg.main = this.props.projectMain;
    pkg.author = this.props.projectAuthor;
    pkg.license = 'MIT';

    this.fs.writeJSON(this.destinationPath('package.json'), pkg);


    //创建文件夹
    mkdirp('build');
    mkdirp('config');
    mkdirp('static');
    mkdirp('src/assets');
    mkdirp('src/router');
    mkdirp('src/store');
    mkdirp('src/tool');
    mkdirp('src/tool/pubService');
    mkdirp('src/tool/resource');
    mkdirp('src/components');
    mkdirp('src/components/basic');
    mkdirp('src/components/frame');




    this.fs.copy(
      this.templatePath('gitignore_tmpl'),
      this.destinationPath('.gitignore')
    );
    this.fs.copy(
      this.templatePath('babelrc_tmpl'),
      this.destinationPath('.babelrc')
    );
    this.fs.copy(
      this.templatePath('index_tmpl.html'),
      this.destinationPath('index.html')
    );
    this.fs.copy(
      this.templatePath('editorconfig_tmpl'),
      this.destinationPath('.editorconfig')
    );
    this.fs.copy(
      this.templatePath('.postcssrc_tmpl.js'),
      this.destinationPath('.postcssrc.js')
    );
    //src目录
    this.fs.copy(
      this.templatePath('src/main_tmpl.js'),
      'src/main.js'
    );
    this.fs.copy(
      this.templatePath('src/App.vue'),
      'src/App.vue'
    );
    //src内components/frame目录
    this.fs.copy(
      this.templatePath('src/components/frame/myPage.vue'),
      'src/components/frame/myPage.vue'
    );
    this.fs.copy(
      this.templatePath('src/components/frame/mySearch.vue'),
      'src/components/frame/mySearch.vue'
    );
    this.fs.copy(
      this.templatePath('src/components/frame/myTreeList.vue'),
      'src/components/frame/myTreeList.vue'
    );
    //src内components/basic目录 基本功能组件
    this.fs.copy(
      this.templatePath('src/components/basic/myAsideMenu.vue'),
      'src/components/basic/myAsideMenu.vue'
    );
    this.fs.copy(
      this.templatePath('src/components/basic/myDropDown.vue'),
      'src/components/basic/myDropDown.vue'
    );
    //src内 components加载文件
    this.fs.copy(
      this.templatePath('src/components/index.js'),
      'src/components/index.js'
    );
    //src内pages/basic目录  基本业务页面
    this.fs.copy(
      this.templatePath('src/pages/basic/home.vue'),
      'src/pages/basic/home.vue'
    );
    this.fs.copy(
      this.templatePath('src/pages/basic/login.vue'),
      'src/pages/basic/login.vue'
    );
    //src内tool/pubService
    this.fs.copy(
      this.templatePath('src/tool/pubService/index.js'),
      'src/tool/pubService/index.js'
    );
    //src内tool/resource
    this.fs.copy(
      this.templatePath('src/tool/resource/api.js'),
      'src/tool/resource/api.js'
    );
    this.fs.copy(
      this.templatePath('src/tool/resource/client.js'),
      'src/tool/resource/client.js'
    );
    this.fs.copy(
      this.templatePath('src/tool/resource/interceptors.js'),
      'src/tool/resource/interceptors.js'
    );
    //src内tool目录
    this.fs.copy(
      this.templatePath('src/tool/Base64.js'),
      'src/tool/Base64.js'
    );
    this.fs.copy(
      this.templatePath('src/tool/myValidate.js'),
      'src/tool/myValidate.js'
    );
    this.fs.copy(
      this.templatePath('src/tool/smallTool.js'),
      'src/tool/smallTool.js'
    );
    this.fs.copy(
      this.templatePath('src/tool/Base64.js'),
      'src/tool/Base64.js'
    );
    //src内router目录
    this.fs.copy(
      this.templatePath('src/router/index.js'),
      'src/router/index.js'
    );
    //src 内store目录 全局状态机
    this.fs.copy(
      this.templatePath('src/store/index.js'),
      'src/store/index.js'
    );
    this.fs.copy(
      this.templatePath('src/store/menu.js'),
      'src/store/menu.js'
    );
    this.fs.copy(
      this.templatePath('src/store/user.js'),
      'src/store/user.js'
    );
    //config目录
    this.fs.copy(
      this.templatePath('static/config.js'),
      'static/config.js'
    );
    //build目录
    this.fs.copy(
      this.templatePath('build/build.js'),
      'build/build.js'
    );
    this.fs.copy(
      this.templatePath('build/check-versions.js'),
      'build/check-versions.js'
    );
    this.fs.copy(
      this.templatePath('build/utils.js'),
      'build/utils.js'
    );
    this.fs.copy(
      this.templatePath('build/vue-loader.conf.js'),
      'build/vue-loader.conf.js'
    );
    this.fs.copy(
      this.templatePath('build/webpack.base.conf.js'),
      'build/webpack.base.conf.js'
    );
    this.fs.copy(
      this.templatePath('build/webpack.dev.conf.js'),
      'build/webpack.dev.conf.js'
    );
    this.fs.copy(
      this.templatePath('build/vue-loader.conf.js'),
      'build/vue-loader.conf.js'
    );
    this.fs.copy(
      this.templatePath('build/webpack.prod.conf.js'),
      'build/webpack.prod.conf.js'
    );
    //config目录
    this.fs.copy(
      this.templatePath('config/dev.env.js'),
      'config/dev.env.js'
    );
    this.fs.copy(
      this.templatePath('config/index.js'),
      'config/index.js'
    );
    this.fs.copy(
      this.templatePath('config/prod.env.js'),
      'config/prod.env.js'
    );
    //static目录
    this.fs.copy(
      this.templatePath('static/config.js'),
      'static/config.js'
    );
  }

使用_.template(lodash的template功能)和this.fs.write将模版中的关键字替换为用户的输入项。

this.fs.readJSONthis.fs.writeJSON,则是将package.json模版中的数据读取出来,作出一定修改写成新的文件。

最后使用mkdirpthis.fs.copy构建工程目录结构和将一些不要修改的配置文件copy到指定目录。

npm发布

如果是第一次发布,需要npm adduser账号注册,注册之后一定要在进行邮箱激活不然会发布失败,我在这遇到过坑,所以重点提出一下。 如果已经有npm账号有则运行npm login登陆。然后到工程根目录下,运行npm publish就可以发布了。

因为npm的repo下载很慢,我们很多时候用的taobaorepo。所以发布的时候需要切回到npmrepo

祝成功!