Svelte Việt Nam (Về trang chủ)
Svelte Việt Nam (Về trang chủ)

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

a human holding hand with an anthropomorphized planet earth

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:

  1. 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.
  2. 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 qua npm.
  3. 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

  1. 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
  2. Chạy lệnh cài đặt tại thư mục gốc của dự án:

    pnpm boot
  3. 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".

Nếu bạn chỉ có thể viết bài bằng một ngôn ngữ, hãy bỏ trống tệp tin còn lại. Ban quản trị và các thành viên cộng đồng có thể hỗ trợ bạn dịch bài.

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 gitGithub 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.

Cách dễ nhất để tiếp cận các cú pháp đặc biệt này là tham khảo mã nguồn của các bài viết đã xuất bản tại trang Blog của Svelte Việt Nam.

Một số cú pháp Markdown hoặc Svelte có thể sẽ không hoạt động như mong đợi. Trong trường hợp này, hãy liên hệ với ban quản trị để tìm giải pháp thay thế.

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ị:

minh họa: ô chữ nhật và chữ blog viết tay
Minh họa 1: Ví dụ hiển thị hình ảnh trong bài viết

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, heightalt 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ị:

Nội dung cần được nhấn mạnh...

Để 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:

Tùy chỉnh
Trạng thái:
Icon (không bắt buộc):
Kết quả

=> 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:postpnpm 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ư readMinutesnumWords.

Ả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],
}));

Sửa trang này tại Github sveltevietnam.dev là một dự án mã nguồn mở và hoan nghênh sự đóng góp của bạn. Xin cảm ơn!