استفاده از TypeScript برای پیاده‌سازی کتابخانه‌های Multi Platform (چندسکویی)

سه شنبه 3 بهمن 1396

TypeScript می‌تواند چندین ورژن جاوااسکریپت و چندین قرارداد اکسپورت ماژول (AMD، CommonJS، System، ES6 یا globals) را هدف قرار دهد.

استفاده از TypeScript برای پیاده‌سازی کتابخانه‌های Multi Platform (چندسکویی)

با این حال، به طور کلی، هر پلت‌فرم نیاز به ساختار ماژول و تنظیمات پیکربندی گوناگون دارد. درنیتجه هیچ گزینه داخلی برای تولید همزمان فایل‌های JavaScript و declaration (d.ts) از یک فایل منبع تایپ‌اسکریپت برای چندین پلت‌فرم وجود ندارد.

استثناَ، قراردادهای AMD و CommonJS توسط ماژول UMD به طور همزمان پوشش داده می‌شوند. اما globals و ES6 به تنظیمات مختلفی نیاز دارند.

در این مقاله، ما به شما نشان می‌دهیم که چگونه پروژه تایپ‌اسکریپت خودتان را با هدف قرار دادن پلت‌فرم‌های UMD، globals و ES6 به طور همزمان، با کمک اسکریپت ساده Node.js، سازماندهی کنید.

این اسکریپت کارهای زیر را انجام خواهد داد:

پیش‌پردازش منابع تایپ‌اسکریپت برای انطباق آن‌ها با هر پلت‌فرمی

هماهنگ‌سازی تمام تغییرات خط لوله (pipeline) تا فایل‌های declaration نهایی (d.ts) و فایل‌های جاوا اسکریپت تولید شوند، همچنین فایل‌های جاوااسکریپت برای تمام پلت‌فرم‌ها فشرده شود.

در این مقاله فرض شده است که شما با تایپ‌اسکریپت آشنا هستید، اگر در تایپ‌اسکریپت مبتدی هستید، مقالات پایه‌ای تایپ‌اسکریپت را مطالعه کنید.

کتابخانه‌های Multi-Platform

معمولا کتابخانه‌های مدرن جاوااسکریپت عملکردهای خود را از طریق دسترسی شیء (access-object) منحصربه فرد (مانند جی‌کوئری) ارائه می‌دهند و این دسترسی اشیاء را به شیوه‌ای سازگار با استانداردهای ماژول‌های مختلف نمایش می‌دهند.

به طور خاص، آنها محیط خود را به طور اتوماتیک شناسایی کرده و ماژول‌های AMD/ CommonJS خودشان را نشان داده یا دسترسی شی‌ء را در یک متغیر سراسری قرار می‌دهند.

در حالی که CommonJS سازگاری با Node.js و Browserify را تضمین می‌کند، AMD سازگاری را با بارگذارهای دیگر (RequireJs و SystemJs) و bundlerهای ماژول (ابزار ساخت SystemJs، ابزار بهینه‌سازی RequireJs، WebPack) تضمین می‌کند.

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

در نتیجه، سازگاری با الگوی متغیر سراسری "باید" باشد.

خوشبختانه، شیوه متغیر سراسری، کتابخانه‌ دسترسی شیء (access-object) را به نمایش می‌گذارد و از دو الگوی متفاوت پیروی می‌کند؛ اول، جایی در کتابخانه‌های رایگان معروف، دسترسی شیء به طور مستقیم در یک متغیر سراسری درج شده است (مثلا jQuery برای کتابخانه جی‌کوئری) و دوم، در کتابخانه‌های اختصاصی تمام دسترسی شیءهای نصب شده به عنوان ویژگی‌های ریشه شیء "Company" قرار می‌گیرند (چیزی شبیه myCompany.myLibrary).

سازگاری چند پلت‌فرم و تشخیص خودکار با بسته‌بندی (wrapper) جاوا اسکریپت که کد اصلی را  احاطه کرده است، انجام می‌شود. هنگامی که کتابخانه با دیگر کدهای جاوااسکریپت بسته‌بندی می‌شود، این کد بسته‌بندی شده ممکن است با برخی از بهینه‌سازی‌های bundler (عمدتا با حذف کد استفاده نشده) مداخله ‌کند.

ترکیب‌بندی ماژول ES6 نیازی به بسته‌بندی ندارد زیرا اشیاء ایمپورت‌شده و اکسپورت‌شده با declarationها تعریف می‌شوند و ترکیب‌بندی declarative به bundlerها در سازماندهی‌یشان کمک می‌کند.

بنابراین اگر احتمالا کتابخانه ما با یک bundler شبیه به WebPack 2 که ترکیب‌بندی ماژول ES6 را پشتیبانی می‌کند، بسته‌بندی شود، ایده خوبی است که نسخه‌ای از ترکیب‌بندی ماژول ES6 ارائه شود.

همچنین می‌توانیم به جای ES6 یک ورژن پایین‌تر جاوااسکریپت را استفاده کنیم، اما از آنجا که تمام اکسپورت/ایمپورت‌ها توسط bundler حذف می‌شوند، تمام ماژول‌ها را درون یک بخش منحصربه فرد ادغام می‌کند.

پیاده‌سازی کتابخانه‌ها با تایپ‌اسکریپت

روش تایپ‌اسکریپت، به جاوااسکریپت مربوط به تنظیمات پیکربندی JSON که در ریشه پروژه تایپ‌اسکریپت قرار دارند، ترجمه می شود.

به طور خاص، ماژول‌هایی که سازماندهی می‌شوند توسط ویژگی “module” کنترل می‌شوند، که مقدار آن باید مانند زیر باشد:

none” برای متغیر سراسری مبنی بر ساختار

UMD” پوشش  دادن CommonJS و AMD

es6” برای ساختار ماژول‌های ES6

بنابراین داشتن فایل‌های مختلف پیکربندی و ورژ‌ن‌های مختلف کتابخانه تنها راه برای اطمینان از سازگاری با چندین پلت‌فرم است.

متاسفانه، پذیرفتن ایده چندین مرحله کامپایل و چندین ورژن کتابخانه‌ برای حل این مشکل کافی نیست، زیرا منابع تایپ‌اسکریپت نیز باید کمی تغییر داده شوند تا آنها را با پلت‌فرم‌های مختلف سازگار کند.

در واقع ورژن متغیر سراسری کتابخانه باید شامل فضای نام‌های تایپ‌اسکریپت باشد مانند زیر:

/// <reference path="./teams.ts" />
namespace company{
    export namespace organization{
        //core code
    }
}

جایی که تگ‌های رفرنس ماژول‌های دیگری که ماژول جاری به آن وابسته است را اعلان می‌کنند، و فضای نام‌های تو در تو یک نام سراسری مثل “companyName.libraryName” را تضمین می‌کنند.

نمونه‌ای از ورژن ES6 مانند زیر است:

import {Person, Team} from "./teams"
//core code

جایی که ایمپورت تمام اشیای موجود در ماژول‌های دیگر را مشخص می‌کنند و توسط ماژول جاری استفاده می‌شوند.

در نهایت، ورژن UMD باید شبیه به یک ES6 باشد، اما حداقل یک ماژول باید شامل برخی از اکسپورت‌های مجدد نامهای ایمپورت‌شده از دیگر ماژول‌ها باشد. در واقع، معمولا کتابخانه‌های AMD و CommonJS از طریق یک دسترسی شیء توسط ماژول اصلی منحصربه‌فرد بازگشت‌ داده می‌شوند که همچنین تمام اشیای موردنیاز موجود در سایر ماژول‌های کتابخانه قرار می‌گیرند.

بنابراین برای ماژول اصلی باید چیزی شبیه به این داشته باشیم:

import {Person, Team} from "./teams"
export {Person, Team}
//core code

در نتیجه، ما به پیش‌پردازش‌های منابع‌مان نیاز داریم.

خوشبختانه، مثال‌های بالا نشان می‌دهند که این پیش‌پردازش واقعا ساده است و شامل موارد زیر است:

استخراج کد اصلی از هر منبع ماژول

ایجاد سه فایل مختلف تایپ‌اسکریپت از هر منبع توسط اضافه کردن یک بسته‌بندی متفاوت پیرامون همان کد اصلی

بنابراین حالا می‌توان مانند زیر اقدام کرد:

1. ماژول‌هایمان را در جاوااسکریپت با استفاده از هر یک از سبک‌هایی که قبلا در بالا تعیین کردیم (متغیر global، UMD، ES6) توسعه می‌دهیم. بهترین داوطلب ES6 است زیرا دستورات به شیوه‌ای که می‌خواهیم کتابخانه را گسترش دهیم وابسته نیست.

2. کد اصلی هر ماژول را با قرار دادن آن بین دو نماد ///{ و ///} به صورت زیر شرح می دهیم:

import {Person, Team} from "./teams"
///{
            //core code is here
///}

سپس برای هر یک از ماژول‌های منبع سه نوع بسته‌بندی تعریف می‌کنیم، یکی برای هر پلت‌فرم مقصد. در هر بسته‌بندی، مکانی را برای قرار دادن کد اصلی با نماد ///{} علامت‌گذاری می‌کنیم، همانند زیر:

namespace company{
        export namespace organization{
            ///{}
        }
}

وقتی همه چیز را در جای خود قرار دادیم، منابع مان، بسته‌بندی‌ها و سه فایل مختلف پیکربندی تایپ‌اسکریپت، می‌توانیم یک اسکریپت Node.js را راه‌اندازی کنیم که اجراکنندگان باید پیش‌پردازش و کامپایل‌های همه فایل‌های پیش‌پردازش را به فایل‌های جاوااسکریپت، فایل‌های declaration تایپ‌اسکریپت (.d.ts) و فایل‌های map (map.) واگذار کنند.

همان اسکریپت می‌تواند هر فایل جاوا اسکریپت را با UglifyJS فشرده کند.

یک نمونه پروژه

اجازه دهید همه چیز را با جزئیات در یک مثال ساده بیان کنیم.

آماده‌سازی پروژه

در قدم اول، مطمئن شوید که آخرین ورژن Node.js و TypeScript compiler را نصب کرده‌اید. (دستور tsc باید در هر دایرکتوری موجود باشد).

در ادامه کار ما از ویژوال استودیو استفاده خواهیم کرد، اما اگر شما بخواهید، می‌توانید تمام مراحل را با IDE متفاوتی اجرا کنید، زیرا از ویژگی خاصی در ویژوال استودیو استفاده نمی‌کنیم.

یک فولدر برای پروژه خود ایجاد کنید، سپس با استفاده از ویژوال استودیو آن را باز کنید.

اگر قابل مشاهده نیست، از ترمینال ویژوال استودیو استفاده کنید. (همچنین می‌توانید command prompt را در پوشه پروژه خود باز کنید)

ما به UglifyJS نیاز داریم، بنابراین اجازه دهید آن را در فولدرمان با استفاده از npm نصب کنیم:

> npm install uglify-js

درنهایت، یک فولدر “src” برای منابع‌مان اضافه می‌کنیم.

تعریف فایل‌های پیکربندی تایپ‌اسکریپت

ما نیاز به سه فایل پیکربندی مختلف تایپ اسکریپت برای هر پلت‌فرم داریم.

از آنجایی که آنها فقط در تعدادی از تنظیمات باهم تفاوت دارند، ممکن است بیشتر تنظیمات خود را از از یک فایل پیکربندی مشترک به ارث برده باشند.

یک فایل جدید به پروژه اضافه کنید و نام آن را “tsconfig.base.json” بگذارید. این فایل شامل پیکربندی مشترک است:

{
    "compilerOptions": {
        "moduleResolution":"classic",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true,
        "declaration":true,
        "target":"es3",
        "strictNullChecks": false
    }
}

حالا می‌توانیم پیکربندی ES6 را به عنوان “tsconfig.es6.json” تعریف کنیم:

{
    "extends": "./tsconfig.base",
    "compilerOptions":{
        "declarationDir": "./dest/es6",
        "outDir":"./dest/es6",
        "module": "es6"
    },
    "include":[
        "proc/es6/**/*.*"
    ]
}

از آنجایی که ویژگی “include” فایل‌های منبع را مشخص می کند، ویژگی “module” نوع ترکیب‌بندی ماژول‌ها را مشخص می‌کند و در نهایت “declarationDir” و “outDir” فولدرهایی هستند که فایل‌های declaration تایپ‌اسکریپت خروجی و فایل‌های جاوااسکریپت را به ترتیب جایگذاری می‌کنند.

پیکربندی UMD به عنوان “tsconfig.umd.json”:

{
    "extends": "./tsconfig.base",
    "compilerOptions":{
        "declarationDir": "./dest/umd",
        "outDir":"./dest/umd",
        "module": "umd"
    },
    "include":[
        "proc/umd/**/*.*"
    ]
}

این مورد، ترکیب‌بندی ماژول “umd” است و فولدرهای ورودی و خروجی متفاوت هستند.
درنهایت پیکربندی متغیر سراسری به عنوان “tsconfig.global.json” به شرح زیر است:

{
    "extends": "./tsconfig.base",
    "compilerOptions":{
        "declarationDir": "./dest/global",
        "outDir":"./dest/global",
         "module": "none"
    },
    "include":[
        "proc/global/**/*.*"
    ]
}

در اینجا “module” با مقدار “none” مشخص می‌کند که هیچ ماژولی وجود ندارد، زیرا به جای آن از فضای نام استفاده می‌کنیم.

سه فایل مختلف JSON در مقدار ویژگی “module” و در مکان‌هایی که منابع‌شان را می‌گیرند و در نتیجه کامپایل تولید می‌شود، تفاوت دارند.

تمام منابع تایپ‌اسکریپت از زیرپوشه‌های مختلف فولدر “proc” گرفته شده‌اند.

در واقع، پیش‌پردازش‌ها همه فایل‌ها را از فولدر “src” و از هر یک از آنها دریافت میکنند و سه ورژن تغییریافته جدید ایجاد خواهد کرد، یکی در فولدر “proc/global”، دیگری در فولدر “proc/es6” و سومی در فولدر “proc/umd”.

به همین ترتیب، تمام نتایج کامپایل در فولدرهای dest/global”، “dest /es6” و “dest /umd” قرار خواهند گرفت.

مثالی از ماژول‌های TypeScript

اجازه دهید ماژولی حاوی کلاس‌های ساده "person" و "team" را داخل فولدر “src” خود اضافه کنیم. نام این فایل را “teams.ts” می‌گذاریم:

///{
export class Person
{
    name: string;
    surname: string;
    role: string;
}
export class Team 
{
    name: string;
    members: Person[]
    constructor(name: string, ...members: Person[]){
        this.name = name;
        this.members=members;
    }
    add(x: Person){
        this.members.push(x);
    }
}
///}

در این ماژول ساده، کد اصلی را بین نمادهای ///{ و ///} قرار می‌دهیم.

همچنین ماژول “projects.ts” را که حاوی منطق برای اختصاص دادن teams و persons در پروژه است را تعریف می‌کنیم.

import {Person, Team} from "./teams"
///{
export class Project
{
    name: string;
    description: string;
    team: Team|null;
 
    constructor(name: string, description: string)
    {
        this.name=name;
        this.description=description;
    }
    assignTo(team: Team)
    {
        this.team=team;
    }
    addHumanResource(x: Person): boolean
    {
        if(!this.team) return false;
        this.team.add(x);
        return true;
    }
}
///}

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

تعریف تمام بسته‌بندی‌ها (wrappers)

بسته‌بندی‌ها در فایل‌های قرار داده شده در فولدر “src” تعریف شده‌اند. ما آن‌ها را همان نامی که ماژول به آن‌ها اشاره می‌کند، می‌نامیم، اما با پسوندهای مختلف.

پسوندها es6.addumd.add. و global.add. نامیده می‌شوند.

در اینجا تمام wtapper ها برای ماژول teams نشان داده شده است.

teams.global.add:
namespace company{
    export namespace organization{
        ///{}
    }
}

teams.umd.add و  teams.es6.add برابر هستند و فقط حاوی placeholder برای کد اصلی هستند، زیرا آن‌ها هیچ import statement ای ندارند:

///{}

projects.es6.add در عوض کلاس‌ها را از ماژول teams ایمپورت می‌کند، محتوای آن به شرح زیر است:

import {Person, Team} from "./teams"
///{}

از آنجا که ماژول‌ projects ماژول اصلی کل کتابخانه است، projects.imd.add نه تنها کلاس‌های تعریف‌شده در ماژول projects را ایمپورت می‌کند، بلکه مجددا آن‌ها را اکسپورت می‌کند، بنابراین محتوای آن مانند زیر است:

import {Person, Team} from "./teams"
export {Person, Team}
///{}

projects.global.add فقط شامل تعاریف فضای نام‌هایی مانند teams.global.add نیست، بلکه شامل یک “reference declaration” و تعریف متغیر برای هر شیء ایمپورت شده از ماژول teams است:

/// <reference path="./teams.ts" />
namespace company{
    export namespace organization{
        let Team = organization.Team;
        let Person = organization.Person;
    }
}

اعلان متغیر باید در بسته “global.add.” هر بار که ماژول منبع شامل ایمپورت بود ارائه شود، وگرنه همه رفرنس‌ها برای اشیای ایمپورت‌شده در کد اصلی تعریف نشده خواهند بود.

ایجاد پروژه

ما یک اسکریپت Node.js ساده برای اجرای پیش‌پردازش‌های مورد نیاز، برای فراخوانی کامپایلر TypeScript و برای فشرده‌سازی تمام فایل‌ها استفاده می‌کنیم. با این حال، شما می‌توانید فقط بخشی از پیش‌پردازش اسکریپت را بگیرید و همه کارهای دیگر را با Gulp یا Grunt سازماندهی کنید.

این اسکریپت را در ریشه پروژه می‌گذاریم و آن را  “go.js” می‌نامیم، اما شما می‌توانید از نام دیگری مثل “build.js” استفاده کنید.

در هدر فایل، تمام موارد مورد نیاز را اضافه می‌کنیم:

var exec = require('child_process').exec;
var fs = require("fs");
var path = require('path');
var UglifyJS = require("uglify-js");

fs” و “path” برای پردازش فایل‌ها و پوشه‌ها مورد نیاز هستند، “Uglify-js” (که هنگام ساخت پروژه نصب شد) برای فشرده‌سازی فایل‌های جاوااسکریپت مورد نظر، و درنهایت “child_process” هم برای راه‌اندازی دستور “tsc” لازم است.

سپس تنظیماتی را انجام می‌دهیم:

var src="./src";
var dest = "./proc";
var fdest ="./dest";
var dirs = [
  'global',
  'umd',
  'es6'
];
var except = [
  'require','exports','module', 'export', 'default'
];

این‌ها شامل تمام اسامی پوشه‌هاست. می توانید آن‌ها را تغییر دهید، اما پس از آن باید پسوندهای wrapperها، نام فایل‌های پیکربندی تایپ‌اسکریپت و همچنین تمام پوشه‌هایی که در این فایل‌های پیکربندی به آن‌ها مراجعه می‌شود را تغییر دهید.

متغیر “except” به UglifyJS پاس داده می‌شود و شامل تمام "کلمات رزرو شده" است که نباید استفاده شوند. در صورت نیاز، ممکن است شما نام‌های بیشتری را اضافه کنید، اما اسامی موجود را حذف نکنید، زیرا آن‌ها در بسته UMD که توسط کامپایلر تایپ‌اسکریپت ایجاد شده است، استفاده می‌شوند.

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

var extract = function(x){
  var startIndex = x.indexOf("///{");
  if(startIndex < 0) return x;
  else startIndex=startIndex+4;
  var endIndex = x.lastIndexOf("///}");
  if(endIndex>0) return x.slice(startIndex, endIndex);
  else return x.slice(startIndex);
}

این دستور کد اصلی را از رشته‌ای حاوی فایل منبع استخراج می‌کند:

var walk = function(directoryName, ext) {
  var res=  [];
  ext=ext || ".ts";
  var files=fs.readdirSync(directoryName);   
  files.forEach(function(file) {
      let fullPath = path.join(directoryName,file);
      let f= fs.statSync(fullPath);
      if (f.isDirectory()) {
          res=res.concat(walk(fullPath));
      } else {
      if(fullPath.match(ext+"$"))
            res.push(fullPath)
      }
    });
  return res;
};

تابع بازگشتی “walk”، نام تمام فایل‌ها با پسوند “ext” موجود در پوشه “directory name” و در تمام پوشه‌های فرزند آن را گردآوری می‌کند.

با تعریف کاربردهای فوق، مرحله پیش پردازش به راحتی اجرا می‌شود:

var toProcess=walk(src);
if(!fs.existsSync(dest)) fs.mkdirSync(dest);
dirs.forEach(function(dir){
  let outDir = path.join(dest, dir);
  if(!fs.existsSync(outDir)) fs.mkdirSync(outDir);
  toProcess.forEach(function(file){
    let toAdd=file.substr(0, file.length-3)+"."+dir+".add";
    let outFile=path.join(dest, dir, path.basename(file));
     
    if(fs.existsSync(toAdd)){
      let input = extract(fs.readFileSync(file, 'utf8'));
      fs.writeFileSync(outFile, 
          fs.readFileSync(toAdd, 'utf8').replace("///{}", input));
    }
    else{
      fs.writeFileSync(outFile, 
          fs.readFileSync(file));
    }
  })
});

این کد تمام فایل‌های منبع را با تابع “walk” جمع‌آوری می‌کند، سپس پوشه root را برای همه فایل‌های پیش‌پردازش، در صورتی که وجود داشته باشند، ایجاد می‌کند.

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

هر فایل منبع در یک رشته خوانده می‌شود، هسته آن توسط تابع “extract” استخراج شده و در “content area” هر فایل wrapper با جایگزینی رشته قرار می‌گیرد.

بخش نهایی کد به طور غیرهمزمان کامپایلر تایپ‌اسکریپت را فراخوانی کرده و تمام نتایج کامپایل را در callback مربوطه فشرده می‌سازد:

if(!fs.existsSync(fdest)) fs.mkdirSync(fdest);
for(let i=0; i<dirs.length; i++)
{
  let config = 'tsc -p tsconfig.'+dirs[i]+'.json';
  let fOutDir = path.join(fdest, dirs[i]);
  if(!fs.existsSync(fOutDir)) fs.mkdirSync(fOutDir);
  console.log("start "+dirs[i]);
  exec(config, function(error, stdout, stderr) {
    console.log(stdout);
    console.error(stderr);
    if(dirs[i] != 'es6') {
      let files = walk(fOutDir, ".js");
      files.forEach(function(file){
        let baseName=file.substr(0, file.length-3);
        if(baseName.match(".min$")) return;
        let inMap = file+".map";
        if(!fs.existsSync(inMap)) inMap=undefined;
        let outFile = baseName+".min.js";
        let outMap = baseName+".min.js.map";
        let res=UglifyJS.minify(file, {
          outSourceMap: path.basename(outMap),
          outFileName: path.basename(outFile),
          inSourceMap: inMap,
          except:except
        });
        fs.writeFileSync(outFile, res.code);
        fs.writeFileSync(outMap, res.map);
      });
    }
    console.log("finished "+dirs[i]);
  });
}

کامپایلر تایپ‌اسکریپت سه مرتبه فراخوانی می‌شود، هر بار برای هر فایل typescript تعریف‌شده. نام فایل پیکربندی به عنوان آرگومان خط فرمان پاس داده می‌شود. قبل از فراخوانی کامپایلر، همه پوشه‌های موردنیاز، اگر وجود نداشته باشند، ایجاد می‌شوند.

در callback، پیام‌ها و خطاهای احتمالی در کنسول نمایش داده می‌شوند. برای هر فایل ورودی که کامپایلر ایجاد می‌کند: فایل JavaScript مربوطه، فایل declaration تایپ‌اسکریپت و یک source map تولید می‌شود.

به این ترتیب کاربران کتابخانه تایپ‌اسکریپت می‌توانند تعاریف کتابخانه را ایمپورت کرده و کدها را دیباگ کنند.

در نهایت، UglifyJS فراخوانی شده و source map را توسط کامپایلری که map ورودی را به آن پاس داده است، ایجاد می‌کند. به این ترتیب، source map نهایی کد فشرده شده به منابع اصلی تایپ‌اسکریپت به جای فایل واسطه جاوااسکریپت رجوع خواهد کرد.

تست اسکریپت Node.js

می‌توانید اسکریپت Node.js را در پروژه‌مان تست کنید.

در ترمینال ویژوال استودیو ( یا در هر پنجره فرمان روت در فولدر پروژه) دستور زیر را اجرا کنید:

> node go

باید پوشه‌های جدید “proc” و “dest” را که هر کدام حاوی سه زیر پوشه (“es6” ، “umd” و “global”) است ببینید.

پوشه “proc” حاوی فایل‌های پیش‌پردازش تایپ‌اسکریپت است، در حالی که هر زیرپوشه فولدر “dest” حاوی تمام نتایج فشرده‌سازی و کامپایل می‌باشد یعنی: “projects.d.ts”, “projects.js”, “projects.js.map”, “projects.min.js” و “projects.min.js.map”.

می‌توانید ورژن umd کتابخانه مثال را با کمک دیباگر Node.js در ویژوال استودیو تست کنید.

این مراحل را دنبال کنید:

یک فایل “test.js” در ریشه پروژه ایجاد کرده و با کدهای زیر آن را پر کنید:

let projects=require("./dest/umd/projects.js")
 
var project = new projects.Project("test", "test description");
project.assignTo(
    new projects.Team("TypeScript team", 
    new projects.Person("Francesco", "Abbruzzese", "team leader")));
var team = project.team;

برای دسترسی به همه کلاس‌ها از ماژول projects استفاه خواهیم کرد، زیرا در ورژن umd کتابخانه، به عنوان ماژول اصلی عمل کرده و تمام اشیای موجود در کتابخانه را اکسپورت کرده است.

یک breakpoint روی اولین خط کد بگذارید. به دیباگر ویژوال استودیو رفته و برای خطایابی دکمه play را کلیک کنید. مرحله به مرحله با کمک کلید “F10” کد را اجرا کنید و مقادیر تمام متغیرها را با نگه داشتن موس روی تمام آن‌ها بررسی کنید.

بنابراین می‌توانید بررسی کنید که همه کلاس‌ها به درستی ایجاد شده باشند.

نتیجه‌گیری

به طور خلاصه، چندین پلت‌فرم، به لطف تعریف تکنیک ساده پیش‌پردازش، می‌توانند به صورت همزمان توسط یک کتابخانه تایپ‌اسکریپت با چندین ورژن تولیدشده از هر ماژول (هر کدام برای هر پلت‌فرم موردنظر) هدف قرار گیرند.

تمام کد منبع این مقاله را از اینجا دانلود کنید. (Github)

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید