Daha hızlı uygulamalar için modern JavaScript'i yayınlayın, gönderin ve yükleyin

Modern JavaScript bağımlılıklarını ve çıkışını etkinleştirerek performansı artırın.

Tarayıcıların %90'ından fazlası modern JavaScript'i çalıştırabilir ancak eski JavaScript'in yaygınlığı günümüzde web'de performans sorunlarının önemli bir kaynağı olmaya devam etmektedir.

Modern JavaScript

Modern JavaScript, belirli bir ECMAScript spesifikasyonu sürümünde yazılmış kod şeklinde değil, tüm modern tarayıcılar tarafından desteklenen söz dizimiyle nitelenir. Chrome, Edge, Firefox ve Safari gibi modern web tarayıcıları, tarayıcı pazarının% 90'ından fazlasını oluşturur. Aynı temel oluşturma motorlarını kullanan farklı tarayıcılar ise ek bir %5'lik tarayıcı oluşturmaktadır. Bu, dünya genelindeki web trafiğinin %95'inin aşağıdakiler dahil olmak üzere son 10 yılda en yaygın kullanılan JavaScript dil özelliklerini destekleyen tarayıcılardan geldiği anlamına gelir:

  • Sınıflar (ES2015)
  • Ok işlevleri (ES2015)
  • Jeneratörler (ES2015)
  • Blok kapsamı (ES2015)
  • Yıkım (ES2015)
  • Rest ve spread parametreleri (ES2015)
  • Nesne kısaltması (ES2015)
  • Eş zamansız/bekleme (ES2017)

Dil spesifikasyonunun yeni sürümlerindeki özellikler, modern tarayıcılarda genellikle daha tutarlı bir şekilde desteklenmez. Örneğin, birçok ES2020 ve ES2021 özelliği yalnızca tarayıcı pazarının %70'inde desteklenir. Bu, tarayıcı sayısının çoğunu ifade etse de bu özelliklere doğrudan güvenmenin güvenli olduğu anlamına gelmez. Bu, "modern" JavaScript'in sürekli değişen bir hedef olmasına rağmen, ES2017'nin yaygın kullanılan modern söz dizimi özelliklerinin çoğunu içerir ve en geniş tarayıcı uyumluluğu yelpazesine sahip olduğu anlamına gelir. Başka bir deyişle, ES2017 günümüzde modern söz dizimine en yakın olan sürümdür.

Eski JavaScript

Eski JavaScript, özellikle yukarıdaki tüm dil özelliklerinin kullanılmadığı koddur. Çoğu geliştirici, kaynak kodlarını modern söz dizimi kullanarak yazar ancak daha fazla tarayıcı desteği için her şeyi eski söz dizimine derleyebilir. Eski söz dizimi için derleme yapmak tarayıcı desteğini artırır ancak bu etki genellikle düşündüğümüzden daha küçüktür. Birçok durumda, destek yaklaşık %95'ten %98'e çıkarken önemli bir maliyete neden olur:

  • Eski JavaScript, genellikle eşdeğer modern koda kıyasla yaklaşık %20 daha büyük ve daha yavaştır. Araç eksiklikleri ve yanlış yapılandırmalar genellikle bu farkı daha da artırır.

  • Yüklenen kitaplıklar, tipik üretim JavaScript kodunun %90'ını oluşturur. Kitaplık kodu, modern kod yayınlayarak önlenebilecek çoklu dolgu ve yardımcı yinelemesi nedeniyle daha da yüksek bir eski JavaScript ek yüküne neden olur.

npm'de modern JavaScript

Node.js, kısa süre önce bir paketin giriş noktalarını tanımlamak için "exports" alanını standartlaştırdı:

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

"exports" alanı tarafından başvurulan modüller, ES2019'u destekleyen en az 12.8 Düğüm sürümünün olduğunu gösterir. Bu, "exports" alanı kullanılarak referans verilen tüm modüllerin modern JavaScript ile yazılabileceği anlamına gelir. Paket tüketicileri, "exports" alanı içeren modüllerin modern kod içerdiğini varsaymalı ve gerekirse derlemeyi yeniden yapmalıdır.

Yalnızca modern

Modern kod içeren bir paket yayınlamak ve paketi bağımlı olarak kullanırken derlemeyi kullanıcıya bırakmak istiyorsanız yalnızca "exports" alanını kullanın.

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

Eski yedek ile modern

Paketinizi modern kod kullanarak yayınlamak ancak eski tarayıcılar için ES5 + CommonJS yedek seçeneği de eklemek istiyorsanız "main" ile birlikte "exports" alanını kullanın.

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

Eski yedek ve ESM paketleyici optimizasyonlarıyla modern

"module" alanı, bir yedek CommonJS giriş noktası tanımlamanın yanı sıra benzer bir eski yedek pakete (JavaScript modülü söz dizimini (import ve export) kullanan bir yedek pakete işaret etmek için de kullanılabilir.)

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

Webpack ve Rollup gibi birçok paketleyici, modül özelliklerinden yararlanmak ve ağaç sallamayı etkinleştirmek için bu alandan yararlanır. Bu, import/export söz diziminden başka modern kod içermeyen eski bir pakettir. Bu nedenle, modern kodu, paket oluşturma için hâlâ optimize edilmiş eski bir yedek ile birlikte göndermek için bu yaklaşımı kullanın.

Uygulamalarda modern JavaScript

Üçüncü taraf bağımlılıkları, web uygulamalarındaki tipik üretim JavaScript kodunun büyük çoğunluğunu oluşturur. npm bağımlılıkları geçmişte eski ES5 söz dizimi olarak yayınlanmış olsa da bu artık güvenli bir varsayım değildir ve uygulamanızdaki tarayıcı desteğini bozan risk bağımlılıkları güncellemeleridir.

Modern JavaScript'e taşınan npm paketlerinin sayısı her geçen gün arttığından derleme aracının bunları işleyecek şekilde ayarlandığından emin olmak önemlidir. Kullandığınız bazı npm paketlerinin zaten modern dil özellikleri kullanma olasılığı yüksektir. Uygulamanızı eski tarayıcılarda bozmadan npm'den modern kod kullanmak için çeşitli seçenekler vardır. Ancak genel fikir, derleme sisteminin bağımlılıkları kaynak kodunuzla aynı söz dizimi hedefine derlemesi şeklindedir.

webpack

Web paketi 5'ten itibaren, paketler ve modüller için kod oluştururken web paketinin hangi söz dizimini kullanacağını artık yapılandırabilirsiniz. Bu işlem, kodunuzu veya bağımlılıklarınızı derlemez. Yalnızca webpack tarafından oluşturulan "yapıştırıcı" kodunu etkiler. Tarayıcı desteği hedefini belirtmek için projenize bir browserslist yapılandırması ekleyin veya bunu doğrudan webpack yapılandırmanızda yapın:

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

Web paketini, modern bir ES Modülleri ortamını hedeflerken gereksiz sarmalayıcı işlevlerini atlayan optimize edilmiş paketler oluşturacak şekilde yapılandırmak da mümkündür. Bu işlem, webpack'i <script type="module"> kullanarak kod bölme paketlerini yükleyecek şekilde de yapılandırır.

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

Optimize Plugin ve BabelEsmFin gibi eski tarayıcıları desteklemeye devam ederken modern JavaScript'i derleyip göndermeyi mümkün kılan bir dizi web paketi eklentisi vardır.

Optimize Eklentisi

Optimize eklentisi, her bir kaynak dosya yerine nihai paketlenmiş kodu modern JavaScript'den eski JavaScript'e dönüştüren bir webpack eklentisidir. Bu, web paketi yapılandırmanızın birden fazla çıkış veya söz dizimi için özel bir bölümleme olmaksızın her şeyin modern JavaScript olduğunu varsaymasını sağlayan bağımsız bir kurulumdur.

Optimize Plugin, tek tek modüller yerine paketler üzerinde çalıştığından uygulamanızın kodunu ve bağımlılarınızı eşit şekilde işler. Bu, npm'den alınan modern JavaScript bağımlılıklarını, kodları doğru söz dizimine göre gruplandırıp aktarılacağı için kullanmayı güvenli hale getirir. Ayrıca, iki derleme adımı içeren geleneksel çözümlerden daha hızlı olurken modern ve eski tarayıcılar için ayrı paketler oluşturmaya devam edebilir. İki paket grubu, modül/modül yok kalıbı kullanılarak yüklenecek şekilde tasarlanmıştır.

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

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

Optimize Plugin, genellikle modern ve eski kodu ayrı olarak paketleyen özel webpack yapılandırmalarından daha hızlı ve verimli olabilir. Ayrıca, Babel'i çalıştırmayı sizin yerinize yönetir, modern ve eski çıkışlar için ayrı optimum ayarlarla Terser'i kullanarak paketleri küçültür. Son olarak, oluşturulan eski paketlerin ihtiyaç duyduğu polyfill'ler, yeni tarayıcılarda hiçbir zaman kopyalanmayacak veya gereksiz yere yüklenmeyecek şekilde özel bir komut dosyasına ayıklanır.

Karşılaştırma: Kaynak modüllerini iki kez derleme ve oluşturulan paketleri derleme.

BabelEsmPlugin

BabelEsmPlugin, modern tarayıcılara daha az derlenmiş kod göndermek için mevcut paketlerin modern sürümlerini oluşturmak amacıyla @babel/preset-env ile birlikte çalışan bir webpack eklentisidir. module/nomodule için hazır en popüler çözümdür. Next.js ve Preact CLI tarafından kullanılır.

// 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, uygulamanızın büyük ölçüde ayrı iki derlemesini çalıştırdığından çok çeşitli webpack yapılandırmalarını destekler. Büyük uygulamalarda iki kez derleme işlemi biraz daha fazla zaman alabilir. Ancak bu teknik, BabelEsmPlugin'ün mevcut webpack yapılandırmalarına sorunsuz bir şekilde entegre edilmesine olanak tanır ve bu yöntemi mevcut en kullanışlı seçeneklerden biri haline getirir.

node_modules dosyalarını derleyebilmesi için babel-loader'ı yapılandırma

babel-loader'ü önceki iki eklentiden biri olmadan kullanıyorsanız modern JavaScript npm modüllerini kullanmak için önemli bir adım uygulamanız gerekir. İki ayrı babel-loader yapılandırması tanımlamak, node_modules'da bulunan modern dil özelliklerini ES2017'ye otomatik olarak derlemeyi sağlar. Bu sırada kendi birinci taraf kodunuzu, projenizin yapılandırmasında tanımlanan Babel eklentileriyle ve hazır ayarlarıyla derleyebilirsiniz. Bu, modül/modülsüz kurulum için modern ve eski paketler oluşturmaz ancak eski tarayıcılarda sorun oluşturmadan modern JavaScript içeren npm paketlerini yükleyip kullanmayı mümkün kılar.

webpack-plugin-modern-npm, package.json içinde "exports" alanı bulunan npm bağımlılıkları derlemek için bu tekniği kullanır. Bu bağımlılıklar modern söz dizimi içerebilir:

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

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

Alternatif olarak, çözülmekte olan modüllerin package.json alanında "exports" alanı olup olmadığını kontrol ederek bu tekniği webpack yapılandırmanızda manuel olarak uygulayabilirsiniz. Özetlemek amacıyla önbelleğe alma işlemini atlayarak özel bir uygulama şu şekilde görünebilir:

// 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'],
          },
        },
      },
    ],
  },
};

Bu yaklaşımı kullanırken, modern söz dizimi özelliğinin sıkıştırıcınız tarafından desteklendiğinden emin olmanız gerekir. Hem Terser hem de uglify-es, sıkıştırma ve biçimlendirme sırasında ES2017 söz dizimini korumak ve bazı durumlarda oluşturmak için {ecma: 2017} belirtme seçeneğine sahiptir.

Toplayıcı

Toplayıcı, tek bir derlemenin parçası olarak birden fazla paket grubu oluşturma için yerleşik desteğe sahiptir ve varsayılan olarak modern kod oluşturur. Sonuç olarak Rollup, muhtemelen zaten kullandığınız resmi eklentilerle modern ve eski paketler oluşturacak şekilde yapılandırılabilir.

@rollup/plugin-babel

Rollup kullanıyorsanız getBabelOutputPlugin() yöntemi (Rollup'un resmi Babel eklentisi tarafından sağlanır) kodu tek tek kaynak modüller yerine oluşturulan paketlerde dönüştürür. Rollup, tek bir derlemenin parçası olarak her biri kendi eklentilerine sahip birden fazla paket grubu oluşturmak için yerleşik destek sunar. Bu özelliği, her birini farklı bir Babel çıkış eklentisi yapılandırmasından geçirerek modern ve eski için farklı paketler oluşturmak amacıyla kullanabilirsiniz:

// 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'],
        }),
      ],
    },
  ],
};

Ek derleme araçları

Rollup ve webpack, yüksek oranda yapılandırılabilirdir. Bu da genellikle her projenin, bağımlılıklarda modern JavaScript söz dizimini etkinleştirmek için yapılandırmasını güncellemesi gerektiği anlamına gelir. Parcel, Snowpack, Vite ve WMR gibi, yapılandırma yerine gelenek ve varsayılanları tercih eden daha üst düzey derleme araçları da vardır. Bu araçların çoğu, npm bağımlılıkları modern söz dizimi içerebileceğini varsayar ve üretim için derleme yaparken bunları uygun söz dizimi düzeylerine dönüştürür.

Webpack ve Rollup için özel eklentilere ek olarak, eski yedekleri olan modern JavaScript paketleri, devrimselleştirme kullanılarak herhangi bir projeye eklenebilir. Devolution, bir derleme sisteminden gelen çıkışı eski JavaScript varyantları oluşturacak şekilde dönüştüren bağımsız bir araçtır. Bu araç, paketlemenin ve dönüşümlerin modern bir çıkış hedefi almasını sağlar.