Skip to content

feat: support deployment for github pages #7052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-pumas-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modern-js/app-tools': patch
---

feat: support deployment for github pages
feat: 支持使用 github pages 部署
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ sidebar_position: 15

Currently, Modern.js offers two deployment way:
- 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.
- You can also deploy your application through a platform. Currently, Modern.js supports the [Netlify](https://www.netlify.com/) and [Vercel](https://vercel.com/).
- 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/).

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

:::info
You can refer to the [deployment project example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).

:::

Now, add a project to the Netlify platform and deploy it!

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

:::info
Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
1. Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
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).
:::


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

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

:::info
You can refer to the [deployment project examples](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).

:::

### Full Stack Project

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

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:

1. Currently, Modern.js does not support deploying BFF projects on the Vercel platform. We will support it in future versions.
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,
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:
```json title="package.json"
"engines": {
"node": "18.x"
}
```
2. The Node.js version for function execution is determined by the project configuration on the Vercel platform.

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

:::

### Monorepo

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

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

## Github Pages

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

```
import { defineConfig } from '@modern-js/app-tools';

export default defineConfig({
//...
server: {
baseUrl: "/<repository-name>"
},
output: {
assetPrefix: "/<repository-name>",
}
});
```

GitHub Pages supports two deployment ways: branch deployment or GitHub Actions deployment.

For branch deployment, follow these steps:

1. In the GitHub repository, navigate to Settings > Pages > Source > Deploy from a branch
2. Install the `gh-pages` as devDependency
3. Add the following script to `package.json`
```
"scripts": {
//...
"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"
}
```

4. Run `npm run deploy:gh-pages`

:::info
1. Running `MODERNJS_DEPLOY=ghPages modern deploy` will build the production output for GitHub in the .output directory.
2. You can refer to the [project](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
:::

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


## Using Self-Built Node.js Server

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sidebar_position: 15
目前,Modern.js 提供了两种部署方式:

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

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


## ModernJS 内置 Node.js 服务器

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

:::info
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。

:::

在 Netlify 平台上添加项目,部署即可。

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

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

:::




### Monorepo 项目

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

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

:::info
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。

:::

### 全栈项目

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

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

1. 当前,Modern.js 还不支持在 Vercel 平台上部署 BFF 项目,我们将在后续的版本中支持。
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` 指定版本:
```json title="package.json"
"engines": {
"node": "18.x"
}
```
2. 函数运行的 node.js 版本由项目在 Vercel 平台配置决定。


:::info
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr)。

:::

### Monorepo 项目

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

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

## Github Pages

如果你要为一个仓库常见 Github 页面,并且你没有自定义域名,则该页面的 URL 将会是以下格式:`http://<username>.github.io/<repository-name>`,所以需要在 `modern.config.ts` 中添加
以下配置:
```ts
import { defineConfig } from '@modern-js/app-tools';

export default defineConfig({
//...
server:{
baseUrl: "/<repository-name>"
},
output: {
assetPrefix: "/<repository-name>",
}
});
```

Github Pages 支持两种部署方式,通过分支部署或通过 Github Actions 部署,如果通过分支部署,可以使用以下步骤:
1. 在 github 仓库中,选择 `Settings > Pages > Source > Deploy from a branch`。
2. 安装 `gh-pages` 依赖作为开发依赖。
3. 在 package.json 的 `scripts` 中添加 `"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"`。
4. 执行 `npm run deploy:gh-pages`。

:::info
1. 执行 `MODERNJS_DEPLOY=ghPages modern deploy`,Modern.js 会把可用于 github 部署的产物构建到 `.output` 目录。
2. 可以参考项目[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。

:::

如果通过 Github Actions 部署,可以选择 Settings > Pages > Source > GitHub Actions,并在项目中添加 workflow 文件,可参考[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。


## 自建 Node.js 服务器

Expand Down
4 changes: 3 additions & 1 deletion packages/solutions/app-tools/src/plugins/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import type {
CliPluginFuture,
} from '../../types';
import type { AppToolsContext } from '../../types/new';
import { createGhPagesPreset } from './platforms/gh-pages';
import { createNetlifyPreset } from './platforms/netlify';
import { createNodePreset } from './platforms/node';
import { createVercelPreset } from './platforms/vercel';
import { getProjectUsage } from './utils';

type DeployPresetCreators = {
node: typeof createNodePreset;
vercel: typeof createVercelPreset;
netlify: typeof createNetlifyPreset;
ghPages: typeof createGhPagesPreset;
};

type DeployTarget = keyof DeployPresetCreators;
Expand All @@ -22,6 +23,7 @@ const deployPresets: DeployPresetCreators = {
node: createNodePreset,
vercel: createVercelPreset,
netlify: createNetlifyPreset,
ghPages: createGhPagesPreset,
};

async function getDeployPreset(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import path from 'path';
import type { ServerRoute } from '@modern-js/types';
import { fs } from '@modern-js/utils';
import { logger } from '@modern-js/utils';
import type { CreatePreset } from './platform';

async function reorganizeHtmlFiles(
routes: ServerRoute[],
baseDir: string,
baseUrl = '/',
) {
if (!routes || !Array.isArray(routes)) {
logger.error('Invalid server routes');
return;
}

await fs.ensureDir(baseDir);

const baseUrlRegexp = new RegExp(`^${baseUrl}\\/?`);

const collectedEntryPaths = new Set<string>();

const copyPromises = routes.map(async route => {
const { urlPath, entryPath } = route;

if (!entryPath) {
logger.warn(`Route ${urlPath} does not specify entryPath, skipping`);
return;
}

if (collectedEntryPaths.has(entryPath)) {
return;
}

collectedEntryPaths.add(entryPath);

const sourceHtmlPath = path.join(baseDir, entryPath);

if (!(await fs.pathExists(sourceHtmlPath))) {
logger.error(`Source HTML file does not exist: ${sourceHtmlPath}`);
return;
}

const targetDir = urlPath.replace(baseUrlRegexp, '');

await fs.ensureDir(path.dirname(targetDir));

const targetHtmlPath = path.join(baseDir, targetDir, 'index.html');

try {
await fs.move(sourceHtmlPath, targetHtmlPath);
} catch (error: any) {
logger.error(`Failed to copy HTML file: ${error.message}`);
}
});

await Promise.all(copyPromises);
}

export const createGhPagesPreset: CreatePreset = (appContext, modernConfig) => {
const { serverRoutes, appDirectory, distDirectory } = appContext;

const {
server: { baseUrl },
} = modernConfig;
const outputDirectory = path.join(appDirectory, '.output');

return {
name: 'gh-pages',
async prepare() {
await fs.remove(outputDirectory);
},
async writeOutput() {
await fs.copy(distDirectory, outputDirectory);
},
async end() {
await reorganizeHtmlFiles(
serverRoutes,
outputDirectory,
Array.isArray(baseUrl) ? baseUrl[0] : baseUrl,
);
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ export const createVercelPreset: CreatePreset = (
},
});

const nodeVersion = process.versions.node.split('.')[0];

await fse.writeJSON(path.join(funcsDirectory, '.vc-config.json'), {
runtime: 'nodejs16.x',
runtime: `nodejs${nodeVersion}.x`,
handler: 'index.js',
launcherType: 'Nodejs',
shouldAddHelpers: false,
Expand Down
Loading