通过本章节,你可以了解到 Modern.js 中的入口约定,以及如何自定义入口。
入口指的是一个页面的起始模块。
在 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
,通过生成器创建入口即可:
? 请选择你想要的操作 创建工程元素
? 请选择创建元素类型 新建「应用入口」
? 请填写入口名称 new-entry
执行后,Modern.js 会自动生成一个新的入口目录,此时可以看到 src/
目录变成如下结构:
.
├── 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
的路由,并且被迁移的代码路由并未发生变化。
Modern.js 会将与 package.json 文件中 name
字段同名的入口作为主入口,主入口的路由为 /
,其他入口的路由为 /{entryName}
。
比如,package.json 中的 name
为 myapp
时,src/myapp
会作为项目的主入口。
不同的入口类型具有不同的编译和运行时行为。在 Modern.js 创建项目时,开发者可以手动选择创建框架模式或是构建模式的项目。完成创建后,可以看到不同模式的项目样板文件是不同的。
默认情况下,Modern.js 启动项目前会对 src/
下的文件进行扫描,识别入口,并生成对应的服务端路由。
可以通过 source.entriesDir 更改入口目录为其他目录。
并非 src/
下所有的一级目录都会成为项目入口, 入口所在目录必须满足以下四个条件之一:
routes/
目录App.[jt]sx?
文件index.[jt]sx?
文件pages/
目录(兼容 Modern.js 1.0)当 src/
目录满足入口特征时,Modern.js 会认为当前项目为单入口应用。
在单入口应用中,默认的入口名为 main
。
当项目不是单入口应用时,Modern.js 会进一步查看 src/
下的一级目录。
框架模式指需要使用 Modern.js 框架能力,例如 Router、SSR、一体化调用等。这类入口约定下,开发者定义的入口并不是真正的 Webpack 编译入口。Modern.js 在启动时会生成一个封装过的入口,可以在 node_modules/.modern/{entryName}/index.js
找到真正的入口。
如果入口中存在 routes/
目录,Modern.js 会在启动时扫描 routes/
下的文件,基于文件约定,自动生成客户端路由(react-router)。例如:
.
├── src
│ └── routes
│ ├── layout.tsx
│ └── page.tsx
上述目录中,layout.tsx
中导出的组件会作为最外层的组件,page.tsx
中导出的组件会作为 /
路由的组件。
详细内容可以参考路由。
如果入口中存在 App.[jt]sx?
文件,开发者可以在这个文件中通过代码的方式,设置客户端路由,或者不设置客户端路由。
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>
);
};
详细内容可以参考路由。
如果入口中存在 index.[jt]sx
文件,并且当文件默认导出函数时,Modern.js 会将默认的 bootstrap 函数作为入参传入,并用导出的函数替代默认的 bootstrap,这样开发者可以自定义将组件挂载到 DOM 节点上,或在挂载前添加自定义行为。例如:
export default (App: React.ComponentType, bootstrap: () => void) => {
// do something before bootstrap...
initSomething().then(() => {
bootstrap();
})
};
此时,Modern.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 类似,需要自己将组件挂载到 DOM 节点、添加热更新代码等。例如:
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
中手动配置入口。
export default defineConfig({
source: {
entries: {
// 指定一个名称为 entry_customize 的新入口
entry_customize: './src/home/test/index.ts',
},
// 禁用默认入口扫描
disableDefaultEntries: true,
},
});
当使用自定义入口时,项目的部分结构可能恰巧命中了 Modern.js 的目录约定,但实际上这部分目录并不是真实的入口。
Modern.js 提供了 disableDefaultEntries
配置,来禁用默认的入口扫描规则。当你需要自定义入口时,一般需要将 disableDefaultEntries
与 entries
结合使用。这样,一些存量项目可以在不修改目录结构的情况下,快速地进行迁移。
export default defineConfig({
source: {
disableDefaultEntries: true,
},
});
详细用法请查看 source.entries 和 source.disableDefaultEntries。