UNPKG

@farmfe/core

Version:

Farm is a extremely fast web build tool written in Rust. Farm can start a project in milliseconds and perform HMR within 10ms, making it much faster than similar tools like webpack and vite.

144 lines 6.06 kB
/** * Serve resources that stored in memory. This middleware will be enabled when server.writeToDisk is false. */ import { ReadStream, existsSync, readFileSync, statSync } from 'node:fs'; import path, { extname } from 'node:path'; import koaStatic from 'koa-static'; import { generateFileTree, generateFileTreeHtml, stripQueryAndHash } from '../../utils/index.js'; function normalizePathByPublicPath(publicPath, resourcePath) { const base = publicPath.match(/^https?:\/\//) ? '' : publicPath; let resourceWithoutPublicPath = resourcePath; if (base && resourcePath.startsWith(base)) { resourcePath = resourcePath.replace(new RegExp(`([^/]+)${base}`), '$1/'); resourceWithoutPublicPath = resourcePath.slice(base.length); } return { resourceWithoutPublicPath, fullPath: resourcePath }; } function outputFilesMiddleware(compiler) { return async (ctx, next) => { if (ctx.path === '/_output_files') { const files = Object.keys(compiler.resources()).sort(); const fileTree = generateFileTree(files); ctx.type = '.html'; ctx.body = generateFileTreeHtml(fileTree); } else { await next(); } }; } function findResource(paths, compiler, publicPath) { for (const resourcePath of new Set(paths)) { const { resourceWithoutPublicPath } = normalizePathByPublicPath(publicPath, resourcePath); const resource = compiler.resource(resourceWithoutPublicPath); if (resource) { return { resource, resourcePath: resourceWithoutPublicPath, rawPath: resourcePath }; } } } export function resourcesMiddleware(compiler, serverContext) { return async (ctx, next) => { await next(); if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return; const hasHtmlPathWithPublicDir = path.resolve(serverContext.publicDir, 'index.html'); let isSkipPublicHtml; if (ctx.body instanceof ReadStream) { const readStream = ctx.body; isSkipPublicHtml = readStream.path === hasHtmlPathWithPublicDir; } // the response is already handled if ((ctx.body || ctx.status !== 404) && !isSkipPublicHtml) return; const { config, publicPath } = serverContext; // if compiler is compiling, wait for it to finish if (compiler.compiling) { await new Promise((resolve) => { compiler.onUpdateFinish(() => resolve(undefined)); }); } // Fallback to index.html if the resource is not found const url = ctx.url?.slice(1) || 'index.html'; // remove leading slash let stripQueryAndHashUrl = stripQueryAndHash(url); const resourceResult = findResource([url, stripQueryAndHashUrl], compiler, publicPath); if (resourceResult === true) { return; } if (resourceResult) { ctx.type = extname(ctx?.path?.slice?.(1) || 'index.html'); ctx.body = resourceResult.resource; return; } const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath(publicPath, stripQueryAndHashUrl); // if resource is image or font, try it in local file system to be compatible with vue { // try local file system const absPath = path.join(compiler.config.config.root, resourceWithoutPublicPath); // const mimeStr = mime.lookup(absPath); if (existsSync(absPath) && statSync(absPath).isFile() // mimeStr && // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) ) { ctx.type = extname(fullPath); ctx.body = readFileSync(absPath); return; } // try local file system with publicDir const absPathPublicDir = path.resolve(compiler.config.config.root, compiler.config.config.assets.publicDir, resourceWithoutPublicPath); if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { ctx.type = extname(fullPath); ctx.body = readFileSync(absPathPublicDir); return; } } // if resource is not found and spa is not disabled, find the closest index.html from resourcePath { // if request mime is not html, return 404 if (!ctx.accepts('html')) { ctx.status = 404; } else if (config.spa !== false) { const pathComps = resourceWithoutPublicPath.split('/'); while (pathComps.length > 0) { const pathStr = pathComps.join('/') + '.html'; const resource = compiler.resources()[pathStr]; if (resource) { ctx.type = '.html'; ctx.body = resource; return; } pathComps.pop(); } const indexHtml = compiler.resources()['index.html']; if (indexHtml) { ctx.type = '.html'; ctx.body = indexHtml; return; } } else { // cannot find index.html, return 404 ctx.status = 404; } } }; } export function resources(devSeverContext) { const middlewares = [outputFilesMiddleware(devSeverContext.getCompiler())]; if (!devSeverContext.config.writeToDisk) { middlewares.push(resourcesMiddleware(devSeverContext.getCompiler(), devSeverContext)); } else { middlewares.push(koaStatic(devSeverContext.getCompiler().config.config.output.path, { extensions: ['html'] })); } middlewares.push(koaStatic(devSeverContext.publicDir)); return middlewares; } //# sourceMappingURL=resources.js.map