Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/metal-walls-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modern-js/runtime-utils': patch
'@modern-js/server-core': patch
---

feat: Modern.js server static middleware should always use system fs
feat: Modern.js 的静态中间件应该始终使用系统 fs
14 changes: 13 additions & 1 deletion packages/server/core/src/adapters/node/plugins/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ export interface ServerStaticOptions {
routes?: ServerRoute[];
}

/**
* This middleware is used to serve static assets
* TODO: In next major version, only serve static assets in the `static` and `upload` directory.
*
* 1. In dev mode, the static assets generated by bundler will be served by the rsbuildDevMiddleware, and other file in `static` directory will be served by this middleware.
* 2. In prod mode, all the static assets in `static` and `upload` directory will be served by this middleware.
* 3. So some file not in `static` can be access in dev mode, but not in prod mode. Cause we can not serve all files in prod mode, as we should not expose server code in prod mode.
* 4. Through Modern.js not serve this file in prod mode, you can upload the files to a CDN.
*/
export function createStaticMiddleware(
options: ServerStaticOptions,
): Middleware {
Expand All @@ -107,7 +116,9 @@ export function createStaticMiddleware(
const favicons = prepareFavicons(favicon, faviconByEntries);
const staticFiles = [cssPath, jsPath, mediaPath].filter(v => Boolean(v));

// TODO: If possible, we should not use `...staticFiles` here, file should only be read in static and upload dir.
const staticReg = ['static/', 'upload/', ...staticFiles];
// TODO: Also remove iconReg
const iconReg = ['favicon.ico', 'icon.png', ...favicons];
const regPrefix = prefix.endsWith('/') ? prefix : `${prefix}/`;
const staticPathRegExp = new RegExp(
Expand Down Expand Up @@ -147,7 +158,8 @@ export function createStaticMiddleware(
}
const stat = await fs.lstat(filepath);
const { size } = stat;
const chunk = await fileReader.readFile(filepath, 'buffer');
// serve static middleware always read file from real filesystem.
const chunk = await fileReader.readFileFromSystem(filepath, 'buffer');

// TODO: handle http range
c.header('Content-Length', String(size));
Expand Down
83 changes: 55 additions & 28 deletions packages/toolkit/runtime-utils/src/node/fileReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,66 @@ export class FileReader {
encoding: 'utf-8' | 'buffer' = 'utf-8',
): Promise<string | Buffer | null> {
const { fs } = this;
const cache = await this.storage.get(path);
if (cache === null) {
return null;
}
if (cache) {
return this.encodingContent(cache, encoding);
}
const _readfile = this._readFileFactory(fs);
return _readfile(path, encoding);
}

async readFileFromSystem(
path: string,
encoding?: 'utf-8',
): Promise<string | null>;
async readFileFromSystem(
path: string,
encoding?: 'buffer',
): Promise<Buffer | null>;
async readFileFromSystem(
path: string,
encoding: 'utf-8' | 'buffer' = 'utf-8',
): Promise<string | Buffer | null> {
console.log(path, '!!');
const _readfile = this._readFileFactory(Fs);
return _readfile(path, encoding);
}

_readFileFactory(fs: typeof Fs) {
return async (
path: string,
encoding: 'utf-8' | 'buffer' = 'utf-8',
): Promise<string | Buffer | null> => {
const cache = await this.storage.get(path);
if (cache === null) {
return null;
}
if (cache) {
return this.encodingContent(cache, encoding);
}

const isExistFile = await new Promise(resolve => {
fs.stat(path, (err, stats) => {
if (err) {
resolve(false);
return;
}
if (stats.isFile()) {
resolve(true);
} else {
resolve(false);
}
const isExistFile = await new Promise(resolve => {
fs.stat(path, (err, stats) => {
if (err) {
resolve(false);
return;
}
if (stats.isFile()) {
resolve(true);
} else {
resolve(false);
}
});
});
});

if (isExistFile) {
const content = await fs.promises.readFile(path);
if (isExistFile) {
const content = await fs.promises.readFile(path);

this.storage.set(path, content);
this.storage.set(path, content);

return this.encodingContent(content, encoding);
} else {
// if is not exist, return null value.
this.storage.set(path, null);
return null;
}
return this.encodingContent(content, encoding);
} else {
// if is not exist, return null value.
this.storage.set(path, null);
return null;
}
};
}

/**
Expand Down