Memperkenalkan CLI Builder

Memperkenalkan CLI Builder

Pada artikel ini, kita akan melihat API CLI Angular baru, yang akan memungkinkan Anda untuk memperluas fitur CLI yang ada dan menambahkan yang baru. Kami akan membahas cara bekerja dengan API ini, dan poin ekstensi apa yang ada, yang memungkinkan penambahan fungsionalitas baru ke CLI.

Ceritanya


Sekitar setahun yang lalu, kami memperkenalkan file workspace ( angular.json ) di CLI Angular dan memikirkan kembali banyak prinsip dasar untuk mengimplementasikan perintahnya. Kami sampai pada fakta bahwa kami menempatkan tim di "kotak":

  1. Perintah skematik - "Perintah skematik . " Sekarang, Anda mungkin sudah mendengar tentang Skema, perpustakaan yang digunakan oleh CLI untuk membuat dan memodifikasi kode Anda. Itu muncul di versi 5 dan saat ini digunakan di sebagian besar perintah yang berkaitan dengan kode Anda, seperti baru , buat , tambah dan perbarui .
  2. Perintah lain-lain - "Tim lain" . Ini adalah perintah yang tidak terkait langsung dengan proyek Anda: bantuan , versi , konfigurasi , doc . Baru-baru ini, analitik telah muncul, serta telur Paskah kami (Ssst! Tidak sepatah kata pun kepada siapa pun!).
  3. Perintah tugas - "Perintah tugas . " Kategori ini, pada umumnya, "meluncurkan proses yang dieksekusi pada kode orang lain." - Sebagai contoh, build adalah build proyek, lint sedang debugging dan tes sedang menguji.

Kami mulai mendesain angular.json sejak dulu. Awalnya, itu dikandung sebagai pengganti konfigurasi Webpack. Selain itu, itu seharusnya memungkinkan pengembang untuk secara mandiri memilih implementasi perakitan proyek. Hasilnya, kami mendapatkan sistem peluncuran tugas dasar, yang tetap sederhana dan nyaman untuk eksperimen kami. Kami menyebut API ini "Arsitek."

Terlepas dari kenyataan bahwa Arsitek tidak didukung secara resmi, itu populer di kalangan pengembang yang ingin menyesuaikan perakitan proyek, serta di antara perpustakaan pihak ketiga yang perlu mengontrol alur kerja mereka. Nx menggunakannya untuk menjalankan perintah Bazel, Ionic menggunakannya untuk menjalankan tes unit pada Jest, dan pengguna dapat memperluas konfigurasi Webpack mereka menggunakan alat seperti ngx-build-plus . Dan itu baru permulaan.

Versi API yang didukung, stabil, dan ditingkatkan secara resmi ini digunakan dalam Angular CLI versi 8.

Konsep


API Arsitek menawarkan alat untuk menjadwalkan dan mengoordinasikan tugas yang digunakan CLI Angular untuk mengimplementasikan perintahnya. Ini menggunakan fungsi yang disebut
"Pembangun" - "kolektor" yang dapat bertindak sebagai tugas atau perencana kolektor lain. Selain itu, ia menggunakan angular.json sebagai seperangkat instruksi untuk kolektor sendiri.

Ini adalah sistem yang sangat umum yang dirancang agar fleksibel dan dapat diperluas. Ini berisi API untuk pelaporan, pencatatan dan pengujian. Jika perlu, sistem dapat diperluas untuk tugas-tugas baru.

Pemetik


Assembler adalah fungsi yang menerapkan logika dan perilaku untuk tugas yang dapat menggantikan perintah CLI. - Misalnya, mulai linter.

Fungsi kolektor mengambil dua argumen: nilai input (atau opsi) dan konteks yang menyediakan hubungan antara CLI dan kolektor itu sendiri. Pembagian tanggung jawab di sini sama dengan Skema - pengguna CLI menentukan opsi, API bertanggung jawab atas konteksnya, dan Anda (pengembang) menetapkan perilaku yang diperlukan. Perilaku dapat diimplementasikan secara sinkron, asinkron, atau hanya menampilkan sejumlah nilai tertentu. Output harus bertipe BuilderOutput , yang berisi keberhasilan bidang logis dan kesalahan bidang opsional, yang berisi pesan kesalahan.

File dan tugas workspace


API Arsitek bergantung pada angular.json , file ruang kerja untuk menyimpan tugas dan pengaturannya.

angular.json membagi ruang kerja menjadi proyek, dan mereka, pada gilirannya, menjadi tugas. Sebagai contoh, aplikasi Anda dibuat dengan perintah baru ng adalah salah satu proyek tersebut. Salah satu tugas dalam proyek ini adalah tugas pembangunan , yang dapat diluncurkan menggunakan perintah ng build . Secara default, tugas ini memiliki tiga kunci:

  1. builder - nama kolektor yang akan digunakan untuk menyelesaikan tugas, dalam format PACKAGE_NAME: ASSEMBLY_NAME .
  2. Pilihan - pengaturan yang digunakan saat memulai tugas secara default.
  3. konfigurasi - pengaturan yang akan diterapkan saat memulai tugas dengan konfigurasi yang ditentukan.

Pengaturan diterapkan sebagai berikut: ketika tugas dimulai, pengaturan diambil dari blok opsi, kemudian, jika konfigurasi telah ditentukan, pengaturannya ditulis di atas yang sudah ada. Setelah itu, jika pengaturan tambahan diteruskan ke scheduleTarget () - blok override , itu akan ditulis terakhir. Saat menggunakan CLI Angular, argumen baris perintah diteruskan ke penggantian . Setelah semua pengaturan ditransfer ke kolektor, dia memeriksanya sesuai dengan rencananya, dan hanya jika pengaturan sesuai dengan itu, konteksnya akan dibuat dan kolektor akan mulai bekerja.

Informasi lebih lanjut tentang ruang kerja di sini .

Buat kolektor Anda sendiri


Sebagai contoh, mari kita buat kolektor yang akan menjalankan perintah pada baris perintah. Untuk membuat kolektor, gunakan pabrik createBuilder dan kembalikan objek BuilderOutput :

import { BuilderOutput, createBuilder } from '@angular-devkit/architect'; export default createBuilder((options, context) => { return new Promise<BuilderOutput>(resolve => { resolve({ success: true }); }); }); 

Sekarang, mari kita tambahkan beberapa logika ke kolektor kami: kami ingin mengontrol kolektor melalui pengaturan, membuat proses baru, menunggu proses selesai, dan jika proses berhasil diselesaikan (yaitu, kembalikan kode 0), beri tanda ini ke Arsitek:

 import { BuilderOutput, createBuilder } from '@angular-devkit/architect'; import * as childProcess from 'child_process'; export default createBuilder((options, context) => { const child = childProcess.spawn(options.command, options.args); return new Promise<BuilderOutput>(resolve => { child.on('close', code => { resolve({ success: code === 0 }); }); }); }); 

Pemrosesan output


Sekarang, metode spawn meneruskan semua data ke output standar dari proses. Kami mungkin ingin mentransfernya ke logger - logger. Dalam hal ini, pertama, debugging selama pengujian akan difasilitasi, dan kedua, Arsitek itu sendiri dapat menjalankan kolektor kami dalam proses terpisah atau menonaktifkan output standar proses (misalnya, dalam aplikasi Electron).

Untuk melakukan ini, kita dapat menggunakan Logger , tersedia di objek konteks , yang akan memungkinkan kita untuk mengarahkan ulang output dari proses:

 import { BuilderOutput, createBuilder } from '@angular-devkit/architect'; import * as childProcess from 'child_process'; export default createBuilder((options, context) => { const child = childProcess.spawn(options.command, options.args, { stdio: 'pipe' }); child.stdout.on('data', (data) => { context.logger.info(data.toString()); }); child.stderr.on('data', (data) => { context.logger.error(data.toString()); }); return new Promise<BuilderOutput>(resolve => { child.on('close', code => { resolve({ success: code === 0 }); }); }); }); 

Laporan Kinerja dan Status


Bagian terakhir dari API terkait dengan penerapan kolektor Anda sendiri adalah laporan kemajuan dan status saat ini.

Dalam kasus kami, perintahnya sudah selesai atau dijalankan, jadi tidak masuk akal untuk menambahkan laporan kemajuan. Namun, kami dapat mengomunikasikan status kami kepada kolektor induk sehingga ia memahami apa yang terjadi.

 import { BuilderOutput, createBuilder } from '@angular-devkit/architect'; import * as childProcess from 'child_process'; export default createBuilder((options, context) => { context.reportStatus(`Executing "${options.command}"...`); const child = childProcess.spawn(options.command, options.args, { stdio: 'pipe' }); child.stdout.on('data', (data) => { context.logger.info(data.toString()); }); child.stderr.on('data', (data) => { context.logger.error(data.toString()); }); return new Promise<BuilderOutput>(resolve => { context.reportStatus(`Done.`); child.on('close', code => { resolve({ success: code === 0 }); }); }); }); 

Untuk melewati laporan kemajuan , gunakan metode reportProgress dengan nilai ringkasan saat ini dan (opsional) sebagai argumen. total bisa berupa angka apa saja. Misalnya, jika Anda tahu berapa banyak file yang perlu Anda proses, Anda dapat mentransfer jumlahnya ke total , kemudian ke saat ini Anda dapat mentransfer jumlah file yang sudah diproses. Ini adalah bagaimana pengumpul tslint melaporkan kemajuannya.

Input Validasi


Objek opsi yang diteruskan ke kolektor diperiksa menggunakan Skema JSON. Ini mirip dengan Skema jika Anda tahu apa itu.

Dalam contoh kolektor kami, kami berharap bahwa parameter kami akan menjadi objek yang menerima dua kunci: command - command (string) dan args - arguments (array of strings). Skema verifikasi kami akan terlihat seperti ini:

 { "$schema": "http://json-schema.org/schema", "type": "object", "properties": { "command": { "type": "string" }, "args": { "type": "array", "items": { "type": "string" } } } 

Skema adalah alat yang sangat kuat yang dapat melakukan sejumlah besar pemeriksaan. Untuk informasi lebih lanjut tentang skema JSON, Anda dapat merujuk ke situs web skema JSON resmi .

Buat paket build


Ada satu file kunci yang perlu kita buat untuk kolektor kita sendiri agar membuatnya kompatibel dengan Angular CLI - builders.json , yang bertanggung jawab atas hubungan antara implementasi kolektor, nama, dan skema verifikasi kami. File itu sendiri terlihat seperti ini:

 { "builders": { "command": { "implementation": "./command", "schema": "./command/schema.json", "description": "Runs any command line in the operating system." } } } 

Kemudian, dalam file package.json , kami menambahkan kunci builders , menunjuk ke file builders.json :

 { "name": "@example/command-runner", "version": "1.0.0", "description": "Builder for Architect", "builders": "builders.json", "devDependencies": { "@angular-devkit/architect": "^1.0.0" } } 

Ini akan memberi tahu Arsitek tempat mencari file definisi kolektor.

Dengan demikian, nama pengumpul kami adalah "@ example / runner-run: command" . Bagian pertama dari nama, sebelum titik dua (:) adalah nama paket, didefinisikan menggunakan package.json . Bagian kedua adalah nama kolektor, didefinisikan menggunakan file builders.json .

Menguji pembangun Anda sendiri


Cara yang disarankan untuk menguji assembler adalah melalui pengujian integrasi. Ini karena membuat konteks tidak mudah, jadi Anda harus menggunakan penjadwal dari Arsitek.

Untuk menyederhanakan pola, kami memikirkan cara sederhana untuk membuat contoh Arsitek: pertama Anda membuat JsonSchemaRegistry (untuk menguji skema), lalu TestingArchitectHost dan, akhirnya, contoh Arsitek . Sekarang Anda dapat mengkompilasi file konfigurasi builders.json .

Berikut adalah contoh menjalankan kolektor, yang menjalankan perintah ls dan memverifikasi bahwa perintah selesai dengan sukses. Harap dicatat bahwa kami akan menggunakan output proses standar dalam logger .

 import { Architect, ArchitectHost } from '@angular-devkit/architect'; import { TestingArchitectHost } from '@angular-devkit/architect/testing'; import { logging, schema } from '@angular-devkit/core'; describe('Command Runner Builder', () => { let architect: Architect; let architectHost: ArchitectHost; beforeEach(async () => { const registry = new schema.CoreSchemaRegistry(); registry.addPostTransform(schema.transforms.addUndefinedDefaults); //  TestingArchitectHost –    . //     ,   . architectHost = new TestingArchitectHost(__dirname, __dirname); architect = new Architect(architectHost, registry); //      NPM-, //    package.json  . await architectHost.addBuilderFromPackage('..'); }); //      Windows it('can run ls', async () => { //  ,     . const logger = new logging.Logger(''); const logs = []; logger.subscribe(ev => logs.push(ev.message)); // "run"    ,       . const run = await architect.scheduleBuilder('@example/command-runner:command', { command: 'ls', args: [__dirname], }, { logger }); // "result" –    . //    "BuilderOutput". const output = await run.result; //  . Architect     //   ,    ,    . await run.stop(); //   . expect(output.success).toBe(true); // ,     . // `ls $__dirname`. expect(logs).toContain('index_spec.ts'); }); }); 

Untuk menjalankan contoh di atas, Anda memerlukan paket ts-node . Jika Anda bermaksud menggunakan Node, ganti nama index_spec.ts menjadi index_spec.js .

Menggunakan kolektor dalam suatu proyek


Mari kita buat angular.json sederhana yang menunjukkan semua yang kita pelajari tentang assembler. Dengan asumsi kami mengemas kolektor kami dalam contoh / command-runner dan kemudian membuat aplikasi baru menggunakan tes builder baru , file angular.json mungkin terlihat seperti ini (beberapa konten telah dihapus karena singkatnya):

 { // ...   . "projects": { // ... "builder-test": { // ... "architect": { // ... "build": { "builder": "@angular-devkit/build-angular:browser", "options": { // ...   "outputPath": "dist/builder-test", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json" }, "configurations": { "production": { // ...   "optimization": true, "aot": true, "buildOptimizer": true } } } } 

Jika kami memutuskan untuk menambahkan tugas baru untuk menerapkan (misalnya) perintah sentuh ke file (memperbarui tanggal modifikasi file) menggunakan kolektor kami, kami akan menjalankan npm install example / command-runner , dan kemudian membuat perubahan ke angular.json :

 { "projects": { "builder-test": { "architect": { "touch": { "builder": "@example/command-runner:command", "options": { "command": "touch", "args": [ "src/main.ts" ] } }, "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/builder-test", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json" }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "aot": true, "buildOptimizer": true } } } } } } } 

CLI Angular memiliki perintah jalankan , yang merupakan perintah utama untuk menjalankan kolektor. Sebagai argumen pertama, dibutuhkan string dengan format PROYEK: TUGAS [: KONFIGURASI] . Untuk menjalankan tugas kita, kita dapat menggunakan perintah ng run builder-test: touch .

Sekarang kita mungkin ingin mendefinisikan kembali beberapa argumen. Sayangnya, kami tidak dapat mendefinisikan ulang array dari baris perintah sejauh ini, namun kami dapat mengubah perintah itu sendiri untuk demonstrasi: ng run builder-test: touch --command = ls . - Ini akan menampilkan file src / main.ts.

Mode Tonton


Secara default, diasumsikan bahwa kolektor akan dipanggil sekali dan diakhiri, namun, mereka dapat mengembalikan Observable untuk menerapkan mode pengamatan sendiri (seperti yang dilakukan kolektor Webpack ). Arsitek akan berlangganan Observable sampai selesai atau berhenti dan dapat berlangganan ke kolektor lagi jika kolektor dipanggil dengan parameter yang sama (walaupun tidak dijamin).

  1. Kolektor harus mengembalikan objek BuilderOutput setelah setiap eksekusi. Setelah selesai, ia dapat memasuki mode pengamatan yang disebabkan oleh peristiwa eksternal dan, jika dimulai lagi, ia harus memanggil fungsi context.reportRunning () untuk memberi tahu Arsitek bahwa kolektor bekerja lagi. Ini akan melindungi kolektor dari menghentikannya oleh Arsitek pada panggilan baru.
  2. Arsitek sendiri berhenti berlangganan dari Observable ketika kolektor berhenti (menggunakan run.stop (), misalnya), menggunakan logika Teardown - algoritma penghancuran. Ini akan memungkinkan Anda untuk berhenti dan menghapus perakitan jika proses ini sudah berjalan.

Ringkas hal di atas, jika kolektor Anda menonton acara eksternal, ia bekerja dalam tiga tahap:

  1. Pemenuhan. Misalnya, kompilasi Webpack. Langkah ini berakhir ketika Webpack selesai membangun dan kolektor Anda mengirimkan BuilderOutput ke Observable .
  2. Pengamatan. - Di antara dua peluncuran, acara eksternal dipantau. Sebagai contoh, Webpack memonitor sistem file untuk setiap perubahan. Langkah ini berakhir ketika Webpack melanjutkan build dan context.reportRunning () dipanggil. Setelah langkah ini, langkah 1 dimulai lagi.
  3. Penyelesaian. - Tugas selesai sepenuhnya (misalnya, diharapkan bahwa Webpack akan memulai beberapa kali tertentu) atau mulai dari kolektor dihentikan (menggunakan run.stop () ). Dalam hal ini, algoritme penghancuran yang dapat diamati dijalankan, dan dihapus.

Kesimpulan


Berikut ini ringkasan dari apa yang kami pelajari dalam publikasi ini:

  1. Kami menyediakan API baru yang akan memungkinkan pengembang untuk mengubah perilaku perintah CLI Angular dan menambahkan yang baru menggunakan assembler yang menerapkan logika yang diperlukan.
  2. Kolektor dapat sinkron, asinkron, dan responsif terhadap peristiwa eksternal. Mereka dapat dipanggil beberapa kali, serta oleh kolektor lain.
  3. Parameter yang diterima kolektor ketika tugas dimulai pertama kali dibaca dari file angular.json , kemudian ditimpa oleh parameter dari konfigurasi, jika ada, dan kemudian ditimpa oleh bendera baris perintah jika ditambahkan.
  4. Cara yang disarankan untuk menguji pengumpul adalah melalui uji integrasi, namun Anda dapat melakukan pengujian unit secara terpisah dari logika pengumpul.
  5. Jika kolektor mengembalikan sebuah Observable, itu harus dibersihkan setelah melewati algoritma penghancuran.

Dalam waktu dekat, frekuensi penggunaan API ini akan meningkat. Misalnya, implementasi Bazel sangat terkait dengan mereka.

Kami sudah melihat bagaimana komunitas membuat kolektor CLI baru untuk digunakan, misalnya, canda dan cemara untuk pengujian.

Source: https://habr.com/ru/post/id450746/


All Articles