最近のフロントエンド開発者は、複数のツールを使用して日常業務を自動化しています。最も人気のあるソリューションの3つは 接地 、 gulp そして Webpack 。これらのツールはそれぞれ異なる哲学に基づいて構築されていますが、フロントエンドの構築プロセスを合理化するという同じ共通の目標を共有しています。たとえば、Gruntは構成主導型ですが、Gulpはほとんど何も実行しません。実際、Gulpは 開発者 ビルドプロセスのフロー(さまざまなビルドタスク)を実装するためのコードを記述します。
これらのツールの1つを選択することになると、私の個人的なお気に入りはGulpです。全体として、これはシンプルで高速かつ信頼性の高いソリューションです。この記事では、Gulpに似た独自のツールを実装することで、Gulpが内部でどのように機能するかを見ていきます。
Gulpには 4つの簡単な機能 :
これらの4つの単純な機能は、さまざまな組み合わせで、Gulpのすべてのパワーと柔軟性を提供します。バージョン4.0では、Gulpはgulp.seriesとgulp.parallelの2つの新しい関数を導入しました。これらのAPIを使用すると、タスクを直列または並列に実行できます。
これらの4つの機能のうち、最初の3つはGulpファイルにとって絶対に不可欠です。コマンドラインインターフェイスからタスクを定義して呼び出すことができます。 4つ目は、ファイルが変更されたときにタスクを実行できるようにすることで、Gulpを真に自動化するものです。
これは基本的なgulpfileです。
gulp.task('test', function{ gulp.src('test.txt') .pipe(gulp.dest('out')); });
簡単なテストタスクについて説明します。呼び出されると、ファイル test.txt 現在の作業ディレクトリ内のディレクトリにコピーする必要があります 。/でる 。 Gulpを実行して試してみてください。
touch test.txt # Create test.txt gulp test
メソッド.pipe
に注意してくださいはGulpの一部ではなく、ノードストリームAPIであり、読み取り可能なストリーム(gulp.src('test.txt')
によって生成される)と書き込み可能なストリーム(gulp.dest('out')
によって生成される)を接続します。 Gulpとプラグイン間のすべての通信はストリームに基づいています。これにより、gulpfileコードをこのようにエレガントな方法で記述できます。
Gulpがどのように機能するかがわかったので、Gulpのような独自のツールであるPlugを作成しましょう。
plugin.taskAPIから始めます。タスクを登録できるようにし、タスク名がコマンドパラメータで渡された場合にタスクを実行する必要があります。
var plug = { task: onTask }; module.exports = plug; var tasks = {}; function onTask(name, callback){ tasks[name] = callback; }
これにより、タスクを登録できるようになります。次に、このタスクを実行可能にする必要があります。簡単にするために、個別のタスクランチャーは作成しません。代わりに、プラグの実装に含めます。
コマンドラインパラメータで指定されたタスクを実行するだけです。また、すべてのタスクが登録された後、次の実行ループでそれを実行しようとすることを確認する必要があります。これを行う最も簡単な方法は、タイムアウトコールバック、またはできればprocess.nextTickでタスクを実行することです。
process.nextTick(function(){ var taskName = process.argv[2]; if (taskName && tasks[taskName]) { tasks[taskName](); } else { console.log('unknown task', taskName) } });
次のようにplugfile.jsを作成します。
var plug = require('./plug'); plug.task('test', function(){ console.log('hello plug'); })
…そしてそれを実行します。
node plugfile.js test
次のように表示されます。
hello plug
Gulpでは、タスク登録時にサブタスクを定義することもできます。この場合、plug.taskは、名前、サブタスクの配列、およびコールバック関数の3つのパラメーターを受け取る必要があります。これを実装しましょう。
市場に匹敵する評価は、純利益とスタートアップの還元利回りに基づいています。
タスクAPIを次のように更新する必要があります。
var tasks = {}; function onTask(name) { if(Array.isArray(arguments[1]) && typeof arguments[2] === 'function'){ tasks[name] = { subTasks: arguments[1], callback: arguments[2] }; } else if(typeof arguments[1] === 'function'){ tasks[name] = { subTasks: [], callback: arguments[1] }; } else{ console.log('invalid task registration') } } function runTask(name){ if(tasks[name].subTasks){ tasks[name].subTasks.forEach(function(subTaskName){ runTask(subTaskName); }); } if(tasks[name].callback){ tasks[name].callback(); } } process.nextTick(function(){ if (taskName && tasks[taskName]) { runTask(taskName); } });
次に、plugfile.jsが次のようになっている場合:
plug.task('subTask1', function(){ console.log('from sub task 1'); }) plug.task('subTask2', function(){ console.log('from sub task 2'); }) plug.task('test', ['subTask1', 'subTask2'], function(){ console.log('hello plug'); })
…それを実行する
node plugfile.js test
…表示されるはずです:
from sub task 1 from sub task 2 hello plug
Gulpはサブタスクを並行して実行することに注意してください。ただし、簡単にするために、実装ではサブタスクを順番に実行しています。 Gulp 4.0では、この記事の後半で実装する2つの新しいAPI関数を使用してこれを制御できます。
ファイルの読み取りと書き込みを許可しない場合、プラグはほとんど役に立ちません。次に、plug.src
を実装します。 Gulpのこのメソッドは、ファイルマスク、ファイル名、またはファイルマスクの配列のいずれかである引数を想定しています。読み取り可能なノードストリームを返します。
今のところ、src
の実装では、ファイル名のみを許可します。
var plug = { task: onTask, src: onSrc }; var stream = require('stream'); var fs = require('fs'); function onSrc(fileName){ var src = new stream.Readable({ read: function (chunk) { }, objectMode: true }); //read file and send it to the stream fs.readFile(path, 'utf8', (e,data)=> { src.push({ name: path, buffer: data }); src.push(null); }); return src; }
ここではオプションのパラメータであるobjectMode: true
を使用していることに注意してください。これは、ノードストリームがデフォルトでバイナリストリームで機能するためです。ストリームを介してJavaScriptオブジェクトを送受信する必要がある場合は、このパラメーターを使用する必要があります。
ご覧のとおり、人工オブジェクトを作成しました。
{ name: path, //file name buffer: data //file content }
…そしてそれをストリームに渡しました。
スプリングブーツ付きスプリングMVC
一方、plug.destメソッドは、ターゲットフォルダー名を受け取り、.srcストリームからオブジェクトを受け取る書き込み可能なストリームを返す必要があります。ファイルオブジェクトが受信されるとすぐに、それはターゲットフォルダに保存されます。
function onDest(path){ var writer = new stream.Writable({ write: function (chunk, encoding, next) { if (!fs.existsSync(path)) fs.mkdirSync(path); fs.writeFile(path +'/'+ chunk.name, chunk.buffer, (e)=> { next() }); }, objectMode: true }); return writer; }
pluginfile.jsを更新しましょう:
var plug = require('./plug'); plug.task('test', function(){ plug.src('test.txt') .pipe(plug.dest('out')) })
…作成する test.txt
touch test.txt
…そしてそれを実行します:
node plugfile.js test ls ./out
test.txt にコピーする必要があります 。/でる フォルダ。
Gulp自体はほぼ同じように機能しますが、人工ファイルオブジェクトの代わりに使用します ビニール オブジェクト。ファイル名とコンテンツだけでなく、現在のフォルダ名、ファイルへのフルパスなどの追加のメタ情報も含まれているため、はるかに便利です。コンテンツバッファ全体が含まれているとは限りませんが、代わりに読み取り可能なコンテンツのストリームがあります。
優れた図書館があります ビニール-fs これにより、ビニールオブジェクトとして表されるファイルを操作できます。基本的に、ファイルマスクに基づいて読み取り可能で書き込み可能なストリームを作成できます。
Vinyl-fsライブラリを使用してプラグ関数を書き換えることができます。ただし、最初にVinyl-fsをインストールする必要があります。
npm i vinyl-fs
これをインストールすると、新しいプラグの実装は次のようになります。
var vfs = require('vinyl-fs') function onSrc(fileName){ return vfs.src(fileName); } function onDest(path){ return vfs.dest(path); } // ...
…そしてそれを試すために:
rm out/test.txt node plugFile.js test ls out/test.txt
結果は同じであるはずです。
プラグインサービスはGulpストリーム規則を使用しているため、プラグツールと一緒にネイティブのGulpプラグインを使用できます。
試してみましょう。 gulp-renameをインストールします。
npm i gulp-rename
…そしてそれを使用するためにplugfile.jsを更新します:
var plug = require('./app.js'); var rename = require('gulp-rename'); plug.task('test', function () { return plug.src('test.txt') .pipe(rename('renamed.txt')) .pipe(plug.dest('out')); });
プラグファイル.jsを実行しても、ご想像のとおり、同じ結果が得られるはずです。
node plugFile.js test ls out/renamed.txt
最後になりましたが重要な方法はgulp.watch
です。このメソッドを使用すると、ファイルリスナーを登録し、ファイルが変更されたときに登録済みタスクを呼び出すことができます。それを実装しましょう:
var plug = { task: onTask, src: onSrc, dest: onDest, watch: onWatch }; function onWatch(fileName, taskName){ fs.watchFile(fileName, (event, filename) => { if (filename) { tasks[taskName](); } }); }
試してみるには、次の行をplugfile.jsに追加します。
plug.watch('test.txt','test');
今の各変更で test.txt 、ファイルは名前が変更された状態でoutフォルダにコピーされます。
GulpのAPIの基本的な機能がすべて実装されたので、さらに一歩進んでみましょう。 Gulpの次のバージョンにはさらに多くの情報が含まれます API関数 。この新しいAPIは、Gulpをより強力にします。
これらのメソッドを使用すると、ユーザーはタスクが実行される順序を制御できます。サブタスクを並列に登録するには、現在のGulpの動作であるgulp.parallelを使用できます。一方、gulp.seriesを使用して、サブタスクを順番に実行することができます。
私たちが持っていると仮定します test1.txt そして test2.txt 現在のフォルダにあります。これらのファイルを並行してoutフォルダーにコピーするために、プラグファイルを作成しましょう。
var plug = require('./plug'); plug.task('subTask1', function(){ return plug.src('test1.txt') .pipe(plug.dest('out')) }) plug.task('subTask2', function(){ return plug.src('test2.txt') .pipe(plug.dest('out')) }) plug.task('test-parallel', plug.parallel(['subTask1', 'subTask2']), function(){ console.log('done') }) plug.task('test-series', plug.series(['subTask1', 'subTask2']), function(){ console.log('done') })
実装を簡素化するために、サブタスクコールバック関数はそのストリームを返すように作成されています。これは、ストリームのライフサイクルを追跡するのに役立ちます。
機械学習アルゴリズムの構築方法
APIの修正を開始します。
var plug = { task: onTask, src: onSrc, dest: onDest, parallel: onParallel, series: onSeries };
更新する必要があります onTask タスクランチャーがサブタスクを適切に処理できるように、タスクメタ情報を追加する必要があるためです。
function onTask(name, subTasks, callback){ if(arguments.length <2){ console.error('invalid task registration',arguments); return; } if(arguments.length === 2){ if(typeof arguments[1] === 'function'){ callback = subTasks; subTasks = {series: []}; } } tasks[name] = subTasks; tasks[name].callback = function(){ if(callback) return callback(); }; } function onParallel(tasks){ return { parallel: tasks }; } function onSeries(tasks){ return { series: tasks }; }
物事を単純にするために、 async.js 、タスクを並列または直列に実行する非同期関数を処理するためのユーティリティライブラリ:
var async = require('async') function _processTask(taskName, callback){ var taskInfo = tasks[taskName]; console.log('task ' + taskName + ' is started'); var subTaskNames = taskInfo.series || taskInfo.parallel || []; var subTasks = subTaskNames.map(function(subTask){ return function(cb){ _processTask(subTask, cb); } }); if(subTasks.length>0){ if(taskInfo.series){ async.series(subTasks, taskInfo.callback); }else{ async.parallel(subTasks, taskInfo.callback); } }else{ var stream = taskInfo.callback(); if(stream){ stream.on('end', function(){ console.log('stream ' + taskName + ' is ended'); callback() }) }else{ console.log('task ' + taskName +' is completed'); callback(); } } }
ストリームがすべてのメッセージを処理して閉じたときに発行されるノードストリームの「終了」に依存しています。これは、サブタスクが完了したことを示します。 async.jsを使用すると、コールバックの大きな混乱に対処する必要はありません。
それを試すために、最初にサブタスクを並行して実行しましょう:
node plugFile.js test-parallel
task test-parallel is started task subTask1 is started task subTask2 is started stream subTask2 is ended stream subTask1 is ended done
そして、同じサブタスクを連続して実行します。
node plugFile.js test-series
task test-series is started task subTask1 is started stream subTask1 is ended task subTask2 is started stream subTask2 is ended done
これで、GulpのAPIが実装され、Gulpプラグインを使用できるようになりました。もちろん、実際のプロジェクトではプラグインを使用しないでください。Gulpはここで実装したもの以上のものです。この小さな演習が、Gulpが内部でどのように機能するかを理解し、より流暢に使用してプラグインで拡張できるようになることを願っています。
関連: Gulpを使用したJavaScript自動化の概要