utquidem
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
360 lines (256 loc) • 10.7 kB
Markdown
---
title: 自定义入口和服务器端路由
sidebar_label: 自定义入口和服务器端路由
sidebar_position: 1
---
使用 Modern.js 时,通常情况下是不需要自己指定入口的,框架会根据项目目录结构得到默认的入口。
在一些特殊情况下,例如目录结构有要求时,Modern.js 也提供了自定义入口的方式。
## 默认入口机制
默认会遍历当前项目 `src/` 目录,根据目录结构来判断当前项目是单入口还是多入口。
:::tip 提示
- 可以通过 [`source.entriesDir`](/docs/apis/config/source/entries-dir) 更改入口目录 `src/` 为其他目录。
- 以下内容省略文件后缀,默认支持 js、jsx、ts、tsx。
:::
### 入口特征
入口所在目录必须满足以下三个条件之一:
1. 具有 `App` 文件
2. 具有 `pages` 目录
3. 具有 `index` 文件
三种情况对应的目录结构示例如下:
```bash title="src/App.(j|t)sx?"
├── src
│ ├── common
│ └── App.(j|t)sx?
```
```bash title="src/pages"
├── src
│ ├── common
│ └── pages // pages 目录
```
```bash title="src/index.(j|t)sx?"
├── src
│ ├── common
│ └── index.(j|t)sx?
```
默认会遍历 `src/` 目录以及 `src/` 目录下的二级目录,如 `src/a/`。
:::tip 提示
Modern.js 内置了 "@" 别名,指向 `src` 目录。
:::
### 单入口应用
当 `src/` 目录满足**入口特征**时,Modern.js 会认为当前项目为单入口应用。
:::tip 提示
单入口应用默认入口名为 `main`。
:::
### 多入口应用
当项目不是单入口应用时,Modern.js 会进一步查看 `src` 下的文件夹。
如 `src/a/`,当 `a` 目录符合单入口应用的特征时,会被当做一个入口,对应的入口名为当前目录名。
多个入口之间公用的代码,推荐放到 `src/common/` 目录下,这里就要求 `common` 目录不能满足入口的特征。
例如,目录结构如下时:
```bash
├── src
│ ├── common
│ │ ├── components
│ │ ├── models
│ │ └── utils
│ ├── landing-page
│ │ └── index.js
│ ├── page-1
│ │ ├── components
│ │ ├── models
│ │ └── App.jsx
│ └── page-2
│ ├── components
│ ├── models
│ └── pages // pages 目录
└── package.json
```
会检测出 3 个入口:`landing-page`、`page-1` 和 `page-2`。
:::tip 提示
* 对于多入口应用,入口名和 `package.json` 文件中的 `name` 属性值相同的入口为主入口,访问时可以直接通过 `/` 访问。
* 如上述目录结构,假如 `package.json` 文件中 `name` 属性值为 `page-2`, 则同名入口就可以通过 `http://localhost:8080` 直接访问。
:::
## App、Pages、Index 入口的区别
### App 入口
在 MWA 项目中,推荐入口目录下只放置 `App` 文件,默认导出整个应用的根组件即可,如下:
```js title=src/App.jsx
import React from 'react';
const App = () => {
return (//...)
};
export default App;
```
当 Modern.js 检测到入口中存在 `App` 文件时,会自动生成真正的 Webpack 入口文件,完成将组件挂载到 DOM 节点等所有工作,大致代码逻辑如下:
```js
import { createApp, bootstrap } from '@modern-js/runtime';
import React from 'react';
import App from 'xxxx/src/App';
const IS_BROWSER = typeof window !== 'undefined';
const MOUNT_ID = 'root';
let AppWrapper = null;
const renderApp = () => {
AppWrapper = createApp({})(App);
bootstrap(AppWrapper, MOUNT_ID);
};
renderApp();
export default AppWrapper;
if (IS_BROWSER && module.hot) {
module.hot.accept('xxxxx/src/App', () => {
renderApp();
});
}
```
:::tip 提示
* Modern.js 生成的入口会统一放到 `node_modules/.modern-js/` 目录。对应的 Webpack 入口文件为 `node_module/.modern-js/${entryName}/index.js`。
* `createApp` 和 `bootstrap` 为 `@modern-js/runtime` API,详情请参考:【[Runtime API](/docs/apis/runtime/app/create-app)】。
:::
### Pages 入口
Modern.js 也同样提供了约定式路由,即**文件系统即路由**。
约定式路由详情请参考: 【[使用约定式路由](/docs/guides/tutorials/c08-client-side-routing/8.2-file-based-routing)】。
### Index 入口
通常情况下,上面两种路由已经能够满足需求,当开发者需要在 `bootstrap` 执行之前做些操作或者完全接管 Webpack 入口时,可以在入口目录下放置 `index` 文件。下面对两种情况分别介绍:
1. **bootstrap 之前添加自定义行为**。
当 `index` 文件默认导出**函数**时,Modern.js 还是会根据 `runtime.features` 的设置情况生成 `createApp` 包裹后的代码。在渲染过程中,将 `createApp` 包裹后的组件作为参数传递给 `index` 文件导出的函数,这样开发者可以自定义将组件挂载到 DOM 节点上。
例如:
```js title=src/index.jsx
import { bootstrap } from '@modern-js/runtime';
export default App => {
// do something before bootstrap...
bootstrap(App, 'root');
};
```
Modern.js 生成的文件内容如下:
```js
import customRender from 'xxxx/src/index.js';
import { createApp, bootstrap } from '@modern-js/runtime';
import React from 'react';
import App from 'xxxx/src/App';
const IS_BROWSER = typeof window !== 'undefined';
const MOUNT_ID = 'root';
let AppWrapper = null;
const renderApp = () => {
AppWrapper = createApp({
// runtime 的插件参数...
})(App);
customRender(AppWrapper);
};
renderApp();
export default AppWrapper;
if (IS_BROWSER && module.hot) {
module.hot.accept('xxx/src/App', () => {
renderApp();
});
}
```
2. **完全接管 Webpack 入口**
当 `index` 文件没有导出函数时,这时候 `index` 文件就是真正的 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.config.js` 文件中添加配置 `source.entries` 自行配置入口。
### 自定义普通入口
通常情况下,创建的入口需要是 **App 类型**,即指定的入口文件只需要导出应用的根组件。
例如以下目录结构:
```bash
.
├── src
│ └── entry
│ ├── chat.tsx
│ └── home.tsx
└── package.json
```
结合上面【默认入口机制】的内容,Modern.js 在分析上述目录结构时,不会得到任何默认入口。
在不想改变目录结构的情况下(如项目迁移),可以通过 `source.entries` 自定义入口:
```js title="modern.config.js"
export default defineConfig({
source: {
entries: {
home: './src/entry/home.tsx',
chat: './src/entry/chat.tsx',
},
},
})
```
`src/entry/home.tsx` 中只需要导出根组件,此时该文件就会和 `src/App` 等价,Modern.js 会生成真正的 Webpack 入口文件。
### disableMount
在迁移旧项目到 Modern.js 时,可能不需要使用默认生成代码的功能,可以设置 `disableMount` 属性,关闭入口代码生成的功能。
针对上面 `chat` 和 `home` 两个入口的目录结构,当配置如下时:
```js title="modern.config.js"
export default defineConfig({
source: {
entries: {
home: {
entry: './src/entry/home.tsx',
disableMount: true, // 禁止生成入口代码
},
chat: './src/entry/chat.tsx',
},
},
})
```
Modern.js 不会为 `src/entry/home.tsx` 生成额外的入口代码,而是直接把它当做普通的 Webpack 入口文件。此时,用户需要自行处理组件挂载、热更新等问题。
### enableFileSystemRoutes
可以通过该选项指定某个目录使用约定式路由,例如以下目录结构:
```bash
.
├── src
│ └── routes
│ ├── a.jsx
│ ├── b.jsx
│ └── index.js
└── package.json
```
需要在 `routes` 目录开启约定式路由时,配置如下:
```js title="modern.config.js"
export default defineConfig({
source: {
entries: {
routes: {
entry: './src/routes', // 启用约定式路由时,entry 必须为目录
enableFileSystemRoutes: true,
},
},
},
})
```
`dev` 命令运行成功后,访问路由与文件对应如下:
* `http://localhost:8080/routes` -> `./src/routes/index.js`
* `http://localhost:8080/routes/a` -> `./src/routes/a.jsx`
* `http://localhost:8080/routes/b` -> `./src/routes/b.jsx`
:::tip 提示
1. 使用 `enableFileSystemRoutes` 时,`entry` 属性必须为目录。
2. `enableFileSystemRoutes` 不能与 `disableMount` 同时开启。
:::
### 自定义入口和默认入口合并
在指定 `source.entries` 后,Modern.js 会将用户自定义的入口与分析目录结构得到的默认入口合并。合并规则为:
比较自定义入口设置的入口路径和默认入口路径,当入口路径一致时,自定义入口会覆盖默认入口。
例如以下目录结构:
```bash
.
├── src
│ ├── chat
│ │ └── App.jsx
│ └── home
│ └── index.js
└── package.json
```
Modern.js 分析 `src/` 目录,得到默认入口 `chat` 和 `home`。当用户在 `modern.config.js` 文件中配置如下时:
```js title="modern.config.js"
export default defineConfig({
source: {
entries: {
index: './src/home/index.js',
},
},
};
```
可以看到自定义入口 `index` 的路径和默认入口 `home` 的路径一致,在合并的过程中,`index` 会覆盖掉 `home`,最终入口如下:
* `chat` -> `./src/chat/App.jsx`
* `index` -> `./src/home/index.js`
需要注意的是 `index` 入口由于没有设置 `disableMount`,所以是会默认生成入口代码。
## 如何修改入口对应的路由
默认情况下,入口名也就是对应的访问路由,当需要修改入口对应的访问路由或者设置多个路由时,可以在 `modern.config.js` 文件中添加 [`server.routes`](/docs/apis/config/server/routes) 配置。