---
sidebar_position: 1
---

# 入口

通过本章节，你可以了解到 Modern.js 中的入口约定，以及如何自定义入口。

## 什么是入口（Entry）

**入口指的是一个页面的起始模块。**

在 Modern.js 项目中，每一个入口对应一个独立的页面，也对应一条服务端路由。默认情况下，Modern.js 会基于目录约定来自动确定页面的入口，同时也支持通过配置项来自定义入口。

Modern.js 提供的很多配置项都是以入口为维度进行划分的，比如页面标题、HTML 模板、页面 Meta 信息、是否开启 SSR/SSG、服务端路由规则等。

## 单入口与多入口

Modern.js 初始化的项目是单入口的（SPA），项目结构如下：

```
.
├── src
│   └── routes
│       ├── index.css
│       ├── layout.tsx
│       └── page.tsx
├── package.json
├── modern.config.ts
└── tsconfig.json
```

在 Modern.js 项目中，你可以很方便的将单入口切换成多入口，直接在项目下执行 `pnpm run new`，通过生成器创建入口即可：

```bash
? 请选择你想要的操作 创建工程元素
? 请选择创建元素类型 新建「应用入口」
? 请填写入口名称 new-entry
```

执行后，Modern.js 会自动生成一个新的入口目录，此时可以看到 `src/` 目录变成如下结构：

```bash
.
├── myapp    # 原入口
│   └── routes
│       ├── index.css
│       ├── layout.tsx
│       └── page.tsx
└── new-entry # 新入口
    └── routes
        ├── index.css
        ├── layout.tsx
        └── page.tsx
```

原本的代码被移动到了和 `package.json` 中 `name` 同名的目录下，并创建了新的目录。

执行 `pnpm run dev` 后，可以看到新增了一条名为 `/new-entry` 的路由，并且被迁移的代码路由并未发生变化。

:::tip
Modern.js 会将与 package.json 文件中 `name` 字段同名的入口作为主入口，主入口的路由为 `/`，其他入口的路由为 `/{entryName}`。

比如，package.json 中的 `name` 为 `myapp` 时，`src/myapp` 会作为项目的主入口。

:::

## 入口类型

不同的入口类型具有不同的编译和运行时行为。在 Modern.js 创建项目时，开发者可以手动选择创建**框架模式**或是**构建模式**的项目。完成创建后，可以看到不同模式的项目样板文件是不同的。

默认情况下，Modern.js 启动项目前会对 `src/` 下的文件进行扫描，识别入口，并生成对应的服务端路由。

:::tip
可以通过 [source.entriesDir](/configure/app/source/entries-dir) 更改入口目录为其他目录。

:::

并非 `src/` 下所有的一级目录都会成为项目入口, 入口所在目录必须满足以下四个条件之一：

1. 具有 `routes/` 目录
2. 具有 `App.[jt]sx?` 文件
3. 具有 `index.[jt]sx?` 文件
4. 具有 `pages/` 目录（兼容 Modern.js 1.0）

当 `src/` 目录满足入口特征时，Modern.js 会认为当前项目为单入口应用。

:::tip
在单入口应用中，默认的入口名为 `main`。

:::

当项目不是单入口应用时，Modern.js 会进一步查看 `src/` 下的一级目录。

### 框架模式入口

框架模式指需要使用 Modern.js 框架能力，例如 Router、SSR、一体化调用等。这类入口约定下，开发者定义的入口并不是真正的 Webpack 编译入口。Modern.js 在启动时会生成一个封装过的入口，可以在 `node_modules/.modern/{entryName}/index.js` 找到真正的入口。

#### 约定式路由

如果入口中存在 `routes/` 目录，Modern.js 会在启动时扫描 `routes/` 下的文件，基于文件约定，自动生成客户端路由（react-router）。例如：

```bash
.
├── src
│   └── routes
│       ├── layout.tsx
│       └── page.tsx
```

上述目录中，`layout.tsx` 中导出的组件会作为最外层的组件，`page.tsx` 中导出的组件会作为 `/` 路由的组件。

详细内容可以参考[路由](/guides/basic-features/routes#约定式路由)。

#### 自控式路由

如果入口中存在 `App.[jt]sx?` 文件，开发者可以在这个文件中通过代码的方式，设置客户端路由，或者不设置客户端路由。

```tsx
import { BrowserRouter, Route, Routes } from '@modern-js/runtime/router';

export default () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route index element={<div>index</div>} />
        <Route path="about" element={<div>about</div>} />
      </Routes>
    </BrowserRouter>
  );
};
```

详细内容可以参考[路由](/guides/basic-features/routes#自控式路由)。

#### 自定义 Bootstrap

如果入口中存在 `index.[jt]sx` 文件，并且当文件默认导出函数时，Modern.js 会将默认的 bootstrap 函数作为入参传入，并用导出的函数替代默认的 bootstrap，这样开发者可以自定义将组件挂载到 DOM 节点上，或在挂载前添加自定义行为。例如：

```tsx
export default (App: React.ComponentType, bootstrap: () => void) => {
  // do something before bootstrap...
  initSomething().then(() => {
    bootstrap();
  })
};
```

此时，Modern.js 生成的文件内容如下：

```js
import React from 'react';
import ReactDOM from 'react-dom/client';
import customBootstrap from '@_modern_js_src/index.tsx';
import App from '@_modern_js_src/App';
import { router, state } from '@modern-js/runtime/plugins';

const IS_BROWSER = typeof window !== 'undefined' && window.name !== 'nodejs';
const MOUNT_ID = 'root';

let AppWrapper = null;
let root = null;

function render() {
  AppWrapper = createApp({
    // runtime 的插件参数...
  })(App);
  if (IS_BROWSER) {
    customBootstrap(AppWrapper, () => bootstrap(AppWrapper, MOUNT_ID, root, ReactDOM));
  }
  return AppWrapper;
}

AppWrapper = render();

export default AppWrapper;
```

### 构建模式入口

构建模式是指不使用任何 Modern.js 运行时的能力，完全由开发者自己定义项目 Webpack 的入口。

如果入口中存在 `index.[jt]sx` ，并且没有默认导出函数时，这时候该文件就是真正的 Webpack 入口文件。这里和 [Create React App](https://github.com/facebook/create-react-app) 类似，需要自己将组件挂载到 DOM 节点、添加热更新代码等。例如:

```js title=src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
```

Modern.js **不推荐**新项目使用这种方式，这种方式丧失了框架的一些能力，如 **`modern.config.js` 文件中的 `runtime` 配置将不会再生效**。但是在项目从其他框架迁移到 Modern.js，例如 CRA，或是自己手动搭建的 webpack 时，这种方式会非常有用。

## 使用配置指定入口

大部分存量项目并不是按照 Modern.js 的目录结构来搭建的。如果要改成 Modern.js 约定的目录结构，会存在一定的迁移成本。

在这种情况下，除了使用文件约定生成入口外，你可以在 `modern.config.[jt]s` 中手动配置入口。

```ts title="modern.config.ts"
export default defineConfig({
  source: {
    entries: {
      // 指定一个名称为 entry_customize 的新入口
      entry_customize: './src/home/test/index.ts',
    },
    // 禁用默认入口扫描
    disableDefaultEntries: true,
  },
});
```

### 禁用默认入口扫描

当使用自定义入口时，项目的部分结构可能恰巧命中了 Modern.js 的目录约定，但实际上这部分目录并不是真实的入口。

Modern.js 提供了 `disableDefaultEntries` 配置，来禁用默认的入口扫描规则。当你需要自定义入口时，一般需要将 `disableDefaultEntries` 与 `entries` 结合使用。这样，一些存量项目可以在不修改目录结构的情况下，快速地进行迁移。

```ts title="modern.config.ts"
export default defineConfig({
  source: {
    disableDefaultEntries: true,
  },
});
```

:::tip
详细用法请查看 [source.entries](/configure/app/source/entries) 和 [source.disableDefaultEntries](/configure/app/source/disable-default-entries)。

:::
