In this chapter, you will learn about the entry convention in Modern.js and how to customize it.
Entry refers to the starting module of a page.
In a Modern.js project, each entry corresponds to an independent page and a server-side route. By default, Modern.js automatically determines the entry of a page based on directory conventions, but also supports customizing the entry through configuration options.
Many configuration options provided by Modern.js are divided by entry, such as page title, HTML template, page meta information, whether to enable SSR/SSG, server-side routing rules, etc.
The project initialized by Modern.js is a single entry (SPA) project, with the following structure:
.
├── src
│ └── routes
│ ├── index.css
│ ├── layout.tsx
│ └── page.tsx
├── package.json
├── modern.config.ts
└── tsconfig.json
In a Modern.js project, you can easily switch from single entry to multiple entries by running pnpm run new
in the project directory and creating an entry:
? Please select the operation you want: Create Element
? Please select the type of element to create: New "entry"
? Please fill in the entry name: new-entry
After running the command, Modern.js will automatically generate a new entry directory. At this point, you can see that the src/
directory has the following structure:
.
├── myapp # Original entry
│ └── routes
│ ├── index.css
│ ├── layout.tsx
│ └── page.tsx
└── new-entry # New entry
└── routes
├── index.css
├── layout.tsx
└── page.tsx
The original entry code has been moved to a directory with the same name as the name
field in package.json
, and a new-entry
entry directory has been created.
You can run pnpm run dev
to start the development server. At this point, you will see a new route named /new-entry
added, and the existing page routes remain unchanged.
Modern.js will use the entry with the same name as the name
field in package.json
as the main entry. The route of the main entry is /
, and the route of other entries is /{entryName}
.
For example, when the name
field in package.json
is myapp
, src/myapp
will be the main entry of the project.
Different entry types have different compilation and runtime behaviors.
By default, Modern.js scans the files under src/
before starting the project, identifies the entry, and generates the corresponding server-side route.
Not all top-level directories under src/
become project entries. The directory where the entry is located must meet one of the following four conditions:
routes/
directory.App.[jt]sx?
file.index.[jt]sx?
file.pages/
directory (compatible with Modern.js 1.0).When the src/
directory meets the entry requirements, Modern.js considers the current project as a single entry application.
In a single entry application, the default entry name is main
.
When the project is not a single entry application, Modern.js will further look at the top-level directories under src/
.
The framework mode refers to the need to use the framework capabilities of Modern.js, such as nested routing, SSR, and integrated BFF, etc. Under this kind of entry convention, the entry defined by the developer is not the actual compilation entry. When Modern.js is launched, it generates a wrapped entry, and the real entry can be found at node_modules/.modern/[entryName]/index.js
.
If there is a routes/
directory in the entry, Modern.js will scan the files under routes/
during startup, and automatically generate client-side routes (react-router) based on file conventions. For example:
.
├── src
│ └── routes
│ ├── layout.tsx
│ └── page.tsx
In the above directory, the component exported in layout.tsx
will be the outermost component, and the component exported in page.tsx
will be the component of the /
route.
For more information, please refer to Conventional Routing.
If there is an App.[jt]sx?
file in the entry, developers can set the client-side route in this file through code, or not set the client-side route.
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>
);
};
For more information, please refer to Self-controlled Routing.
If there is an index.[jt]sx
file in the entry, and the file exports a function by default, Modern.js will pass the default bootstrap
function as a parameter and use the exported function to replace the default bootstrap
. This way, developers can customize how components are mounted to DOM nodes or add custom behavior before mounting. For example:
export default (App: React.ComponentType, bootstrap: () => void) => {
// do something before bootstrap...
initSomething().then(() => {
bootstrap();
});
};
At this point, the generated file content of Modern.js is as follows:
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;
function render() {
AppWrapper = createApp({
// plugin parameters for runtime...
})(App);
if (IS_BROWSER) {
customBootstrap(AppWrapper);
}
return AppWrapper;
}
AppWrapper = render();
export default AppWrapper;
Build mode refers to the mode where the entry point of the page is not automatically generated by Modern.js, but is fully defined by the developers themselves.
When there is an index.[jt]sx
file in the entry directory and it is not exported as a function using export default
, this type of file will be recognized as the entry module for webpack or Rspack.
In this case, Modern.js will not generate the entry code automatically. Therefore, you need to manually mount the component to the DOM node, for example:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
This approach is equivalent to enabling the source.entries.disableMount option in Modern.js. When you use this approach, you will not be able to use the runtime capabilities of the Modern.js framework, such as the runtime
configuration in the modern.config.js file will no longer take effect.
In some cases, you may need to customize the entry configuration instead of using the entry conventions provided by Modern.js.
For example, if you want to migrate a non-Modern.js project to Modern.js and it is not structured according to Modern.js directory structure, there might be some migration costs involved in changing it to the conventional structure. In such cases, you can custom the entries.
Modern.js provides the following configuration options that you can set in modern.config.ts:
Here is an example of a custom entry point. You can also refer to the documentation of the corresponding configuration options for more usage.
export default defineConfig({
source: {
entries: {
// Specify an entry named 'my-entry'
'my-entry': {
// Path to the entry module
entry: './src/my-page/index.tsx',
// Disable automatic generation of entry code by Modern.js
disableMount: true,
},
},
// Disable entry scanning behavior
disableDefaultEntries: true,
},
});
Note that when you enable disableMount
, you won't be able to use the runtime capabilities of the Modern.js framework, such as the runtime
configuration in the modern.config.ts file.