diff options
author | 2021-09-19 03:43:17 -0700 | |
---|---|---|
committer | 2021-09-19 03:43:17 -0700 | |
commit | 60b5fb95b19b2f96dcfd851663b40e1155c9cc0e (patch) | |
tree | 483c76e2ff7b87e5a1fdf510b1cc577826055df8 /src/linker.zig | |
parent | 9ae35ec5811f7395f98988ccdcd07395cd731bb0 (diff) | |
download | bun-60b5fb95b19b2f96dcfd851663b40e1155c9cc0e.tar.gz bun-60b5fb95b19b2f96dcfd851663b40e1155c9cc0e.tar.zst bun-60b5fb95b19b2f96dcfd851663b40e1155c9cc0e.zip |
WIP macros
Diffstat (limited to 'src/linker.zig')
-rw-r--r-- | src/linker.zig | 1211 |
1 files changed, 603 insertions, 608 deletions
diff --git a/src/linker.zig b/src/linker.zig index 182fe3b3c..ee77fecce 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -32,698 +32,693 @@ pub const CSSResolveError = error{ResolveError}; pub const OnImportCallback = fn (resolve_result: *const Resolver.Result, import_record: *ImportRecord, source_dir: string) void; -pub fn NewLinker(comptime BundlerType: type) type { - return struct { - const HashedFileNameMap = std.AutoHashMap(u64, string); - const ThisLinker = @This(); +pub const Linker = struct { + const HashedFileNameMap = std.AutoHashMap(u64, string); + const ThisLinker = @This(); + allocator: *std.mem.Allocator, + options: *Options.BundleOptions, + fs: *Fs.FileSystem, + log: *logger.Log, + resolve_queue: *ResolveQueue, + resolver: *Resolver.Resolver, + resolve_results: *_bundler.ResolveResults, + any_needs_runtime: bool = false, + runtime_import_record: ?ImportRecord = null, + runtime_source_path: string, + hashed_filenames: HashedFileNameMap, + import_counter: usize = 0, + + onImportCSS: ?OnImportCallback = null, + + pub fn init( allocator: *std.mem.Allocator, - options: *Options.BundleOptions, - fs: *Fs.FileSystem, log: *logger.Log, resolve_queue: *ResolveQueue, - resolver: *BundlerType.Resolver, + options: *Options.BundleOptions, + resolver: *Bundler.Resolver, resolve_results: *_bundler.ResolveResults, - any_needs_runtime: bool = false, - runtime_import_record: ?ImportRecord = null, - runtime_source_path: string, - hashed_filenames: HashedFileNameMap, - import_counter: usize = 0, - - onImportCSS: ?OnImportCallback = null, - - pub fn init( - allocator: *std.mem.Allocator, - log: *logger.Log, - resolve_queue: *ResolveQueue, - options: *Options.BundleOptions, - resolver: *BundlerType.Resolver, - resolve_results: *_bundler.ResolveResults, - fs: *Fs.FileSystem, - ) ThisLinker { - relative_paths_list = ImportPathsList.init(allocator); - - return ThisLinker{ - .allocator = allocator, - .options = options, - .fs = fs, - .log = log, - .resolve_queue = resolve_queue, - .resolver = resolver, - .resolve_results = resolve_results, - .runtime_source_path = fs.absAlloc(allocator, &([_]string{"__runtime.js"})) catch unreachable, - .hashed_filenames = HashedFileNameMap.init(allocator), - }; - } - - // fs: fs.FileSystem, - // TODO: - pub fn requireOrImportMetaForSource(c: ThisLinker, source_index: Ref.Int) RequireOrImportMeta { - return RequireOrImportMeta{}; - } - - pub fn getHashedFilename( - this: *ThisLinker, - file_path: Fs.Path, - fd: ?FileDescriptorType, - ) !string { - if (BundlerType.isCacheEnabled) { - var hashed = std.hash.Wyhash.hash(0, file_path.text); - var hashed_result = try this.hashed_filenames.getOrPut(hashed); - if (hashed_result.found_existing) { - return hashed_result.value_ptr.*; - } - } - - var file: std.fs.File = if (fd) |_fd| std.fs.File{ .handle = _fd } else try std.fs.openFileAbsolute(file_path.text, .{ .read = true }); - Fs.FileSystem.setMaxFd(file.handle); - var modkey = try Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, file_path.text, file); - const hash_name = try modkey.hashName(file_path.name.base); - - if (BundlerType.isCacheEnabled) { - var hashed = std.hash.Wyhash.hash(0, file_path.text); - try this.hashed_filenames.put(hashed, try this.allocator.dupe(u8, hash_name)); + fs: *Fs.FileSystem, + ) ThisLinker { + relative_paths_list = ImportPathsList.init(allocator); + + return ThisLinker{ + .allocator = allocator, + .options = options, + .fs = fs, + .log = log, + .resolve_queue = resolve_queue, + .resolver = resolver, + .resolve_results = resolve_results, + .runtime_source_path = fs.absAlloc(allocator, &([_]string{"__runtime.js"})) catch unreachable, + .hashed_filenames = HashedFileNameMap.init(allocator), + }; + } + + // fs: fs.FileSystem, + // TODO: + pub fn requireOrImportMetaForSource(c: ThisLinker, source_index: Ref.Int) RequireOrImportMeta { + return RequireOrImportMeta{}; + } + + pub fn getHashedFilename( + this: *ThisLinker, + file_path: Fs.Path, + fd: ?FileDescriptorType, + ) !string { + if (Bundler.isCacheEnabled) { + var hashed = std.hash.Wyhash.hash(0, file_path.text); + var hashed_result = try this.hashed_filenames.getOrPut(hashed); + if (hashed_result.found_existing) { + return hashed_result.value_ptr.*; } + } - if (this.fs.fs.needToCloseFiles() and fd == null) { - file.close(); - } + var file: std.fs.File = if (fd) |_fd| std.fs.File{ .handle = _fd } else try std.fs.openFileAbsolute(file_path.text, .{ .read = true }); + Fs.FileSystem.setMaxFd(file.handle); + var modkey = try Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, file_path.text, file); + const hash_name = try modkey.hashName(file_path.name.base); - return hash_name; + if (Bundler.isCacheEnabled) { + var hashed = std.hash.Wyhash.hash(0, file_path.text); + try this.hashed_filenames.put(hashed, try this.allocator.dupe(u8, hash_name)); } - pub fn resolveCSS( - this: anytype, - path: Fs.Path, - url: string, - range: logger.Range, - kind: ImportKind, - comptime import_path_format: Options.BundleOptions.ImportPathFormat, - comptime resolve_only: bool, - ) !string { - const dir = path.name.dirWithTrailingSlash(); - - switch (kind) { - .at => { - var resolve_result = try this.resolver.resolve(dir, url, .at); - if (resolve_only or resolve_result.is_external) { - return resolve_result.path_pair.primary.text; - } + if (this.fs.fs.needToCloseFiles() and fd == null) { + file.close(); + } - var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; + return hash_name; + } + + pub fn resolveCSS( + this: anytype, + path: Fs.Path, + url: string, + range: logger.Range, + kind: ImportKind, + comptime import_path_format: Options.BundleOptions.ImportPathFormat, + comptime resolve_only: bool, + ) !string { + const dir = path.name.dirWithTrailingSlash(); + + switch (kind) { + .at => { + var resolve_result = try this.resolver.resolve(dir, url, .at); + if (resolve_only or resolve_result.is_external) { + return resolve_result.path_pair.primary.text; + } - const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; + var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; - this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; - return import_record.path.text; - }, - .at_conditional => { - var resolve_result = try this.resolver.resolve(dir, url, .at_conditional); - if (resolve_only or resolve_result.is_external) { - return resolve_result.path_pair.primary.text; - } + const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; - var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; - const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; - - this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; - return import_record.path.text; - }, - .url => { - var resolve_result = try this.resolver.resolve(dir, url, .url); - if (resolve_only or resolve_result.is_external) { - return resolve_result.path_pair.primary.text; - } + this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; + return import_record.path.text; + }, + .at_conditional => { + var resolve_result = try this.resolver.resolve(dir, url, .at_conditional); + if (resolve_only or resolve_result.is_external) { + return resolve_result.path_pair.primary.text; + } - var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; - const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; + var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; + const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; - this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; - return import_record.path.text; - }, - else => unreachable, - } - unreachable; - } + this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; + return import_record.path.text; + }, + .url => { + var resolve_result = try this.resolver.resolve(dir, url, .url); + if (resolve_only or resolve_result.is_external) { + return resolve_result.path_pair.primary.text; + } - pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string { - if (this.options.platform == .bun) return "/node_modules.server.bun"; + var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind }; + const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; - return if (this.options.node_modules_bundle_url.len > 0) - this.options.node_modules_bundle_url - else - this.options.node_modules_bundle.?.bundle.import_from_name; + this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable; + return import_record.path.text; + }, + else => unreachable, } - - // pub const Scratch = struct { - // threadlocal var externals: std.ArrayList(u32) = undefined; - // threadlocal var has_externals: std.ArrayList(u32) = undefined; - // pub fn externals() { - - // } - // }; - // This modifies the Ast in-place! - // But more importantly, this does the following: - // - Wrap CommonJS files - threadlocal var require_part: js_ast.Part = undefined; - threadlocal var require_part_stmts: [1]js_ast.Stmt = undefined; - threadlocal var require_part_import_statement: js_ast.S.Import = undefined; - threadlocal var require_part_import_clauses: [1]js_ast.ClauseItem = undefined; - const require_alias: string = "__require"; - pub fn link( - linker: *ThisLinker, - file_path: Fs.Path, - result: *_bundler.ParseResult, - comptime import_path_format: Options.BundleOptions.ImportPathFormat, - comptime ignore_runtime: bool, - ) !void { - var needs_runtime = result.ast.uses_exports_ref or result.ast.uses_module_ref or result.ast.runtime_imports.hasAny(); - const source_dir = if (file_path.is_symlink and file_path.pretty.len > 0 and import_path_format == .absolute_url and linker.options.platform != .bun) - Fs.PathName.init(file_path.pretty).dirWithTrailingSlash() - else - file_path.sourceDir(); - var externals = std.ArrayList(u32).init(linker.allocator); - var needs_bundle = false; - var first_bundled_index: ?u32 = null; - var had_resolve_errors = false; - var needs_require = false; - - // Step 1. Resolve imports & requires - switch (result.loader) { - .jsx, .js, .ts, .tsx => { - for (result.ast.import_records) |*import_record, _record_index| { - if (import_record.is_unused) continue; - - const record_index = @truncate(u32, _record_index); - if (comptime !ignore_runtime) { - if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) { - // runtime is included in the bundle, so we don't need to dynamically import it - if (linker.options.node_modules_bundle != null) { - import_record.path.text = linker.nodeModuleBundleImportPath(); - result.ast.runtime_import_record_id = record_index; - } else { - import_record.path = try linker.generateImportPath( - source_dir, - linker.runtime_source_path, - Runtime.version(), - false, - "bun", - import_path_format, - ); - result.ast.runtime_import_record_id = record_index; - result.ast.needs_runtime = true; - } - continue; + unreachable; + } + + pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string { + if (this.options.platform == .bun) return "/node_modules.server.bun"; + + return if (this.options.node_modules_bundle_url.len > 0) + this.options.node_modules_bundle_url + else + this.options.node_modules_bundle.?.bundle.import_from_name; + } + + // pub const Scratch = struct { + // threadlocal var externals: std.ArrayList(u32) = undefined; + // threadlocal var has_externals: std.ArrayList(u32) = undefined; + // pub fn externals() { + + // } + // }; + // This modifies the Ast in-place! + // But more importantly, this does the following: + // - Wrap CommonJS files + threadlocal var require_part: js_ast.Part = undefined; + threadlocal var require_part_stmts: [1]js_ast.Stmt = undefined; + threadlocal var require_part_import_statement: js_ast.S.Import = undefined; + threadlocal var require_part_import_clauses: [1]js_ast.ClauseItem = undefined; + const require_alias: string = "__require"; + pub fn link( + linker: *ThisLinker, + file_path: Fs.Path, + result: *_bundler.ParseResult, + comptime import_path_format: Options.BundleOptions.ImportPathFormat, + comptime ignore_runtime: bool, + ) !void { + var needs_runtime = result.ast.uses_exports_ref or result.ast.uses_module_ref or result.ast.runtime_imports.hasAny(); + const source_dir = if (file_path.is_symlink and file_path.pretty.len > 0 and import_path_format == .absolute_url and linker.options.platform != .bun) + Fs.PathName.init(file_path.pretty).dirWithTrailingSlash() + else + file_path.sourceDir(); + var externals = std.ArrayList(u32).init(linker.allocator); + var needs_bundle = false; + var first_bundled_index: ?u32 = null; + var had_resolve_errors = false; + var needs_require = false; + + // Step 1. Resolve imports & requires + switch (result.loader) { + .jsx, .js, .ts, .tsx => { + for (result.ast.import_records) |*import_record, _record_index| { + if (import_record.is_unused) continue; + + const record_index = @truncate(u32, _record_index); + if (comptime !ignore_runtime) { + if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) { + // runtime is included in the bundle, so we don't need to dynamically import it + if (linker.options.node_modules_bundle != null) { + import_record.path.text = linker.nodeModuleBundleImportPath(); + result.ast.runtime_import_record_id = record_index; + } else { + import_record.path = try linker.generateImportPath( + source_dir, + linker.runtime_source_path, + Runtime.version(), + false, + "bun", + import_path_format, + ); + result.ast.runtime_import_record_id = record_index; + result.ast.needs_runtime = true; } + continue; } + } - if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*_resolved_import| { - const resolved_import: *const Resolver.Result = _resolved_import; - if (resolved_import.is_external) { - externals.append(record_index) catch unreachable; - continue; - } + if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*_resolved_import| { + const resolved_import: *const Resolver.Result = _resolved_import; + if (resolved_import.is_external) { + externals.append(record_index) catch unreachable; + continue; + } - const path = resolved_import.pathConst() orelse { - import_record.path.is_disabled = true; - continue; - }; - - const loader = linker.options.loader(path.name.ext); - if (loader.isJavaScriptLikeOrJSON()) { - bundled: { - if (linker.options.node_modules_bundle) |node_modules_bundle| { - const package_json_ = resolved_import.package_json orelse brk: { - if (resolved_import.isLikelyNodeModule()) { - break :brk linker.resolver.packageJSONForResolvedNodeModule(resolved_import); - } + const path = resolved_import.pathConst() orelse { + import_record.path.is_disabled = true; + continue; + }; + + const loader = linker.options.loader(path.name.ext); + if (loader.isJavaScriptLikeOrJSON()) { + bundled: { + if (linker.options.node_modules_bundle) |node_modules_bundle| { + const package_json_ = resolved_import.package_json orelse brk: { + if (resolved_import.isLikelyNodeModule()) { + break :brk linker.resolver.packageJSONForResolvedNodeModule(resolved_import); + } - break :bundled; - }; - if (package_json_) |package_json| { - const package_base_dir = package_json.source.path.sourceDir(); - const node_module_root = std.fs.path.sep_str ++ "node_modules" ++ std.fs.path.sep_str; - if (strings.lastIndexOf(package_base_dir, node_module_root)) |last_node_modules| { - if (node_modules_bundle.getPackageIDByName(package_json.name)) |possible_pkg_ids| { - const pkg_id: u32 = brk: { - for (possible_pkg_ids) |pkg_id| { - const pkg = node_modules_bundle.bundle.packages[pkg_id]; - if (pkg.hash == package_json.hash) { - break :brk pkg_id; - } + break :bundled; + }; + if (package_json_) |package_json| { + const package_base_dir = package_json.source.path.sourceDir(); + const node_module_root = std.fs.path.sep_str ++ "node_modules" ++ std.fs.path.sep_str; + if (strings.lastIndexOf(package_base_dir, node_module_root)) |last_node_modules| { + if (node_modules_bundle.getPackageIDByName(package_json.name)) |possible_pkg_ids| { + const pkg_id: u32 = brk: { + for (possible_pkg_ids) |pkg_id| { + const pkg = node_modules_bundle.bundle.packages[pkg_id]; + if (pkg.hash == package_json.hash) { + break :brk pkg_id; } - - linker.log.addRangeWarningFmt( - &result.source, - import_record.range, - linker.allocator, - "Multiple versions of \"{s}\".\n {s}@{s}\n {s}@{s}", - .{ - package_json.name, - package_json.name, - node_modules_bundle.str(node_modules_bundle.bundle.packages[possible_pkg_ids[0]].version), - package_json.name, - package_json.version, - }, - ) catch {}; - break :bundled; - }; - - const package = &node_modules_bundle.bundle.packages[pkg_id]; - - if (comptime isDebug) { - std.debug.assert(strings.eql(node_modules_bundle.str(package.name), package_json.name)); } - const package_relative_path = linker.fs.relative( - package_base_dir, - if (!strings.eqlComptime(path.namespace, "node")) path.pretty else path.text, - ); - - const found_module = node_modules_bundle.findModuleInPackage(package, package_relative_path) orelse { - // linker.log.addErrorFmt( - // null, - // logger.Loc.Empty, - // linker.allocator, - // "New dependency import: \"{s}/{s}\"\nPlease run `bun bun` to update the .bun.", - // .{ - // package_json.name, - // package_relative_path, - // }, - // ) catch {}; - break :bundled; - }; - - if (comptime isDebug) { - const module_path = node_modules_bundle.str(found_module.path); - std.debug.assert( - strings.eql( - module_path, - package_relative_path, - ), - ); - } + linker.log.addRangeWarningFmt( + &result.source, + import_record.range, + linker.allocator, + "Multiple versions of \"{s}\".\n {s}@{s}\n {s}@{s}", + .{ + package_json.name, + package_json.name, + node_modules_bundle.str(node_modules_bundle.bundle.packages[possible_pkg_ids[0]].version), + package_json.name, + package_json.version, + }, + ) catch {}; + break :bundled; + }; + + const package = &node_modules_bundle.bundle.packages[pkg_id]; + + if (comptime isDebug) { + std.debug.assert(strings.eql(node_modules_bundle.str(package.name), package_json.name)); + } - import_record.is_bundled = true; - import_record.path.text = linker.nodeModuleBundleImportPath(); - import_record.module_id = found_module.id; - needs_bundle = true; - continue; + const package_relative_path = linker.fs.relative( + package_base_dir, + if (!strings.eqlComptime(path.namespace, "node")) path.pretty else path.text, + ); + + const found_module = node_modules_bundle.findModuleInPackage(package, package_relative_path) orelse { + // linker.log.addErrorFmt( + // null, + // logger.Loc.Empty, + // linker.allocator, + // "New dependency import: \"{s}/{s}\"\nPlease run `bun bun` to update the .bun.", + // .{ + // package_json.name, + // package_relative_path, + // }, + // ) catch {}; + break :bundled; + }; + + if (comptime isDebug) { + const module_path = node_modules_bundle.str(found_module.path); + std.debug.assert( + strings.eql( + module_path, + package_relative_path, + ), + ); } + + import_record.is_bundled = true; + import_record.path.text = linker.nodeModuleBundleImportPath(); + import_record.module_id = found_module.id; + needs_bundle = true; + continue; } } } } } + } - linker.processImportRecord( - loader, - - // Include trailing slash - source_dir, - resolved_import, - import_record, - import_path_format, - ) catch continue; - - // If we're importing a CommonJS module as ESM - // We need to do the following transform: - // import React from 'react'; - // => - // import {_require} from 'RUNTIME_IMPORTS'; - // import * as react_module from 'react'; - // var React = _require(react_module).default; - // UNLESS it's a namespace import - // If it's a namespace import, assume it's safe. - // We can do this in the printer instead of creating a bunch of AST nodes here. - // But we need to at least tell the printer that this needs to happen. - if (result.ast.exports_kind != .cjs and - (import_record.kind == .require or - (import_record.kind == .stmt and resolved_import.shouldAssumeCommonJS(import_record)))) - { - import_record.wrap_with_to_module = true; - import_record.module_id = @truncate(u32, std.hash.Wyhash.hash(0, path.pretty)); + linker.processImportRecord( + loader, - result.ast.needs_runtime = true; - needs_require = true; - } else if (result.ast.exports_kind == .cjs) { - import_record.module_id = @truncate(u32, std.hash.Wyhash.hash(0, path.pretty)); - } - } else |err| { - had_resolve_errors = true; - - switch (err) { - error.ModuleNotFound => { - if (import_record.path.text.len > 0 and Resolver.isPackagePath(import_record.path.text)) { - if (linker.options.platform.isWebLike() and Options.ExternalModules.isNodeBuiltin(import_record.path.text)) { - try linker.log.addResolveError( - &result.source, - import_record.range, - linker.allocator, - "Could not resolve: \"{s}\". Try setting --platform=\"node\" (after bun build exists)", - .{import_record.path.text}, - import_record.kind, - ); - continue; - } else { - try linker.log.addResolveError( - &result.source, - import_record.range, - linker.allocator, - "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?", - .{import_record.path.text}, - import_record.kind, - ); - continue; - } + // Include trailing slash + source_dir, + resolved_import, + import_record, + import_path_format, + ) catch continue; + + // If we're importing a CommonJS module as ESM + // We need to do the following transform: + // import React from 'react'; + // => + // import {_require} from 'RUNTIME_IMPORTS'; + // import * as react_module from 'react'; + // var React = _require(react_module).default; + // UNLESS it's a namespace import + // If it's a namespace import, assume it's safe. + // We can do this in the printer instead of creating a bunch of AST nodes here. + // But we need to at least tell the printer that this needs to happen. + if (result.ast.exports_kind != .cjs and + (import_record.kind == .require or + (import_record.kind == .stmt and resolved_import.shouldAssumeCommonJS(import_record)))) + { + import_record.wrap_with_to_module = true; + import_record.module_id = @truncate(u32, std.hash.Wyhash.hash(0, path.pretty)); + + result.ast.needs_runtime = true; + needs_require = true; + } else if (result.ast.exports_kind == .cjs) { + import_record.module_id = @truncate(u32, std.hash.Wyhash.hash(0, path.pretty)); + } + } else |err| { + had_resolve_errors = true; + + switch (err) { + error.ModuleNotFound => { + if (import_record.path.text.len > 0 and Resolver.isPackagePath(import_record.path.text)) { + if (linker.options.platform.isWebLike() and Options.ExternalModules.isNodeBuiltin(import_record.path.text)) { + try linker.log.addResolveError( + &result.source, + import_record.range, + linker.allocator, + "Could not resolve: \"{s}\". Try setting --platform=\"node\" (after bun build exists)", + .{import_record.path.text}, + import_record.kind, + ); + continue; } else { try linker.log.addResolveError( &result.source, import_record.range, linker.allocator, - "Could not resolve: \"{s}\"", - .{ - import_record.path.text, - }, + "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?", + .{import_record.path.text}, import_record.kind, ); continue; } - }, - else => { + } else { try linker.log.addResolveError( &result.source, import_record.range, linker.allocator, - "{s} resolving \"{s}\"", + "Could not resolve: \"{s}\"", .{ - @errorName(err), import_record.path.text, }, import_record.kind, ); continue; - }, - } + } + }, + else => { + try linker.log.addResolveError( + &result.source, + import_record.range, + linker.allocator, + "{s} resolving \"{s}\"", + .{ + @errorName(err), + import_record.path.text, + }, + import_record.kind, + ); + continue; + }, } } - }, - else => {}, - } - if (had_resolve_errors) return error.ResolveError; - result.ast.externals = externals.toOwnedSlice(); - - if (result.ast.needs_runtime and result.ast.runtime_import_record_id == null) { - var import_records = try linker.allocator.alloc(ImportRecord, result.ast.import_records.len + 1); - std.mem.copy(ImportRecord, import_records, result.ast.import_records); - - import_records[import_records.len - 1] = ImportRecord{ - .kind = .stmt, - .path = if (linker.options.node_modules_bundle != null) - Fs.Path.init(linker.nodeModuleBundleImportPath()) - else - try linker.generateImportPath( - source_dir, - linker.runtime_source_path, - Runtime.version(), - false, - "bun", - import_path_format, - ), - .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, - }; - result.ast.runtime_import_record_id = @truncate(u32, import_records.len - 1); - result.ast.import_records = import_records; - } + } + }, + else => {}, + } + if (had_resolve_errors) return error.ResolveError; + result.ast.externals = externals.toOwnedSlice(); + + if (result.ast.needs_runtime and result.ast.runtime_import_record_id == null) { + var import_records = try linker.allocator.alloc(ImportRecord, result.ast.import_records.len + 1); + std.mem.copy(ImportRecord, import_records, result.ast.import_records); + + import_records[import_records.len - 1] = ImportRecord{ + .kind = .stmt, + .path = if (linker.options.node_modules_bundle != null) + Fs.Path.init(linker.nodeModuleBundleImportPath()) + else + try linker.generateImportPath( + source_dir, + linker.runtime_source_path, + Runtime.version(), + false, + "bun", + import_path_format, + ), + .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, + }; + result.ast.runtime_import_record_id = @truncate(u32, import_records.len - 1); + result.ast.import_records = import_records; + } - // We _assume_ you're importing ESM. - // But, that assumption can be wrong without parsing code of the imports. - // That's where in here, we inject - // > import {require} from 'bun:runtime'; - // Since they definitely aren't using require, we don't have to worry about the symbol being renamed. - if (needs_require and !result.ast.uses_require_ref) { - result.ast.uses_require_ref = true; - require_part_import_clauses[0] = js_ast.ClauseItem{ - .alias = require_alias, - .original_name = "", - .alias_loc = logger.Loc.Empty, - .name = js_ast.LocRef{ - .loc = logger.Loc.Empty, - .ref = result.ast.require_ref, - }, - }; - - require_part_import_statement = js_ast.S.Import{ - .namespace_ref = Ref.None, - .items = std.mem.span(&require_part_import_clauses), - .import_record_index = result.ast.runtime_import_record_id.?, - }; - require_part_stmts[0] = js_ast.Stmt{ - .data = .{ .s_import = &require_part_import_statement }, + // We _assume_ you're importing ESM. + // But, that assumption can be wrong without parsing code of the imports. + // That's where in here, we inject + // > import {require} from 'bun:runtime'; + // Since they definitely aren't using require, we don't have to worry about the symbol being renamed. + if (needs_require and !result.ast.uses_require_ref) { + result.ast.uses_require_ref = true; + require_part_import_clauses[0] = js_ast.ClauseItem{ + .alias = require_alias, + .original_name = "", + .alias_loc = logger.Loc.Empty, + .name = js_ast.LocRef{ .loc = logger.Loc.Empty, - }; - result.ast.prepend_part = js_ast.Part{ .stmts = std.mem.span(&require_part_stmts) }; - } + .ref = result.ast.require_ref, + }, + }; - // This is a bad idea - // I don't think it's safe to do this - const ImportStatementSorter = struct { - import_records: []ImportRecord, - pub fn lessThan(ctx: @This(), lhs: js_ast.Stmt, rhs: js_ast.Stmt) bool { - switch (lhs.data) { - .s_import => |li| { - switch (rhs.data) { - .s_import => |ri| { - const a = ctx.import_records[li.import_record_index]; - const b = ctx.import_records[ri.import_record_index]; - if (a.is_bundled and !b.is_bundled) { - return false; - } else { - return true; - } - }, - else => { + require_part_import_statement = js_ast.S.Import{ + .namespace_ref = Ref.None, + .items = std.mem.span(&require_part_import_clauses), + .import_record_index = result.ast.runtime_import_record_id.?, + }; + require_part_stmts[0] = js_ast.Stmt{ + .data = .{ .s_import = &require_part_import_statement }, + .loc = logger.Loc.Empty, + }; + result.ast.prepend_part = js_ast.Part{ .stmts = std.mem.span(&require_part_stmts) }; + } + + // This is a bad idea + // I don't think it's safe to do this + const ImportStatementSorter = struct { + import_records: []ImportRecord, + pub fn lessThan(ctx: @This(), lhs: js_ast.Stmt, rhs: js_ast.Stmt) bool { + switch (lhs.data) { + .s_import => |li| { + switch (rhs.data) { + .s_import => |ri| { + const a = ctx.import_records[li.import_record_index]; + const b = ctx.import_records[ri.import_record_index]; + if (a.is_bundled and !b.is_bundled) { + return false; + } else { return true; - }, - } - }, - else => { - switch (rhs.data) { - .s_import => |ri| { - const a = ctx.import_records[ri.import_record_index]; - if (!a.is_bundled) { - return false; - } else { - return true; - } - }, - else => { + } + }, + else => { + return true; + }, + } + }, + else => { + switch (rhs.data) { + .s_import => |ri| { + const a = ctx.import_records[ri.import_record_index]; + if (!a.is_bundled) { + return false; + } else { return true; - }, - } - }, - } + } + }, + else => { + return true; + }, + } + }, } - }; + } + }; - // std.sort.sort(comptime T: type, items: []T, context: anytype, comptime lessThan: fn(context:@TypeOf(context), lhs:T, rhs:T)bool) + // std.sort.sort(comptime T: type, items: []T, context: anytype, comptime lessThan: fn(context:@TypeOf(context), lhs:T, rhs:T)bool) - // Change the import order so that any bundled imports appear last - // This is to make it so the bundle (which should be quite large) is least likely to block rendering - // if (needs_bundle) { - // const sorter = ImportStatementSorter{ .import_records = result.ast.import_records }; - // for (result.ast.parts) |*part, i| { - // std.sort.sort(js_ast.Stmt, part.stmts, sorter, ImportStatementSorter.lessThan); - // } - // } - } + // Change the import order so that any bundled imports appear last + // This is to make it so the bundle (which should be quite large) is least likely to block rendering + // if (needs_bundle) { + // const sorter = ImportStatementSorter{ .import_records = result.ast.import_records }; + // for (result.ast.parts) |*part, i| { + // std.sort.sort(js_ast.Stmt, part.stmts, sorter, ImportStatementSorter.lessThan); + // } + // } + } + + const ImportPathsList = allocators.BSSStringList(512, 128); + pub var relative_paths_list: *ImportPathsList = undefined; + + pub fn generateImportPath( + linker: *ThisLinker, + source_dir: string, + source_path: string, + package_version: ?string, + use_hashed_name: bool, + namespace: string, + comptime import_path_format: Options.BundleOptions.ImportPathFormat, + ) !Fs.Path { + switch (import_path_format) { + .absolute_path => { + if (strings.eqlComptime(namespace, "node")) { + return Fs.Path.initWithNamespace(source_path, "node"); + } - const ImportPathsList = allocators.BSSStringList(512, 128); - pub var relative_paths_list: *ImportPathsList = undefined; - - pub fn generateImportPath( - linker: *ThisLinker, - source_dir: string, - source_path: string, - package_version: ?string, - use_hashed_name: bool, - namespace: string, - comptime import_path_format: Options.BundleOptions.ImportPathFormat, - ) !Fs.Path { - switch (import_path_format) { - .absolute_path => { - if (strings.eqlComptime(namespace, "node")) { - return Fs.Path.initWithNamespace(source_path, "node"); - } + var relative_name = linker.fs.relative(source_dir, source_path); + + return Fs.Path.initWithPretty(source_path, relative_name); + }, + .relative => { + var relative_name = linker.fs.relative(source_dir, source_path); + + var pretty: string = undefined; + if (use_hashed_name) { + var basepath = Fs.Path.init(source_path); + const basename = try linker.getHashedFilename(basepath, null); + var dir = basepath.name.dirWithTrailingSlash(); + var _pretty = try linker.allocator.alloc(u8, dir.len + basename.len + basepath.name.ext.len); + std.mem.copy(u8, _pretty, dir); + var remaining_pretty = _pretty[dir.len..]; + std.mem.copy(u8, remaining_pretty, basename); + remaining_pretty = remaining_pretty[basename.len..]; + std.mem.copy(u8, remaining_pretty, basepath.name.ext); + pretty = _pretty; + relative_name = try linker.allocator.dupe(u8, relative_name); + } else { + pretty = try linker.allocator.dupe(u8, relative_name); + relative_name = pretty; + } - var relative_name = linker.fs.relative(source_dir, source_path); + return Fs.Path.initWithPretty(pretty, relative_name); + }, + .relative_nodejs => { + var relative_name = linker.fs.relative(source_dir, source_path); + var pretty: string = undefined; + if (use_hashed_name) { + var basepath = Fs.Path.init(source_path); + const basename = try linker.getHashedFilename(basepath, null); + var dir = basepath.name.dirWithTrailingSlash(); + var _pretty = try linker.allocator.alloc(u8, dir.len + basename.len + basepath.name.ext.len); + std.mem.copy(u8, _pretty, dir); + var remaining_pretty = _pretty[dir.len..]; + std.mem.copy(u8, remaining_pretty, basename); + remaining_pretty = remaining_pretty[basename.len..]; + std.mem.copy(u8, remaining_pretty, basepath.name.ext); + pretty = _pretty; + relative_name = try linker.allocator.dupe(u8, relative_name); + } else { + pretty = try linker.allocator.dupe(u8, relative_name); + relative_name = pretty; + } - return Fs.Path.initWithPretty(source_path, relative_name); - }, - .relative => { - var relative_name = linker.fs.relative(source_dir, source_path); + var pathname = Fs.PathName.init(pretty); + var path = Fs.Path.initWithPretty(pretty, relative_name); + path.text = path.text[0 .. path.text.len - path.name.ext.len]; + return path; + }, + + .absolute_url => { + if (strings.eqlComptime(namespace, "node")) { + if (comptime isDebug) std.debug.assert(strings.eqlComptime(source_path[0..5], "node:")); + + return Fs.Path.init(try std.fmt.allocPrint( + linker.allocator, + // assumption: already starts with "node:" + "{s}/{s}", + .{ + linker.options.origin.origin, + source_path, + }, + )); + } else { + var absolute_pathname = Fs.PathName.init(source_path); - var pretty: string = undefined; - if (use_hashed_name) { - var basepath = Fs.Path.init(source_path); - const basename = try linker.getHashedFilename(basepath, null); - var dir = basepath.name.dirWithTrailingSlash(); - var _pretty = try linker.allocator.alloc(u8, dir.len + basename.len + basepath.name.ext.len); - std.mem.copy(u8, _pretty, dir); - var remaining_pretty = _pretty[dir.len..]; - std.mem.copy(u8, remaining_pretty, basename); - remaining_pretty = remaining_pretty[basename.len..]; - std.mem.copy(u8, remaining_pretty, basepath.name.ext); - pretty = _pretty; - relative_name = try linker.allocator.dupe(u8, relative_name); - } else { - pretty = try linker.allocator.dupe(u8, relative_name); - relative_name = pretty; + if (!linker.options.preserve_extensions) { + if (linker.options.out_extensions.get(absolute_pathname.ext)) |ext| { + absolute_pathname.ext = ext; + } } - return Fs.Path.initWithPretty(pretty, relative_name); - }, - .relative_nodejs => { - var relative_name = linker.fs.relative(source_dir, source_path); - var pretty: string = undefined; - if (use_hashed_name) { - var basepath = Fs.Path.init(source_path); - const basename = try linker.getHashedFilename(basepath, null); - var dir = basepath.name.dirWithTrailingSlash(); - var _pretty = try linker.allocator.alloc(u8, dir.len + basename.len + basepath.name.ext.len); - std.mem.copy(u8, _pretty, dir); - var remaining_pretty = _pretty[dir.len..]; - std.mem.copy(u8, remaining_pretty, basename); - remaining_pretty = remaining_pretty[basename.len..]; - std.mem.copy(u8, remaining_pretty, basepath.name.ext); - pretty = _pretty; - relative_name = try linker.allocator.dupe(u8, relative_name); - } else { - pretty = try linker.allocator.dupe(u8, relative_name); - relative_name = pretty; + var base = linker.fs.relativeTo(source_path); + if (strings.lastIndexOfChar(base, '.')) |dot| { + base = base[0..dot]; } - var pathname = Fs.PathName.init(pretty); - var path = Fs.Path.initWithPretty(pretty, relative_name); - path.text = path.text[0 .. path.text.len - path.name.ext.len]; - return path; - }, - - .absolute_url => { - if (strings.eqlComptime(namespace, "node")) { - if (comptime isDebug) std.debug.assert(strings.eqlComptime(source_path[0..5], "node:")); - - return Fs.Path.init(try std.fmt.allocPrint( - linker.allocator, - // assumption: already starts with "node:" - "{s}/{s}", - .{ - linker.options.origin.origin, - source_path, - }, - )); - } else { - var absolute_pathname = Fs.PathName.init(source_path); - - if (!linker.options.preserve_extensions) { - if (linker.options.out_extensions.get(absolute_pathname.ext)) |ext| { - absolute_pathname.ext = ext; - } - } - - var base = linker.fs.relativeTo(source_path); - if (strings.lastIndexOfChar(base, '.')) |dot| { - base = base[0..dot]; - } - - var dirname = std.fs.path.dirname(base) orelse ""; + var dirname = std.fs.path.dirname(base) orelse ""; - var basename = std.fs.path.basename(base); + var basename = std.fs.path.basename(base); - if (use_hashed_name) { - var basepath = Fs.Path.init(source_path); - basename = try linker.getHashedFilename(basepath, null); - } - - return Fs.Path.init(try linker.options.origin.joinAlloc( - linker.allocator, - linker.options.routes.asset_prefix_path, - dirname, - basename, - absolute_pathname.ext, - )); + if (use_hashed_name) { + var basepath = Fs.Path.init(source_path); + basename = try linker.getHashedFilename(basepath, null); } - }, - else => unreachable, - } - } + return Fs.Path.init(try linker.options.origin.joinAlloc( + linker.allocator, + linker.options.routes.asset_prefix_path, + dirname, + basename, + absolute_pathname.ext, + )); + } + }, - pub fn processImportRecord( - linker: *ThisLinker, - loader: Options.Loader, - source_dir: string, - resolve_result: *const Resolver.Result, - import_record: *ImportRecord, - comptime import_path_format: Options.BundleOptions.ImportPathFormat, - ) !void { - linker.import_counter += 1; - // lazy means: - // Run the resolver - // Don't parse/print automatically. - if (linker.options.resolve_mode != .lazy) { - _ = try linker.enqueueResolveResult(resolve_result); - } - const path = resolve_result.pathConst() orelse unreachable; - - import_record.path = try linker.generateImportPath( - source_dir, - if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform != .bun) path.pretty else path.text, - if (resolve_result.package_json) |package_json| package_json.version else "", - BundlerType.isCacheEnabled and loader == .file, - path.namespace, - import_path_format, - ); - - switch (loader) { - .css => { - if (linker.onImportCSS) |callback| { - callback(resolve_result, import_record, source_dir); - } - // This saves us a less reliable string check - import_record.print_mode = .css; - }, - .file => { - import_record.print_mode = .import_path; - }, - else => {}, - } + else => unreachable, } + } + + pub fn processImportRecord( + linker: *ThisLinker, + loader: Options.Loader, + source_dir: string, + resolve_result: *const Resolver.Result, + import_record: *ImportRecord, + comptime import_path_format: Options.BundleOptions.ImportPathFormat, + ) !void { + linker.import_counter += 1; + // lazy means: + // Run the resolver + // Don't parse/print automatically. + if (linker.options.resolve_mode != .lazy) { + _ = try linker.enqueueResolveResult(resolve_result); + } + const path = resolve_result.pathConst() orelse unreachable; + + import_record.path = try linker.generateImportPath( + source_dir, + if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform != .bun) path.pretty else path.text, + if (resolve_result.package_json) |package_json| package_json.version else "", + Bundler.isCacheEnabled and loader == .file, + path.namespace, + import_path_format, + ); + + switch (loader) { + .css => { + if (linker.onImportCSS) |callback| { + callback(resolve_result, import_record, source_dir); + } + // This saves us a less reliable string check + import_record.print_mode = .css; + }, + .file => { + import_record.print_mode = .import_path; + }, + else => {}, + } + } - pub fn resolveResultHashKey(linker: *ThisLinker, resolve_result: *const Resolver.Result) u64 { - const path = resolve_result.pathConst() orelse unreachable; - var hash_key = path.text; - - // Shorter hash key is faster to hash - if (strings.startsWith(path.text, linker.fs.top_level_dir)) { - hash_key = path.text[linker.fs.top_level_dir.len..]; - } + pub fn resolveResultHashKey(linker: *ThisLinker, resolve_result: *const Resolver.Result) u64 { + const path = resolve_result.pathConst() orelse unreachable; + var hash_key = path.text; - return std.hash.Wyhash.hash(0, hash_key); + // Shorter hash key is faster to hash + if (strings.startsWith(path.text, linker.fs.top_level_dir)) { + hash_key = path.text[linker.fs.top_level_dir.len..]; } - pub fn enqueueResolveResult(linker: *ThisLinker, resolve_result: *const Resolver.Result) !bool { - const hash_key = linker.resolveResultHashKey(resolve_result); + return std.hash.Wyhash.hash(0, hash_key); + } - const get_or_put_entry = try linker.resolve_results.getOrPut(hash_key); + pub fn enqueueResolveResult(linker: *ThisLinker, resolve_result: *const Resolver.Result) !bool { + const hash_key = linker.resolveResultHashKey(resolve_result); - if (!get_or_put_entry.found_existing) { - try linker.resolve_queue.writeItem(resolve_result.*); - } + const get_or_put_entry = try linker.resolve_results.getOrPut(hash_key); - return !get_or_put_entry.found_existing; + if (!get_or_put_entry.found_existing) { + try linker.resolve_queue.writeItem(resolve_result.*); } - }; -} -pub const Linker = NewLinker(_bundler.Bundler); -pub const ServeLinker = NewLinker(_bundler.ServeBundler); + return !get_or_put_entry.found_existing; + } +}; |