Entry Changes
This chapter introduces changes related to page entries when upgrading from Modern.js 2.0 to 3.0.
Overview
Modern.js 3.0 has optimized and simplified the entry mechanism. The main changes include:
- Entry File Naming Change: Custom entry files changed from
index.tsx to entry.tsx
- Bootstrap Function Replacement: Use the new
createRoot and render APIs
- Runtime Configuration Migration:
App.config and config exports from routes/layout need to be migrated
- Initialization Logic Migration:
App.init and init exports from routes/layout need to be changed to runtime plugins
Entry Type Identification
Before starting migration, first identify the entry type used in your project.
Entry Identification Conditions
Modern.js scans directories and identifies entries that meet any of the following conditions:
- Has a
routes/ directory → Convention-based routing entry
- Has an
App.tsx? file → Self-controlled routing entry
- Has an
index.tsx? file (2.0) or entry.tsx? file (3.0) → Custom entry
Single Entry vs Multiple Entries
Single Entry Application: Scans the src/ directory by default
src/
├── routes/ # or
├── App.tsx # or
└── index.tsx # 2.0 version
Multiple Entry Application: Scans first-level subdirectories under src/
src/
├── entry1/
│ └── routes/ # Each subdirectory is an entry
└── entry2/
└── App.tsx
Tip
You can modify the entry scanning directory through the source.entriesDir configuration.
Migration Steps
The migration operations in this section only need to be performed when the corresponding usage actually exists in the project, such as bootstrap function, App.config/App.init, config/init functions in routes/layout.tsx, etc.
1. Custom Entry File Rename
If your project uses a custom entry file (index.tsx), you need to rename it to entry.tsx.
2.0 Version:
3.0 Version:
2. Bootstrap Function Migration
If your entry file exports a function that receives App and bootstrap parameters, you need to use the new API instead.
2.0 Version:
src/index.tsx
export default (App: React.ComponentType, bootstrap: () => void) => {
// Perform initialization operations
initSomething().then(() => {
bootstrap();
});
};
3.0 Version:
src/entry.tsx
import { createRoot } from '@modern-js/runtime/react';
import { render } from '@modern-js/runtime/browser';
// Create root component
const ModernRoot = createRoot();
// Perform initialization operations
async function beforeRender() {
await initSomething();
}
// Render application
beforeRender().then(() => {
render(<ModernRoot />);
});
Note
- The component returned by
createRoot() corresponds to the component generated by the routes/ directory or exported by App.tsx
- The
render() function is used to handle rendering and mounting components
3. App.config Migration
If you defined App.config in App.tsx, you need to migrate it to the runtime configuration file.
2.0 Version:
src/App.tsx
const App = () => {
return <div>Hello</div>;
};
App.config = {
router: {
supportHtml5History: true,
},
};
export default App;
3.0 Version:
Create modern.runtime.ts in the same directory as the entry:
src/modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
router: {
supportHtml5History: true,
},
});
Note
Modern.js 3.0 no longer supports configuring runtime in modern.config.ts, you must use the modern.runtime.ts file.
4. App.init Migration
If you defined App.init in App.tsx, you need to change it to a runtime plugin.
2.0 Version:
src/App.tsx
const App = () => {
return <div>Hello</div>;
};
App.init = context => {
context.store = createStore();
context.request = (url: string) => fetch(url);
};
export default App;
3.0 Version:
src/modern.runtime.ts
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()],
});
5. config Export Migration from routes/layout.tsx
If you exported a config function in routes/layout.tsx, you need to migrate it to the runtime configuration file.
2.0 Version:
src/routes/layout.tsx
export const config = () => {
return {
router: {
supportHtml5History: true,
},
};
};
export default function Layout() {
return <Outlet />;
}
3.0 Version:
src/routes/layout.tsx
export default function Layout() {
return <Outlet />;
}
src/modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
router: {
supportHtml5History: true,
},
});
6. init Export Migration from routes/layout.tsx
If you exported an init function in routes/layout.tsx, you need to change it to a runtime plugin.
2.0 Version:
src/routes/layout.tsx
export const init = context => {
context.request = (url: string) => fetch(url);
};
export default function Layout() {
return <Outlet />;
}
3.0 Version:
src/routes/layout.tsx
export default function Layout() {
return <Outlet />;
}
src/modern.runtime.ts
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()],
});
Multi-Entry Application Migration Notes
For multi-entry applications, you need to use function-form configuration in src/modern.runtime.ts, returning different runtime configurations based on entry names.
Configuration Method
Directory Structure:
src/
├── modern.runtime.ts # Unified runtime configuration file
├── entry1/
│ └── routes/
└── entry2/
└── App.tsx
Configuration Example:
src/modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig(entryName => {
// Common configuration
const commonConfig = {
plugins: [commonPlugin()],
};
// Return specific configuration based on entry name
if (entryName === 'entry1') {
return {
...commonConfig,
router: {
supportHtml5History: true,
},
plugins: [...commonConfig.plugins, entry1Plugin()],
};
}
if (entryName === 'entry2') {
return {
...commonConfig,
router: {
supportHtml5History: false,
},
plugins: [...commonConfig.plugins, entry2Plugin()],
};
}
// Default configuration
return commonConfig;
});
Note
- The
entryName parameter corresponds to the entry directory name
- Main entry (same name as
name in package.json): the directory name is passed in
- Other entries: the entry directory name is passed in
Migration Notes
-
Merge configurations for the same entry: If both App.config/App.init and config/init from routes/layout.tsx exist for the same entry, you need to merge them into the corresponding entry configuration in the src/modern.runtime.ts file
-
Multiple plugins in parallel: Multiple runtime plugins can be configured in parallel in the plugins array
-
Clean up old code: After migration is complete, remember to delete from the original files:
App.config property
App.init method
config export from routes/layout.tsx
init export from routes/layout.tsx
Migration Example
Assume you have a 2.0 version multi-entry application:
2.0 Version Directory Structure:
src/
├── main/
│ ├── routes/
│ │ └── layout.tsx # Contains config and init
│ └── App.tsx # Contains App.config and App.init
└── admin/
└── routes/
└── layout.tsx # Contains config and init
2.0 Version Configuration:
src/main/App.tsx
const App = () => <div>Main App</div>;
App.config = {
router: { supportHtml5History: true },
};
App.init = context => {
context.mainData = 'main';
};
src/admin/routes/layout.tsx
export const config = () => ({
router: { supportHtml5History: false },
});
export const init = context => {
context.adminData = 'admin';
};
3.0 Version After Migration:
src/
├── modern.runtime.ts # New unified configuration file
├── main/
│ ├── routes/
│ │ └── layout.tsx # Removed config and init
│ └── App.tsx # Removed App.config and App.init
└── admin/
└── routes/
└── layout.tsx # Removed config and init
src/modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
import type { RuntimePlugin } from '@modern-js/runtime';
// Main entry initialization plugin
const mainInitPlugin = (): RuntimePlugin => ({
name: 'main-init-plugin',
setup: api => {
return {
init({ context }) {
context.mainData = 'main';
},
};
},
});
// Admin entry initialization plugin
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 {};
});