مهاجرت یک پروژه Angular به یک فضای کاری Nx: Ng-Morph برای نجات!

من در حال حاضر در حال انتقال چندین پروژه و کتابخانه های مشترک در یک فضای کاری Nx هستم تا از قدرت، ابزار و تجربه توسعه دهنده (DX) ارائه شده توسط Nx استفاده کنم. من روند را در این مقاله توضیح نمی دهم، اما در طول این انتقال، مجبور شدم کارهای خسته کننده و تکراری متعددی را انجام دهم. خوشبختانه، من یک کتابخانه شگفت انگیز به نام کشف کردم آف-مورف که به ما امکان می دهد اسکریپت هایی برای خودکارسازی چنین کارهای تکراری در درخت پروژه خود بنویسیم.
در این مقاله سه نمونه از فیلمنامه هایی که نوشتم را برای سرعت بخشیدن به کار و کاهش درد آن توضیح خواهم داد.
تمام فایل های تست غیر ضروری را حذف کنید.
هنگام تولید کامپوننت، دایرکتیو یا لوله با استفاده از Angular CLI، یک فایل مشخصات با یک آزمایش “باید ایجاد” ایجاد می کند. در فضای کاری فعلی من، بسیاری از این فایل ها نگهداری می شدند، اما در واقع غیر ضروری هستند و زمان اجرای دستور تست را کاهش می دهند. هدف حذف تمام فایل هایی است که فقط حاوی تست “باید ایجاد” هستند.
با استفاده از Ng-morph، با تعریف درخت فایل و نوع فایل هایی که می خواهیم با آنها کار کنیم، شروع می کنیم. تنظیم پروژه فعال ما با خط زیر الزامی است:
setActiveProject(createProject(new NgMorphTree(), '/', ['**/*.spec.ts']));
const sourceFiles = getSourceFiles(['apps/**/*.spec.ts', 'libs/**/*.spec.ts']);
sourceFiles.forEach(s => {
const text = s.getFullText();
if (text.match(new RegExp("(it\\('should be created'|it\\('should create)", 'g'))) {
const secondTest = text.match(new RegExp("(it\\(')", 'g'));
if (secondTest && secondTest.length === 1) {
s.delete();
}
}
});
در مرحله بعد همه فایل های مشخصات را با استفاده از آن بارگذاری می کنیم getSourceFiles
. پس از اتمام، هر فایل را تکرار می کنیم، بررسی می کنیم که آیا با معیارهای ما مطابقت دارد یا خیر (با استفاده از جستجوی regex اولیه روی متن فایل)و در صورت وجود آن را حذف کنید.
در نهایت، تغییرات خود را با تماس ذخیره می کنیم saveActiveProject
.
همانطور که می بینید، اسکریپت ساده و آسان برای نوشتن و خواندن است. حتی بدون اطلاع قبلی از کتابخانه، نوشتن آن کمتر از 15 دقیقه طول کشید. با تمرین می توانید این اسکریپت را تنها در 5 دقیقه بنویسید. اگر بخواهید این کار را به صورت دستی انجام دهید، بسیار بیشتر طول می کشد و لذت کمتری خواهد داشت.
نکته مهمی که باید به آن توجه داشت این است که نیازی به صرف زمان برای نوشتن اسکریپت با متغیرهای نامگذاری شده یا کد قابل نگهداری نیست، زیرا قرار است اسکریپت یک بار اجرا شود تا کار مورد نظر انجام شود.
در نهایت برای اجرای اسکریپت از آن استفاده می کنیم ts-node
:
npx ts-node path/to/script
صادرات همه فایل ها به فایل api عمومی:
در Nx، ما libs/packages/modules ایجاد می کنیم (آنها را هر طور که دوست دارید نام ببرید) برای کپسوله کردن کد برای افشای کد به صورت خارجی، باید آن را در یک فایل بشکه یا فایل API عمومی صادر کنیم. در پروژه فعلی که من روی آن کار میکنم، هر تابع/کلاس مستقیماً از فایل مربوطه وارد میشود و در نتیجه مسیرهایی مانند زیر ایجاد میشود:
import { FooComponent } from 'src/path/to/foo.component';
اما در Nx، اگر میخواهید از FooComponentin کتابخانه دیگری استفاده کنید، باید آن را از فایلی که API کتابخانه ما را صادر میکند صادر کنیم. انجام دستی این کار می تواند خسته کننده باشد، زیرا ما باید مسیرهای همه فایل های داخل کتابخانه را پیدا کرده و صادر کنیم.
با Ng-Morph می توانیم این کار را به سرعت خودکار کنیم. بیایید نگاهی به فیلمنامه بیاندازیم:
const project = process.argv[2];
const folder = `libs/${project}`;
const fs = require('fs');
if (!fs.existsSync(folder)) {
console.log("name of project doesn't exist");
process.exit();
}
این بار، تصمیم گرفتم اسکریپت را با یک استدلال بپذیرم، زیرا می توان از آن برای بسیاری از کتابخانه های دیگر استفاده کرد.
setActiveProject(createProject(new NgMorphTree(), '/', ['**/*.ts']));
همانطور که قبلا ذکر شد، ما باید پروژه فعال را برای ثبت درخت خود راه اندازی کنیم.
const sourceFiles = getSourceFiles([`/${folder}/src/lib/**/*.ts`, `!/${folder}/src/lib/**/*.spec.ts`])
.map(s => s.getFilePath().replace(`/${folder}/src`, '.').replace('.ts', ''))
.map(n => `export * from '${n}';`);
const barrelFile = sourceFiles.reduce((acc, file) => `${acc}\n${file}`, '');
createSourceFile(`/${folder}/src/index.ts`, barrelFile, {
overwrite: true
});
saveActiveProject();
تمام منطق در این چند خط وجود دارد.
ما همه فایل های TS به جز فایل های spec را بارگذاری می کنیم و آن ها را حذف می کنیم .ts
پسوند و اضافه کردن export * from
عبارت برای هر مسیر فایل
در مرحله بعد، همه این رشته ها را به یک رشته متصل می کنیم و فایل بشکه ای کتابخانه را بازنویسی می کنیم.
در آخر یادمون نره زنگ بزنیم setActiveProject
برای ذخیره تغییرات ما
توجه داشته باشید: این اسکریپت پایه است و می توان آن را بهینه کرد. تمام فایلها، از جمله مواردی که در خارج از کتابخانه استفاده نمیشوند را صادر میکند. با این حال، ما در حال حاضر در حال انتقال چندین پروژه در Nx هستیم و اولویت ما این است که آن را به سرعت کار کنیم. سایر اعضای تیم با تنظیمات قدیمی کار می کنند، و هر چه شاخه ها بیشتر از هم جدا شوند، ادغام چالش برانگیزتر خواهد بود.
در مرحله بعدی، میتوانیم فرآیند را با تقسیم کتابخانه و حذف هرگونه فایل صادراتی غیرضروری از API عمومی، بهینه کنیم.
تغییر نام واردات فایل:
این وضعیت اغلب هنگام جابجایی فایل ها و توابع رخ می دهد. به عنوان مثال، ممکن است یک فایل utils بزرگ داشته باشیم و بخواهیم آن را تقسیم کنیم و برخی از توابع را به خارج از آن فایل و به یک کتابخانه اشتراکی/utils منتقل کنیم. در نتیجه واردات قدیمی باید حذف شود و واردات جدید جایگزین شود.
ما می توانیم واردات را مانند این پیدا کنیم:
import { addDate, removeDate } from '@test/shared'
import { addDate } from '@test/shared'
مورد دوم را می توان به راحتی با ویژگی جستجو و جایگزینی در هر IDE مدیریت کرد. با این حال، مورد اول چالش برانگیزتر است زیرا باید حذف کنیم addDate
از آرایه واردات در حین نگهداری removeDate
سالم.
با Ng-morph، این کار می تواند به راحتی خودکار شود.
const importToReplace = 'addDate';
const newNamespace = '@test/shared/date-utils';
function importIfNotPresent(source: string) {
addImports(source, [
{
namedImports: [importToReplace],
moduleSpecifier: newNamespace
}
]);
}
setActiveProject(createProject(new NgMorphTree(), '/', ['**/*.ts']));
const sourceFiles = getSourceFiles([`**/*.ts`]).map(s => s.getFilePath());
sourceFiles.forEach(s => {
const imports = getImports(s, { namedImports: importToReplace }).filter(
s => s.getModuleSpecifier().getText().replace(/'/g, '') !== newNamespace
);
imports.forEach(i => {
const namedImports = i.getNamedImports();
if (namedImports.length === 1) {
i.remove();
importIfNotPresent(s);
} else {
namedImports.forEach(n => {
if (n.getName() === importToReplace) {
n.remove();
importIfNotPresent(s);
}
});
}
});
});
saveActiveProject();
خواندن و درک فیلمنامه بسیار آسان است، اما اجازه دهید آن را بشکنیم:
ابتدا وارداتی را که می خواهیم جایگزین کنیم تعریف می کنیم importToReplace
و فضای نام جدید newNamespace
جایی که تابع قرار خواهد گرفت.
طبق معمول با تماس شروع می کنیم setActiveProject
برای شروع اسکریپت ما ما همه فایل TS را بارگذاری می کنیم و روی آنها تکرار می کنیم.
ما به دنبال رخدادها هستیم addDate
در واردات هر فایل و فیلتر برای اطمینان از اینکه فضای نام قبلاً به روز نشده است.
سپس، روی واردات نامگذاری شده تکرار میکنیم. اگر فقط یک واردات وجود داشته باشد، کل عبارت import را حذف می کنیم و آن را با عبارت جدید جایگزین می کنیم.
در صورت وجود چند واردات، تابع مورد نظر را حذف کرده و دستور import جدید را به فایل اضافه می کنیم.
توجه داشته باشید: همانطور که می بینید، ما نیازی به کار با عملکرد پیچیده AST یا انجام جستجوهای پیچیده نداریم. Ng-Morph به ما این امکان را می دهد که به روشی ساده و سرراست به فایل های خود دسترسی پیدا کرده و آنها را اصلاح کنیم.
هنگامی که نحوه استفاده از Ng-Morph را فهمیدید، کار با کل مخزن شما آسان و لذت بخش می شود.”
امیدوارم درک بهتری از این کتابخانه شگفت انگیز داشته باشید و همه چیزهای باورنکردنی را که می توانید با آن به دست آورید، دیده باشید. 🚀
می تونی منو پیدا کنی توییتر یا Github. در صورت داشتن هرگونه سوال با من تماس بگیرید.