diff --git a/packages/brick-container/build.config.js b/packages/brick-container/build.config.js index 4353476747..ea91ac8792 100644 --- a/packages/brick-container/build.config.js +++ b/packages/brick-container/build.config.js @@ -118,27 +118,12 @@ export default { mockdate: { test: /[\\/]node_modules[\\/]mockdate[\\/]/, priority: -5, - reuseExistingChunk: true, name: "mockdate", minSize: 100, }, - defaultVendors: { - test: /[\\/]node_modules[\\/]/, + all: { priority: -10, - reuseExistingChunk: true, - name: "vendors", - }, - core: { - // Make it compatible with EasyOps CI. - test: /[\\/](?:next-core|data[\\/]easyops)[\\/](?:packages|sdk)[\\/](?!theme[\\/])/, - priority: -10, - reuseExistingChunk: true, - name: "core", - }, - default: { - minChunks: 2, - priority: -20, - reuseExistingChunk: true, + name: "all", }, }, }, diff --git a/packages/brick-container/serve/middlewares/getMiddlewares.js b/packages/brick-container/serve/middlewares/getMiddlewares.js index 5a367ca5fc..9544e77a21 100644 --- a/packages/brick-container/serve/middlewares/getMiddlewares.js +++ b/packages/brick-container/serve/middlewares/getMiddlewares.js @@ -1,10 +1,10 @@ -import express from "express"; import jsYaml from "js-yaml"; import bootstrapJson from "./bootstrapJson.js"; import mockAuth from "./mockAuth.js"; import singleAppBootstrapJson from "./singleAppBootstrapJson.js"; import standaloneBootstrapJson from "./standaloneBootstrapJson.js"; import serveBricksWithVersions from "./serveBricksWithVersions.js"; +import { serveBricks } from "@next-core/serve-helpers"; const { safeDump, JSON_SCHEMA } = jsYaml; @@ -34,7 +34,7 @@ export function getMiddlewares(env) { }); middlewares.push({ path: `${baseHref}api/v1/runtime_standalone`, - middleware(req, res) { + middleware(_req, res) { res.send({ code: 0, data: { @@ -45,7 +45,7 @@ export function getMiddlewares(env) { }); middlewares.push({ path: `${baseHref}api/gateway/micro_app_standalone.runtime.RuntimeMicroAppStandalone/api/v1/micro_app_standalone/runtime/:appId`, - middleware(req, res) { + middleware(_req, res) { res.send({ code: 0, data: null, @@ -54,7 +54,7 @@ export function getMiddlewares(env) { }); middlewares.push({ path: `${baseHref}api/gateway/data_exchange.store.ClickHouseInsertData/api/v1/data_exchange/frontend_stat`, - middleware(req, res) { + middleware(_req, res) { res.send({ code: 0, data: null, @@ -67,7 +67,7 @@ export function getMiddlewares(env) { } export function getPreMiddlewares(env) { - const { baseHref, localMicroApps, localBrickFolders, userConfigByApps } = env; + const { baseHref, localMicroApps, userConfigByApps } = env; /** * @type {import("webpack-dev-server").Middleware[]} @@ -81,7 +81,7 @@ export function getPreMiddlewares(env) { }); middlewares.push({ path: `${baseHref}sa-static/${appId}/versions/0.0.0/webroot/conf.yaml`, - middleware(req, res) { + middleware(_req, res) { if (userConfigByApps) { const conf = { user_config_by_apps: userConfigByApps, @@ -108,12 +108,10 @@ export function getPreMiddlewares(env) { middleware: serveBricksWithVersions(env), }); - for (const dir of localBrickFolders) { - middlewares.push({ - path: `${baseHref}bricks/`, - middleware: express.static(dir), - }); - } + middlewares.push({ + path: `${baseHref}bricks/`, + middleware: serveBricks(env), + }); return middlewares; } diff --git a/packages/brick-playground/scripts/start.js b/packages/brick-playground/scripts/start.js index 8bbe10707d..04608be13e 100644 --- a/packages/brick-playground/scripts/start.js +++ b/packages/brick-playground/scripts/start.js @@ -1,9 +1,9 @@ import path from "node:path"; import { existsSync } from "node:fs"; import WebpackDevServer from "webpack-dev-server"; -import express from "express"; import glob from "glob"; import { build } from "@next-core/build-next-bricks"; +import { serveBricks } from "@next-core/serve-helpers"; import config from "../build.config.js"; import bootstrapJson from "../serve/bootstrapJson.js"; import examplesJson from "../serve/examplesJson.js"; @@ -52,12 +52,10 @@ const server = new WebpackDevServer( open: true, port: 8082, setupMiddlewares(middlewares) { - for (const folder of localBrickFolders) { - middlewares.push({ - path: "/preview/bricks/", - middleware: express.static(folder), - }); - } + middlewares.push({ + path: "/preview/bricks/", + middleware: serveBricks({ localBrickFolders }), + }); middlewares.push({ path: "/preview/", diff --git a/packages/brick-playground/serve/index.js b/packages/brick-playground/serve/index.js index 261588aaec..239fa44324 100644 --- a/packages/brick-playground/serve/index.js +++ b/packages/brick-playground/serve/index.js @@ -4,6 +4,7 @@ import { existsSync } from "node:fs"; import express from "express"; import compression from "compression"; import glob from "glob"; +import { serveBricks } from "@next-core/serve-helpers"; import bootstrapJson from "./bootstrapJson.js"; import examplesJson from "./examplesJson.js"; @@ -46,9 +47,7 @@ const localBrickFolders = ( ) ).flat(); -for (const folder of localBrickFolders) { - app.use("/preview/bricks/", express.static(folder)); -} +app.use("/preview/bricks/", serveBricks({ localBrickFolders })); app.use("/preview/", bootstrapJson(localBrickFolders)); app.use(examplesJson(rootDir)); diff --git a/packages/serve-helpers/index.d.ts b/packages/serve-helpers/index.d.ts index 0079e26fd9..fdfc05aa91 100644 --- a/packages/serve-helpers/index.d.ts +++ b/packages/serve-helpers/index.d.ts @@ -1,4 +1,4 @@ -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; export function getBrickPackages( localBrickFolders: string[], @@ -19,3 +19,6 @@ export function tryServeFiles( req: Request, res: Response ): string | undefined; +export function serveBricks(options: { + localBrickFolders: string[]; +}): (req: Request, res: Response, next: NextFunction) => void; diff --git a/packages/serve-helpers/src/index.js b/packages/serve-helpers/src/index.js index cf623954a0..2a8026820f 100644 --- a/packages/serve-helpers/src/index.js +++ b/packages/serve-helpers/src/index.js @@ -1,2 +1,3 @@ export * from "./getBrickPackages.js"; export * from "./tryFiles.js"; +export * from "./serveBricks.js"; diff --git a/packages/serve-helpers/src/serveBricks.js b/packages/serve-helpers/src/serveBricks.js new file mode 100644 index 0000000000..38da5ea4ac --- /dev/null +++ b/packages/serve-helpers/src/serveBricks.js @@ -0,0 +1,27 @@ +// @ts-check +import path from "node:path"; +import { tryServeFiles } from "./tryFiles.js"; + +/** + * @typedef {import("express").Request} Request + * @typedef {import("express").Response} Response + * @typedef {import("express").NextFunction} NextFunction + */ + +/** + * @param {{ localBrickFolders: string[] }} options + * @returns {(req: Request, res: Response, next: NextFunction ) => void} + */ +export function serveBricks({ localBrickFolders }) { + /** + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + return function (req, res, next) { + const files = localBrickFolders.map((dir) => + path.join(dir, req.path.split("/").join(path.sep)) + ); + tryServeFiles(files, req, res, next); + }; +} diff --git a/packages/serve-helpers/src/tryFiles.js b/packages/serve-helpers/src/tryFiles.js index 1448d869cf..ab72304772 100644 --- a/packages/serve-helpers/src/tryFiles.js +++ b/packages/serve-helpers/src/tryFiles.js @@ -1,4 +1,5 @@ -import { existsSync } from "node:fs"; +import { randomUUID } from "node:crypto"; +import { createReadStream, existsSync, statSync } from "node:fs"; /** * @param files {string|string[]} @@ -21,8 +22,55 @@ export function tryFiles(files) { export function tryServeFiles(files, req, res, next) { const filePath = tryFiles(files); if (filePath) { + // Express.js doesn't support multiple ranges out of the box. + const range = req.get("range"); + if (range) { + const matches = range.match(/bytes=([\d-]+(?:,\s*[\d-]+)+)/); + if (matches) { + const ranges = matches[1].split(/,\s*/g); + if (ranges.length > 1) { + return sendMultiRanges(filePath, ranges, res); + } + } + } res.sendFile(filePath); return; } next(); } + +function sendMultiRanges(filePath, _ranges, res) { + const stat = statSync(filePath); + const fileSize = stat.size; + + const ranges = _ranges.map((range) => { + const [start, end] = range.split("-").map(Number); + return { + start: isNaN(start) ? fileSize - end : start, + end: isNaN(end) ? fileSize - 1 : end, + }; + }); + + const boundary = randomUUID().replaceAll("-", ""); + res.writeHead(206, { + "Content-Type": `multipart/byteranges; boundary=${boundary}`, + "Last-Modified": stat.mtime.toUTCString(), + "Cache-Control": "max-age=31536000", + // "Expires": "Thu, 12 Feb 2026 09:39:38 GMT", + }); + + (async () => { + for (const { start, end } of ranges) { + await new Promise((resolve) => { + const stream = createReadStream(filePath, { start, end }); + let chunk = `\r\n--${boundary}\r\n`; + chunk += `Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`; + res.write(chunk); + stream.pipe(res, { end: false }); + stream.on("end", resolve); + }); + } + })().then(() => { + res.end(`\r\n--${boundary}--\r\n`); + }); +}