Viết bài
Sơ lược quy trình và hướng dẫn viết bài cho Blog Svelte Việt Nam
Mục lục
Sơ lược thiết kế kỹ thuật
Blog của Svelte Việt Nam là một trang web tĩnh: dữ liệu của Blog là một phần của mã nguồn và nội dung của bài viết được soạn bằng cú pháp Markdown và Svelte. Quá trình viết bài tương đương với một tác vụ lập trình thông thường chứ không thông qua cơ sở dữ liệu hoặc trình soạn thảo đặc biệt nào (WYSIWYG, trình soạn thảo Markdown, rich text, …). Ở một góc nhìn khác, Blog của Svelte Việt Nam sử dụng Git làm CMS.
Thiết kế này đòi hỏi người trực tiếp viết bài cần có một số kiến thức lập trình web cơ bản, nhưng đồng thời cũng giúp tiết kiệm đáng kể tài nguyên bảo trì hạ tầng của trang Blog.
Với người viết không phải là lập trình viên
Nếu bạn muốn đóng góp nội dung cho Blog nhưng không thể tiếp cận các khía cạnh kỹ thuật được liệt kê tại đây, bạn có thể liên hệ ban quản trị tại Discord của Svelte Việt Nam hoặc gởi bài viết bằng các định dạng văn bản truyền thống đến email blog@sveltevietnam.dev.
Cấu trúc mã nguồn bài viết
Mỗi bài viết là tập hợp một số tệp tin tại thư mục sites/sveltevietnam.dev/src/data/blog/posts/:id
:
sites/sveltevietnam.dev/src/data/blog/posts/:id
│
├── metadata.ts <-- dữ liệu bài viết
│
├── content/
┆ │
┆ ├── en.md.svelte <-- nội dung bài viết bằng Tiếng Anh
┆ └── vi.md.svelte <-- nội dung bài viết bằng Tiếng Việt
│
├── images/
┆ │
┆ └── thumbnail.jpg <-- ảnh bìa bài viết
┆ └── ... <-- các hình ảnh khác được sử dụng trong bài viết
┆
├── ... <-- các dữ liệu khác của bài viết
Sơ lược quy trình viết bài
Bước 1: cài đặt các phần mềm cần thiết:
- node.js:
- Phiên bản node.js cần thiết cho dự án được định nghĩa trong tệp package.json tại khóa "engines",
- Dự án khuyến khích bạn sử dụng volta để quản lý phiên bản node.js.
Nếu đã được cài đặt, volta sẽ tự động thiết lập phiên bản phù hợp cho dự án thông qua định nghĩa trong
package.json
.
- lefthook: trình quản lý git hook,
tự động chạy các tác vụ kiểm định trước khi commit mã nguồn.
Dự án khuyến khích bạn cài đặt
lefthook
binary vào hệ điều hành thay vì thông quanpm
. - pnpm: trình quản lý package thay thế cho
npm
.
Và đương nhiên bạn cần cài đặt git và một trình trình soạn thảo mã nguồn (code editor) tùy thích.
Bước 2: thiết lập môi trường phát triển
Sao chép mã nguồn:
git clone https://github.com/sveltevietnam/sveltevietnam.dev.git
git clone git@github.com:sveltevietnam/sveltevietnam.dev.git
gh repo clone sveltevietnam/sveltevietnam.dev
Chạy lệnh cài đặt tại thư mục gốc của dự án:
pnpm boot
Khởi động development server tại thư mục
sites/sveltevietnam.dev
:cd sites/sveltevietnam.dev pnpm dev
Nếu các bước thiết lập đã thành công, bạn có thể truy cập trang Blog tại http://localhost:5005/vi/blog.
Bạn có thể tham khảo thêm các hướng dẫn khác dành cho lập trình viên trong tài liệu DEVELOPMENT.md
Bước 3: khởi tạo dữ liệu bài viết
Truy cập bằng terminal vào thư mục sites/sveltevietnam.dev
.
Nếu đây là lần đầu bạn đóng góp nội dung cho trang sveltevietnam.dev, khởi tạo dữ liệu tác giả bằng lệnh sau:
pnpm create:person
Sau đó, khởi tạo dữ liệu bài viết bằng lệnh:
pnpm create:post
Bước 4: hoàn thiện bài viết
Nếu bạn dùng VSCode, hãy mở dự án tại thư mục gốc thay vì sites/sveltevietnam.dev
,
để VSCode có thể đọc được một số tùy chỉnh trong thư mục .vscode
.
Theo sau bước 3, bạn cần hoàn thiện nội dung bài viết tại hai tệp vừa được tạo ra:
.../content/vi.md.svelte
: nội dung bài viết bằng tiếng Việt,.../content/en.md.svelte
: nội dung bài viết bằng tiếng Anh.
Bạn có thể tham khảo thêm một số ví dụ và cú pháp đặc biệt tại mục "Cú pháp Markdown + Svelte trong bài viết", và cách chỉnh sửa dữ liệu bài viết tại mục "Chỉnh sửa và bổ sung dữ liệu cho bài viết".
Bước 5: tạo pull request
Sau khi hoàn tất nội dung bài viết, bạn cần thực hiện các thao tác quản lý mã nguồn thông thường với git
và Github
như commit các thay đổi trên một nhánh mới, và tạo pull request đến nhánh mặc định của dự án (nhánh hiển thị tại sveltevietnam/sveltevietnam.dev repository).
Bạn có thể sử dụng Github CLI để giản lược một vài thao tác.
Ví dụ, sau khi cài đặt Github CLI và login (gh auth
), bạn có thể tạo pull request dễ dàng hơn bằng lệnh:
gh pr create
Thao khảo thêm cách viết commit message tại DEVELOPMENT.md - Commit Message Guidelines.
Quá trình thao tác với git
và Github
không khó nhưng bao gồm nhiều bước và có nhiều cách
thực hiện khác nhau nên không được liệt kê chi tiết tại đây. Nếu có thắc mắc hoặc cần hỗ trợ,
hãy liên hệ với ban quản trị qua kênh Discord - Thảo luận - Blog nhé.
Bước 6: kiểm duyệt và xuất bản
Sau khi pull request đã được tạo, ban quản trị sẽ kiểm duyệt nội dung và dữ liệu bài viết. Nếu cần thay đổi hoặc bổ sung nội dung, ban quản trị sẽ thảo luận cùng bạn ngay tại trang pull request trên Github.
Khi quy trình kiểm duyệt hoàn tất, ban quản trị sẽ thực hiện thao tác merge pull request và xuất bản bài viết lên trang Blog 🎉.
Cú pháp Markdown + Svelte trong bài viết
Nội dung của bài viết được xử lý bằng một Svelte preprocessor chuyên biệt. Bạn có thể tham khảo mã nguồn của preprocessor này để hiểu thêm cách hoạt động của nó. Về cơ bản:
- phần lớn nội dung được viết bằng cú pháp Github Flavored Markdown,
- bạn cũng có thể sử dụng cú pháp Svelte, ví dụ như thẻ
script
,enhanced:img
, hay các component khác, - syntax highlight cho các ô hiển thị mã nguồn (code block) được xử lý bằng Shiki.
Một số cú pháp đặc biệt được phát triển để giúp việc viết bài trở nên dễ dàng hơn. Các cú pháp này được giới thiệu sau đây.
Code Block
Để hiển thị một đoạn mã nguồn trong bài viết, bạn có thể sử dụng cú pháp theo ví dụ sau:
```ts title="basic.ts"
console.log('Hello, world!');
```
Nội dung trên sẽ hiển thị như sau:
console.log('Hello, world!');
Hiển thị thay đổi mã nguồn (diff)
Để chỉ rõ các thay đổi trong một mã nguồn, bạn có thể bọc đoạn mã xung quanh comment với chỉ thị :::diff +
hoặc :::diff -
. Ví dụ, nội dung sau...
```svelte title="diff.svelte"
<script>
// :::diff -
import ComponentOld from './old.svelte';
// :::
// :::diff +
import ComponentNew from './new.svelte';
// :::
</script>
<!-- :::diff - -->
<ComponentOld>...</ComponentOld>
<!-- ::: -->
<!-- :::diff + -->
<ComponentNew>...</ComponentNew>
<!-- ::: -->
```
...sẽ hiển thị:
<script>
import ComponentOld from './old.svelte';
import ComponentNew from './new.svelte';
</script>
<ComponentOld>...</ComponentOld>
<ComponentNew>...</ComponentNew>
Đánh dấu mã nguồn (highlight & focus)
Để nhấn mạnh một hay nhiều dòng trong đoạn mã, bạn có thể sử bọc đoạn mã xung quanh comment với chỉ
thị :::highlight
hoặc :::focus
. Ví dụ, nội dung sau...
```css title="highlight-focus.css"
.example {
/* :::highlight error */
@extend mx-auto; /* not supported */
/* ::: */
/* :::highlight warning */
@apply mx-auto; /* not recommend */
/* ::: */
/* :::highlight */
margin: 0 auto; /* good */
/* ::: */
/* :::highlight success */
/* better */
margin-left: auto;
margin-right: auto;
/* ::: */
/* :::focus */
margin-inline: auto; /* best */
/* ::: */
}
```
...sẽ hiển thị:
.example {
@extend mx-auto; /* not supported */
@apply mx-auto; /* not recommend */
margin: 0 auto; /* good */
/* better */
margin-left: auto;
margin-right: auto;
margin-inline: auto; /* best */
}
Nhóm mã nguồn (group)
Để hiển thị nhiều mã nguồn thuộc cùng một ngữ cảnh, bạn có thể sử dụng thẻ enhanced-code-block
và thuộc tính group
. Ví dụ, nội dung sau...
<enhanced-code-block group display="tabs">
```bash title="npm"
npm install --save-dev @sveltevietnam/markdown
```
```bash title="yarn"
yarn add -D @sveltevietnam/markdown
```
```bash title="pnpm"
pnpm add -D @sveltevietnam/markdown
```
</enhanced-code-block>
...sẽ hiển thị nhóm dưới dạng tab:
npm install --save-dev @sveltevietnam/markdown
yarn add -D @sveltevietnam/markdown
pnpm add -D @sveltevietnam/markdown
Ngoài ra, để nhóm các mã nguồn như tập hợp các tệp tin, bạn có thể sử dụng display="files"
. Ví dụ,
nội dung sau...
<enhanced-code-block group display="files">
```html title="example.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
<link rel="stylesheet" href="./example.css">
<script module src="./example.js"></script>
</head>
```
```css title="example.css"
.example {
color: red;
}
```
```js title="example.js"
console.log('Hello, world!');
```
</enhanced-code-block>
...sẽ hiển thị:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
<link rel="stylesheet" href="./example.css">
<script module src="./example.js"></script>
</head>
.example {
color: red;
}
console.log('Hello, world!');
Chú ý: các dòng trống xung quanh thẻ enhanced-code-block
và các nội dung bên trong là cần thiết.
Hình ảnh
Hiện tại, dự án chưa thể hỗ trợ cú pháp Markdown cho hình ảnh trong bài viết. Thay vào đó, hãy sử dụng các thẻ HTML như ví dụ sau đây:
<script>
import illustration1 from '../images/blog.png?format=avif';
</script>
<figure>
<img src={illustration1} class="mx-auto max-w-full" width="573" height="376" alt="minh họa: ô chữ nhật và chữ blog viết tay" />
<figcaption>Minh họa 1: Ví dụ hiển thị hình ảnh trong bài viết</figcaption>
</figure>
Kết quả hiển thị:

Lưu ý:
- dự án khuyến khích bạn đặt hình ảnh trong thư mục
.../images
, tương tự nhưthumbnail.jpg
được đề cập trong mục "Cấu trúc mã nguồn bài viết", - sử dụng import query
format=avif
để tối ưu hóa kích thước ảnh. Tham khảo thêm tại imagetools - directives, - thiết lập các thuộc tính
width
,height
vàalt
phù hợp cho ảnh.
Callout
Callout được sử dụng để tạo điểm nhấn cho một số nội dung đặc biệt trong bài viết. Dưới đây là ví dụ một giao diện mà callout có thể hiển thị:
Để hiển thị giao diện trên trong bài viết, bạn có thể sử dụng cú pháp sau:
<div class="c-callout c-callout--success c-callout--icon-trophy">
Nội dung cần được nhấn mạnh...
</div>
Bạn có thể dùng trình thử nghiệm sau để tìm lớp và giao diện phù hợp:
=> Lớp cần áp dụng: c-callout
c-callout--info
.
Đây là giao diện mẫu cho “callout”, giúp bổ sung ngữ cảnh cho một số văn bản và tạo điểm nhấn để lôi kéo sự chú ý của người dùng. Bạn có thể tham khảo thêm ví dụ và cách sử dụng callout tại trang "Blog Svelte Việt Nam - Hướng dẫn viết bài".
Chỉnh sửa và bổ sung dữ liệu
Theo "Sơ lược quy trình viết bài", dữ liệu bài viết và tác giả sẽ được khởi
tạo tự động bằng lệnh pnpm create:post
và pnpm create:person
. Tuy nhiên, bạn có thể chỉnh sửa
dữ liệu này khi cần thiết.
Cho bài viết
Như đã đề cập tại muc "Cấu trúc mã nguyền bài viết", dữ liệu chính của mỗi bài viết được đặt
tại têp sites/sveltevietnam.dev/src/data/blog/posts/:id/metadata.ts
. Ví dụ, dữ liệu của bài viết "Đến với web thông qua Svelte" được định nghĩa như sau:
import { defineBlogPostMetadata } from '..';
export default defineBlogPostMetadata((lang) => ({
publishedAt: new Date('2024-04-20'),
authors: ['vnphanquang'],
categories: ['svelte-and-kit', 'ecosystem'],
...(
{
en: {
slug: '20240420-come-for-svelte-stay-for-the-web',
title: 'Come for Svelte, Stay for the Web',
description:
'Svelte is exceptionally good at staying out of your way, allowing you to focus on building a better web. No one cares about frameworks anyway!',
keywords: 'web, ecosystem, runtime, compile-time, action',
readMinutes: 8,
numWords: 1590,
translation: 'manual',
},
vi: {
slug: '20240420-den-voi-web-thong-qua-svelte',
title: 'Đến với web thông qua Svelte',
description:
'API của Svelte được thiết kế để thân thiện với các công nghệ và kiến thức nền tảng, giúp bạn dễ dàng tập trung vào việc xây dựng ứng dụng web, thay vì quan tâm đến framework này và nọ.',
keywords: 'web, hệ sinh thái, runtime, compile-time, action',
readMinutes: 8,
numWords: 1990,
translation: 'original',
},
} as const
)[lang],
}));
Khi bạn đã hoàn thành nội dung bài viết, hãy quay trở lại tệp .../metadata.ts
tương ứng và cập nhật
các trường dữ liệu cần thiết, ví dụ như readMinutes
và numWords
.
Ảnh bìa bài viết
Để thay đổi ảnh bìa bài viết, bạn chỉ cần thêm tệp thumbnail.jpg
vào thư mục .../images
như
đã liệt kê tại mục "Cấu trúc mã nguồn bài viết".
Dự án có một số quy định và đề xuất cho ảnh bìa như sau:
- cần ở định dạng JPEG và có tên là
thumbnail.jpg
, - có chiều rộng tối thiểu là 2240px,
- có tỷ lệ khung hình là 16:9,
- tối giản, và hạn chế văn bản trong ảnh.
Cho tác giả
Dữ liệu tác giả được định nghĩa tại tệp sites/sveltevietnam.dev/src/data/people/:id/index.ts
.
Ví dụ, dữ liệu của "Phan Quang" được định nghĩa như sau:
import { definePerson } from '..';
export default definePerson((lang) => ({
links: {
website: 'https://vnphanquang.com',
bluesky: 'https://bsky.app/profile/vnphanquang.com',
github: 'https://github.com/vnphanquang',
},
...{
en: {
name: 'Quang Phan',
description: 'Developer, administrator',
},
vi: {
name: 'Phan Quang',
description: 'Lập trình viên, quản trị viên',
},
}[lang],
}));