ایمپورت دینامیک در جاوا اسکریپت — از صفر تا صد

۲۰۳ بازدید
آخرین به‌روزرسانی: ۰۷ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
ایمپورت دینامیک در جاوا اسکریپت — از صفر تا صد

ما از نسخه ES2020 به بعد به طور رسمی می‌توانیم از امکان ایمپورت دینامیک استفاده کنیم. این قابلیت علاوه بر همه قابلیت‌های دیگر نسخه جدید ES بسیار برجسته به نظر می‌رسد. در این راهنما به بررسی تفاوت‌های بین ایمپورت معمولی و ایمپورت دینامیک در جاوا اسکریپت و بحث «درخت‌تکانی» (Tree Shaking) می‌پردازیم.

درخت‌تکانی یعنی چه؟

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

درخت‌تکانی می‌تواند موجب کاهش اندازه فایل باندل‌شده جاوا اسکریپت شود. این عملیات تنها با ساختار import و export کار می‌کند. بنابراین require که یک ساختار رایج جاوا اسکریپت است، پشتیبانی نمی‌شود. برای نمونه Webpack برای هر ماژول نامی تعیین می‌کند و شامل ماژول‌هایی است که از فایل‌های دیگر در فایل باندل شده در زمان بیلد فراخوانی می‌شوند.

تنظیمات پروژه

ما در این راهنما از پروژه زیر استفاده می‌کنیم:

1export const pow = (a: number, b: number): number => a ** b;
2export const subtract = (a: number, b: number): number => a - b;
3export const multiply = (a: number, b: number): number => a * b;
4export const divide = (a: number, b: number): number => a / b;

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

1import { pow } from './a';
2pow(1, 2);

Main.ts تنها add را ایمپورت می‌کند و آن را مورد استفاده قرار می‌دهد. تابع‌های دیگر در این پروژه استفاده نمی‌شوند.

1const path = require('path');
2const { CleanWebpackPlugin } = require('clean-webpack-plugin');
3
4module.exports = {
5  entry: './main.ts',
6  output: {
7    filename: 'bundle.js',
8    path: path.resolve(__dirname, 'dist')
9  },
10  module: {
11    rules: [
12      {
13        test: /\.tsx?$/,
14        use: 'ts-loader',
15        exclude: /node_modules/
16      }
17    ]
18  },
19  plugins: [new CleanWebpackPlugin()],
20  resolve: {
21    extensions: ['.tsx', '.ts', '.js']
22  },
23  mode: 'development',
24  optimization: {
25    usedExports: true
26  }
27};

فایل تنظیمات Webpack به صورت زیر است:

1mode: 'development',
2optimization: {
3  usedExports: true
4}

mode را به صورت development تنظیم کرده‌ایم، از این رو فایل باندل شده ویرایش نمی‌شود و به منظور درخت‌تکانی، usedExported به صورت true تعیین می‌شود.

1"build": "webpack"

اکنون باید این کد را روی ترمینال اجرا کنیم.

نتیجه ایمپورت استاتیک (جزئی)

> npm run build

اینک فایل باندل شده است. فایل main.ts تابع pow را از a.ts با کلیدواژه import می‌گیرد.

// main.ts
import { pow } from './a';
pow(1, 2);

در فایل bundle.js می‌توانید کد زیر را ببینید:

1/* harmony export (binding) */ 
2__webpack_require__.d(__webpack_exports__, "a", function() { return pow; });
3
4/* unused harmony export subtract */
5/* unused harmony export multiply */
6/* unused harmony export divide */
7var pow = function (a, b) { return Math.pow(a, b); };
8var subtract = function (a, b) { return a - b; };
9var multiply = function (a, b) { return a * b; };
10var divide = function (a, b) { return a / b; };
11//# sourceURL=webpack:///./a.ts?

علی‌رغم این که همه تابع‌های اکسپورت شده در باندل گنجایش یافته‌اند، اما تنها pow در عمل به دست می‌آید. همچنین پیام ...unused harmony export  به اطلاع ما و وب‌پک می‌رساند که کدام یک اکسپورت شده‌اند. نتیجه آن به صورت زیر است:

// main.ts
import { pow, multiply } from './a';
pow(1, 2);

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

نتیجه ایمپورت استاتیک (کامل)

شاید کنجکاو باشید که در صورت ایمپورت کردن همه تابع‌ها به صورت یکباره چه تفاوتی بروز می‌یابد؟

// main.ts
import * as module from './a';
module.pow(1, 2);

نتیجه یکسان است. در مورد حالتی که تابع default وجود داشته باشد:

export default divide;

اکنون divide تابع پیش‌فرض است و هنگامی که تابع‌های ایمپورت شونده ذکر نشده باشند، اکسپورت می‌شود.

// main.js
import divide, { pow } from './a';
divide(1, 2);
pow(1, 2);

نتیجه کمی متفاوت است:

1/* harmony export (binding) */ 
2__webpack_require__.d(__webpack_exports__, "b", function() { return pow; });
3/* unused harmony export subtract */
4/* unused harmony export multiply */
5/* unused harmony export divide */
6var pow = function (a, b) { return Math.pow(a, b); };
7var subtract = function (a, b) { return a - b; };
8var multiply = function (a, b) { return a * b; };
9var divide = function (a, b) { return a / b; };
10
11/* harmony default export */ 
12__webpack_exports__["a"] = (divide);
13
14//# sourceURL=webpack:///./a.ts?

اکنون وب پک divide را به صورت تابع اکسپورت پیش‌فرض اضافه می‌کند. اما نکته اینجا است که وب‌پک می‌داند کدام تابع در عمل استفاده می‌شود و آن‌ها را به روش خاص خود دسته‌بندی می‌کند.

نکته

در مستندات رسمی وب پک اشاره شده است که تصمیم‌گیری واقعی در مورد این که کدام تابع‌ها باید در فایل‌های باندل قرار گیرند، کار بسیار دشواری است. همچنین در مورد عوارض جانبی هر تابع برای نمونه تغییر دادن مقادیر در دامنه بیرونی دامنه جاری صحبت شده است. تست کردن این مثال کاملاً آسان است.

نتیجه ایمپورت دینامیک

اکنون کد را طوری تغییر می‌دهیم که از ایمپورت دینامیک استفاده کند:

1import('./a').then(module => {
2  const { pow } = module;
3  pow(1, 2);
4});

ایمپورت دینامیک یک promise بازگشت می‌دهد که می‌توان به متدهای then یا catch وصل کرد. اگر فایل با موفقیت بارگذاری شود، نتیجه به then ارسال می‌شود. این متد یک پارامتر می‌گیرد که معمولاً نام آن module است. Module شامل تابع اکسپورت است. ایمپورت دینامیک اساساً به خاطر قابلیت غیر درخت‌تکانی خود مشهور است. در ادامه وضعیت کنونی فایل باندل را بررسی می‌کنیم.

زمانی که دستور npm run build را اجرا می‌کنیم، یک فایل باندل جدید به نام 0.bundle.js ساخته می‌شود:

1_webpack_require__.r(__webpack_exports__);
2
3/* harmony export (binding) */ 
4__webpack_require__.d(__webpack_exports__, "pow", function() { return pow; });
5/* harmony export (binding) */ 
6__webpack_require__.d(__webpack_exports__, "subtract", function() { return subtract; });
7/* harmony export (binding) */ 
8__webpack_require__.d(__webpack_exports__, "multiply", function() { return multiply; });
9/* harmony export (binding) */ 
10__webpack_require__.d(__webpack_exports__, "divide", function() { return divide; });
11
12var pow = function (a, b) { return Math.pow(a, b); };
13var subtract = function (a, b) { return a - b; };
14var multiply = function (a, b) { return a * b; };
15var divide = function (a, b) { return a / b; };
16
17/* harmony default export */ 
18__webpack_exports__["default"] = (divide);
19
20//# sourceURL=webpack:///./a.ts?

همه تابع‌ها در لیست اکسپورت گنجایش یافته‌اند. در حالت پروداکشن همه تابع‌های استفاده نشده حذف می‌شوند. دلیل این که تابع‌های استفاده نشده را نیز در فایل‌های باندل شده می‌بینید، این است که mode در webpack.config.json به جای production روی development قرار دارد.

Mode را به صورت زیر عوض کنید:

1"mode": "production"

این بخش را نیز حذف کنید:

1// remove
2"optimization": {
3  "usedExports": true
4}

اکنون دستور npm run build را اجرا کنید.

با ایمپورت استاتیک

1// main.ts
2import { pow } from './a';
3pow(1, 2);
4// bundle.js
5!(function(e) {
6  ...
7})([
8  function(e, t, r) {
9    'use strict';
10    r.r(t);
11    !(function(e, t) {
12      Math.pow(e, t);
13    })(1, 2);
14  }
15])

چنان گه گفتیم ایمپورت دینامیک یک promise بازگشت می‌دهد که به then وصل می‌شود، اما به نظر می‌رسد که تنها تابع مورد استفاده در فایل باندل شده نیز گنجانده شده است.

فایل‌های دیگری نیز وجود دارند:

1(window.webpackJsonp = window.webpackJsonp || []).push([
2  [1],
3  [
4    ,
5    function(n, t, u) {
6      'use strict';
7      u.r(t),
8        u.d(t, 'pow', function() {
9          return r;
10        }),
11        u.d(t, 'subtract', function() {
12          return i;
13        }),
14        u.d(t, 'multiply', function() {
15          return o;
16        }),
17        u.d(t, 'divide', function() {
18          return c;
19        });
20      var r = function(n, t) {
21          return Math.pow(n, t);
22        },
23        i = function(n, t) {
24          return n - t;
25        },
26        o = function(n, t) {
27          return n * t;
28        },
29        c = function(n, t) {
30          return n / t;
31        };
32      t.default = c;
33    }
34  ]
35]);

همه تابع‌ها در این فایل قرار گرفته‌اند.

چرا ایمپورت دینامیک از درخت‌تکانی پشتیبانی نمی‌کند؟

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

1if (isOdd(num)) {
2  import('./odd').then(module => ...);
3}

وب‌پک نمی‌داند که آیا فایل در نهایت ایمپورت خواهد شد یا نه. زیرا isOdd تضمین نمی‌کند که همواره true باشد.

سخن پایانی

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

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

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
۱ دیدگاه برای «ایمپورت دینامیک در جاوا اسکریپت — از صفر تا صد»

سلام این مطلب شما از بسیاری از پاسخ به بعضی سوالات در خصوص موضوع مشابه در StackOverFlow بهتر بود. متشکرم.
اگر در مطلب پایانی
(window.webpackJsonp = window.webpackJsonp || []).push
کسی بخواهد یک تابع را از فایلی دیگر و یا کنسول فراخوانی کند چطور باید رفرنس بده.
مثلا این تابع:
var r = function(n, t) {
return Math.pow(n, t);

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *