.
This commit is contained in:
+342
@@ -0,0 +1,342 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
0 && (module.exports = {
|
||||
default: null,
|
||||
getFilenameAndExtension: null
|
||||
});
|
||||
function _export(target, all) {
|
||||
for(var name in all)Object.defineProperty(target, name, {
|
||||
enumerable: true,
|
||||
get: all[name]
|
||||
});
|
||||
}
|
||||
_export(exports, {
|
||||
default: function() {
|
||||
return _default;
|
||||
},
|
||||
getFilenameAndExtension: function() {
|
||||
return getFilenameAndExtension;
|
||||
}
|
||||
});
|
||||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
|
||||
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
|
||||
const _mimetype = require("../../../lib/mime-type");
|
||||
const _utils = require("./utils");
|
||||
const _installbindings = require("../../swc/install-bindings");
|
||||
function _interop_require_default(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
function errorOnBadHandler(resourcePath) {
|
||||
return `
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error('Default export is missing in ${JSON.stringify(resourcePath)}')
|
||||
}
|
||||
`;
|
||||
}
|
||||
/* re-export the userland route configs */ async function createReExportsCode(resourcePath, loaderContext) {
|
||||
const exportNames = await (0, _utils.getLoaderModuleNamedExports)(resourcePath, loaderContext);
|
||||
// Re-export configs but avoid conflicted exports
|
||||
const reExportNames = exportNames.filter((name)=>name !== 'default' && name !== 'generateSitemaps' && name !== 'dynamicParams');
|
||||
return reExportNames.length > 0 ? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(resourcePath)}\n` : '';
|
||||
}
|
||||
const CACHE_HEADERS = {
|
||||
NO_CACHE: 'no-cache, no-store',
|
||||
REVALIDATE: 'public, max-age=0, must-revalidate'
|
||||
};
|
||||
function getFilenameAndExtension(resourcePath) {
|
||||
const filename = _path.default.basename(resourcePath);
|
||||
const [name, ext] = filename.split('.', 2);
|
||||
return {
|
||||
name,
|
||||
ext
|
||||
};
|
||||
}
|
||||
function getContentType(resourcePath) {
|
||||
let { name, ext } = getFilenameAndExtension(resourcePath);
|
||||
if (ext === 'jpg') ext = 'jpeg';
|
||||
if (name === 'favicon' && ext === 'ico') return 'image/x-icon';
|
||||
if (name === 'sitemap') return 'application/xml';
|
||||
if (name === 'robots') return 'text/plain';
|
||||
if (name === 'manifest') return 'application/manifest+json';
|
||||
if (ext === 'png' || ext === 'jpeg' || ext === 'ico' || ext === 'svg') {
|
||||
return _mimetype.imageExtMimeTypeMap[ext];
|
||||
}
|
||||
return 'text/plain';
|
||||
}
|
||||
async function getStaticAssetRouteCode(resourcePath, fileBaseName) {
|
||||
const cache = process.env.NODE_ENV !== 'production' ? CACHE_HEADERS.NO_CACHE : CACHE_HEADERS.REVALIDATE;
|
||||
const isTwitter = fileBaseName === 'twitter-image';
|
||||
const isOpenGraph = fileBaseName === 'opengraph-image';
|
||||
// Twitter image file size limit is 5MB.
|
||||
// General Open Graph image file size limit is 8MB.
|
||||
// x-ref: https://developer.x.com/en/docs/x-for-websites/cards/overview/summary
|
||||
// x-ref(facebook): https://developers.facebook.com/docs/sharing/webmasters/images
|
||||
const fileSizeLimit = isTwitter ? 5 : 8;
|
||||
const imgName = isTwitter ? 'Twitter' : 'Open Graph';
|
||||
const code = `\
|
||||
/* static asset route */
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const contentType = ${JSON.stringify(getContentType(resourcePath))}
|
||||
const buffer = Buffer.from(${JSON.stringify((await _fs.default.promises.readFile(resourcePath)).toString('base64'))}, 'base64'
|
||||
)
|
||||
|
||||
if (${isTwitter || isOpenGraph}) {
|
||||
const fileSizeInMB = buffer.byteLength / 1024 / 1024
|
||||
if (fileSizeInMB > ${fileSizeLimit}) {
|
||||
throw new Error('File size for ${imgName} image ${JSON.stringify(resourcePath)} exceeds ${fileSizeLimit}MB. ' +
|
||||
\`(Current: \${fileSizeInMB.toFixed(2)}MB)\n\` +
|
||||
'Read more: https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image#image-files-jpg-png-gif'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function GET() {
|
||||
return new NextResponse(buffer, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': ${JSON.stringify(cache)},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const dynamic = 'force-static'
|
||||
`;
|
||||
return code;
|
||||
}
|
||||
async function getDynamicTextRouteCode(resourcePath, loaderContext) {
|
||||
return `\
|
||||
/* dynamic asset route */
|
||||
import { NextResponse } from 'next/server'
|
||||
import handler from ${JSON.stringify(resourcePath)}
|
||||
import { resolveRouteData } from 'next/dist/build/webpack/loaders/metadata/resolve-route-data'
|
||||
|
||||
const contentType = ${JSON.stringify(getContentType(resourcePath))}
|
||||
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
|
||||
|
||||
${errorOnBadHandler(resourcePath)}
|
||||
${await createReExportsCode(resourcePath, loaderContext)}
|
||||
|
||||
export async function GET() {
|
||||
const data = await handler()
|
||||
const content = resolveRouteData(data, fileType)
|
||||
|
||||
return new NextResponse(content, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': ${JSON.stringify(CACHE_HEADERS.REVALIDATE)},
|
||||
},
|
||||
})
|
||||
}
|
||||
`;
|
||||
}
|
||||
async function getDynamicImageRouteCode(resourcePath, loaderContext) {
|
||||
return `\
|
||||
/* dynamic image route with generateImageMetadata */
|
||||
import { NextResponse } from 'next/server'
|
||||
import { default as handler, generateImageMetadata } from ${JSON.stringify(resourcePath)}
|
||||
|
||||
${errorOnBadHandler(resourcePath)}
|
||||
${await createReExportsCode(resourcePath, loaderContext)}
|
||||
|
||||
export async function GET(_, ctx) {
|
||||
const paramsPromise = ctx.params
|
||||
const idPromise = paramsPromise.then(params => params?.__metadata_id__)
|
||||
const restParamsPromise = paramsPromise.then(params => {
|
||||
if (!params) return undefined
|
||||
const { __metadata_id__, ...rest } = params
|
||||
return rest
|
||||
})
|
||||
|
||||
const restParams = await restParamsPromise
|
||||
const __metadata_id__ = await idPromise
|
||||
const imageMetadata = await generateImageMetadata({ params: restParams })
|
||||
const id = imageMetadata.find((item) => {
|
||||
if (item?.id == null) {
|
||||
throw new Error('id property is required for every item returned from generateImageMetadata')
|
||||
}
|
||||
|
||||
return item.id.toString() === __metadata_id__
|
||||
})?.id
|
||||
|
||||
if (id == null) {
|
||||
return new NextResponse('Not Found', {
|
||||
status: 404,
|
||||
})
|
||||
}
|
||||
|
||||
return handler({ params: restParamsPromise, id: idPromise })
|
||||
}
|
||||
|
||||
export async function generateStaticParams({ params }) {
|
||||
const imageMetadata = await generateImageMetadata({ params })
|
||||
const staticParams = []
|
||||
|
||||
for (const item of imageMetadata) {
|
||||
if (item?.id == null) {
|
||||
throw new Error('id property is required for every item returned from generateImageMetadata')
|
||||
}
|
||||
staticParams.push({ __metadata_id__: item.id.toString() })
|
||||
}
|
||||
return staticParams
|
||||
}
|
||||
`;
|
||||
}
|
||||
async function getSingleImageRouteCode(resourcePath, loaderContext) {
|
||||
return `\
|
||||
/* dynamic image route without generateImageMetadata */
|
||||
import { NextResponse } from 'next/server'
|
||||
import { default as handler } from ${JSON.stringify(resourcePath)}
|
||||
|
||||
${errorOnBadHandler(resourcePath)}
|
||||
${await createReExportsCode(resourcePath, loaderContext)}
|
||||
|
||||
export async function GET(_, ctx) {
|
||||
return handler({ params: ctx.params })
|
||||
}
|
||||
`;
|
||||
}
|
||||
// <metadata-image>/[id]/route.js
|
||||
async function getImageRouteCode(resourcePath, loaderContext) {
|
||||
const exportNames = await (0, _utils.getLoaderModuleNamedExports)(resourcePath, loaderContext);
|
||||
const hasGenerateParamsExport = exportNames.includes('generateImageMetadata');
|
||||
if (hasGenerateParamsExport) {
|
||||
return getDynamicImageRouteCode(resourcePath, loaderContext);
|
||||
} else {
|
||||
return getSingleImageRouteCode(resourcePath, loaderContext);
|
||||
}
|
||||
}
|
||||
async function getSingleSitemapRouteCode(resourcePath, loaderContext) {
|
||||
return `\
|
||||
/* single sitemap route */
|
||||
import { NextResponse } from 'next/server'
|
||||
import { default as handler } from ${JSON.stringify(resourcePath)}
|
||||
import { resolveRouteData } from 'next/dist/build/webpack/loaders/metadata/resolve-route-data'
|
||||
|
||||
const contentType = ${JSON.stringify(getContentType(resourcePath))}
|
||||
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
|
||||
|
||||
${errorOnBadHandler(resourcePath)}
|
||||
${await createReExportsCode(resourcePath, loaderContext)}
|
||||
|
||||
export async function GET() {
|
||||
const data = await handler()
|
||||
const content = resolveRouteData(data, fileType)
|
||||
|
||||
return new NextResponse(content, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': ${JSON.stringify(CACHE_HEADERS.REVALIDATE)},
|
||||
},
|
||||
})
|
||||
}
|
||||
`;
|
||||
}
|
||||
async function getDynamicSitemapRouteCode(resourcePath, loaderContext) {
|
||||
const code = `\
|
||||
/* dynamic sitemap route with generateSitemaps */
|
||||
import { NextResponse } from 'next/server'
|
||||
import { default as handler, generateSitemaps } from ${JSON.stringify(resourcePath)}
|
||||
import { resolveRouteData } from 'next/dist/build/webpack/loaders/metadata/resolve-route-data'
|
||||
|
||||
const contentType = ${JSON.stringify(getContentType(resourcePath))}
|
||||
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
|
||||
|
||||
${errorOnBadHandler(resourcePath)}
|
||||
${await createReExportsCode(resourcePath, loaderContext)}
|
||||
|
||||
export async function GET(_, ctx) {
|
||||
const paramsPromise = ctx.params
|
||||
const idPromise = paramsPromise.then(params => params?.__metadata_id__)
|
||||
|
||||
const id = await idPromise
|
||||
const hasXmlExtension = id ? id.endsWith('.xml') : false
|
||||
const sitemaps = await generateSitemaps()
|
||||
let foundId
|
||||
for (const item of sitemaps) {
|
||||
if (item?.id == null) {
|
||||
throw new Error('id property is required for every item returned from generateSitemaps')
|
||||
}
|
||||
|
||||
const baseId = id && hasXmlExtension ? id.slice(0, -4) : undefined
|
||||
if (item.id.toString() === baseId) {
|
||||
foundId = item.id
|
||||
}
|
||||
}
|
||||
if (foundId == null) {
|
||||
return new NextResponse('Not Found', {
|
||||
status: 404,
|
||||
})
|
||||
}
|
||||
|
||||
const targetIdPromise = idPromise.then(id => {
|
||||
const hasXmlExtension = id ? id.endsWith('.xml') : false
|
||||
return id && hasXmlExtension ? id.slice(0, -4) : undefined
|
||||
})
|
||||
const data = await handler({ id: targetIdPromise })
|
||||
const content = resolveRouteData(data, fileType)
|
||||
|
||||
return new NextResponse(content, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': ${JSON.stringify(CACHE_HEADERS.REVALIDATE)},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const sitemaps = await generateSitemaps()
|
||||
const params = []
|
||||
|
||||
for (const item of sitemaps) {
|
||||
if (item?.id == null) {
|
||||
throw new Error('id property is required for every item returned from generateSitemaps')
|
||||
}
|
||||
params.push({ __metadata_id__: item.id.toString() + '.xml' })
|
||||
}
|
||||
return params
|
||||
}
|
||||
`;
|
||||
return code;
|
||||
}
|
||||
// <metadata-sitemap>/[id]/route.js
|
||||
async function getSitemapRouteCode(resourcePath, loaderContext) {
|
||||
const exportNames = await (0, _utils.getLoaderModuleNamedExports)(resourcePath, loaderContext);
|
||||
const hasGenerateSitemaps = exportNames.includes('generateSitemaps');
|
||||
if (hasGenerateSitemaps) {
|
||||
return getDynamicSitemapRouteCode(resourcePath, loaderContext);
|
||||
} else {
|
||||
return getSingleSitemapRouteCode(resourcePath, loaderContext);
|
||||
}
|
||||
}
|
||||
// When it's static route, it could be favicon.ico, sitemap.xml, robots.txt etc.
|
||||
// TODO-METADATA: improve the cache control strategy
|
||||
const nextMetadataRouterLoader = async function() {
|
||||
// Install bindings early so they are definitely available to the loader.
|
||||
// When run by webpack in next this is already done with correct configuration so this is a no-op.
|
||||
// In turbopack loaders are run in a subprocess so it may or may not be done.
|
||||
await (0, _installbindings.installBindings)();
|
||||
const { isDynamicRouteExtension, filePath } = this.getOptions();
|
||||
const { name: fileBaseName } = getFilenameAndExtension(filePath);
|
||||
this.addDependency(filePath);
|
||||
let code = '';
|
||||
if (isDynamicRouteExtension === '1') {
|
||||
if (fileBaseName === 'robots' || fileBaseName === 'manifest') {
|
||||
code = await getDynamicTextRouteCode(filePath, this);
|
||||
} else if (fileBaseName === 'sitemap') {
|
||||
code = await getSitemapRouteCode(filePath, this);
|
||||
} else {
|
||||
code = await getImageRouteCode(filePath, this);
|
||||
}
|
||||
} else {
|
||||
code = await getStaticAssetRouteCode(filePath, fileBaseName);
|
||||
}
|
||||
return code;
|
||||
};
|
||||
const _default = nextMetadataRouterLoader;
|
||||
|
||||
//# sourceMappingURL=next-metadata-route-loader.js.map
|
||||
Reference in New Issue
Block a user