Hari ini kami menerbitkan bagian pertama dari terjemahan materi, yang didedikasikan untuk membuat konstruksi sintaks Anda sendiri untuk JavaScript menggunakan Babel.

Ulasan
Pertama, mari kita lihat apa yang akan kita capai ketika kita sampai pada akhir materi ini:
Kami akan mengimplementasikan sintaks
@@
yang memungkinkan fungsi
currying . Sintaks ini mirip dengan yang digunakan untuk membuat
fungsi generator , tetapi dalam kasus kami, alih-alih tanda
*
, urutan karakter
@@
ditempatkan antara kata kunci
function
dan nama
function
. Akibatnya, saat mendeklarasikan fungsi, Anda bisa menggunakan konstruksi dari form
function @@ name(arg1, arg2)
.
Dalam contoh di atas, ketika bekerja dengan fungsi
foo
, Anda dapat menggunakan
aplikasi parsialnya . Memanggil fungsi
foo
dengan melewatkannya begitu banyak parameter yang kurang dari jumlah argumen yang dibutuhkan, akan mengembalikan fungsi baru yang dapat mengambil argumen yang tersisa:
foo(1, 2, 3);
Saya memilih urutan karakter
@@
karena simbol
@
tidak dapat digunakan dalam nama variabel. Ini berarti bahwa konstruk
function@@foo(){}
form
function@@foo(){}
juga akan benar secara sintaksis. Selain itu, "operator"
@
digunakan untuk
fungsi dekorator , dan saya ingin menggunakan sesuatu yang sama sekali baru. Akibatnya, saya memilih konstruksi
@@
.
Untuk mencapai tujuan kami, kami perlu melakukan tindakan berikut:
- Buat garpu parser Babel.
- Buat plugin Babel Anda sendiri untuk transformasi kode.
Sepertinya ada yang mustahil?
Faktanya, tidak ada yang mengerikan di sini, kami akan menganalisis semuanya secara detail bersama. Saya harap ketika Anda membaca ini, Anda akan dengan ahli menguasai seluk-beluk Babel.
Membuat garpu Babel
Pergi ke
gudang Babel di GitHub dan klik tombol
Fork
, yang terletak di kiri atas halaman.
Membuat garpu Babel ( gambar ukuran penuh )Dan omong-omong, jika Anda baru saja membuat garpu proyek open source populer untuk pertama kalinya - selamat!
Sekarang kloning garpu Babel di komputer Anda dan
persiapkan untuk bekerja .
$ git clone https:
Sekarang izinkan saya berbicara secara singkat tentang pengaturan repositori Babel.
Babel menggunakan monorepositori. Semua paket (mis.
@babel/core
,
@babel/parser
,
@babel/plugin-transform-react-jsx
dan sebagainya) berada di
packages/
folder. Ini terlihat seperti ini:
- doc - packages - babel-core - babel-parser - babel-plugin-transform-react-jsx - ... - Gulpfile.js - Makefile - ...
Saya perhatikan bahwa Babel menggunakan
Makefile untuk mengotomatisasi tugas. Saat membangun proyek dengan perintah
make build
,
Gulp digunakan sebagai task manager.
Konversi Kode ke Kursus Singkat AST
Jika Anda tidak terbiasa dengan konsep-konsep seperti "parser" dan "Pohon Sintaksis Abstrak" (AST), maka sebelum Anda melanjutkan membaca, saya sangat menyarankan Anda melihat materi
ini .
Jika Anda berbicara dengan sangat singkat tentang apa yang terjadi ketika parsing (parsing) kode, Anda mendapatkan yang berikut ini:
- Kode yang disajikan sebagai string (tipe
string
) terlihat seperti daftar panjang karakter: f, u, n, c, t, i, o, n, , @, @, f, ...
- Pada awalnya, Babel melakukan tokenization kode. Pada langkah ini, Babel memindai kode dan membuat token. Misalnya, sesuatu seperti
function, @@, foo, (, a, ...
- Kemudian token dilewatkan melalui parser untuk parsing mereka. Di sini Babel, berdasarkan spesifikasi bahasa JavaScript, membuat pohon sintaksis abstrak.
Ini adalah sumber yang bagus untuk mereka yang ingin belajar lebih banyak tentang kompiler.
Jika Anda berpikir bahwa "kompiler" adalah sesuatu yang sangat kompleks dan tidak dapat dipahami, maka ketahuilah bahwa pada kenyataannya semuanya tidak begitu misterius. Kompilasi hanya menguraikan kode dan membuat kode baru atas dasar, yang akan kita sebut XXX. Kode XXX dapat diwakili oleh kode mesin (mungkin, kode mesin adalah yang pertama kali muncul di benak sebagian besar dari kita ketika kita berpikir tentang kompiler). Ini mungkin kode JavaScript yang kompatibel dengan browser lawas. Sebenarnya, salah satu fungsi utama Babel adalah kompilasi JS-code modern menjadi kode yang dapat dimengerti oleh browser yang sudah ketinggalan zaman.
Mengembangkan parser Anda sendiri untuk Babel
Kita akan bekerja di
packages/babel-parser/
folder:
- src/ - tokenizer/ - parser/ - plugins/ - jsx/ - typescript/ - flow/ - ... - test/
Kita sudah bicara tentang tokenization dan parsing. Anda dapat menemukan kode yang mengimplementasikan proses ini di folder dengan nama yang sesuai.
plugins/
folder berisi plugins (plug-in) yang memperluas kemampuan parser dasar dan menambahkan dukungan untuk sintaks tambahan ke dalam sistem. Itulah tepatnya, misalnya, dukungan
jsx
dan
flow
diimplementasikan.
Mari kita selesaikan masalah kita menggunakan teknologi
pengembangan melalui pengujian (Test-driven development, TDD). Menurut pendapat saya, yang paling mudah adalah menulis tes terlebih dahulu, dan kemudian, secara bertahap bekerja pada sistem, buat tes ini berjalan tanpa kesalahan. Pendekatan ini sangat baik ketika bekerja di basis kode yang tidak dikenal. TDD memudahkan untuk memahami di mana Anda perlu membuat perubahan pada kode untuk mengimplementasikan fungsi yang Anda inginkan.
packages/babel-parser/test/curry-function.js import { parse } from '../lib'; function getParser(code) { return () => parse(code, { sourceType: 'module' }); } describe('curry function syntax', function() { it('should parse', function() { expect(getParser(`function @@ foo() {}`)()).toMatchSnapshot(); }); });
Anda dapat menjalankan tes untuk
babel-parser
seperti ini:
TEST_ONLY=babel-parser TEST_GREP="curry function" make test-only
. Ini memungkinkan Anda melihat kesalahan:
SyntaxError: Unexpected token (1:9) at Parser.raise (packages/babel-parser/src/parser/location.js:39:63) at Parser.raise [as unexpected] (packages/babel-parser/src/parser/util.js:133:16) at Parser.unexpected [as parseIdentifierName] (packages/babel-parser/src/parser/expression.js:2090:18) at Parser.parseIdentifierName [as parseIdentifier] (packages/babel-parser/src/parser/expression.js:2052:23) at Parser.parseIdentifier (packages/babel-parser/src/parser/statement.js:1096:52)
Jika Anda merasa bahwa melihat semua tes terlalu banyak waktu, Anda dapat, untuk menjalankan tes yang diinginkan, langsung menelepon
jest
:
BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/curry-function.js
Pengurai kami menemukan 2
@
token, yang tampaknya sama sekali tidak bersalah, di mana seharusnya tidak.
Bagaimana saya tahu itu? Jawaban atas pertanyaan ini akan membantu kami menemukan penggunaan mode pemantauan kode yang diluncurkan oleh perintah
make watch
.
Melihat tumpukan panggilan membawa kita ke
paket / babel-parser / src / parser / expression.js , di mana pengecualian
this.unexpected()
dilemparkan.
Tambahkan beberapa perintah masuk ke file ini:
packages/babel-parser/src/parser/expression.js parseIdentifierName(pos: number, liberal?: boolean): string { if (this.match(tt.name)) {
Seperti yang Anda lihat, kedua token adalah
@
:
TokenType { label: '@',
Bagaimana saya mengetahui bahwa konstruksi
this.state.type
dan
this.lookahead().type
akan memberi saya token saat ini dan selanjutnya?
Saya akan membicarakan hal ini di bagian materi yang dikhususkan untuk fungsi
this.eat
,
this.match
dan
this.next
.
Sebelum melanjutkan, mari rangkum:
- Kami menulis tes untuk
babel-parser
. - Kami menjalankan tes menggunakan
make test-only
. - Kami menggunakan mode pemantauan kode menggunakan
make watch
. - Kami belajar tentang status pengurai dan
this.state.type
informasi tentang jenis token saat ini ( this.state.type
) di konsol.
Dan sekarang kami akan memastikan bahwa 2
@
karakter tidak dianggap sebagai token terpisah, tetapi sebagai token
@@
baru, yang kami putuskan untuk digunakan untuk fungsi kari.
Token baru: "@@"
Pertama, mari kita lihat di mana jenis token ditentukan. Ini adalah
paket file
/ babel-parser / src / tokenizer / types.js .
Di sini Anda dapat menemukan daftar token. Tambahkan di sini definisi
atat
baru:
packages/babel-parser/src/tokenizer/types.js export const types: { [name: string]: TokenType } = {
Sekarang mari kita mencari tempat di kode di mana, dalam proses tokenization, token dibuat. Mencari urutan karakter
tt.at
di
babel-parser/src/tokenizer
membawa kita ke file:
paket / babel-parser / src / tokenizer / index.js . Dalam
babel-parser
tipe token diimpor sebagai
tt
.
Sekarang, jika setelah simbol
@
saat ini datang
@
lain, buat token
tt.atat
alih-
tt.at
token
tt.at
:
packages/babel-parser/src/tokenizer/index.js getTokenFromCode(code: number): void { switch (code) {
Jika Anda menjalankan tes lagi, Anda akan melihat bahwa informasi tentang token saat ini dan selanjutnya telah berubah:
Itu sudah terlihat cukup bagus. Kami akan melanjutkan pekerjaan.
Parser baru
Sebelum melanjutkan, lihat bagaimana fungsi generator diwakili dalam AST.
AST untuk fungsi generator ( gambar ukuran penuh )Seperti yang Anda lihat,
generator: true
atribut
generator: true
dari entitas
FunctionDeclaration
menunjukkan bahwa ini adalah
FunctionDeclaration
generator.
Kita dapat mengambil pendekatan serupa untuk menggambarkan fungsi yang mendukung currying. Yaitu, kita dapat menambahkan atribut
curry: true
ke
FunctionDeclaration
.
AST untuk fungsi currying ( gambar ukuran penuh )Sebenarnya, sekarang kami punya rencana. Mari kita hadapi implementasinya.
Jika Anda melihat kode untuk kata
FunctionDeclaration
, Anda dapat pergi ke fungsi
parseFunction
, yang dideklarasikan dalam
paket / babel-parser / src / parser / statement.js . Di sini Anda dapat menemukan garis tempat atribut
generator
diatur. Tambahkan baris lain ke kode:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser {
Jika kita menjalankan tes lagi, kejutan yang menyenangkan akan menunggu kita. Kode berhasil diuji!
PASS packages/babel-parser/test/curry-function.js curry function syntax ✓ should parse (12ms)
Hanya itu semua Apa yang telah kami lakukan untuk membuat ujian lulus secara ajaib?
Untuk mengetahuinya, mari kita bicara tentang cara kerja parsing. Dalam perjalanan percakapan ini, saya harap Anda akan mengerti bagaimana garis
node.curry = this.eat(tt.atat);
.
Dilanjutkan ...
Pembaca yang budiman! Apakah Anda menggunakan babel?
