يمكنك نشر محتوى JavaScript حديث وشحنه وتثبيته للحصول على تطبيقات أسرع.

يمكنك تحسين الأداء من خلال تفعيل تبعيات ومخرجات JavaScript الحديثة.

بإمكان أكثر من 90% من المتصفّحات تشغيل إصدار JavaScript الحديث، غير أنّ انتشار استخدام JavaScript القديم لا يزال مصدرًا كبيرًا لمشاكل الأداء على الويب اليوم.

JavaScript حديث

لا تتم الإشارة إلى لغة JavaScript الحديثة كرمز مكتوب في إصدار خاص بمواصفات ECMAScript، بل ضمن بنية متوافقة مع جميع المتصفحات الحديثة. وتشكّل متصفّحات الويب الحديثة، مثل Chrome وEdge وFirefox وSafari، أكثر من 90% من سوق المتصفّحات، وتشكّل المتصفّحات المختلفة التي تعتمد على نفس المحركات الأساسية للعرض 5% إضافية. وهذا يعني أنّ 95% من عدد زيارات الويب على مستوى العالم تأتي من المتصفحات التي تتوافق مع ميزات لغة JavaScript الأكثر استخدامًا على مدار السنوات العشر الماضية، بما في ذلك:

  • الفصول الدراسية (ES2015)
  • دوال الأسهم (ES2015)
  • مولدات (ES2015)
  • حظر تحديد نطاق (ES2015)
  • التدمير (ES2015)
  • مَعلمة الراحة والانتشار (ES2015)
  • اختصار الكائنات (ES2015)
  • Async/await (ES2017)

عادةً ما تكون الميزات في الإصدارات الأحدث من مواصفات اللغة أقل اتساقًا عبر المتصفحات الحديثة. على سبيل المثال، تتوافق العديد من ميزات ES2020 وES2021 مع 70% من سوق المتصفحات فقط، ولا تزال معظم المتصفحات متاحة في الوقت نفسه، ولكن هذه الميزات غير كافية لاعتماد بشكل مباشر على هذه الميزات. وهذا يعني أنّه على الرغم من أنّ لغة JavaScript "الحديثة" هدف متغيّر، يتضمّن ES2017 أوسع نطاق من التوافق مع المتصفّحات مع تضمين معظم ميزات البنية الحديثة الشائعة الاستخدام. بمعنى آخر، ES2017 هو الأقرب إلى بناء الجملة الحديث اليوم.

JavaScript قديمة

لغة JavaScript القديمة هي رمز برمجي يتجنّب على وجه التحديد استخدام جميع ميزات اللغة المذكورة أعلاه. يكتب معظم المطورين رمز المصدر الخاص بهم باستخدام بناء جملة حديث، ولكن يجمع كل شيء حسب البنية القديمة لزيادة دعم المتصفح. ولا شك في أنّ التجميع إلى بنية قديمة يؤدي إلى زيادة في التوافق مع المتصفّح، إلا أنّ تأثيره غالبًا ما يكون أقل من توقّعاتنا. في كثير من الحالات، يزداد الدعم من حوالي 95٪ إلى 98% مع تكبد تكلفة كبيرة:

  • وتكون لغة JavaScript القديمة عادةً أكبر وأبطأ بنسبة% 20 من الرموز البرمجية الحديثة المكافئة. غالبًا ما تؤدي أوجه القصور في الأدوات والتكوين الخاطئ إلى توسيع هذه الفجوة بشكل أكبر.

  • تمثل المكتبات المثبتة ما يصل إلى 90% من رمز JavaScript للإنتاج النموذجي. تتحمّل رموز المكتبة مقدارًا أعلى من تجاوز JavaScript القديم بسبب تكرار رموز polyfill ومساعدها الذي يمكن تجنُّبه من خلال نشر الرموز البرمجية الحديثة.

JavaScript حديث على npm

في الآونة الأخيرة، وحّدت Node.js الحقل "exports" لتحديد نقاط إدخال الحزمة:

{
  "exports": "./index.js"
}

تشير الوحدات المشار إليها في الحقل "exports" إلى إصدار عقدة لا يقلّ عن 12.8 يتوافق مع ES2019. وهذا يعني أنّ أي وحدة تتم الإشارة إليها باستخدام الحقل "exports" يمكن كتابتها بلغة JavaScript حديثة. على مستهلكي الحِزم افتراض أنّ الوحدات التي تحتوي على الحقل "exports" تحتوي على رموز برمجية حديثة ويمكن نقلها إذا لزم الأمر

حديث فقط

إذا أردت نشر حزمة باستخدام رمز حديث وتركها للمستهلك للتعامل مع الترجمة عند استخدامها كتبعية، استخدِم الحقل "exports" فقط.

{
  "name": "foo",
  "exports": "./modern.js"
}

حديث مع احتياطي قديم

استخدِم حقل "exports" إلى جانب "main" لنشر الحزمة باستخدام الرموز الحديثة، مع تضمين أيضًا إجراء احتياطي ES5 + CommonJS للمتصفّحات القديمة.

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

خيار عصري مع تحسينات قديمة على الجهاز الاحتياطي وحزمة ESM

بالإضافة إلى تحديد نقطة إدخال CommonJS احتياطية، يمكن استخدام الحقل "module" للإشارة إلى حزمة احتياطية قديمة مشابهة، ولكنّها تستخدم بنية وحدة JavaScript (import وexport).

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

تعتمد العديد من حِزم البيانات، مثل webpack وrollup، على هذا الحقل للاستفادة من ميزات الوحدة وتفعيل اهتزاز الأشجار. هذه الحزمة ما زالت قديمة لا تحتوي على أي رموز برمجية حديثة باستثناء بنية import/export، لذا يمكنك استخدام هذه الطريقة لإرسال الرموز البرمجية الحديثة مع عنصر احتياطي قديم لا يزال محسَّنًا للدمج.

لغة JavaScript الحديثة في التطبيقات

تشكل تبعيات الجهات الخارجية الغالبية العظمى من رمز JavaScript النموذجي للإنتاج في تطبيقات الويب. رغم نشر تبعيات npm سابقًا كبنية ES5 قديمة، لم يعد هذا افتراضًا آمنًا، كما أنّ تحديثات التبعية قد تتسبب في تعطُّل توافق المتصفّح في تطبيقك.

مع انتقال عدد متزايد من حزم npm إلى لغة JavaScript الحديثة، من المهم التأكّد من إعداد أدوات الإنشاء للتعامل معها. هناك فرصة جيدة أن بعض حزم npm التي تعتمد عليها تستخدم بالفعل ميزات لغة حديثة. هناك عدد من الخيارات المتاحة لاستخدام التعليمات البرمجية الحديثة من npm بدون تعطيل تطبيقك في المتصفحات القديمة، ولكن الفكرة العامة هي أن يقوم نظام التصميم بتحويل التبعيات إلى نفس هدف البنية مثل رمز المصدر الخاص بك.

حزمة الويب

بدءًا من Webpack 5، أصبح بالإمكان ضبط بناء الجملة الذي ستستخدمه حزمة webpack عند إنشاء التعليمات البرمجية للحِزم والوحدات. لا يؤدي هذا إلى تحويل التعليمة البرمجية أو التبعيات، بل يؤثر فقط على كود "الغراء" الذي يتم إنشاؤه بواسطة حزمة الويب. لتحديد هدف دعم المتصفّح، أضِف إعدادًا لقائمة المتصفّحات إلى مشروعك، أو نفِّذ ذلك مباشرةً في إعدادات حزمة الويب:

module.exports = {
  target: ['web', 'es2017'],
};

من الممكن أيضًا ضبط حزمة الويب لإنشاء حِزم محسَّنة تحذف دوال برامج التضمين غير الضرورية عند استهداف بيئة وحدات ES حديثة. يؤدي ذلك أيضًا إلى إعداد حزمة الويب لتحميل حِزم مقسَّمة الرموز باستخدام <script type="module">.

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

يتوفر عدد من مكوّنات حزم الويب الإضافية التي تتيح تجميع لغة JavaScript الحديثة ونقلها مع الاستمرار في دعم المتصفّحات القديمة، مثل المكوّن الإضافي Optimize وBabelEsmPlugin.

المكوّن الإضافي "أدوات تحسين الأداء"

المكوّن الإضافي Optimize هو مكوّن إضافي لحزمة ويب يعمل على تحويل الرمز البرمجي النهائي المجمّع من لغة JavaScript الحديثة إلى القديمة بدلاً من كل ملف مصدر فردي. وهو إعداد مستقل يسمح لتكوين حزمة الويب بافتراض أن كل شيء هو لغة JavaScript حديثة دون تفريع خاص للمخرجات أو البنية المتعددة.

بما أنّ مكوّن "أدوات تحسين الأداء" يعمل على حِزم بدلاً من وحدات فردية، فإنّه يعالج رمز تطبيقك وتبعياتك بالتساوي. وهذا يجعل من الآمن استخدام تبعيات JavaScript الحديثة من npm، لأن تعليماتها البرمجية سيتم تجميعها وترجمتها إلى بناء الجملة الصحيح. ويمكن أن تكون هذه الطريقة أسرع أيضًا من الحلول التقليدية التي تتضمّن خطوتَين للتجميع، مع إنشاء حِزم منفصلة للمتصفّحات الحديثة والقديمة. ومجموعتا الحِزم مُصممة لتحميلهما باستخدام نمط الوحدة/nounit.

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

يمكن أن يكون Optimize Plugin أسرع وأكثر كفاءة من عمليات إعداد حزمة الويب المخصّصة، والتي عادةً ما تجمع عادةً الرموز القديمة والحديثة بشكل منفصل. كما أنّها تتعامل مع تشغيل Babel نيابةً عنك، وتقلل الحُزم باستخدام Terser مع إعدادات منفصلة مثالية للمخرجات الحديثة والقديمة. وأخيرًا، يتم استخراج رموز polyfill التي تحتاجها الحزم القديمة التي تم إنشاؤها في نص برمجي مخصص حتى لا يتم تكرارها أو تحميلها بدون داعٍ في المتصفحات الأحدث.

المقارنة: ترجمة وحدات المصدر مرتين مقابل تحويل الحزم التي تم إنشاؤها.

BabelEsmPlugin

BabelEsmPlugin هو مكوّن إضافي لحزمة ويب يعمل مع @babel/preset-env لإنشاء إصدارات حديثة من الحِزم الحالية من أجل شحن رموز برمجية أقل ترجمة وشرح إلى المتصفّحات الحديثة. وهو الحل الأكثر استخدامًا للأجهزة الجاهزة الذي يتضمّن الوحدة/noModule، ويُستخدَم في كل من Next.js وPreact CLI.

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};

تتوافق BabelEsmPlugin مع مجموعة واسعة من إعدادات حِزم الويب، لأنها تشغِّل إصدارَين منفصلَين إلى حد كبير من تطبيقك. قد يستغرق تجميع البيانات مرّتين في التطبيقات الكبيرة بعض الوقت، إلا أنّ هذه التقنية تتيح دمج BabelEsmPlugin بسلاسة في إعدادات حزمة الويب الحالية وتجعلها أحد الخيارات المتاحة الأكثر ملاءمة.

إعداد أداة تحميل babel لتحويل وحدات <b}

إذا كنت تستخدم babel-loader بدون أحد المكوّنَين الإضافيَين السابقَين، عليك اتخاذ خطوة مهمة لاستخدام وحدات JavaScript npm حديثة. من خلال تحديد إعدادَين منفصلَين لبرامج babel-loader، يصبح من الممكن التجميع التلقائي لميزات اللغة الحديثة المتوفّرة من node_modules إلى ES2017، مع مواصلة تحويل رمز الطرف الأول الخاص بك باستخدام مكوّنات Babel الإضافية والإعدادات المسبقة المحدّدة في إعدادات مشروعك. ولا يؤدي هذا إلى إنشاء حزم حديثة وقديمة لإعداد وحدة/nounit، ولكنه يتيح إمكانية تثبيت واستخدام حزم npm التي تحتوي على لغة JavaScript حديثة بدون تعطُّل المتصفحات القديمة.

يستخدم webpack-plugin-modern-npm هذه التقنية لتجميع تبعيات npm التي تحتوي على حقل "exports" في package.json، نظرًا لأن هذه قد تحتوي على بنية حديثة:

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

يمكنك بدلاً من ذلك تطبيق هذه التقنية يدويًا في إعدادات حزمة الويب عن طريق البحث عن حقل "exports" في package.json من الوحدات عند حلّها. مع تجاهل التخزين المؤقت للإيجاز، قد يبدو التنفيذ المخصص كما يلي:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

عند استخدام هذا المنهج، ستحتاج إلى التأكد من أن بناء الجملة الحديث متوافق مع أداة التصغير. يحتوي كل من Terser وuglify-es على خيار تحديد {ecma: 2017} للاحتفاظ ببنية ES2017 وإنشائها في بعض الحالات أثناء الضغط والتنسيق.

البيانات المجمّعة

يوفّر تطبيق التجميع دعمًا مضمّنًا لإنشاء مجموعات متعددة من الحِزم كجزء من إصدار واحد، كما ينشئ تلقائيًا تعليمات برمجية حديثة. نتيجةً لذلك، يمكن ضبط ميزة "دمج البيانات" لإنشاء حِزم حديثة وقديمة تتضمّن المكوّنات الإضافية الرسمية التي من المحتمل أن تستخدمها حاليًا.

@rollup/extension-babel

إذا كنت تستخدم أداة "دمج البيانات"، تحوِّل الطريقة getBabelOutputPlugin() (التي يوفّرها المكوّن الإضافي الرسمي Babel) الرمز البرمجي في حِزم تم إنشاؤها بدلاً من وحدات مصدر فردية. يتضمّن الدمج ميزة إنشاء مجموعات متعددة من الحِزم كجزء من إصدار واحد، يتضمّن كل منها مكونات إضافية خاصة به. يمكنك استخدام هذا لإنتاج حِزم مختلفة للمكوّنات الحديثة والقديمة من خلال تمرير كل منها عبر تهيئة مختلفة للمكوّن الإضافي لإخراج Babel:

// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};

أدوات تصميم إضافية

يكون التجميع وحزمة الويب قابلَين للضبط بشكل كبير، ما يعني بشكل عام أنّ كل مشروع يجب أن يعدّل إعداداته لتفعيل بنية JavaScript الحديثة في التبعيات. هناك أيضًا أدوات تصميم ذات مستوى أعلى تفضّل الاصطلاحات والإعدادات التلقائية على الإعدادات، مثل Parcel وSnowpack وVite وWMR. تفترض معظم هذه الأدوات أن تبعيات npm قد تحتوي على بناء جملة حديث، وستحوّلها إلى مستويات بناء الجملة المناسبة عند الإنشاء للإنتاج.

بالإضافة إلى المكوّنات الإضافية المخصّصة لحزمة الويب ومجموعة البيانات، يمكن إضافة حِزم JavaScript حديثة تتضمّن عناصر احتياطية قديمة إلى أي مشروع باستخدام devolution. التطوير هو أداة مستقلة تعمل على تحويل المخرجات من نظام إنشاء لإنتاج صيغ قديمة من JavaScript، ما يسمح للتجميع والتحويل بافتراض هدف ناتج حديث.