ساخت و انتشار بسته های NPM با تایپ اسکریپت، چندین نقطه ورودی، tailwind، tsup و npm
درس های آموخته شده از ساخت و انتشار 3 بسته NPM
در این آموزش، نحوه ایجاد و انتشار بسته های NPM خود را با استفاده از TypeScript یاد خواهیم گرفت. ما همه چیز از راه اندازی پروژه شما با TypeScript را با استفاده از آن پوشش خواهیم داد tsup
برای ساختن
یک بسته NPM را منتشر کنید که باید 3 پیکربندی را که در یک پروژه متوسط جاوا اسکریپت به آنها فکر نمی کنیم، درک کنیم.
-
package.json
: این فایل برای پیکربندی بسته NPM شما استفاده می شود. این شامل اطلاعاتی در مورد بسته شما مانند نام، نسخه، توضیحات و وابستگی ها است.
{
"name": "my-package",
"version": "1.0.0",
"description": "My package description",
"repository": {
},
"main": "index.js",
"exports": {
"*":{
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js"
}
},
"scripts": {
"build":"tsup"
},
"dependencies": {
},
"devDependencies": {
},
}
مهمترین زمینه برای هر بسته NPM هستند
name
: نام بسته شما. این باید منحصر به فرد باشد و قبلاً روی NPM گرفته نشده باشد.version
:` شماره نسخه بسته شما. این باید به دنبال نسخهسازی معنایی باشد.توضیحات: شرح کوتاهی از بسته شما.
مخزن: محل کد منبع بسته شما.
main
: نقطه ورود بسته شما. این فایلی است که زمانی بارگذاری می شود که شخصی به بسته شما نیاز داشته باشد.exports
:این فیلد برای مشخص کردن فایلهایی استفاده میشود که باید هنگام وارد کردن بستههای شما شامل شوند. از این فیلد می توان برای اشاره بهd.ts
فایل ها و چندین نقطه ورودی
هنگامی که چندین نقطه ورودی را در فیلد “صادرات” مشخص می کنید، می توانید یک رابط عمومی برای بسته خود تعریف کنید و آن را کپسوله کنید و از هر نقطه ورودی دیگری غیر از مواردی که در “صادرات” تعریف شده است جلوگیری کنید. این می تواند زمانی مفید باشد که بخواهید فقط بخش های خاصی از بسته خود را در معرض ماژول های دیگر قرار دهید.
به عنوان مثال، در شما package.jso
فایل n`، می توانید چندین نقطه ورودی را مانند این تعریف کنید:
src/index.ts
export function foo(){
return "FOO";
}
export function bar(){
return "BAR";
}
src/math/foo.ts
export function foo(){
return "FOO";
}
src/math/bar.ts
export function bar(){
return "BAR";
}
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./foo": {
"require": "./dist/foo.js",
"import": "./dist/foo.js",
"types": "./dist/foo.d.ts"
},
"./bar": {
"require": "./dist/bar.js",
"import": "./dist/bar.js",
"types": "./dist/bar.d.ts"
}
}
این به کسی اجازه می دهد بسته شما را به این صورت وارد کند:
import { foo, bar } from 'my-package';
import foo from 'my-package/foo';
import bar from 'my-package/bar';
این می تواند زمانی مفید باشد که می خواهید قسمت های مختلف بسته خود را به عنوان ماژول های جداگانه در معرض دید قرار دهید.
زمینه صادرات بسیار مهم است زیرا به پروژه ای که بسته شما را نصب می کند می گوید کجا منابع خاص را پیدا کند
require
: "./dist/index.js"
: این فیلد مسیر ماژول CommonJS را مشخص می کند که زمانی که شخصی به بسته شما نیاز دارد باید استفاده شود. require()
const bar = require('my-package/bar');
import
: "./dist/index.js"
: این فیلد مسیر ماژول ES را مشخص می کند که وقتی شخصی بسته شما را با استفاده از آن وارد می کند، باید استفاده شود import
import bar from 'my-package/bar';
types
: "./dist/index.d.ts"
: این فیلد مسیر فایل اعلان TypeScript را مشخص می کند که زمانی که شخصی می خواهد از بسته شما با TypeScript استفاده کند باید استفاده شود.
scripts: از این فیلد برای تعریف اسکریپت هایی استفاده می شود که می توانند با اجرای NPM اجرا شوند.
dependencies: این فیلد برای تعیین بسته هایی که بسته شما به آنها وابسته است استفاده می شود.
devDependencies: این فیلد برای تعیین بسته هایی که فقط در حین توسعه مورد نیاز هستند استفاده می شود.
peerDependencies
: این فیلد برای تعیین بسته هایی استفاده می شود که بسته شما به آنها وابسته است اما انتظار دارد که نصب کننده قبلاً داشته باشد.اگر بسته خاص کتابخانه ای باشد، می توانید فرضیاتی مانند این داشته باشید
react/vue
کتابخانه کامپوننت، اما میتوانید آن را برای چیزی عمومیتر مانند کمککننده ریاضی جاوا اسکریپت خالی بگذارید.react
،vue
… نمونه های رایج وابستگی به همتایان هستند
tsconfig.json
: این فایل برای پیکربندی کامپایلر TypeScript استفاده می شود. این شامل اطلاعاتی در مورد نحوه کامپایل کد شما توسط TypeScript است.tsconfig.node.json
: این فایل برای پیکربندی کامپایلر TypeScript برای Node.js استفاده می شود. این شامل اطلاعاتی در مورد اینکه چگونه TypeScript باید کد شما را برای Node.js کامپایل کند.
پروژه 1: پلاگین Tailwind ساده برای shadcn/ui
در این پروژه، من فقط یک راه تمیزتر برای پیکربندی می خواستم shadcn/ui tailwind comfig
و اسناد tailwind بسیار آسان و مستقیم بودند، تنها کاری که باید انجام میدادم صادر کردن بود plugin
با پیکربندی اولیه مورد نظر من عمل کنید
src/index.ts
import plugin from 'tailwindcss/plugin'
export default plugin(
function () { },
{
content: [
'./node_modules/shadcn-fe-ui/dist/**/*.{js,ts,jsx,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
}
}
)
و می دویم tsup
برای ساختن با tsup config
import { defineConfig } from 'tsup';
export default defineConfig({
dts: true, // Generate .d.ts files
minify: true, // Minify output
sourcemap: true, // Generate sourcemaps
treeshake: true, // Remove unused code
splitting: true, // Split output into chunks
clean: true, // Clean output directory before building
outDir:"dist", // Output directory
entry: ['src/index.ts'], // Entry point(s)
format: ['esm'], // Output format(s)
});
-
dts
: این فیلد مشخص می کند که تولید شود یا نهd.ts
فایل های کد TypeScript شما. -
minify
: این فیلد مشخص می کند که خروجی ساخت خود را کوچک کنید یا خیر. -
sourcemap
: این فیلد مشخص می کند که آیا نقشه های منبع برای ساخت شما تولید شود یا خیر. -
treeshake
: این فیلد مشخص می کند که آیا کدهای استفاده نشده را از بیلد خود حذف کنید یا خیر. -
splitting
: این فیلد مشخص می کند که آیا خروجی خود را به قطعات تقسیم کنید یا خیر.
clean
: این فیلد مشخص می کند که دایرکتوری خروجی قبل از ساخت پاک شود یا خیر. -
outDir
: این فیلد دایرکتوری خروجی ساخت شما را مشخص می کند. -
entry
: این فیلد نقطه(های) ورودی ساخت شما را مشخص می کند. -
format
: این فیلد فرمت(های) خروجی ساخت شما را مشخص می کند.
را
tsup config
را نیز می توان در تعریف کردpackage.json
از آنجایی که این یک بسته ساده است که فقط یک فایل را صادر می کند، داشتن یک نقطه ورودی تنها خوب است. پس از ساخت شما یک dist
پوشه
-
index.js
کد واقعی را دارد -
index.d.ts
انواع تایپ را دارد -
index.js.map
نقشه های منبع را دارد
که با آنچه ما در خود تعریف کرده ایم مطابقت دارد package.json
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
پس از انتشار در NPM می توانیم آن را نصب کرده و مانند آن استفاده کنیم
npm i shadcn-fe-tw
yarn add shadcn-fe-tw
pnpm add shadcn-fe-tw
export default {
content: [
// vite
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}"
// next
"app/**/*.{ts,tsx}",
"components/**/*.{ts,tsx}",
],
plugins: [
require("tailwindcss-animate"),
require("shadcn-fe-tw")
],
};
پروژه 2: اصلاح شد shadcn/ui
CLI
این یکی نیز شبیه به اول است اما دارای فایل های بیشتری است که همه به فایل ورودی وارد می شوند
و سپس ما و اصلی خود را اعلام می کنیم dts
در package.json
"type": "module",
"exports": "./dist/index.js",
"bin": "./dist/index.js",
"files": [
"dist"
]
را bin
میدان در package.json
فایل برای تعیین محل فایل های اجرایی که باید در PATH نصب شوند استفاده می شود. این یک نقشه از نام فرمان به نام فایل محلی است. هنگامی که بسته ای را با یک bin مشخص نصب می کنید، NPM این کار را انجام می دهد symlink
آن فایل به پیشوند/بین برای نصب های جهانی یا ./node_modules/.bin/ for local installs12
پروژه 3: shadcn/ui
اجزای موجود در NPM
در این پروژه من ساختم shadcn/ui
اجزای یک بسته NPM قابل نصب
36 جزء در مجموع استفاده از چندین نقطه ورودی را ایده خوبی می کند
ابتدا کامپوننت ها را در پوشه ها مرتب می کنیم و سپس آنها را وارد می کنیم src/index.ts
در مورد من، من از ابزار cli برای دانلود تمام کامپوننت ها استفاده کردم src/shadcn
، سپس از یک اسکریپت برای سازماندهی آنها در پوشه ها استفاده کردم
ساختار پوشه مورد انتظار باید باشد src/components/[component name]
و داخل کامپوننت کارگردان خواهیم داشت
-
index.ts
: برای صادرات همه چیز از -
[compnent name].tsx
: جزء واقعی -
[component name].stories.ts
: داستان جزء. - هر فایل مرتبط دیگری
و با آن ساختار پوشه می توانیم آن را به آن وارد کنیم src/index.ts
به عنوان نقطه ورود اصلی
در این مرحله می توانیم آن را در قسمت اعلام کنیم package.json
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
اما ما میتوانیم آن را بیشتر بهینهسازی کنیم، از آزمایشهای من که با این تنظیمات آنطور که هست، همه چیز وارد میشود وقتی شما
import { Button } from "shadcn-fe-ui";
برخی از مؤلفههای موجود در این فهرست دارای قلابها و سایر کنترلکنندههای رویداد هستند که در برنامه بعدی مجاز نیستند!3 appDir بدون افزودن یک "use client"
بخشنامه
این بدان معناست که استفاده از آن به این صورت ما را از هرگونه دستاوردی که RSCها با مجبور کردن مؤلفهها به یک مؤلفه مشتری به ما میدهند، منصرف میکند.
خوشبختانه tsup
می تواند چندین نقطه ورودی را وارد کند و چندین فایل را در ما ایجاد کند dist
دایرکتوری، به ما کمک می کند تا یک مؤلفه را با قلاب جدا کنیم تا بتوان آن را مانند آن نصب کرد
import { ClientButton } from "shadcn-fe-ui/client-button";
ابتدا باید نقاط ورودی جدید خود را به آن اضافه کنیم tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
dts: true, // Generate .d.ts files
minify: true, // Minify output
sourcemap: true, // Generate sourcemaps
treeshake: true, // Remove unused code
splitting: true, // Split output into chunks
clean: true, // Clean output directory before building
outDir:"dist", // Output directory
entry: ['src/index.ts', 'src/client-buttonts'], // Entry point(s)
format: ['esm'], // Output format(s)
});
سپس پروژه را می سازیم و آن را تنظیم می کنیم package.json
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./client-button": {
"import":"./dist/client-button.js",
"types":"./dist/client-button.d.ts"
}
}
و حالا وقتی نصب می کنیم می توانیم آن را مانند آن وارد کنیم
import { ClientButton } from "shadcn-fe-ui/client-button";
تکرار این فرآیند برای 36 جزء می تواند خسته کننده باشد، بنابراین من یک اسکریپت نوشتم تا فیلدهای مناسب را به آن اضافه کنم. package.json
همانطور که برای tsup.config.ts
ما می توانیم از یک regex تطبیق الگو برای تعیین نقاط ورودی استفاده کنیم
import { defineConfig } from 'tsup';
export default defineConfig({
dts: true, // Generate .d.ts files
minify: true, // Minify output
sourcemap: true, // Generate sourcemaps
treeshake: true, // Remove unused code
splitting: true, // Split output into chunks
clean: true, // Clean output directory before building
outDir:"dist", // Output directory
entry: ['src/index.ts','src/components/**/index.ts'], // Entry point(s)
format: ['esm'], // Output format(s)
});
من متوجه یک اشکال در این شدم که در هنگام تولید با شکست مواجه می شود d.ts
و خطای مربوط به تایم اوت کارگر را پرتاب کنید.
اگر ساخت را فقط به 5 فایل محدود کنید، این اشکال ظاهر نمی شود
اما یک تابع ساخت را ارائه می دهد که به ما اجازه می دهد برنامه ای بسازیم.
بنابراین من از آن برای دسته بندی بیلدهای 3 در یک زمان استفاده کردم (شما می توانید تا 7 بالا بروید).
import glob from "glob"
import { build } from 'tsup'
import _ from 'lodash';
async function buildStage({ clean, entry }) {
console.log("🚀 ~ building entry ", entry)
try {
await build({
dts: true,
minify: true,
sourcemap: true,
treeshake: true,
// splitting: true,
outDir: 'dist',
clean,
entry,
external: ['react', 'react-dom'],
format: ['esm'],
});
} catch (error) {
console.log("🚀 ~ error while building entries :", entry);
console.log(error);
throw error;
}
}
export async function buildAllStages() {
const root_file = glob.sync('src/index.ts');
const files = glob.sync('src/components/**/index.ts');
const chunkSize = 3;
const chunks = _.chunk(files, chunkSize);
for await (const [index, chunk] of chunks.entries()) {
console.log('🚀 ~ chnk === ', chunk);
await buildStage({ clean:index===0, entry: chunk });
}
await buildStage({ clean:false, entry: root_file });
// await buildStage({ clean:true, entry: root_file });
}
export function invokeBuild(){
buildAllStages().then(()=>{
console.log("🚀 ~ buildAllStages success");
}).catch((error)=>{
console.log("🚀 ~ buildAllStages error === ", error);
})
}
invokeBuild()
و یک اسکریپت جدید را به ما اضافه کرد package.json
"scripts": {
"build": "node scripts/tsup-build-stages.js",
}
و اکنون باید اجزای خود را به عنوان یک بسته NPM قابل نصب دریافت کنیم
افکار نهایی
-
tsup
یک ابزار ساخت عالی است و به شما امکان می دهد تا با حداقل پیکربندی بسیار دور بروید، آنها همچنین تنها ابزار ساخت بدون درز هستندd.ts
تولید با ابزار دیگری که از شما می خواهد استفاده کنیدtsc
به طور مستقیم برایd.ts
خروجی ها. اشاره های قابل توجه شامل- bun: ساخت عالی پیکربندی بسیار آسان، اما نه
d.ts
خروجی - بسته: من نتوانستم بفهمم چگونه چند نقطه ورود را انجام دهم و پیکربندی آنها باید در یکی باشد
package.json
یا الف.parcel
فایلی که پیشنهاد کدی را ارائه نمی دهد و کاوش گزینه ها را دشوار می کند. -
vite
:vite
عالی می سازد، دارای یکd.ts
پلاگین اما خروجی های آن کم است، آنها همچنین انجام چندین نقطه ورودی آسان ندارند و باید به صورت دستی فایل ها را مشخص کنید بدون الگوی regex مطابقت داشته باشند و حتی با استفاده از یک تابع مسیرهایی را ایجاد کنید که هنوز به همان شکل نبوده است. آسان به عنوانtsup
- bun: ساخت عالی پیکربندی بسیار آسان، اما نه
تنها چیزی که مدتی طول کشید تا سرم را به اطراف بپیچم، رابطه بین آن ها بود build tool config
و package.json
.
-
build tool config
اراده:- نقاط ورود را مشخص کنید
- دایرکتوری خروجی را مشخص کنید
- فرمت خروجی را مشخص کنید
- نقشه منبع را مشخص کنید
- minify را مشخص کنید یا خیر
- تقسیم یا نه را مشخص کنید
-
package.json
اراده: - نام پروژه را مشخص کنید
- نسخه پروژه را مشخص کنید
- محل خروجی/صادرات را مشخص کنید
- محل پیدا کردن انواع مختلف را مشخص کنید (
import
:esm
،require
:cjs
،…) - مکان انواع را مشخص کنید
منابع
shadcn/ui
: پروژه بزرگی که الهام بخش من شد
راهنمای ساده: راهنمای اولیه ایجاد و انتشار یک کتابخانه NPM
npm
+ release-it: آموزش ساده در مورد نحوه انتشار و خودکار کردن انتشار یک npm
بسته ها