워드프레스 플러그인 개발, 왜 functions.php 가 아니었나 — 남의 코드 위에 기능을 얹는 경계
운영 중인 워드프레스 사이트를 건드리지 않고 로컬 Docker 로 통째로 복제하는 이야기를 쓴 적이 있습니다. 그 글은 환경을 만드는 데까지였고, 정작 그 위에서 워드프레스 플러그인을 어떻게 만들었는지는 쓰지 않았습니다. 안전한 사본을 손에 넣고 나서 처음 부딪힌 질문은 기능을 어떻게 만들 것인가가 아니라 이 코드를 어디에 둘 것인가였습니다.
답을 정하는 데 생각보다 시간이 걸렸고, 정하고 나서도 한 번 더 물어야 했습니다. 그 과정에서 알게 된 것들을 몇 편에 걸쳐 정리해보려 합니다. 이번 편은 그 첫 번째 질문, 코드를 놓을 자리를 고르는 이야기입니다.
검색을 고치는 일이 아니었습니다
어떤 제조사의 부품 카탈로그 사이트에 부품번호 통합검색을 새로 넣어야 했습니다. 기존 스택은 워드프레스 위에 게시판 플러그인과 페이지 빌더, 슬라이더가 얹힌 구성이었고, 영문과 한국어 두 개 사이트를 묶은 멀티사이트였습니다. 검색 기능 자체는 이미 게시판 플러그인이 제공하고 있었습니다.
그런데 검색 결과를 열어보고 나서 문제의 성격이 달라졌습니다. 부품 정보가 데이터가 아니라 통이미지 PNG 한 장이었습니다. 도면과 스펙표가 픽셀로 구워져 있었으니, 텍스트 검색은 개선의 여지가 부족한 게 아니라 원리적으로 성립하지 않는 상태였습니다. 즉 검색을 개선하는 일이 아니라 검색할 수 있는 데이터 자체를 새로 만드는 일이었습니다.
새 데이터를 저장할 곳이 필요하고, 그것을 보여줄 화면이 필요하다는 뜻입니다. 남의 코드가 이미 빽빽하게 자리를 잡고 있는 사이트에 제 코드를 얹어야 했습니다.
워드프레스에서 기능을 넣을 수 있는 자리는 네 군데였습니다
워드프레스에서 코드를 놓을 수 있는 자리는 여러 곳입니다. 처음에는 어느 쪽이든 동작만 하면 된다고 생각했는데, 각 자리를 다음 업데이트가 왔을 때 어떻게 되는가라는 기준으로 놓고 보니 성격이 완전히 달랐습니다.
| 코드를 놓는 자리 | 어떻게 넣는가 | 다음 업데이트 때 |
|---|---|---|
| 테마 파일 직접 수정 | page-xxx.php 를 고칩니다 |
테마 업데이트가 덮어씁니다 |
테마 functions.php |
함수를 추가합니다 | 같습니다. 테마를 바꾸면 기능이 통째로 사라집니다 |
| 벤더 플러그인 fork | 게시판 플러그인 코드를 고칩니다 | 그 플러그인 업데이트가 덮어씁니다. 보안 패치를 못 받게 됩니다 |
| 독립 플러그인 | 새 디렉토리를 만듭니다 | 아무도 건드리지 않습니다 |
앞의 세 자리는 공통점이 있습니다. 전부 남이 소유한 파일 안에 제 코드를 넣는 방식입니다. 그 파일의 주인이 새 버전을 내놓는 순간 제 코드는 사라집니다. 사라지지 않게 하려면 업데이트를 안 받아야 하는데, 벤더 플러그인의 경우 그건 보안 패치를 포기한다는 뜻이기도 합니다.
자식 테마를 떠올릴 수도 있습니다. 자식 테마는 테마 업데이트는 견딥니다. 다만 테마 교체는 견디지 못합니다. 유료 테마를 다른 것으로 바꾸는 결정은 언제든 내려질 수 있고, 그때 기능이 함께 딸려 나갑니다. 방어의 정도가 다를 뿐, 기능이 테마에 종속된다는 사실 자체는 그대로였습니다.
확장은 "고치기"가 아니라 "끼어들기"입니다
표를 그려놓고 한참 들여다보다가, 제가 문제를 잘못 잡고 있었다는 생각이 들었습니다. 저는 계속 어느 파일을 고칠까를 고민하고 있었습니다. 그런데 워드프레스에서 확장은 애초에 고치는 행위가 아니었습니다.
남의 파일을 수정하지 않고, 워드프레스가 정해둔 지점에서 내 코드가 호출되게 만듭니다.
이것이 워드프레스가 상정하고 있는 확장의 방식입니다. 고치기가 아니라 끼어들기입니다. 그리고 그 끼어들기를 담는 그릇이 플러그인입니다. 플러그인은 "기능을 모아두는 폴더"가 아니라, 남의 코드를 건드리지 않겠다는 약속을 구조로 표현한 것에 가깝습니다.
그래서 제가 플러그인을 고른 이유는 그게 정석이라서가 아니었습니다. 게시판 플러그인과 테마가 각자 자기 일정대로 업데이트되어도 제 코드가 영향을 받지 않는 유일한 자리였기 때문입니다. 기술 취향의 문제가 아니라, 누가 그 파일의 주인인가라는 소유권 문제였습니다.
"기능 하나인데 플러그인까지 만들어야 하나요"
이 이야기를 하면 대체로 두 가지 반응이 돌아옵니다. 첫 번째는 기능 하나 넣자고 플러그인을 만드는 건 과하지 않냐는 것입니다.
여기에는 오해가 하나 섞여 있는 것 같습니다. 플러그인의 최소 형태는 헤더 주석이 달린 PHP 파일 한 개입니다. 디렉토리 하나와 파일 하나면 성립합니다.
<?php
/**
* Plugin Name: Catalog Search
* Description: 부품번호 통합검색
* Version: 0.1.0
*/
// 여기서부터 기능을 얹습니다.
파일 경로는 catalog-search/catalog-search.php 하나입니다. 빌드 도구도, 프레임워크도, 설정 파일도 필요하지 않습니다. 진입 비용이 거의 없는데도 플러그인을 무겁게 느끼는 것은, 아마 배포된 상용 플러그인들의 덩치를 먼저 떠올리기 때문일 것입니다.
두 번째 반응은 functions.php 에 넣는 게 더 빠르지 않냐는 것입니다. 이건 사실입니다. 개발은 실제로 더 빠릅니다.
다만 그 속도가 어디서 나오는지 생각해볼 필요가 있습니다. 파일을 새로 만들지 않고, 경계를 정하지 않고, 나중에 이걸 어떻게 걷어낼지도 정하지 않았기 때문에 빠른 것입니다. 그 속도는 되돌리기 비용을 미래로 미룬 대가로 얻은 것이라는 생각이 들었습니다.
워드프레스 플러그인이 주는 것은 되돌리기입니다
실제로 플러그인을 선택하고 나서 가장 크게 체감한 것은 개발 편의가 아니었습니다. 되돌리기가 명시적으로 존재한다는 점이었습니다.
플러그인에는 활성화, 비활성화, 삭제라는 세 단계 상태가 워드프레스에 이미 마련되어 있습니다. 문제가 생기면 운영자가 관리 화면에 들어가 비활성화를 한 번 누르는 것으로 기능을 끌 수 있습니다. functions.php 에 넣은 코드에는 그 스위치가 없습니다. 문제가 생기면 FTP 로 들어가서 코드를 지워야 합니다.
이 차이는 개발자를 위한 것이 아니라 운영자를 위한 안전장치입니다. 개발자는 어차피 코드를 열 수 있으니까요. 정작 그 시간에 사이트 앞에 앉아 있는 사람은 코드를 열 수 없는 운영자입니다. 인수인계할 때 이 차이가 특히 큽니다. "문제가 생기면 이 스위치를 끄세요"라고 말할 수 있는 것과, "저에게 연락 주세요"밖에 말할 수 없는 것은 전혀 다른 상태입니다.
그런데 PHP 를 분리했다고 CSS 까지 격리된 건 아니었습니다
여기까지가 제가 처음에 내린 결론이었습니다. 그리고 이 결론은 절반만 맞았습니다.
플러그인으로 PHP 코드는 확실히 분리됐습니다. 테마도 게시판 플러그인도 제 파일을 건드리지 않았고, 제 코드도 남의 파일을 건드리지 않았습니다. 그런데 화면을 보다가 이상한 점을 발견했습니다. CSS 는 분리되지 않았습니다.
테마에는 이런 선택자가 있었습니다.
/* 테마 */
.content table { width: 100%; }
/* 플러그인 */
.spec-table { width: auto; }
두 선택자의 특이성(specificity)을 계산해보면 이렇습니다.
| 선택자 | 소유자 | 특이성 |
|---|---|---|
.content table |
테마 | 0,1,1 |
.spec-table |
플러그인 | 0,1,0 |
특이성은 테마 쪽이 높습니다. 규칙대로라면 테마가 이겨야 합니다. 그런데 화면에서는 그동안 플러그인 스타일이 이겨 왔습니다.
이유를 찾아보니 허무했습니다. 플러그인 CSS 가 나중에 로드되어서, 캐스케이드 순서상 뒤에 온 규칙이 적용되고 있었을 뿐이었습니다. 특이성으로 이긴 게 아니라 순서로 우연히 이기고 있었던 것입니다.
이 사실이 불편했던 이유는, 로드 순서가 제가 통제하는 값이 아니라는 데 있습니다. 캐시 플러그인이 들어오거나, CSS 를 결합하고 최소화하는 설정이 켜지거나, 테마가 업데이트되면 순서는 언제든 바뀔 수 있습니다. 그날이 오면 레이아웃이 조용히 무너지고, 저는 최근에 아무것도 배포하지 않았으므로 원인을 한참 늦게 찾게 될 것입니다. 동작하고 있었지만 우연에 기대고 있었습니다.
해결 자체는 단순했습니다. 플러그인이 그리는 화면 전체를 루트 컨테이너로 감싸고, 모든 선택자를 그 안으로 스코프했습니다.
/* 우연에 기대던 형태 */
.spec-table { width: auto; }
/* 스코프해서 확정한 형태 */
.catalog-search .spec-table { width: auto; }
클래스가 하나 더 붙으면서 특이성이 0,2,0 이 되었습니다. 이제 테마의 0,1,1 보다 확정적으로 높습니다. 로드 순서가 어떻게 바뀌든 결과가 달라지지 않습니다. 이기던 것을 계속 이기게 만든 게 아니라, 이기는 이유를 우연에서 규칙으로 옮긴 것입니다.
여기서 배운 것은 CSS 문법이 아니었습니다. 파일을 분리하는 것과 런타임에서 격리되는 것은 다르다는 사실이었습니다.
PHP 는 플러그인이라는 경계로 깔끔하게 나뉩니다. 각자 자기 파일 안에서 실행되니까요. 그런데 CSS 는 최종적으로 한 문서 안에서 전부 섞입니다. 파일이 몇 개로 나뉘어 있든 브라우저에는 하나의 캐스케이드만 존재합니다.
경계는 제가 그은 곳에 그어지는 것이 아니라, 언어마다 다르게 그어져 있었습니다. 저는 PHP 의 경계를 보고 전부 격리됐다고 믿었던 것입니다.
플러그인으로 독립하면서 대신 포기한 것들
플러그인을 선택하면서 잃은 것도 분명히 있었습니다. 솔직히 적어두는 편이 나을 것 같습니다.
- 테마의 디자인 토큰을 모릅니다. 색, 폰트, 간격 같은 값을 플러그인은 알 방법이 없습니다. 스타일을 스스로 전부 정의해야 하고, 그래서 테마와 이질적으로 보이기 쉽습니다.
- 라이프사이클을 스스로 관리해야 합니다. 테이블을 만들고, 버전이 올라갈 때 스키마를 옮기고, 삭제할 때 정리하는 일이 전부 제 책임이 됩니다.
- 테마 안에 있었다면 공짜로 얻었을 것들을 포기합니다. 테마 템플릿과 전역 설정이 그렇습니다.
독립은 공짜가 아니었습니다. 남의 업데이트로부터 자유로워지는 대가로, 남이 대신 해주던 일들을 전부 떠안게 되는 구조였습니다. 그중 라이프사이클 이야기는 뒤에 나올 편들에서 따로 다뤄보려 합니다.
경계는 한 번 긋고 끝나지 않았습니다
돌이켜 생각해보면 이번 일에서 가장 오래 남은 것은 플러그인이라는 선택 자체가 아니었습니다. "남의 코드를 고치지 않겠다"는 결정이 한 번으로 끝나지 않는다는 감각이었습니다.
PHP 에서 경계를 긋고 나면 CSS 에서 다시 물어야 했습니다. 그리고 다음 층이 하나 더 남아 있었습니다. 플러그인이 자기 자리에 얌전히 있다고 해서 워드프레스가 제 코드를 불러주는 것은 아니고, 언제 불러주고 언제 부르지 않는지는 또 다른 규칙에 달려 있었습니다. 그 지점에서 저는 같은 질문을 세 번째로 하게 됩니다.
다음 편에서는 워드프레스가 내 코드를 부르는 방식, 그리고 부르지 않는 순간에 대해 써보려 합니다. 경계는 선언이 아니라 층마다 다시 확인해야 하는 것이었습니다.
관련 글
운영 워드프레스를 건드리지 않고 로컬 Docker로 클론해 개발하기
운영 중인 워드프레스를 직접 수정하는 건 위험합니다. 운영에는 읽기만 하면서 DB와 파일을 로컬 Docker로 복제해 안전한 개발 환경을 만든 과정과, 멀티사이트 도메인·PHP 버전·문자 인코딩에서 마주친 함정을 정리했습니다.
6년간의 웹빌더 회사 경험: 개발 여정을 돌아보며 얻은 실질적인 배움
6년간 웹빌더 회사에서 다양한 웹 시스템 유지보수 및 고도화를 경험하며 사용자 경험과 안정성을 중시한 개발 여정을 소개합니다. 개인 프로젝트로 AI 웹 서비스를 구축하며 얻은 실질적인 배움도 공유합니다.
개발도 결국은 상품을 만드는 일 — 기술 스택 선택이 납품과 유지보수를 결정한다
사이드 프로젝트의 모던 스택을 클라이언트에게 납품하려 하니, 기술 선택이 곧 유지보수 비용과 클라이언트 경험을 좌우한다는 걸 깨달았습니다. 개발자가 시장과 목적에 맞는 기술 스택을 선택해야 하는 이유를 정리했습니다.