Loader Interface

로더는 함수를 내보내는 자바스크립트 모듈입니다. 로드 러너는 이 함수를 호출하여 이전 로더 혹은 리소스 파일의 결과를 전달합니다. 함수의 this 컨텍스트는 webpack과 로드 러너에 의해 채워지며, 로더가 호출스타일을 비동기로 변경하거나 쿼리 매개변수를 가져올 수 있도록 하는 몇가지 유용한 메소드를 제공합니다.

첫 번째 로더에는 리소스 파일의 내용이라는 하나의 인수를 넘깁니다. 컴파일러는 마지막 로더의 결과를 기대합니다. 결과는 모듈의 자바스크립트 소스 코드를 나타내는 String 또는 Buffer(문자열로 변환됨)여야 합니다. 선택적 소스맵 결과(JSON 객체로)도 전달할 수 있습니다.

단일 결과는 동기 모드에서 반환될 수 있습니다. 여러 결과의 경우 this.callback()을 호출해야 합니다. 비동기 모드에서는 로드 러너가 비동기 결과를 기다려야 함을 나타내기 위해 this.async()를 호출해야 합니다. 이는 this.callback()을 반환합니다. 그 후 로더는 undefined를 반환하고 해당 콜백을 호출해야 합니다.

/**
 *
 * @param {string|Buffer} [content] 리소스 파일의 내용
 * @param {object} [map] https://github.com/mozilla/source-map 에서 사용할 수 있는 소스맵 데이터
 * @param {any} [meta] 메타 데이터, 무엇이든 될 수 있습니다
 */
function webpackLoader(content, map, meta) {
  // webpack 로더의 코드
}

Examples

다음 섹션에서는 다양한 유형의 로더에 대한 몇 가지 기본 예를 제공합니다. mapmeta 매개변수는 선택 사항입니다. 아래의 this.callback을 참고하세요.

Synchronous Loaders

return 또는 this.callback을 사용하여 변환된 content를 동기적으로 반환할 수 있습니다.

sync-loader.js

module.exports = function (content, map, meta) {
  return someSyncOperation(content);
};

this.callback 메소드는 content만 사용하는 것이 아니라 여러 인수를 전달할 수 있으므로 더 유연합니다.

sync-loader-with-multiple-results.js

module.exports = function (content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // callback() 함수를 호출하면 항상 undefined를 반환합니다.
};

Asynchronous Loaders

비동기 로더의 경우 this.asynccallback 함수를 가져오기 위해 사용됩니다.

async-loader.js

module.exports = function (content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function (err, result) {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
};

async-loader-with-multiple-results.js

module.exports = function (content, map, meta) {
  var callback = this.async();
  someAsyncOperation(content, function (err, result, sourceMaps, meta) {
    if (err) return callback(err);
    callback(null, result, sourceMaps, meta);
  });
};

"Raw" Loader

기본적으로 리소스 파일은 UTF-8 문자열로 변환되어 로더에 전달됩니다. raw 플래그를 true로 설정하면 로더가 원시 Buffer를 받게 됩니다. 모든 로더는 결과를 String 혹은 Buffer로 전달할 수 있습니다. 컴파일러는 로더 사이에서 변환되어 작동됩니다.

raw-loader.js

module.exports = function (content) {
  assert(content instanceof Buffer);
  return someSyncOperation(content);
  // 반환 값도 `Buffer`가 될 수 있습니다.
  // 로더가 "raw"가 아닌 경우에도 허용됩니다.
};
module.exports.raw = true;

Pitching Loader

로더는 항상 오른쪽에서 왼쪽으로 호출됩니다. 로더가 요청 뒤의 메타 데이터에만 관심을 두고 이전 로더의 결과를 무시할 수 있는 경우가 있습니다. 로더의 pitch 메소드는 로더가 실제로 실행(오른쪽에서 왼쪽으로)되기 전에 왼쪽에서 오른쪽으로 호출됩니다.

다음과 같은 use 설정을 살펴보겠습니다.

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        use: ['a-loader', 'b-loader', 'c-loader'],
      },
    ],
  },
};

다음 단계들이 발생합니다.

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- 요청된 모듈이 의존성으로 선택됩니다
    |- c-loader 정상 실행
  |- b-loader 정상 실행
|- a-loader 정상 실행

그렇다면 로더가 "pitching" 단계를 이용하는 이유는 무엇일까요?

첫째, pitch 메소드에 전달된 data는 실행 단계에서도 this.data 아래에 노출되며 실행 주기의 초기부터 정보를 캡처하고 공유하는 데 유용할 수 있습니다.

module.exports = function (content) {
  return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

둘째, 로더가 pitch 방식으로 결과를 전달하면 프로세스가 돌아가 나머지 로더를 건너뜁니다. 아래는 위의 예시에서 b-loaderpitch 메소드가 무언가를 반환했을 때의 경우를 살펴봅니다.

module.exports = function (content) {
  return someSyncOperation(content);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return (
      'module.exports = require(' +
      JSON.stringify('-!' + remainingRequest) +
      ');'
    );
  }
};

위의 단계는 다음과 같이 단축됩니다.

|- a-loader `pitch`
  |- b-loader는 `pitch`를 통해 모듈을 리턴합니다
|- a-loader 정상 실행

The Loader Context

로더 컨텍스트는 this 속성에 할당된 로더 내부에서 사용할 수 있는 속성을 나타냅니다.

Example for the loader context

다음 예제가 주어지면 require 호출이 사용됩니다.

/abc/file.js의 경우

require('./loader1?xyz!loader2!./resource?rrr');

this.addContextDependency

addContextDependency(directory: string)

로더 결과의 의존성으로 디렉터리를 추가합니다.

this.addDependency

addDependency(file: string)
dependency(file: string) // 단축

기존 파일을 감시할 수 있게 만들기 위해 로더 결과의 의존성으로 파일을 추가합니다. 예를 들어, sass-loader, less-loader는 가져온 css 파일이 변경될 때마다 이를 사용하여 재컴파일합니다.

this.addMissingDependency

addMissingDependency(file: string)

존재하지 않는 파일을 감시할 수 있게 만들기 위해 로더 결과의 의존성으로 파일을 추가합니다. addDependency와 유사하지만 감시자가 올바르게 연결되기 전에 컴파일하는 동안 파일 생성을 처리합니다.

this.async

로더가 비동기로 콜백할 예정임을 loader-runner에 알립니다. this.callback을 반환합니다.

this.cacheable

아래는 캐시 가능 여부를 플래그로 설정하는 함수입니다.

cacheable(flag = true: boolean)

기본적으로 로더 결과는 캐시 가능한 것으로 플래그가 지정됩니다. 로더의 결과를 캐시할 수 없도록 하려면 false를 전달해 이 메소드를 호출하세요.

캐시 가능한 로더는 입력과 의존성이 변경되지 않는 한 변하지 않는 결과를 가져야 합니다. 이는 로더가 this.addDependency로 지정된 것 이외의 의존성을 갖지 않아야 함을 의미합니다.

this.callback

여러 결과를 반환하기 위해 동기식 또는 비동기식으로 호출할 수 있는 함수입니다. 예상되는 인수는 다음과 같습니다.

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);
  1. 첫 번째 인수는 반드시 Error 또는 null이어야 합니다.
  2. 두 번째 인수는 string 또는 Buffer입니다.
  3. 세 번째 인수는 선택적입니다. 세 번째 인수는 이 모듈로 파싱될 수 있는 소스맵이어야 합니다.
  4. 네 번째 인수도 선택적입니다. webpack이 무시하는 네 번째 옵션은 무엇이든 될 수 있습니다.(예: 일부 메타 데이터)

이 함수가 호출되는 경우 로더의 모호한 결과를 피하고자 undefined를 반환해야 합니다.

this.clearDependencies

clearDependencies();

로더 결과의 모든 의존성을 제거합니다. 이는 초기 의존성과 다른 로더의 의존성을 포함합니다. pitch 사용을 고려하세요.

this.context

모듈의 디렉터리. 다른 것을 해석하기 위한 컨텍스트로 사용할 수 있습니다.

이 예제에서 resource.js가 이 디렉터리에 있기 때문에 /abc가 됩니다.

this.data

로더의 pitch와 정상 실행 단계 간에 공유되는 데이터 객체입니다.

this.emitError

emitError(error: Error)

출력에도 표시될 수 있는 오류를 내보냅니다.

ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Error (from ./src/loader.js):
Here is an Error!
 @ ./src/index.js 1:0-25

this.emitFile

emitFile(name: string, content: Buffer|string, sourceMap: {...})

파일을 내보냅니다. 이는 webpack에 따라 다릅니다.

this.emitWarning

emitWarning(warning: Error)

다음과 같이 출력에 표시될 경고를 내보냅니다.

WARNING in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Warning (from ./src/loader.js):
Here is a Warning!
 @ ./src/index.js 1:0-25

this.environment

생성된 런타임 코드에서 어떤 종류의 ES 기능을 사용할 수 있는지 확인하세요.

예시:

{
  // 화살표 함수('() => { ... }')를 지원합니다.
  "arrowFunction": true,
  // BigInt를 문자 그대로 지원합니다(예를 들어, 123n).
  "bigIntLiteral": false,
  // const 및 let 변수 선언을 지원합니다.
  "const": true,
  // destructuring을 지원합니다('{ a, b } = obj' 처럼 사용합니다).
  "destructuring": true,
  // EcmaScript 모듈을 가져오는 비동기 import() 함수를 지원합니다.
  "dynamicImport": false,
  // 현재 웹 대상에 대해서만 작업자를 만들 때 async import()를 지원합니다.
  "dynamicImportInWorker": false,
  // 'for of' 반복자를 지원합니다('for (const x of array) { ... }' 처럼 사용합니다).
  "forOf": true,
  // 'globalThis'를 지원합니다.
  "globalThis": true,
  // ECMAScript 모듈을 가져오는 ECMAScript Module syntax를 지원합니다(import ... from '...' 처럼 사용합니다).
  "module": false,
  // 옵셔널 체이닝('obj?.a' 또는 'obj?.()')을 지원합니다.
  "optionalChaining": true,
  // 템플릿 리터럴을 지원합니다.
  "templateLiteral": true
}

this.fs

compilationinputFileSystem 속성에 접근합니다.

this.getOptions(schema)

주어진 로더 옵션을 추출합니다. 선택적으로 JSON 스키마를 인수로 허용합니다.

this.getResolve

getResolve(options: ResolveOptions): resolve

resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>

this.resolve와 유사한 resolve 함수를 만듭니다.

Webpack resolve 옵션 아래의 모든 옵션이 가능합니다. 설정된 resolve 옵션과 병합됩니다. "..."resolve 옵션에서 값을 확장하기 위해 배열에서 사용할 수 있습니다(예: { extensions: [".sass", "..."] }).

options.dependencyType은 추가 옵션입니다. 이는 우리가 resolve 옵션에서 byDependency를 해석하는 데 사용되는 의존성 타입을 지정할 수 있도록 합니다.

해석 작업의 모든 의존성은 현재 모듈에 대한 의존성으로 자동 추가됩니다.

this.hot

로더용 HMR에 대한 정보입니다.

module.exports = function (source) {
  console.log(this.hot); // --hot 플래그 또는 webpack 설정을 통해 HMR이 활성화된 경우 true입니다.
  return source;
};

this.importModule

5.32.0+

this.importModule(request, options, [callback]): Promise

자식 컴파일러가 빌드 시 요청을 컴파일하고 실행하기 위한 대체 경량 솔루션입니다.

  • request: 모듈을 로드할 요청 문자열입니다
  • options:
    • layer: 이 모듈이 배치/컴파일되는 레이어를 지정합니다
    • publicPath: 빌드된 모듈에 사용되는 공개 경로입니다
  • callback: 모듈의 export 또는 ESM용 네임스페이스 객체를 리턴하는 Node.js 스타일의 선택적 콜백 함수입니다. 콜백을 제공하지 않으면 importModule이 Promise를 리턴합니다.

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /stylesheet\.js$/i,
        use: ['./a-pitching-loader.js'],
        type: 'asset/source', // 로더가 문자열을 리턴하므로 타입을 'asset/source'로 설정합니다
      },
    ],
  },
};

a-pitching-loader.js

exports.pitch = async function (remaining) {
  const result = await this.importModule(
    this.resourcePath + '.webpack[javascript/auto]' + '!=!' + remaining
  );
  return result.default || result;
};

src/stylesheet.js

import { green, red } from './colors.js';
export default `body { background: ${red}; color: ${green}; }`;

src/colors.js

export const red = '#f00';
export const green = '#0f0';

src/index.js

import stylesheet from './stylesheet.js';
// 스타일 시트는 빌드시 문자열 `body { background: #f00; color: #0f0; }`가 됩니다

위 예에서 다음과 같은 사실을 알 수 있습니다

  1. pitching 로더가 존재합니다
  2. 해당 pitching 로더에서 !=! 구문을 사용하여 요청에 대한 matchResource를 설정합니다. 즉, 원본 리소스 대신 module.rules와 일치시키기 위해 this.resourcePath + '.webpack[javascript/auto]'를 사용합니다.
  3. .webpack[javascript/auto].webpack[type] 패턴의 유사 확장이며, 다른 모듈 타입이 지정되지 않은 경우 기본 모듈 타입을 지정하는 데 사용합니다. 일반적으로 !=! 구문과 함께 사용됩니다.

위의 예는 단순화된 예제이므로 webpack 저장소에서 전체 예제를 확인할 수 있습니다.

this.loaderIndex

현재 로더의 로더 배열에 있는 인덱스입니다.

예제에서 loader1은 0, loader2은 1입니다.

this.loadModule

loadModule(request: string, callback: function(err, source, sourceMap, module))

모듈에 대한 주어진 요청을 해석하고 설정된 모든 로더를 적용하고 생성된 소스, 소스맵 및 모듈 인스턴스(일반적으로 NormalModule의 인스턴스)로 콜백합니다. 결과를 생성하기 위해 다른 모듈의 소스 코드를 알아야 하는 경우 이 기능을 사용합니다.

로더 컨텍스트의 this.loadModule은 기본적으로 CommonJS 해석 규칙을 사용합니다. 'esm', 'commonjs' 또는 사용자 정의와 같은 다른 해석을 사용하기 전에 적절한 dependencyType과 함께 this.getResolve를 사용하세요.

this.loaders

모든 로더의 배열입니다. pitch 단계에서 쓸 수 있습니다.

loaders = [{request: string, path: string, query: string, module: function}]

예제의 내용입니다.

[
  {
    request: '/abc/loader1.js?xyz',
    path: '/abc/loader1.js',
    query: '?xyz',
    module: [Function],
  },
  {
    request: '/abc/node_modules/loader2/index.js',
    path: '/abc/node_modules/loader2/index.js',
    query: '',
    module: [Function],
  },
];

this.mode

어떤 mode webpack이 실행되고 있는지 읽습니다.

가능한 값은 'production', 'development', 'none'입니다.

this.query

  1. 로더가 options 객체로 설정된 경우 해당 객체를 가리킵니다.
  2. 로더에 options이 없지만 쿼리 문자열로 호출된 경우 ?로 시작하는 문자열이 됩니다.

this.request

해석된 요청 문자열입니다.

예제'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'

this.resolve

resolve(context: string, request: string, callback: function(err, result: string))

require 표현식과 같은 요청을 resolve(해석)합니다.

  • context는 디렉터리의 절대 경로여야 합니다. 이 디렉터리는 해석을 위한 시작 위치로 사용됩니다.
  • request는 해석될 요청입니다. 일반적으로 ./relative와 같은 상대 요청이나 module/path와 같은 모듈이 요청되지만 /some/path와 같은 절대 경로도 요청으로 가능합니다.
  • callback은 resolve된 경로를 제공하는 일반 Node.js 스타일 콜백 함수입니다.

resolve 작업의 모든 의존성은 현재 모듈에 대한 의존성으로 자동 추가됩니다.

this.resource

쿼리를 포함한 요청의 리소스 부분입니다.

예제'/abc/resource.js?rrr'

this.resourcePath

리소스 파일입니다.

예제'/abc/resource.js'

this.resourceQuery

리소스의 쿼리입니다.

예제'?rrr'

this.rootContext

Webpack 4부터 기존의 this.options.contextthis.rootContext로 제공됩니다.

this.sourceMap

소스맵을 생성해야 하는지 여부를 알려줍니다. 소스맵을 생성하는 것은 비용이 많이 드는 작업이므로 소스맵이 실제로 요청되었는지 확인해야 합니다.

this.target

컴파일 대상입니다. 설정 옵션에서 전달됩니다.

예: 'web', 'node'

this.utils

5.27.0+

다음 유틸리티에 접근합니다.

  • absolutify: 가능한 경우 절대 경로를 사용하여 새 요청 문자열을 리턴합니다.
  • contextify: 가능한 경우 절대 경로를 피하는 새 요청 문자열을 리턴합니다.
  • createHash: 제공된 해시 함수에서 새 Hash 객체를 반환합니다.

my-sync-loader.js

module.exports = function (content) {
  this.utils.contextify(
    this.context,
    this.utils.absolutify(this.context, './index.js')
  );
  this.utils.absolutify(this.context, this.resourcePath);
  const mainHash = this.utils.createHash(
    this._compilation.outputOptions.hashFunction
  );
  mainHash.update(content);
  mainHash.digest('hex');
  // …
  return content;
};

this.version

로더 API 버전입니다. 현재는 2버전입니다. 이는 이전 버전과의 호환성을 제공하는 데 유용합니다. 버전을 사용하여 커스텀 동작 또는 주요 변경 사항에 대한 대체를 지정할 수 있습니다.

this.webpack

이 boolean은 webpack에 의해 컴파일될 때 true로 설정됩니다.

Webpack specific properties

로더 인터페이스는 모든 모듈 관련 정보를 제공합니다. 그러나 드문 경우로 컴파일러 API 자체에 접근해야 할 수도 있습니다.

그러므로 최후의 수단으로만 사용해야 합니다. 이 속성들을 사용하면 로더의 범용성이 떨어집니다.

this._compilation

Webpack의 현재 Compilation 객체에 접근합니다.

this._compiler

Webpack의 현재 Compiler 객체에 접근합니다.

Deprecated context properties

this.debug

Boolean 플래그입니다. 디버그 모드일 때 설정됩니다.

this.inputValue

마지막 로더에서 전달되었습니다. 입력 인수를 모듈로 실행하려는 경우 이 변수를 바로가기(성능용)로 읽는 것을 고려하세요.

this.minimize

결과가 최소화 되어야 하는지 여부를 알려줍니다.

this.value

다음 로더에 값을 전달합니다. 모듈로 실행했을 때 결과가 무엇을 내보내는지 알고 있다면 여기에서 이 값을 설정(유일한 배열 요소로)하십시오.

this._module

로드되는 모듈 객체에 대한 hacky 접근.

Error Reporting

다음을 통해 로더 내부에서 오류를 보고할 수 있습니다.

  • this.emitError를 사용합니다. 모듈의 컴파일을 중단하지 않고 오류를 보고합니다.
  • throw(혹은 기타 잡히지 않는 예외)를 사용합니다. 로더가 실행되는 동안 오류가 발생하면 현재 모듈 컴파일이 실패합니다.
  • callback(비동기 모드에서)를 사용합니다. 콜백에 오류를 전달하면 모듈 컴파일 실패도 발생합니다.

예시:

./src/index.js

require('./loader!./lib');

로더에서 오류가 발생하는 경우입니다.

./src/loader.js

module.exports = function (source) {
  throw new Error('This is a Fatal Error!');
};

또는 비동기 모드에서 콜백에 오류를 전달하는 경우입니다.

./src/loader.js

module.exports = function (source) {
  const callback = this.async();
  //...
  callback(new Error('This is a Fatal Error!'), source);
};

모듈은 다음과 같이 번들로 제공됩니다.

/***/ "./src/loader.js!./src/lib.js":
/*!************************************!*\
  !*** ./src/loader.js!./src/lib.js ***!
  \************************************/
/*! no static exports found */
/***/ (function(module, exports) {

throw new Error("Module build failed (from ./src/loader.js):\nError: This is a Fatal Error!\n    at Object.module.exports (/workspace/src/loader.js:3:9)");

/***/ })

그후 다음 빌드 출력에도 오류가 표시됩니다(this.emitError와 유사합니다).

ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module build failed (from ./src/loader.js):
Error: This is a Fatal Error!
    at Object.module.exports (/workspace/src/loader.js:2:9)
 @ ./src/index.js 1:0-25

아래에서 볼 수 있듯이 오류 메시지뿐만 아니라 관련 로더 및 모듈에 대한 세부 정보도 표시됩니다.

  • 모듈 경로: ERROR in ./src/lib.js
  • 요청 문자열: (./src/loader.js!./src/lib.js)
  • 로더 경로: (from ./src/loader.js)
  • 호출자 경로: @ ./src/index.js 1:0-25

Inline matchResource

새로운 인라인 요청 구문이 webpack v4에 도입되었습니다. 요청에 접두사 <match-resource>!=!를 붙이면 이 요청에 대한 matchResource가 설정됩니다.

matchResource가 설정되면 원래 리소스 대신 module.rules와 일치하는 데 사용됩니다. 이는 리소스에 추가 로더를 적용해야 하거나 모듈 유형을 변경해야 하는 경우에 유용할 수 있습니다. 또한 통계에 표시되며 splitChunks에서 test Rule.issuer를 일치시키는 데 사용됩니다.

예제:

file.js

/* STYLE: body { background: red; } */
console.log('yep');

로더는 파일을 다음 파일로 변환하고 matchResource를 사용하여 사용자 지정 CSS 처리 규칙을 적용할 수 있습니다.

file.js (로더에 의해 변환되었습니다)

import './file.js.css!=!extract-style-loader/getStyles!./file.js';
console.log('yep');

이는 extract-style-loader/getStyles!./file.js에 종속성을 추가하고 결과를 file.js.css로 처리합니다. module.rules에는 /\.css$/와 일치하는 규칙이 있고 이 종속성에 적용되기 때문입니다.

로더는 다음과 같이 보일 수 있습니다.

extract-style-loader/index.js

const getStylesLoader = require.resolve('./getStyles');

module.exports = function (source) {
  if (STYLES_REGEXP.test(source)) {
    source = source.replace(STYLES_REGEXP, '');
    return `import ${JSON.stringify(
      this.utils.contextify(
        this.context || this.rootContext,
        `${this.resource}.css!=!${getStylesLoader}!${this.remainingRequest}`
      )
    )};${source}`;
  }
  return source;
};

extract-style-loader/getStyles.js

module.exports = function (source) {
  const match = source.match(STYLES_REGEXP);
  return match[0];
};

Logging

로깅 API는 webpack 4.37 릴리즈부터 사용할 수 있습니다. stats 설정에서 logging이 활성화되거나 infrastructure 로깅이 활성화되면 로더가 메세지를 기록할 수 있으며 이 메세지는 해당 로거 형식(stats, infrastructure)으로 출력됩니다.

  • 로더는 this.getLogger()를 사용하는 것을 선호해야 합니다. 이는 로더 경로와 처리된 파일을 포함한 compilation.getLogger()의 단축어입니다. 이러한 종류의 로깅은 stats에 저장되고 그에 따라 형식이 지정됩니다. webpack 사용자가 필터링하고 내보낼 수 있습니다.
  • 로더는 this.getLogger('name')를 사용하여 자식 이름을 가진 독립 로거를 얻을 수 있습니다. 로더 경로 및 처리된 파일은 계속 추가됩니다.
  • 로더는 getLogger 방식을 지원하지 않는 이전 webpack 버전이 사용되는 경우 대체 코드를 제공하기 위해 this.getLogger ? this.getLogger() : console를 감지하는 특정한 대체 코드를 사용할 수 있습니다.

12 Contributors

TheLarkInnjhnnstbroadleybyzyksokraEugeneHlushkojantimonsuperburritowizardofhogwartssnitin315chenxsanjamesgeorge007

Translators