NOMO.asia

발단

내가 작성하고 있는 어느 한 유저스크립트는 처음 30줄 정도의 코드로 시작했다. 그리고 2년이 지난 지금은 1만줄이 넘는 꽤 커다란 크기를 가지게 되었다. 이렇게 파일이 커지게 된 것은 동적으로 요소(element)를 생성하기 위한 html 과 css 등이 꽤 많은 부분을 차지하고 있다는 이유도 있지만, cdn 등에서 불러오는 몇가지 라이브러리를 제외하고는 대부분의 내용을 한 파일에 포함해야 하다보니 부피가 많이 커진 것이 원인이다.

이렇게 라인 수가 커짐에 따라 생긴 가장 큰 문제는 유지보수이다. 함수와 이벤트가 너무 많아서 내가 사용하는 코드 에디터의 책갈피 기능을 이용하더라도 어디가 어딘지 찾기 어렵고, 필요한 부분을 찾기 위해 매번 검색 기능을 이용해야했다. 어떤 웹사이트를 탐색할 때 효율성을 올리기 위해서 유저스크립트를 만든건데, 유저스크립트를 만드는 과정에서의 효율성이 떨어져버렸다.

목표

위와 같이 파일이 복잡해진 문제를 해결하기 위해 아래와 같은 목표를 세웠다.

  • 주요 기능, 함수 별로 파일을 분리하여 파일 작성
  • 배포 전 유저스크립트 형태로 분리된 파일들을 합치기
  • (Option) 합치기 전, 각 부분을 타입 별로(css, js, html) minimize 하여 최종 유저스크립트의 파일 크기를 줄임

가능한 방법

가장 단순한 방법은 지금 한 파일에 작성된 내용을 나눠서 func1.txt, func2.txt 처럼 만들고, 각 파일을 순차적으로 읽어서 하나의 파일로 합치는 것이다. 단순히 파일을 읽고 합치고 쓰는 정도는 어떤 프로그래밍 언어나 가능하니까 말이다.

하지만 이미 이러한 코드 통합 작업을 위해 만들어진 Webpack 이라는 도구가 있었다. Webpack은 JavaScript로 작성된 프로젝트의 구조를 분석하고 관련 리소스나 모듈을 하나의 파일로 합치는데 사용할 수 있는 도구인데, 이런게 있다고는 알고있었지만 따로 써볼 기회는 없었다.

Webpack 을 사용하기 위한 여러가지 세팅은 매우 귀찮은 작업이지만, 한 번 입맛에 맞게 세팅해두면 계속 쓸 수 있으므로 나는 Webpack 을 사용해보기로 마음먹었다. 물론 수십 줄짜리 간단한 유저스크립트를 작성하는데까지 Webpack 을 쓸 필요는 없을 것이다.

아래에서는 Webpack 을 사용하여 Userscript 를 개발하기 위해 내가 수행한 기본 과정을 정리해보았다. Nodejs 와 Webpack 에 대하여 아무것도 모르는 사람도 일단 따라서 진행할 수 있도록, 자세한 설명보다는 오퍼레이션 위주로 설명하고자한다.

Webpack 을 이용한 Userscript 개발을 위한 환경 구축

Nodejs 설치

작업환경: Windows 10

가장 먼저 Webpack 을 사용하기 위해 Nodejs 를 설치해야 한다.

https://nodejs.org/en/download/ 로 접속하여 운영체제에 맞는 설치파일을 다운받아 설치한다. 설치 방법은 매우 쉽다. 다운받은 설치 파일을 실행하고 그냥 Next 버튼만 계속 눌러주다보면 완료된다.

Nodejs 프로젝트 생성

드라이브의 원하는 경로에 프로젝트를 생성할 폴더를 하나 만든다.

나는 F:\webpack_userscript 경로에 폴더를 생성했다.

폴더를 생성한 후 [시작메뉴 - 모든 프로그램 - 보조프로그램 - 명령 프롬프트]를 클릭하거나
[윈도우키 + R]을 눌러서 cmd 를 입력하고 엔터를 치는 방법으로 cmd 창을 연다.

cmd 창이 켜지면, 먼저 위에서 생성한 폴더로 이동해야한다.

나처럼 c 드라이브가 아닌 다른 드라이브에 폴더를 생성하였다면, 먼저 아래와 같이 [드라이브명:] 을 입력하여 드라이브를 이동해야 한다.

F:

그 다음에 아래와 같이 [cd 경로] 와 같이 입력하여 이동한다.

cd F:\webpack_userscript

이후 아래와 같이 입력하여 Nodejs 프로젝트를 위한 package.json 파일을 만든다.

npm init

위 명령어를 입력한 뒤로는 package name (일종의 프로젝트 명), 버전, 설명 등을 입력하는 과정이 나온다. 추후 수정이 가능하니 그냥 엔터를 계속 연달아 눌러서 기본값을 사용해도 된다.

내 경우 entry point(Nodejs 프로젝트에서 가장 처음 읽어들일 파일)를 src/index.js 로 수정하고 author 만 입력해주었다.

참고로 나는 소스 파일은 src 폴더에 모아두고, 배포할 파일은 dist 폴더에 모아둘 예정이다.

위 과정을 완료하면 아래와 같이 package.json 파일이 생성된다.

팁1. 탐색기에서 마우스 우클릭을 했을 때 나타나는 메뉴에 "여기에 CMD 창 열기" 메뉴를 추가할 수 있는 방법이 있다. 해당 메뉴를 추가하면 처음 cmd 실행 후 cd 명령어를 이용해 폴더를 이동할 필요가 없다. 자세한 방법은 구글에 "마우스 우클릭 CMD 창 열기"로 검색하면 나온다.

팁2. Microsoft Visual Studio Code 는 에디터 내에서 바로 CMD 나 Powershell 같은 터미널을 이용할 수 있도록 되어있다. 이 것을 이용하면 더욱 편하다.

작업에 필요한 모듈 설치

이제 앞에서 설명한 webpack 을 포함하여, 이것저것 필요한 것들을 해당 폴더에 내려받아야한다.

아래와 같이 입력하여 필요한 모듈을 내려받는다.

npm install webpack webpack-cli webpack-userscript -D

세개의 모듈, webpack, webpack-cli, webpack-userscript 만을 설치할 것이다. Webpack 을 더욱 편하게 사용할 수 있는 다른 모듈들이 매우 많지만 일단은 기본 중의 기본만 가지고 정리한다. 각 모듈의 설명은 아래와 같다.

  • webpack : 기본 webpack 모듈
  • webpack-cli : cmd 에서 webpack 관련 명령어를 사용하기 위해서 필요
  • webpack-userscript : Userscript 개발 시 메타 블록(상단 주석)을 좀 더 편하게 입력하고, 최종 결과물을 Userscript 형태로 내보내기 위해 필요

앞에서 입력한 명령어 마지막의 -D 는 development 에 대한 플래그로, 개발 중에만 사용할 모듈로 설치하겠다는 의미인데 자세한 설명은 생략한다.

설치가 완료되면 아래와 같이 뜰 것이다.

앞서 생성한 폴더로 가면 아래와 같이 되어있을 것이다.

webpack.config.js 기본 설정

실제 코딩을 진행하기 전에, Webpack 사용 설정을 위한 webpack.config.js 파일을 위 경로에 생성해주어야 한다. 자세한 설명은 구글에서 webpack.config.js 설정 이라고 검색하여 찾아보는 것을 추천한다.

webpack.config.js 파일을 아래의 내용으로 만든다. my_project_name 에는 내가 작성할 Userscript 의 이름을 적어주면 된다.

const path = require('path')
const WebpackUserscript = require('webpack-userscript')
const my_project_name = 'My_Userscript_Name';

module.exports = {
  mode: 'production',
  entry: path.join(__dirname, './src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: my_project_name+'.js'
  },
  plugins: [
    new WebpackUserscript({
      headers: path.join(__dirname, './src/headers.json'),
      pretty: false
    })
  ]
}

위와 같은 설정으로 이루어지는 것을 간단히 설명하자면, 일단 가장 먼저 읽어들이는 파일은 entry point 에 해당하는 /src/index.js 파일이다. 그리고 이 파일이 만약 require 나 import 등으로 다른 js 파일을 불러오는 등의 작업을 한다면, Webpack 은 이러한 파일 간의 연결 관계를 파악한다. 그리고 최종적으로 모든 내용을 파일 하나에 합친 뒤 dist 폴더에 output 으로 내보낸다. 여기까지가 Webpack 의 역할이다.

Webpack-userscript 의 역할은 Output 파일을 my_project_name 에 입력한 이름에 해당하는 My_Userscript_Name.user.js 파일과 My_Userscript_Name.meta.js 로 내보내도록 해준다.

~.user.js 파일은 완전한 Userscript 파일의 형태이므로 이 파일을 사용자에게 배포하면 된다.

~.meta.js 파일은 Userscript 최상단의 메타 블록, 즉 주석에 해당하는 내용만이 포함되어 있다. 이 파일은 package.json 파일과 ./src/headers.json 파일의 내용으로부터 생성된다. meta 파일을 별도로 분리하는 이유는 meta 파일의 내용만을 읽어서 빌드에 따른 userscript 의 버전 관리를 할 수 있도록 하기 위한 것이다. 단순히 Webpack 으로 빌드할 때만 사용하는 것이 아니라 웹에 meta 파일까지 함께 업로드하고, Userscript 에서 해당 파일을 읽어들여 버전 업데이트 가능 여부 등을 확인하는 걸로도 이용할 수 있다.

package.json 파일 수정

이제 cmd 창에서 webpack 과 관련된 명령어를 사용할 수 있도록 package.json 의 scripts 에 명령어를 추가해주어야 한다.

package.json 파일을 열면 아래와 같이 되어있을 것이다.

{
  "name": "webpack_userscript",
  "version": "1.0.0",
  "description": "",
  "main": "./src/index.js",
  "dependencies": {},
  "devDependencies": {
    "webpack": "^4.35.0",
    "webpack-cli": "^3.3.5",
    "webpack-userscript": "^2.3.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Nomo",
  "license": "ISC"
}

이것을 아래와 같이 수정해준다.

{
  "name": "webpack_userscript",
  "version": "1.0.0",
  "description": "",
  "main": "./src/index.js",
  "dependencies": {},
  "devDependencies": {
    "webpack": "^4.35.0",
    "webpack-cli": "^3.3.5",
    "webpack-userscript": "^2.3.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "watch": "webpack --wahch"
  },
  "author": "Nomo",
  "license": "ISC"
}

scripts 에 "build": "webpack" 과 "watch": "webpack --wahch" 을 추가해주었다. 이를 통해 cmd 창에서 webpack 으로 build 및 watch 하라는 명령을 실행할 수 있다.

만약 build 명령을 실행하기 위해 npm run build 라고 치면, /src/index.js 파일을 읽어들여, webpack.config.json 에 설정한 대로 dist 폴더에 output 파일을 생성할 것이다.

만약 watch 명령을 실행하기 위해 npm run watch 라고 치면, 파일이 변경될 때마다 자동으로 빌드를 수행할 것이다. 이렇게 파일이 변경될 때마다 자동으로 빌드를 실행하는 기능을 wahch 라고 한다. 매번 귀찮게 npm run build 명령을 실행할 필요가 없으므로 편하다. 만약 중지하고 싶다면 cmd 창에서 Ctrl+C 를 두번 눌러 나가거나, 켜진 cmd 창을 끄면 된다.

여기까지가 필요한 모든 설정이다.

Webpack 으로 빌드하기 위한 유저스크립트 코딩

Javascript 코딩

/src/index.js 파일을 생성하고 아래와 같은 내용을 입력하였다. 일단 기본적으로 콘솔창에 Hello world 라는 메시지를 출력하며, 동일 경로의 sub1.js 파일에서 모듈로 정의된 f1(함수) 을 불러오고 이 함수를 실행한다.

import f1 from "./sub1";

console.log("Hello world");
f1();

/src/sub1.js 파일을 생성하고 아래와 같은 내용을 입력하였다. f1 함수의 역할은 콘솔창에 Hello world from sub1.js 를 출력하는 것이다.

export default function f1() {
    console.log('Hello world from sub1.js');
}

Userscript 메타 블록 설정

webpack-userscript 를 이용할 때, 메타 블록의 내용을 입력하는 방법은 여러가지가 있지만 제일 선호하는 방법은 headers.json 파일에 필요한 내용을 넣어두고 읽어들이는 것이다. 그래서 위에서 webpack.config.js 를 설정할 때도 headers.json 파일을 읽어오도록 미리 설정해두었다.

/src/headers.json 파일을 아래와 같은 형태로 만든다.

{ 
  "name": "My_Userscript_Name",
  "version": "1.0.0",
  "author": "Nomo",
  "description": "Userscript for Testing",
  "match": "https://*.google.com/*",
  "grant": [
    "GM_info",
    "GM_setValue",
    "GM_getValue"
  ]
}

입력해주지 않은 내용 중 package.json 에서 불러올 수 있는 내용 (예:homepage) 가 있다면 해당 내용을 불러와 meta 블록에 채운다. 만약 중복되는 key 를 가지는 것이 있다면 headers.json 의 내용을 우선한다. 메타 블록에서 가장 자주 바뀌는 것은 version 일텐데, headers.json 파일을 자주 수정하고 싶지 않다면 여기에 버전을 적지 말고 package.json 에서 버전을 수정해주면 된다.

Webpack 으로 Userscript 빌드하기

cmd 창에서 작업 폴더, 내 경우에는 F:\webpack_userscript 로 이동해주는 것은 기본이다. npm run build 명령을 입력하고 엔터를 누르면 빌드가 수행되고, 문제가 없다면 아래와 같이 완료될 것이다.

이후 dist 폴더에 들어가면 아래와 같은 두 개의 파일이 생성되어 있을 것이다. 만약 파일 이름이 마음에 들지 않는다면 webpack.config.js 파일을 수정해주면 된다.

My_Userscript_Name.user.js 파일의 내용은 아래와 같다.

// ==UserScript==
// @name My_Userscript_Name
// @version 1.0.0
// @author Nomo
// @description Userscript for Testing
// @match https://*.google.com/*
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==

!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r,t){"use strict";t.r(r),console.log("Hello world"),console.log("Hello world from sub1.js")}]);

My_Userscript_Name.meta.js 파일의 내용은 아래와 같다.

// ==UserScript==
// @name My_Userscript_Name
// @version 1.0.0
// @author Nomo
// @description Userscript for Testing
// @match https://*.google.com/*
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==

My_Userscript_Name.user.js 파일의 내용을 브라우저의 Userscript 확장기능에 등록한 후, Google 에 접속해보면 아래와 같이 콘솔창에 메시지가 잘 찍히는걸 볼 수 있다.

예제 파일

이렇게 작업한 파일은 Github 에 올려두었다. https://github.com/nomomo/webpack-userscript-example

실행 방법

  1. 위의 Github 링크로 접속한 후, clone or download 버튼을 누르고 ZIP 버튼을 클릭하여 모든 파일을 압축한 zip 파일을 다운 받는다.
  2. 다운로드 받은 파일의 압축을 푼다.
  3. 필요한 경우 압축 파일 내의 example 폴더를 원하는 경로에 이동시킨다.
  4. cmd 창을 켜고, cd 명령어를 이용해 example 폴더의 경로로 이동한다.
    예) cd C:\Users\MyUserName\Downloads\webpack-userscript-example\example
  5. 파일 크기를 줄이기 위하여, 모듈 파일들은 포함하지 않았으므로 직접 설치해야 한다.
    cmd 창에 npm install 이라고 입력하면 node_modules 폴더가 생성되고, package.json 에 적힌 모듈들을 다운로드 받을 것이다.
  6. npm run build 라고 입력하면 dist 폴더가 생성되고 Userscript 가 빌드되어 파일이 생성되는 것을 확인할 수 있을 것이다.

그 외 고려 가능한 부분들

Babel

Webpack 과 함께 많이 사용되는 것이 Babel 이라는 트랜스파일러(Transpiler) 이다. Babel 은 ES6+ 코드를 ES5 이하의 버전으로 트랜스파일링 해주는 역할을 한다. 그러니까 내가 최신의 언어로 코드를 작성하더라도 구버전 IE 에서 문제없이 코드가 실행될 수 있도록 자동으로 코드를 변환해주는 역할을 한다. 하지만 유저스크립트를 사용할 대부분의 사용자는 ES6 를 지원하는 모던 브라우저를 사용할 것이므로, 굳이 Babel 을 사용해주지 않아도 된다.

CSS loader, HTML loader

만약 DOM 요소로 CSS 나 HTML 내용을 삽입하고 이러한 내용이 많다면, css 와 html 을 별도의 파일로 분리하고 import 키워드로 css 파일과 html 파일을 불러오는 것을 고려해볼 수 있다. 기본적으로는 js 파일만 import 키워드를 이용하여 삽입이 가능하지만, Webpack 의 loader 로 CSS loader 와 HTML loader 를 사용하면 다른 형태의 파일도 모듈 형태로 import 가 가능하다. 자세한 설명은 직접 찾아보길 바라며 생략.

이렇게 CSS loader 와 HTML loader 를 사용하는 것의 장점은 기본적으로 파일을 분리하여 관리할 수 있으므로 구조화 하기 좋고 무엇보다 문법 검사가 편하다는 점이 있겠다. 그리고 CSS 와 HTML에 대하여 minifier 를 사용할 수 있으므로, 최종 파일 크기를 줄일 수 있다.

webpack-userscript 의 다른 기능들

webpack-userscript 의 기능을 이용하면 빌드 시 마다 해쉬 값을 버전 뒤에 추가해주는 등의 작업이 가능하다. webpack-userscript 의 Github 페이지에 가면 자세한 설명이 적혀있다. 또한 Test 폴더에 가면 내가 이 포스트에서 설명하지 않은 다른 몇가지 방법들에 대한 예제가 존재하니 참고해보면 좋을듯.