mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Trace cacheHandler and cacheHandlers when using adapters (#94197)
### What? Add focused production adapter coverage for configured `cacheHandler` and `cacheHandlers` module graphs in Node `output.assets`, including transitive helper dependencies and Turbopack `assetsHashes` assertions. ### Why? Adapter deployments dynamically load configured cache handlers at runtime. This coverage demonstrates that Node adapter outputs currently omit those handler files and their transitive dependencies. ### How? Add an App Router Cache Components fixture using `adapterPath`, both cache-handler APIs, and a `use cache: remote` route. Its adapter writes `onBuildComplete` context to disk and the test inspects Node outputs for each handler and uniquely named helper. ### Verification - `NEXT_TEST_PREFER_OFFLINE=1 pnpm --filter=next build` - Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts` (no Node output contains all four handler assets) - Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts` (same adapter handoff gap) - `git diff canary --check` - Not run continuously: `pnpm --filter=next dev` (`EMFILE: too many open files` occurred after startup in the local watcher) <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Niklas Mischkulnig <[email protected]>
This commit is contained in:
co-authored by
Niklas Mischkulnig
parent
20ac9ce9cf
commit
d342d0b91f
@@ -2002,7 +2002,7 @@ impl AppEndpoint {
|
||||
this.app_project.project(),
|
||||
Some(app_function_name(&app_entry.original_name).into()),
|
||||
*module_graphs.full,
|
||||
vec![*rsc_entry],
|
||||
*rsc_entry,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ impl InstrumentationEndpoint {
|
||||
*this.project,
|
||||
None,
|
||||
this.project.module_graph(userland_module),
|
||||
vec![userland_module],
|
||||
userland_module,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ impl MiddlewareEndpoint {
|
||||
*this.project,
|
||||
None,
|
||||
this.project.module_graph(userland_module),
|
||||
vec![userland_module],
|
||||
userland_module,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,10 @@ use turbo_tasks_hash::HashAlgorithm;
|
||||
use turbopack::externals_tracing_module_context;
|
||||
use turbopack_core::{
|
||||
asset::{Asset, AssetContent},
|
||||
context::AssetContext,
|
||||
file_source::FileSource,
|
||||
module::{Module, Modules},
|
||||
module_graph::{ModuleGraph, SingleModuleGraph, chunk_group_info::ChunkGroupEntry},
|
||||
output::{OutputAsset, OutputAssets, OutputAssetsReference},
|
||||
reference_type::{CommonJsReferenceSubType, ReferenceType},
|
||||
reference_type::CommonJsReferenceSubType,
|
||||
resolve::{ResolveErrorMode, origin::PlainResolveOrigin, parse::Request},
|
||||
};
|
||||
use turbopack_resolve::ecmascript::cjs_resolve;
|
||||
@@ -123,9 +121,9 @@ impl Asset for ServerNftJsonAsset {
|
||||
|
||||
let mut server_output_assets = traced_modules_for_entries(
|
||||
module_graph,
|
||||
Modules::empty(),
|
||||
self.entries(),
|
||||
Some(self.ignores()),
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
@@ -219,35 +217,9 @@ impl ServerNftJsonAsset {
|
||||
get_next_package(project_path.clone()).await?.join("_")?,
|
||||
));
|
||||
|
||||
let cache_handler = self
|
||||
.project
|
||||
.next_config()
|
||||
.cache_handler(project_path.clone())
|
||||
.await?;
|
||||
let cache_handlers = self
|
||||
.project
|
||||
.next_config()
|
||||
.cache_handlers(project_path.clone())
|
||||
.await?;
|
||||
|
||||
// These are used by packages/next/src/server/require-hook.ts
|
||||
let shared_entries = ["styled-jsx", "styled-jsx/style", "styled-jsx/style.js"];
|
||||
|
||||
let cache_handler_entries = cache_handler
|
||||
.iter()
|
||||
.chain(cache_handlers.iter())
|
||||
.map(|f| {
|
||||
asset_context
|
||||
.process(
|
||||
Vc::upcast(FileSource::new(f.clone())),
|
||||
ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
|
||||
)
|
||||
.module()
|
||||
})
|
||||
.map(|m| m.to_resolved())
|
||||
.try_join()
|
||||
.await?;
|
||||
|
||||
let entries = match self.ty {
|
||||
ServerNftType::Full => Either::Left(
|
||||
if is_standalone {
|
||||
@@ -270,29 +242,24 @@ impl ServerNftJsonAsset {
|
||||
};
|
||||
|
||||
Ok(Vc::cell(
|
||||
cache_handler_entries
|
||||
shared_entries
|
||||
.into_iter()
|
||||
.chain(
|
||||
shared_entries
|
||||
.into_iter()
|
||||
.chain(entries)
|
||||
.map(async |path| {
|
||||
Ok(cjs_resolve(
|
||||
next_resolve_origin,
|
||||
Request::parse_string(path.into()),
|
||||
CommonJsReferenceSubType::Undefined,
|
||||
None,
|
||||
ResolveErrorMode::Error,
|
||||
)
|
||||
.await?
|
||||
.primary_modules()
|
||||
.await?
|
||||
.into_iter())
|
||||
})
|
||||
.try_flat_join()
|
||||
.await?,
|
||||
)
|
||||
.collect(),
|
||||
.chain(entries)
|
||||
.map(async |path| {
|
||||
Ok(cjs_resolve(
|
||||
next_resolve_origin,
|
||||
Request::parse_string(path.into()),
|
||||
CommonJsReferenceSubType::Undefined,
|
||||
None,
|
||||
ResolveErrorMode::Error,
|
||||
)
|
||||
.await?
|
||||
.primary_modules()
|
||||
.await?
|
||||
.into_iter())
|
||||
})
|
||||
.try_flat_join()
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
+20
-10
@@ -60,7 +60,7 @@ pub async fn trace_endpoint(
|
||||
project: ResolvedVc<Project>,
|
||||
page_name: Option<RcStr>,
|
||||
module_graph: ResolvedVc<ModuleGraph>,
|
||||
entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
|
||||
entry_module: ResolvedVc<Box<dyn Module>>,
|
||||
) -> Result<Vc<EndpointTraceResult>> {
|
||||
let span = tracing::info_span!("trace endpoint", path = debug(&page_name));
|
||||
async {
|
||||
@@ -71,19 +71,21 @@ pub async fn trace_endpoint(
|
||||
.output_file_tracing_includes(project_path.clone())
|
||||
.await?;
|
||||
|
||||
let traced_entries = project.additional_traced_modules();
|
||||
|
||||
// Collect referenced assets and externals from module graph
|
||||
let all_modules = traced_modules_for_entries(
|
||||
*module_graph,
|
||||
Vc::cell(entry_modules.clone()),
|
||||
Vc::cell(vec![entry_module]),
|
||||
traced_entries,
|
||||
tracing_exclude_glob(page_name.clone(), project_path.clone(), next_config)
|
||||
.await?
|
||||
.map(|v| *v),
|
||||
false,
|
||||
Some(next_config.config_file_path(project_path.clone())),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let module_data = traced_module_data_for_graph(*module_graph, false)
|
||||
let module_data = traced_module_data_for_graph(*module_graph, traced_entries)
|
||||
.to_resolved()
|
||||
.await?;
|
||||
let module_paths = module_data.await?.idents;
|
||||
@@ -265,13 +267,13 @@ pub async fn tracing_exclude_glob(
|
||||
pub async fn traced_modules_for_entries(
|
||||
module_graph: Vc<ModuleGraph>,
|
||||
entry_modules: Vc<Modules>,
|
||||
traced_entries: Vc<Modules>,
|
||||
exclude_glob: Option<Vc<Glob>>,
|
||||
entries_are_traced: bool,
|
||||
forbidden_path: Option<Vc<FileSystemPath>>,
|
||||
) -> Result<Vc<Modules>> {
|
||||
let exclude_glob_and_module_idents = if let Some(exclude_glob) = exclude_glob {
|
||||
let exclude_glob = exclude_glob.await?;
|
||||
let data = traced_module_data_for_graph(module_graph, entries_are_traced).await?;
|
||||
let data = traced_module_data_for_graph(module_graph, traced_entries).await?;
|
||||
Some((exclude_glob, data.idents.await?))
|
||||
} else {
|
||||
None
|
||||
@@ -288,14 +290,20 @@ pub async fn traced_modules_for_entries(
|
||||
};
|
||||
|
||||
let mut forbidden_issues = vec![];
|
||||
let traced_entries = traced_entries.await?;
|
||||
let traced_entries_set = traced_entries.iter().copied().collect::<FxHashSet<_>>();
|
||||
|
||||
let mut traced_modules = FxIndexSet::default();
|
||||
module_graph.await?.traverse_edges_dfs(
|
||||
entry_modules.await?.iter().copied(),
|
||||
entry_modules
|
||||
.await?
|
||||
.iter()
|
||||
.chain(traced_entries.iter())
|
||||
.copied(),
|
||||
&mut (),
|
||||
|parent, target, _| {
|
||||
let Some((parent, ref_data)) = parent else {
|
||||
if entries_are_traced {
|
||||
if traced_entries_set.contains(&target) {
|
||||
traced_modules.insert(target);
|
||||
}
|
||||
return Ok(GraphTraversalAction::Continue);
|
||||
@@ -368,20 +376,22 @@ pub struct TracedModuleData {
|
||||
#[turbo_tasks::function]
|
||||
pub async fn traced_module_data_for_graph(
|
||||
module_graph: Vc<ModuleGraph>,
|
||||
entries_are_traced: bool,
|
||||
traced_entries: Vc<Modules>,
|
||||
) -> Result<Vc<TracedModuleData>> {
|
||||
// This function is very similar to traced_modules_for_entries, but doesn't apply the glob and
|
||||
// is executed only once for the whole graph.
|
||||
let module_graph = module_graph.await?;
|
||||
let entries = module_graph.graphs.iter().flat_map(|g| g.entry_modules());
|
||||
|
||||
let traced_entries = traced_entries.await?.into_iter().collect::<FxHashSet<_>>();
|
||||
|
||||
let mut traced_modules = FxHashSet::default();
|
||||
module_graph.traverse_edges_dfs(
|
||||
entries,
|
||||
&mut (),
|
||||
|parent, target, _| {
|
||||
let Some((parent, ref_data)) = parent else {
|
||||
if entries_are_traced {
|
||||
if traced_entries.contains(&target) {
|
||||
traced_modules.insert(target);
|
||||
}
|
||||
return Ok(GraphTraversalAction::Continue);
|
||||
|
||||
@@ -1569,7 +1569,7 @@ impl PageEndpoint {
|
||||
this.pages_project.project(),
|
||||
Some(pages_function_name(&this.original_name).into()),
|
||||
ssr_module_graph,
|
||||
vec![*ssr_module],
|
||||
*ssr_module,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use next_core::{
|
||||
ServerChunkingContextOptions, ServerContextType, get_server_chunking_context,
|
||||
get_server_chunking_context_with_client_assets, get_server_compile_time_info,
|
||||
get_server_module_options_context, get_server_resolve_options_context,
|
||||
get_tracing_compile_time_info,
|
||||
},
|
||||
next_telemetry::ProjectFeatureUsageSummary,
|
||||
parse_segment_config_from_source,
|
||||
@@ -44,7 +45,7 @@ use turbo_tasks_fs::{
|
||||
};
|
||||
use turbo_unix_path::{join_path, unix_to_sys};
|
||||
use turbopack::{
|
||||
ModuleAssetContext, evaluate_context::node_build_environment,
|
||||
ModuleAssetContext, evaluate_context::node_build_environment, externals_tracing_module_context,
|
||||
global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
|
||||
};
|
||||
use turbopack_core::{
|
||||
@@ -62,7 +63,7 @@ use turbopack_core::{
|
||||
issue::{
|
||||
CollectibleIssuesExt, Issue, IssueExt, IssueFilter, IssueSeverity, IssueStage, StyledString,
|
||||
},
|
||||
module::Module,
|
||||
module::{Module, Modules},
|
||||
module_graph::{
|
||||
GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
|
||||
binding_usage_info::{
|
||||
@@ -75,6 +76,7 @@ use turbopack_core::{
|
||||
expand_output_assets,
|
||||
},
|
||||
reference::all_assets_from_entries,
|
||||
reference_type::{CommonJsReferenceSubType, ReferenceType},
|
||||
resolve::{FindContextFileResult, find_context_file},
|
||||
version::{
|
||||
NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
|
||||
@@ -1443,6 +1445,13 @@ impl Project {
|
||||
.try_flat_join()
|
||||
.await?;
|
||||
modules.extend(self.client_main_modules().await?.iter().cloned());
|
||||
modules.extend(
|
||||
self.additional_traced_modules()
|
||||
.await?
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|m| ChunkGroupEntry::Entry(vec![m])),
|
||||
);
|
||||
Ok(Vc::cell(modules))
|
||||
}
|
||||
|
||||
@@ -2591,6 +2600,40 @@ impl Project {
|
||||
}
|
||||
.cell())
|
||||
}
|
||||
|
||||
/// Returns any modules specified as `nextConfig.cacheHandler` and/or `nextConfig.cacheHandlers`
|
||||
#[turbo_tasks::function]
|
||||
pub async fn additional_traced_modules(self: Vc<Self>) -> Result<Vc<Modules>> {
|
||||
let project_path = self.project_path().owned().await?;
|
||||
let cache_handler = self
|
||||
.next_config()
|
||||
.cache_handler(project_path.clone())
|
||||
.await?;
|
||||
let cache_handlers = self
|
||||
.next_config()
|
||||
.cache_handlers(project_path.clone())
|
||||
.await?;
|
||||
|
||||
let asset_context =
|
||||
externals_tracing_module_context(get_tracing_compile_time_info(), false);
|
||||
|
||||
Ok(Vc::cell(
|
||||
cache_handler
|
||||
.iter()
|
||||
.chain(cache_handlers.iter())
|
||||
.map(|f| {
|
||||
asset_context
|
||||
.process(
|
||||
Vc::upcast(FileSource::new(f.clone())),
|
||||
ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
|
||||
)
|
||||
.module()
|
||||
})
|
||||
.map(|m| m.to_resolved())
|
||||
.try_join()
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales down or shuts down the Node.js process pool after module graph computation.
|
||||
|
||||
@@ -53,6 +53,7 @@ import { sortSortableRoutes } from '../../shared/lib/router/utils/sortable-route
|
||||
import { defaultOverrides } from '../../server/require-hook'
|
||||
import { generateRoutesManifest } from '../generate-routes-manifest'
|
||||
import { Bundler } from '../../lib/bundler'
|
||||
import { resolveCacheHandlerPathToFilesystem } from '../../lib/format-dynamic-import-path'
|
||||
|
||||
interface SharedRouteFields {
|
||||
/**
|
||||
@@ -582,6 +583,7 @@ export async function handleBuildComplete({
|
||||
tracingRoot,
|
||||
bundler,
|
||||
hasInstrumentationHook,
|
||||
config,
|
||||
})
|
||||
|
||||
async function handleTraceFiles(
|
||||
@@ -2126,6 +2128,7 @@ async function getSharedNodeAssets({
|
||||
tracingRoot,
|
||||
requiredServerFiles,
|
||||
hasInstrumentationHook,
|
||||
config,
|
||||
}: {
|
||||
dir: string
|
||||
bundler: Bundler
|
||||
@@ -2133,6 +2136,7 @@ async function getSharedNodeAssets({
|
||||
tracingRoot: string
|
||||
requiredServerFiles: string[]
|
||||
hasInstrumentationHook: boolean
|
||||
config: NextConfigComplete
|
||||
}) {
|
||||
const sharedNodeAssets: Record<string, string> = {}
|
||||
const sharedNodeAssetsHashes: Record<string, string> = {}
|
||||
@@ -2199,6 +2203,7 @@ async function getSharedNodeAssets({
|
||||
bundler
|
||||
)
|
||||
|
||||
// Turbopack handles this automatically and these files are listed in the nft.json files.
|
||||
if (bundler !== Bundler.Turbopack) {
|
||||
const { nodeFileTrace } =
|
||||
require('next/dist/compiled/@vercel/nft') as typeof import('next/dist/compiled/@vercel/nft')
|
||||
@@ -2231,6 +2236,33 @@ async function getSharedNodeAssets({
|
||||
...Object.values(defaultOverrides).filter((item) => path.extname(item)),
|
||||
]
|
||||
|
||||
const { cacheHandler, cacheHandlers } = config
|
||||
// ensure we trace any dependencies needed for a custom incremental cache handler
|
||||
if (cacheHandler) {
|
||||
const resolvedPath = resolveCacheHandlerPathToFilesystem(cacheHandler)
|
||||
necessaryNodeDependencies.push(
|
||||
require.resolve(
|
||||
path.isAbsolute(resolvedPath)
|
||||
? resolvedPath
|
||||
: path.join(dir, resolvedPath)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (cacheHandlers) {
|
||||
for (const handlerPath of Object.values(cacheHandlers)) {
|
||||
if (handlerPath) {
|
||||
const resolvedPath = resolveCacheHandlerPathToFilesystem(handlerPath)
|
||||
necessaryNodeDependencies.push(
|
||||
require.resolve(
|
||||
path.isAbsolute(resolvedPath)
|
||||
? resolvedPath
|
||||
: path.join(dir, resolvedPath)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { fileList, esmFileList } = await nodeFileTrace(
|
||||
necessaryNodeDependencies,
|
||||
{
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { nextTestSetup } from 'e2e-utils'
|
||||
import path from 'path'
|
||||
import type { NextAdapter } from 'next'
|
||||
|
||||
describe('adapter-cache-handlers', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
|
||||
it('includes configured cache handler dependencies in Node adapter outputs', async () => {
|
||||
const { outputs }: Parameters<NextAdapter['onBuildComplete']>[0] =
|
||||
await next.readJSON('build-complete.json')
|
||||
|
||||
const nodeOutputs = [
|
||||
...outputs.pages,
|
||||
...outputs.pagesApi,
|
||||
...outputs.appPages,
|
||||
...outputs.appRoutes,
|
||||
].filter((output) => output.runtime === 'nodejs')
|
||||
|
||||
expect(nodeOutputs.length).toBeGreaterThan(0)
|
||||
|
||||
const expectedAssets = [
|
||||
'incremental-cache-handler.js',
|
||||
'incremental-cache-helper.js',
|
||||
'use-cache-handler.js',
|
||||
'use-cache-helper.js',
|
||||
]
|
||||
const output = nodeOutputs.find((candidate) =>
|
||||
expectedAssets.every((filename) =>
|
||||
Object.values(candidate.assets).some(
|
||||
(assetPath) => path.basename(assetPath) === filename
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
expect(output).toBeDefined()
|
||||
|
||||
for (const filename of expectedAssets) {
|
||||
const assetKey = Object.entries(output!.assets).find(
|
||||
([, assetPath]) => path.basename(assetPath) === filename
|
||||
)?.[0]
|
||||
|
||||
expect(assetKey).toBeString()
|
||||
|
||||
if (process.env.IS_TURBOPACK_TEST) {
|
||||
expect(output!.assetsHashes[assetKey!]).toBeString()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ReactNode } from 'react'
|
||||
export default function Root({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
async function getMessage() {
|
||||
'use cache: remote'
|
||||
|
||||
return 'hello world'
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
return <p>{await getMessage()}</p>
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const helper = require('./incremental-cache-helper.js')
|
||||
|
||||
module.exports = class IncrementalCacheHandler {
|
||||
constructor() {
|
||||
helper()
|
||||
}
|
||||
|
||||
async get() {
|
||||
return null
|
||||
}
|
||||
|
||||
async set() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function incrementalCacheHelper() {
|
||||
return 'incremental-cache-helper'
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import fs from 'fs/promises'
|
||||
|
||||
/** @type {import('next').NextAdapter } */
|
||||
export default {
|
||||
name: 'adapter-cache-handlers',
|
||||
async onBuildComplete(ctx) {
|
||||
await fs.writeFile('build-complete.json', JSON.stringify(ctx, null, 2))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {
|
||||
adapterPath: require.resolve('./my-adapter.mjs'),
|
||||
cacheHandler: require.resolve('./incremental-cache-handler.js'),
|
||||
cacheHandlers: {
|
||||
remote: require.resolve('./use-cache-handler.js'),
|
||||
},
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,21 @@
|
||||
const helper = require('./use-cache-helper.js')
|
||||
|
||||
/** @type {import('next/dist/server/lib/cache-handlers/types').CacheHandler} */
|
||||
module.exports = {
|
||||
async get() {
|
||||
helper()
|
||||
return undefined
|
||||
},
|
||||
|
||||
async set() {
|
||||
helper()
|
||||
},
|
||||
|
||||
async refreshTags() {},
|
||||
|
||||
async getExpiration() {
|
||||
return Infinity
|
||||
},
|
||||
|
||||
async updateTags() {},
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function useCacheHelper() {
|
||||
return 'use-cache-helper'
|
||||
}
|
||||
@@ -83,20 +83,22 @@ async fn compute_side_effect_free_module_info_single(
|
||||
// parent is a module that depends on it.
|
||||
|child, parent, _s| {
|
||||
Ok(if child.is_some() {
|
||||
match module_side_effects
|
||||
.get(&parent)
|
||||
.expect("the map is populated for all modules in this graph")
|
||||
{
|
||||
ModuleSideEffects::SideEffectful | ModuleSideEffects::SideEffectFree => {
|
||||
match module_side_effects.get(&parent) {
|
||||
Some(ModuleSideEffects::SideEffectful | ModuleSideEffects::SideEffectFree) => {
|
||||
// We have either already seen this or don't want to follow it
|
||||
GraphTraversalAction::Exclude
|
||||
}
|
||||
ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
|
||||
Some(ModuleSideEffects::ModuleEvaluationIsSideEffectFree) => {
|
||||
// this module is side effect free locally but depends on `child` which is
|
||||
// effectful so it too is effectful
|
||||
locally_side_effect_free_modules_that_have_side_effects.insert(parent);
|
||||
GraphTraversalAction::Continue
|
||||
}
|
||||
None => {
|
||||
// The reverse traversal might end up visiting nodes that were excluded in
|
||||
// the iter_reachable_nodes traversal above. So ignore them.
|
||||
GraphTraversalAction::Exclude
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// entry point, we already determined it was effectful, keep going
|
||||
@@ -105,6 +107,7 @@ async fn compute_side_effect_free_module_info_single(
|
||||
},
|
||||
|_, _, _| Ok(()),
|
||||
)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::sync::LazyLock;
|
||||
|
||||
Reference in New Issue
Block a user