diff --git a/.changeset/gold-pumas-perform.md b/.changeset/gold-pumas-perform.md new file mode 100644 index 000000000000..125c850d3f99 --- /dev/null +++ b/.changeset/gold-pumas-perform.md @@ -0,0 +1,6 @@ +--- +'@modern-js/app-tools': patch +--- + +feat: support deployment for github pages +feat: 支持使用 github pages 部署 diff --git a/packages/document/main-doc/docs/en/guides/basic-features/deploy.mdx b/packages/document/main-doc/docs/en/guides/basic-features/deploy.mdx index 3429bfd0089a..1a89062219a1 100644 --- a/packages/document/main-doc/docs/en/guides/basic-features/deploy.mdx +++ b/packages/document/main-doc/docs/en/guides/basic-features/deploy.mdx @@ -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. @@ -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 @@ -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). ::: @@ -213,6 +219,11 @@ Commit your project to git, select Framework Preset as `Other` on the Vercel pla +:::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**. @@ -220,14 +231,12 @@ Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. Th 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 @@ -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://.github.io/`. Therefore, you need to add the following configuration in `modern.config.ts`: + +``` +import { defineConfig } from '@modern-js/app-tools'; + +export default defineConfig({ + //... + server: { + baseUrl: "/" + }, + output: { + assetPrefix: "/", + } +}); +``` + +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. diff --git a/packages/document/main-doc/docs/zh/guides/basic-features/deploy.mdx b/packages/document/main-doc/docs/zh/guides/basic-features/deploy.mdx index fda47d98b1aa..16f91715007f 100644 --- a/packages/document/main-doc/docs/zh/guides/basic-features/deploy.mdx +++ b/packages/document/main-doc/docs/zh/guides/basic-features/deploy.mdx @@ -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 环境中运行,未来将提供更多运行时环境的支持。 @@ -26,6 +26,7 @@ MODERNJS_DEPLOY=netlify npx modern deploy 在 Modern.js 官方支持的部署平台中部署时,无需指定环境变量。 ::: + ## ModernJS 内置 Node.js 服务器 ### 单仓库项目 @@ -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 平台上添加项目,部署即可。 ### 全栈项目 @@ -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 @@ -207,6 +217,11 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的 +:::info +你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。 + +::: + ### 全栈项目 全栈项目是指使用了自定义 Web Server、SSR、BFF 的项目,这些项目需要部署在 **Vercel Functions** 上。 @@ -214,12 +229,13 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的 全栈项目除了按照[纯前端项目](#纯前端项目)的方式配置 `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 项目 @@ -273,6 +289,38 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的 提交你的代码,使用 Vercel 平台部署即可。 +## Github Pages + +如果你要为一个仓库常见 Github 页面,并且你没有自定义域名,则该页面的 URL 将会是以下格式:`http://.github.io/`,所以需要在 `modern.config.ts` 中添加 +以下配置: +```ts +import { defineConfig } from '@modern-js/app-tools'; + +export default defineConfig({ + //... + server:{ + baseUrl: "/" + }, + output: { + assetPrefix: "/", + } +}); +``` + +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 服务器 diff --git a/packages/solutions/app-tools/src/plugins/deploy/index.ts b/packages/solutions/app-tools/src/plugins/deploy/index.ts index 11f9811ac345..1a21a5aa1aaf 100644 --- a/packages/solutions/app-tools/src/plugins/deploy/index.ts +++ b/packages/solutions/app-tools/src/plugins/deploy/index.ts @@ -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; @@ -22,6 +23,7 @@ const deployPresets: DeployPresetCreators = { node: createNodePreset, vercel: createVercelPreset, netlify: createNetlifyPreset, + ghPages: createGhPagesPreset, }; async function getDeployPreset( diff --git a/packages/solutions/app-tools/src/plugins/deploy/platforms/gh-pages.ts b/packages/solutions/app-tools/src/plugins/deploy/platforms/gh-pages.ts new file mode 100644 index 000000000000..dd27a7baa847 --- /dev/null +++ b/packages/solutions/app-tools/src/plugins/deploy/platforms/gh-pages.ts @@ -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(); + + 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, + ); + }, + }; +}; diff --git a/packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts b/packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts index 122c40e1efb3..fe0457b928d0 100644 --- a/packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts +++ b/packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts @@ -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,