Kullanıcı arabirimi boyutunu küçült

Uygulamanızı mümkün olduğunca küçük hale getirmek için Webpack'i kullanma

Bir uygulamayı optimize ederken yapılacak ilk şeylerden biri, uygulamayı mümkün olduğunca küçültmektir. Bunu webpack ile nasıl yapacağınız burada açıklanmaktadır.

Üretim modunu kullanma (yalnızca web paketi 4)

Webpack 4, yeni mode bayrağını kullanıma sundu. Web paketini, uygulamayı belirli bir ortam için derlediğinize dair ipucu vermek amacıyla bu işareti 'development' veya 'production' olarak ayarlayabilirsiniz:

// webpack.config.js
module.exports = {
  mode: 'production',
};

Uygulamanızı üretim için derlerken production modunu etkinleştirdiğinizden emin olun. Bu; küçültme, kitaplıklardaki yalnızca geliştirme amaçlı kodların kaldırılması gibi vb. optimizasyonlar web paketinin uygulamasını sağlar.

Daha fazla bilgi

Küçültmeyi etkinleştir

Fazladan boşlukları kaldırarak, değişken adlarını kısaltarak ve benzer şekillerde kodu sıkıştırarak küçültme yapabilirsiniz. Aşağıdaki gibi:

// Original code
function map(array, iteratee) {
  let index = -1;
  const length = array == null ? 0 : array.length;
  const result = new Array(length);

  while (++index < length) {
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}

Webpack, kodu küçültmek için iki yöntemi destekler: paket düzeyinde küçültme ve yükleyiciye özel seçenekler. Bunlar aynı anda kullanılmalıdır.

Paket düzeyinde küçültme

Paket düzeyinde küçültme, derlemeden sonra tüm paketi sıkıştırır. İşleyiş şekli:

  1. Şuna benzer bir kod yazarsınız:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. Webpack, bunu yaklaşık olarak aşağıdaki gibi derler:

    // bundle.js (part of)
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony export (immutable) */ __webpack_exports__["render"] = render;
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1);
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default =
    __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);
    
    function render(data, target) {
    console.log('Rendered!');
    }
    
  3. Bir küçültücü, onu yaklaşık olarak şu şekilde sıkıştırır:

    // minified bundle.js (part of)
    "use strict";function t(e,n){console.log("Rendered!")}
    Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
    

Web paketi 4'te paket düzeyinde küçültme işlemi,hem üretim modunda hem de bağımsız olarak otomatik olarak etkinleştirilir. Gelişmiş olarak UglifyJS küçültücüyü kullanır. (Kısaltmayı devre dışı bırakmanız gerekirse geliştirme modunu kullanmanız veya false öğesini optimization.minimize seçeneğine iletmeniz yeterlidir.)

webpack 3'te doğrudan UglifyJS eklentisini kullanmanız gerekir. Eklenti, web paketi ile birlikte gelir. Eklentiyi etkinleştirmek için yapılandırmanın plugins bölümüne ekleyin:

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
  ],
};

Yükleyiciye özel seçenekler

Kodu küçültmenin ikinci yolu, yükleyiciye özel seçeneklerdir (yükleyici nedir?). Yükleyici seçenekleri sayesinde küçültücünün küçültemeyeceği öğeleri sıkıştırabilirsiniz. Örneğin, css-loader ile bir CSS dosyasını içe aktardığınızda dosya bir dizede derlenir:

/* comments.css */
.comment {
  color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n  color: black;\r\n}",""]);

Bu kod bir dize olduğu için küçültücü tarafından sıkıştırılamaz. Dosya içeriğini küçültmek için yükleyiciyi bunu yapacak şekilde yapılandırmamız gerekir:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } },
        ],
      },
    ],
  },
};

Daha fazla bilgi

NODE_ENV=production değerini belirtin

Kullanıcı arabirimi boyutunu azaltmanın bir başka yolu da, kodunuzdaki NODE_ENV ortam değişkenini production değerine ayarlamaktır.

Kitaplıklar, geliştirmede mi yoksa üretim modunda mı çalışmaları gerektiğini belirlemek için NODE_ENV değişkenini okur. Bazı kitaplıklar bu değişkene göre farklı davranır. Örneğin, NODE_ENV production olarak ayarlanmadığında Vue.js ek kontroller ve baskı uyarıları yapar:

// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
  warn('props must be strings when using array syntax.');
}
// …

Tepki vermek benzer şekilde çalışır. Uyarıları içeren bir geliştirme derlemesi yükler:

// react/index.js
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

// react/cjs/react.development.js
// …
warning$3(
    componentClass.getDefaultProps.isReactClassApproved,
    'getDefaultProps is only used on classic React.createClass ' +
    'definitions. Use a static property named `defaultProps` instead.'
);
// …

Bu tür kontroller ve uyarılar üretimde genellikle gereksizdir ancak kodda kalır ve kitaplık boyutunu artırır. webpack 4'te optimization.nodeEnv: 'production' seçeneğini ekleyerek bunları kaldırın:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    nodeEnv: 'production',
    minimize: true,
  },
};

webpack 3'te bunun yerine DefinePlugin kullanın:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    new webpack.optimize.UglifyJsPlugin()
  ]
};

Hem optimization.nodeEnv seçeneği hem de DefinePlugin aynı şekilde çalışır. Tüm process.env.NODE_ENV tekrarlarını belirtilen değerle değiştirirler. Yukarıdaki yapılandırma ile:

  1. Web paketi, tüm process.env.NODE_ENV oluşumlarını "production" ile değiştirecek:

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if (process.env.NODE_ENV !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    
  2. Ardından küçültücü, bu tür tüm if dallarını kaldırır. Çünkü "production" !== 'production' her zaman yanlıştır ve eklenti, bu dalların içindeki kodun hiçbir zaman yürütülmeyeceğini anlar:

    // vue/dist/vue.runtime.esm.js
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    } else if ("production" !== 'production') {
      warn('props must be strings when using array syntax.');
    }
    

    // vue/dist/vue.runtime.esm.js (without minification)
    if (typeof val === 'string') {
      name = camelize(val);
      res[name] = { type: null };
    }
    

Daha fazla bilgi

ES modüllerini kullanma

Kullanıcı arabirimi boyutunu azaltmanın bir sonraki yolu da ES modüllerini kullanmaktır.

ES modüllerini kullandığınızda webpack, ağaç sallama işlemini yapabilir. Ağaç sallama, bir paketleyicinin tüm bağımlılık ağacını geçip hangi bağımlılıkların kullanıldığını kontrol etmesi ve kullanılmayanları kaldırmasıdır. Böylece, ES modülü söz dizimini kullanırsanız webpack kullanılmayan kodu ortadan kaldırabilir:

  1. Birden çok dışa aktarma işlemi olan bir dosya yazarsınız ancak uygulama bunlardan yalnızca birini kullanır:

    // comments.js
    export const render = () => { return 'Rendered!'; };
    export const commentRestEndpoint = '/rest/comments';
    
    // index.js
    import { render } from './comments.js';
    render();
    
  2. Webpack, commentRestEndpoint yönergesinin kullanılmadığını anlar ve pakette ayrı bir dışa aktarma noktası oluşturmaz:

    // bundle.js (part that corresponds to comments.js)
    (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    const render = () => { return 'Rendered!'; };
    /* harmony export (immutable) */ __webpack_exports__["a"] = render;
    
    const commentRestEndpoint = '/rest/comments';
    /* unused harmony export commentRestEndpoint */
    })
    
  3. Küçültücü, kullanılmayan değişkeni kaldırır:

    // bundle.js (part that corresponds to comments.js)
    (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
    

Bu işlev, ES modülleriyle yazılmış kitaplıklarda da çalışır.

Yine de tam olarak webpack'in yerleşik küçültücüyü (UglifyJsPlugin) kullanmanız gerekmez. Ölü kod kaldırma özelliğini destekleyen her türlü küçültücü (ör. Babel Minify eklentisi veya Google Closure Compiler eklentisi) sorunu çözecektir.

Daha fazla bilgi

Resimleri optimize edin

Resimler, sayfa boyutunun yarıdan fazlasını oluşturur. JavaScript kadar kritik olmasa da (ör. oluşturmayı engellemezler), yine de bant genişliğinin büyük bir kısmını tüketirler. Bunları web paketinde optimize etmek için url-loader, svg-url-loader ve image-webpack-loader kullanın.

url-loader, küçük statik dosyaları uygulamaya satır içine alır. Yapılandırma olmadan, geçirilen bir dosyayı alır, derlenen paketin yanına yerleştirir ve bu dosyanın URL'sini döndürür. Bununla birlikte, limit seçeneğini belirtirsek bu sınırdan küçük dosyaları Base64 veri URL'si olarak kodlar ve bu URL'yi döndürür. Bu işlem, resmi JavaScript kodunda satır içine alır ve bir HTTP isteği kaydeder:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif)$/,
        loader: 'url-loader',
        options: {
          // Inline files smaller than 10 kB (10240 bytes)
          limit: 10 * 1024,
        },
      },
    ],
  }
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`

svg-url-loader, url-loader ile aynı şekilde çalışır. Tek fark, dosyaları Base64 yerine URL kodlaması ile kodlamasıdır. Bu, SVG resimleri için kullanışlıdır. SVG dosyaları düz metin olduğundan, bu kodlama boyut açısından daha etkilidir.

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: "svg-url-loader",
        options: {
          limit: 10 * 1024,
          noquotes: true
        }
      }
    ]
  }
};

image-webpack-loader, üzerinden geçen resimleri sıkıştırır. JPG, PNG, GIF ve SVG resimleri desteklediğinden tüm bu türler için kullanacağız.

Bu yükleyici, resimleri uygulamaya yerleştirmediğinden url-loader ve svg-url-loader ile birlikte çalışması gerekir. Her iki kurala da (biri JPG/PNG/GIF resimleri ve diğeri SVG resimleri için) kopyalanıp yapıştırılmaması için bu yükleyiciyi enforce: 'pre' ile ayrı bir kural olarak ekleyeceğiz:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'image-webpack-loader',
        // This will apply the loader before the other ones
        enforce: 'pre'
      }
    ]
  }
};

Yükleyicinin varsayılan ayarlarını kullanmaya başlayabilirsiniz. Ancak, yükleyiciyi daha fazla yapılandırmak isterseniz eklenti seçeneklerine bakın. Hangi seçenekleri belirteceğinizi seçmek için Addy Osmani'nin mükemmel resim optimizasyonu rehberine göz atın.

Daha fazla bilgi

Bağımlılıkları optimize edin

Ortalama JavaScript boyutunun yarıdan fazlası bağımlılıklardan gelir ve bu boyutun bir kısmı gereksiz olabilir.

Örneğin, Lodash (4.17.4 sürümünden itibaren) pakete 72 KB küçültülmüş kod ekler. Ancak yöntemlerinden yalnızca 20 tanesini kullanırsanız, yaklaşık 65 KB'lık küçültülmüş kodun herhangi bir etkisi olmaz.

Diğer bir örnek ise Moment.js. 2.19.1 sürümü, 223 KB boyutunda küçültülmüş kod kapsıyor. Bu çok büyük bir değer. Bir sayfadaki JavaScript'in ortalama boyutu Ekim 2017'de 452 KB'tı. Ancak bu boyutun 170 KB'ı yerelleştirme dosyalarıdır. Moment.js'yi birden fazla dilde kullanmıyorsanız bu dosyalar paketi bir amaç olmadan şişirir.

Tüm bu bağımlılıklar kolayca optimize edilebilir. Optimizasyon yaklaşımlarını bir GitHub deposunda topladık. İnceleyin.

ES modülleri için modül birleştirmeyi (diğer bir deyişle kapsam kaldırma) etkinleştir

Bir paket oluştururken, webpack her bir modülü bir işleve sarmalar:

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

// bundle.js (part  of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
  Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_exports__["a"] = render;
  function render(data, target) {
    console.log('Rendered!');
  }
})

Geçmişte, CommonJS/AMD modüllerini birbirinden ayırmak için bu gerekliydi. Ancak, bu durum her modül için bir boyut ve performans yükü ekliyordu.

Web paketi 2'de sunulan ES modülleri, CommonJS ve AMD modüllerinin aksine, her biri bir işlevle sarmalanmadan paketlenebilir. Webpack 3 ise modül birleştirme sayesinde böyle bir paketleme işlemi gerçekleştirilmesini sağladı. Modül birleştirmenin işi şu şekildedir:

// index.js
import {render} from './comments.js';
render();

// comments.js
export function render(data, target) {
  console.log('Rendered!');
}

// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files

// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

  // CONCATENATED MODULE: ./comments.js
    function render(data, target) {
    console.log('Rendered!');
  }

  // CONCATENATED MODULE: ./index.js
  render();
})

Farkı görüyor musunuz? Düz pakette, modül 0, modül 1'den render gerektiriyordu. Modül birleştirmede, require yalnızca gerekli işlevle değiştirilir ve modül 1 kaldırılır. Pakette daha az modül ve daha az modül ek yükü olur.

Bu davranışı etkinleştirmek için webpack 4'te optimization.concatenateModules seçeneğini etkinleştirin:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    concatenateModules: true
  }
};

webpack 3'te ModuleConcatenationPlugin kullanın:

// webpack.config.js (for webpack 3)
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};

Daha fazla bilgi

Hem webpack hem de webpack olmayan kodunuz varsa externals kullanın

Bazı kodların webpack ile derlendiği, bazı kodların ise olmadığı büyük bir projeniz olabilir. Oynatıcı widget'ının web paketi ile oluşturulabileceği ancak etrafındaki sayfanın aşağıdaki özelliklere sahip olmadığı bir video barındırma sitesi gibi:

Video barındırma sitesinin ekran görüntüsü
(Tamamen rastgele bir video barındırma sitesi)

Her iki kod parçasının da ortak bağımlılıkları varsa kodlarını birden fazla kez indirmemek için bunları paylaşabilirsiniz. Bu işlem web paketinin externals seçeneğiyle yapılır. Modülleri değişkenlerle veya diğer harici içe aktarma işlemleriyle değiştirir.

window konumunda bağımlılıklar varsa

Web paketi olmayan kodunuz window içinde değişken olarak bulunan bağımlılıklara dayanıyorsa bağımlılık adlarını değişken adlarına takma ad olarak verir:

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  }
};

Bu yapılandırmada webpack, react ve react-dom paketlerini paketlemez. Bunların yerine, şunun gibi bir metin yer alır:

// bundle.js (part of)
(function(module, exports) {
  // A module that exports `window.React`. Without `externals`,
  // this module would include the whole React bundle
  module.exports = React;
}),
(function(module, exports) {
  // A module that exports `window.ReactDOM`. Without `externals`,
  // this module would include the whole ReactDOM bundle
  module.exports = ReactDOM;
})

Bağımlılıkların AMD paketleri olarak yüklenip yüklenmediği

Web paketi olmayan kodunuz window ürününde bağımlılıkları açığa çıkarmıyorsa işler daha karmaşıktır. Ancak web paketi olmayan kod bu bağımlılıkları AMD paketleri olarak tüketiyorsa aynı kodu iki kez yüklemekten kaçınabilirsiniz.

Bunu yapmak için web paketi kodunu bir AMD paketi ve kitaplık URL'leri için takma ad modülleri olarak derleyin:

// webpack.config.js
module.exports = {
  output: {
    libraryTarget: 'amd'
  },
  externals: {
    'react': {
      amd: '/libraries/react.min.js'
    },
    'react-dom': {
      amd: '/libraries/react-dom.min.js'
    }
  }
};

Webpack, paketi define() içine sarmalayarak şu URL'lere bağlı olmasını sağlar:

// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });

Web paketi olmayan kod, bağımlılıklarını yüklemek için aynı URL'leri kullanıyorsa bu dosyalar yalnızca bir kez yüklenir. Ek istekler, yükleyici önbelleğini kullanır.

Daha fazla bilgi

  • externals üzerindeki web paketi dokümanları

Özet

  • Webpack 4 kullanıyorsanız üretim modunu etkinleştirin
  • Paket düzeyinde küçültücü ve yükleyici seçenekleriyle kodunuzu küçültün
  • NODE_ENV yerine production koyarak yalnızca geliştirme amaçlı kodu kaldırın
  • Ağaç sallamayı etkinleştirmek için ES modüllerini kullanın
  • Resimleri sıkıştırın
  • Bağımlılığa özgü optimizasyonlar uygulama
  • Modül birleştirmeyi etkinleştir
  • Sizin için mantıklıysa externals kullanın.