使用YEOMAN創建屬于自己的前端工作流
背景
前幾天看了一篇文章大受啟發:我理想中的前端工作流,現在工作中一直在使用gulp和webpack做自動化,在單獨項目中效果很不錯。但是隨著項目逐漸怎多,是需要使用一個工具來幫你快速創建和規范項目。
Yeoman是什么
Yeoman是一個腳手架,可以快速生成一個項目的骨架。官網上有很多大家 已經寫好的腳手架 ,也可以自己寫一個適合自己的,接下來我會翻譯下官網的教程,學習的同時把經驗分享給大家。
Yeoman能做什么
Yeoman只是幫我們生成項目的骨架是遠遠不夠的,官網上介紹,Yeoman是由三部分組成的:腳手架工具 - Yo、構建工具 - Grunt or Gulp、包管理工具 - Bower or npm。
創建Yeoman
翻譯yeoman官網Creating a generator流程
(一)Getting started 快速開始
(二)Running Context 生命周期
(三)User Interactions 和使用者互動
(四)composability 組合
(五)Managing Dependencies 依賴管理
(六)Interacting with the file system 文件操作
(一)快速開始
原文地址:http://yeoman.io/authorin...
1.1 建立一個node模塊
1.首先要建立一個文件夾,在這個文件夾內寫你的generator,這個文件夾的名字 必須 被命名為 generator-name ,name為你generator的名字,假如我想寫一個vue的腳手架,我可以命名為: generator-vue ,這個很關鍵,Yeoman文件系統只會信任這種規范的generator。
mkdir generator-vue
2.建立node模塊,首先要必備文件 package.json ,這個文件可以通過執行 npm init 指令來生成,前提是需要安裝 node 及 npm。
{
"name": "generator-vue",
"version": "0.1.0",
"description": "",
"files": [
"app",
"router"
],
"keywords": ["yeoman-generator"],
"dependencies": {
"yeoman-generator": "^0.20.2"
}
}
幾點要求:
-
name:必須格式為 generator-你項目的名字 。
-
keywords:數組中必須有 yeoman-generator ,這樣你的項目才會被 Yeoman官方的generators列表 所收錄。
-
如果需要添加其他的屬性可以去 npm官網文檔 中查看。
1.2 文檔結構
通過1.1步驟已經有了package.json,下一步在新建兩個文件夾分別叫app和router,結構如下。
├───package.json
├───app/
│ └───index.js
└───router/
└───index.js
1.2.1 默認項目
當你執行Yeoman指令 yo vue (上面你已經建立的項目名字)的時候,他會默認執行你根目錄下app/index.js的內容,所以一個新項目, app/ 目錄是必須的。
1.2.2 子項目
router/ 作為子項目,可以通過 yo vue:router 來執行。
1.2.3 更改文件目錄
├───package.json
└───generators/
├───app/
│ └───index.js
└───router/
└───index.js
如果不喜歡把所有項目都放在根目錄下,Yeoman還允許把項目放在 generators/ 下面,改寫上面的例子:
├───package.json
└───generators/
├───app/
│ └───index.js
└───router/
└───index.js
如果更改了文件目錄,要同步修改 package.json 中對應文件的目錄結構:
{
"files": [
"generators/app",
"generators/router"
]
}
1.3 擴展generator
1.3.1 重寫構造函數
module.exports = generators.Base.extend({
// The name constructor
is important here
constructor: function () {
// Calling the super constructor is important so our generator is correctly set up
generators.Base.apply(this, arguments);
// Next, add your custom code
this.option('coffee'); // This method adds support for a `--coffee` flag
}
});</code></pre>
1.3.2 添加自己的方法
module.exports = generators.Base.extend({
method1: function () {
console.log('method 1 just ran');
},
method2: function () {
console.log('method 2 just ran');
}
});
下一步的時候當你運行generator的時候,會看到這兩句console輸出在控制臺。
1.4 運行generator
1.4.1 安裝全局generator
在項目跟路徑下 generator-name(vue)/ 執行指令:
npm link
過程中,將會安裝node模塊依賴,和創建軟連接指向你當前項目。
//1.到本地全局node模塊路徑下
cd /usr/local/lib/node_modules
//2.查看列表
ll
//3.會看到已經安裝了一個全局的geneator-vue模塊
npm
geneator-vue -> /Users/lvjinlong/generator-vue
gulp
..
//4.此時在任意新建的項目文件夾中yo項目的名字,會看到上面實例中的console打印出來的結果。
yo vue</code></pre>
1.4.2 尋找根目錄
安裝geneator的時候,Yeoman會搜索你的文件夾,會把包含 .yo-rc.json 文件的文件夾作為你的根目錄來初始化項目。
問:那么, .yo-rc.json 是個什么東西呢?
答:當你 第一次 調用 this.config.save() 的時候,系統會生成這個文件。
問:那么, this.config.save() 這個方法的作用是什么呢?
答: 官網 這篇文章會有講解,大體意思是會利用 .yo-rc.json 來存儲或是讀取用戶相關信息。
所以當你初始化一個項目的時候,別忘記清除掉之前系統生成的 .yo-rc.json 。
(二)生命周期
原文地址:http://yeoman.io/authorin...
2.1 Prototype methods as actions
每個方法會直接附加在generator原型上作為一個action,每個action按照一定的循序執行在Yeoman的生命周期中。
這些方法相當于直接執行了 Object.getPrototypeOf(generator) ,
所有方法都會 自動執行 。如果不想都自動執行,請往下看。
2.1.1 私有方法
只有私有方法在Yeoman中才不會自動執行,下面有三種辦法幫你創建一個私有方法。
1. 在方法名前面加下劃線(例如: _method )
2. 使用實例方法
generators.Base.extend({
init: function () {
this.helperMethod = function () {
console.log('won\'t be called automatically');
};
};
});
3.繼承父generator
var MyBase = generators.Base.extend({
helper: function () {
console.log('won\'t be called automatically');
}
});
module.exports = MyBase.extend({
exec: function () {
this.helper();
}
});</code></pre>
2.2 生命周期
Yeoman中的定義了生命周期鉤子,和這些鉤子命名相同的會按照順序執行,如果和這些鉤子名稱不一樣則默認為 default
這些生命周期鉤子 按順 序為:
-
initializing:初始化方法(檢驗當前項目狀態、獲取configs、等)
-
prompting:獲取用戶選項
-
configuring:保存配置(創建 .editorconfig 文件)
-
default:如果函數名稱如生命周期鉤子不一樣,則會被放進這個組
-
writing:寫generator特殊的文件(路由、控制器、等)
-
conflicts:沖突后處理辦法
-
install:正在安裝(npm、bower)
-
end:安裝結束、清除文件、設置good bye文案、等
(三)和用戶互動
原文地址:http://yeoman.io/authorin...
Yeoman默認在終端中執行,但是也支持在多種不同工具中執行。這時候我們使用 console.log() 或是 process.stdout.write() 用戶就可能看不到,Yeoman中使用 generator.log() 來統一打印輸出結果。
3.1和用戶互動
3.1.1 Prompts - 提示框
提示框是Yeoman主要和用戶交流的手段,是通過 Inquirer 模塊來實現的,所有的API及參數可以看 這里 ,執行以下實例看下效果:
module.exports = generators.Base.extend({
prompting: function () {
var done = this.async();
this.prompt({
type : 'input',
name : 'name',
message : 'Your project name',
default : this.appname // Default to current folder name
}, function (answers) {
this.log(answers.name);
done();
}.bind(this));
}
})
3.1.2 Remembering user preferences 記錄用戶預設參數
一個確定的答案,比如帳號,用戶可能多次提交同一個答案,這時候可以用Yeoman提供 store 來存儲這些答案。
this.prompt({
type : 'input',
name : 'username',
message : 'What\'s your Github username',
store : true
}, callback);
這時候會在跟路徑下生成一個 .yo-rc.json 文件,里面會存儲name信息。 可以參考官網storage這一節
3.1.3 Arguments - 參數
參數直接通過命令行傳遞,例如:
yo webapp my-project
這個例子中,my-project 是第一個參數。
通知系統我們需要參數,我們使用 generator.argument() 方法,這個方法接受兩種形式:
-
name(String) -- generator['name']
-
hash(key-value) -- 哈希值的形式,接受以下參數作為key值
-
desc -> 參數描述
-
required -> 是否為必須傳遞 [ ture | false ]
-
optional -> 是否可選 [ ture | false ]
-
type -> 參數類型 [ String | Number | Array | Object]
var _ = require('lodash'); //需要提前安裝lodash模塊,提供一些常用方法
module.exports = generators.Base.extend({
//注: arguments和options必須在constructor中定義.
constructor: function () {
generators.Base.apply(this, arguments);
//appname為一個必須的參數
this.argument('appname', { type: String, required: true });
//用駝峰式把這個參數保存起來
this.appname = _.camelCase(this.appname);
}
});
3.1.4 Options - 選項
Options(選項)看起來像是Arguments(參數),但是他們是在命令行中的標志。
實例:舉一個官網團隊的腳手架demo - webapp - 15行
module.exports = generators.Base.extend({
constructor: function () {
generators.Base.apply(this, arguments);
this.option('skip-welcome-message', {
desc: 'Skips the welcome message',
type: Boolean
});
}
})
用法:
yo webapp --skip-install
3.2 輸出信息
輸出信息使用 generator.log 模塊,和js的 console.log() 基本一致。
module.exports = generators.Base.extend({
myAction: function () {
this.log('Something has gone wrong!');
}
});
傳值的方式同Arguments(參數),字符串或hash。區別是參數:
-
desc:描述
-
alias:簡寫(--version 簡寫為 -v)
-
type:[ Boolean | String | Number ]
-
defaults:默認值
-
hide :[ Boolean ] 是否隱藏幫助信息
(四)組合
原文地址:http://yeoman.io/authorin...
很有趣的是,官網的第一個demo竟然是一個變形金剛組合的 gif ,可見他們是多么想表達各個小功能組合起來后的yeoman是有多強大。
可以通過以下兩種方式開始組合:
-
依賴另外一個generator(例如: generator-backbone 使用 generator-mocha )。
-
使用者,根據自己的需求在初始化項目的時候選擇配置。(例如: sass 或者 less 來搭配 webpack 或是 gulp )
4.1 generator.composeWith()
composeWith 方法允許你的generator來組合別人的generator,但是一旦組合成功,不要忘記第二章的內容 <(二)Running Context 生命周期> ,所有被組合的generator都遵循Yeoman的生命周期規則來順序執行,不同的generator執行順序,取決于 composeWith 調用他們的順序,看下面的API及執行順序實例。
4.1.1 API
composeWith 接收三個參數:
-
namespace :聲明generator和誰組合。[ String ]
-
options :調用generator的時候需要接收的參數。[ Object | Array ]
-
settings :你的generator用這些配置來決定如果運行其他的generators。[ Object ]
-
settings.local :需要在 dependencies 中配置,使用dependencies安裝的模塊相當于本地模塊,這里使用 require.resolve 來返回一個本地模塊的路徑,如: node_modules/generator-name [ String ]
-
settings.link : weak or strong [ String ]
-
week link:在初始化的時候不運行,比如后端運行的,frameworks或css的預處理。
-
strong link:一直運行。
當需要用 peerDependencies 來組合generator
this.composeWith('backbone:route', { options: {
rjs: true
}});
當需要用 dependencies 來組合generator
this.composeWith('backbone:route', {}, {
local: require.resolve('generator-bootstrap')
});
//注:require.resolve()將返回node.js需要的模塊路徑。</code></pre>
接下來4.2中會解釋 peerDependencies 和 dependencies 的區別。
4.1.2 執行順序實例
// In my-generator/generators/turbo/index.js
module.exports = require('yeoman-generator').Base.extend({
'prompting' : function () {
console.log('prompting - turbo');
},
'writing' : function () {
console.log('writing - turbo');
}
});
// In my-generator/generators/electric/index.js
module.exports = require('yeoman-generator').Base.extend({
'prompting' : function () {
console.log('prompting - zap');
},
'writing' : function () {
console.log('writing - zap');
}
});
// In my-generator/generators/app/index.js
module.exports = require('yeoman-generator').Base.extend({
'initializing' : function () {
this.composeWith('my-generator:turbo');
this.composeWith('my-generator:electric');
}
});</code></pre>
來分析下上面這段腳本:
-
以上這段腳本在初始化的時候執行了兩個 composeWith 方法 turbo 和 electric。
-
分別執行了他們目錄下的index.js。
-
加載順序判斷:turbo 優先于 electric。
-
生命周期問題:prompting 優先于 writing。
所以執行后的結果如下:
prompting - turbo
prompting - zap
writing - turbo
writing - zap
4.2 peerDependencies 和 dependencies 的區別
npm允許以下三種dependencies(依賴):
-
dependencies :使用依賴,自己或是別人使用你的generator所必備的依賴模塊。這些模塊被generator視為本地模塊。
-
peerDependencies :看下面的 注: npm@3后,peerDependencies不會再被自動安裝,需要手動。
-
devDependencies :開發依賴,作為開發或者是測試需要用的模塊,如果別人安裝你的generator,這些模塊不應該被安裝。
當使用 peerDependencies 別的模塊也要依賴當前這個模塊,小心不要創建版本導致沖突,Yeoman推薦使用(>=) 或 (*) 來安裝可用的版本,如:
{
"peerDependencies": {
"generator-gruntfile": "*",
"generator-bootstrap": ">=1.0.0"
}
}
注:npm@3以后, peerDependencies 不會再被自動安裝,安裝他們必須執行如下:
npm install generator-yourgenerator generator-gruntfile generator-bootstrap@">=1.0.0"
(五)依賴管理
原文地址:http://yeoman.io/authorin...
Yeoman提供以下幾種形式來安裝依賴。
5.1 npm
使用 generator.npmInstall() 來安裝npm包,如果你在多個generators調用了 npm install Yeoman保證只會執行一次。
例如:你需要安裝 lodash 這個模塊作為發開依賴。
generators.Base.extend({
installingLodash: function() {
this.npmInstall(['lodash'], { 'saveDev': true });
}
});
效果等同于直接在終端輸入:
npm install lodash --save-dev
5.2 Bower
使用 generator.bowerInstall() 來安裝依賴。實例:同npm。
5.3 Both npm & Bower
使用 generator.installDependencies() 來同時安裝npm 和 bower。實例:同npm。
5.4 Using other tools
可以使用 spawnCommand 來安裝其他工具。比如:PHP的composer。
(六)文件操作
原文地址:http://yeoman.io/authorin...
6.1 根路徑
Yeoman會在這個根路徑中創建你項目的腳手架。
根路徑會以以下兩種方式定義:
-
當前工作路徑
-
最近一級中包含 .yo-rc.json 的路徑
你可以通過Yeoman提供的 generator.destinationRoot() 方法來獲取根路徑,這個方法接收一個參數 generator.destinationPath('sub/path') 來獲取子目錄的路徑。
例如:
查看當前路徑
$ pwd
~/projects
//跟路徑是 ~/projects
generators.Base.extend({
paths: function () {
this.destinationRoot();
// returns '~/projects'
this.destinationPath('/sub/index.js');
// returns '~/projects/sub/index.js'
}
});</code></pre>
6.2 常用工作路徑
原文是Template context,其實我感覺直譯不太好,換做叫常用工作路徑會更好。
這個路徑的默認取你當前目錄 ./templates/ , 可以手動覆蓋這個路徑 generator.sourceRoot('new/template/path')
例如:
generators.Base.extend({
paths: function () {
this.sourceRoot(); //設置常用工作路徑
// returns './templates'
this.templatePath('index.js'); //讀取常用工作路徑
// returns './templates/index.js'
}
});</code></pre>
6.3 文件操作
所有文件相關的方法都會通過 this.fs 暴露出來。 這里有所有文件操作相關方法 ,包括下面的 copyTpl 方法。
實例: 把一個 常用工作路徑 的文件復制到 根路徑 下,并傳一個參數。
1.常用工作路徑下的 ./templates/index.html 內容是:
<html>
<head>
<title><%= title %></title>
</head>
</html>
2.我們用 copyTpl 方法把來復制文件,該方法使用 ejs模板語法
generators.Base.extend({
writing: function () {
this.fs.copyTpl(
this.templatePath('index.html'),//第一個參數:from
this.destinationPath('public/index.html'),//第二個參數:to
{ title: 'Templating with Yeoman' }//第三個參數:options
);
}
});
3.來看復制后的 public/index.html
<html>
<head>
<title>Templating with Yeoman</title>
</head>
</html>
6.4 通過「流」來改變文件
Yeoman提供 registerTransformStream() 方法,使用gulp的來操作文件。
例如:
var beautify = require('gulp-beautify');
this.registerTransformStream(beautify({indentSize: 2 }));
6.5 修改已經存在文件的內容
Yeoman介紹了幾個比較流行的解析器:
1. Cheerio for parsing HTML,基本實現流程如下
var cheerio = require('cheerio'),
$ = cheerio.load('<h2 class="title">Hello world</h2>');
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
$.html();
//=> <h2 class="title welcome">Hello there!</h2></code></pre>
2. Esprima for parsing JavaScript
3. For JSON files 可以使用JSON原生的方法
本次只翻譯了前六章,后續會翻譯后六章、自己如果寫一個generator以及遇到的坑和問題。都會更新在我的github的Yeoman-article中。