1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! `src/options_types/import_record.zig` — `ImportRecord` and friends.
//!
//! Lives in `bun_ast` so `Ast` (which holds `Vec<ImportRecord>`) is
//! self-contained and `bun_js_printer` can drop its `bun_js_parser` dep.
//! `ImportKind::to_api()` lives in `bun_ast::ImportKindExt` (would
//! back-edge into the schema crate).
use crate::Range;
use bun_paths::fs::Path;
// Re-exported here (canonical at crate root) so callers that path through
// `bun_ast::import_record::{ImportKind, Index, Loader}` — mirroring the Zig
// `import_record.zig` namespace — keep resolving.
pub use crate::{ImportKind, Index, Loader};
pub struct ImportRecord {
pub range: Range,
// TODO(port): lifetime — `bun_paths::fs::Path<'a>` borrows resolver-owned
// strings. Uses 'static (PORTING.md: no struct lifetime params).
pub path: Path<'static>,
pub kind: ImportKind,
pub tag: Tag,
pub loader: Option<Loader>,
pub source_index: Index,
/// `js_printer::printBundledImport` reads this. The Zig field was removed
/// from `ImportRecord` but the printer body referencing it is dead (never
/// analysed by Zig's lazy compilation). Kept here so the eagerly-compiled
/// Rust port of that body type-checks; always 0 in practice.
// TODO(port): delete once `printBundledImport` is confirmed dead and removed.
pub module_id: u32,
/// The original import specifier as written in source code (e.g., "./foo.js").
/// This is preserved before resolution overwrites `path` with the resolved path.
/// Used for metafile generation.
// TODO(port): lifetime — Zig `[]const u8` defaulting to "", never freed in this file.
// Likely a borrow into parser-owned source text; using &'static [u8] as a placeholder.
pub original_path: &'static [u8],
/// Pack all boolean flags into 2 bytes to reduce padding overhead.
/// Previously 15 separate bool fields caused ~14-16 bytes of padding waste.
pub flags: Flags,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
pub struct Flags: u16 {
/// True for the following cases:
///
/// try { require('x') } catch { handle }
/// try { await import('x') } catch { handle }
/// try { require.resolve('x') } catch { handle }
/// import('x').catch(handle)
/// import('x').then(_, handle)
///
/// In these cases we shouldn't generate an error if the path could not be
/// resolved.
const HANDLES_IMPORT_ERRORS = 1 << 0;
const IS_INTERNAL = 1 << 1;
/// Sometimes the parser creates an import record and decides it isn't needed.
/// For example, TypeScript code may have import statements that later turn
/// out to be type-only imports after analyzing the whole file.
const IS_UNUSED = 1 << 2;
/// If this is true, the import contains syntax like "* as ns". This is used
/// to determine whether modules that have no exports need to be wrapped in a
/// CommonJS wrapper or not.
const CONTAINS_IMPORT_STAR = 1 << 3;
/// If this is true, the import contains an import for the alias "default",
/// either via the "import x from" or "import {default as x} from" syntax.
const CONTAINS_DEFAULT_ALIAS = 1 << 4;
const CONTAINS_ES_MODULE_ALIAS = 1 << 5;
/// If true, this "export * from 'path'" statement is evaluated at run-time by
/// calling the "__reExport()" helper function
const CALLS_RUNTIME_RE_EXPORT_FN = 1 << 6;
/// True for require calls like this: "try { require() } catch {}". In this
/// case we shouldn't generate an error if the path could not be resolved.
const IS_INSIDE_TRY_BODY = 1 << 7;
/// If true, this was originally written as a bare "import 'file'" statement
const WAS_ORIGINALLY_BARE_IMPORT = 1 << 8;
const WAS_ORIGINALLY_REQUIRE = 1 << 9;
/// If a macro used <import>, it will be tracked here.
const WAS_INJECTED_BY_MACRO = 1 << 10;
/// If true, this import can be removed if it's unused
const IS_EXTERNAL_WITHOUT_SIDE_EFFECTS = 1 << 11;
/// Tell the printer to print the record as "foo:my-path" instead of "path"
/// where "foo" is the namespace
///
/// Used to prevent running resolve plugins multiple times for the same path
const PRINT_NAMESPACE_IN_PATH = 1 << 12;
const WRAP_WITH_TO_ESM = 1 << 13;
const WRAP_WITH_TO_COMMONJS = 1 << 14;
/// "import defer * as ns from 'path'" — defer evaluation of the
/// imported module until a property on the namespace object is
/// accessed. Requires `CONTAINS_IMPORT_STAR`.
const PHASE_DEFER = 1 << 15;
}
}
pub type List<'a> = bun_alloc::ArenaVec<'a, ImportRecord>;
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub enum Tag {
/// A normal import to a user's source file
#[default]
None,
/// An import to 'bun'
Bun,
/// A builtin module, such as `node:fs` or `bun:sqlite`
Builtin,
/// An import to the internal runtime
Runtime,
/// A 'macro:' import namespace or 'with { type: "macro" }'
Macro,
/// For Bun Kit, if a module in the server graph should actually
/// crossover to the SSR graph. See bake.Framework.ServerComponents.separate_ssr_graph
BakeResolveToSsrGraph,
Tailwind,
}
impl Tag {
#[inline]
pub fn is_runtime(self) -> bool {
self == Tag::Runtime
}
#[inline]
pub fn is_internal(self) -> bool {
(self as u8) >= (Tag::Runtime as u8)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum PrintMode {
Normal,
ImportPath,
Css,
NapiModule,
}
// NOTE: no `impl Default for ImportRecord` — Zig gives `range`, `path`, `kind` no defaults,
// so `.{}` is invalid there. Construction sites must supply required fields explicitly
// (struct-update or a `new(range, path, kind)` helper).
// ported from: src/options_types/import_record.zig