Gulp.jsを使用してプロジェクトを構築します。 ヤンデックスワークショップ

こんにちは、ボリスです。 私はテスト部門のYandexで働いており、テスターの生活をより簡単で幸せにするツールを作成しています。 私たちのチームは部分的に研究しているため、非常に珍しいツールと実験を使用する余裕があります。 最近、同僚にこれらの実験の1つであるGulp.jsについて話しました。 今日、この経験を皆さんと共有したいと思います。







まず、Webテクノロジーの進化の背景について少し説明します。 最初は、独立した概念としてのフロントエンドはありませんでした。ほとんどのロジックはサーバーで実行されました。 したがって、スクリプトやスタイルをアセンブルするためのさまざまなタスク、および画像、フォント、その他のリソースを準備することは、Apache AntやMavenなどのバックエンドとそのアセンブラーによって実行されました。 フロントエンドには不利な点があり、これらのアセンブラーが提供するツールは彼にはあまり適していません。 この問題は、Gruntが登場して初めて解決されました。 これは、JSで記述された最初のコンパイラです。 各フロントエンドはJavaScriptを知っているため、Gruntの下でタスクを簡単に記述し、すでに記述されている内容を理解できます。 これは、このコレクターの成功につながりました。 Gruntには多くの利点がありますが、欠点もあります。



たとえば、これは単純なGruntファイルの外観です。



Gruntfile.js
module.exports = function (grunt) { "use strict"; // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), jshint: { files: ['<%= pkg.name %>.js'] }, concat: { options: { banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' }, main: { src: ['<%= pkg.name %>.js'], dest: 'build/<%= pkg.name %>.js' } }, uglify: { main: { src: 'build/<%= pkg.name %>.js', dest: 'build/<%= pkg.name %>.min.js' } } }); grunt.loadTasks('tasks/'); grunt.registerTask('default', ['jshint', 'concat', 'uglify']); return grunt; };
      
      







タスクがあり、プラグインを使用してそれを完了します。 さらにアクションが必要な場合は、プラグインを追加します。 その結果、何も見つからない巨大なコードシートが得られます。 また、Gruntファイルが大きいため、アセンブリが非常に長くなります。 また、Gruntアーキテクチャにはこれを行う手段がないため、高速化する方法は完全に理解できません。



Gruntfile.js
 module.exports = function (grunt) { "use strict"; // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), karma: { options: { configFile: 'karma.conf.js' }, unit: {}, travis: { browsers: ['Firefox'] } }, jshint: { files: ['<%= pkg.name %>.js'] }, concat: { options: { banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' }, main: { src: ['<%= pkg.name %>.js'], dest: 'build/<%= pkg.name %>.js' } }, uglify: { main: { src: 'build/<%= pkg.name %>.js', dest: 'build/<%= pkg.name %>.min.js' } }, copy: { main: { expand: true, cwd: 'docs/', src: ['**', '!**/*.tpl.html'], dest: 'build/' } }, buildcontrol: { options: { dir: 'build', connectCommits: false, commit: true, push: true, message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' }, pages: { options: { remote: 'git@github.com:just-boris/angular-ymaps.git', branch: 'gh-pages' } } } }); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-build-control'); grunt.registerTask('test', 'Run tests on singleRun karma server', function() { if (process.env.TRAVIS) { //this task can be executed in Travis-CI grunt.task.run('karma:travis'); } else { grunt.task.run('karma:unit'); } }); grunt.registerTask('build', ['jshint', 'test', 'concat', 'uglify']); grunt.registerTask('default', ['build', 'demo']); grunt.registerTask('build-gh', ['default', 'buildcontrol:pages']); return grunt; };
      
      







したがって、唯一の方法は、最初からやり直して反対側から入力することです。 まあ、初心者にとっては、世界ですでに有用なものを見ることができます。 たとえば、UNIXシェルがあります。 パイプライン-あるプロセスの排気方向から別のプロセスの入力方向への有用な概念があり、チェーン内の次のプロセスなどに送信できます。



 $ cat *.coffee \ | coffee \ | concat \ | uglify \ > build/app.min.js
      
      





したがって、組み立てを実行する実際のコンベアを構築できます。 コンベアベルトで組み立てることは非常に論理的です。 これは、フロントエンドタスクにも適用されます。 ただし、クリーンシェルでこれを行うと、いくつかの問題が発生する可能性があります。 第一に、すべてのオペレーティングシステムにシェルがあるわけではありません。第二に、たとえばコーヒーをJSに変換するコマンドがありません。



しかし、Gulpはこれを行うことができます。 このユーティリティはJavaScriptで記述されています。 シェルスクリプトと同じ原理を使用しますが、ここでは縦棒の代わりにpipe()



関数を使用してpipe()



ます。



 gulp.src('*.coffee') .pipe(coffee()) .pipe(concat()) .pipe(uglify()) .pipe(gulp.dest('build/'))
      
      





つまり まったく同じことができます。彼がやっていることは、必要に応じてブロックを交換したり、削除したり、一般的にコンベヤを好きなように構成したりできることは明らかです。



Gulpはすでに非常に安定しており、3番目のバージョンに開発され、ファンを見つけました。 私たちの好きな方法でインストールされます:



 npm install -g gulp
      
      





私は自分のプロジェクトの1つでそれをテストすることにし、それを使ったアセンブリがGruntよりも少し速く役立つことを知って驚いた。 そして今、私はその理由を説明しようとします。







問題は、アセンブリ中の最も高価な操作はファイルシステムへのアクセスであるということです。アセンブリはプロセッサ上で行われ、ファイルシステムはどこか遠くにあり、そこに行く必要があり、これには時間がかかります。 図では、赤い矢印はこれらの操作を示しています。 Gulpには、そのうち2つ(入力で読み取り、出力で書き留められている)と、Gruntで4つ(各プラグインの読み取りと書き込み)があることがわかります。 さて、すべてが高速に動作するので、Gulpに切り替えてみませんか。 しかし、最初に、私はすべてを注意深くチェックすることにしました。 コーヒーファイルとスタイルを収集してパッケージ化するテストケースを準備し、GruntとGulpにこのタスクを説明し、それらを1つずつ実行し、実際にゲインがあることを確認しました。gulpは850ミリ秒に対して640ミリ秒です。もう少し複雑なテストを準備しました。 その中で、スタイルを少し前処理する必要があります。 もちろん、ほとんどのスタイルはブートストラップです。 ソースのless-filesからそれを収集してみましょう。そして、そのサイズを減らすために、CSSOを試してみましょう。 Gulpでは、これは非常に簡単に実行できますcsso



csso



両方のプラグインがあります。



 var gulp = require('gulp'); var csso = require('gulp-csso'); var less = require('gulp-less'); gulp.task('default', function() { return gulp.src('bower_components/bootstrap/less/bootstrap.less') .pipe(less()) .pipe(csso()) .pipe(gulp.dest('dest/')); });
      
      





うなり声ファイルはより大きくなります。



 module.exports = function(grunt) { require('time-grunt')(grunt); grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-csso'); grunt.initConfig({ less: { compile: { src: 'bower_components/bootstrap/less/bootstrap.less', dest: 'dest/bootstrap.css' } }, csso: { compile: { src: 'dest/bootstrap.css', dest: 'dest/bootstrap.min.css' } } }); grunt.registerTask('default', ['less', 'csso']); };
      
      





その結果、Gulpが再び勝ちました:2.3に対して2秒。 Gruntは、ジャンクファイルの読み取りと書き込みに300ミリ秒を費やしました。



GulpのプラグインはGruntほど多くありませんが、一般的なタスクには400個で十分です。 まだ何かが必要な場合は、いつでも自分で書くことができます。 Gulpの主なアイデアはストリームです。 これらはすでにカーネルnode.jsにあります。このため、何も接続する必要はありません。 小さな例を考えてみましょう。みんなを歓迎するプラグインです。 私たちは彼の言葉であり、彼は私たちに挨拶しています:







これはJavaScriptでどのように見えるかです:



 var stream = require('stream'), greeterStream = new stream.Transform({objectMode: true}); greeterStream._transform = function(str) { this.push('Hello, '+str+'!'); }; greeterStream.pipe(process.stdout) greeterStream.write('world'); // Hello, world! greeterStream.write('uncle Ben'); // Hello, uncle Ben!
      
      





_transform



メソッドを定義する必要がある既製のネイティブオブジェクトがあります。 文字列が彼の入力に送信され、処理して返されます。 書き込み、変換します。 何も接続する必要はありません。これはネイティブAPI node.jsです。 Gulpにどのように収まるかを確認するには、蓋を取り外して中を見てください。 そこで、OrchestratorとVinyl fsの2つのモジュールが表示されます。 Orchestratorはフローを実行し、それらを整列させ、最大限の並列性で実行しようとし、通常はすべてがオーケストラのように機能するように注意します。 Vinylでは、すべてがもう少し面白くなっています。 ストリームはデータセットであり、ファイルを収集します。 これは単なるデータではなく、名前、拡張子、その他の属性でもあります。 どういうわけか、連続ストリームを個別のファイルに分割したいと思います。 Vinylはこれをすべて行います。 実際、これはファイルのラッパーです。データだけでなくオブジェクトも取得します。 Vinylはそこに必要なすべてのフィールドを置きます。 それらを変更して管理することができます。



 var coffeeFile = new File({ cwd: "/", base: "/test/", path: "/test/file.coffee" contents: new Buffer("test = 123") });
      
      





たとえば、 gulp-freezeなど、すべてのプラグインがこれを実行します。 静的にフリーズするように設計されています。 Gulpでは、これはすべて非常に簡単に行われます。コンテンツがあり、そこからmd5ハッシュを計算し、これがファイル名であると言います。 次に、ファイルをストリームにさらに書き込みます。 Gulpは残りの操作を行います。ファイルを読み取り、プラグインに渡し、残りのプラグインに渡して、最終的にファイルシステムに書き込みます。 そして、最も興味深いプラグインのみを作成します。



 var through = require('through2'); module.exports = function() { return through.obj(function(/**Vinyl*/file, enc, callback) { var content = file.contents.toString('utf-8'), checksum = createMD5(content), file.path = checksum; this.push(file); callback(); }); };
      
      





そして、余分なものは何もないので、テストは非常に簡単です。 偽のデータを入れるテストストリームを作成します。ファイルシステムを使用することさえできません。 大きなプラグインを作成し、たとえばTravisのようにCIを構成する場合、ビルドの速度に驚きます。 すべてのテストケースで、仮想ファイルを生成し、ストリームに書き込み、出力をリッスンできます。 出力が正しいデータである場合、すべてが正常であり、テストに合格します。そうでない場合、間違いがあります。修正します。



 var freeze = require('./index.js') var testStream = freeze() testStream.on('data', function(file) { //assert here }); testStream.write(fakeFile);
      
      





プラグインを作成する必要さえない場合もあります。 一部の関数は、ストリームに直接挿入できます。 たとえば、Yateテンプレートエンジン用のGulpプラグインはまだ作成されていません。 ただし、直接呼び出すことができます。



 var map = require('vinyl-map'); var yate = require('yate'); gulp.src('*.yate') .pipe(map(function(code, filename) { //        return yate.compile(code.toString('utf-8')).js; })) .pipe(gulp.dest('dist/'))
      
      







そのようなシステムには、よりエキゾチックな用途があります。 たとえば、このコレクターはJekyllを置き換えることができます。 マークダウンの記事があり、そこからHTMLでWebページを収集するとします。 このスキームは理想的にはGulpのイデオロギーに適合し、その助けによりジキルテンプレートを収集できます。 これを行うには、投稿を読んで処理し、小さなプラグインをいくつか作成する必要があります。その結果、node.jsで完全なJekyllポートを取得する必要があります。 とても快適です。 Gruntでは、原則としてこれを行うことは不可能であるように思えます。



 gulp.task('jekyll', function() { return gulp.src('_posts/**') .pipe(liquid.collectMeta()) //    .pipe(post()) //    .pipe(gulp.src('!_*/?*')) //   .pipe(markdown()) //  html,   .pipe(liquid.template()) // .pipe(gulp.dest('_site/')); //  });
      
      







PSこのレポートは、2014年の春に、過去6か月にわたってツールが開発され、何かが変わる可能性があると伝えられましたが、基本的な考え方は変わりません。



All Articles