발행 시간: 2025-04-15T01:21:00.000Z
프로젝트 구조
Rino는 프로젝트 디렉토리와 파일 구조를 기반으로 웹사이트를 빌드합니다. 이는 표준 HTML, CSS, Javascript, Node.js를 사용하여 웹사이트 제작을 쉽게 학습하고 구축할 수 있다는 의미입니다.
- pages
- components
- mds
- public
- scripts
- export
- styles
- export
- rino-config.js
- contents
- content-theme
- backoffice.bat&backoffice.sh
/pages
/pages
디렉토리는 기본 HTML 파일을 넣는 공간입니다. Rino는 이 디렉토리를 기준으로 웹 페이지를 빌드합니다. 파일 및 디렉토리 구조는 그대로 유지됩니다.
/components
/components
디렉토리는 컴포넌트 HTML 파일을 넣는 공간입니다. 컴포넌트는 다른 컴포넌트를 포함할 수 있습니다. 그러나 동일한 컴포넌트를 순환 호출하여 무한 루프 오류를 발생시키지 않도록 주의하세요. 컴포넌트는 아래와 같은 문법으로 사용할 수 있습니다:
<component @path="" @tag="" />
-
@path
: 컴포넌트를 불러오기 위해 필요한 속성입니다. 예:@path="/header"
또는@path="/ko/footer"
-
@tag
: 선택 속성입니다. 컴포넌트 태그에 HTML 속성을 사용하려면 HTML 태그 이름을 지정해야 합니다. 예:@tag="div"
또는
<component @path="/button" @tag="button" onclick="myFunction()" />
/mds
/mds
디렉토리는 마크다운 파일을 넣는 공간입니다. Rino는 이 디렉토리의 모든 마크다운을 로드하고 요청 시 HTML로 변환합니다.
<script @type="md" @path="" @tag="" type="text/markdowns"></script>
@type
: 마크다운을 사용하려면 이 속성에md
또는markdown
을 지정해야 합니다.@path
: 마크다운을 불러오기 위한 경로를 지정합니다.@tag
: 선택 속성입니다. HTML 속성을 사용하려면 HTML 태그 이름을 지정해야 합니다.
/public
/public
디렉토리는 이미지, 외부 CSS, 자바스크립트 라이브러리 등 정적 파일을 넣는 공간입니다.
/scripts
/scripts
디렉토리는 자바스크립트 라이브러리를 작성하는 공간입니다. npm 패키지를 사용할 수 있으며, /scripts/export/
디렉토리에 자바스크립트 파일을 넣으면 Rino가 빌드하여 /scripts
디렉토리에 출력합니다. HTML에서 일반적으로 사용하는 방식으로 사용할 수 있습니다. 타입스크립트도 지원됩니다.
스크립트는 import/export
모듈 방식으로 처리됩니다. 파일 이름이 스크립트 인스턴스 이름이 되며, 예를 들어 hello.js
또는 hello.ts
인 경우, 외부에서는 hello.FunctionName()
으로 호출합니다.
<script src="/scripts/example.js" />
/styles
/styles
디렉토리는 CSS 라이브러리를 작성하는 공간입니다. /styles/export/
디렉토리에 CSS 파일을 넣으면 Rino가 빌드하여 /styles
디렉토리에 출력합니다. HTML에서 일반적으로 사용하는 방식으로 사용할 수 있습니다.
<link rel="stylesheet" href="/styles/example.css" />
로컬 CSS 파일을 불러오는 예시:
@import "../header.css";
@import "../sidebar.css";
@import "../footer.css";
rino-config.js
rino-config.js
는 Rino의 설정 파일입니다. 배포 디렉토리 변경, 개발 서버 포트 변경, 사이트 URL 설정 및 사이트맵 추가 등을 설정할 수 있습니다.
contents
/contents
는 블로그용 마크다운 콘텐츠를 저장하는 디렉토리입니다. 파일 경로 및 URL 구조는 /contents/theme/category/markdownfile.md
형식을 따릅니다. 마크다운 파일 상단에는 아래와 같이 HTML 주석 형태로 JSON 데이터를 작성할 수 있습니다:
<!--
{
"title": "Project Structure",
"time": "2025-04-15T01:21:00.000Z",
"description": "Project Structure Rino build website based on project directory and file structure. Which means it is easy to learn and build a website with standard HTML, CSS, Javascript and Node.js. pages components mds public scripts export styles export contents contenttheme rinoconfig.js backoffice.bat & backoffice.sh..."
}
-->
이 데이터는 콘텐츠 테마 페이지에서 {{ content.title }}
등의 문법으로 사용할 수 있습니다.
content-theme
/content-theme
는 콘텐츠 템플릿 페이지를 저장하는 디렉토리입니다. 파일 경로 및 URL 구조는 /content-theme/theme/content.html
및 /content-theme/theme/content-list.html
을 따릅니다. /contents
디렉토리 안의 같은 테마 이름의 콘텐츠 파일들과 매칭됩니다.
예: 아래는 해당 웹사이트에서 사용하는 템플릿 페이지입니다:
/content-theme/en/content.html
:
<!DOCTYPE html>
<html>
<head>
<component @path="/en/head"></component>
<meta name="description" content="{{content.description}}" />
<meta property="og:title" content="{{ content.title }}" />
<meta property="og:description" content="{{ content.description }}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://rinojs.org{{ content.urlPath }}" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml">
<link rel="alternate" type="application/atom+xml" title="Atom" href="/atom.xml">
<title>{{ content.title }}</title>
</head>
<body>
<div class="top">
<component @path="/en/sidebar"></component>
<div class="top-right">
<component @path="/en/header"></component>
<main>
<div class="content-container">
<div class="content" id="content">
<article>
<p class="published-time">
Published:
<span id="published-time" data-raw-time="{{ content.time }}">
{{ content.time }}
</span>
</p>
{{ content.body }}
</article>
<hr />
<h3>Content Navigation:</h3>
<nav class="content-navigation">
<script @type="js" type="text/javascript">
const args = process.argv;
const contentDataString = args[args.length - 1];
const contentData = JSON.parse(contentDataString);
if (Array.isArray(contentData.nearby))
{
const nearby = contentData.nearby;
const currentIndex = nearby.findIndex(post => post.link === contentData.urlPath);
let start = 0;
let end = nearby.length;
if (currentIndex !== -1)
{
const maxItems = 5;
const before = Math.min(2, currentIndex);
const after = Math.min(2, nearby.length - currentIndex - 1);
let total = 1 + before + after;
if (total < maxItems)
{
const extra = maxItems - total;
const moreBefore = Math.min(extra, currentIndex - before);
const moreAfter = Math.min(extra - moreBefore, nearby.length - currentIndex - 1 - after);
start = currentIndex - (before + moreBefore);
end = currentIndex + 1 + after + moreAfter;
}
else
{
start = currentIndex - before;
end = currentIndex + 1 + after;
}
const sliced = nearby.slice(start, end);
console.log(`<ul>`);
sliced.forEach(post =>
{
if (post.link === contentData.urlPath)
{
console.log(`<li><span>${post.title}</span></li>`);
}
else
{
console.log(`<li><a href="${post.link}">${post.title}</a></li>`);
}
});
console.log(`</ul>`);
}
}
</script>
</nav>
</div>
<div class="tab2-content tab2-content--invisible" id="tab2-content">
<component @path="/en/tab2-content"></component>
</div>
</div>
</main>
</div>
</div>
<component @path="/en/footer"></component>
</body>
</html>
/content-theme/en/content-list.html
:
<!DOCTYPE html>
<html>
<head>
<component @path="/en/head"></component>
<meta name="description" content="This is a page for navigating contents." />
<meta property="og:title" content="Content List" />
<meta property="og:description" content="This is a page for navigating contents." />
<meta property="og:type" content="website" />
<title>Content List</title>
</head>
<body>
<div class="top">
<component @path="/en/sidebar"></component>
<div class="top-right">
<component @path="/en/header"></component>
<main>
<div class="content-container">
<div class="content" id="content">
<article>
<ol>
<script @type="js" type="text/javascript">
var args = process.argv;
var contentListDataString = args[args.length - 1];
var contentListData = JSON.parse(contentListDataString);
var contentList = contentListData.contentList;
for (let i = 0; i < contentList.length; i++)
{
const item = contentList[i];
if (item?.title && item?.link)
{
console.log(`<li><a href="${item.link}">${item.title}</a></li>`);
}
}
</script>
</ol>
</article>
</div>
<div class="tab2-content tab2-content--invisible" id="tab2-content">
<component @path="/en/tab2-content"></component>
</div>
<hr />
<nav class="content-list-navigation">
<script @type="js" type="text/javascript">
var args = process.argv;
var contentListDataString = args[args.length - 1];
var contentListData = JSON.parse(contentListDataString);
var pagination = contentListData.pagination;
if (pagination.prevLink)
{
console.log(`
<div>
<a href="${pagination.prevLink}">Previous List</a>
</div>
`);
}
if (pagination.nextLink)
{
console.log(`
<div>
<a href="${pagination.nextLink}">Next List</a>
</div>
`);
}
</script>
</nav>
</div>
</main>
</div>
</div>
<component @path="/en/footer"></component>
</body>
</html>
backoffice.bat & backoffice .sh
backoffice.bat
과 backoffice.sh
는 콘텐츠 관리 및 이미지 압축을 위한 백오피스 서버를 실행하는 스크립트입니다. 이 파일을 더블 클릭하면 npm run backoffice
명령을 실행하게 되어 웹사이트 관리자가 편리하게 웹사이트를 유지 관리할 수 있도록 돕습니다.