جاوااسکریپت - چگونه کتابخانه NodeJS نوشته شده در TypeScript را برای هر دو محیط BrowserJS و NodeJS با قابلیت tree shaking موجود در Webpack توزیع کنیم؟
مانند lodash، کتابخانه @yamato-daiwa/es-extensions توابع رایج را برای هر دو BrowserJS و NodeJS پیشنهاد میدهد، اما بر خلاف lodash، یک فایل اصلی واحد index.js با بازصادرات دارد.
الزامات
این کتابخانه:
- باید در محیط مرورگر با وبپک کار کند (دستیابی شده ✔️)
- باید قابلیت tree shaking وبپک (به عبارت دیگر، حذف عملکردهای استفادهنشده در ساخت نسخه تولید) را برای BrowserJS فراهم کند، جایی که هر کیلوبایت مهم است (✔️ رسید).
- باید در محیط NodeJS کار کند.
- باید با ts-node کار کند
مشکل اصلی
نقطهٔ مورد اختلاف نوع ماژولها است. برای فعال کردن قابلیت tree shaking در Webpack، ماژولهای ES الزامی هستند، اما در حال حاضر NodeJS فقط از ماژولهای CommonJS پشتیبانی میکند و در ts-node پشتیبانی از ماژولهای ES محدود است (بهویژه در مورد صادر کردن کتابخانه).
آنچه که داریم این است:
| نوع ماژولها | براوزر جیاس (وبپک) | لرزاندن درخت | نودجیاس | ts-node |
|---|---|---|---|---|
| کامانجیاس | بله | نه | بله | بله |
| ایاس۲۰ایکسایکس | بله | بله | نه | محدود |
زیرا تکان دادن درخت برای BroswerJS حیاتی است، قبلاً تصمیم گرفته شده است که کتابخانه از طریق ماژولهای ES2020 توزیع شود. اما به این روش، پشتیبانی از NodeJS و ts-node از دست خواهد رفت.
حتی اگر بخواهید برنامه NodeJS را با Webpack بسازید، جایی که توصیه نمیشود کتابخانههای NodeJS را بستهبندی کنید (از webpack node modules externals برای حذف آنها استفاده میشود)، برنامه خراب خواهد شد اگر @yamato-daiwa/es-extensions با ماژولهای ES خود به لیست حذف از webpack node modules externals اضافه نشود.
بازتولید
در این نمونه، npm run "Webpack:ProductionBuild" فایلهای BrowserJS.js و NodeJS.js را برای محیط مناسب میسازد. چون کد منبع تنها از تابع isUndefined کتابخانه "@yamato-daiwa/es-extensions" استفاده میکند، در BrowserJS.js نباید هیچ قابلیت دیگری وجود داشته باشد (درجات درختی Webpack).
(()=>{"use strict";console.log(!1),console.log("End of the script")})();
خب، این چالش با موفقیت پشت سر گذاشته شد. موضوع این سوال NodeJS.js است:
(()=>{"use strict";const e=require("@yamato-daiwa/es-extensions");console.log((0,e.isUndefined)("ALPHA")),console.log("End of the script")})();
اگر تلاش کنیم آن را اجرا کنیم، با خطا مواجه خواهیم شد:
C:\Users\***\WebpackDoesNotCuttOfTheUnusedFunctionality\ConsumingProject\node_modules\@yamato-dai
wa\es-extensions\Distributable\index.js:6
export { default as formatNumberWith4KetaKanji } from "./Numbers/formatNumberWith4KetaKanji";
حالا بیایید سعی کنیم آن را از طریق ts-node اجرا کنیم (npm run "TSNode test"):
> ts-node index.ts
(node:18148) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
C:\Users\***\WebpackDoesNotCuttOfTheUnusedFunctionality\ConsumingProject\index.ts:1
import { isUndefined } from "@yamato-daiwa/es-extensions";
آیا این به این معنی است که «باید کتابخانه را تغییر دهید»؟
یادداشت
این سؤال قبل از انتشار TypeScript 4.5 مطرح شده است. شاید قابلیت جدید آن این مشکلات را حل کند؟
پاسخ شماره ۱
ورودی "main" در فایل package.json باید همیشه به فرمت commonjs باشد. ورودی "module" باید همیشه به ماژولهای es باشد. در حال حاضر، شما "main" را به ماژولهای es اشاره دادهاید، که به درستی پردازش نخواهد شد (برای مثال خطای ts-node شما).
به طور کلی، اگر بخواهید به مصرفکنندگان گزینه بدهید، باید دو نسخه در فایل توزیعپذیر ایجاد کنید.
(شما نیاز دارید کامنتها را در این فایلهای json حذف کنید)
// tsconfig-esm.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ES2020",
// es module build target
"outDir": "Distributable/esm"
}
}
// tsconfig-cjs.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
// commonjs build target
"outDir": "Distributable/cjs",
// the other one will build types, so not needed here
"declaration": false
}
}
سپس، در package.json خود:
"main": "./Distributable/cjs/index.js",
"module": "./Distributable/esm/index.js",
"types": "./Distributable/esm/index.d.ts",
"scripts": {
"build": "rimraf Distributable && tsc -p tsconfig-cjs.json && tsc -p tsconfig-esm.json",
"Lint": "eslint Source Tests"
},
"files": [
"Distributable"
],
"devDependencies": {
"rimraf": "^3.0.2",
توجه :
- شما میتوانید اسکریپتهای خود را هر طور که دوست دارید نامگذاری کنید، اما
buildحالت اصطلاحی است. فاصلهها من را دیوانه میکرد، چونnpm run Rebuild distributableکار نمیکرد. شما باید یا با نقلقول مانندnpm run 'Rebuild distributable'استفاده کنید یا با فرار مانندnpm run Rebuild\ distributable - اگر شما یک ورودی
"files"درpackage.jsonدارید، دیگر نیازی به.npmignoreندارید. این به این معناست که «فقط فایلهای مشخص شده را بستهبندی کنید»، که بسیار ایمنتر است نسبت به اینکه مجبور باشید به یاد داشته باشید هر فایل جدید را به فهرست نادیده گرفتن اضافه کنید. - من وابستگی
rimrafرا اضافه کردم، که ابزاری رایج برای حذف پوشه dist به صورت مستقل از پلتفرم است.del-cliفقط برای ویندوز کار میکند.
