ESNext #2: 블록 제작

블록과 블록 에디터 환경에서 자유자재로 원하는 기능을 만들고 싶다.

기존에는 페이지를 제작할때 주로 숏코드를 사용했다. 물론 숏코드는 여전히 유용하고 유효하다. 페이지의 모든 내용이 고정되며 페이지 전반에 어떤 특정 기능을 구현해야 할 때, 그리고 그 때 서버 사이드 스크립트가 더 편리할 때는 그렇게 작성하는 것도 편리하다.

그러나 엘레멘터 같은 비주얼 빌더들이 워드프레스 제작 환경에는 사실상 표준이 되어가고 있고, 구텐베르크 또한 마찬가지다. 더구나 엘레멘터는 구텐베르크의 블록또한 가져와 사용할 수 있도록 호환성마저 제공하기도 한다. 아무튼 이제는 블록 에디터를 개발에 활용해야 할 때다.

블록 에디터에 익숙해지기 위해 우선 이 버전의 hello, world! 부터 해 보자. 이것도 이전과 마찬가지로 github 리포지터리에 코드를 커밋해 두었다.

블록 등록 절차

커스텀 블록을 사용하려면 크게 두 가지 과정을 거치면 된다. 코어에 블록을 등록한다. 그리고 그 블록이 어떻게 동작할지 정의한다. 각각의 역할은 PHP와 JS 에서 나눠 구현된다.

대략 API 함수를 보면 PHP 측에서 등록은 자바스크립트 의존성, 인큐잉의 순서 같은 올바른 스크립트 삽입을 위한 코드인 것 같다. 반면 JS 는 등록된 커스텀 블록이 에디터에서, 프론트엔드에서 어떤 동작을 취하는지에 대한 동작을 상세하게 지정하는 성격이 강하다. 뭐, 사실 당연한 말인 것 같다.

보통 워드프레스 스크립트를 등록(register), 인큐(enqueue) 처리할 때 ‘init’, ‘wp_enqueue_scripts’, ‘admin_enqueue_scripts’ 세 훅을 사용한다. 한편 블록 에디터를 위한 ‘enqueue_block_editor_assets’ 훅이 추가되었다. 이 훅은 ‘init’ 보다는 뒤에, 그러나 ‘wp_enqueue_scripts’ 보다는 앞에 불릴 것이다. 보통 예제에서는 init을 많이 사용하는데 상황에 따라 적절히 선택하면 될 것 같다.

PHP 사이드에서 블록 등록 절차

  1. wp_register_script()를 호출해 블록을 등록하는 스크립트를 등록 처리한다.
    • 이 때 빌드된 디렉토리 쪽에 *.asset.php 파일이 있을 것이다. 이 파일은 하나의 배열을 리턴한다. 이 배열에는 해당 스크립트이 의존성 목록과 버전 정보가 담겨있다.
    • wp-script가 작성된 코드를 분석해 적절히 의존성을 결정해준 것이다.
    • 버전은 매번 변경되는 코드가 캐싱되지 않도록 하는 목적이 크다.
  2. register_block_type() 함수를 사용해 블록을 코어에 등록시킨다.
    • 첫번째 인자로 쓰는 블록 이름은 소문자로만, 케밥 케이스로, 또 ‘{네임스페이스}/{블록 이름}‘ 식으로 작명하도록 한다. 네임스페이스는 플러그인 고유의 구분자로, 블록 이름은 블록의 구분자로 생각할 수 있다.
    • 두번째 인자에 배열이 들어가는데 ‘editor_script’는 반드시 지정한다. 이 값으로 1단계에서 등록한 스크립트의 핸들을 입력한다.
  3. 추가적으로 번역 파일이 있다면 wp_set_script_translations() 함수를 사용해 등록한다.
    • 번역 파일의 이름은 {텍스트도메인}-{언어 코드}-{핸들}.json 이다. 혹은 {텍스트도메인}-{언어 코드}-{해시}.json 로도 사용할 수 있다.
    • 첫번째 인자는 스크립트 핸들, 두번째는 텍스트 도메인, 세번째는 번역 파일이 있는 디렉토리의 절대경로이다.
    • .json 파일을 생성하는 것이 고역일 수 있다. 그러나 이후 설명할 WP CLI를 사용하면 어렵지 않다.

JS 사이드에서 블록 등록 절차

아주 기본적인 함수를 불러오는 것(import … from …)부터 시작하자.

  • ‘@wordpress/blocks’ 패키지에서 ‘registerBlockType’ 함수
  • ‘@wordpress/i18n’에서 번역을 위한 ‘__’ 함수를 불러온다.

이 두 패키지를 devDependencies에 등록시키자. 그리고 가져온 registerBlockType 함수를 호출하면 된다.

  • 첫번째 인자는 이 블록의 이름이다. PHP의 register_block_type() 함수에서 썼던 것과 동일한 이름, 즉 ‘{네임스페이스}/{블록 이름}‘을 입력해야 한다.
  • 두번째 인자는 이 블록이 어떤 속성을 가지는지 상세하게 정의하는 부분이다. 이 부분은 많은 설명이 필요하며 여기서는 다 다룰 수가 없다. 차후 개발 문서를 자주 방문하면서 숙련도를 쌓아야 할 것 같다.
  • 예제를 위해 사용한 속성은 총 5개인데 아래에서 간단히 설명한다.
    • title: 블록의 제목. 문자열.
    • description: 블록의 상세 설명. 문자열.
    • category: 어떤 분류에 속하는지 지정한다. 텍스트, 미디어, 임베드 등등이 있다. 문자열.
    • edit: 콜백 함수. 에디터 화면에서 어떻게 그려져야 할지를 지정한다.
    • save: 저장된 후 프론트에서 어떻게 그려져야 할지를 결정한다.

안녕, 세상!을 위해 만든 간단한 블록은 아래 코드처럼 처리했다.

registerBlockType('wp-esnext-study/wes02-hello-world', {
    title: __('WP ES Next #2 Hello, World!', 'wp-esnext-study'),
    description: __('"Hello, World!" sample for block editor.', 'wp-esnext-study'),
    category: 'common',
    edit() {
        /* 이 블록이 관리자 편집시 보이는 내용입니다. */
        return (
            <p>Hello, World!</p>
        )
    },
    save() {
        /* 이 블록이 저장될 때 내용입니다. */
        const helloText = __('WP ES Next #2 Hello, World!', 'wp-esnext-study');
        return (
            <p>{helloText}</p>
        )
    }
});

관리자 페이지에서 보면 이렇게 나온다.

이것을 프론트에서 보면 이렇게 나온다.

번역문 처리

번역 파일을 로드하려면 주로 ‘init’ 훅의 콜백에서 다음처럼 코드를 작성한다.

PHP에서 번역문 로드

load_plugin_textdomain( 'wp-esnext-study', false, wp_basename( dirname( __DIR__ ) ) . '/languages' );
  • 첫번째 인자는 번역 텍스트도메인.
  • 둘째는 사용하지 않는다.
  • 셋째는 번역 파일이 있는 곳의 디렉토리. wp-content/plugins 디렉토리로부터 상대 경로이다. 주로 wp_basename( dirname( plugin_dir_path( MAIN_FILE ) ) ) . '/languages' 식으로 입력하곤 한다.

pot 파일 생성

poedit 같은 프로그램을 통해 디렉토리를 분석해 po 파일을 생성하는 방법도 있지만, 이렇게 하면 플러그인의 헤더 부분이 제대로 번역되지 않는다. 플러그인 이름, 제작자 같은 플러그인의 헤더 정보까지 올바르게 번역하려면 WP CLI i18n 커맨드를 이용하는 것이 좋다. 커맨드는 플러그인 루트에서 아래처럼 입력한다.

mkdir languages # languages 디렉토리가 없다면 입력.
wp i18n make-pot . languages/{텍스트도메인}.pot --domain={텍스트도메인}

그러면 languages 디렉토리에 텍스트도메인으로된 .pot 파일이 생성될 것이다. 여기에 JS, PHP에서 사용한 모든 번역문이 담겨있을 것이다.

이 POT 파일을 가져다 languages/{텍스트도메인}-{언어코드}.po 파일을 작성해야 한다. 보통 poedit 같은 프로그램을 이용한다. 내 예제를 보면 languages/wp-esnext-study-ko_KR.po 파일이 생성된 것을 알 수 있다.

poedit을 사용하면 .po 파일과 함께 컴파일된 .mo 파일도 생성할 수 있다. PHP에서는 결국 이 .mo 파일을 이용하여 번역 텍스트를 처리할 것이다.

번역문 JSON 생성

그런데 .mo 파일의 번역은 PHP로 작성한 코드에서만 유효하다. 자바스크립트에서 번역문이 올바르게 처리되려면 .mo 파일이 아닌, .json 파일을 사용해야 한다. 이것은 pot 때와 마찬가지로 WP CLI를 사용하면 보다 편리하게 작업 가능하다.

JSON 생성을 위해 번역을 두 벌 해야 할 필요가 없다. JSON 번역 파일은 .po 파일을 기본으로 하여 작성되기 때문이다. 그러므로 우선 .po 파일의 번역을 끝마친 후에 .json 파일을 생성해야 할 것이다.

명령은 플러그인의 루트 디렉토리에서 아래처럼 실행한다.

wp i18n make-json languages

이 명령을 실행하면 .po 파일로부터 JS 쪽의 번역문만 따로 뽑아내 .json으로 만든다. 이후 .po 파일에서는 JS 쪽 번역문은 사라지게 된다. 이런 파일의 수정을 일어나지 않게 하려면 뒤에 --no-purge 옵션을 더해 주면 된다.

번역 JSON 파일의 해시값

생성된 JSON 파일의 이름을 보면 {텍스트도메인}-{언어}-{해시}.json으로 되어 있다. 워드프레스는 이보다 {텍스트도메인}-{언어}-{핸들}.json을 먼저 읽기는 한다. 그러나 WP CLI i18n make-json 명령으로는 핸들로 된 파일을 만들지 않고 해시로 된 파일을 만든다.

그런데 의문점이 있다. 해시는 어떻게 생성하는가? 그리고 해당 명령으로 만들면 파일은 2개가 생성된다. 이것은 왜 그러는가?

우선 파일이 2개 생성되는 이유를 살펴보자. 파일의 JSON 구조를 보면 “source” 키의 값으로 “build/index.js”, “src/index.js” 각각 두개로 나눠져 있는 것을 알 수 있다. 그 외에 두 파일의 내용은 동일하다.

그리고 src/index.js와 build/index.js 각각의 md5 해시값이 뒤에 붙어 다른 파일이 2개 생성되는 것이다. 이렇게 되는 이유는 pot 파일에서 찾을 수 있다. 아래는 pot 파일의 일부이다.

#: 02-block-editor-hello-world/build/index.js:115
#: 02-block-editor-hello-world/src/index.js:11
msgid "\"Hello, World!\" sample for block editor."
msgstr ""

#은 주석인데, 해당 소스 코드 어디에서 이 문자열이 발견되었는지를 기록한다. js 파일에 대해 src와 build 두 곳에서 문자열을 가지고 오게 된다. 그리고 저 위치는 .json 파일을 만들때 ‘source’ 키에 사용하게 된다. 또한 이 ‘source’ 의 값을 md5 해시로 돌려 보면 해당 파일의 이름으로 된 해시값과 동일한 결과가 나옴을 알 수 있다.

각 md5 해시 값의 결과는 아래와 같다. 만들어진 json 파일의 결과와 비교해 보면 서로 동일하다는 것을 알 수 있다.

  • 02-block-editor-hello-world/src/index.js – 8fd9b2d6decbdc39f7e175586b1ca3a2
  • 02-block-editor-hello-world/build/index.js – e3c4e88d8ce2f5a27f6d3f9da2c228e6

이런 이유로 language 디렉토리는 플러그인 디렉토리 바로 밑에서 작성하고, 번역문은 플러그인 전역에서 처리하는 것이 좋다. 그렇지 않으면 해시값에 대해 따로 처리를 해 주어야 할 것이다.

댓글 남기기