Bu yazıda, CommonJS’nin ne olduğunu ve neden JavaScript paketlerinizi gereğinden fazla büyüttüğünü inceleyeceğiz.
Özet: Paketleyicinin uygulamanızı başarılı bir şekilde optimize edebilmesini sağlamak için, CommonJS modüllerine bağımlı olmaktan kaçının ve uygulamanızın tamamında ECMAScript modülü sözdizimini kullanın.
CommonJS nedir? #
CommonJS, 2009’dan itibaren JavaScript modülleri için kurallar oluşturan bir standarttır. Başlangıçta, öncelikle sunucu tarafı uygulamalar için web tarayıcısının dışında kullanılmak üzere tasarlanmıştı.
CommonJS ile modülleri tanımlayabilir, bunlardan işlevleri dışa aktarabilir ve diğer modüllere içe aktarabilirsiniz. Örneğin, aşağıdaki kod parçası, beş işlevi dışa aktaran bir modülü tanımlar: add
, subtract
, multiply
, divide
Ve max
:
// utils.js
const { maxBy } = require('lodash-es');
const fns = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => a / b,
max: arr => maxBy(arr)
};Object.keys(fns).forEach(fnName => module.exports[fnName] = fns[fnName]);
Daha sonra, başka bir modül bu işlevlerin bazılarını veya tümünü içe aktarabilir ve kullanabilir:
// index.js
const { add } = require('./utils.js');
console.log(add(1, 2));
Çağırma index.js
ile node
numarayı çıkaracak 3
konsolda.
2010’ların başında tarayıcıda standartlaştırılmış bir modül sisteminin olmaması nedeniyle CommonJS, JavaScript istemci tarafı kitaplıkları için de popüler bir modül biçimi haline geldi.
CommonJS nihai paket boyutunuzu nasıl etkiler? #
Sunucu tarafı JavaScript uygulamanızın boyutu, tarayıcıdaki kadar kritik değildir, bu nedenle CommonJS, üretim paketi boyutunun küçültülmesi düşünülerek tasarlanmamıştır. Aynı zamanda, analiz JavaScript paket boyutunun, tarayıcı uygulamalarını yavaşlatmanın bir numaralı nedeni olmaya devam ettiğini gösteriyor.
JavaScript paketleyicileri ve küçültücüleri, örneğin webpack
Ve terser
, uygulamanızın boyutunu küçültmek için farklı optimizasyonlar gerçekleştirin. Uygulamanızı derleme zamanında analiz ederek, kullanmadığınız kaynak kodundan mümkün olduğu kadar fazlasını çıkarmaya çalışırlar.
Örneğin, yukarıdaki snippet’te son paketiniz yalnızca şunları içermelidir: add
işlev, çünkü bu, utils.js
içe aktardığınız index.js
.
Aşağıdakileri kullanarak uygulamayı oluşturalım webpack
yapılandırma:
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
filename: 'out.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'production',
};
Burada üretim modu optimizasyonlarını kullanmak istediğimizi belirtiyoruz ve index.js
bir giriş noktası olarak. Çağırdıktan sonra webpack
eğer keşfedersek çıktı boyut, bunun gibi bir şey göreceğiz:
$ cd dist && ls -lah
625K Apr 13 13:04 out.js
Dikkat edin paket 625KB. Çıktıya bakarsak, tüm işlevleri utils.js
artı birçok modül lodash
. kullanmamamıza rağmen lodash
içinde index.js
çıktının bir parçasıbu da üretim varlıklarımıza çok fazla ekstra ağırlık katıyor.
Şimdi modül formatını şu şekilde değiştirelim: ECMAScript modülleri ve yeniden dene. Bu zaman, utils.js
şöyle görünürdü:
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => a / b;import { maxBy } from 'lodash-es';
export const max = arr => maxBy(arr);
Ve index.js
dan ithal ederdi utils.js
ECMAScript modülü sözdizimini kullanarak:
import { add } from './utils.js';console.log(add(1, 2));
aynısını kullanarak webpack
yapılandırma, uygulamamızı oluşturabilir ve çıktı dosyasını açabiliriz. şimdi 40 bayt Takip ederek çıktı:
(()=>{"use strict";console.log(1+2)})();
Son paketin şu işlevlerden hiçbirini içermediğine dikkat edin: utils.js
kullanmadığımız ve hiçbir iz olmadığı lodash
! Bundan da öte, terser
(JavaScript küçültücü webpack
kullanır) add
işlevi console.log
.
Sorabileceğiniz adil bir soru, CommonJS kullanmak neden çıktı paketinin neredeyse 16.000 kat daha büyük olmasına neden oluyor?? Tabii ki, bu bir oyuncak örneği, gerçekte boyut farkı o kadar büyük olmayabilir, ancak CommonJS’nin üretim yapınıza önemli bir ağırlık katma olasılığı yüksektir.
CommonJS modüllerini, ES modüllerinden çok daha dinamik oldukları için genel durumda optimize etmek daha zordur. Paketleyicinizin ve küçültücünüzün uygulamanızı başarılı bir şekilde optimize edebilmesini sağlamak için, CommonJS modüllerine bağımlı olmaktan kaçının ve uygulamanızın tamamında ECMAScript modülü sözdizimini kullanın.
ECMAScript modüllerini kullanıyor olsanız bile index.js
kullandığınız modül bir CommonJS modülüyse, uygulamanızın paket boyutu zarar görür.
CommonJS neden uygulamanızı büyütür? #
Bu soruyu cevaplamak için, davranışlarına bakacağız. ModuleConcatenationPlugin
içinde webpack
ve ondan sonra, statik analiz edilebilirliği tartışın. Bu eklenti, tüm modüllerinizin kapsamını tek bir kapakta birleştirir ve kodunuzun tarayıcıda daha hızlı yürütme süresine sahip olmasını sağlar. Bir örneğe bakalım:
// utils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// index.js
import { add } from './utils.js';
const subtract = (a, b) => a - b;console.log(add(1, 2));
Yukarıda içe aktardığımız bir ECMAScript modülümüz var. index.js
. Ayrıca bir tanımlıyoruz subtract
işlev. Projeyi aynı kullanarak inşa edebiliriz webpack
yukarıdaki gibi yapılandırma, ancak bu sefer küçültmeyi devre dışı bırakacağız:
const path = require('path');module.exports = {
entry: 'index.js',
output: {
filename: 'out.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
minimize: false
},
mode: 'production',
};
Üretilen çıktıya bakalım:
/******/ (() => { // webpackBootstrap
/******/ "use strict";// CONCATENATED MODULE: ./utils.js**
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// CONCATENATED MODULE: ./index.js**
const index_subtract = (a, b) => a - b;**
console.log(add(1, 2));**
/******/ })();
Yukarıdaki çıktıda, tüm işlevler aynı ad alanının içindedir. Çarpışmaları önlemek için web paketi, subtract
işlevi index.js
ile index_subtract
.
Bir küçültücü yukarıdaki kaynak kodunu işlerse:
- Kullanılmayan işlevleri kaldırın
subtract
Veindex_subtract
- Tüm yorumları ve gereksiz boşlukları kaldırın
- gövdesini satır içi
add
işleviconsole.log
Arama
Genellikle geliştiriciler buna atıfta bulunur kullanılmayan ithalatların ağaç sallama olarak kaldırılması. Ağaç sallama, yalnızca webpack’in hangi sembolleri içe aktardığımızı statik olarak (yapım sırasında) anlayabilmesi nedeniyle mümkün oldu. utils.js
ve hangi sembolleri dışa aktardığı.
Bu davranış için varsayılan olarak etkindir. ES modülleri Çünkü onlar daha statik olarak analiz edilebilirCommonJS ile karşılaştırıldığında.
Tam olarak aynı örneğe bakalım, ancak bu sefer değişiklik utils.js
ES modülleri yerine CommonJS kullanmak için:
// utils.js
const { maxBy } = require('lodash-es');const fns = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => a / b,
max: arr => maxBy(arr)
};
Object.keys(fns).forEach(fnName => module.exports[fnName] = fns[fnName]);
Bu küçük güncelleme çıktıyı önemli ölçüde değiştirecek. Bu sayfaya yerleştirmek çok uzun olduğu için sadece küçük bir bölümünü paylaştım:
...
(() => {"use strict";
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(288);
const subtract = (a, b) => a - b;
console.log((0,_utils__WEBPACK_IMPORTED_MODULE_0__/* .add */ .IH)(1, 2));
})();
Son paketin bazılarını içerdiğine dikkat edin. webpack
“çalışma zamanı”: paketlenmiş modüllerden içe/dışa aktarma işlevinden sorumlu olan enjekte edilmiş kod. Bu sefer, tüm sembolleri yerleştirmek yerine utils.js
Ve index.js
aynı ad alanı altında, çalışma zamanında dinamik olarak add
kullanarak fonksiyon __webpack_require__
.
Bu gereklidir çünkü CommonJS ile dışa aktarma adını isteğe bağlı bir ifadeden alabiliriz. Örneğin, aşağıdaki kod kesinlikle geçerli bir yapıdır:
module.exports[localStorage.getItem(Math.random())] = () => { … };
Paket oluşturucunun, dışa aktarılan sembolün adının ne olduğunu oluşturma zamanında bilmesinin bir yolu yoktur, çünkü bu, yalnızca çalışma zamanında, kullanıcının tarayıcısı bağlamında mevcut olan bilgileri gerektirir.
Bu şekilde, küçültücü tam olarak ne olduğunu anlayamaz. index.js
bağımlılıklarından kullanır, böylece onu ağaç sallayamaz. Aynı davranışı üçüncü taraf modüller için de gözlemleyeceğiz. Bir CommonJS modülünü şu adresten içe aktarırsak: node_modules
derleme araç zinciriniz onu düzgün bir şekilde optimize edemez.
CommonJS ile ağaç sallama #
Tanım gereği dinamik oldukları için CommonJS modüllerini analiz etmek çok daha zordur. Örneğin, ES modüllerindeki içe aktarma konumu, bir ifade olduğu CommonJS ile karşılaştırıldığında her zaman bir dize sabit değeridir.
Bazı durumlarda, kullandığınız kitaplık CommonJS’yi nasıl kullandığına ilişkin belirli kuralları izliyorsa, kullanılmayan dışa aktarımları derleme sırasında bir üçüncü taraf kullanarak kaldırmak mümkündür. webpack
Eklenti. Bu eklenti ağaç sallama için destek eklese de, bağımlılıklarınızın CommonJS’yi kullanabileceği tüm farklı yolları kapsamaz. Bu, ES modülleriyle aynı garantileri almadığınız anlamına gelir. Ek olarak, oluşturma işleminizin bir parçası olarak varsayılana ek bir maliyet ekler. webpack
davranış.
Çözüm #
Paketleyicinin uygulamanızı başarılı bir şekilde optimize edebilmesini sağlamak için, CommonJS modüllerine bağımlı olmaktan kaçının ve uygulamanızın tamamında ECMAScript modülü sözdizimini kullanın.
Optimum yolda olduğunuzu doğrulamak için eyleme geçirilebilir birkaç ipucu:
- Rollup.js’yi kullanın düğüm çözme eklenti ve ayarlayın
modulesOnly
Yalnızca ECMAScript modüllerine bağımlı olmak istediğinizi belirtmek için bayrak. - paketi kullan
is-esm
bir npm paketinin ECMAScript modülleri kullandığını doğrulamak için. - Angular kullanıyorsanız, ağaç sallanamayan modüllere bağlıysanız varsayılan olarak bir uyarı alırsınız.