더 빠른 애플리케이션을 위한 최신 JavaScript 게시, 제공, 설치

최신 JavaScript 종속 항목 및 출력을 사용 설정하여 성능을 개선하세요.

브라우저의 90% 이상이 최신 자바스크립트를 실행할 수 있지만, 기존 자바스크립트의 보급이 오늘날 웹 성능 문제의 주요 원인으로 남아 있습니다.

최신 자바스크립트

최신 JavaScript는 특정 ECMAScript 사양 버전으로 작성된 코드가 아니라, 모든 최신 브라우저에서 지원되는 구문으로 작성된 코드라고 할 수 있습니다. Chrome, Edge, Firefox, Safari와 같은 최신 웹브라우저가 브라우저 시장의 90% 이상을 차지하며, 동일한 기본 렌더링 엔진을 사용하는 다양한 브라우저가 추가로 5%를 차지합니다. 이는 전 세계 웹 트래픽의 95% 가 지난 10년간 가장 널리 사용된 자바스크립트 언어 기능을 지원하는 브라우저에서 발생함을 의미합니다.

  • 클래스 (ES2015)
  • 화살표 함수 (ES2015)
  • Generators (ES2015)
  • 블록 범위 지정 (ES2015)
  • 디스트럭처링 (ES2015)
  • 휴식 및 확산 매개변수 (ES2015)
  • 객체 약칭 (ES2015)
  • Async/await (ES2017)

언어 사양의 최신 버전에 포함된 기능은 일반적으로 최신 브라우저에서 일관성이 떨어집니다. 예를 들어 대부분의 ES2020 및 ES2021 기능은 브라우저 시장의 70%(여전히 대부분의 브라우저에서만 지원)에서만 지원되지만, 이러한 기능에 직접 의존하는 것은 안전하지 않습니다. 즉, '최신' JavaScript는 움직이는 타겟이지만 ES2017은 일반적으로 사용되는 대부분의 최신 구문 기능을 포함하는 동시에 브라우저 호환성의 범위가 가장 넓다는 의미입니다. 즉, ES2017은 현재 최신 구문에 가장 가깝습니다.

기존 자바스크립트

기존 자바스크립트는 위의 모든 언어 기능을 사용하지 않는 코드입니다. 대부분의 개발자는 최신 구문을 사용하여 소스 코드를 작성하지만 브라우저 지원을 높이기 위해 모든 것을 기존 구문으로 컴파일합니다. 기존 구문으로 컴파일하면 브라우저 지원이 증가하지만 효과는 우리가 인식하는 것보다 작은 경우가 많습니다. 대부분의 경우 지원이 약 95%에서 98% 로 증가하지만 상당한 비용이 발생합니다.

  • 기존 JavaScript는 일반적으로 동등한 최신 코드보다 약 20% 더 크고 느립니다. 도구의 결함과 잘못된 구성으로 인해 이러한 격차가 더욱 벌어지는 경우가 많습니다.

  • 설치된 라이브러리는 일반적인 프로덕션 JavaScript 코드의 최대 90% 를 차지합니다. 라이브러리 코드에서는 최신 코드를 게시하면 피할 수 있는 폴리필 및 도우미 중복으로 인해 기존 JavaScript 오버헤드가 훨씬 커집니다.

npm의 최신 자바스크립트

최근 Node.js는 패키지의 진입점을 정의하기 위해 "exports" 필드를 표준화했습니다.

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

"exports" 필드에서 참조되는 모듈은 ES2019를 지원하는 12.8 이상의 노드 버전을 의미합니다. 즉, "exports" 필드를 사용하여 참조되는 모든 모듈은 최신 JavaScript로 작성할 수 있습니다. 패키지 소비자는 "exports" 필드가 있는 모듈에 최신 코드와 필요한 경우 트랜스파일이 포함되어 있다고 가정해야 합니다.

최신 제품만

최신 코드로 패키지를 게시하고 사용자가 종속 항목으로 사용할 때 트랜스파일링을 처리하도록 소비자에게 맡기려면 "exports" 필드만 사용하세요.

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

기존 대체를 사용하는 최신

최신 코드를 사용하여 패키지를 게시하려면 "main"와 함께 "exports" 필드를 사용하고 기존 브라우저의 ES5 + CommonJS 대체도 포함합니다.

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

기존 대체 및 ESM 번들러 최적화를 포함한 최신 기능

대체 CommonJS 진입점 정의 외에도 "module" 필드는 JavaScript 모듈 구문 (importexport)을 사용하는 유사한 기존 대체 번들을 가리키는 데 사용할 수 있습니다.

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

webpack 및 Rollup과 같은 많은 번들러가 이 필드를 사용하여 모듈 기능을 활용하고 트리 쉐이킹을 지원합니다. 이는 여전히 import/export 구문 외에 최신 코드를 포함하지 않는 기존 번들이므로 이 접근 방식을 사용하여 번들링에 최적화된 기존 대체와 함께 최신 코드를 제공하세요.

애플리케이션의 최신 JavaScript

서드 파티 종속 항목은 웹 애플리케이션에서 일반적인 프로덕션 JavaScript 코드의 대부분을 구성합니다. 지금까지 npm 종속 항목은 기존 ES5 구문으로 게시되었지만 더 이상 안전한 가정이 아니며 종속 항목 업데이트로 인해 애플리케이션의 브라우저 지원이 중단될 위험이 있습니다.

npm 패키지가 최신 자바스크립트로 이전되는 경우가 늘어남에 따라 이를 처리할 수 있도록 빌드 도구를 설정하는 것이 중요해졌습니다. 사용하는 npm 패키지 중 일부는 이미 최신 언어 기능을 사용하고 있을 가능성이 높습니다. 이전 브라우저에서 애플리케이션을 중단하지 않고 npm에서 최신 코드를 사용할 수 있는 다양한 옵션이 있지만, 일반적으로 빌드 시스템이 종속 항목을 소스 코드와 동일한 구문 타겟으로 트랜스파일하도록 하는 것이 좋습니다.

웹팩

이제 webpack 5부터 번들 및 모듈의 코드를 생성할 때 webpack에서 사용할 구문을 구성할 수 있습니다. 이는 코드나 종속 항목을 트랜스파일하지 않으며 webpack에서 생성된 'glue' 코드에만 영향을 미칩니다. 브라우저 지원 타겟을 지정하려면 프로젝트에 browserslist 구성을 추가하거나 웹팩 구성에서 직접 수행합니다.

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

최신 ES 모듈 환경을 타겟팅할 때 불필요한 래퍼 함수를 생략하는 최적화된 번들을 생성하도록 webpack을 구성할 수도 있습니다. 또한 <script type="module">를 사용하여 코드 분할 번들을 로드하도록 webpack을 구성합니다.

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

최적화 도구 플러그인 및 BabelEsmPlugin과 같은 기존 브라우저를 계속 지원하면서 최신 자바스크립트를 컴파일하고 제공하는 다양한 Webpack 플러그인이 있습니다.

최적화 도구 플러그인

최적화 도구 플러그인은 최종 번들 코드를 개별 소스 파일이 아닌 최신 자바스크립트에서 기존 자바스크립트로 변환하는 웹팩 플러그인입니다. 이 방법은 독립 실행형 설정으로, 웹팩 구성에서 모든 항목이 최신 자바스크립트이며 여러 출력 또는 구문에 대한 특수 브랜치가 없다고 가정할 수 있습니다.

최적화 도구 플러그인은 개별 모듈이 아닌 번들에서 작동하므로 애플리케이션의 코드와 종속 항목을 동일하게 처리합니다. 이렇게 하면 코드가 올바른 구문으로 번들로 묶여 트랜스파일되므로 npm의 최신 자바스크립트 종속 항목을 안전하게 사용할 수 있습니다. 또한 두 가지 컴파일 단계가 포함된 기존 솔루션보다 빠를 수 있으며, 최신 및 기존 브라우저용으로 별도의 번들을 생성할 수도 있습니다. 번들의 두 세트는 module/nomodule 패턴을 사용하여 로드되도록 설계되었습니다.

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

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

Optimize Plugin는 일반적으로 최신 코드와 기존 코드를 별도로 번들로 제공하는 맞춤 webpack 구성보다 빠르고 효율적입니다. 또한 Babel 실행을 처리하고 최신 출력과 기존 출력에 대한 별도의 최적 설정이 있는 Terser를 사용하여 번들을 축소합니다. 마지막으로, 생성된 기존 번들에 필요한 polyfill은 전용 스크립트로 추출되므로 최신 브라우저에 중복되거나 불필요하게 로드되지 않습니다.

비교: 소스 모듈을 두 번 트랜스파일하는 경우와 생성된 번들을 트랜스파일하는 비교

BabelEsmPlugin

BabelEsmPlugin@babel/preset-env와 함께 작동하여 기존 번들의 최신 버전을 생성하여 트랜스파일이 적은 코드를 최신 브라우저에 제공하는 웹팩 플러그인입니다. Next.jsPreact CLI에서 사용하는 가장 널리 사용되는 기성 모듈/nomodule 솔루션입니다.

// 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는 크게 분리된 두 개의 애플리케이션 빌드를 실행하므로 다양한 웹팩 구성을 지원합니다. 대규모 애플리케이션의 경우 두 번 컴파일하는 데 약간의 추가 시간이 걸릴 수 있지만 이 기법을 사용하면 BabelEsmPlugin가 기존 webpack 구성과 원활하게 통합되므로 사용 가능한 가장 편리한 옵션 중 하나가 됩니다.

node_modules를 트랜스파일하도록 babel-loader 구성

위의 두 플러그인 중 하나 없이 babel-loader를 사용하는 경우 최신 JavaScript npm 모듈을 사용하기 위해 중요한 단계가 필요합니다. 별도의 두 babel-loader 구성을 정의하면 node_modules에 있는 최신 언어 기능을 ES2017로 자동 컴파일하는 동시에 프로젝트 구성에 정의된 Babel 플러그인과 사전 설정으로 자체 퍼스트 파티 코드를 계속 트랜스파일할 수 있습니다. 이렇게 하면 모듈/nomodule 설정을 위한 최신 및 기존 번들이 생성되지 않지만 이전 브라우저를 손상시키지 않고 최신 JavaScript가 포함된 npm 패키지를 설치하고 사용할 수 있습니다.

webpack-plugin-modern-npm은 이 기법을 사용하여 package.json"exports" 필드가 있는 npm 종속 항목을 컴파일합니다. 최신 구문이 포함될 수 있기 때문입니다.

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

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

또는 해결되는 모듈의 package.json에서 "exports" 필드를 확인하여 webpack 구성에서 이 기법을 수동으로 구현할 수 있습니다. 간결성을 위해 캐싱을 생략하는 경우 맞춤 구현은 다음과 같을 수 있습니다.

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

이 접근 방식을 사용할 때는 축소기에서 최신 구문을 지원하는지 확인해야 합니다. Terseruglify-es에는 모두 압축 및 형식 지정 중에 ES2017 구문을 보존하고 경우에 따라 생성하기 위해 {ecma: 2017}를 지정하는 옵션이 있습니다.

롤업

Rollup에는 단일 빌드의 일부로 여러 번들 세트를 생성하는 지원이 기본 제공되며 기본적으로 최신 코드를 생성합니다. 따라서 이미 사용 중일 수 있는 공식 플러그인을 통해 최신 및 기존 번들을 생성하도록 Rollup을 구성할 수 있습니다.

@rollup/plugin-babel

Rollup을 사용하는 경우 getBabelOutputPlugin() 메서드(Rollup의 공식 Babel 플러그인에서 제공)가 개별 소스 모듈이 아닌 생성된 번들의 코드를 변환합니다. Rollup에는 각각 자체 플러그인이 있는 단일 빌드의 일부로 여러 번들 세트를 생성할 수 있는 지원 기능이 내장되어 있습니다. 이를 사용하여 각각 다른 Babel 출력 플러그인 구성을 통해 전달하여 최신 버전과 기존 버전의 다양한 번들을 생성할 수 있습니다.

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

추가 빌드 도구

Rollup 및 webpack은 구성 가능성이 높으므로 일반적으로 각 프로젝트가 종속 항목에서 최신 JavaScript 구문을 사용하도록 구성을 업데이트해야 합니다. Parcel, Snowpack, Vite, WMR과 같이 구성보다 규칙과 기본값을 선호하는 상위 수준 빌드 도구도 있습니다. 이러한 도구는 대부분 npm 종속 항목에 최신 문법이 포함되어 있다고 가정하고 프로덕션용으로 빌드할 때 적절한 구문 수준으로 변환 컴파일합니다.

webpack 및 Rollup 전용 플러그인 외에 기존 대체가 포함된 최신 JavaScript 번들을 devolution을 사용하여 모든 프로젝트에 추가할 수 있습니다. Devolution은 빌드 시스템의 출력을 변환하여 기존 JavaScript 변형을 생성하는 독립형 도구로, 이를 통해 번들링과 변환이 최신 출력 목표를 가정할 수 있습니다.