웹 패키지 플러그인 메커니즘 찾기

6326 단어 webpack
웹 패키지는 사람을 기쁘게 하고 걱정스럽게 하며 기능이 강하지만 일정한 학습 비용이 필요하다고 할 수 있다.웹 패키지 플러그인 메커니즘을 찾기 전에 먼저 재미있는 것을 알아야 한다. 웹 패키지 플러그인 메커니즘은 전체 웹 패키지 도구의 골격이고 웹 패키지 자체도 이 플러그인 메커니즘을 이용하여 구축된 것이다.따라서 웹 패키지 플러그인 메커니즘을 깊이 있게 인식한 후에 프로젝트의 관련 최적화를 진행하면 큰 도움이 될 것이다.
웹팩 플러그인
먼저 웹 패키지 플러그인의 프로젝트에서의 활용을 살펴보겠습니다.
const MyPlugin = require('myplugin')
const webpack = require('webpack')

webpack({
  ...,
  plugins: [new MyPlugin()]
  ...,
})

그러면 어떤 조건에 부합되면 웹 패키지 플러그인으로 쓸 수 있을까요?일반적으로 웹 패키지 플러그인은 다음과 같은 특징을 가지고 있다.
  • 독립된 JS 모듈, 상응하는 함수 노출
  • 함수 원형의 apply 방법은compiler 대상에 주입
  • compiler 대상에 해당하는 웹 패키지 이벤트 갈고리 마운트
  • 이벤트 갈고리의 리셋 함수에서 컴파일된compilation 대상을 얻을 수 있고, 비동기 갈고리라면 해당하는callback
  • 을 얻을 수 있다.
    다음 코드는 다음과 같습니다.
    function MyPlugin(options) {}
    // 2.       apply       compiler   
    MyPlugin.prototype.apply = function(compiler) {
      // 3.compiler           webpack      4.                  compilation   
      compiler.plugin('emit', (compilation, callback) => {
        ...
      })
    }
    // 1.    JS   ,       
    module.exports = MyPlugin

    이렇게 웹 패키지 플러그인의 기본적인 윤곽이 그려졌는데, 이때 의문점은 몇 가지가 있다.
  • 의문1: 함수의 원형에서 왜 apply 방법을 정의합니까?원본 코드를 읽은 후 원본 코드에서 plugin.apply() 플러그인을 호출한 것을 발견했습니다.
  • const webpack = (options, callback) => {
      ...
      for (const plugin of options.plugins) {
        plugin.apply(compiler);
      }
      ...
    }
  • 의문2:compiler 대상은 무엇입니까?
  • 의문3:compiler 대상의 사건 갈고리는 어떻게 됩니까?
  • 의문4: 사건 갈고리의 리셋 함수에서 얻을 수 있는compilation 대상은 또 무엇입니까?

  • 이 의문들도 본문의 단서이니 하나씩 탐색해 봅시다.
    compiler 객체
    compiler는 웹 패키지의 편집기 대상입니다. 웹 패키지를 호출할 때 자동으로 compiler 대상을 초기화합니다. 원본은 다음과 같습니다.
    // webpack/lib/webpack.js
    const Compiler = require("./Compiler")
    
    const webpack = (options, callback) => {
      ...
      options = new WebpackOptionsDefaulter().process(options) //     webpack      
      let compiler = new Compiler(options.context)             //     compiler   ,   options.context   process.cwd()
      compiler.options = options                               //   compiler        
      new NodeEnvironmentPlugin().apply(compiler)              //   compiler    Node       
      for (const plugin of options.plugins) {
        plugin.apply(compiler);
      }
      ...
    }

    마지막으로,compiler 대상에는 모든 웹 패키지가 설정할 수 있는 내용이 포함되어 있으며, 플러그인을 개발할 때,compiler 대상에서 웹 패키지의 주 환경과 관련된 모든 내용을 얻을 수 있습니다.
    compilation 객체
    compilation 대상은 단일한 버전 구축과 자원 생성을 대표합니다.웹 패키지를 실행할 때 파일의 변화가 감지될 때마다 새로운 컴파일러가 생성되어 새로운 컴파일러 자원을 생성합니다.하나의 컴파일 대상은 현재의 모듈 자원, 컴파일 생성 자원, 변화된 파일, 그리고 추적에 의존하는 상태 정보를 나타낸다.
    원본과 결합하여 위의 말을 이해하면 먼저 웹팩은 실행할 때마다 호출compiler.run()(원본의 위치)를 하고 onCompiled 함수에서 전송된compilation 파라미터를 추적하면compilation이 구조 함수Compilation에서 온 것을 발견할 수 있다.
    // webpack/lib/Compiler.js
    const Compilation = require("./Compilation");
    
    newCompilation(params) {
      const compilation = new Compilation(this);
      ...
      return compilation;
    }

    언급할 수 없는 tapable 라이브러리
    compiler 대상과compilation 대상을 다시 소개한 후에는tapable 라이브러리를 언급하지 않을 수 없습니다. 이 라이브러리는 사건과 관련된pub/sub의 모든 방법을 폭로했습니다.또한 함수 Compiler와 함수 Compilation은 Tapable에서 상속됩니다.
    이벤트 훅
    이벤트 갈고리는 사실 MVVM 프레임워크와 유사한 생명주기 함수로 특정 단계에서 특수한 논리 처리를 할 수 있다.흔히 볼 수 있는 이벤트 갈고리를 이해하는 것은 웹 패키지 플러그인을 쓰는 선행 조건이다. 다음은 흔히 볼 수 있는 이벤트 갈고리와 작용을 열거한다.
    갈고리
    역할
    매개 변수
    타입
    after-plugins
    초기화 플러그인 설정 후
    compiler
    sync
    after-resolvers
    Resolvers 설정 후
    compiler
    sync
    run
    레코드를 읽기 전에
    compiler
    async
    compile
    새 compilation을 만들기 전에
    compilationParams
    sync
    compilation
    compilation 생성 완료
    compilation
    sync
    emit
    자원을 생성하고 디렉터리로 출력하기 전에
    compilation
    async
    after-emit
    자원을 생성하고 디렉터리로 출력한 후
    compilation
    async
    done
    컴파일 완료
    stats
    sync
    공식 문서 매뉴얼을 완전하게 참고하고 관련 원본 코드를 찾아보면 각 이벤트 갈고리의 정의를 뚜렷하게 볼 수 있다.
    플러그인 프로세스 분석
    emit 갈고리를 예로 들면 다음과 같이 플러그인 호출 코드를 분석합니다.
    compiler.plugin('emit', (compilation, callback) => {
      //                    
    })

    여기에서 호출된plugin 함수는 위에서 언급한tapable 라이브러리에서 유래한 것으로, 최종 호출 창고는 hook을 가리킨다.tapAsync () 는 Event Emitter의 on과 유사하며 소스는 다음과 같습니다.
    // Tapable.js
    options => {
      ...
      if(hook !== undefined) {
        const tapOpt = {
          name: options.fn.name || "unnamed compat plugin",
          stage: options.stage || 0
        };
        if(options.async)
          hook.tapAsync(tapOpt, options.fn); //                
        else
          hook.tap(tapOpt, options.fn);
        return true;
      }
    };

    주입은 반드시 촉발하는 곳이 있고 원본에서callAsync 방법을 통해 주입하기 전에 주입한 비동기 이벤트를 촉발한다.callAsync는 Event Emitter의emit와 유사하며 관련 원본은 다음과 같다.
    this.hooks.emit.callAsync(compilation, err => {
        if (err) return callback(err);
        outputPath = compilation.getPath(this.outputPath);
        this.outputFileSystem.mkdirp(outputPath, emitFiles);
    });

    일부 깊이 있는 세부 사항은 여기서 전개하지 않고 비교적 큰 프로젝트의 원본 코드를 읽는 두 가지 체험을 말한다.
  • 줄거리를 잡고 읽고 세부 사항을 소홀히 해야 한다.그렇지 않으면 많은 시간을 낭비하고 좌절감을 느낄 수 있다.
  • 디버깅 도구와 결합하여 분석하면 디버깅 도구를 사용하지 않으면 이것저것 고려하기 쉽다.

  • 웹 패키지 플러그인 만들기
    상기 지식점의 분석을 결합하여 자신의 웹 패키지 플러그인을 쓰기 어렵지 않은데 관건은 생각에 있다.프로젝트에서 웹 패키지 각 패키지의 효과적인 사용 상황을 통계하기 위해 fork 웹 패키지-visualizer를 토대로 코드를 업그레이드하여 프로젝트 주소를 지정했습니다.효과는 다음과 같습니다.
    플러그인 핵심 코드는 위에서 언급한emit 갈고리와compiler와compilation 대상을 바탕으로 한다.코드는 다음과 같습니다.class AnalyzeWebpackPlugin { constructor(opts = { filename: 'analyze.html' }) { this.opts = opts } apply(compiler) { const self = this compiler.plugin("emit", function (compilation, callback) { let stats = compilation.getStats().toJson({ chunkModules: true }) // let stringifiedStats = JSON.stringify(stats) // let html = ` AnalyzeWebpackPlugin window.stats = ${stringifiedStats}; ${jsString} ` compilation.assets[`${self.opts.filename}`] = { // source: () => html, size: () => html.length } callback() }) } }
    참고 자료
    진짜 Webpack 플러그인 잘 보세요.
    웹팩 홈페이지

    좋은 웹페이지 즐겨찾기