next
Version:
The React Framework
513 lines (512 loc) • 24.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.handleWebpackExtenalForEdgeRuntime = handleWebpackExtenalForEdgeRuntime;
exports.default = void 0;
var _routeRegex = require("../../../shared/lib/router/utils/route-regex");
var _getModuleBuildInfo = require("../loaders/get-module-build-info");
var _utils = require("../../../shared/lib/router/utils");
var _webpack = require("next/dist/compiled/webpack/webpack");
var _constants = require("../../../shared/lib/constants");
class MiddlewarePlugin {
constructor({ dev }){
this.dev = dev;
}
apply(compiler) {
compiler.hooks.compilation.tap(NAME, (compilation, params)=>{
const { hooks } = params.normalModuleFactory;
/**
* This is the static code analysis phase.
*/ const codeAnalyzer = getCodeAnalizer({
dev: this.dev,
compiler,
compilation
});
hooks.parser.for("javascript/auto").tap(NAME, codeAnalyzer);
hooks.parser.for("javascript/dynamic").tap(NAME, codeAnalyzer);
hooks.parser.for("javascript/esm").tap(NAME, codeAnalyzer);
/**
* Extract all metadata for the entry points in a Map object.
*/ const metadataByEntry = new Map();
compilation.hooks.afterOptimizeModules.tap(NAME, getExtractMetadata({
compilation,
compiler,
dev: this.dev,
metadataByEntry
}));
/**
* Emit the middleware manifest.
*/ compilation.hooks.processAssets.tap({
name: "NextJsMiddlewareManifest",
stage: _webpack.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
}, getCreateAssets({
compilation,
metadataByEntry
}));
});
}
}
exports.default = MiddlewarePlugin;
const NAME = "MiddlewarePlugin";
const middlewareManifest = {
sortedMiddleware: [],
middleware: {},
functions: {},
version: 1
};
async function handleWebpackExtenalForEdgeRuntime({ request , context , contextInfo , getResolve }) {
if (contextInfo.issuerLayer === "middleware" && isNodeJsModule(request)) {
// allows user to provide and use their polyfills, as we do with buffer.
try {
await getResolve()(context, request);
} catch {
return `root globalThis.__import_unsupported('${request}')`;
}
}
}
function getCodeAnalizer(params) {
return (parser)=>{
const { dev , compiler: { webpack: wp } , compilation , } = params;
const { hooks } = parser;
/**
* For an expression this will check the graph to ensure it is being used
* by exports. Then it will store in the module buildInfo a boolean to
* express that it contains dynamic code and, if it is available, the
* module path that is using it.
*/ const handleExpression = ()=>{
if (!isInMiddlewareLayer(parser)) {
return;
}
wp.optimize.InnerGraph.onUsage(parser.state, (used = true)=>{
const buildInfo = (0, _getModuleBuildInfo).getModuleBuildInfo(parser.state.module);
if (buildInfo.usingIndirectEval === true || used === false) {
return;
}
if (!buildInfo.usingIndirectEval || used === true) {
buildInfo.usingIndirectEval = used;
return;
}
buildInfo.usingIndirectEval = new Set([
...Array.from(buildInfo.usingIndirectEval),
...Array.from(used),
]);
});
};
/**
* This expression handler allows to wrap a dynamic code expression with a
* function call where we can warn about dynamic code not being allowed
* but actually execute the expression.
*/ const handleWrapExpression = (expr)=>{
if (!isInMiddlewareLayer(parser)) {
return;
}
if (dev) {
const { ConstDependency } = wp.dependencies;
const dep1 = new ConstDependency("__next_eval__(function() { return ", expr.range[0]);
dep1.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep1);
const dep2 = new ConstDependency("})", expr.range[1]);
dep2.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep2);
}
handleExpression();
return true;
};
/**
* This expression handler allows to wrap a WebAssembly.compile invocation with a
* function call where we can warn about WASM code generation not being allowed
* but actually execute the expression.
*/ const handleWrapWasmCompileExpression = (expr)=>{
if (!isInMiddlewareLayer(parser)) {
return;
}
if (dev) {
const { ConstDependency } = wp.dependencies;
const dep1 = new ConstDependency("__next_webassembly_compile__(function() { return ", expr.range[0]);
dep1.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep1);
const dep2 = new ConstDependency("})", expr.range[1]);
dep2.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep2);
}
handleExpression();
};
/**
* This expression handler allows to wrap a WebAssembly.instatiate invocation with a
* function call where we can warn about WASM code generation not being allowed
* but actually execute the expression.
*
* Note that we don't update `usingIndirectEval`, i.e. we don't abort a production build
* since we can't determine statically if the first parameter is a module (legit use) or
* a buffer (dynamic code generation).
*/ const handleWrapWasmInstantiateExpression = (expr)=>{
if (!isInMiddlewareLayer(parser)) {
return;
}
if (dev) {
const { ConstDependency } = wp.dependencies;
const dep1 = new ConstDependency("__next_webassembly_instantiate__(function() { return ", expr.range[0]);
dep1.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep1);
const dep2 = new ConstDependency("})", expr.range[1]);
dep2.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep2);
}
};
/**
* Declares an environment variable that is being used in this module
* through this static analysis.
*/ const addUsedEnvVar = (envVarName)=>{
const buildInfo = (0, _getModuleBuildInfo).getModuleBuildInfo(parser.state.module);
if (buildInfo.nextUsedEnvVars === undefined) {
buildInfo.nextUsedEnvVars = new Set();
}
buildInfo.nextUsedEnvVars.add(envVarName);
};
/**
* A handler for calls to `process.env` where we identify the name of the
* ENV variable being assigned and store it in the module info.
*/ const handleCallMemberChain = (_, members)=>{
if (members.length >= 2 && members[0] === "env") {
addUsedEnvVar(members[1]);
if (!isInMiddlewareLayer(parser)) {
return true;
}
}
};
/**
* A handler for calls to `new Response()` so we can fail if user is setting the response's body.
*/ const handleNewResponseExpression = (node)=>{
var ref;
const firstParameter = node == null ? void 0 : (ref = node.arguments) == null ? void 0 : ref[0];
if (isInMiddlewareFile(parser) && firstParameter && !isNullLiteral(firstParameter) && !isUndefinedIdentifier(firstParameter)) {
const error = buildWebpackError({
message: `Middleware is returning a response body (line: ${node.loc.start.line}), which is not supported.
Learn more: https://nextjs.org/docs/messages/returning-response-body-in-middleware`,
compilation,
parser,
...node
});
if (dev) {
compilation.warnings.push(error);
} else {
compilation.errors.push(error);
}
}
};
/**
* Handler to store original source location of static and dynamic imports into module's buildInfo.
*/ const handleImport = (node)=>{
var ref;
if (isInMiddlewareLayer(parser) && ((ref = node.source) == null ? void 0 : ref.value) && (node == null ? void 0 : node.loc)) {
var ref1;
const { module , source } = parser.state;
const buildInfo = (0, _getModuleBuildInfo).getModuleBuildInfo(module);
if (!buildInfo.importLocByPath) {
buildInfo.importLocByPath = new Map();
}
const importedModule = (ref1 = node.source.value) == null ? void 0 : ref1.toString();
buildInfo.importLocByPath.set(importedModule, {
sourcePosition: {
...node.loc.start,
source: module.identifier()
},
sourceContent: source.toString()
});
if (!dev && isNodeJsModule(importedModule)) {
compilation.warnings.push(buildWebpackError({
message: `A Node.js module is loaded ('${importedModule}' at line ${node.loc.start.line}) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`,
compilation,
parser,
...node
}));
}
}
};
/**
* A noop handler to skip analyzing some cases.
* Order matters: for it to work, it must be registered first
*/ const skip = ()=>isInMiddlewareLayer(parser) ? true : undefined;
for (const prefix of [
"",
"global."
]){
hooks.expression.for(`${prefix}Function.prototype`).tap(NAME, skip);
hooks.expression.for(`${prefix}Function.bind`).tap(NAME, skip);
hooks.call.for(`${prefix}eval`).tap(NAME, handleWrapExpression);
hooks.call.for(`${prefix}Function`).tap(NAME, handleWrapExpression);
hooks.new.for(`${prefix}Function`).tap(NAME, handleWrapExpression);
hooks.expression.for(`${prefix}eval`).tap(NAME, handleExpression);
hooks.expression.for(`${prefix}Function`).tap(NAME, handleExpression);
hooks.call.for(`${prefix}WebAssembly.compile`).tap(NAME, handleWrapWasmCompileExpression);
hooks.call.for(`${prefix}WebAssembly.instantiate`).tap(NAME, handleWrapWasmInstantiateExpression);
}
hooks.new.for("Response").tap(NAME, handleNewResponseExpression);
hooks.new.for("NextResponse").tap(NAME, handleNewResponseExpression);
hooks.callMemberChain.for("process").tap(NAME, handleCallMemberChain);
hooks.expressionMemberChain.for("process").tap(NAME, handleCallMemberChain);
hooks.importCall.tap(NAME, handleImport);
hooks.import.tap(NAME, handleImport);
/**
* Support static analyzing environment variables through
* destructuring `process.env` or `process["env"]`:
*
* const { MY_ENV, "MY-ENV": myEnv } = process.env
* ^^^^^^ ^^^^^^
*/ hooks.declarator.tap(NAME, (declarator)=>{
var ref, ref2;
if (((ref = declarator.init) == null ? void 0 : ref.type) === "MemberExpression" && isProcessEnvMemberExpression(declarator.init) && ((ref2 = declarator.id) == null ? void 0 : ref2.type) === "ObjectPattern") {
for (const property of declarator.id.properties){
if (property.type === "RestElement") continue;
if (property.key.type === "Literal" && typeof property.key.value === "string") {
addUsedEnvVar(property.key.value);
} else if (property.key.type === "Identifier") {
addUsedEnvVar(property.key.name);
}
}
if (!isInMiddlewareLayer(parser)) {
return true;
}
}
});
if (!dev) {
// do not issue compilation warning on dev: invoking code will provide details
registerUnsupportedApiHooks(parser, compilation);
}
};
}
function getExtractMetadata(params) {
const { dev , compilation , metadataByEntry , compiler } = params;
const { webpack: wp } = compiler;
return ()=>{
metadataByEntry.clear();
for (const [entryName, entryData] of compilation.entries){
if (entryData.options.runtime !== _constants.EDGE_RUNTIME_WEBPACK) {
continue;
}
const { moduleGraph } = compilation;
const entryModules = new Set();
const addEntriesFromDependency = (dependency)=>{
const module = moduleGraph.getModule(dependency);
if (module) {
entryModules.add(module);
}
};
entryData.dependencies.forEach(addEntriesFromDependency);
entryData.includeDependencies.forEach(addEntriesFromDependency);
const entryMetadata = {
env: new Set(),
wasmBindings: new Map(),
assetBindings: new Map()
};
for (const entryModule of entryModules){
const buildInfo = (0, _getModuleBuildInfo).getModuleBuildInfo(entryModule);
/**
* When building for production checks if the module is using `eval`
* and in such case produces a compilation error. The module has to
* be in use.
*/ if (!dev && buildInfo.usingIndirectEval && isUsingIndirectEvalAndUsedByExports({
entryModule: entryModule,
moduleGraph: moduleGraph,
runtime: wp.util.runtime.getEntryRuntime(compilation, entryName),
usingIndirectEval: buildInfo.usingIndirectEval,
wp
})) {
const id = entryModule.identifier();
if (/node_modules[\\/]regenerator-runtime[\\/]runtime\.js/.test(id)) {
continue;
}
compilation.errors.push(buildWebpackError({
message: `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime ${typeof buildInfo.usingIndirectEval !== "boolean" ? `\nUsed by ${Array.from(buildInfo.usingIndirectEval).join(", ")}` : ""}`,
entryModule,
compilation
}));
}
/**
* The entry module has to be either a page or a middleware and hold
* the corresponding metadata.
*/ if (buildInfo == null ? void 0 : buildInfo.nextEdgeSSR) {
entryMetadata.edgeSSR = buildInfo.nextEdgeSSR;
} else if (buildInfo == null ? void 0 : buildInfo.nextEdgeMiddleware) {
entryMetadata.edgeMiddleware = buildInfo.nextEdgeMiddleware;
} else if (buildInfo == null ? void 0 : buildInfo.nextEdgeApiFunction) {
entryMetadata.edgeApiFunction = buildInfo.nextEdgeApiFunction;
}
/**
* If there are env vars found in the module, append them to the set
* of env vars for the entry.
*/ if ((buildInfo == null ? void 0 : buildInfo.nextUsedEnvVars) !== undefined) {
for (const envName of buildInfo.nextUsedEnvVars){
entryMetadata.env.add(envName);
}
}
/**
* If the module is a WASM module we read the binding information and
* append it to the entry wasm bindings.
*/ if (buildInfo == null ? void 0 : buildInfo.nextWasmMiddlewareBinding) {
entryMetadata.wasmBindings.set(buildInfo.nextWasmMiddlewareBinding.name, buildInfo.nextWasmMiddlewareBinding.filePath);
}
if (buildInfo == null ? void 0 : buildInfo.nextAssetMiddlewareBinding) {
entryMetadata.assetBindings.set(buildInfo.nextAssetMiddlewareBinding.name, buildInfo.nextAssetMiddlewareBinding.filePath);
}
/**
* Append to the list of modules to process outgoingConnections from
* the module that is being processed.
*/ for (const conn of moduleGraph.getOutgoingConnections(entryModule)){
if (conn.module) {
entryModules.add(conn.module);
}
}
}
metadataByEntry.set(entryName, entryMetadata);
}
};
}
/**
* Checks the value of usingIndirectEval and when it is a set of modules it
* check if any of the modules is actually being used. If the value is
* simply truthy it will return true.
*/ function isUsingIndirectEvalAndUsedByExports(args) {
const { moduleGraph , runtime , entryModule , usingIndirectEval , wp } = args;
if (typeof usingIndirectEval === "boolean") {
return usingIndirectEval;
}
const exportsInfo = moduleGraph.getExportsInfo(entryModule);
for (const exportName of usingIndirectEval){
if (exportsInfo.getUsed(exportName, runtime) !== wp.UsageState.Unused) {
return true;
}
}
return false;
}
function getCreateAssets(params) {
const { compilation , metadataByEntry } = params;
return (assets)=>{
for (const entrypoint of compilation.entrypoints.values()){
var ref, ref3, ref4, ref5;
if (!entrypoint.name) {
continue;
}
// There should always be metadata for the entrypoint.
const metadata = metadataByEntry.get(entrypoint.name);
const page = (metadata == null ? void 0 : (ref = metadata.edgeMiddleware) == null ? void 0 : ref.page) || (metadata == null ? void 0 : (ref3 = metadata.edgeSSR) == null ? void 0 : ref3.page) || (metadata == null ? void 0 : (ref4 = metadata.edgeApiFunction) == null ? void 0 : ref4.page);
if (!page) {
continue;
}
const { namedRegex } = (0, _routeRegex).getNamedMiddlewareRegex(page, {
catchAll: !metadata.edgeSSR && !metadata.edgeApiFunction
});
const regexp = (metadata == null ? void 0 : (ref5 = metadata.edgeMiddleware) == null ? void 0 : ref5.matcherRegexp) || namedRegex;
const edgeFunctionDefinition = {
env: Array.from(metadata.env),
files: getEntryFiles(entrypoint.getFiles(), metadata),
name: entrypoint.name,
page: page,
regexp,
wasm: Array.from(metadata.wasmBindings, ([name, filePath])=>({
name,
filePath
})),
assets: Array.from(metadata.assetBindings, ([name, filePath])=>({
name,
filePath
}))
};
if (metadata.edgeApiFunction || metadata.edgeSSR) {
middlewareManifest.functions[page] = edgeFunctionDefinition;
} else {
middlewareManifest.middleware[page] = edgeFunctionDefinition;
}
}
middlewareManifest.sortedMiddleware = (0, _utils).getSortedRoutes(Object.keys(middlewareManifest.middleware));
assets[_constants.MIDDLEWARE_MANIFEST] = new _webpack.sources.RawSource(JSON.stringify(middlewareManifest, null, 2));
};
}
function getEntryFiles(entryFiles, meta) {
const files = [];
if (meta.edgeSSR) {
if (meta.edgeSSR.isServerComponent) {
files.push(`server/${_constants.FLIGHT_MANIFEST}.js`);
files.push(...entryFiles.filter((file)=>file.startsWith("pages/") && !file.endsWith(".hot-update.js")).map((file)=>"server/" + file.replace(".js", _constants.NEXT_CLIENT_SSR_ENTRY_SUFFIX + ".js")));
}
files.push(`server/${_constants.MIDDLEWARE_BUILD_MANIFEST}.js`, `server/${_constants.MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js`);
}
files.push(...entryFiles.filter((file)=>!file.endsWith(".hot-update.js")).map((file)=>"server/" + file));
return files;
}
function registerUnsupportedApiHooks(parser, compilation) {
for (const expression of _constants.EDGE_UNSUPPORTED_NODE_APIS){
const warnForUnsupportedApi = (node)=>{
if (!isInMiddlewareLayer(parser)) {
return;
}
compilation.warnings.push(buildUnsupportedApiError({
compilation,
parser,
apiName: expression,
...node
}));
return true;
};
parser.hooks.call.for(expression).tap(NAME, warnForUnsupportedApi);
parser.hooks.expression.for(expression).tap(NAME, warnForUnsupportedApi);
parser.hooks.callMemberChain.for(expression).tap(NAME, warnForUnsupportedApi);
parser.hooks.expressionMemberChain.for(expression).tap(NAME, warnForUnsupportedApi);
}
const warnForUnsupportedProcessApi = (node, [callee])=>{
if (!isInMiddlewareLayer(parser) || callee === "env") {
return;
}
compilation.warnings.push(buildUnsupportedApiError({
compilation,
parser,
apiName: `process.${callee}`,
...node
}));
return true;
};
parser.hooks.callMemberChain.for("process").tap(NAME, warnForUnsupportedProcessApi);
parser.hooks.expressionMemberChain.for("process").tap(NAME, warnForUnsupportedProcessApi);
}
function buildUnsupportedApiError({ apiName , loc , ...rest }) {
return buildWebpackError({
message: `A Node.js API is used (${apiName} at line: ${loc.start.line}) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime`,
loc,
...rest
});
}
function buildWebpackError({ message , loc , compilation , entryModule , parser }) {
const error = new compilation.compiler.webpack.WebpackError(message);
error.name = NAME;
const module = entryModule != null ? entryModule : parser == null ? void 0 : parser.state.current;
if (module) {
error.module = module;
}
error.loc = loc;
return error;
}
function isInMiddlewareLayer(parser) {
var ref;
return ((ref = parser.state.module) == null ? void 0 : ref.layer) === "middleware";
}
function isInMiddlewareFile(parser) {
var ref, ref6;
return ((ref = parser.state.current) == null ? void 0 : ref.layer) === "middleware" && /middleware\.\w+$/.test((ref6 = parser.state.current) == null ? void 0 : ref6.rawRequest);
}
function isNullLiteral(expr) {
return expr.value === null;
}
function isUndefinedIdentifier(expr) {
return expr.name === "undefined";
}
function isProcessEnvMemberExpression(memberExpression) {
var ref, ref7, ref8;
return ((ref = memberExpression.object) == null ? void 0 : ref.type) === "Identifier" && memberExpression.object.name === "process" && (((ref7 = memberExpression.property) == null ? void 0 : ref7.type) === "Literal" && memberExpression.property.value === "env" || ((ref8 = memberExpression.property) == null ? void 0 : ref8.type) === "Identifier" && memberExpression.property.name === "env");
}
function isNodeJsModule(moduleName) {
return require("module").builtinModules.includes(moduleName);
}
//# sourceMappingURL=middleware-plugin.js.map
;