تاریخ انتشار: 31 ژانویه 2025
تصور کنید یک وبلاگ کاملاً کاربردی را در مرورگر خود اجرا کنید - نه فقط قسمت ظاهری، بلکه باطن نیز. هیچ سرور یا ابری درگیر نیست - فقط شما، مرورگرتان و... WebAssembly ! WebAssembly با اجازه دادن به فریمورکهای سمت سرور برای اجرای محلی، مرزهای توسعه وب کلاسیک را محو میکند و امکانات جدید هیجانانگیزی را باز میکند. در این پست، ولادیمیر دمنتیف (رئیس Backend در Evil Martians ) پیشرفت در ساخت Ruby on Rails Wasm و مرورگر را به اشتراک می گذارد:
- چگونه Rails را در 15 دقیقه وارد مرورگر کنیم.
- پشت صحنه Wamification Rails.
- آینده ریل و واسم.
"وبلاگ در 15 دقیقه" معروف Ruby on Rails اکنون مستقیماً در مرورگر شما اجرا می شود
Ruby on Rails یک چارچوب وب است که بر بهره وری توسعه دهندگان و ارسال سریع چیزها متمرکز شده است. این فناوری است که توسط رهبران صنعت مانند GitHub و Shopify استفاده می شود. محبوبیت این فریم ورک سال ها پیش با انتشار ویدیوی معروف «چگونه در 15 دقیقه وبلاگ بسازیم» که توسط دیوید هاین مایر هانسون (یا DHH) منتشر شد، آغاز شد. در سال 2005، ساختن یک برنامه وب کاملاً کارآمد در مدت زمان کوتاه غیرقابل تصور بود. حس جادویی بود!
امروز، من می خواهم با ایجاد یک برنامه Rails که به طور کامل در مرورگر شما اجرا می شود، این احساس جادویی را به شما بازگردانم. سفر شما با ایجاد یک برنامه پایه Rails به روش معمول شروع می شود و سپس آن را برای Wasm بسته بندی می کند.
پس زمینه: یک "وبلاگ در 15 دقیقه" در خط فرمان
با فرض اینکه Ruby و Ruby on Rails را روی دستگاه خود نصب کردهاید ، با ایجاد یک برنامه جدید Ruby on Rails و داربستبندی برخی عملکردها شروع میکنید (درست مانند ویدیوی اصلی "وبلاگ در 15 دقیقه"):
$ rails new --css=tailwind web_dev_blog
create .ruby-version
...
$ cd web_dev_blog
$ bin/rails generate scaffold Post title:string date:date body:text
create db/migrate/20241217183624_create_posts.rb
create app/models/post.rb
...
$ bin/rails db:migrate
== 20241217183624 CreatePosts: migrating ====================
-- create_table(:posts)
-> 0.0017s
== 20241217183624 CreatePosts: migrated (0.0018s) ===========
حتی بدون دست زدن به پایگاه کد، اکنون می توانید برنامه را اجرا کنید و آن را در عمل مشاهده کنید:
$ bin/dev
=> Booting Puma
=> Rails 8.0.1 application starting in development
...
* Listening on http://127.0.0.1:3000
اکنون، می توانید وبلاگ خود را در http://localhost:3000/posts باز کنید و شروع به نوشتن پست کنید!
شما یک برنامه وبلاگ بسیار ساده اما کاربردی دارید که در عرض چند دقیقه ساخته شده است. این یک برنامه کاربردی تمام پشته و تحت کنترل سرور است: شما یک پایگاه داده ( SQLite ) برای نگهداری داده های خود، یک وب سرور برای رسیدگی به درخواست های HTTP ( Puma ) و یک برنامه Ruby برای حفظ منطق کسب و کار شما، ارائه رابط کاربری و پردازش تعاملات کاربر دارید. در نهایت، یک لایه نازک از جاوا اسکریپت ( Turbo ) برای ساده کردن تجربه مرور وجود دارد.
نسخه ی نمایشی رسمی Rails در جهت استقرار این برنامه بر روی یک سرور فلزی خالی و در نتیجه آماده سازی آن برای تولید ادامه دارد. سفر شما در جهت مخالف ادامه خواهد یافت: به جای اینکه برنامه خود را در جایی دورتر قرار دهید، آن را به صورت محلی "استقرار" خواهید کرد.
سطح بعدی: "وبلاگ در 15 دقیقه" در Wasm
از زمان اضافه شدن WebAssembly، مرورگرها قادر به اجرای نه تنها کد جاوا اسکریپت، بلکه هر کد قابل کامپایل در Wasm شدند. و روبی نیز از این قاعده مستثنی نیست. مطمئنا، Rails چیزی بیشتر از Ruby است، اما قبل از بررسی تفاوتها، اجازه دهید نسخه آزمایشی را ادامه دهیم و برنامه Rails را (فعل ابداع شده توسط کتابخانه wasmify-rails ) کنیم !
برای کامپایل کردن برنامه وبلاگ خود در ماژول Wasm و اجرای آن در مرورگر، فقط باید چند دستور را اجرا کنید.
ابتدا، کتابخانه wasmify-rails را با استفاده از Bundler ( npm
Ruby) نصب میکنید و ژنراتور آن را با استفاده از Rails CLI اجرا میکنید:
$ bundle add wasmify-rails
$ bin/rails wasmify:install
create config/wasmify.yml
create config/environments/wasm.rb
...
info ✅ The application is prepared for Wasm-ificaiton!
دستور wasmify:rails
یک محیط اجرای اختصاصی "wasm" را پیکربندی می کند (علاوه بر محیط های پیش فرض "توسعه"، "تست" و "تولید") و وابستگی های مورد نیاز را نصب می کند. برای برنامه Greenfield Rails، این کافی است تا Wasm-ready شود.
در مرحله بعد، ماژول اصلی Wasm را بسازید که شامل زمان اجرا Ruby، کتابخانه استاندارد و تمام وابستگی های برنامه است:
$ bin/rails wasmify:build
==> RubyWasm::BuildSource(3.3) -- Building
...
==> RubyWasm::CrossRubyProduct(ruby-3.3-wasm32-unknown-wasip1-full-4aaed4fbda7afe0bdf4e22167afd101e) -- done in 47.37s
INFO: Packaging gem: rake-13.2.1
...
INFO: Packaging gem: wasmify-rails-0.2.0
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 73.77 MB
این مرحله ممکن است کمی طول بکشد: شما باید Ruby را از منبع بسازید تا به درستی افزونههای بومی (نوشته شده به زبان C) را از کتابخانههای شخص ثالث پیوند دهید. این اشکال (موقت) در ادامه پست پوشش داده شده است.
ماژول Wasm کامپایل شده فقط پایه ای برای برنامه شماست. شما همچنین باید خود کد برنامه و تمام دارایی ها (به عنوان مثال، تصاویر، CSS، جاوا اسکریپت) را بسته بندی کنید . قبل از انجام بستهبندی، یک برنامه راهانداز اولیه ایجاد کنید که میتوان از آن برای اجرای Wasmified Rails در مرورگر استفاده کرد. برای آن، یک دستور ژنراتور نیز وجود دارد:
$ bin/rails wasmify:pwa
create pwa
create pwa/boot.html
create pwa/boot.js
...
prepend config/wasmify.yml
دستور قبلی یک برنامه کاربردی حداقل PWA را ایجاد می کند که با Vite ساخته شده است که می تواند به صورت محلی برای آزمایش ماژول Rails Wasm کامپایل شده یا به صورت ایستا برای توزیع برنامه استفاده شود.
اکنون، با راهانداز، تنها چیزی که نیاز دارید این است که کل برنامه را در یک باینری Wasm جمع کنید:
$ bin/rails wasmify:pack
...
Packed the application to pwa/app.wasm
Size: 76.2 MB
همین! برنامه لانچر را اجرا کنید و برنامه وبلاگ نویسی Rails خود را ببینید که به طور کامل در مرورگر اجرا می شود:
$ cd pwa/
$ yarn dev
VITE v4.5.5 ready in 290 ms
➜ Local: http://localhost:5173/
به http://localhost:5173 بروید، کمی صبر کنید تا دکمه «راهاندازی» فعال شود و روی آن کلیک کنید—از کار با برنامه Rails که به صورت محلی در مرورگر خود اجرا میشود، لذت ببرید!
آیا اجرای یک برنامه یکپارچه سمت سرور نه تنها بر روی دستگاه بلکه در جعبه ایمنی مرورگر مانند جادویی نیست؟ برای من (با وجود اینکه من "جادوگر" هستم)، هنوز هم مانند یک فانتزی به نظر می رسد. اما هیچ جادویی در کار نیست، فقط پیشرفت تکنولوژی است.
نسخه ی نمایشی
می توانید نسخه ی نمایشی تعبیه شده در مقاله را تجربه کنید یا نسخه ی نمایشی را در یک پنجره مستقل راه اندازی کنید. کد منبع را در GitHub بررسی کنید.
پشت صحنه Rails on Wasm
برای درک بهتر چالشها (و راهحلهای) بستهبندی یک برنامه سمت سرور در ماژول Wasm، بقیه این پست اجزایی که بخشی از این معماری هستند را توضیح میدهد.
یک برنامه وب به چیزهای بیشتری بستگی دارد تا فقط یک زبان برنامه نویسی که برای نوشتن کد برنامه استفاده می شود. هر مؤلفه همچنین باید به محیط استقرار محلی - مرورگر شما آورده شود. چیزی که در مورد نسخه نمایشی "وبلاگ در 15 دقیقه" هیجان انگیز است، این است که می توان بدون بازنویسی کد برنامه به این کار دست یافت. از همین کد برای اجرای برنامه در حالت کلاسیک، سمت سرور و در مرورگر استفاده شد.
یک چارچوب، مانند Ruby on Rails، یک رابط، یک انتزاع برای برقراری ارتباط با اجزای زیرساخت به شما می دهد. بخش زیر نحوه استفاده از معماری چارچوب را برای پاسخگویی به نیازهای خدمات محلی تا حدودی باطنی مورد بحث قرار می دهد.
پایه: یاقوت سرخ
Ruby در سال 2022 رسماً Wasm-ready شد (از نسخه 3.2.0) به این معنی که کد منبع C را می توان در Wasm کامپایل کرد و یک ماشین مجازی Ruby را به هر کجا که می خواهید بیاورید. پروژه ruby.wasm ماژول های از پیش کامپایل شده و اتصالات جاوا اسکریپت را برای اجرای Ruby در مرورگر (یا هر زمان اجرا جاوا اسکریپت دیگری) ارسال می کند. پروژه ruby:wasm همچنین با ابزارهای ساختی همراه است که به شما امکان میدهد یک نسخه روبی سفارشی با وابستگیهای اضافی بسازید - این برای پروژههایی که به کتابخانههایی با پسوند C متکی هستند بسیار مهم است. بله، شما می توانید پسوندهای بومی را در Wasm نیز کامپایل کنید! (خب، هنوز هیچ افزونه ای وجود ندارد، اما اکثر آنها).
در حال حاضر، روبی به طور کامل از رابط سیستم WebAssembly، WASI 0.1 پشتیبانی می کند. WASI 0.2 که شامل Component Model است در حال حاضر در حالت آلفا قرار دارد و چند مرحله مانده به تکمیل. هنگامی که WASI 0.2 پشتیبانی می شود، نیاز فعلی به کامپایل مجدد کل زبان را هر زمان که نیاز به اضافه کردن وابستگی های بومی جدید داشته باشید برطرف می کند: آنها می توانند جزء سازی شوند.
به عنوان یک عارضه جانبی، مدل مؤلفه نیز باید به کاهش اندازه بسته کمک کند. شما میتوانید در مورد توسعه و پیشرفت ruby.wasm از گفتگوی What you can do with Ruby on WebAssembly اطلاعات بیشتری کسب کنید.
بنابراین، بخش روبی معادله Wasm حل می شود. اما Rails به عنوان یک چارچوب وب به تمام اجزای نشان داده شده در نمودار قبلی نیاز دارد. برای یادگیری نحوه قرار دادن سایر مؤلفه ها در مرورگر و پیوند آنها با یکدیگر در Rails به ادامه مطلب بروید.
به یک پایگاه داده در حال اجرا در مرورگر متصل شوید
SQLite3 با یک توزیع رسمی Wasm و یک بسته بندی جاوا اسکریپت مربوطه ارائه می شود، بنابراین آماده است تا در مرورگر جاسازی شود. PostgreSQL for Wasm از طریق پروژه PGlite در دسترس است. بنابراین، شما فقط باید نحوه اتصال به پایگاه داده درون مرورگر را از برنامه Rails on Wasm بیابید.
یک جزء یا فریم فریم از Rails که مسئول مدلسازی دادهها و تعاملات پایگاهداده است، Active Record نامیده میشود (بله، نام آن از الگوی طراحی ORM گرفته شده است). Active Record پیاده سازی واقعی پایگاه داده به زبان SQL را از کد برنامه از طریق آداپتورهای پایگاه داده جدا می کند. خارج از جعبه، Rails به شما آداپتورهای SQLite3، PostgreSQL و MySQL می دهد. با این حال، همه آنها اتصال به پایگاه های داده واقعی موجود در شبکه را فرض می کنند. برای غلبه بر این مشکل، میتوانید آداپتورهای خود را برای اتصال به پایگاههای داده داخلی و درون مرورگر بنویسید!
به این صورت است که آداپتورهای SQLite3 Wasm و PGlite که به عنوان بخشی از پروژه Wasmify Rails پیادهسازی شدهاند، ایجاد میشوند:
- کلاس آداپتور از آداپتور داخلی مربوطه به ارث می رسد (به عنوان مثال،
class PGliteAdapter < PostgreSQLAdapter
)، بنابراین می توانید از آماده سازی پرس و جو واقعی و منطق تجزیه نتایج دوباره استفاده کنید. - به جای اتصال پایگاه داده سطح پایین، از یک شی رابط خارجی استفاده می کنید که در زمان اجرا جاوا اسکریپت زندگی می کند - پلی بین یک ماژول Rails Wasm و یک پایگاه داده.
به عنوان مثال، در اینجا پیاده سازی پل برای SQLite3 Wasm است:
export function registerSQLiteWasmInterface(worker, db, opts = {}) {
const name = opts.name || "sqliteForRails";
worker[name] = {
exec: function (sql) {
let cols = [];
let rows = db.exec(sql, { columnNames: cols, returnValue: "resultRows" });
return {
cols,
rows,
};
},
changes: function () {
return db.changes();
},
};
}
از منظر برنامه، تغییر از یک پایگاه داده واقعی به یک پایگاه داده درون مرورگر فقط به پیکربندی بستگی دارد:
# config/database.yml
development:
adapter: sqlite3
production:
adapter: sqlite3
wasm:
adapter: sqlite3_wasm
js_interface: "sqliteForRails"
کار با پایگاه داده محلی نیاز به تلاش زیادی ندارد. با این حال، اگر همگام سازی داده ها با برخی از منابع مرکزی حقیقت مورد نیاز باشد، ممکن است با چالشی در سطح بالاتر روبرو شوید. این سوال خارج از محدوده این پست است (نکته: نسخه ی نمایشی Rails در PGlite و ElectricSQL را بررسی کنید).
کارگر سرویس به عنوان یک وب سرور
یکی دیگر از اجزای ضروری هر برنامه وب، وب سرور است. کاربران با استفاده از درخواست های HTTP با برنامه های کاربردی وب تعامل دارند. بنابراین، شما به راهی برای مسیریابی درخواستهای HTTP که توسط ناوبری یا ارسالهای فرم به ماژول Wasm خود راهاندازی میشوند، نیاز دارید. خوشبختانه، مرورگر پاسخی برای آن دارد— کارگران خدمات .
Service Worker نوع خاصی از Web Worker است که به عنوان یک پروکسی بین برنامه جاوا اسکریپت و شبکه عمل می کند. میتواند درخواستها را رهگیری کند و آنها را دستکاری کند، برای مثال: دادههای ذخیرهشده را ارائه میکند، به URLهای دیگر هدایت میکند یا... به ماژولهای Wasm! در اینجا طرحی از سرویسی است که با استفاده از یک برنامه Rails که در Wasm اجرا می شود، درخواست ها را ارائه می دهد:
// The vm variable holds a reference to the Wasm module with a
// Ruby VM initialized
let vm;
// The db variable holds a reference to the in-browser
// database interface
let db;
const initVM = async (progress, opts = {}) => {
if (vm) return vm;
if (!db) {
await initDB(progress);
}
vm = await initRailsVM("/app.wasm");
return vm;
};
const rackHandler = new RackHandler(initVM});
self.addEventListener("fetch", (event) => {
// ...
return event.respondWith(
rackHandler.handle(event.request)
);
});
هر بار که درخواستی توسط مرورگر ارسال می شود، "واکشی" فعال می شود. شما می توانید اطلاعات درخواست (URL، هدر HTTP، بدنه) را بدست آورید و شی درخواست خود را بسازید.
Rails، مانند بسیاری از برنامه های کاربردی وب روبی، برای کار با درخواست های HTTP به رابط Rack متکی است. رابط Rack فرمت اشیاء درخواست و پاسخ و همچنین رابط کنترل کننده HTTP (برنامه) زیرین را توصیف می کند. شما می توانید این ویژگی ها را به صورت زیر بیان کنید:
request = {
"REQUEST_METHOD" => "GET",
"SCRIPT_NAME" => "",
"SERVER_NAME" => "localhost",
"SERVER_PORT" => "3000",
"PATH_INFO" => "/posts"
}
handler = proc do |env|
[
200,
{"Content-Type" => "text/html"},
["<!doctype html><html><body>Hello Web!</body></html>"]
]
end
handler.call(request) #=> [200, {...}, [...]]
اگر فرمت درخواست برای شما آشنا بود، احتمالاً در گذشته با CGI کار کرده اید.
شی جاوا اسکریپت RackHandler
مسئول تبدیل درخواست ها و پاسخ ها بین قلمروهای جاوا اسکریپت و روبی است. با توجه به اینکه Rack توسط اکثر برنامه های تحت وب Ruby استفاده می شود، پیاده سازی جهانی می شود، نه مخصوص Rails. اگرچه اجرای واقعی برای پست کردن در اینجا بسیار طولانی است.
یک سرویس دهنده یکی از نکات کلیدی یک برنامه وب درون مرورگر است. این نه تنها یک پروکسی HTTP، بلکه یک لایه کش و یک سوئیچر شبکه نیز هست (یعنی می توانید یک برنامه محلی اول یا آفلاین بسازید). این نیز مؤلفه ای است که می تواند به شما در ارائه فایل های آپلود شده توسط کاربر کمک کند.
آپلود فایل ها را در مرورگر نگه دارید
یکی از اولین ویژگی های اضافی برای پیاده سازی در برنامه جدید وبلاگ شما احتمالاً پشتیبانی از آپلود فایل یا به طور خاص تر، پیوست کردن تصاویر به پست ها است. برای رسیدن به این هدف، شما به راهی برای ذخیره و ارائه فایل ها نیاز دارید.
در Rails، بخشی از چارچوب که مسئول رسیدگی به آپلود فایل است ، Active Storage نامیده می شود. Active Storage به توسعه دهندگان انتزاعات و رابط هایی می دهد تا بدون فکر کردن به مکانیسم ذخیره سازی سطح پایین، با فایل ها کار کنند. مهم نیست که فایل های خود را کجا ذخیره می کنید، روی هارد دیسک یا در فضای ابری، کد برنامه از آن بی اطلاع می ماند.
همانند Active Record، برای پشتیبانی از مکانیزم ذخیره سازی سفارشی، تنها چیزی که نیاز دارید این است که یک آداپتور سرویس ذخیره سازی مربوطه را پیاده سازی کنید. کجا فایل ها را در مرورگر ذخیره کنیم؟
گزینه سنتی استفاده از پایگاه داده است. بله، می توانید فایل ها را به صورت حباب در پایگاه داده ذخیره کنید، بدون نیاز به اجزای زیرساخت اضافی. و در حال حاضر یک افزونه آماده برای آن در Rails، Active Storage Database وجود دارد. با این حال، ارائه فایلهای ذخیرهشده در پایگاه داده از طریق برنامه Rails که در WebAssembly اجرا میشود، ایدهآل نیست، زیرا شامل دورهای (سریالزدایی) است که رایگان نیستند.
یک راه حل بهتر و بهینه تر برای مرورگر استفاده از API های سیستم فایل و پردازش آپلود فایل ها و فایل های آپلود شده توسط سرور به طور مستقیم از سرویس دهنده است. یک نامزد عالی برای چنین زیرساختی، OPFS (سیستم فایل خصوصی مبدا)، یک API مرورگر بسیار جدید است که قطعا نقش مهمی برای برنامه های کاربردی درون مرورگر آینده خواهد داشت.
آنچه Rails و Wasm با هم می توانند به دست آورند
من تقریباً مطمئن هستم که با شروع خواندن مقاله این سوال را از خود پرسیده اید: چرا یک چارچوب سمت سرور در مرورگر اجرا شود؟ ایده فریمورک یا کتابخانه سمت سرور (یا سمت مشتری) فقط یک برچسب است. کد خوب و مخصوصاً یک انتزاع خوب در همه جا کار می کند. برچسبها نباید شما را از کشف احتمالات جدید و فشار دادن به مرزهای چارچوب (مثلاً Ruby on Rails) و همچنین مرزهای زمان اجرا (WebAssembly) باز دارند. هر دو می توانند از چنین موارد استفاده غیر متعارف سود ببرند.
موارد استفاده معمولی یا عملی زیادی نیز وجود دارد.
اول، آوردن چارچوب به مرورگر فرصت های یادگیری و نمونه سازی عظیمی را باز می کند. تصور کنید بتوانید با کتابخانه ها، پلاگین ها و الگوها درست در مرورگر خود و همراه با افراد دیگر بازی کنید. Stackblitz این امکان را برای چارچوب های جاوا اسکریپت فراهم کرد. مثال دیگر یک زمین بازی وردپرس است که امکان بازی با تم های وردپرس را بدون خروج از صفحه وب فراهم می کند. Wasm می تواند چیزی مشابه را برای روبی و اکوسیستم آن فعال کند.
یک مورد خاص از کدنویسی درون مرورگر به ویژه برای توسعه دهندگان منبع باز مفید است - مشکلات تریاژ و اشکال زدایی . مجدداً، StackBlitz این را برای پروژههای جاوا اسکریپت تبدیل کرد: شما یک اسکریپت بازتولید حداقلی ایجاد میکنید، به پیوند موجود در GitHub Issue اشاره میکنید، و به نگهبانان وقت صرف میکنید تا سناریوی خود را تولید کنند. و در واقع، به لطف پروژه RunRuby.dev از قبل در روبی شروع شده است (در اینجا یک نمونه مشکل حل شده با بازتولید درون مرورگر وجود دارد).
یکی دیگر از موارد استفاده ، برنامههای دارای قابلیت آفلاین (یا آفلاین آگاه) است. برنامههای دارای قابلیت آفلاین که معمولاً با استفاده از شبکه کار میکنند، اما وقتی اتصالی وجود ندارد، قابل استفاده میمانند. به عنوان مثال، یک سرویس گیرنده ایمیل که به شما امکان می دهد در حالت آفلاین در صندوق ورودی خود جستجو کنید. یا، یک برنامه کتابخانه موسیقی با قابلیت "ذخیره در دستگاه"، بنابراین موسیقی مورد علاقه شما حتی اگر اتصال شبکه وجود نداشته باشد، همچنان به صدا در می آید. هر دو مثال به داده های ذخیره شده به صورت محلی بستگی دارند، نه فقط از یک کش مانند PWA های کلاسیک استفاده می کنند.
در نهایت، ساخت برنامههای محلی (یا دسکتاپ) با Rails نیز منطقی است، زیرا بهرهوری این چارچوب به زمان اجرا بستگی ندارد. فریمورکهای با امکانات کامل برای ساخت برنامههای کاربردی با دادههای شخصی و منطقی مناسب هستند. و استفاده از Wasm به عنوان یک قالب توزیع قابل حمل نیز یک گزینه مناسب است.
این تازه آغاز این سفر Rails on Wasm است. میتوانید درباره چالشها و راهحلهای موجود در کتاب الکترونیکی Ruby on Rails در WebAssembly (که اتفاقاً خود یک برنامه Rails با قابلیت آفلاین است) اطلاعات بیشتری کسب کنید.
،تاریخ انتشار: 31 ژانویه 2025
تصور کنید یک وبلاگ کاملاً کاربردی را در مرورگر خود اجرا کنید - نه فقط قسمت ظاهری، بلکه باطن نیز. هیچ سرور یا ابری درگیر نیست - فقط شما، مرورگرتان و... WebAssembly ! WebAssembly با اجازه دادن به فریمورکهای سمت سرور برای اجرای محلی، مرزهای توسعه وب کلاسیک را محو میکند و امکانات جدید هیجانانگیزی را باز میکند. در این پست، ولادیمیر دمنتیف (رئیس Backend در Evil Martians ) پیشرفت در ساخت Ruby on Rails Wasm و مرورگر را به اشتراک می گذارد:
- چگونه Rails را در 15 دقیقه وارد مرورگر کنیم.
- پشت صحنه Wamification Rails.
- آینده ریل و واسم.
"وبلاگ در 15 دقیقه" معروف Ruby on Rails اکنون مستقیماً در مرورگر شما اجرا می شود
Ruby on Rails یک چارچوب وب است که بر بهره وری توسعه دهندگان و ارسال سریع چیزها متمرکز شده است. این فناوری است که توسط رهبران صنعت مانند GitHub و Shopify استفاده می شود. محبوبیت این فریم ورک سال ها پیش با انتشار ویدیوی معروف «چگونه در 15 دقیقه وبلاگ بسازیم» که توسط دیوید هاین مایر هانسون (یا DHH) منتشر شد، آغاز شد. در سال 2005، ساختن یک برنامه وب کاملاً کارآمد در مدت زمان کوتاه غیرقابل تصور بود. حس جادویی بود!
امروز، من می خواهم با ایجاد یک برنامه Rails که به طور کامل در مرورگر شما اجرا می شود، این احساس جادویی را به شما بازگردانم. سفر شما با ایجاد یک برنامه پایه Rails به روش معمول شروع می شود و سپس آن را برای Wasm بسته بندی می کند.
پس زمینه: یک "وبلاگ در 15 دقیقه" در خط فرمان
با فرض اینکه Ruby و Ruby on Rails را روی دستگاه خود نصب کردهاید ، با ایجاد یک برنامه جدید Ruby on Rails و داربستبندی برخی عملکردها شروع میکنید (درست مانند ویدیوی اصلی "وبلاگ در 15 دقیقه"):
$ rails new --css=tailwind web_dev_blog
create .ruby-version
...
$ cd web_dev_blog
$ bin/rails generate scaffold Post title:string date:date body:text
create db/migrate/20241217183624_create_posts.rb
create app/models/post.rb
...
$ bin/rails db:migrate
== 20241217183624 CreatePosts: migrating ====================
-- create_table(:posts)
-> 0.0017s
== 20241217183624 CreatePosts: migrated (0.0018s) ===========
حتی بدون دست زدن به پایگاه کد، اکنون می توانید برنامه را اجرا کنید و آن را در عمل مشاهده کنید:
$ bin/dev
=> Booting Puma
=> Rails 8.0.1 application starting in development
...
* Listening on http://127.0.0.1:3000
اکنون، می توانید وبلاگ خود را در http://localhost:3000/posts باز کنید و شروع به نوشتن پست کنید!
شما یک برنامه وبلاگ بسیار ساده اما کاربردی دارید که در عرض چند دقیقه ساخته شده است. این یک برنامه کاربردی تمام پشته و تحت کنترل سرور است: شما یک پایگاه داده ( SQLite ) برای نگهداری داده های خود، یک وب سرور برای رسیدگی به درخواست های HTTP ( Puma ) و یک برنامه Ruby برای حفظ منطق کسب و کار شما، ارائه رابط کاربری و پردازش تعاملات کاربر دارید. در نهایت، یک لایه نازک از جاوا اسکریپت ( Turbo ) برای ساده کردن تجربه مرور وجود دارد.
نسخه ی نمایشی رسمی Rails در جهت استقرار این برنامه بر روی یک سرور فلزی خالی و در نتیجه آماده سازی آن برای تولید ادامه دارد. سفر شما در جهت مخالف ادامه خواهد یافت: به جای اینکه برنامه خود را در جایی دورتر قرار دهید، آن را به صورت محلی "استقرار" خواهید کرد.
سطح بعدی: "وبلاگ در 15 دقیقه" در Wasm
از زمان اضافه شدن WebAssembly، مرورگرها قادر به اجرای نه تنها کد جاوا اسکریپت، بلکه هر کد قابل کامپایل در Wasm شدند. و روبی نیز از این قاعده مستثنی نیست. مطمئنا، Rails چیزی بیشتر از Ruby است، اما قبل از بررسی تفاوتها، اجازه دهید نسخه آزمایشی را ادامه دهیم و برنامه Rails را (فعل ابداع شده توسط کتابخانه wasmify-rails ) کنیم !
برای کامپایل کردن برنامه وبلاگ خود در ماژول Wasm و اجرای آن در مرورگر، فقط باید چند دستور را اجرا کنید.
ابتدا، کتابخانه wasmify-rails را با استفاده از Bundler ( npm
Ruby) نصب میکنید و ژنراتور آن را با استفاده از Rails CLI اجرا میکنید:
$ bundle add wasmify-rails
$ bin/rails wasmify:install
create config/wasmify.yml
create config/environments/wasm.rb
...
info ✅ The application is prepared for Wasm-ificaiton!
دستور wasmify:rails
یک محیط اجرای اختصاصی "wasm" را پیکربندی می کند (علاوه بر محیط های پیش فرض "توسعه"، "تست" و "تولید") و وابستگی های مورد نیاز را نصب می کند. برای برنامه Greenfield Rails، این کافی است تا Wasm-ready شود.
در مرحله بعد، ماژول اصلی Wasm را بسازید که شامل زمان اجرا Ruby، کتابخانه استاندارد و تمام وابستگی های برنامه است:
$ bin/rails wasmify:build
==> RubyWasm::BuildSource(3.3) -- Building
...
==> RubyWasm::CrossRubyProduct(ruby-3.3-wasm32-unknown-wasip1-full-4aaed4fbda7afe0bdf4e22167afd101e) -- done in 47.37s
INFO: Packaging gem: rake-13.2.1
...
INFO: Packaging gem: wasmify-rails-0.2.0
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 73.77 MB
این مرحله ممکن است کمی طول بکشد: شما باید Ruby را از منبع بسازید تا به درستی افزونههای بومی (نوشته شده به زبان C) را از کتابخانههای شخص ثالث پیوند دهید. این اشکال (موقت) در ادامه پست پوشش داده شده است.
ماژول Wasm کامپایل شده فقط پایه ای برای برنامه شماست. شما همچنین باید خود کد برنامه و تمام دارایی ها (به عنوان مثال، تصاویر، CSS، جاوا اسکریپت) را بسته بندی کنید . قبل از انجام بستهبندی، یک برنامه راهانداز اولیه ایجاد کنید که میتوان از آن برای اجرای Wasmified Rails در مرورگر استفاده کرد. برای آن، یک دستور ژنراتور نیز وجود دارد:
$ bin/rails wasmify:pwa
create pwa
create pwa/boot.html
create pwa/boot.js
...
prepend config/wasmify.yml
دستور قبلی یک برنامه کاربردی حداقل PWA را ایجاد می کند که با Vite ساخته شده است که می تواند به صورت محلی برای آزمایش ماژول Rails Wasm کامپایل شده یا به صورت ایستا برای توزیع برنامه استفاده شود.
اکنون، با راهانداز، تنها چیزی که نیاز دارید این است که کل برنامه را در یک باینری Wasm جمع کنید:
$ bin/rails wasmify:pack
...
Packed the application to pwa/app.wasm
Size: 76.2 MB
همین! برنامه لانچر را اجرا کنید و برنامه وبلاگ نویسی Rails خود را ببینید که به طور کامل در مرورگر اجرا می شود:
$ cd pwa/
$ yarn dev
VITE v4.5.5 ready in 290 ms
➜ Local: http://localhost:5173/
به http://localhost:5173 بروید، کمی صبر کنید تا دکمه «راهاندازی» فعال شود و روی آن کلیک کنید—از کار با برنامه Rails که به صورت محلی در مرورگر خود اجرا میشود، لذت ببرید!
آیا اجرای یک برنامه یکپارچه سمت سرور نه تنها بر روی دستگاه بلکه در جعبه ایمنی مرورگر مانند جادویی نیست؟ برای من (با وجود اینکه من "جادوگر" هستم)، هنوز هم مانند یک فانتزی به نظر می رسد. اما هیچ جادویی در کار نیست، فقط پیشرفت تکنولوژی است.
نسخه ی نمایشی
می توانید نسخه ی نمایشی تعبیه شده در مقاله را تجربه کنید یا نسخه ی نمایشی را در یک پنجره مستقل راه اندازی کنید. کد منبع را در GitHub بررسی کنید.
پشت صحنه Rails on Wasm
برای درک بهتر چالشها (و راهحلهای) بستهبندی یک برنامه سمت سرور در ماژول Wasm، بقیه این پست اجزایی که بخشی از این معماری هستند را توضیح میدهد.
یک برنامه وب به چیزهای بیشتری بستگی دارد تا فقط یک زبان برنامه نویسی که برای نوشتن کد برنامه استفاده می شود. هر مؤلفه همچنین باید به محیط استقرار محلی - مرورگر شما آورده شود. چیزی که در مورد نسخه نمایشی "وبلاگ در 15 دقیقه" هیجان انگیز است، این است که می توان بدون بازنویسی کد برنامه به این کار دست یافت. از همین کد برای اجرای برنامه در حالت کلاسیک، سمت سرور و در مرورگر استفاده شد.
یک چارچوب، مانند Ruby on Rails، یک رابط، یک انتزاع برای برقراری ارتباط با اجزای زیرساخت به شما می دهد. بخش زیر نحوه استفاده از معماری چارچوب را برای پاسخگویی به نیازهای خدمات محلی تا حدودی باطنی مورد بحث قرار می دهد.
پایه: یاقوت سرخ
Ruby در سال 2022 رسماً Wasm-ready شد (از نسخه 3.2.0) به این معنی که کد منبع C را می توان در Wasm کامپایل کرد و یک ماشین مجازی Ruby را به هر کجا که می خواهید بیاورید. پروژه ruby.wasm ماژول های از پیش کامپایل شده و اتصالات جاوا اسکریپت را برای اجرای Ruby در مرورگر (یا هر زمان اجرا جاوا اسکریپت دیگری) ارسال می کند. پروژه ruby:wasm همچنین با ابزارهای ساختی همراه است که به شما امکان میدهد یک نسخه روبی سفارشی با وابستگیهای اضافی بسازید - این برای پروژههایی که به کتابخانههایی با پسوند C متکی هستند بسیار مهم است. بله، شما می توانید پسوندهای بومی را در Wasm نیز کامپایل کنید! (خب، هنوز هیچ افزونه ای وجود ندارد، اما اکثر آنها).
در حال حاضر، روبی به طور کامل از رابط سیستم WebAssembly، WASI 0.1 پشتیبانی می کند. WASI 0.2 که شامل Component Model است در حال حاضر در حالت آلفا قرار دارد و چند مرحله مانده به تکمیل. هنگامی که WASI 0.2 پشتیبانی می شود، نیاز فعلی به کامپایل مجدد کل زبان را هر زمان که نیاز به اضافه کردن وابستگی های بومی جدید داشته باشید برطرف می کند: آنها می توانند جزء سازی شوند.
به عنوان یک عارضه جانبی، مدل مؤلفه نیز باید به کاهش اندازه بسته کمک کند. شما میتوانید در مورد توسعه و پیشرفت ruby.wasm از گفتگوی What you can do with Ruby on WebAssembly اطلاعات بیشتری کسب کنید.
بنابراین، بخش روبی معادله Wasm حل می شود. اما Rails به عنوان یک چارچوب وب به تمام اجزای نشان داده شده در نمودار قبلی نیاز دارد. برای یادگیری نحوه قرار دادن سایر مؤلفه ها در مرورگر و پیوند آنها با یکدیگر در Rails به ادامه مطلب بروید.
به یک پایگاه داده در حال اجرا در مرورگر متصل شوید
SQLite3 با یک توزیع رسمی Wasm و یک بسته بندی جاوا اسکریپت مربوطه ارائه می شود، بنابراین آماده است تا در مرورگر جاسازی شود. PostgreSQL for Wasm از طریق پروژه PGlite در دسترس است. بنابراین، شما فقط باید نحوه اتصال به پایگاه داده درون مرورگر را از برنامه Rails on Wasm بیابید.
یک جزء یا فریم فریم از Rails که مسئول مدلسازی دادهها و تعاملات پایگاهداده است، Active Record نامیده میشود (بله، نام آن از الگوی طراحی ORM گرفته شده است). Active Record پیاده سازی واقعی پایگاه داده به زبان SQL را از کد برنامه از طریق آداپتورهای پایگاه داده جدا می کند. خارج از جعبه، Rails به شما آداپتورهای SQLite3، PostgreSQL و MySQL می دهد. با این حال، همه آنها اتصال به پایگاه های داده واقعی موجود در شبکه را فرض می کنند. برای غلبه بر این مشکل، میتوانید آداپتورهای خود را برای اتصال به پایگاههای داده داخلی و درون مرورگر بنویسید!
به این صورت است که آداپتورهای SQLite3 Wasm و PGlite که به عنوان بخشی از پروژه Wasmify Rails پیادهسازی شدهاند، ایجاد میشوند:
- کلاس آداپتور از آداپتور داخلی مربوطه به ارث می رسد (به عنوان مثال،
class PGliteAdapter < PostgreSQLAdapter
)، بنابراین می توانید از آماده سازی پرس و جو واقعی و منطق تجزیه نتایج دوباره استفاده کنید. - به جای اتصال پایگاه داده سطح پایین، از یک شی رابط خارجی استفاده می کنید که در زمان اجرا جاوا اسکریپت زندگی می کند - پلی بین یک ماژول Rails Wasm و یک پایگاه داده.
به عنوان مثال، در اینجا پیاده سازی پل برای SQLite3 Wasm است:
export function registerSQLiteWasmInterface(worker, db, opts = {}) {
const name = opts.name || "sqliteForRails";
worker[name] = {
exec: function (sql) {
let cols = [];
let rows = db.exec(sql, { columnNames: cols, returnValue: "resultRows" });
return {
cols,
rows,
};
},
changes: function () {
return db.changes();
},
};
}
از منظر برنامه، تغییر از یک پایگاه داده واقعی به یک پایگاه داده درون مرورگر فقط به پیکربندی بستگی دارد:
# config/database.yml
development:
adapter: sqlite3
production:
adapter: sqlite3
wasm:
adapter: sqlite3_wasm
js_interface: "sqliteForRails"
کار با پایگاه داده محلی نیاز به تلاش زیادی ندارد. با این حال، اگر همگام سازی داده ها با برخی از منابع مرکزی حقیقت مورد نیاز باشد، ممکن است با چالشی در سطح بالاتر روبرو شوید. این سوال خارج از محدوده این پست است (نکته: نسخه ی نمایشی Rails در PGlite و ElectricSQL را بررسی کنید).
کارگر سرویس به عنوان یک وب سرور
یکی دیگر از اجزای ضروری هر برنامه وب، وب سرور است. کاربران با استفاده از درخواست های HTTP با برنامه های کاربردی وب تعامل دارند. بنابراین، شما به راهی برای مسیریابی درخواستهای HTTP که توسط ناوبری یا ارسالهای فرم به ماژول Wasm خود راهاندازی میشوند، نیاز دارید. خوشبختانه، مرورگر پاسخی برای آن دارد— کارگران خدمات .
Service Worker نوع خاصی از Web Worker است که به عنوان یک پروکسی بین برنامه جاوا اسکریپت و شبکه عمل می کند. میتواند درخواستها را رهگیری کند و آنها را دستکاری کند، برای مثال: دادههای ذخیرهشده را ارائه میکند، به URLهای دیگر هدایت میکند یا... به ماژولهای Wasm! در اینجا طرحی از سرویسی است که با استفاده از یک برنامه Rails که در Wasm اجرا می شود، درخواست ها را ارائه می دهد:
// The vm variable holds a reference to the Wasm module with a
// Ruby VM initialized
let vm;
// The db variable holds a reference to the in-browser
// database interface
let db;
const initVM = async (progress, opts = {}) => {
if (vm) return vm;
if (!db) {
await initDB(progress);
}
vm = await initRailsVM("/app.wasm");
return vm;
};
const rackHandler = new RackHandler(initVM});
self.addEventListener("fetch", (event) => {
// ...
return event.respondWith(
rackHandler.handle(event.request)
);
});
هر بار که درخواستی توسط مرورگر ارسال می شود، "واکشی" فعال می شود. شما می توانید اطلاعات درخواست (URL، هدر HTTP، بدنه) را بدست آورید و شی درخواست خود را بسازید.
Rails، مانند بسیاری از برنامه های کاربردی وب روبی، برای کار با درخواست های HTTP به رابط Rack متکی است. رابط Rack فرمت اشیاء درخواست و پاسخ و همچنین رابط کنترل کننده HTTP (برنامه) زیرین را توصیف می کند. شما می توانید این ویژگی ها را به صورت زیر بیان کنید:
request = {
"REQUEST_METHOD" => "GET",
"SCRIPT_NAME" => "",
"SERVER_NAME" => "localhost",
"SERVER_PORT" => "3000",
"PATH_INFO" => "/posts"
}
handler = proc do |env|
[
200,
{"Content-Type" => "text/html"},
["<!doctype html><html><body>Hello Web!</body></html>"]
]
end
handler.call(request) #=> [200, {...}, [...]]
اگر فرمت درخواست برای شما آشنا بود، احتمالاً در گذشته با CGI کار کرده اید.
شی جاوا اسکریپت RackHandler
مسئول تبدیل درخواست ها و پاسخ ها بین قلمروهای جاوا اسکریپت و روبی است. با توجه به اینکه Rack توسط اکثر برنامه های تحت وب Ruby استفاده می شود، پیاده سازی جهانی می شود، نه مخصوص Rails. اگرچه اجرای واقعی برای پست کردن در اینجا بسیار طولانی است.
یک سرویس دهنده یکی از نکات کلیدی یک برنامه وب درون مرورگر است. این نه تنها یک پروکسی HTTP، بلکه یک لایه کش و یک سوئیچر شبکه نیز هست (یعنی می توانید یک برنامه محلی اول یا آفلاین بسازید). این نیز مؤلفه ای است که می تواند به شما در ارائه فایل های آپلود شده توسط کاربر کمک کند.
آپلود فایل ها را در مرورگر نگه دارید
یکی از اولین ویژگی های اضافی برای پیاده سازی در برنامه جدید وبلاگ شما احتمالاً پشتیبانی از آپلود فایل یا به طور خاص تر، پیوست کردن تصاویر به پست ها است. برای رسیدن به این هدف، شما به راهی برای ذخیره و ارائه فایل ها نیاز دارید.
در Rails، بخشی از چارچوب که مسئول رسیدگی به آپلود فایل است ، Active Storage نامیده می شود. Active Storage به توسعه دهندگان انتزاعات و رابط هایی می دهد تا بدون فکر کردن به مکانیسم ذخیره سازی سطح پایین، با فایل ها کار کنند. مهم نیست که فایل های خود را کجا ذخیره می کنید، روی هارد دیسک یا در فضای ابری، کد برنامه از آن بی اطلاع می ماند.
همانند Active Record، برای پشتیبانی از مکانیزم ذخیره سازی سفارشی، تنها چیزی که نیاز دارید این است که یک آداپتور سرویس ذخیره سازی مربوطه را پیاده سازی کنید. کجا فایل ها را در مرورگر ذخیره کنیم؟
گزینه سنتی استفاده از پایگاه داده است. بله، می توانید فایل ها را به صورت حباب در پایگاه داده ذخیره کنید، بدون نیاز به اجزای زیرساخت اضافی. و در حال حاضر یک افزونه آماده برای آن در Rails، Active Storage Database وجود دارد. با این حال، ارائه فایلهای ذخیرهشده در پایگاه داده از طریق برنامه Rails که در WebAssembly اجرا میشود، ایدهآل نیست، زیرا شامل دورهای (سریالزدایی) است که رایگان نیستند.
یک راه حل بهتر و بهینه تر برای مرورگر استفاده از API های سیستم فایل و پردازش آپلود فایل ها و فایل های آپلود شده توسط سرور به طور مستقیم از سرویس دهنده است. یک نامزد عالی برای چنین زیرساختی، OPFS (سیستم فایل خصوصی مبدا)، یک API مرورگر بسیار جدید است که قطعا نقش مهمی برای برنامه های کاربردی درون مرورگر آینده خواهد داشت.
آنچه Rails و Wasm با هم می توانند به دست آورند
من تقریباً مطمئن هستم که با شروع خواندن مقاله این سوال را از خود پرسیده اید: چرا یک چارچوب سمت سرور در مرورگر اجرا شود؟ ایده فریمورک یا کتابخانه سمت سرور (یا سمت مشتری) فقط یک برچسب است. کد خوب و مخصوصاً یک انتزاع خوب در همه جا کار می کند. برچسبها نباید شما را از کشف احتمالات جدید و فشار دادن به مرزهای چارچوب (مثلاً Ruby on Rails) و همچنین مرزهای زمان اجرا (WebAssembly) باز دارند. هر دو می توانند از چنین موارد استفاده غیر متعارف سود ببرند.
موارد استفاده معمولی یا عملی زیادی نیز وجود دارد.
اول، آوردن چارچوب به مرورگر فرصت های یادگیری و نمونه سازی عظیمی را باز می کند. تصور کنید بتوانید با کتابخانه ها، پلاگین ها و الگوها درست در مرورگر خود و همراه با افراد دیگر بازی کنید. Stackblitz این امکان را برای چارچوب های جاوا اسکریپت فراهم کرد. مثال دیگر یک زمین بازی وردپرس است که امکان بازی با تم های وردپرس را بدون خروج از صفحه وب فراهم می کند. Wasm می تواند چیزی مشابه را برای روبی و اکوسیستم آن فعال کند.
یک مورد خاص از کدنویسی درون مرورگر به ویژه برای توسعه دهندگان منبع باز مفید است - مشکلات تریاژ و اشکال زدایی . مجدداً، StackBlitz این را برای پروژههای جاوا اسکریپت تبدیل کرد: شما یک اسکریپت بازتولید حداقلی ایجاد میکنید، به پیوند موجود در GitHub Issue اشاره میکنید، و به نگهبانان وقت صرف میکنید تا سناریوی خود را تولید کنند. و در واقع، به لطف پروژه RunRuby.dev از قبل در روبی شروع شده است (در اینجا یک نمونه مشکل حل شده با بازتولید درون مرورگر وجود دارد).
یکی دیگر از موارد استفاده ، برنامههای دارای قابلیت آفلاین (یا آفلاین آگاه) است. برنامههای دارای قابلیت آفلاین که معمولاً با استفاده از شبکه کار میکنند، اما وقتی اتصالی وجود ندارد، قابل استفاده میمانند. به عنوان مثال، یک سرویس گیرنده ایمیل که به شما امکان می دهد در حالت آفلاین در صندوق ورودی خود جستجو کنید. یا، یک برنامه کتابخانه موسیقی با قابلیت "ذخیره در دستگاه"، بنابراین موسیقی مورد علاقه شما حتی اگر اتصال شبکه وجود نداشته باشد، همچنان به صدا در می آید. هر دو مثال به داده های ذخیره شده به صورت محلی بستگی دارند، نه فقط از یک کش مانند PWA های کلاسیک استفاده می کنند.
در نهایت، ساخت برنامههای محلی (یا دسکتاپ) با Rails نیز منطقی است، زیرا بهرهوری این چارچوب به زمان اجرا بستگی ندارد. فریمورکهای با امکانات کامل برای ساخت برنامههای کاربردی با دادههای شخصی و منطقی مناسب هستند. و استفاده از Wasm به عنوان یک قالب توزیع قابل حمل نیز یک گزینه مناسب است.
این فقط آغاز این ریل ها در سفر WASM است. می توانید در مورد چالش ها و راه حل های موجود در Ruby on Rails در کتاب الکترونیکی WebAnsembly اطلاعات بیشتری کسب کنید (که به هر حال ، خود یک برنامه ریل های آفلاین است).