部署应用

目前,Modern.js 提供了两种部署方式:

  • 你可以将应用自行托管在包含 Node.js 环境的容器中,这为应用提供了部署的灵活性。
  • 你也可以通过平台部署应用,目前 Modern.js 官方支持了 Netlify, VercelGithub pages 平台。
INFO

目前 Modern.js 仅支持在 Node.js 环境中运行,未来将提供更多运行时环境的支持。

构建部署产物

执行 modern deploy 命令将自动输出部署产物。此过程包括优化 Bundler 构建产物及产物依赖,检测当前部署平台,并自动生成可以在该平台运行的产物。 如果你希望在本地生成并测试特定部署平台的产物,可以通过设置环境变量来指定平台:

MODERNJS_DEPLOY=netlify npx modern deploy
INFO

在 Modern.js 官方支持的部署平台中部署时,无需指定环境变量。

ModernJS 内置 Node.js 服务器

单仓库项目

默认情况下,如果未检测到 Modern.js 支持的部署平台,Modern.js 将生成可以在 Node.js 环境下运行的部署产物。

你可以使用以下命令构建项目:

npx modern deploy

当执行 modern deploy 命令时,Modern.js 将生成可执行的部署产物,并在控制台输出以下内容:

Static directory: `.output/static`
You can preview this build by `node .output/index`

现在,你可以通过执行 node .output/index 命令来运行服务器。在 .output/static 目录中,存放了页面运行所需的静态资源,你可以选择将这些资源上传到 CDN 以提高访问速度。

INFO

默认情况下,运行 Modern.js 服务器时会监听 8080 端口,如果你想修改监听的端口,可以指定 PORT 环境变量:

PORT=3000 node .output/index

Monorepo

对于 Monorepo 项目,除了需要构建当前的项目外,还需要构建当前项目依赖的仓库中其他子项目。

假设当前项目的 package.json 中的 name 为 app,以 pnpm 作为 Monorepo 管理工具为例,你可以在项目 package.json 中添加以下命令用于构建:

app/package.json
{
  "scripts": {
    "build:packages": "pnpm --filter 'app^...' run build",
    "deploy": "pnpm run build:packages && modern deploy",
  }
}

如果你使用 rush 作为 Monorepo 管理工具,可以在 package.json 中添加以下命令:

{
  "scripts": {
    "build:packages": "rush rebuild --to-except app",
    "deploy": "rushx build:packages && modern deploy",
  }
}

构建完成后,框架会将项目中所有的依赖生成在 .output/node_modules 目录下。你同样可以使用 node .output/index 运行 Modern.js 服务器。

Netlify

Netlify 是一个流行的 Web 开发平台,专为构建、发布和维护现代 Web 项目而设计。在 Netlify 上部署,通常需要配置 netlify.toml 文件,你可以根据项目复杂度,渐进地配置该文件。

纯前端项目

在当前项目的根目录添加 netlify.toml 文件:

./
├── src
├── modern.config.ts
├── netlify.toml
└── package.json

netlify.toml 中添加以下内容:

[build]
  publish = "dist"
  command = "modern deploy"
INFO

你可参考部署项目示例

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

全栈项目

全栈项目是指使用了自定义 Web Server、SSR、BFF 的项目,这些项目需要部署在 Netlify Functions 上。你需要基于上述的 netlify.toml 文件,添加以下配置:

netlify.toml
[build]
  publish = "dist"
  command = "modern deploy"

[functions]
  directory = ".netlify/functions"
  node_bundler = "none"
  included_files = [".netlify/functions/**"]
INFO
  1. 目前 Modern.js 还不支持在 Netlify Edge Functions 进行部署,我们将在后续的版本中支持。
  2. 你可参考部署项目示例

Monorepo 项目

INFO

以下指南主要针对于全栈项目,对于纯 CSR 的项目,只需要按照纯前端项目部署即可。

对于 Monorepo 项目,除了需要构建当前的项目外,还需要构建当前项目依赖的仓库中其他子项目。这里以一个 pnpm Monorepo 仓库为例,在 Netlify 上对 Monorepo 项目进行部署。

假设 Monorepo 仓库目录结构如下:

. ├── packages │ ├── app │ └── app-dep1 ├── package.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml

你需要在 Netlify 平台上配置 Base directorypackages/app:

packages/app/package.json 中添加以下 script,在执行 app 仓库的部署命令之前,先执行 workspace 中其他仓库的构建:

{
  "scripts": {
    "build:packages": "pnpm --filter 'app^...' run build",
    "deploy": "pnpm run build:packages && modern deploy",
  }
}

netlify.toml 配置构建命令:

[build]
  publish = "dist"
  command = "npm run deploy"

[functions]
  directory = ".netlify/functions"
  node_bundler = "none"
  included_files = [".netlify/functions/**"]

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

Vercel

Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的功能,支持部署静态网站,服务端渲染应用等。在 Vercel 上部署,通常需要配置 vercel.json 文件,你可以根据项目复杂度,渐进地配置该文件。

纯前端项目

在当前项目的根目录添加 vercel.json 文件:

./
├── src
├── modern.config.ts
├── vercel.json
└── package.json

vercel.json 中添加以下内容:

vercel.json
{
  "buildCommand": "modern deploy",
  "outputDirectory": ".vercel/output"
}

提交你的项目到 git,在 Vercel 平台上选择 Frmeworkwork Preset 为 Other,部署即可。

INFO

你可参考部署项目示例

全栈项目

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

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

  1. 当前,Modern.js 还不支持在 Vercel 平台上部署 BFF 项目,我们将在后续的版本中支持。

  2. 函数运行的 node.js 版本由项目在 Vercel 平台配置决定。

INFO

你可参考部署项目示例

Monorepo 项目

INFO

以下指南主要针对于全栈项目,对于纯 CSR 的项目,只需要按照纯前端项目部署即可。

对于 Monorepo 项目,除了需要构建当前的项目外,还需要构建当前项目依赖的仓库中其他子项目。这里以一个 pnpm Monorepo 仓库为例,在 Vercel 上对 Monorepo 项目进行部署。

假设 Monorepo 仓库目录结构如下:

. ├── packages │ ├── app │ └── app-dep1 ├── package.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml

首先,你需要在 Vercel 平台上配置 Root Directorypackages/app:

将 Node.js 运行时设置为 18.x

package.json
"engines": {
  "node": "18.x"
},

packages/app/package.json 中添加以下 script,在执行 app 仓库的部署命令之前,先执行 workspace 中其他仓库的构建:

{
  "scripts": {
    "build:packages": "pnpm --filter 'app^...' run build",
    "deploy": "pnpm run build:packages && modern deploy",
  }
}

packages/app/vercel.json 文件中添加以下内容:

vercel.json
{
  "buildCommand": "npm run deploy",
  "outputDirectory": ".vercel/output"
}

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

Github Pages

如果你要为一个仓库常见 Github 页面,并且你没有自定义域名,则该页面的 URL 将会是以下格式:http://<username>.github.io/<repository-name>,所以需要在 modern.config.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. 可以参考项目示例

如果通过 Github Actions 部署,可以选择 Settings > Pages > Source > GitHub Actions,并在项目中添加 workflow 文件,可参考示例

自建 Node.js 服务器

通常情况下,我们推荐使用 Modern.js 内置的 Node.js 服务器来部署应用,它支持托管纯前端项目或者全栈项目,并且保证在开发和生产环境下的表现一致。

如果你的项目是纯前端项目,也可以通过自建 Node.js 服务器来部署应用,以下用一个 Koa 服务器的示例来演示如何托管一个纯前端项目的产物。

例如你有一个 Node.js 服务器的仓库,你可以将项目的产物复制到该仓库下,现在结构如下:

.
├── .output
│   ├── html
│   └── static
└── server.js

server.js 中,假定有如下代码:

server.js
import Koa from 'koa';

const app = new Koa();
app.use(async (ctx, next) => {
  ctx.body = 'Hello Modern.js';
});
app.listen(3000);

现在,你可以新增部分代码,将静态资源和 HTML 文件的访问逻辑添加到 server.js 中。这里需要通过 mime-types 包来获取静态资源的 MIME 类型,因此我们先安装依赖:

npm
yarn
pnpm
bun
npm add mime-types
server.js
const Koa = require('koa');
const fs = require('fs');
const path = require('path');
const mime = require('mime-types');

const app = new Koa();
app.use(async (ctx, next) => {
  if (ctx.path.startsWith('/static')) {
    ctx.type = mime.lookup(ctx.path);
    ctx.body = fs.createReadStream(path.resolve(__dirname, `.output${ctx.path}`));
  } else if (ctx.path === '/') {
    ctx.type = 'html';
    ctx.body = fs.createReadStream(path.resolve(__dirname, '.output/html/main/index.html'));
  }
});
app.listen(3000);
NOTE

以上代码是最基础的例子,你的应用可能是多入口的,需要根据不同的路径访问不同的 HTML 文件,自建 Node.js 服务器也会存在更多的逻辑。

需要注意的是,如果你的项目中使用 Modern.js 约定式路由,或是使用 React Router 自行搭建了浏览器端路由,你必须通过正确的 baseURL 来访问 HTML 文件。

在 Modern.js 中,默认的 baseURL'/',你可以通过在 modern.config.ts 中修改 server.baseUrl 来配置。

DANGER

存在浏览器路由的项目,永远无法通过 /index.html 路径来访问到 HTML 文件。

Nginx

Nginx 是一个高性能的 HTTP 和反向代理服务器,它可以处理静态文件、反向代理、负载均衡等功能。在 Nginx 上部署,通常需要配置 nginx.conf 文件。

如果你的项目是纯前端项目,也可以通过 Nginx 来部署应用,以下提供一个 Nginx 配置的示例来演示如何托管一个纯前端项目的产物。

nginx.conf
# user [user] [group];
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    server {
        listen 8080;
        server_name localhost;

        location / {
            # root [projectPath]/.output/html/main;
            index index.html;
            try_files $uri $uri/ =404;
        }

        location /static {
          # alias [projectPath]/.output/static;
        }
    }
}

在上述配置中,你需要将 [projectPath] 替换为你的项目路径,将 [user][group] 替换为你当前的用户和用户组。

你可以将上述配置复制到 Nginx 安装目录的 nginx.conf 文件中,然后启动 Nginx 服务。你也可以通过 nginx -c 启动指定路径下的配置文件,此时你需要额外保证 include 指令配置的路径正确。