[Vue.js] Markdown to HTML Converter 제작기
Class Prefix를 추가할 수 있는 Markdown to HTML Converter
제작 배경
마크업 개발을 하다보면, 이용약관처럼 텍스트는 방대하고 태그를 일일이 붙이기에는 번거로운 작업들이 종종 발생합니다. 이런 작업들은 텍스트 포맷팅이 비교적 쉬운 마크다운을 활용하면 조금 더 수월할 것 같다는 생각이 들었습니다. 물론 바로 사용할 수 있는 Markdown to HTML Converter(이하 Md2HTML)는 매우 많습니다. 그렇지만, 스타일 적용을 위한 클래스도 넣어줄 순 없을까? 하는 단순한 니즈에 Vue.js를 활용한 Md2HTML을 제작하게 되었습니다.
Step1. 마크업
HTML은 크게 세 영역으로 구성됩니다.
(1) 마크다운을 입력하는 곳에는 입력한 내용이 md_text
라는 변수에 들어가도록 v-model=”md_text”
를 선언해주었습니다. 그리고 파싱된 결과는 previewText
에 담기고 (2) HTML을 그대로 보여주기 위해 {{previewText}}
으로 선언한 곳과, (3) v-html
디렉티브를 사용해 HTML이 렌더링된 결과가 출력되게 하는 곳이 있습니다.
<textarea class="viewer__cont" rows="16" v-model="md_text"></textarea> // 마크다운을 입력할 곳<textarea class="viewer__cont" rows="16" readonly>{{previewText}}</textarea> // 파싱된 결과(HTML)를 보여줄 곳<div class="viewer__cont" v-html="previewText" ></div> // 파싱된 결과(렌더링)를 보여줄 곳
Step2. Parser 적용하기
node.js 개발환경에서 사용할 수 있는 마크다운 파서는 굉장히 많습니다. 그 중에서, Vue.js 공식 홈페이지에서 제공하고 있는 Markdown Editor( https://vuejs.org/v2/examples/index.html) 예제에서 사용한 Marked라는 패키지를 사용하기로 했습니다.
Marked 공식 홈페이지: https://marked.js.org/
먼저, 패키지를 설치합니다.
npm install marked --save-dev
이제 스크립트 영역입니다.
소스에서 패키지를 불러옵니다.
const marked = require('marked');
md_text
에 입력된내용이 바로 변환되어 previewText
에 담기면 되므로, computed
속성에 다음과 같이 입력합니다.
여기에서는 이 과정에서 다양한 옵션을 지정해주고 있는데, 옵션에 대한 자세한 내용은 Marked 공식 홈페이지( https://marked.js.org/#/USING_ADVANCED.md#options) 에서 확인할 수 있습니다.
data: function() {
return{
md_text: '# hello',
}
},
computed: {
previewText() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
headerIds: false,
tables: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
return marked(this.md_text);
}
}
여기까지는 기본적인 MD2HTML입니다.
Step3. 원하는 클래스 추가하기
이제 파싱된 HTML에 원하는 클래스를 추가하고, 바뀐 HTML 코드를 보여주는 과정입니다.
HTML에 클래스의 prefix를 입력받을 input을 추가합니다. 입력받은 내용은 prefix
라는 곳에 담기게 됩니다.
<input type="text" id="prefix" class="header__inp" v-model="prefix" placeholder="Input Class Prefix"/>
새로운 변수를 사용했으니, data에 prefix의 값을 초기화해줍니다.
data: function() {
return{
md_text: '# hello',
prefix: '',
}
},
이제 입력받은 prefix가 적용된 클래스를 추가해줍니다.
const tags = ['h1','h2','h3','h4','h5','h6','em','strong','del','u','ul','ol','a','img','pre','code','table','blockquote','hr','p'];
... computed: {
previewText() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
headerIds: false,
tables: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
let changedText = marked(this.md_text); // previewText()에서 return된 값을 changedText에 담기
if (this.prefix != ''){
for (let tag of tags){
let classTempName;
let regex = new RegExp('<' + tag + '>', 'g'); // 시작 태그 찾기
switch(tag){ // 일부 태그에는 태그명과 다른 클래스명을 부여하기 위해 사용
case 'h1' : classTempName = 'tit_lv1';break;
case 'h2' : classTempName = 'tit_lv2';break;
case 'h3' : classTempName = 'tit_lv3';break;
case 'h4' : classTempName = 'tit_lv4';break;
case 'h5' : classTempName = 'tit_lv5';break;
case 'h6' : classTempName = 'tit_lv6';break;
case 'strong' : classTempName = 'str';break;
case 'ul' : classTempName = 'lst';break;
case 'ol' : classTempName = 'olst';break;
case 'a' : classTempName = 'link';break;
case 'table' : classTempName = 'tb';break;
case 'blockquote' : classTempName = 'qt';break;
case 'hr' : classTempName = 'bar';break;
case 'p' : classTempName = 'txt';break;
default: classTempName = '';
}
changedText = changedText.replace(regex, `<${tag} class="${this.prefix}_${classTempName}">`); // prefix와 태그명으로 구성된 클래스 추가
}
}
return changedText; // changedText라는 새로운 값을 반환
}
}
}
결과 확인
완성된 결과물은 http://fe.hivelab.co.kr/md2html 에서 확인이 가능합니다.