[npm] esbuild-wasm #2

dev stefanCho·2021년 8월 31일
0

npm

목록 보기
2/8

#1 에서 만들었던 esbuild.onResolve와 esbuild.onLoad에 대해 refactoring을 해본다.

우선 기존에 unpkg-path-plugin에 있던 onLoad를 fetch-plugin로 나누었다.

App.tsx

plugins에 plugin을 추가했다. esbuild 공식페이지에 가보면 Plugin에 대한 내용이 있다.

// 생략 ...
    const result = await ref.current.build({
      entryPoints: ["index.js"],
      bundle: true,
      write: false,
      plugins: [unpkgPathPlugin(), fetchPlugin(input)],
      define: {
        "process.env.NODE_ENV": '"production"',
        global: "window", // replace global to window
      },
    });
// ... 생략

unpkg-path-plugin.ts

onResolve는 filter에 pattern에 따라 code split 하였다.

import * as esbuild from 'esbuild-wasm';
 
export const unpkgPathPlugin = () => {
  return {
    name: 'unpkg-path-plugin',
    setup(build: esbuild.PluginBuild) {
      // try to figure out where the index.js file is stored
      build.onResolve({ filter: /^index\.js$/ }, (args: any) => {
        return { path: args.path, namespace: "a" };
      });

      build.onResolve({ filter: /\.+\// }, (args: any) => {
        return {
          path: new URL(args.path, "https://unpkg.com" + args.resolveDir + "/")
            .href,
          namespace: "a",
        };
      });

      build.onResolve({ filter: /.*/ }, (args: any) => {
        return {
          path: `https://unpkg.com/${args.path}`,
          namespace: "a",
        };
      });
    },
  };
};

fetch-plugin.ts

#1에서 없던 localforage가 추가 되었는데, localforage는 indexedDB를 편하게 사용하기 위한 라이브러리이다.

indexedDB를 사용하는 이유는 캐싱을 하기 위함이다. react와 같은 여러가지 라이브러리들은 dependency가 있다. 그래서 모듈 (혹은 css 등등)을 하나 부르면 그거 연결된 여러가지 것들을 한번에 요청한다. (모듈 몇개만 추가해도 수백개의 요청이 발생한다.)

그래서 간단한 cli를 수정하고 저장을 해도 수백개의 요청이 지속적으로 발생한다. localforage로 한번 요청한 것은 indexedDB에 캐싱하고, getItem으로 해당 key value pair가 있는 경우에는, 저장된 값을 return 하도록 한다.

import * as esbuild from 'esbuild-wasm';
import axios from 'axios';
import localforage from 'localforage';

const fileCache = localforage.createInstance({
  name: 'filecache'
})

export const fetchPlugin = (inputCode: string) => {
  return {
    name: "fetch-plugin",
    setup(build: esbuild.PluginBuild) {
      // attempt to load up the index.js file
      build.onLoad({ filter: /^index\.js$/ }, async (args: any) => {
        // Check to see if we have already fetched this file
        // and if it is in the cache
        const cachedResult = await fileCache.getItem<esbuild.OnLoadResult>(
          args.path
        );

        // if it is, return it immediately
        if (cachedResult) {
          return cachedResult;
        }
        return {
          loader: "jsx",
          contents: inputCode,
        };
      });

      build.onLoad({ filter: /.*/ }, async (args: any) => {
        // Check to see if we have already fetched this file
        // and if it is in the cache
        const cachedResult = await fileCache.getItem<esbuild.OnLoadResult>(
          args.path
        ); // getItem할 때, 내부 object 형태를 모르므로, esbuild.OnLoadResult로 타입정의를 해주었다. OnLoadResult의 interface는 가장아래에 Ref 부분을 참고하자.

        // if it is, return it immediately
        if (cachedResult) {
          return cachedResult;
        }
      });

      build.onLoad({ filter: /.css$/ }, async (args: any) => {
        const { data, request } = await axios.get(args.path);
        const escaped = data
          .replace(/\n/g, "")
          .replace(/"/g, '\\"')
          .replace(/'/g, "\\'");
        const contents = `
          const style = document.createElement('style');
          style.innerText = '${escaped}';
          document.head.appendChild(style);
        `;
        const result: esbuild.OnLoadResult = {
          loader: "jsx",
          contents,
          resolveDir: new URL("./", request.responseURL).pathname, // nested import를 처리하기 위해서 resolveDir를 사용한다. (예를 들면 src/index.js에서 src/components/test.js를 import한다면, src/ 라는 path를 유지해야한다.)
        };
        // store response in cache
        await fileCache.setItem(args.path, result);
        return result;
      });

      build.onLoad({ filter: /.*$/ }, async (args: any) => {
        const { data, request } = await axios.get(args.path);
        const result: esbuild.OnLoadResult = {
          loader: "jsx",
          contents: data,
          resolveDir: new URL("./", request.responseURL).pathname, // nested import를 처리하기 위해서 resolveDir를 사용한다. (예를 들면 src/index.js에서 src/components/test.js를 import한다면, src/ 라는 path를 유지해야한다.)
        };
        // store response in cache
        await fileCache.setItem(args.path, result);
        return result;
      });
    },
  };
}



Ref

OnLoadResult의 interface

profile
Front-end Developer

0개의 댓글