"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > لقد كتبت وحدة مجمعة. ملاحظات، الخ

لقد كتبت وحدة مجمعة. ملاحظات، الخ

تم النشر بتاريخ 2024-08-05
تصفح:799

I wrote a module bundler. notes, etc

لقد قمت ببناء أداة تجميع جافا سكريبت بسيطة وتبين أنها أسهل بكثير مما كنت أتوقع. سأشارك كل ما تعلمته في هذا المنشور.

عند كتابة تطبيقات كبيرة، من الممارسات الجيدة تقسيم كود مصدر JavaScript إلى ملفات js منفصلة، ​​ومع ذلك فإن إضافة هذه الملفات إلى مستند html الخاص بك باستخدام علامات البرنامج النصي المتعددة يؤدي إلى مشاكل جديدة مثل

  • تلوث مساحة الاسم العالمية.

  • شروط السباق.

تجمع حزم الوحدات النمطية كود المصدر الخاص بنا من ملفات مختلفة في ملف واحد كبير، مما يساعدنا على الاستمتاع بفوائد التجريدات مع تجنب الجوانب السلبية.

تقوم حزم الوحدات النمطية عمومًا بذلك في خطوتين.

  1. العثور على جميع ملفات مصدر JavaScript، بدءًا من ملف الإدخال. يُعرف هذا بتحليل التبعية وتسمى الخريطة التي تم إنشاؤها برسم التبعية.
  2. استخدام الرسم البياني للتبعية لإنشاء حزمة: سلسلة كبيرة من كود مصدر JavaScript يمكن تشغيلها في المتصفح. يمكن كتابة هذا في ملف وإضافته إلى مستند html باستخدام علامة البرنامج النصي.

حل التبعية

كما ذكرنا سابقًا، هنا

  • خذ ملف الإدخال،
  • قراءة وتحليل محتواه،
  • إضافته إلى مجموعة من الوحدات
  • العثور على جميع تبعياته (الملفات الأخرى التي يستوردها)،
  • قراءة وتحليل محتويات التبعيات
  • إضافة تبعيات إلى المصفوفة
  • ابحث عن تبعيات التبعيات وما إلى ذلك حتى نصل إلى الوحدة الأخيرة

إليك كيفية القيام بذلك (رمز JavaScript مقدمًا)

قم بإنشاء ملف Bundler.js في محرر النصوص لديك وأضف الكود التالي:

const bundler = (entry)=>{
          const graph = createDependencyGraph(entry)

          const bundle = createBundle(graph)
          return bundle
}

وظيفة المجمع هي المدخل الرئيسي لمجمعنا. يأخذ المسار إلى ملف (ملف إدخال) ويعيد سلسلة (الحزمة). ومن داخله، يقوم بإنشاء رسم بياني للتبعية باستخدام وظيفة createDependencyGraph.

const createDependencyGraph = (path)=>{
          const entryModule = createModule(path)

          /* other code */
}

تأخذ وظيفة createDependencyGraph المسار إلى ملف الإدخال. ويستخدم وظيفة createModule لإنشاء تمثيل وحدة لهذا الملف.

let ID = 0
const createModule = (filename)=>{
          const content = fs.readFileSync(filename)
          const ast = babylon.parse(content, {sourceType: “module”})

          const {code} = babel.transformFromAst(ast, null, {
              presets: ['env']
            })

           const dependencies = [ ]
           const id = ID  
           traverse(ast, {
                   ImportDeclaration: ({node})=>{
                       dependencies.push(node.source.value)
                   }
            }
            return {
                           id,
                           filename,
                           code,
                           dependencies
                       }
}

تأخذ وظيفة createAsset المسار إلى الملف وتقرأ محتواه في سلسلة. ثم يتم تحليل هذه السلسلة إلى شجرة بناء جملة مجردة. شجرة بناء الجملة المجردة هي تمثيل شجرة لمحتوى التعليمات البرمجية المصدر. يمكن تشبيهها بشجرة DOM لمستند html. وهذا يجعل من السهل تشغيل بعض الوظائف على الكود مثل البحث وما إلى ذلك.
نقوم بإنشاء ast من الوحدة باستخدام المحلل اللغوي babylon.

بعد ذلك بمساعدة برنامج babel الأساسي، نقوم بتحويل محتوى الكود إلى صيغة ما قبل es2015 للتوافق عبر المتصفحات.
بعد ذلك يتم اجتياز ast باستخدام وظيفة خاصة من babel للعثور على كل إعلان استيراد لملفنا المصدر (التبعيات).

نقوم بعد ذلك بدفع هذه التبعيات (وهي عبارة عن سلاسل نصية لمسارات الملفات النسبية) إلى مصفوفة تبعية.

كما نقوم بإنشاء معرف لتعريف هذه الوحدة بشكل فريد و
أخيرًا نعيد كائنًا يمثل هذه الوحدة. تحتوي هذه الوحدة على معرف ومحتويات ملفنا بتنسيق سلسلة ومجموعة من التبعيات ومسار الملف المطلق.

const createDependencyGraph = (path)=>{
          const entryModule = createModule(path)

          const graph = [ entryModule ]
          for ( const module of graph) {
                  module.mapping = { }
module.dependencies.forEach((dep)=>{
         let absolutePath = path.join(dirname, dep);
         let child = graph.find(mod=> mod.filename == dep)
         if(!child){
               child = createModule(dep)
               graph.push(child)
         }
         module.mapping[dep] = child.id
})
          }
          return graph
}

بالعودة إلى وظيفة createDependencyGraph، يمكننا الآن البدء في عملية إنشاء الرسم البياني الخاص بنا. الرسم البياني الخاص بنا عبارة عن مجموعة من الكائنات حيث يمثل كل كائن كل ملف مصدر مستخدم في تطبيقنا.
نقوم بتهيئة الرسم البياني الخاص بنا باستخدام وحدة الإدخال ثم نقوم بتكراره. على الرغم من أنه يحتوي على عنصر واحد فقط، إلا أننا نضيف عناصر إلى نهاية المصفوفة عن طريق الوصول إلى مصفوفة التبعيات الخاصة بوحدة الإدخال (والوحدات الأخرى التي سنضيفها).

تحتوي مصفوفة التبعيات على مسارات ملفات نسبية لجميع تبعيات الوحدة النمطية. يتم تكرار المصفوفة ولكل مسار ملف نسبي، يتم حل المسار المطلق أولاً واستخدامه لإنشاء وحدة نمطية جديدة. يتم دفع هذه الوحدة الفرعية إلى نهاية الرسم البياني وتبدأ العملية من جديد حتى يتم تحويل جميع التبعيات إلى وحدات.
كما توفر كل وحدة كائن تعيين يقوم ببساطة بتعيين كل مسار نسبي للتبعية إلى معرف الوحدة الفرعية.
يتم إجراء التحقق مما إذا كانت الوحدة موجودة بالفعل على كل تبعية لمنع تكرار الوحدات والتبعيات الدائرية اللانهائية.
وأخيرًا نعيد الرسم البياني الخاص بنا والذي يحتوي الآن على جميع وحدات تطبيقنا.

التجميع

مع الانتهاء من الرسم البياني للتبعية، سيتضمن إنشاء الحزمة خطوتين

  1. تغليف كل وحدة في وظيفة. يؤدي هذا إلى إنشاء فكرة أن يكون لكل وحدة نطاقها الخاص
  2. تغليف الوحدة في وقت التشغيل.

التفاف كل وحدة

يتعين علينا تحويل كائنات الوحدة النمطية لدينا إلى سلاسل حتى نتمكن من كتابتها في ملف Bundle.js. نقوم بذلك عن طريق تهيئة ModuleString كسلسلة فارغة. بعد ذلك، نمر عبر الرسم البياني الخاص بنا ونلحق كل وحدة في سلسلة الوحدة كأزواج قيمة رئيسية، مع كون معرف الوحدة هو المفتاح ومصفوفة تحتوي على عنصرين: أولاً، محتوى الوحدة ملفوف في الوظيفة (لإعطائها نطاقًا كما ذكرنا سابقًا ) والثاني كائن يحتوي على تعيين تبعياته.

const wrapModules = (graph)=>{
         let modules = ‘’
           graph.forEach(mod => {
    modules  = `${http://mod.id}: [
      function (require, module, exports) {
        ${mod.code}
      },
      ${JSON.stringify(mod.mapping)},
    ],`;
  });
return modules
}

تجدر الإشارة أيضًا إلى أن وظيفة تغليف كل وحدة تأخذ كائنات الطلب والتصدير والوحدة النمطية كوسيطات. وذلك لأن هذه الوحدات غير موجودة في المتصفح ولكن بما أنها تظهر في الكود الخاص بنا، فسوف نقوم بإنشائها وتمريرها إلى هذه الوحدات.

إنشاء وقت التشغيل

هذا هو الكود الذي سيتم تشغيله فور تحميل الحزمة، وسيزود وحداتنا بالكائنات المطلوبة والوحدة النمطية وmodule.exports.

const bundle = (graph)=>{
        let modules = wrapModules(graph)
        const result = `
    (function(modules) {
      function require(id) {
        const [fn, mapping] = modules[id];

        function localRequire(name) {
          return require(mapping[name]);
        }

        const module = { exports : {} };

        fn(localRequire, module, module.exports);

        return module.exports;
      }

      require(0);
    })({${modules}})`;
  return result;
}

نستخدم تعبير دالة يتم استدعاؤه على الفور والذي يأخذ كائن الوحدة الخاص بنا كوسيطة. داخله نحدد وظيفة الطلب الخاصة بنا التي تحصل على وحدة نمطية من كائن الوحدة النمطية الخاص بنا باستخدام معرفها.
يقوم بإنشاء وظيفة localRequire خاصة بوحدة معينة لتعيين سلسلة مسار الملف إلى المعرف. وكائن وحدة به خاصية تصدير فارغة
فهو يقوم بتشغيل كود الوحدة الخاصة بنا، ويمرر الكائن localrequire وmodule وexports كوسائط ثم يقوم بإرجاع Module.exports تمامًا مثلما تفعل وحدة Node js.
أخيرًا، نستدعي الأمر require على وحدة الإدخال الخاصة بنا (الفهرس 0).

لاختبار المجمع الخاص بنا، في دليل العمل لملف Bundler.js الخاص بنا، قم بإنشاء ملف Index.js ودليلين: src والدليل العام.

في الدليل العام، قم بإنشاء ملف Index.html، وأضف الكود التالي في علامة النص:


    
        Module bundler

اسم ثابت = "ديفيد"
تصدير الاسم الافتراضي

also create a hello.js file and add the following code

استيراد الاسم من "./name.js"
const hello = document.getElementById("root")
hello.innerHTML = اسم "مرحبًا"

Lastly in the index.js file of the root directory import our bundler, bundle the files and write it to a bundle.js file in the public directory

const createBundle = require(“./bundler.js”)
تشغيل ثابت = (الإخراج، الإدخال)=>{
دع الحزمة = createBundle(entry)
fs.writeFileSync(حزمة، 'utf-8')

تشغيل(“./public/bundle.js”، “./src/hello.js”)


Open our index.html file in the browser to see the magic.

In this post we have illustrated how a simple module bundler works. This is a minimal bundler meant for understanding how these technologies work behind the hood.

please like if you found this insightful and comment any questions you may have.
بيان الافراج تم إعادة إنتاج هذه المقالة على: https://dev.to/frontendokeke/i-wrote-a-module-bundler-notes-etc-4ofa?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3