롤업으로 최신 JavaScript 제공

This post is a few months old, but it still gets a few hits on my blog everyday so maybe it can help people.



코드베이스에서 ES2017(및 최신) 구문을 사용하는 것과 이를 사용자에게 보내는 것은 별개입니다.

IE11을 지원하거나 @babel/preset-env을 사용해야 하거나 단일 번들(코드 분할 포함)을 배송해야 하는 경우 브라우저가 classes 또는 async/await을 완벽하게 이해할 수 있더라도 모든 사람에게 ES5를 보낼 가능성이 있습니다. .

이는 우리가 필요 이상으로 더 많은 코드를 보내고 장기적으로 일부 "무료"성능 향상을 놓칠 것이기 때문에 차선책입니다.

Six Speed project에서 ES5 트랜스파일 버전과 비교한 ES6 기능의 성능에 대해 자세히 알아볼 수 있습니다. 브라우저 공급업체가 새 구문의 성능을 정기적으로 개선하기 때문에 현상 유지는 고정되어 있지 않습니다(thisthis 참조).

또한 모든 preset-env 타겟이 최신 상태이더라도 Babel may still be transpiling some syntax because of edge-cases 입니다. 게다가, 우리의 의존성은 아마도 어쨌든 ES5로 변환될 것입니다.

더 나은 방법은 트랜스파일된 ES5를 레거시 브라우저로, ES2017을 최신 브라우저로 최대한 많이 제공하는 것입니다.

이를 달성하기 위해 취해야 할 단계는 다음과 같습니다.
  • 2개의 개별 번들 생성
  • preset-modules 대신
  • 을 사용하여 최신 빌드를 만듭니다.
  • 출력 ES2017
  • terser 구성

  • 올바른 브라우저에서 각 번들을 로드합니다.

  • 작업 예제 in this repo 을 볼 수 있습니다.

    번들링



    롤업을 사용하는 경우 이미 ES 모듈 또는 기본적으로 폴백을 출력할 수 있습니다.
    그러나 다른 구성으로 각 빌드를 생성하려면 약간의 구성을 추가해야 합니다.

    이를 수행하는 한 가지 방법은 npm 스크립트에서 환경 변수 preset-env 을 사용하는 것입니다.

    롤업 및 Terser 구성



    // package.json
    {
        /** ... **/
        "scripts": {
            "build": "npm-run-all --parallel \"build:*\"",
            "build:legacy": "env ROLLUP_BUILD_TYPE=\"legacy\" rollup -c",
            "build:modern": "env ROLLUP_BUILD_TYPE=\"modern\" rollup -c"
        },
        "devDependencies" : {
            "npm-run-all": "...",
            "rollup": "..."
        }
        /** ... **/
    }
    



    // rollup.config.js
    export default () => {
        const buildType = typeof process.env.ROLLUP_BUILD_TYPE !== "undefined" ? process.env.ROLLUP_BUILD_TYPE : "modern";
        return {
            input: ["./src/index.jsx"],
            output: buildType === "modern" ?
            {
                dir: `/public/js/system/`,
                format: "system"
            } :
            {
                dir: `/public/js/esm/`,
                format: "esm"
            },
            plugins: [
                /** ... **/
                babel({
                    /**
                    * Uncomment to ignore node_modules. This will accelerate yur build,
                    * but prevent you from using modern syntax in your dependencies
                    */
                    // exclude: "node_modules/**"
                }),
                terser({
                    compress: {
                        unused: false,
                        collapse_vars: false
                    },
                    sourcemap: true,
                    ecma: buildType === "legacy" ? 5 : 2017,
                    safari10: true,
                })
                /** ... **/
            ]
        };
    };
    

    Babel에서 node_modules/를 제외하지 않은 방법에 주목하세요. 이렇게 하면 최신 구문을 내보내는 경우 종속성을 최적으로 변환할 수 있습니다. 나중에 자세히 설명합니다.

    Terser은 우리가 사용하는 언어의 버전을 알려야 하는 축소기입니다.

    바벨 구성



    Babel 7은 정적 개체 대신 함수로 구성할 수 있습니다. 조건부 및 주석이 달린 코드를 작성하기 위해 이 기능을 활용할 수 있습니다.

    우리의 목표는 가능한 한 적게 트랜스파일하는 것임을 기억하십시오. 이 예에서는 반응용 사전 설정과 ES2020 features용 플러그인을 포함합니다. 플러그인은 최신 브라우저에서도 한동안 계속 사용됩니다.

    // in babel.config.cjs
    module.exports = function(api) {
        api.cache.invalidate(() => [
            process.env.NODE_ENV, 
            process.env.ROLLUP_BUILD_TYPE
        ].join("-"));
        const environment = api.env();
        const modern = process.env.ROLLUP_BUILD_TYPE === "modern";
        /**
         * Will be used for the legacy build
         */
        const presetEnv = [
            "@babel/preset-env",
            {
                modules: false,
                targets: {
                    browsers: [">0.25%", "not op_mini all"],
                },
            },
        ];
        /**
         * Will be used for the modern build
         */
        const presetModule = [
            "@babel/preset-modules",
            {
                loose: true,
            },
        ];
        const alwaysUsedPresets = ["@babel/preset-react"];
    
        const alwaysUsedPlugins = [
            "@babel/plugin-syntax-dynamic-import",
            "@babel/plugin-proposal-optional-chaining",
            "@babel/plugin-proposal-nullish-coalescing-operator",
        ];
        /**
         * Only loaded in the legacy build
         */
        const legacyPlugins = [
            "@babel/plugin-proposal-object-rest-spread"
        ];
    
        const productionConfig =
            modern === "modern"
                ? {
                      /**
                       * Modern build
                       */
                      presets: [
                          presetModule, 
                          ...alwaysUsedPresets
                        ],
                      plugins: [...alwaysUsedPlugins],
                  }
                : {
                      /**
                       * Legacy build
                       */
                      presets: [
                          presetEnv,
                          ...alwaysUsedPresets
                        ],
                      plugins: [
                          ...alwaysUsedPlugins,
                          ...legacyPlugins
                        ],
                  };
    
        const developmentConfig =
            modern === "modern"
                ? {
                      /**
                       * Modern build
                       */
                      presets: [
                          presetModule,
                          ...alwaysUsedPresets
                        ],
                      plugins: [
                          ...alwaysUsedPlugins
                        ],
                  }
                : {
                      /**
                       * Legacy build
                       */
                      presets: [
                          presetEnv, 
                          ...alwaysUsedPresets
                        ],
                      plugins: [
                          ...alwaysUsedPlugins, 
                          ...legacyPlugins
                          ],
                  };
        return {
            env: {
                production: productionConfig,
                development: developmentConfig,
                test: {
                    /**
                     * Chances are tests will run in node
                    **/
                    presets: [
                        "@babel/preset-env",
                        ...alwaysUsedPresets
                    ],
                    plugins: [
                        ...alwaysUsedPlugins, 
                        ...legacyPlugins
                    ],
                },
            },
        };
    };
    


    babel이 비동기적으로 로드될 때만 작동하기 때문에 .mjs 대안을 사용하지 않았습니다.
    ROLLUP_BUILD_TYPEpreset-modules보다 적은 코드를 트랜스파일합니다. 이유를 이해하려면 the documentation을 읽으십시오.

    이제 번들이 있으므로 타겟에 로드할 방법이 필요합니다.

    모듈/노모듈 패턴



    이 기술(2017에서)은 레거시 브라우저(요즘 대부분 IE)와 "에버그린"브라우저(Chrome, Firefox, Safari, Edge 등) 간의 중단점으로 preset-env 지원을 활용합니다.
    이를 통해 최신 번들을 최신 브라우저에 보내고 레거시 번들을 이전 브라우저에 보낼 수 있습니다.

    오랫동안 일이 그렇게 간단하지 않았습니다. 이것은 이제 삭제되었으며 <script type="module">을 안전하게 사용할 수 있습니다.

    패턴:

    <script type="module">
    import("/js/esm/main.js").then((module) => {
        /** ... **/
    });
    </script>
    <script nomodule src="/js/legacy/main.js"></script>
    


    미래



    이 게시물은 ES2017에 관한 것이지만 브라우저 지원이 계속 유지되는 한 여기에 설명된 기술을 나중에 업데이트하여 최신 버전의 언어를 사용할 수 있습니다.
    주목해야 할 중요한 제한 사항 중 하나는 대부분의 타사 라이브러리가 여전히 don't export untranspiled code 이라는 것입니다. 불행히도 이것은 당신이 제공하는 대부분의 코드가 현재로서는 여전히 ES5라는 것을 의미합니다.
    그러나 Babel 구성에서 node_modules/제외를 중단하고 더 많은 사람들이 동일한 작업을 수행하면 상황이 곧 개선될 수 있습니다.

    좋은 웹페이지 즐겨찾기