本章节介绍 Modern.js 从 2.0 升级到 3.0 时,页面入口相关的变更内容。
Modern.js 3.0 对入口机制进行了优化和简化,主要变更包括:
index.[jt]sx 改为 entry.[jt]sxcreateRoot 和 render APIApp.config 和 routes/layout 的 config 导出需要迁移App.init 和 routes/layout 的 init 导出需要改为运行时插件在开始迁移前,首先需要识别你的项目使用的入口类型。
Modern.js 会扫描目录并识别符合以下任一条件的入口:
routes/ 目录 → 约定式路由入口App.[jt]sx? 文件 → 自控式路由入口index.[jt]sx? 文件(2.0)或 entry.[jt]sx? 文件(3.0)→ 自定义入口单入口应用:默认扫描 src/ 目录
src/
├── routes/ # 或者
├── App.tsx # 或者
└── index.tsx # 2.0 版本多入口应用:扫描 src/ 目录下的一级子目录
src/
├── entry1/
│ └── routes/ # 每个子目录都是一个入口
└── entry2/
└── App.tsx你可以通过 source.entriesDir 配置修改入口扫描目录。
本小节中的迁移操作都是仅当项目中实际存在对应用法时才需要执行,例如 bootstrap 函数、App.config/App.init、routes/layout.tsx 中的 config/init 函数等。
如果你的项目使用了自定义入口文件(index.[jt]sx),需要将其重命名为 entry.[jt]sx。
2.0 版本:
src/
└── index.tsx3.0 版本:
src/
└── entry.tsx如果你的入口文件导出了一个接收 App 和 bootstrap 参数的函数,需要改用新的 API。
2.0 版本:
export default (App: React.ComponentType, bootstrap: () => void) => {
// 执行初始化操作
initSomething().then(() => {
bootstrap();
});
};3.0 版本:
import { createRoot } from '@modern-js/runtime/react';
import { render } from '@modern-js/runtime/browser';
// 创建根组件
const ModernRoot = createRoot();
// 执行初始化操作
async function beforeRender() {
await initSomething();
}
// 渲染应用
beforeRender().then(() => {
render(<ModernRoot />);
});createRoot() 返回的组件对应 routes/ 目录生成或 App.tsx 导出的组件render() 函数用于处理渲染与挂载组件如果你在 App.[tj]sx 中定义了 App.config,需要将其迁移到运行时配置文件中。
2.0 版本:
const App = () => {
return <div>Hello</div>;
};
App.config = {
router: {
supportHtml5History: true,
},
};
export default App;3.0 版本:
在入口同级目录创建 modern.runtime.ts:
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
router: {
supportHtml5History: true,
},
});
Modern.js 3.0 不再支持在 modern.config.ts 中配置 runtime,必须使用 modern.runtime.ts 文件。
如果你在 App.[tj]sx 中定义了 App.init,需要将其改为运行时插件。
2.0 版本:
const App = () => {
return <div>Hello</div>;
};
App.init = (context) => {
context.store = createStore();
context.request = (url: string) => fetch(url);
};
export default App;3.0 版本:
import type { RuntimePlugin } from '@modern-js/runtime';
import { defineRuntimeConfig } from '@modern-js/runtime';
const initPlugin = (): RuntimePlugin => ({
name: 'init-plugin',
setup: (api) => {
return {
init({ context }) {
context.store = createStore();
context.request = (url: string) => fetch(url);
},
};
},
});
export default defineRuntimeConfig({
plugins: [initPlugin()],
});如果你在 routes/layout.tsx 中导出了 config 函数,需要将其迁移到运行时配置文件。
2.0 版本:
export const config = () => {
return {
router: {
supportHtml5History: true,
},
};
};
export default function Layout() {
return <Outlet />;
}3.0 版本:
export default function Layout() {
return <Outlet />;
}import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
router: {
supportHtml5History: true,
},
});如果你在 routes/layout.tsx 中导出了 init 函数,需要将其改为运行时插件。
2.0 版本:
export const init = (context) => {
context.request = (url: string) => fetch(url);
};
export default function Layout() {
return <Outlet />;
}3.0 版本:
export default function Layout() {
return <Outlet />;
}import type { RuntimePlugin } from '@modern-js/runtime';
import { defineRuntimeConfig } from '@modern-js/runtime';
const initPlugin = (): RuntimePlugin => ({
name: 'init-plugin',
setup: (api) => {
return {
init({ context }) {
context.request = (url: string) => fetch(url);
},
};
},
});
export default defineRuntimeConfig({
plugins: [initPlugin()],
});对于多入口应用,需要在 src/modern.runtime.ts 中使用函数形式的配置,根据入口名称返回不同的运行时配置。
目录结构:
src/
├── modern.runtime.ts # 统一的运行时配置文件
├── entry1/
│ └── routes/
└── entry2/
└── App.tsx配置示例:
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig((entryName) => {
// 公共配置
const commonConfig = {
plugins: [commonPlugin()],
};
// 根据入口名称返回特定配置
if (entryName === 'entry1') {
return {
...commonConfig,
router: {
supportHtml5History: true,
},
plugins: [
...commonConfig.plugins,
entry1Plugin(),
],
};
}
if (entryName === 'entry2') {
return {
...commonConfig,
router: {
supportHtml5History: false,
},
plugins: [
...commonConfig.plugins,
entry2Plugin(),
],
};
}
// 默认配置
return commonConfig;
});entryName 参数对应入口目录名称package.json 中 name 同名):传入的是该目录名合并同一入口的配置:如果同一入口下同时存在 App.config/App.init 和 routes/layout.tsx 的 config/init,需要将它们合并到 src/modern.runtime.ts 文件中对应入口的配置里
多个插件并列配置:多个运行时插件可以在 plugins 数组中并列配置
清理旧代码:迁移完成后,记得删除原文件中的:
App.config 属性App.init 方法routes/layout.tsx 中的 config 导出routes/layout.tsx 中的 init 导出假设你有一个 2.0 版本的多入口应用:
2.0 版本目录结构:
src/
├── main/
│ ├── routes/
│ │ └── layout.tsx # 含 config 和 init
│ └── App.tsx # 含 App.config 和 App.init
└── admin/
└── routes/
└── layout.tsx # 含 config 和 init2.0 版本配置:
const App = () => <div>Main App</div>;
App.config = {
router: { supportHtml5History: true },
};
App.init = (context) => {
context.mainData = 'main';
};export const config = () => ({
router: { supportHtml5History: false },
});
export const init = (context) => {
context.adminData = 'admin';
};3.0 版本迁移后:
src/
├── modern.runtime.ts # 新增统一配置文件
├── main/
│ ├── routes/
│ │ └── layout.tsx # 移除 config 和 init
│ └── App.tsx # 移除 App.config 和 App.init
└── admin/
└── routes/
└── layout.tsx # 移除 config 和 initimport { defineRuntimeConfig } from '@modern-js/runtime';
import type { RuntimePlugin } from '@modern-js/runtime';
// main 入口的初始化插件
const mainInitPlugin = (): RuntimePlugin => ({
name: 'main-init-plugin',
setup: (api) => {
return {
init({ context }) {
context.mainData = 'main';
},
};
},
});
// admin 入口的初始化插件
const adminInitPlugin = (): RuntimePlugin => ({
name: 'admin-init-plugin',
setup: (api) => {
return {
init({ context }) {
context.adminData = 'admin';
},
};
},
});
export default defineRuntimeConfig((entryName) => {
if (entryName === 'main') {
return {
router: {
supportHtml5History: true,
},
plugins: [mainInitPlugin()],
};
}
if (entryName === 'admin') {
return {
router: {
supportHtml5History: false,
},
plugins: [adminInitPlugin()],
};
}
return {};
});