Hari ini, kami menerbitkan bagian kedua dari terjemahan ekstensi sintaksis JavaScript menggunakan Babel.

โ Dizzy
bagian pertamaCara kerja parsing
Parser menerima daftar token dari sistem tokenization kode dan, mempertimbangkan token satu per satu, membangun AST. Untuk membuat keputusan tentang bagaimana menggunakan token dan memahami token yang dapat diharapkan selanjutnya, parser merujuk pada spesifikasi tata bahasa bahasa.
Spesifikasi tata bahasa terlihat seperti ini:
... ExponentiationExpression -> UnaryExpression UpdateExpression ** ExponentiationExpression MultiplicativeExpression -> ExponentiationExpression MultiplicativeExpression ("*" or "/" or "%") ExponentiationExpression AdditiveExpression -> MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression ...
Ini menggambarkan prioritas mengeksekusi ekspresi atau pernyataan. Misalnya, ekspresi
AdditiveExpression
dapat mewakili salah satu konstruksi berikut:
- Expression
MultiplicativeExpression
. - Ekspresi
AdditiveExpression
, diikuti oleh operator +
token, diikuti oleh ekspresi MultiplicativeExpression
. - Ekspresi
AdditiveExpression
, diikuti oleh token โ -
โ, diikuti oleh ekspresi MultiplicativeExpression
.
Akibatnya, jika kita memiliki ekspresi
1 + 2 * 3
, maka akan terlihat seperti ini:
(AdditiveExpression "+" 1 (MultiplicativeExpression "*" 2 3))
Tetapi tidak akan demikian:
(MultiplicativeExpression "*" (AdditiveExpression "+" 1 2) 3)
Program, menggunakan aturan-aturan ini, dikonversi menjadi kode yang dikeluarkan oleh parser:
class Parser {
Harap dicatat bahwa di sini adalah versi yang sangat disederhanakan dari apa yang sebenarnya ada di Babel. Tetapi saya berharap bahwa potongan kode ini memungkinkan kita untuk menggambarkan esensi dari apa yang terjadi.
Seperti yang Anda lihat, pengurai pada dasarnya bersifat rekursif. Ini bergerak dari desain prioritas terendah ke desain prioritas tertinggi. Misalnya,
parseAdditiveExpression
memanggil
parseMultiplicativeExpression
, dan konstruk ini memanggil
parseExponentiationExpression
dan seterusnya. Proses rekursif ini disebut
Parsing Keturunan Rekursif .
Fungsi this.eat, this.match, this.next
Anda mungkin telah memperhatikan bahwa dalam contoh sebelumnya beberapa fungsi tambahan digunakan, seperti
this.eat
,
this.match
,
this.next
dan lainnya. Ini adalah fungsi internal parser Babel. Fungsi-fungsi ini, bagaimanapun, tidak unik untuk Babel, mereka biasanya hadir di parser lain.
- Fungsi
this.match
mengembalikan nilai boolean yang menunjukkan apakah token saat ini memenuhi kondisi yang ditentukan. - Fungsi
this.next
maju dalam daftar token ke token berikutnya. - Fungsi
this.eat
mengembalikan sama dengan fungsi this.match
, dan jika this.match
mengembalikan true
, maka this.eat
melakukan, sebelum mengembalikan true
, panggilan ke this.next
. - Fungsi
this.lookahead
memungkinkan Anda untuk mendapatkan token berikutnya tanpa bergerak maju, yang membantu untuk membuat keputusan pada node saat ini.
Jika Anda melihat kembali kode parser yang kami ubah, Anda akan menemukan bahwa membacanya menjadi lebih mudah:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser { parseStatementContent() {
Saya tahu bahwa saya tidak masuk jauh ke dalam menjelaskan fitur parser. Oleh karena itu,
di sana -
sini - beberapa sumber daya yang berguna tentang topik ini. Saya belajar banyak dari mereka dan saya dapat merekomendasikan mereka kepada Anda.
Anda mungkin tertarik mempelajari tentang bagaimana saya dapat memvisualisasikan sintaks yang saya buat di Babel AST Explorer ketika saya menunjukkan atribut "
curry
" baru yang muncul di AST.
Ini menjadi mungkin karena fakta bahwa saya menambahkan fitur baru di Babel AST Explorer yang memungkinkan Anda memuat parser Anda sendiri ke alat penelitian AST ini.
Jika Anda mengikuti
packages/babel-parser/lib
path
packages/babel-parser/lib
, Anda dapat menemukan versi kompilasi dari parser dan peta kode. Di panel
Babel AST Explorer
, Anda dapat melihat tombol untuk memuat parser Anda sendiri. Dengan mengunduh
packages/babel-parser/lib/index.js
Anda dapat memvisualisasikan AST yang dihasilkan menggunakan parser Anda sendiri.
Visualisasi ASTPlugin kami untuk Babel
Sekarang setelah pengurai selesai, mari kita menulis sebuah plugin untuk Babel.
Tapi, mungkin sekarang Anda memiliki keraguan tentang bagaimana tepatnya kita akan menggunakan parser Babel kita sendiri, terutama mengingat tumpukan teknologi yang kita gunakan untuk membangun proyek.
Benar, tidak ada yang perlu ditakutkan. Plugin Babel dapat memberikan kemampuan parser.
Dokumentasi terkait dapat ditemukan di situs web Babel.
babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() { return { parserOverride(code, opts) { return customParser.parse(code, opts); }, }; }
Karena kami membuat garpu parser Babel, ini berarti bahwa semua fitur parser yang ada, serta plugin bawaan, akan terus berfungsi dengan baik.
Setelah kita menghilangkan keraguan ini, mari kita lihat bagaimana membuat fungsi sedemikian rupa sehingga mendukung currying.
Jika Anda tidak tahan dengan harapan dan sudah mencoba menambahkan plug-in kami ke sistem build proyek Anda, Anda mungkin memperhatikan bahwa fungsi yang mendukung currying dikompilasi menjadi fungsi biasa.
Ini terjadi karena, setelah parsing dan mengubah kode, Babel menggunakan
@babel/generator
untuk menghasilkan kode dari AST yang diubah. Karena
@babel/generator
tidak tahu apa-apa tentang atribut
curry
baru, itu hanya mengabaikannya.
Jika suatu hari nanti fungsi yang mendukung currying masuk ke standar JavaScript, maka Anda mungkin ingin melakukan PR untuk menambahkan kode baru di
sini .
Untuk membuat fungsi mendukung kari, Anda bisa membungkusnya dengan
currying
fungsi urutan lebih tinggi:
function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); }
Jika Anda tertarik pada fitur implementasi mekanisme fungsi currying di JS - lihat materi
ini .
Akibatnya, kami, mentransformasikan fungsi yang mendukung currying, dapat melakukan ini:
Untuk saat ini, kami tidak akan memperhatikan mekanisme
meningkatkan fungsi dalam JavaScript, yang memungkinkan Anda untuk memanggil fungsi
foo
sebelum didefinisikan.
Seperti apa bentuk kode transformasi:
babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
Akan lebih mudah bagi Anda untuk mengetahuinya jika Anda membaca materi
ini tentang transformasi dalam Babel.
Sekarang kita dihadapkan dengan pertanyaan tentang bagaimana menyediakan mekanisme ini dengan akses ke fungsi
currying
. Di sini Anda dapat menggunakan salah satu dari dua pendekatan.
โ Pendekatan No. 1: dapat diasumsikan bahwa fungsi currying dinyatakan dalam lingkup global
Jika demikian, maka pekerjaan sudah selesai.
Jika, ketika mengeksekusi kode yang dikompilasi, ternyata fungsi
currying
tidak didefinisikan, maka kita akan menjumpai pesan kesalahan yang terlihat seperti "
currying is not defined
". Ini sangat mirip dengan pesan "
regeneratorRuntime tidak didefinisikan ".
Oleh karena itu, jika seseorang menggunakan
babel-plugin-transformation-curry-function
Anda, Anda mungkin perlu memberi tahu dia bahwa dia perlu menginstal polyfill curry untuk memastikan plugin ini berfungsi dengan baik.
โ Pendekatan # 2: Anda dapat menggunakan babel / helper
Anda dapat menambahkan fungsi pembantu baru ke
@babel/helpers
. Perkembangan ini tidak mungkin digabungkan dengan
@babel/helpers
resmi
@babel/helpers
. Akibatnya, Anda harus menemukan cara untuk menunjukkan
@babel/core
lokasi
@babel/helpers
:
package.json { "resolutions": { "@babel/helpers": "7.6.0--your-custom-forked-version", }
Saya belum mencoba ini sendiri, tetapi saya percaya bahwa mekanisme ini akan berhasil. Jika Anda mencobanya dan mengalami masalah, saya akan
membahasnya dengan senang hati.
@babel/helpers
fungsi pembantu baru ke
@babel/helpers
sangat sederhana.
Pertama, buka file
paket / babel-helpers / src / helpers.js dan tambahkan entri baru:
helpers.currying = helper("7.6.0")` export default function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); } `;
Saat mendeskripsikan fungsi tambahan, versi yang diperlukan
@babel/core
diindikasikan. Beberapa kesulitan di sini mungkin disebabkan oleh
export default
fungsi
currying
.
Untuk menggunakan fungsi pembantu, cukup panggil
this.addHelper()
:
Perintah
this.addHelper
, jika perlu, akan menyematkan fungsi helper di bagian atas file dan mengembalikan
Identifier
menunjukkan fungsi yang diimplementasikan.
Catatan
Saya telah terlibat dalam mengerjakan Babel selama beberapa waktu, tetapi saya belum harus menambahkan fitur untuk mendukung sintaks JavaScript baru ke parser. Saya terutama bekerja memperbaiki bug dan meningkatkan apa yang relevan dengan fitur bahasa resmi.
Namun, untuk beberapa waktu sekarang saya sibuk dengan gagasan untuk menambahkan konstruksi sintaks baru ke bahasa. Akibatnya, saya memutuskan untuk menulis materi tentang itu dan mencobanya. Sangat menyenangkan melihat bahwa semuanya bekerja persis seperti yang diharapkan.
Kemampuan untuk mengendalikan sintaksis bahasa yang Anda gunakan adalah sumber inspirasi yang kuat. Ini memungkinkan, dengan mengimplementasikan beberapa konstruksi kompleks, untuk menulis lebih sedikit kode, atau untuk menulis kode yang lebih sederhana daripada sebelumnya. Mekanisme untuk mengubah kode sederhana menjadi konstruksi kompleks diotomatisasi dan dipindahkan ke tahap kompilasi. Ini mengingatkan bagaimana
async/await
menyelesaikan masalah panggilan balik neraka dan rantai panjang janji.
Ringkasan
Di sini kita berbicara tentang bagaimana memodifikasi kemampuan parser Babel, kami menulis plugin transformasi kode kami sendiri, berbicara secara singkat tentang
@babel/generator
dan tentang membuat fungsi pembantu menggunakan
@babel/helpers
. Informasi mengenai transformasi kode hanya diberikan secara skematis. Baca lebih lanjut tentang mereka di
sini .
Dalam prosesnya, kami menyentuh beberapa fitur parser. Jika Anda tertarik dengan topik ini - lalu, di
sini dan di
sana - sumber daya yang berguna bagi Anda.
Urutan tindakan yang kami lakukan sangat mirip dengan bagian dari proses yang dilakukan ketika fitur JavaScript baru dikirimkan ke TC39.
Ini adalah halaman repositori TC39 tempat Anda dapat menemukan informasi tentang penawaran terkini.
Di sini Anda dapat menemukan informasi lebih rinci tentang cara bekerja dengan penawaran serupa. Saat mengajukan fitur JavaScript baru, orang yang menawarkannya biasanya menulis polyfill atau, dengan mengarang Babel, menyiapkan demonstrasi yang membuktikan bahwa kalimat itu berfungsi. Seperti yang Anda lihat, membuat garpu pengurai atau menulis polyfill bukan bagian yang paling sulit dari proses pengajuan fitur JS baru. Sulit untuk menentukan bidang subjek inovasi, untuk merencanakan dan memikirkan opsi untuk penggunaannya dan kasus batas; Sulit untuk mengumpulkan pendapat dan saran dari anggota komunitas programmer JavaScript. Oleh karena itu, saya ingin mengucapkan terima kasih kepada semua orang yang menemukan kekuatan untuk menawarkan fitur JavaScript baru TC39, sehingga mengembangkan bahasa ini.
Ini adalah halaman di GitHub yang memungkinkan Anda melihat gambaran besar dari apa yang kami lakukan di sini.
Pembaca yang budiman! Pernahkah Anda ingin memperluas sintaksis JavaScript?
