起因
因为工作中遇到了需要在多个市场创建新的管理端权限管控(基于Vue全家桶)项目。
目前开源的vue脚手架一般都是使用vue-cli,但是在实际使用过程中自己已经在原有项目上编写好了很多通用且可复用的公共组件。若使用vue-cli 免不了进行ctrl+c和ctrl+v操作,十分繁琐。所以有了搭建一个专属于这类型项目的前端脚手架的想法。一番查阅资料之后选择了Yeoman-genarator来搭建脚手架项目,并成功实现且在多个市场进行复用。
一是学习了新的东西,二来确实相当方便,新建工程爽爽的。所以说也不算是教程,只是自己成功搞定generator的思路,欢迎大家指教([email protected])。该脚手架已上传至npm(generator-manage-cli),欢迎大家下载。
github: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 [email protected]
? 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.readJSON
和this.fs.writeJSON
,则是将package.json
模版中的数据读取出来,作出一定修改写成新的文件。
最后使用mkdirp
和this.fs.copy
构建工程目录结构和将一些不要修改的配置文件copy到指定目录。
npm发布
如果是第一次发布,需要npm adduser
账号注册,注册之后一定要在进行邮箱**不然会发布失败,我在这遇到过坑,所以重点提出一下。
如果已经有npm账号有则运行npm login
登陆。然后到工程根目录下,运行npm publish
就可以发布了。
因为npm的repo下载很慢,我们很多时候用的
taobao
的repo
。所以发布的时候需要切回到npm
的repo
。
祝成功!