دليل لإدارة تبعيات Webpack

نشرت: 2022-03-11

يعد مفهوم النمذجة جزءًا متأصلًا في معظم لغات البرمجة الحديثة. على الرغم من ذلك ، فقد افتقرت JavaScript إلى أي نهج رسمي للوحدة النمطية حتى وصول أحدث إصدار من ECMAScript ES6.

في Node.js ، أحد أكثر أطر عمل JavaScript شيوعًا اليوم ، تسمح حِزم الوحدات النمطية بتحميل وحدات NPM في متصفحات الويب ، وتشجع المكتبات الموجهة للمكونات (مثل React) وتسهل تعديل كود JavaScript.

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

حزمة الويب: برنامج تعليمي للمبتدئين

ومع ذلك ، يمكن أن يكون تكوين Webpack وتبعياته مرهقًا ولا يكون دائمًا عملية مباشرة ، خاصة للمبتدئين.

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

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

تكوين الأسماء المستعارة والمسارات النسبية

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

لنفترض أن لدينا هيكل المشروع التالي:

 - Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js

يمكننا الإشارة إلى التبعيات من خلال المسارات النسبية للملفات التي نحتاجها ، وإذا أردنا استيراد المكونات إلى حاويات في كود المصدر الخاص بنا ، فسيبدو الأمر كما يلي:


Home.js

 Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';


Modal.js

 import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

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

يمكننا التعامل بسهولة مع هذه المشكلة باستخدام خيار resolve.alias 's solution.alias. يمكننا إعلان ما يسمى بالأسماء المستعارة - اسم دليل أو وحدة مع موقعها ، ولا نعتمد على المسارات النسبية في الكود المصدري للمشروع.


webpack.config.js

 resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }

في ملف Modal.js ، يمكننا الآن استيراد منتقي البيانات بشكل أبسط:

 import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

تقسيم الكود

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

في تكوين Webpack ، يخبر خيار Entry Webpack حيث تكون نقطة البداية للحزمة النهائية. يمكن أن تحتوي نقطة الإدخال على ثلاثة أنواع مختلفة من البيانات: سلسلة أو صفيف أو كائن.

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

إذا أردنا إلحاق ملفات متعددة ، ولا تعتمد على بعضها البعض ، فيمكننا استخدام تنسيق Array. على سبيل المثال ، يمكننا إلحاق analytics.js بنهاية bundle.js :


webpack.config.js

 module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };

إدارة نقاط الدخول المتعددة

لنفترض أن لدينا تطبيقًا متعدد الصفحات به عدة ملفات HTML ، مثل index.html و admin.html . يمكننا إنشاء حزم متعددة باستخدام نقطة الدخول كنوع كائن. يُنشئ التكوين أدناه حزمتين من JavaScript:


webpack.config.js

 module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };


index.html

 <script src=”build/index.js”></script>


admin.html

 <script src=”build/admin.js”></script>

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


webpack.config.js

 var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };

الآن ، يجب ألا ننسى إضافة <script src="build/common.js"></script> قبل البرامج النصية المجمعة.

تمكين التحميل الكسول

يمكن لـ Webpack تقسيم الأصول الثابتة إلى أجزاء أصغر ، وهذا النهج أكثر مرونة من التسلسل القياسي. إذا كان لدينا تطبيق صفحة واحدة كبيرة (SPA) ، فإن التسلسل البسيط في حزمة واحدة ليس طريقة جيدة لأن تحميل حزمة ضخمة واحدة يمكن أن يكون بطيئًا ، وعادة لا يحتاج المستخدمون إلى كل التبعيات في كل عرض.

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

بالنسبة لـ SPA ، يجب أن نوفر فقط تلك الأصول الثابتة المطلوبة لتقديم العرض الحالي. يعد جهاز التوجيه من جانب العميل في بنية SPA مكانًا مثاليًا للتعامل مع تقسيم الكود. عندما يدخل المستخدم مسارًا ، يمكننا فقط تحميل التبعيات المطلوبة للعرض الناتج. بدلاً من ذلك ، يمكننا تحميل التبعيات أثناء قيام المستخدم بالتمرير لأسفل الصفحة.

لهذا الغرض ، يمكننا استخدام وظائف تتطلب. require.ensure أو System.import ، والتي يمكن لـ Webpack اكتشافها بشكل ثابت. يمكن لـ Webpack إنشاء حزمة منفصلة بناءً على نقطة الانقسام هذه واستدعائها عند الطلب.

في هذا المثال ، لدينا حاويتان من React ؛ طريقة عرض المسؤول وعرض لوحة القيادة.


admin.jsx

 import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }


dashboard.jsx

 import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }

إذا أدخل المستخدم إما /dashboard أو /admin عنوان URL للمسؤول ، فسيتم تحميل حزمة JavaScript المطلوبة المقابلة فقط. أدناه يمكننا أن نرى أمثلة مع وبدون جهاز التوجيه من جانب العميل.


index.jsx

 if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }


index.jsx

 ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );

استخراج الأنماط في حزم منفصلة

في Webpack ، تقوم برامج التحميل ، مثل style-loader css-loader ، بمعالجة أوراق الأنماط مسبقًا وتضمينها في حزمة JavaScript الناتجة ، ولكن في بعض الحالات ، يمكن أن تسبب فلاش محتوى غير منظم (FOUC).

يمكننا تجنب FOUC باستخدام ExtractTextWebpackPlugin الذي يسمح بإنشاء جميع الأنماط في حزم CSS منفصلة بدلاً من تضمينها في حزمة JavaScript النهائية.


webpack.config.js

 var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }

التعامل مع مكتبات الطرف الثالث والمكونات الإضافية

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

فيما يلي بعض الأمثلة على مكونات jQuery الإضافية ، مع شرح لكيفية تكوين Webpack بشكل صحيح لتتمكن من إنشاء الحزمة النهائية.

توفير

تعتمد معظم المكونات الإضافية التابعة لجهات خارجية على وجود تبعيات عالمية محددة. في حالة jQuery ، تعتمد المكونات الإضافية على المتغير $ أو jQuery الذي يتم تحديده ، ويمكننا استخدام ملحقات jQuery عن طريق استدعاء $('div.content').pluginFunc() في الكود الخاص بنا.

يمكننا استخدام ProvidePlugin plugin ProviderPlugin للإضافة إلى var $ = require("jquery") في كل مرة يصادف فيها معرِّف $ العام.


webpack.config.js

 webpack.ProvidePlugin({ '$': 'jquery', })

عندما يعالج Webpack الكود ، فإنه يبحث عن الوجود $ ، ويوفر مرجعًا إلى التبعيات العالمية دون استيراد الوحدة النمطية المحددة بواسطة الوظيفة require .

محمل الواردات

تفترض بعض ملحقات jQuery وجود $ في مساحة الاسم العامة أو تعتمد على this الكائن في window . لهذا الغرض ، يمكننا استخدام imports-loader الذي يضخ المتغيرات العالمية في الوحدات النمطية.


example.js

 $('div.content').pluginFunc();

بعد ذلك ، يمكننا حقن المتغير $ في الوحدة عن طريق تكوين imports-loader :

 require("imports?$=jquery!./example.js");

يؤدي هذا ببساطة var $ = require("jquery"); إلى example.js .

في حالة الاستخدام الثانية:


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }

باستخدام الرمز => (يجب عدم الخلط بينه وبين وظائف السهم ES6) ، يمكننا تعيين متغيرات عشوائية. القيمة this تعيد تعريف المتغير الشامل للإشارة إلى كائن window . إنه نفس التفاف محتوى الملف بالكامل مع (function () { ... }).call(window); واستدعاء this الوظيفة window كوسيطة.

يمكننا أيضًا طلب مكتبات باستخدام تنسيق الوحدة النمطية CommonJS أو AMD:

 // CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });

يمكن لبعض المكتبات والوحدات النمطية دعم تنسيقات مختلفة للوحدات النمطية.

في المثال التالي ، لدينا ملحق jQuery يستخدم تنسيق الوحدة النمطية AMD و CommonJS وله تبعية jQuery:


jquery-plugin.js

 (function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }

يمكننا اختيار تنسيق الوحدة الذي نريد استخدامه للمكتبة المحددة. إذا أعلنا أن define يساوي false ، فإن Webpack لا تحلل الوحدة في تنسيق وحدة AMD ، وإذا أعلنا أن exports المتغيرة تساوي false ، فإن Webpack لا تحلل الوحدة في تنسيق الوحدة النمطية CommonJS.

فضح محمل

إذا احتجنا إلى عرض وحدة على السياق العالمي ، فيمكننا استخدام expose-loader . يمكن أن يكون هذا مفيدًا ، على سبيل المثال ، إذا كان لدينا برامج نصية خارجية ليست جزءًا من تكوين Webpack وتعتمد على الرمز في مساحة الاسم العامة ، أو نستخدم المكونات الإضافية للمتصفح التي تحتاج إلى الوصول إلى رمز في وحدة تحكم المتصفح.


webpack.config.js

 module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }

مكتبة jQuery متاحة الآن في مساحة الاسم العامة للنصوص الأخرى على صفحة الويب.

 window.$ window.jQuery

تكوين التبعيات الخارجية

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

يمكننا تكوين البرامج النصية الخارجية باستخدام خيار العناصر externals في تكوين Webpack. على سبيل المثال ، يمكننا استخدام مكتبة من CDN عبر علامة <script> منفصلة ، مع استمرار الإعلان عنها صراحةً على أنها تبعية وحدة نمطية في مشروعنا.


webpack.config.js

 externals: { react: 'React', 'react-dom': 'ReactDOM' }

دعم مثيلات متعددة للمكتبة

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

يمكن أن يحدث هذا ، على سبيل المثال ، مع مكتبة React ، حيث يمكننا تثبيت React من NPM ولاحقًا يمكن أن يتوفر إصدار مختلف من React مع بعض الحزم الإضافية أو المكوِّن الإضافي. يمكن أن يبدو هيكل مشروعنا كما يلي:

 project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react

المكونات القادمة من react-plugin رد فعل لها مثيل React مختلف عن باقي المكونات في المشروع. الآن لدينا نسختان منفصلتان من React ، ويمكن أن تكونا نسختين مختلفتين. في تطبيقنا ، يمكن أن يفسد هذا السيناريو DOM العالمي القابل للتغيير ، ويمكننا رؤية رسائل الخطأ في سجل وحدة تحكم الويب. حل هذه المشكلة هو الحصول على نفس الإصدار من React طوال المشروع بأكمله. يمكننا حلها عن طريق أسماء Webpack المستعارة.


webpack.config.js

 module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }

عندما يحاول react-plugin التفاعلي طلب node_modules ، فإنه يستخدم الإصدار الموجود في وحدات عقدة المشروع. إذا أردنا معرفة إصدار React الذي نستخدمه ، فيمكننا إضافة console.log(React.version) في الكود المصدري.

ركز على التطوير وليس تكوين Webpack

هذا المنشور يخدش فقط سطح قوة وفائدة Webpack.

هناك العديد من أدوات تحميل Webpack والمكونات الإضافية الأخرى التي ستساعدك على تحسين وتبسيط تجميع JavaScript.

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

الموضوعات ذات الصلة: الحفاظ على التحكم: دليل Webpack والتفاعل ، Pt. 1