프런트엔드 크기 줄이기

webpack을 사용하여 앱을 최대한 작게 만드는 방법

애플리케이션을 최적화할 때 가장 먼저 해야 할 일은 애플리케이션을 있습니다. webpack으로 이를 수행하는 방법은 다음과 같습니다.

프로덕션 모드 사용 (webpack 4만 해당)

Webpack 4는 새로운 mode 플래그를 도입했습니다. 다음과 같이 설정할 수 있습니다. 이 플래그를 'development' 또는 'production'로 지정하여 빌드 중인 webpack에 대한 힌트를 얻을 수 있습니다. 애플리케이션을 배포합니다.

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

프로덕션용 앱을 빌드할 때는 production 모드를 사용 설정해야 합니다. 이렇게 하면 Webpack이 압축, 개발 전용 코드 삭제와 같은 최적화를 적용합니다. 라이브러리

추가 자료

압축 사용 설정

축소는 여분의 공백을 제거하고 변수 이름을 단축하고 등등. 다음 행을 추가하면 됩니다.

// 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은 코드를 축소하는 두 가지 방법인 번들 수준 축소로더별 옵션을 제공합니다. 동시에 사용해야 합니다.

번들 수준 축소

번들 수준 축소는 컴파일 후 전체 번들을 압축합니다. 이용 방법은 다음과 같습니다.

  1. 다음과 같은 코드를 작성합니다.

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. Webpack은 이를 대략 다음과 같이 컴파일합니다.

    // 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. 축소기는 파일을 대략 다음과 같이 압축합니다.

    // 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)
    

Webpack 4에서는 번들 수준 축소가 자동으로 사용 설정됩니다. 둘 다 프로덕션 환경에서 있습니다. UglifyJS 축소기를 사용합니다. 살펴보겠습니다 축소를 사용 중지해야 하는 경우 개발 모드만 사용하여 또는 falseoptimization.minimize 옵션에 전달합니다.)

Webpack 3에서는 UglifyJS 플러그인을 사용해야 합니다. 바로 그것입니다. 플러그인은 webpack과 함께 번들로 제공됩니다. 사용 설정하려면 plugins에 추가하세요. 섹션에서 다음을 실행합니다.

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

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

로더별 옵션

코드를 축소하는 두 번째 방법은 로더별 옵션 (로더는 is) 로더 옵션을 사용하면 축소할 수 없습니다. 예를 들어 css-loader: 파일이 문자열로 컴파일됩니다.

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

이 코드는 문자열이므로 축소기가 압축할 수 없습니다. 파일 콘텐츠를 축소하려면 이렇게 하도록 로더를 구성합니다.

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

추가 자료

NODE_ENV=production 지정

프런트엔드 크기를 줄이는 또 다른 방법은 NODE_ENV 환경 변수 값을 production로 변경합니다.

라이브러리는 NODE_ENV 변수를 읽고 작동해야 하는 모드를 감지합니다. 프로덕션에 배포할 수 있습니다. 일부 라이브러리는 이 변수에 따라 다르게 동작합니다. 대상 예를 들어 NODE_ENVproduction로 설정되지 않은 경우 Vue.js가 추가 검사를 실행하고 인쇄합니다. 경고:

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

React도 비슷하게 작동합니다. 즉, 경고가 포함된 개발 빌드를 로드합니다.

// 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.'
);
// …

이러한 검사와 경고는 일반적으로 프로덕션에서 불필요하지만 코드에 남아 있고 라이브러리 크기를 늘릴 수 있습니다. Webpack 4에서는 optimization.nodeEnv: 'production' 옵션:

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

Webpack 3에서는 대신 DefinePlugin를 사용합니다.

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

optimization.nodeEnv 옵션과 DefinePlugin는 모두 동일한 방식으로 작동합니다. 모든 process.env.NODE_ENV 항목을 지정된 값으로 바꿉니다. 구성:

  1. Webpack이 process.env.NODE_ENV과(와) 일치하는 항목을 모두 다음으로 바꿉니다. "production":

    // 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. 그런 다음 최소기가 이러한 모든 if 브랜치: "production" !== 'production'가 항상 false이기 때문에 그리고 플러그인은 이러한 브랜치 내의 코드가 실행되지 않는다는 것을 이해합니다.

    // 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 };
    }
    

추가 자료

ES 모듈 사용

프런트엔드 크기를 줄이는 다음 방법은 ES를 사용하는 것입니다. 모듈을 참조하세요.

ES 모듈을 사용하면 webpack이 트리 쉐이킹을 할 수 있게 됩니다. 나무 흔들림은 번들러가 전체 종속 항목 트리를 순회하고 사용된 종속 항목을 확인하고 사용되지 않는 종속 항목을 삭제합니다. 따라서 ES 모듈 구문을 사용하는 경우 webpack으로 사용되지 않는 코드를 제거할 수 있습니다.

  1. 내보내기가 여러 개 포함된 파일을 작성하지만 앱에서 그중 하나만 사용합니다.

    // comments.js
    export const render = () => { return 'Rendered!'; };
    export const commentRestEndpoint = '/rest/comments';
    
    // index.js
    import { render } from './comments.js';
    render();
    
  2. Webpack은 commentRestEndpoint이 사용되지 않으며 번들에 있는 별도의 내보내기 지점을 지정합니다.

    // 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. 최소기는 사용되지 않는 변수를 삭제합니다.

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

라이브러리가 ES 모듈로 작성된 경우에도 작동합니다.

하지만 Webpack에 내장된 축소기 (UglifyJsPlugin)를 정확하게 사용할 필요는 없습니다. 데드 코드 삭제를 지원하는 축소기 (예: Babel Minify 플러그인) 또는 Google 클로저 컴파일러 플러그인) 할 수 있습니다.

추가 자료

이미지 최적화

Google에서 제공하는 이미지는 절반으로 줄일 수 있습니다. 다른 자바스크립트만큼 중요하지 않지만 (예: 렌더링을 차단하지 않음) 지정할 수도 있습니다 url-loader, svg-url-loader, image-webpack-loader를 사용하여 webpack이 포함되어 있습니다

url-loader는 작은 정적 파일을 있습니다. 구성이 없으면 전달된 파일을 가져와서 컴파일된 번들 옆에 배치하고 다음을 반환합니다. 해당 파일의 URL입니다. 그러나 limit 옵션을 지정하면 이 한도를 Base64 데이터 URL로 저장하고 이 URL을 반환합니다. 이 이미지를 자바스크립트 코드에 삽입하고 HTTP 요청을 저장합니다.

// 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-loaderurl-loader처럼 작동합니다. URL 인코딩을 사용하여 있습니다 이는 SVG 이미지에 유용합니다. SVG 파일은 일반 텍스트이기 때문에 더 효율적으로 사용할 수 있습니다.

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

image-webpack-loader는 이동 중인 이미지를 압축합니다 그것을 통해. JPG, PNG, GIF, SVG 이미지를 지원하므로 이 모든 유형에 사용합니다.

이 로더는 앱에 이미지를 삽입하지 않으므로 url-loadersvg-url-loader 두 규칙 모두에 복사해서 붙여넣지 않으려면 (JPG/PNG/GIF 이미지에 대해 하나씩, 다른 규칙은 SVG용 하나) enforce: 'pre'를 사용하여 별도의 규칙으로 이 로더를 포함합니다.

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

로더의 기본 설정은 이미 준비가 되어 있지만, 기본 설정을 구성하기를 원하는 경우 플러그인 옵션을 참고하세요. 받는사람 추가할 옵션을 선택하려면 Addy Osmani의 이미지 관련 유용한 가이드를 읽어보세요 최적화를 참조하세요.

추가 자료

종속 항목 최적화

평균 JavaScript 크기의 절반 이상이 종속 항목에서 비롯되며, 그 중 일부는 불필요할 수도 있습니다

예를 들어 Lodash (v4.17.4부터)는 번들에 72KB의 축소된 코드를 추가합니다. 그러나 만일 약 65KB의 축소된 코드는 아무 작업도 하지 않습니다.

또 다른 예는 Moment.js입니다. 최신 버전의 2.19.1 버전은 223KB의 축소된 코드를 사용합니다. 10월에 페이지의 JavaScript 평균 크기는 452KB였습니다. 2017 하지만 이 크기의 170KB는 현지화는 파일을 참고하세요. 만약 Moment.js를 여러 언어로 사용하지 않는 경우 이 파일은 있습니다.

이러한 모든 종속 항목을 쉽게 최적화할 수 있습니다. Google은 여러 연구에서 GitHub 저장소 – 확인

ES 모듈에 모듈 연결 사용 설정 (범위 호이스팅이라고도 함)

번들을 빌드할 때 webpack은 각 모듈을 함수로 래핑합니다.

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

이전에는 CommonJS/AMD 모듈을 서로 격리하기 위해 이렇게 해야 했습니다. 그러나 크기 및 성능 오버헤드가 존재합니다

Webpack 2는 CommonJS 및 AMD 모듈과 달리 번들로 묶을 수 있는 ES 모듈 지원을 도입했습니다. 각 함수를 함수로 래핑하지 않고 함수를 만들 수 있습니다 그리고 Webpack 3는 이러한 번들을 가능하게 했습니다. 모듈 연결. 방법은 다음과 같습니다. 모듈 연결은 어떤 역할을 하나요?

// 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();
})

차이점을 아시겠죠? 일반 번들에서 모듈 0은 모듈 1의 render를 요구했습니다. 다음으로 바꿉니다. require는 단순히 필수 함수로 대체되고 모듈 1은 삭제되었습니다. 번들은 모듈이 더 적고 모듈 오버헤드도 적습니다.

이 동작을 사용 설정하려면 Webpack 4에서 optimization.concatenateModules 옵션을 사용 설정하세요.

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

Webpack 3에서 ModuleConcatenationPlugin를 사용합니다.

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

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

추가 자료

Webpack 및 Webpack 외의 코드가 모두 있는 경우 externals 사용

일부 코드는 webpack으로 컴파일되는 대규모 프로젝트의 경우, 어떤 코드는 컴파일되지 않을 수 있습니다. 좋아요 웹팩으로 플레이어 위젯을 구축할 수 있는 동영상 호스팅 사이트와 주변 페이지 다음과 같지 않을 수 있습니다.

<ph type="x-smartling-placeholder">
</ph> 동영상 호스팅 사이트의 스크린샷
(완전히 임의의 동영상 호스팅 사이트)

두 코드 모두에 공통 종속 항목이 있는 경우 이를 공유하여 코드 다운로드를 방지할 수 있습니다. 여러 번 반복해야 할 수도 있습니다. 이 작업은 Webpack의 externals 옵션을 사용하여 모듈을 변수로 대체하거나 기타 외부 가져오기를 실행할 수 있습니다

window에서 종속 항목을 사용할 수 있는 경우

webpack 외의 코드가 window에서 변수로 사용할 수 있는 종속 항목을 사용하는 경우 별칭 종속 항목 이름을 변수 이름으로 바꿉니다.

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

이 구성을 사용하면 webpack이 reactreact-dom 패키지를 번들로 묶지 않습니다. 대신 다음과 같이 바꿉니다.

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

종속 항목이 AMD 패키지로 로드되는 경우

webpack 이외의 코드가 window에 종속 항목을 노출하지 않으면 상황이 더 복잡해집니다. 하지만 webpack 이외 코드에서 AMD 패키지로 종속 항목을 설치합니다.

이렇게 하려면 다음과 같이 webpack 코드를 AMD 번들로 컴파일하고 별칭 모듈을 라이브러리 URL로 컴파일합니다.

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

Webpack은 번들을 define()로 래핑하고 다음 URL에 종속되도록 합니다.

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

webpack 이외의 코드에서 동일한 URL을 사용하여 종속 항목을 로드하는 경우 이러한 파일이 로드됩니다. 한 번만 – 추가 요청에서 로더 캐시를 사용합니다.

추가 자료

요약

  • webpack 4를 사용하는 경우 프로덕션 모드 사용 설정
  • 번들 수준의 최소화기 및 로더 옵션으로 코드 최소화
  • NODE_ENVproduction로 바꿔 개발 전용 코드를 삭제합니다.
  • ES 모듈을 사용하여 트리 쉐이킹 사용 설정
  • 이미지 압축
  • 종속 항목별 최적화 적용
  • 모듈 연결 사용 설정
  • 이 옵션이 적절하다면 externals을(를) 사용하세요.