Skip to content

Commit 7cde161

Browse files
authored
feat: support deployment for github pages (#7052)
1 parent f098a7d commit 7cde161

File tree

6 files changed

+212
-19
lines changed

6 files changed

+212
-19
lines changed

.changeset/gold-pumas-perform.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/app-tools': patch
3+
---
4+
5+
feat: support deployment for github pages
6+
feat: 支持使用 github pages 部署

packages/document/main-doc/docs/en/guides/basic-features/deploy.mdx

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ sidebar_position: 15
66

77
Currently, Modern.js offers two deployment way:
88
- You can host your application in a container that includes a Node.js environment on your own, which provides flexibility for the deployment of the application.
9-
- You can also deploy your application through a platform. Currently, Modern.js supports the [Netlify](https://www.netlify.com/) and [Vercel](https://vercel.com/).
9+
- You can also deploy your application through a platform. Currently, Modern.js officially supports deployment on [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), and [Github pages](https://pages.github.com/).
1010

1111
:::info
1212
Currently, Modern.js only supports running in a Node.js environment. Support for more runtime environments will be provided in the future.
@@ -110,6 +110,11 @@ Add the following content to `netlify.toml`:
110110
command = "modern deploy"
111111
```
112112

113+
:::info
114+
You can refer to the [deployment project example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
115+
116+
:::
117+
113118
Now, add a project to the Netlify platform and deploy it!
114119

115120
### Full Stack Project
@@ -129,7 +134,8 @@ Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. Th
129134
```
130135

131136
:::info
132-
Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
137+
1. Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
138+
2. You can refer to the [deployment project example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr).
133139
:::
134140

135141

@@ -213,21 +219,24 @@ Commit your project to git, select Framework Preset as `Other` on the Vercel pla
213219

214220
<img src="https://sf16-sg.tiktokcdn.com/obj/eden-sg/lmeh7nuptpfnuhd/vercel-framework-preset.png" />
215221

222+
:::info
223+
You can refer to the [deployment project examples](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
224+
225+
:::
226+
216227
### Full Stack Project
217228

218229
Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. These projects need to be deployed on **Vercel Functions**.
219230

220231
In addition to configuring `vercel.json` in the same way as a [pure front-end project](#pure-front-end-project), there are two points to note for full-stack projects:
221232

222233
1. Currently, Modern.js does not support deploying BFF projects on the Vercel platform. We will support it in future versions.
223-
2. When deploying on Vercel platform, the default node runtime is `20.x`, it is recommended to choose `18.x` when deploying full-stack projects,
224-
see [Serverless Function contains invalid runtime error](https://vercel.com/guides/serverless-function-contains-invalid-runtime-error), you can modify `package.json` to specify the version:
225-
```json title="package.json"
226-
"engines": {
227-
"node": "18.x"
228-
}
229-
```
234+
2. The Node.js version for function execution is determined by the project configuration on the Vercel platform.
230235

236+
:::info
237+
You can refer to the [deployment project examples](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr).
238+
239+
:::
231240

232241
### Monorepo
233242

@@ -284,6 +293,48 @@ Add the following content to the `packages/app/vercel.json` file:
284293

285294
Just submit your code and deploy it using the Vercel platform.
286295

296+
## Github Pages
297+
298+
If you're creating a GitHub Pages for a repository without a custom domain, the page URL will follow this format: `http://<username>.github.io/<repository-name>`. Therefore, you need to add the following configuration in `modern.config.ts`:
299+
300+
```
301+
import { defineConfig } from '@modern-js/app-tools';
302+
303+
export default defineConfig({
304+
//...
305+
server: {
306+
baseUrl: "/<repository-name>"
307+
},
308+
output: {
309+
assetPrefix: "/<repository-name>",
310+
}
311+
});
312+
```
313+
314+
GitHub Pages supports two deployment ways: branch deployment or GitHub Actions deployment.
315+
316+
For branch deployment, follow these steps:
317+
318+
1. In the GitHub repository, navigate to Settings > Pages > Source > Deploy from a branch
319+
2. Install the `gh-pages` as devDependency
320+
3. Add the following script to `package.json`
321+
```
322+
"scripts": {
323+
//...
324+
"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"
325+
}
326+
```
327+
328+
4. Run `npm run deploy:gh-pages`
329+
330+
:::info
331+
1. Running `MODERNJS_DEPLOY=ghPages modern deploy` will build the production output for GitHub in the .output directory.
332+
2. You can refer to the [project](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
333+
:::
334+
335+
For GitHub Actions deployment, select Settings > Pages > Source > GitHub Actions, and add a workflow file to the project. You can refer to the [example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
336+
337+
287338
## Using Self-Built Node.js Server
288339

289340
Typically, we recommend using the built-in Node.js server of Modern.js to deploy applications. It supports hosting both pure frontend and full-stack projects, ensuring consistent performance in both development and production environments.

packages/document/main-doc/docs/zh/guides/basic-features/deploy.mdx

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar_position: 15
77
目前,Modern.js 提供了两种部署方式:
88

99
- 你可以将应用自行托管在包含 Node.js 环境的容器中,这为应用提供了部署的灵活性。
10-
- 你也可以通过平台部署应用,目前 Modern.js 官方支持了 [Netlify](https://www.netlify.com/)[Vercel](https://vercel.com/) 平台。
10+
- 你也可以通过平台部署应用,目前 Modern.js 官方支持了 [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/)[Github pages](https://pages.github.com/) 平台。
1111

1212
:::info
1313
目前 Modern.js 仅支持在 Node.js 环境中运行,未来将提供更多运行时环境的支持。
@@ -26,6 +26,7 @@ MODERNJS_DEPLOY=netlify npx modern deploy
2626
在 Modern.js 官方支持的部署平台中部署时,无需指定环境变量。
2727
:::
2828

29+
2930
## ModernJS 内置 Node.js 服务器
3031

3132
### 单仓库项目
@@ -107,6 +108,11 @@ Netlify 是一个流行的 Web 开发平台,专为构建、发布和维护现
107108
command = "modern deploy"
108109
```
109110

111+
:::info
112+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
113+
114+
:::
115+
110116
在 Netlify 平台上添加项目,部署即可。
111117

112118
### 全栈项目
@@ -126,10 +132,14 @@ Netlify 是一个流行的 Web 开发平台,专为构建、发布和维护现
126132
```
127133

128134
:::info
129-
目前 Modern.js 还不支持在 Netlify Edge Functions 进行部署,我们将在后续的版本中支持。
135+
1. 目前 Modern.js 还不支持在 Netlify Edge Functions 进行部署,我们将在后续的版本中支持。
136+
2. 你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr)
137+
130138
:::
131139

132140

141+
142+
133143
### Monorepo 项目
134144

135145
:::info
@@ -207,19 +217,25 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
207217

208218
<img src="https://sf16-sg.tiktokcdn.com/obj/eden-sg/lmeh7nuptpfnuhd/vercel-framework-preset.png" />
209219

220+
:::info
221+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
222+
223+
:::
224+
210225
### 全栈项目
211226

212227
全栈项目是指使用了自定义 Web Server、SSR、BFF 的项目,这些项目需要部署在 **Vercel Functions** 上。
213228

214229
全栈项目除了按照[纯前端项目](#纯前端项目)的方式配置 `vercel.json` 外,有两点需要注意:
215230

216231
1. 当前,Modern.js 还不支持在 Vercel 平台上部署 BFF 项目,我们将在后续的版本中支持。
217-
2. Vercel 平台部署时,默认 node 运行时为 `20.x`,部署全栈项目时建议选择 `18.x`,具体原因详见[Serverless Function contains invalid runtime error](https://vercel.com/guides/serverless-function-contains-invalid-runtime-error),你可以修改 `package.json` 指定版本:
218-
```json title="package.json"
219-
"engines": {
220-
"node": "18.x"
221-
}
222-
```
232+
2. 函数运行的 node.js 版本由项目在 Vercel 平台配置决定。
233+
234+
235+
:::info
236+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr)
237+
238+
:::
223239

224240
### Monorepo 项目
225241

@@ -273,6 +289,38 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
273289

274290
提交你的代码,使用 Vercel 平台部署即可。
275291

292+
## Github Pages
293+
294+
如果你要为一个仓库常见 Github 页面,并且你没有自定义域名,则该页面的 URL 将会是以下格式:`http://<username>.github.io/<repository-name>`,所以需要在 `modern.config.ts` 中添加
295+
以下配置:
296+
```ts
297+
import { defineConfig } from '@modern-js/app-tools';
298+
299+
export default defineConfig({
300+
//...
301+
server:{
302+
baseUrl: "/<repository-name>"
303+
},
304+
output: {
305+
assetPrefix: "/<repository-name>",
306+
}
307+
});
308+
```
309+
310+
Github Pages 支持两种部署方式,通过分支部署或通过 Github Actions 部署,如果通过分支部署,可以使用以下步骤:
311+
1. 在 github 仓库中,选择 `Settings > Pages > Source > Deploy from a branch`
312+
2. 安装 `gh-pages` 依赖作为开发依赖。
313+
3. 在 package.json 的 `scripts` 中添加 `"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"`
314+
4. 执行 `npm run deploy:gh-pages`
315+
316+
:::info
317+
1. 执行 `MODERNJS_DEPLOY=ghPages modern deploy`,Modern.js 会把可用于 github 部署的产物构建到 `.output` 目录。
318+
2. 可以参考项目[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
319+
320+
:::
321+
322+
如果通过 Github Actions 部署,可以选择 Settings > Pages > Source > GitHub Actions,并在项目中添加 workflow 文件,可参考[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
323+
276324

277325
## 自建 Node.js 服务器
278326

packages/solutions/app-tools/src/plugins/deploy/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import type {
55
CliPluginFuture,
66
} from '../../types';
77
import type { AppToolsContext } from '../../types/new';
8+
import { createGhPagesPreset } from './platforms/gh-pages';
89
import { createNetlifyPreset } from './platforms/netlify';
910
import { createNodePreset } from './platforms/node';
1011
import { createVercelPreset } from './platforms/vercel';
1112
import { getProjectUsage } from './utils';
12-
1313
type DeployPresetCreators = {
1414
node: typeof createNodePreset;
1515
vercel: typeof createVercelPreset;
1616
netlify: typeof createNetlifyPreset;
17+
ghPages: typeof createGhPagesPreset;
1718
};
1819

1920
type DeployTarget = keyof DeployPresetCreators;
@@ -22,6 +23,7 @@ const deployPresets: DeployPresetCreators = {
2223
node: createNodePreset,
2324
vercel: createVercelPreset,
2425
netlify: createNetlifyPreset,
26+
ghPages: createGhPagesPreset,
2527
};
2628

2729
async function getDeployPreset(
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import path from 'path';
2+
import type { ServerRoute } from '@modern-js/types';
3+
import { fs } from '@modern-js/utils';
4+
import { logger } from '@modern-js/utils';
5+
import type { CreatePreset } from './platform';
6+
7+
async function reorganizeHtmlFiles(
8+
routes: ServerRoute[],
9+
baseDir: string,
10+
baseUrl = '/',
11+
) {
12+
if (!routes || !Array.isArray(routes)) {
13+
logger.error('Invalid server routes');
14+
return;
15+
}
16+
17+
await fs.ensureDir(baseDir);
18+
19+
const baseUrlRegexp = new RegExp(`^${baseUrl}\\/?`);
20+
21+
const collectedEntryPaths = new Set<string>();
22+
23+
const copyPromises = routes.map(async route => {
24+
const { urlPath, entryPath } = route;
25+
26+
if (!entryPath) {
27+
logger.warn(`Route ${urlPath} does not specify entryPath, skipping`);
28+
return;
29+
}
30+
31+
if (collectedEntryPaths.has(entryPath)) {
32+
return;
33+
}
34+
35+
collectedEntryPaths.add(entryPath);
36+
37+
const sourceHtmlPath = path.join(baseDir, entryPath);
38+
39+
if (!(await fs.pathExists(sourceHtmlPath))) {
40+
logger.error(`Source HTML file does not exist: ${sourceHtmlPath}`);
41+
return;
42+
}
43+
44+
const targetDir = urlPath.replace(baseUrlRegexp, '');
45+
46+
await fs.ensureDir(path.dirname(targetDir));
47+
48+
const targetHtmlPath = path.join(baseDir, targetDir, 'index.html');
49+
50+
try {
51+
await fs.move(sourceHtmlPath, targetHtmlPath);
52+
} catch (error: any) {
53+
logger.error(`Failed to copy HTML file: ${error.message}`);
54+
}
55+
});
56+
57+
await Promise.all(copyPromises);
58+
}
59+
60+
export const createGhPagesPreset: CreatePreset = (appContext, modernConfig) => {
61+
const { serverRoutes, appDirectory, distDirectory } = appContext;
62+
63+
const {
64+
server: { baseUrl },
65+
} = modernConfig;
66+
const outputDirectory = path.join(appDirectory, '.output');
67+
68+
return {
69+
name: 'gh-pages',
70+
async prepare() {
71+
await fs.remove(outputDirectory);
72+
},
73+
async writeOutput() {
74+
await fs.copy(distDirectory, outputDirectory);
75+
},
76+
async end() {
77+
await reorganizeHtmlFiles(
78+
serverRoutes,
79+
outputDirectory,
80+
Array.isArray(baseUrl) ? baseUrl[0] : baseUrl,
81+
);
82+
},
83+
};
84+
};

packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,10 @@ export const createVercelPreset: CreatePreset = (
101101
},
102102
});
103103

104+
const nodeVersion = process.versions.node.split('.')[0];
105+
104106
await fse.writeJSON(path.join(funcsDirectory, '.vc-config.json'), {
105-
runtime: 'nodejs16.x',
107+
runtime: `nodejs${nodeVersion}.x`,
106108
handler: 'index.js',
107109
launcherType: 'Nodejs',
108110
shouldAddHelpers: false,

0 commit comments

Comments
 (0)