بسته بندی منابع غیر جاوا اسکریپت

نحوه وارد کردن و بسته بندی انواع دارایی ها از جاوا اسکریپت را بیاموزید.

فرض کنید روی یک برنامه وب کار می کنید. در این صورت، این احتمال وجود دارد که شما نه تنها با ماژول های جاوا اسکریپت، بلکه با انواع منابع دیگر هم سروکار داشته باشید - Web Workers (که آنها نیز جاوا اسکریپت هستند، اما بخشی از نمودار ماژول معمولی نیستند)، تصاویر، شیوه نامه ها، فونت ها، ماژول های WebAssembly و موارد دیگر.

ممکن است به برخی از آن منابع به طور مستقیم در HTML ارجاع داده شود، اما اغلب آنها به طور منطقی با اجزای قابل استفاده مجدد همراه هستند. به عنوان مثال، یک شیوه نامه برای یک کشویی سفارشی که به بخش جاوا اسکریپت آن گره خورده است، تصاویر نمادهایی که به یک جزء نوار ابزار گره خورده اند، یا ماژول WebAssembly که به چسب جاوا اسکریپت گره خورده است. در این موارد، راحت‌تر است که منابع را مستقیماً از ماژول‌های جاوا اسکریپت خود ارجاع دهید و زمانی که (یا اگر) مؤلفه مربوطه بارگذاری شد، آنها را به صورت پویا بارگذاری کنید.

نمودار تجسم انواع مختلفی از دارایی های وارد شده به JS.

با این حال، اکثر پروژه‌های بزرگ دارای سیستم‌هایی هستند که بهینه‌سازی‌های اضافی و سازماندهی مجدد محتوا را انجام می‌دهند - به عنوان مثال، بسته‌بندی و کوچک‌سازی. آن‌ها نمی‌توانند کد را اجرا کنند و پیش‌بینی کنند که نتیجه اجرا چه خواهد بود، و همچنین نمی‌توانند از تمام رشته‌های ممکن در جاوا اسکریپت عبور کنند و حدس بزنند که آیا URL منبع است یا نه. بنابراین چگونه می‌توانید آنها را وادار کنید که دارایی‌های پویا بارگیری شده توسط مؤلفه‌های جاوا اسکریپت را ببینند و آن‌ها را در بیلد بگنجانید؟

واردات سفارشی در باندلر

یکی از رویکردهای رایج استفاده مجدد از نحو واردات استاتیک است. در برخی از بسته‌ها، ممکن است فرمت را با پسوند فایل به طور خودکار شناسایی کند، در حالی که برخی دیگر به افزونه‌ها اجازه می‌دهند از یک طرح URL سفارشی مانند مثال زیر استفاده کنند:

// regular JavaScript import
import { loadImg } from './utils.js';

// special "URL imports" for assets
import imageUrl from 'asset-url:./image.png';
import wasmUrl from 'asset-url:./module.wasm';
import workerUrl from 'js-url:./worker.js';

loadImg(imageUrl);
WebAssembly.instantiateStreaming(fetch(wasmUrl));
new Worker(workerUrl);

هنگامی که یک افزونه باندلر وارداتی را با پسوندی که تشخیص می‌دهد یا چنین طرح سفارشی صریح ( asset-url: و js-url: در مثال بالا) پیدا می‌کند، دارایی ارجاع‌شده را به نمودار ساخت اضافه می‌کند، آن را به نمودار نهایی کپی می‌کند. مقصد، بهینه‌سازی‌های مربوط به نوع دارایی را انجام می‌دهد و URL نهایی را برای استفاده در زمان اجرا برمی‌گرداند.

مزایای این رویکرد: استفاده مجدد از نحو واردات جاوا اسکریپت تضمین می کند که همه URL ها ثابت و نسبت به فایل فعلی هستند، که مکان یابی چنین وابستگی هایی را برای سیستم ساخت آسان می کند.

با این حال، یک اشکال مهم دارد: چنین کدی نمی‌تواند مستقیماً در مرورگر کار کند، زیرا مرورگر نمی‌داند چگونه آن طرح‌ها یا برنامه‌های افزودنی واردات سفارشی را مدیریت کند. اگر همه کدها را کنترل کنید و به هر حال به یک بسته‌کننده برای توسعه تکیه کنید، ممکن است خوب باشد، اما استفاده از ماژول‌های جاوا اسکریپت به طور مستقیم در مرورگر، حداقل در طول توسعه، برای کاهش اصطکاک، به طور فزاینده‌ای رایج است. شخصی که روی یک نسخه نمایشی کوچک کار می کند ممکن است حتی در مرحله تولید به یک باندلر هم نیاز نداشته باشد.

الگوی جهانی برای مرورگرها و باندلرها

اگر روی یک جزء قابل استفاده مجدد کار می کنید، می خواهید که در هر یک از محیط ها کار کند، خواه مستقیماً در مرورگر استفاده شود یا به عنوان بخشی از یک برنامه بزرگتر از قبل ساخته شده باشد. اکثر باندلرهای مدرن با پذیرش الگوی زیر در ماژول های جاوا اسکریپت این امکان را می دهند:

new URL('./relative-path', import.meta.url)

این الگو را می‌توان به‌صورت ایستا با ابزارها تشخیص داد، تقریباً انگار یک نحو خاص است، اما یک عبارت جاوا اسکریپت معتبر است که مستقیماً در مرورگر نیز کار می‌کند.

هنگام استفاده از این الگو، مثال بالا را می توان به صورت زیر بازنویسی کرد:

// regular JavaScript import
import { loadImg } from './utils.js';

loadImg(new URL('./image.png', import.meta.url));
WebAssembly.instantiateStreaming(
  fetch(new URL('./module.wasm', import.meta.url)),
  { /* … */ }
);
new Worker(new URL('./worker.js', import.meta.url));

چگونه کار می کند؟ بیایید آن را تجزیه کنیم. سازنده new URL(...) یک URL نسبی را به عنوان آرگومان اول می گیرد و آن را با URL مطلق ارائه شده به عنوان آرگومان دوم حل می کند. در مورد ما، آرگومان دوم import.meta.url است که نشانی اینترنتی ماژول جاوا اسکریپت فعلی را می دهد، بنابراین آرگومان اول می تواند هر مسیری نسبت به آن باشد.

این مبادلات مشابه واردات پویا دارد. در حالی که می‌توان import(...) با عبارات دلخواه مانند import(someUrl) استفاده کرد، بسته‌کننده‌ها رفتار ویژه‌ای با الگویی با URL ثابت import('./some-static-url.js') به عنوان راهی برای پیش پردازش می‌کنند. یک وابستگی شناخته شده در زمان کامپایل، با این حال آن را به تکه‌ای تقسیم می‌کند که به صورت پویا بارگذاری می‌شود.

به طور مشابه، می‌توانید از new URL(...) با عبارات دلخواه مانند new URL(relativeUrl, customAbsoluteBase) استفاده کنید، اما الگوی new URL('...', import.meta.url) سیگنال واضحی برای پیش‌پردازش باندلرها است. و شامل یک وابستگی در کنار جاوا اسکریپت اصلی است.

URL های نسبی مبهم

ممکن است تعجب کنید که چرا باندلرها نمی توانند الگوهای رایج دیگر را شناسایی کنند - به عنوان مثال، fetch('./module.wasm') بدون پوشه های new URL ؟

دلیل آن این است که بر خلاف دستورهای واردات، هر درخواست پویا نسبت به خود سند حل می شود و نه به فایل جاوا اسکریپت فعلی. فرض کنید ساختار زیر را دارید:

  • index.html :
    html <script src="src/main.js" type="module"></script>
  • src/
    • main.js
    • module.wasm

اگر می خواهید module.wasm از main.js بارگیری کنید، ممکن است وسوسه انگیز باشد که از یک مسیر نسبی مانند fetch('./module.wasm') استفاده کنید.

با این حال، fetch نشانی اینترنتی فایل جاوا اسکریپتی که در آن اجرا شده را نمی‌داند، در عوض، URL‌ها را نسبت به سند حل می‌کند. در نتیجه، fetch('./module.wasm') به جای http://example.com/src/module.wasm در نظر گرفته شده تلاش می کند http://example.com/module.wasm بارگیری کند و شکست بخورد. (یا بدتر از آن، بی سر و صدا منبعی متفاوت از آنچه در نظر داشتید بارگیری کنید).

با قرار دادن URL نسبی در new URL('...', import.meta.url) می توانید از این مشکل جلوگیری کنید و تضمین کنید که URL ارائه شده نسبت به URL ماژول جاوا اسکریپت فعلی حل شده است ( import.meta.url ) قبل از اینکه به لودرها منتقل شود.

fetch('./module.wasm') با fetch(new URL('./module.wasm', import.meta.url)) جایگزین کنید و ماژول WebAssembly مورد انتظار را با موفقیت بارگیری می کند و همچنین به باندلرها راهی برای آن مسیرهای نسبی را در طول زمان ساخت نیز پیدا کنید.

پشتیبانی از ابزار

باندلرها

بسته‌کننده‌های زیر قبلاً از طرح new URL پشتیبانی می‌کنند:

WebAssembly

هنگام کار با WebAssembly، معمولا ماژول Wasm را با دست بارگیری نمی کنید، بلکه چسب جاوا اسکریپت منتشر شده توسط زنجیره ابزار را وارد می کنید. زنجیره های ابزار زیر می توانند الگوی new URL(...) توصیف شده را در زیر هود برای شما منتشر کنند.

C/C++ از طریق Emscripten

هنگام استفاده از Emscripten، می توانید از طریق یکی از گزینه های زیر از آن بخواهید که چسب جاوا اسکریپت را به عنوان یک ماژول ES6 به جای یک اسکریپت معمولی منتشر کند:

$ emcc input.cpp -o output.mjs
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6

هنگام استفاده از این گزینه، خروجی از الگوی new URL(..., import.meta.url) در زیر هود استفاده می کند، به طوری که باندلرها می توانند فایل Wasm مرتبط را به طور خودکار پیدا کنند.

همچنین می توانید از این گزینه با رشته های WebAssembly با افزودن یک پرچم -pthread استفاده کنید:

$ emcc input.cpp -o output.mjs -pthread
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6 -pthread

در این صورت، Web Worker تولید شده به همان شیوه گنجانده می شود و همچنین توسط باندلرها و مرورگرها به طور یکسان قابل کشف خواهد بود.

زنگ زدگی از طریق wasm-pack / wasm-bindgen

wasm-pack - زنجیره ابزار Rust اولیه برای WebAssembly - همچنین دارای چندین حالت خروجی است.

به طور پیش‌فرض، یک ماژول جاوا اسکریپت را منتشر می‌کند که به پیشنهاد یکپارچه‌سازی WebAssembly ESM متکی است. در لحظه نگارش، این پیشنهاد هنوز آزمایشی است و خروجی تنها زمانی کار خواهد کرد که با Webpack همراه باشد.

در عوض، می‌توانید از wam-pack بخواهید که یک ماژول ES6 سازگار با مرورگر را از طریق --target web منتشر کند:

$ wasm-pack build --target web

خروجی از الگوی new URL(..., import.meta.url) استفاده می کند و فایل Wasm به طور خودکار توسط باندلرها نیز کشف می شود.

اگر می خواهید از رشته های WebAssembly با Rust استفاده کنید، داستان کمی پیچیده تر است. برای کسب اطلاعات بیشتر بخش مربوط به راهنما را بررسی کنید.

نسخه کوتاه این است که نمی‌توانید از APIهای رشته دلخواه استفاده کنید، اما اگر از Rayon استفاده می‌کنید، می‌توانید آن را با آداپتور wasm-bindgen-rayon ترکیب کنید تا بتواند Workers را در وب ایجاد کند. چسب جاوا اسکریپت مورد استفاده توسط wasm-bindgen-rayon همچنین شامل الگوی new URL(...) در زیر هود است، و بنابراین Workers توسط باندلرها نیز قابل کشف و گنجانده خواهد شد.

ویژگی های آینده

import.meta.resolve

تماس اختصاصی import.meta.resolve(...) یک پیشرفت بالقوه در آینده است. این اجازه می دهد تا مشخص کننده ها را نسبت به ماژول فعلی به روشی ساده تر و بدون پارامترهای اضافی حل کنیم:

new URL('...', import.meta.url)
await import.meta.resolve('...')

همچنین بهتر با نقشه‌های وارداتی و حل‌کننده‌های سفارشی ادغام می‌شود، زیرا از سیستم وضوح ماژول مشابهی import می‌کند. این یک سیگنال قوی‌تر برای باندلرها نیز خواهد بود، زیرا یک نحو استاتیک است که به APIهای زمان اجرا مانند URL بستگی ندارد.

import.meta.resolve قبلاً به‌عنوان یک آزمایش در Node.js پیاده‌سازی شده است، اما هنوز برخی سؤالات حل‌نشده درباره نحوه عملکرد آن در وب وجود دارد.

اظهارات واردات

ادعاهای واردات یک ویژگی جدید است که امکان وارد کردن انواعی غیر از ماژول های ECMAScript را می دهد. در حال حاضر آنها به JSON محدود هستند:

foo.json:

{ "answer": 42 }

main.mjs:

import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42

آنها همچنین ممکن است توسط بسته‌کننده‌ها استفاده شوند و جایگزین موارد مصرفی شوند که در حال حاضر تحت پوشش الگوی new URL هستند، اما انواع در اظهارات واردات بر اساس هر مورد اضافه می‌شوند. در حال حاضر آنها فقط JSON را پوشش می دهند و ماژول های CSS به زودی ارائه می شوند، اما انواع دیگر دارایی ها همچنان به یک راه حل عمومی تر نیاز دارند.

برای کسب اطلاعات بیشتر در مورد این ویژگی، توضیح دهنده ویژگی v8.dev را بررسی کنید.

نتیجه گیری

همانطور که می بینید، راه های مختلفی برای گنجاندن منابع غیر جاوا اسکریپت در وب وجود دارد، اما آنها اشکالات مختلفی دارند و در زنجیره های ابزار مختلف کار نمی کنند. پیشنهادهای آینده ممکن است به ما اجازه دهند که چنین دارایی هایی را با نحو تخصصی وارد کنیم، اما هنوز کاملاً به آنجا نرسیده ایم.

تا آن زمان، الگوی new URL(..., import.meta.url) امیدوارکننده‌ترین راه حلی است که امروزه در مرورگرها، بسته‌های مختلف و زنجیره‌های ابزار WebAssembly کار می‌کند.