react-router 4 에서 코드 분할 방법(웹 팩 기반)

머리말
전단 프로젝트 가 계속 확대 되면 서 간단 한 웹 애플 리 케 이 션 에서 인용 한 js 파일 이 점점 커 질 수 있 습 니 다.특히 최근 유행 하 는 단일 페이지 애플 리 케 이 션 에 서 는 패키지 도구(예 를 들 어 웹 팩)에 점점 의존 하고 있 습 니 다.이 패키지 도 구 를 통 해 처리 하고 의존 하 는 모듈 을 하나의 bundle 파일 로 직접 포장 하여 페이지 를 처음 불 러 올 때 모든 js 를 불 러 옵 니 다.그러나 많은 장면 이 있 습 니 다.우 리 는 한 페이지 의 응용 에 대한 모든 의존 도 를 한꺼번에 불 러 올 필요 가 없습니다.예 를 들 어 우 리 는 현재 권한 을 가 진'주문 백 스테이지 관리'단일 페이지 응용 프로그램 이 있 습 니 다.일반 관리 자 는'주문 관리'부분 에 만 들 어 갈 수 있 고 슈퍼 사용 자 는'시스템 관리'를 할 수 있 습 니 다.또는,우 리 는 방대 한 단일 페이지 응용 프로그램 을 가지 고 있 습 니 다.사용자 가 처음 페이지 를 열 때,무관 한 자원 을 오래 불 러 올 때 까지 기 다 려 야 합 니 다.이 럴 때 우 리 는 일정한 코드 분할(code splitting)을 고려 할 수 있다.
실현 방식
간단하게 필요 에 따라 불 러 오기
코드 분할 의 핵심 목적 은 자원 의 필요 에 따라 로드 하 는 것 이다.이러한 장면 을 고려 하면 우리 사이트 에서 오른쪽 아래 에 채 팅 상자 와 유사 한 구성 요소 가 있 습 니 다.우리 가 원형 단 추 를 눌 렀 을 때 페이지 는 채 팅 구성 요 소 를 보 여 줍 니 다.

btn.addEventListener('click', function(e) {
  //      chat       chat.js
});
이 예 에서 알 수 있 듯 이 chat.js 를 불 러 오 는 작업 을 btn 클릭 이벤트 에 연결 하면 채 팅 단 추 를 누 른 후 채 팅 구성 요 소 를 필요 에 따라 불 러 올 수 있 습 니 다.js 자원 을 동적 으로 불 러 오 는 방식 도 간단 하 다.페이지 에탭 을 동적 으로 추가 하고 src 속성 을 이 자원 에 가리 키 면 됩 니 다.

btn.addEventListener('click', function(e) {
  //      chat       chat.js
  var ele = document.createElement('script');
  ele.setAttribute('src','/static/chat.js');
  document.getElementsByTagName('head')[0].appendChild(ele);
});
코드 분할 은 필요 에 따라 불 러 오 는 작업 을 실현 하기 위해 서 입 니 다.상상 해 보 세 요.우 리 는 포장 도 구 를 사용 하여 모든 js 를 bundle.js 라 는 파일 에 포장 했다.이런 상황 에서 위 에서 말 한 필요 에 따라 불 러 올 수 없 기 때문에 우 리 는 불 러 올 코드 에 따라 포장 과정 에서 분리 하 는 것 을 말 해 야 합 니 다.이것 이 바로 코드 분할 입 니 다.그렇다면 이 자원 들 에 대해 우 리 는 수 동 으로 분할 해 야 합 니까?물론 아 닙 니 다.포장 도 구 를 빌려 야 합 니 다.웹 팩 의 코드 분할 을 소개 합 니 다.
코드 분할
웹 팩 에서 코드 분할 을 하 는 방법 을 소개 합 니 다.웹 팩 에 서 는 구축 을 위 한 다양한 방식 이 있 습 니 다.
import()
이 곳 의 import 는 모듈 이 도 입 될 때의 import 와 달리 동적 으로 불 러 오 는 모듈 의 함수(function-like)로 이해 할 수 있 습 니 다.그 안에 들 어 오 는 매개 변 수 는 해당 모듈 입 니 다.예 를 들 어 기 존 모듈 에 import react from'react'를 도입 하면 import('react')라 고 쓸 수 있 습 니 다.그러나 주의해 야 할 것 은 import()가 Promise 대상 을 되 돌려 줍 니 다.따라서 다음 과 같은 방식 으로 사용 할 수 있다.

btn.addEventListener('click', e => {
  //      chat       chat.js
  import('/components/chart').then(mod => {
    someOperate(mod);
  });
});
평소 우리 가 사용 하 던 Promise 와 다 르 지 않 게 사용 방식 이 간단 하 다 는 것 을 알 수 있다.물론 이상 처 리 를 추가 할 수도 있다.

btn.addEventListener('click', e => {
  import('/components/chart').then(mod => {
    someOperate(mod);
  }).catch(err => {
    console.log('failed');
  });
});
물론 import()는 Promise 대상 을 되 돌려 주기 때문에 호환성 문제 에 주의해 야 합 니 다.이 문 제 를 해결 하 는 것 도 어렵 지 않 습 니 다.Promise 의 poly fill 을 사용 하여 호 환 을 실현 할 수 있 습 니 다.이 를 통 해 알 수 있 듯 이 동적 import()의 방식 은 의미 적 으로 나 문법 적 으로 나 비교적 뚜렷 하고 간결 하 다.
require.ensure()
웹 팩 2 홈 페이지 에 이렇게 적 혀 있 습 니 다.
require.ensure() is specific to webpack and superseded by import().
따라서 웹 팩 2 에 서 는 require.ensure()라 는 방법 을 사용 하 는 것 을 권장 하지 않 을 것 입 니 다.하지만 아직 은 이 방법 이 효과 가 있 기 때문에 간단하게 소개 할 수 있다.웹 팩 1 에 도 사용 가능 합 니 다.다음은 require.ensure()의 문법 입 니 다.

require.ensure(dependencies: String[], callback: function(require), errorCallback: function(error), chunkName: String)
require.ensure()는 세 개의 인 자 를 받 아들 입 니 다.
  • 첫 번 째 매개 변수 dependencies 는 하나의 배열 로 현재 require 가 들 어 온 모듈 의 의존 도 를 대표 합 니 다.
  • 두 번 째 매개 변수 인 콜 백 은 리 셋 함수 입 니 다.그 중에서 주의해 야 할 것 은 이 반전 함수 에 인자 require 가 있 는데 이 require 를 통 해 반전 함수 에서 다른 모듈 을 동적 으로 도입 할 수 있 습 니 다.주의해 야 할 것 은 이 require 는 리 셋 함수 의 매개 변수 이지 만 이론 적 으로 다른 이름 을 바 꿀 수 있 지만 사실은 바 꿀 수 없습니다.그렇지 않 으 면 webpack 은 정태 적 으로 분석 할 수 없 을 때 처리 할 수 없습니다.
  • 세 번 째 매개 변수 인 error Callback 은 error 의 리 셋 을 처리 하 는 것 이 좋 습 니 다.
  • 네 번 째 인자 chunk Name 은 포 장 된 chunk 이름 을 지정 합 니 다.
  • 따라서 require.ensure()의 구체 적 인 용법 은 다음 과 같다.
    
    btn.addEventListener('click', e => {
      require.ensure([], require => {
        let chat = require('/components/chart');
        someOperate(chat);
      }, error => {
        console.log('failed');
      }, 'mychat');
    });
    
    Bundle Loader
    상기 두 가지 방법 을 사용 하 는 것 외 에 웹 팩 의 일부 구성 요 소 를 사용 할 수 있 습 니 다.예 를 들 어 Bundle Loader 사용 하기:
    
    npm i --save bundle-loader
    require 사용("bundle-loader!./file.js")에 해당 하 는 chunk 로 딩 을 진행 합 니 다.이 방법 은 function 을 되 돌려 줍 니 다.이 function 은 반전 함 수 를 매개 변수 로 받 아들 입 니 다.
    
    let chatChunk = require("bundle-loader?lazy!./components/chat");
    chatChunk(function(file) {
      someOperate(file);
    });
    다른 loader 와 유사 하 며,Bundle Loader 도 webpack 설정 파일 에 해당 하 는 설정 을 해 야 합 니 다.Bundle-Loader 의코드도 간단 합 니 다.읽 어 보면 실제 적 으로 require.ensure()를 사용 하여 이 루어 진 것 을 알 수 있 습 니 다.Bundle-Loader 에 되 돌아 오 는 함수 에 해당 하 는 모듈 처리 리 턴 함 수 를 입력 하면 require.ensure()에서 처리 할 수 있 습 니 다.코드 는 마지막 에 해당 하 는 출력 형식 을 보 여 줍 니 다.
    
    /*
    Output format:
      var cbs = [],
        data;
      module.exports = function(cb) {
        if(cbs) cbs.push(cb);
          else cb(data);
      }
      require.ensure([], function(require) {
        data = require("xxx");
        var callbacks = cbs;
        cbs = null;
        for(var i = 0, l = callbacks.length; i < l; i++) {
          callbacks[i](data);
        }
      });
    */
    
    react-router v4 의 코드 분할
    마지막 으로 실제 작업 으로 돌아 가 웹 팩 을 기반 으로 react-router 4 에서 코드 분할 을 실현 합 니 다.react-router 4 는 react-router 3 에 비해 비교적 큰 변동 이 있 었 다.그 중에서 코드 분할 에 있어 서 react-router 4 의 사용 방식 도 react-router 3 과 큰 차이 가 있다.
    react-router 3 에 서 는 Route 구성 요소 에서 getComponent 라 는 API 를 사용 하여 코드 를 나 눌 수 있 습 니 다.getComponent 는 비동기 적 이 며 경로 가 일치 할 때 만 호출 됩 니 다.그러나 react-router 4 에서 이 API 를 찾 지 못 했 습 니 다.코드 분할 은 어떻게 하 시 겠 습 니까?
    react-router 4홈 페이지 에 코드 분할 의 예 가 있다.그 중에서 Bundle Loader 를 사용 하여 필요 에 따라 로드 와 동적 도입 을 진행 합 니 다.
    
    import loadSomething from 'bundle-loader?lazy!./Something'
    그러나 프로젝트 에서 유사 한 방식 을 사용 한 후 이러한 경고 가 나 타 났 다.
    Unexpected '!' in 'bundle-loader?lazy!./component/chat'. Do not use import syntax to configure webpack loaders import/no-webpack-loader-syntax
    Search for the keywords to learn more about each error.
    웹 팩 2 에 서 는 import 와 같은 방식 으로 loader 를 도입 할 수 없습니다no-webpack-loader-syntax
    Webpack allows specifying the loaders to use in the import source string using a special syntax like this:
    
    var moduleWithOneLoader = require("my-loader!./my-awesome-module");
    This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a Webpack configuration file.
    내 응용 프로그램 은 create-react-app 을 비계 로 사용 하여 웹 팩 의 일부 설정 을 차단 했다.물론 npm run eject 를 실행 하여 웹 팩 등 프로필 을 노출 시 킬 수도 있 습 니 다.하지만 다른 방법 을 써 도 될까요?그럼요.
    앞에서 말 한 두 가지 방식 으로 처리 할 수 있 습 니 다:import()또는 require.ensure().
    공식 인 스 턴 스 와 유사 합 니 다.우선 비동기 로 불 러 오 는 포장 구성 요소 Bundle 이 필요 합 니 다.Bundle 의 주요 기능 은 구성 요소 의 비동기 로드 방법 을 받 아들 이 고 해당 하 는 react 구성 요 소 를 되 돌려 주 는 것 입 니 다.
    
    export default class Bundle extends Component {
      constructor(props) {
        super(props);
        this.state = {
          mod: null
        };
      }
    
      componentWillMount() {
        this.load(this.props)
      }
    
      componentWillReceiveProps(nextProps) {
        if (nextProps.load !== this.props.load) {
          this.load(nextProps)
        }
      }
    
      load(props) {
        this.setState({
          mod: null
        });
        props.load((mod) => {
          this.setState({
            mod: mod.default ? mod.default : mod
          });
        });
      }
    
      render() {
        return this.state.mod ? this.props.children(this.state.mod) : null;
      }
    }
    기 존의 예 에서 Bundle Loader 를 통 해 모듈 을 도입 합 니 다.
    
    import loadSomething from 'bundle-loader?lazy!./About'
    
    const About = (props) => (
      <Bundle load={loadAbout}>
        {(About) => <About {...props}/>}
      </Bundle>
    )
    
    
    Bundle Loader 를 더 이상 사용 하지 않 기 때문에 import()를 사용 하여 이 코드 를 고 칠 수 있 습 니 다.
    
    const Chat = (props) => (
      <Bundle load={() => import('./component/chat')}>
        {(Chat) => <Chat {...props}/>}
      </Bundle>
    );
    
    주의해 야 할 것 은 import()가 Promise 대상 을 되 돌려 주기 때문에 Bundle 구성 요소 의 코드 도 상응 하 게 조정 해 야 합 니 다.
    
    export default class Bundle extends Component {
      constructor(props) {
        super(props);
        this.state = {
          mod: null
        };
      }
    
      componentWillMount() {
        this.load(this.props)
      }
    
      componentWillReceiveProps(nextProps) {
        if (nextProps.load !== this.props.load) {
          this.load(nextProps)
        }
      }
    
      load(props) {
        this.setState({
          mod: null
        });
        //    ,  Promise  ; mod.default    
        props.load().then((mod) => {
          this.setState({
            mod: mod.default ? mod.default : mod
          });
        });
      }
    
      render() {
        return this.state.mod ? this.props.children(this.state.mod) : null;
      }
    }
    
    
    경로 부분 에 변화 가 없습니다.
    
    <Route path="/chat" component={Chat}/>
    
    이 때 npm run start 를 실행 하면 최초의 페이지 를 불 러 올 때 불 러 온 자원 을 다음 과 같이 볼 수 있 습 니 다.

    /chat 경 로 를 터치 하면 볼 수 있 습 니 다.

    2.chunk.js 라 는 js 파일 을 동적 으로 불 러 왔 습 니 다.이 파일 을 열 어 보면 이것 이 바로 우리 가 방금 동적 import()에 들 어 온 모듈 임 을 알 수 있 습 니 다.
    물론 import()를 사용 하 는 것 외 에 도 require.ensure()를 사용 하여 모듈 의 비동기 로드 를 할 수 있 습 니 다.관련 예시 코드 는 다음 과 같다.
    
    const Chat = (props) => (
      <Bundle load={(cb) => {
        require.ensure([], require => {
          cb(require('./component/chat'));
        });
      }}>
      {(Chat) => <Chat {...props}/>}
     </Bundle>
    );
    
    export default class Bundle extends Component {
      constructor(props) {
        super(props);
        this.state = {
          mod: null
        };
      }
    
      load = props => {
        this.setState({
          mod: null
        });
        props.load(mod => {
          this.setState({
            mod: mod ? mod : null
          });
        });
      }
    
      componentWillMount() {
        this.load(this.props);
      }
    
      render() {
        return this.state.mod ? this.props.children(this.state.mod) : null
      }
    }
    
    
    또한 웹 팩 config 를 직접 사용 하면 다음 과 같은 설정 을 할 수 있 습 니 다.
    
    output: {
      // The build folder.
      path: paths.appBuild,
      // There will be one main bundle, and one file per asynchronous chunk.
      filename: 'static/js/[name].[chunkhash:8].js',
      chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
     },
    
    끝나다
    코드 분할 은 한 페이지 응용 에서 흔히 볼 수 있 고 한 페이지 응용 의 성능 과 체험 을 향상 시 키 는 데 도움 이 된다.저 희 는 첫 번 째 방문 응용 을 통 해 필요 하지 않 은 모듈 을 분리 하고 scipt 태그 동적 로드 원 리 를 통 해 효과 적 인 코드 분 리 를 실현 할 수 있 습 니 다.실제 항목 에 서 는 웹 팩 의 import(),require.ensure()또는 일부 loader(예 를 들 어 Bundle Loader)를 사용 하여 코드 분할 과 구성 요 소 를 필요 에 따라 불 러 옵 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기