feat(blog): add new blog posts about JavaScript, Docusaurus, Git, browser rendering, and Gitea Actions
Add five new technical blog posts covering various frontend and DevOps topics: - JavaScript map and parseInt trap explanation - Tailwind CSS integration with Docusaurus v3 - Git commit conventions and best practices - Browser rendering principles and optimization - Automated deployment workflows using Gitea Actions Each post includes detailed explanations, code examples, and practical implementation guidance
This commit is contained in:
100
src/pages/blog/posts/browser-rendering-principles.md
Normal file
100
src/pages/blog/posts/browser-rendering-principles.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
layout: "@/layouts/BlogPostLayout.astro"
|
||||
title: "Browser Rendering Principles"
|
||||
description: "This article details the seven stages of browser page rendering (HTML parsing, style calculation, layout, layering, painting, tiling, and rasterization) as well as the concepts of reflow and repaint, and why transform is efficient."
|
||||
date: "2023-08-10"
|
||||
image: "https://images.unsplash.com/photo-1510511459019-5dda7724fd87?q=80&w=1470&auto=format&fit=crop"
|
||||
tags: ["Browser", "Frontend", "Rendering Principles"]
|
||||
tagId: ["browser", "frontend", "rendering"]
|
||||
category: "Frontend Development"
|
||||
categoryId: "frontend"
|
||||
readTime: "7 min read"
|
||||
---
|
||||
|
||||
The browser rendering process can be divided into seven stages:
|
||||
|
||||
### 1. HTML Parsing
|
||||
|
||||
After receiving the HTML document from the server response, the browser first parses the HTML document to build a DOM tree. The DOM tree consists of DOM elements and attribute nodes, with the document object as the root of the tree.
|
||||
|
||||
### 2. Style Calculation
|
||||
|
||||
The browser parses CSS files and inline styles on elements to calculate the styles of DOM nodes. This process includes:
|
||||
|
||||
- Converting CSS into a structure that the browser can understand
|
||||
- Calculating the specific styles of DOM nodes
|
||||
|
||||
### 3. Layout
|
||||
|
||||
The layout phase calculates the geometric positions of visible elements in the DOM tree, a process called layout or reflow.
|
||||
|
||||
### 4. Layering
|
||||
|
||||
The browser divides the DOM tree into multiple layers based on the layout. This process is mainly to handle complex effects in the page, such as 3D transformations and page scrolling.
|
||||
|
||||
### 5. Painting
|
||||
|
||||
Based on the layers, the browser generates a paint list for each layer and submits it to the compositing thread.
|
||||
|
||||
### 6. Tiling
|
||||
|
||||
The compositing thread divides the layers into tiles, which are typically 256x256 or 512x512 in size.
|
||||
|
||||
### 7. Rasterization
|
||||
|
||||
The compositing thread prioritizes generating bitmaps for tiles near the viewport, a process called rasterization. During rasterization, tiles are converted into bitmaps.
|
||||
|
||||
## Reflow and Repaint
|
||||
|
||||
### Reflow
|
||||
|
||||
When our modifications to the DOM cause changes in the geometric dimensions of DOM elements (such as modifying the width, height, or hiding elements), the browser needs to recalculate the geometric properties of the elements and then draw the results. This process is called reflow (also known as relayout).
|
||||
|
||||
Operations that trigger reflow include:
|
||||
|
||||
- Initial page rendering
|
||||
- Changes in browser window size
|
||||
- Changes in element dimensions or position
|
||||
- Changes in element content (such as text quantity or image size)
|
||||
- Changes in element font size
|
||||
- Adding or removing visible DOM elements
|
||||
- Activating CSS pseudo-classes (e.g., :hover)
|
||||
- Querying certain properties or calling certain methods
|
||||
|
||||
Some commonly used properties and methods that cause reflow:
|
||||
|
||||
- clientWidth, clientHeight, clientTop, clientLeft
|
||||
- offsetWidth, offsetHeight, offsetTop, offsetLeft
|
||||
- scrollWidth, scrollHeight, scrollTop, scrollLeft
|
||||
- scrollIntoView(), scrollIntoViewIfNeeded()
|
||||
- getComputedStyle()
|
||||
- getBoundingClientRect()
|
||||
- scrollTo()
|
||||
|
||||
### Repaint
|
||||
|
||||
When our modifications to the DOM cause style changes but do not affect its geometric properties (such as modifying color or background color), the browser does not need to recalculate the geometric properties of the element and directly draws the new style for the element. This process is called repaint.
|
||||
|
||||
Compared to reflow, repaint has a smaller performance overhead because repaint only redraws the appearance of the element without recalculating the position of the element.
|
||||
|
||||
## Why Transform is Efficient
|
||||
|
||||
Transform is implemented by creating a new composite layer, which is drawn separately and then composited with other layers. This way, when we use transform for transformations, only this composite layer needs to be redrawn, not the entire page.
|
||||
|
||||
Using transform can avoid reflow and repaint because it does not affect the layout of the DOM, only the final rendering result. This is why using transform for animations is more efficient than directly modifying properties like top and left of elements.
|
||||
|
||||
## Optimization Strategies
|
||||
|
||||
To reduce reflow and repaint, we can adopt the following strategies:
|
||||
|
||||
1. **Batch DOM modifications**: Use DocumentFragment or first set the element to display: none, then make multiple modifications, and finally display it.
|
||||
|
||||
2. **Avoid frequent reading of properties that trigger reflow/repaint**: If you must read multiple times, cache the values.
|
||||
|
||||
3. **Use transform and opacity for animation effects**: These two properties do not trigger reflow.
|
||||
|
||||
4. **Use the will-change property**: This property can inform the browser in advance of changes that will occur to the element, allowing the browser to prepare optimization work.
|
||||
|
||||
5. **Use absolute or fixed positioning**: Elements with absolute or fixed positioning only affect themselves when changed, not other elements.
|
||||
|
||||
By understanding browser rendering principles and optimization strategies, we can write more efficient frontend code and improve user experience.
|
||||
129
src/pages/blog/posts/docusaurus-v3-with-tailwindcss.md
Normal file
129
src/pages/blog/posts/docusaurus-v3-with-tailwindcss.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
layout: "@/layouts/BlogPostLayout.astro"
|
||||
title: "Using Tailwind CSS in Docusaurus v3"
|
||||
description: "This article details how to integrate and configure Tailwind CSS in Docusaurus v3, including installing dependencies, configuring tailwind.config.js, creating tailwind.css and postcss.config.js, and more."
|
||||
date: "2023-11-05"
|
||||
image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?q=80&w=1470&auto=format&fit=crop"
|
||||
tags: ["Docusaurus", "TailwindCSS", "Frontend Framework"]
|
||||
tagId: ["docusaurus", "tailwindcss", "frontend-framework"]
|
||||
category: "Frontend Development"
|
||||
categoryId: "frontend"
|
||||
readTime: "4 min read"
|
||||
---
|
||||
|
||||
## Installing Dependencies
|
||||
|
||||
```bash
|
||||
npm install tailwindcss
|
||||
```
|
||||
## Configuring tailwind.config.js
|
||||
|
||||
```js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx,md,mdx}",
|
||||
"./docs/**/*.{md,mdx}",
|
||||
],
|
||||
theme: {
|
||||
screens: {
|
||||
xs: '480px',
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
lg: '998px',
|
||||
xl: '1200px',
|
||||
'2xl': '1400px',
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: 'var(--ifm-font-family-base)',
|
||||
firacode: 'var(--font-family-firacode)',
|
||||
kaiti: 'var(--font-family-kaiti)',
|
||||
},
|
||||
colors: {
|
||||
'font-color': 'var(--ifm-font-color-base)',
|
||||
'link-color': 'var(--ifm-link-color)',
|
||||
'link-hover-color': 'var(--ifm-link-hover-color)',
|
||||
primary: 'var(--ifm-color-primary)',
|
||||
'primary-light': 'var(--ifm-color-primary-light)',
|
||||
'primary-lighter': 'var(--ifm-color-primary-lighter)',
|
||||
'primary-lightest': 'var(--ifm-color-primary-lightest)',
|
||||
'primary-dark': 'var(--ifm-color-primary-dark)',
|
||||
'primary-darker': 'var(--ifm-color-primary-darker)',
|
||||
'primary-darkest': 'var(--ifm-color-primary-darkest)',
|
||||
},
|
||||
boxShadow: {
|
||||
nysm: '0 0 2px 0 rgb(0 0 0 / 0.05)',
|
||||
ny: '0 0 3px 0 rgb(0 0 0 / 0.1), 0 0 2px - 1px rgb(0 0 0 / 0.1)',
|
||||
nymd: '0 0 6px -1px rgb(0 0 0 / 0.1), 0 0 4px -2px rgb(0 0 0 / 0.1)',
|
||||
nylg: '0 0 15px -3px rgb(0 0 0 / 0.1), 0 0 6px -4px rgb(0 0 0 / 0.1)',
|
||||
spread: '0 5px 40px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: ["class", '[data-theme="dark"]'],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
blocklist: ["container"],
|
||||
}
|
||||
```
|
||||
## Creating tailwind.css
|
||||
|
||||
Create a `tailwind.css` file in the `src/css` directory and add the following content:
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
```
|
||||
**Note: After creation, you need to import tailwind.css in custom.css, otherwise the tailwind styles will not take effect**
|
||||
|
||||
## Creating postcss.config.js
|
||||
|
||||
Create a `postcss.config.js` file in the project root directory and add the following content:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Importing tailwind.css in custom.css
|
||||
|
||||
Find the `src/css/custom.css` file in your project and add the following content at the top of the file:
|
||||
|
||||
```css
|
||||
@import './tailwind.css';
|
||||
```
|
||||
|
||||
## Using Tailwind CSS
|
||||
|
||||
Now, you can use Tailwind CSS styles in your project. For example:
|
||||
|
||||
```jsx
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-gray-100 dark:bg-gray-800 rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold text-primary mb-2">Hello Tailwind CSS</h2>
|
||||
<p className="text-gray-700 dark:text-gray-300">This is a paragraph styled with Tailwind CSS</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. Since Docusaurus already has its own CSS styles, we disabled `preflight` in the Tailwind CSS configuration to avoid style conflicts.
|
||||
|
||||
2. We used the `darkMode: ["class", '[data-theme="dark"]']` configuration to make Tailwind CSS's dark mode consistent with Docusaurus's dark mode.
|
||||
|
||||
3. We added some variables in `theme.extend.colors` that use Docusaurus's CSS variables, making Tailwind CSS colors consistent with Docusaurus theme colors.
|
||||
|
||||
4. We added `container` to the `blocklist` because Docusaurus already has its own container styles, and we don't want Tailwind CSS's container styles to override them.
|
||||
|
||||
## Summary
|
||||
|
||||
Through the above steps, we have successfully integrated Tailwind CSS into Docusaurus v3. Now, we can use all the features of Tailwind CSS while maintaining consistency with the Docusaurus theme.
|
||||
|
||||
I hope this article has been helpful to you! If you have any questions, feel free to leave a comment.
|
||||
152
src/pages/blog/posts/git-commit-convention.md
Normal file
152
src/pages/blog/posts/git-commit-convention.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
layout: "@/layouts/BlogPostLayout.astro"
|
||||
title: "Git Commit Convention"
|
||||
description: "This article details the Git commit convention, including the specific content and usage rules of the Header, Body, and Footer sections, helping team members better understand and follow a unified commit standard."
|
||||
date: "2023-10-20"
|
||||
image: "https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?q=80&w=1476&auto=format&fit=crop"
|
||||
tags: ["Git", "Convention", "Version Control"]
|
||||
tagId: ["git", "convention", "version-control"]
|
||||
category: "Development Tools"
|
||||
categoryId: "dev-tools"
|
||||
readTime: "5 min read"
|
||||
---
|
||||
|
||||
### Why We Need Commit Conventions
|
||||
|
||||
In team collaboration, if Git commit messages are precise, they become traceable during later collaboration and bug handling. Project development can quickly generate development logs based on standardized commit messages, making it easier for developers or users to track project development information and feature updates.
|
||||
|
||||
### Structure of Commit Messages
|
||||
|
||||
A commit message consists of three parts: Header, Body, and Footer.
|
||||
|
||||
```
|
||||
<Header>
|
||||
|
||||
<Body>
|
||||
|
||||
<Footer>
|
||||
```
|
||||
|
||||
#### Header
|
||||
|
||||
The Header section includes three fields: type (required), scope (optional), and subject (required).
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
|
||||
##### type
|
||||
|
||||
The type indicates the category of the commit and only allows the use of the following seven identifiers:
|
||||
|
||||
- **feat**: new feature
|
||||
- **fix**: bug fix
|
||||
- **docs**: documentation
|
||||
- **style**: formatting (changes that do not affect code execution)
|
||||
- **refactor**: code changes that neither add features nor fix bugs
|
||||
- **test**: adding tests
|
||||
- **chore**: changes to the build process or auxiliary tools
|
||||
|
||||
If the type is feat or fix, the commit will definitely appear in the Change log. For other cases (docs, chore, style, refactor, test), it's up to you whether to include them in the Change log, though it's recommended not to.
|
||||
|
||||
##### scope
|
||||
|
||||
The scope indicates the range affected by the commit, such as data layer, control layer, view layer, etc., which varies depending on the project.
|
||||
|
||||
##### subject
|
||||
|
||||
The subject is a brief description of the commit's purpose, not exceeding 50 characters.
|
||||
|
||||
- Start with a verb in the present tense of the first person, such as "change" instead of "changed" or "changes"
|
||||
- First letter lowercase
|
||||
- No period (.) at the end
|
||||
|
||||
#### Body
|
||||
|
||||
The Body section is a detailed description of this commit and can be divided into multiple lines.
|
||||
|
||||
There are two points to note:
|
||||
|
||||
- Use the present tense of the first person, such as "change" instead of "changed" or "changes"
|
||||
- Should explain the motivation for the code change and the comparison with previous behavior
|
||||
|
||||
#### Footer
|
||||
|
||||
The Footer section is only used for two situations:
|
||||
|
||||
##### Breaking Changes
|
||||
|
||||
If the current code is incompatible with the previous version, the Footer section should start with BREAKING CHANGE, followed by a description of the change, the reason for the change, and the migration method.
|
||||
|
||||
```
|
||||
BREAKING CHANGE: isolate scope bindings definition has changed.
|
||||
|
||||
To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
scope: {
|
||||
myAttr: 'attribute',
|
||||
}
|
||||
|
||||
After:
|
||||
|
||||
scope: {
|
||||
myAttr: '@',
|
||||
}
|
||||
|
||||
The removed `inject` wasn't generaly useful for directives so there should be no code using it.
|
||||
```
|
||||
|
||||
##### Closing Issues
|
||||
|
||||
If the current commit addresses a specific issue, you can close this issue in the Footer section.
|
||||
|
||||
```
|
||||
Closes #234
|
||||
```
|
||||
|
||||
You can also close multiple issues at once.
|
||||
|
||||
```
|
||||
Closes #123, #245, #992
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
feat(browser): onUrlChange event (popstate/hashchange/polling)
|
||||
|
||||
Added new event to browser:
|
||||
- forward popstate event if available
|
||||
- forward hashchange event if popstate not available
|
||||
- do polling when neither popstate nor hashchange available
|
||||
|
||||
Closes #123, #305, #392
|
||||
```
|
||||
|
||||
```
|
||||
fix(release): need to depend on latest rxjs and zone.js
|
||||
|
||||
The version in our package.json gets copied to the one we publish, and users need the latest of these.
|
||||
```
|
||||
|
||||
```
|
||||
docs(changelog): update change log to beta.5
|
||||
```
|
||||
|
||||
```
|
||||
style(router): fix linting errors
|
||||
```
|
||||
|
||||
```
|
||||
refactor(core): rename onInit to ngOnInit
|
||||
```
|
||||
|
||||
```
|
||||
chore(deps): bump rxjs from 5.0.0-beta.2 to 5.0.0-beta.6
|
||||
```
|
||||
|
||||
### Summary
|
||||
|
||||
Standardized Git commit messages can help team members better understand and follow unified commit standards, improve team collaboration efficiency, and also provide convenience for later project maintenance.
|
||||
352
src/pages/blog/posts/gitea-actions-deployment-automation.md
Normal file
352
src/pages/blog/posts/gitea-actions-deployment-automation.md
Normal file
@@ -0,0 +1,352 @@
|
||||
---
|
||||
layout: "@/layouts/BlogPostLayout.astro"
|
||||
title: "Creating Automated Deployment Workflows with Gitea Actions"
|
||||
description: "Using a frontend project deployment as an example, this article provides a step-by-step guide on how to create automated deployment workflows using Gitea actions. For those who don't want to use Jenkins, Gitea actions is a good CI/CD solution."
|
||||
date: "2023-12-15"
|
||||
image: "https://images.unsplash.com/photo-1607799279861-4dd421887fb3?q=80&w=1470&auto=format&fit=crop"
|
||||
tags: ["Gitea", "Actions", "CI/CD", "Automated Deployment"]
|
||||
tagId: ["gitea", "actions", "cicd", "automation-deployment"]
|
||||
category: "DevOps"
|
||||
categoryId: "devops"
|
||||
readTime: "10 min read"
|
||||
---
|
||||
Using a frontend project deployment as an example, this article will provide a step-by-step guide on how to create automated deployment workflows using Gitea actions. For those who don't want to use Jenkins, Gitea actions is a good `CI/CD` solution.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Introduction
|
||||
After developing a project, we need to deploy it to a server so that users can access it. However, manual deployment for each update wastes time and energy. Therefore, we can use Gitea actions to create an automated workflow for quick project deployment.
|
||||
|
||||
## What is Gitea Actions
|
||||
Gitea actions is a feature of Gitea that allows us to create automated workflows for quick project deployment. Gitea actions can perform various tasks such as building, testing, and deploying. Gitea actions can use various programming languages and tools, such as Python, Java, Docker, etc. Additionally, Gitea actions is configured to be compatible with GitHub actions configuration as much as possible, so if you are already familiar with GitHub actions, you can also easily use Gitea actions.
|
||||
For more details, see the official documentation: [Gitea actions](https://docs.gitea.com/en/usage/actions/overview)
|
||||
|
||||
## Preparation
|
||||
Before we start creating this workflow, you need to prepare the following materials:
|
||||
1. A server, preferably with root privileges (for installing tools)
|
||||
2. A deployed Gitea repository, version at least >= `v1.19`, officially strongly recommended to use `v1.20` or higher
|
||||
3. Docker environment (not required, used on the host where the runner is deployed)
|
||||
|
||||
> Note: The author has prepared: one build server + one deployment (target) server. You can use just one server, but be mindful of environment isolation.
|
||||
|
||||
Please ensure you have completed all preparations before proceeding with subsequent steps. For specific installation and configuration, please refer to the official documentation, which I won't repeat here. [Installation](https://docs.gitea.com/en/category/installation)
|
||||
|
||||
If you use the **1panel panel**, you can complete the installation with one click from the app store, which is what the author used.
|
||||
|
||||
>Note: In versions < `v1.21.0`, you need to enable the `actions` feature in the configuration file. Please refer to the official documentation [Configuration](https://docs.gitea.com/en/usage/actions/quickstart). In later versions, it is enabled by default.
|
||||
|
||||
This tutorial uses Gitea version `v1.22.6`!
|
||||
|
||||
|
||||
## Configuring Runner
|
||||
In Gitea actions, running Jobs is done through Runners, so we need to configure a Runner first for our subsequent deployment workflow. There are two installation methods: you can install directly on the host or through a docker container. Here, the author will install through docker. To avoid excessive resource consumption, you can install it on a separate host (PS: For users in China, you can choose a Hong Kong server, which will make running jobs faster 😄). The author chose a Hong Kong VPS as the host.
|
||||
|
||||
### Downloading act_Runner
|
||||
|
||||
First, we need to download this tool to register or generate configurations [Download Link](https://gitea.com/gitea/act_runner/releases). Please select the version corresponding to your system on this page for download. After downloading, upload it to the server. Here, I uploaded it to the `/gitea_runner2` directory on the server. You can also use tools like `curl` to download directly. You can use the following command, but pay attention to the name and download link. My system is `Debian12 x86_64`, so I downloaded `act_runner-0.2.11-linux-amd64`.
|
||||
|
||||
```bash
|
||||
curl -o act_runner2 https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
|
||||
```
|
||||
After completion, set the execution permissions
|
||||
```bash
|
||||
chmod +x act_runner2 # Note the filename, please modify according to your actual situation
|
||||
|
||||
./act_runner --version # Seeing the version information means success
|
||||
```
|
||||
### Generating Configuration
|
||||
```
|
||||
./act_runner2 generate-config > config.yaml
|
||||
```
|
||||
Generally, the default configuration is sufficient. If you have other requirements, please modify `config.yaml` yourself.
|
||||
|
||||
**Since the author chose to install via docker, if you choose to install directly on the host, you can skip the subsequent installation steps and refer to the official documentation for installation [Direct Installation](https://docs.gitea.com/en/usage/actions/quickstart)**
|
||||
|
||||
### Registering Runner
|
||||
|
||||
Here I choose to register through the `docker-compose.yml` configuration method
|
||||
|
||||
**Note: Modify the parameters in the configuration according to your own situation. The following configuration is for reference only!!!**
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
environment:
|
||||
CONFIG_FILE: /config.yaml
|
||||
GITEA_INSTANCE_URL: "<your_gitea.com>"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "<your_registration_token_here>"
|
||||
GITEA_RUNNER_NAME: "act_vps_runner2"
|
||||
GITEA_RUNNER_LABELS: "act_vps_runner2"
|
||||
volumes:
|
||||
- /gitea_runner2/config.yaml:/config.yaml
|
||||
- /gitea_runner2/runner_data/data:/data
|
||||
- /gitea_runner2/runner_data/cache:/root/.cache
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
### Configuration Explanation
|
||||
|
||||
**GITEA_RUNNER_REGISTRATION_TOKEN** This parameter is the registration token, which needs to be obtained from your gitea. There are three levels in total. I directly chose the organization level, which can be obtained from the organization settings page, as shown in the following figure:
|
||||

|
||||
|
||||
For other instance configuration pages and explanations, please refer to the official documentation, which has detailed explanations.
|
||||
|
||||
**If you cannot see the settings page, please ensure that you have the correct permissions and that Actions is enabled. Check the Gitea version!!!**
|
||||
|
||||
### Running and Generating Runner
|
||||
|
||||
Since the author uses the 1panel panel, it can be directly orchestrated in the container options. If you don't use this type of visualization tool, you need to manually execute `docker-compose up -d`
|
||||
|
||||
After success, you can check the container's operation and logs. The logs will display the registration success information, as shown in the following figures:
|
||||
|
||||

|
||||

|
||||
|
||||
Then we return to the page where you obtained the runner token, refresh it, and you should see your runner, as shown in the following figure:
|
||||

|
||||
The name in the figure is based on the name you set during registration configuration.
|
||||
|
||||
> At this point, our runner deployment is complete, and we can now use it in projects.
|
||||
|
||||
## Using Actions
|
||||
|
||||
The usage of gitea actions is similar to github actions. If you are not familiar with this type of configuration, you can first learn about **github actions**.
|
||||
The author will use a `vitepress` project as an example.
|
||||
|
||||
### Creating Configuration Files
|
||||
1. Create a `.gitea/workflows` directory in the project root directory
|
||||
2. Create a `deploy.yaml` configuration file in this directory
|
||||
3. Let's first write a test configuration to test environment variables, as shown below:
|
||||
```yaml
|
||||
name: Deploy Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Print Environment Variables
|
||||
run: env
|
||||
|
||||
- name: Print Current Directory
|
||||
run: pwd
|
||||
|
||||
- name: List Files in Working Directory
|
||||
run: ls -la
|
||||
```
|
||||
Then commit the code to the repository, and we should be able to see the execution task in the repository's actions, as shown in the following figure:
|
||||

|
||||
|
||||
> **PS: If you don't see actions execution, or even don't see the actions option, you may need to enable the actions option in the repository, organization, or management page**
|
||||
### Configuring Build
|
||||
We modify the `deploy.yaml` configuration file to add build steps. I'm using pnpm here, please adjust some configurations according to your actual situation. I won't elaborate too much here.
|
||||
|
||||
````yaml
|
||||
name: Deploy docs for project
|
||||
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
steps:
|
||||
# TODO Temporary solution for https://github.com/go-gitea/gitea/issues/32481, waiting for gitea to release 1.23.0 for fix
|
||||
- name: Install git
|
||||
run: |
|
||||
if ! command -v git &> /dev/null; then
|
||||
apt-get update && apt-get install -y git
|
||||
else
|
||||
echo "git is already installed"
|
||||
fi
|
||||
|
||||
- name: Checkout repository code
|
||||
uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
ref: 'main'
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
# Skip pnpm cache to reduce overhead
|
||||
- name: Install pnpm
|
||||
run: |
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
npm install -g pnpm@9
|
||||
else
|
||||
echo "pnpm is already installed"
|
||||
fi
|
||||
|
||||
- name: Check Node.js and pnpm versions
|
||||
run: |
|
||||
echo "pnpm version ===>" && pnpm -v
|
||||
echo "node version ===>" && node -v
|
||||
|
||||
# Simplify node_modules handling without caching
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
|
||||
echo "Dependencies are outdated or missing, installing..."
|
||||
pnpm install
|
||||
else
|
||||
echo "Dependencies are up-to-date, skipping installation."
|
||||
fi
|
||||
|
||||
- name: Build docs
|
||||
run: pnpm run docs:build
|
||||
````
|
||||
|
||||
> **Special note: Installing git is to fix the current issue in gitea actions where code cannot be checked out through `actions/checkout`. This issue is expected to be fixed in `v1.23.0`. Currently, we can only handle it this way. But from the author's observation, it seems to be related to the image version used. If it's `ubuntu-latest`, you might not need to install it. Please base it on your actual situation.**
|
||||
|
||||
If everything goes smoothly, you should see the build task completed in the workflow, as shown in the figure:
|
||||

|
||||
|
||||
### Deploying the Project
|
||||
|
||||
The principle of code deployment is actually to connect to the target server via `ssh` and execute deployment scripts. Therefore, we need to configure SSH first, then use the `actions` [easingthemes/ssh-deploy](https://github.com/easingthemes/ssh-deploy) to connect to the target server and push the code.
|
||||
|
||||
#### Configuring SSH
|
||||
This step is actually to configure a user for the server and support `SSH` login, which is password-free login. Therefore, we use the following commands in sequence on the **target server**. The author is using `Debian 12`, please adjust the commands according to your actual situation!
|
||||
|
||||
##### Creating a User (It is recommended to create a user to operate the deployment directory, do not use the root user!!!)
|
||||
```bash
|
||||
sudo adduser deployuser # Please modify the username according to your actual situation
|
||||
```
|
||||
After successful creation, you can check if the `/home/deployuser` directory exists. If it doesn't exist, you may need to create it manually.
|
||||
|
||||
##### Creating Key Pairs
|
||||
We need to first switch to the `deployuser` user, then execute the following commands:
|
||||
```bash
|
||||
# 1. Create public and private keys
|
||||
ssh-keygen -t rsa -b 4096 -C ""
|
||||
# Add the public key content to the authorized_keys file
|
||||
echo ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
|
||||
# Set correct permissions
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
chmod 700 ~/.ssh
|
||||
```
|
||||
##### Granting Read and Write Permissions to Specific Directories (Can be skipped)
|
||||
This requires a user with `root` privileges to operate:
|
||||
```bash
|
||||
# 1. Create directory
|
||||
mkdir -p /var/www/html/project
|
||||
# 2. Modify directory permissions
|
||||
sudo chown deployuser:deployuser /var/www/project
|
||||
sudo chmod 700 /var/www/project
|
||||
```
|
||||
Since the author used `1panel` to manage websites, the directory is automatically generated, which differs from this, but the process steps are the same. Please modify according to your actual situation.
|
||||
|
||||
#### Configuring Key Information in Gitea
|
||||
|
||||
1. In your gitea's personal, organization, or management actions page, click `Secrets`, copy the private key we just generated on the server to the `Secret`, and then click `Add Secret`.
|
||||
2. Since we will need the server IP and username later, it is also recommended to configure them in secrets or variables for later use.
|
||||
|
||||
Here, the author has configured the private key, server IP, and username in secrets, as shown in the figure:
|
||||

|
||||
|
||||
At this point, we have completed the configuration required for deploying the project, and we can now start the deployment process.
|
||||
|
||||
> PS: Since `easingthemes/ssh-deploy` needs to use `rsync` to execute script commands, this tool is required in both the image container and the target server. If it doesn't exist, you need to install it in advance, especially on the **target server**. Generally, this needs to be done by a root user or a user with installation permissions.
|
||||
|
||||
### Project Deployment
|
||||
|
||||
We add the following code to `deploy.yml`, pay attention to the format!!!
|
||||
|
||||
````yaml
|
||||
- name: ssh deploy
|
||||
uses: easingthemes/ssh-deploy@v5.1.0
|
||||
with:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
|
||||
SOURCE: "/.vitepress/dist/"
|
||||
TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # Please modify to your deployment directory according to your actual situation
|
||||
EXCLUDE: "/node_modules/"
|
||||
````
|
||||
If everything goes smoothly, you will see the actions executed successfully, and there will be a dist directory in your deployment directory, as shown in the following figures:
|
||||

|
||||

|
||||
|
||||
Next, you just need to use tools like `nginx` to point to this directory, and it can be accessed.
|
||||
|
||||
> PS: If you encounter errors in the above step, please carefully check the error logs. Based on the author's experience, it's most likely due to issues with SSH keys and permission handling. If you're not using `ubuntu-latest`, you may also need to install some necessary tools according to the error messages, such as `git`, `rsync`, etc.
|
||||
|
||||
|
||||
### Complete Configuration
|
||||
|
||||
````yaml
|
||||
name: Deploy docs for project
|
||||
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
steps:
|
||||
- name: Checkout repository code
|
||||
uses: https://gitea.com/actions/checkout@v4
|
||||
with:
|
||||
ref: 'main'
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
# Skip pnpm cache to reduce overhead
|
||||
- name: Install pnpm
|
||||
run: |
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
npm install -g pnpm@9
|
||||
else
|
||||
echo "pnpm is already installed"
|
||||
fi
|
||||
|
||||
- name: Check Node.js and pnpm versions
|
||||
run: |
|
||||
echo "pnpm version ===>" && pnpm -v
|
||||
echo "node version ===>" && node -v
|
||||
|
||||
# Simplify node_modules handling without caching
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
|
||||
echo "Dependencies are outdated or missing, installing..."
|
||||
pnpm install
|
||||
else
|
||||
echo "Dependencies are up-to-date, skipping installation."
|
||||
fi
|
||||
|
||||
- name: Build docs
|
||||
run: pnpm run docs:build
|
||||
|
||||
- name: ssh deploy
|
||||
uses: easingthemes/ssh-deploy@v5.1.0
|
||||
with:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
|
||||
SOURCE: "/.vitepress/dist/"
|
||||
TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # Please modify to your deployment directory according to your actual situation
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
````
|
||||
83
src/pages/blog/posts/javascript-map-parseint-trap.md
Normal file
83
src/pages/blog/posts/javascript-map-parseint-trap.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
layout: "@/layouts/BlogPostLayout.astro"
|
||||
title: "The Trap of Using map with parseInt in JavaScript"
|
||||
description: "This article analyzes in detail why [\"1\",\"2\",\"3\"].map(parseInt) returns [1, NaN, NaN] in JavaScript, revealing this common JavaScript trap by examining how map and parseInt methods work."
|
||||
date: "2023-09-15"
|
||||
image: "https://images.unsplash.com/photo-1579468118864-1b9ea3c0db4a?q=80&w=1470&auto=format&fit=crop"
|
||||
tags: ["JavaScript", "Functional Programming", "Traps"]
|
||||
tagId: ["javascript", "functional-programming", "trap"]
|
||||
category: "Frontend Development"
|
||||
categoryId: "frontend"
|
||||
readTime: "3 min read"
|
||||
---
|
||||
|
||||
In JavaScript, there's a classic question:
|
||||
|
||||
```javascript
|
||||
["1","2","3"].map(parseInt)
|
||||
```
|
||||
|
||||
What is the return value of this code? Many people might think it's `[1, 2, 3]`, but it's actually `[1, NaN, NaN]`. Why is that? Let's analyze it together.
|
||||
|
||||
### How the map Method Works
|
||||
|
||||
First, we need to understand how the `map` method works. The `map` method calls the provided function once for each element in the array and creates a new array with the return values of the function.
|
||||
|
||||
The callback function for the `map` method accepts three parameters:
|
||||
|
||||
1. `currentValue`: The current element being processed
|
||||
2. `index`: The index of the current element
|
||||
3. `array`: The array that called the `map` method
|
||||
|
||||
So, when we execute `["1","2","3"].map(parseInt)`, we're actually executing:
|
||||
|
||||
```javascript
|
||||
["1","2","3"].map((item, index, array) => parseInt(item, index, array))
|
||||
```
|
||||
|
||||
### How the parseInt Method Works
|
||||
|
||||
Next, we need to understand how the `parseInt` method works. The `parseInt` method parses a string argument and returns an integer of the specified radix.
|
||||
|
||||
The `parseInt` method accepts two parameters:
|
||||
|
||||
1. `string`: The value to be parsed. If the argument is not a string, it's converted to a string. Leading whitespace in the string is ignored.
|
||||
2. `radix`: An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the above string. For example, specifying 10 means to use the decimal number system. Always specifying this parameter can eliminate confusion when reading the code and ensure predictable conversion results. When the radix is not specified, different implementations will produce different results, usually defaulting to 10.
|
||||
|
||||
### Analyzing the Problem
|
||||
|
||||
Now, let's analyze the execution process of `["1","2","3"].map(parseInt)`:
|
||||
|
||||
1. For the first element "1", the `map` method calls `parseInt("1", 0, ["1","2","3"])`. Since `radix` is 0, `parseInt` will determine the radix based on the string's prefix. If the string doesn't start with "0x" or "0", the radix is 10. So, `parseInt("1", 0)` returns 1.
|
||||
|
||||
2. For the second element "2", the `map` method calls `parseInt("2", 1, ["1","2","3"])`. Since `radix` is 1, and the valid `radix` range is 2-36, `parseInt("2", 1)` returns `NaN`.
|
||||
|
||||
3. For the third element "3", the `map` method calls `parseInt("3", 2, ["1","2","3"])`. Since `radix` is 2, representing binary, and in binary, only 0 and 1 are valid, `parseInt("3", 2)` returns `NaN`.
|
||||
|
||||
Therefore, the return value of `["1","2","3"].map(parseInt)` is `[1, NaN, NaN]`.
|
||||
|
||||
### How to Use Correctly
|
||||
|
||||
If we want to convert a string array to a number array, we can use the following methods:
|
||||
|
||||
```javascript
|
||||
["1","2","3"].map(Number) // [1, 2, 3]
|
||||
```
|
||||
|
||||
Or use an arrow function:
|
||||
|
||||
```javascript
|
||||
["1","2","3"].map(item => parseInt(item)) // [1, 2, 3]
|
||||
```
|
||||
|
||||
Or use the bound function of `parseInt`:
|
||||
|
||||
```javascript
|
||||
["1","2","3"].map(parseInt.bind(null)) // [1, 2, 3]
|
||||
```
|
||||
|
||||
### Summary
|
||||
|
||||
This problem reveals a common trap in functional programming in JavaScript. When using higher-order functions like `map`, we need to pay attention to the parameters and behavior of the callback function. In this example, the parameters accepted by the `parseInt` function don't completely match the parameters provided by the `map` method, leading to unexpected results.
|
||||
|
||||
Understanding this problem helps us better understand functional programming and type conversion in JavaScript.
|
||||
Reference in New Issue
Block a user