diff options
Diffstat (limited to 'src/linker.zig')
-rw-r--r-- | src/linker.zig | 476 |
1 files changed, 241 insertions, 235 deletions
diff --git a/src/linker.zig b/src/linker.zig index 5490bad4e..9c41bf4be 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -26,277 +26,283 @@ const Bundler = _bundler.Bundler; const ResolveQueue = _bundler.ResolveQueue; const Runtime = @import("./runtime.zig").Runtime; -pub const Linker = struct { - 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, - - pub fn init( +pub fn NewLinker(comptime BundlerType: type) type { + return struct { + const ThisLinker = @This(); allocator: *std.mem.Allocator, + options: *Options.BundleOptions, + fs: *Fs.FileSystem, log: *logger.Log, resolve_queue: *ResolveQueue, - options: *Options.BundleOptions, - resolver: *Resolver.Resolver, + resolver: *BundlerType.Resolver, resolve_results: *_bundler.ResolveResults, - fs: *Fs.FileSystem, - ) Linker { - relative_paths_list = ImportPathsList.init(allocator); - - return Linker{ - .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, - }; - } - - // fs: fs.FileSystem, - // TODO: - pub fn requireOrImportMetaForSource(c: Linker, source_index: Ref.Int) RequireOrImportMeta { - return RequireOrImportMeta{}; - } - - // 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 - pub fn link(linker: *Linker, file_path: Fs.Path, result: *Bundler.ParseResult) !void { - var needs_runtime = result.ast.uses_exports_ref or result.ast.uses_module_ref or result.ast.runtime_imports.hasAny(); - const source_dir = file_path.name.dir; - var externals = std.ArrayList(u32).init(linker.allocator); - - // Step 1. Resolve imports & requires - switch (result.loader) { - .jsx, .js, .ts, .tsx => { - for (result.ast.import_records) |*import_record, record_index| { - if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) { - import_record.path = try linker.generateImportPath( - source_dir, - linker.runtime_source_path, - Runtime.version(), - ); - result.ast.runtime_import_record_id = @truncate(u32, record_index); - result.ast.needs_runtime = true; - continue; - } + any_needs_runtime: bool = false, + runtime_import_record: ?ImportRecord = null, + runtime_source_path: string, + + 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, + }; + } - if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*resolved_import| { - if (resolved_import.is_external) { - externals.append(@truncate(u32, record_index)) catch unreachable; - continue; - } + // fs: fs.FileSystem, + // TODO: + pub fn requireOrImportMetaForSource(c: ThisLinker, source_index: Ref.Int) RequireOrImportMeta { + return RequireOrImportMeta{}; + } - linker.processImportRecord( - // Include trailing slash - file_path.text[0 .. source_dir.len + 1], - resolved_import, - import_record, - ) 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 (import_record.kind == .stmt and resolved_import.shouldAssumeCommonJS(import_record)) { - import_record.wrap_with_to_module = true; + // 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 + pub fn link(linker: *ThisLinker, file_path: Fs.Path, result: *_bundler.ParseResult) !void { + var needs_runtime = result.ast.uses_exports_ref or result.ast.uses_module_ref or result.ast.runtime_imports.hasAny(); + const source_dir = file_path.name.dir; + var externals = std.ArrayList(u32).init(linker.allocator); + + // Step 1. Resolve imports & requires + switch (result.loader) { + .jsx, .js, .ts, .tsx => { + for (result.ast.import_records) |*import_record, record_index| { + if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) { + import_record.path = try linker.generateImportPath( + source_dir, + linker.runtime_source_path, + Runtime.version(), + ); + result.ast.runtime_import_record_id = @truncate(u32, record_index); result.ast.needs_runtime = true; + continue; } - } else |err| { - switch (err) { - error.ModuleNotFound => { - if (Resolver.Resolver.isPackagePath(import_record.path.text)) { - if (linker.options.platform != .node and Options.ExternalModules.isNodeBuiltin(import_record.path.text)) { - try linker.log.addRangeErrorFmt( - &result.source, - import_record.range, - linker.allocator, - "Could not resolve: \"{s}\". Try setting --platform=\"node\"", - .{import_record.path.text}, - ); + + if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*resolved_import| { + if (resolved_import.is_external) { + externals.append(@truncate(u32, record_index)) catch unreachable; + continue; + } + + linker.processImportRecord( + // Include trailing slash + file_path.text[0 .. source_dir.len + 1], + resolved_import, + import_record, + ) 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 (import_record.kind == .stmt and resolved_import.shouldAssumeCommonJS(import_record)) { + import_record.wrap_with_to_module = true; + result.ast.needs_runtime = true; + } + } else |err| { + switch (err) { + error.ModuleNotFound => { + if (BundlerType.Resolver.isPackagePath(import_record.path.text)) { + if (linker.options.platform != .node and Options.ExternalModules.isNodeBuiltin(import_record.path.text)) { + try linker.log.addRangeErrorFmt( + &result.source, + import_record.range, + linker.allocator, + "Could not resolve: \"{s}\". Try setting --platform=\"node\"", + .{import_record.path.text}, + ); + } else { + try linker.log.addRangeErrorFmt( + &result.source, + import_record.range, + linker.allocator, + "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?", + .{import_record.path.text}, + ); + } } else { try linker.log.addRangeErrorFmt( &result.source, import_record.range, linker.allocator, - "Could not resolve: \"{s}\". Maybe you need to \"npm install\" (or yarn/pnpm)?", - .{import_record.path.text}, + "Could not resolve: \"{s}\"", + .{ + import_record.path.text, + }, ); + continue; } - } else { - try linker.log.addRangeErrorFmt( - &result.source, - import_record.range, - linker.allocator, - "Could not resolve: \"{s}\"", - .{ - import_record.path.text, - }, - ); + }, + else => { continue; - } - }, - else => { - continue; - }, + }, + } } } - } - }, - else => {}, - } - 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 = try linker.generateImportPath( - source_dir, - linker.runtime_source_path, - Runtime.version(), - ), - .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, - }; - } - } - - const ImportPathsList = allocators.BSSStringList(512, 128); - pub var relative_paths_list: *ImportPathsList = undefined; - threadlocal var relative_path_allocator: std.heap.FixedBufferAllocator = undefined; - threadlocal var relative_path_allocator_buf: [4096]u8 = undefined; - threadlocal var relative_path_allocator_buf_loaded: bool = false; - - pub fn generateImportPath(linker: *Linker, source_dir: string, source_path: string, package_version: ?string) !Fs.Path { - if (!relative_path_allocator_buf_loaded) { - relative_path_allocator_buf_loaded = true; - relative_path_allocator = std.heap.FixedBufferAllocator.init(&relative_path_allocator_buf); + }, + else => {}, + } + 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 = try linker.generateImportPath( + source_dir, + linker.runtime_source_path, + Runtime.version(), + ), + .range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 }, + }; + } } - defer relative_path_allocator.reset(); - var absolute_pathname = Fs.PathName.init(source_path); + const ImportPathsList = allocators.BSSStringList(512, 128); + pub var relative_paths_list: *ImportPathsList = undefined; + threadlocal var relative_path_allocator: std.heap.FixedBufferAllocator = undefined; + threadlocal var relative_path_allocator_buf: [4096]u8 = undefined; + threadlocal var relative_path_allocator_buf_loaded: bool = false; - if (!linker.options.preserve_extensions) { - if (linker.options.out_extensions.get(absolute_pathname.ext)) |ext| { - absolute_pathname.ext = ext; + pub fn generateImportPath(linker: *ThisLinker, source_dir: string, source_path: string, package_version: ?string) !Fs.Path { + if (!relative_path_allocator_buf_loaded) { + relative_path_allocator_buf_loaded = true; + relative_path_allocator = std.heap.FixedBufferAllocator.init(&relative_path_allocator_buf); } - } + defer relative_path_allocator.reset(); - switch (linker.options.import_path_format) { - .relative => { - var pretty = try linker.allocator.dupe(u8, linker.fs.relative(source_dir, source_path)); - var pathname = Fs.PathName.init(pretty); - return Fs.Path.initWithPretty(pretty, pretty); - }, - .relative_nodejs => { - var pretty = try linker.allocator.dupe(u8, linker.fs.relative(source_dir, source_path)); - var pathname = Fs.PathName.init(pretty); - var path = Fs.Path.initWithPretty(pretty, pretty); - path.text = path.text[0 .. path.text.len - path.name.ext.len]; - return path; - }, - - .absolute_url => { - var base = linker.fs.relativeTo(source_path); - if (strings.lastIndexOfChar(base, '.')) |dot| { - base = base[0..dot]; - } + var absolute_pathname = Fs.PathName.init(source_path); - if (linker.options.append_package_version_in_query_string and package_version != null) { - const absolute_url = - try std.fmt.allocPrint( - linker.allocator, - "{s}{s}{s}?v={s}", - .{ - linker.options.public_url, - base, - absolute_pathname.ext, - package_version.?, - }, - ); - - return Fs.Path.initWithPretty(absolute_url, absolute_url); - } else { - const absolute_url = try std.fmt.allocPrint( - linker.allocator, - "{s}{s}{s}", - .{ - linker.options.public_url, - base, - absolute_pathname.ext, - }, - ); - - return Fs.Path.initWithPretty(absolute_url, absolute_url); + if (!linker.options.preserve_extensions) { + if (linker.options.out_extensions.get(absolute_pathname.ext)) |ext| { + absolute_pathname.ext = ext; } - }, + } - else => unreachable, - } - } + switch (linker.options.import_path_format) { + .relative => { + var pretty = try linker.allocator.dupe(u8, linker.fs.relative(source_dir, source_path)); + var pathname = Fs.PathName.init(pretty); + return Fs.Path.initWithPretty(pretty, pretty); + }, + .relative_nodejs => { + var pretty = try linker.allocator.dupe(u8, linker.fs.relative(source_dir, source_path)); + var pathname = Fs.PathName.init(pretty); + var path = Fs.Path.initWithPretty(pretty, pretty); + path.text = path.text[0 .. path.text.len - path.name.ext.len]; + return path; + }, + + .absolute_url => { + var base = linker.fs.relativeTo(source_path); + if (strings.lastIndexOfChar(base, '.')) |dot| { + base = base[0..dot]; + } + + if (linker.options.append_package_version_in_query_string and package_version != null) { + const absolute_url = + try std.fmt.allocPrint( + linker.allocator, + "{s}{s}{s}?v={s}", + .{ + linker.options.public_url, + base, + absolute_pathname.ext, + package_version.?, + }, + ); - pub fn processImportRecord(linker: *Linker, source_dir: string, resolve_result: *Resolver.Resolver.Result, import_record: *ImportRecord) !void { + return Fs.Path.initWithPretty(absolute_url, absolute_url); + } else { + const absolute_url = try std.fmt.allocPrint( + linker.allocator, + "{s}{s}{s}", + .{ + linker.options.public_url, + base, + absolute_pathname.ext, + }, + ); - // extremely naive. - resolve_result.is_from_node_modules = strings.contains(resolve_result.path_pair.primary.text, "/node_modules"); + return Fs.Path.initWithPretty(absolute_url, absolute_url); + } + }, - // lazy means: - // Run the resolver - // Don't parse/print automatically. - if (linker.options.resolve_mode != .lazy) { - try linker.enqueueResolveResult(resolve_result); + else => unreachable, + } } - import_record.path = try linker.generateImportPath( - source_dir, - resolve_result.path_pair.primary.text, - resolve_result.package_json_version, - ); - } + pub fn processImportRecord(linker: *ThisLinker, source_dir: string, resolve_result: *Resolver.Result, import_record: *ImportRecord) !void { + + // extremely naive. + resolve_result.is_from_node_modules = strings.contains(resolve_result.path_pair.primary.text, "/node_modules"); - pub fn resolveResultHashKey(linker: *Linker, resolve_result: *const Resolver.Resolver.Result) string { - var hash_key = resolve_result.path_pair.primary.text; + // lazy means: + // Run the resolver + // Don't parse/print automatically. + if (linker.options.resolve_mode != .lazy) { + try linker.enqueueResolveResult(resolve_result); + } - // Shorter hash key is faster to hash - if (strings.startsWith(resolve_result.path_pair.primary.text, linker.fs.top_level_dir)) { - hash_key = resolve_result.path_pair.primary.text[linker.fs.top_level_dir.len..]; + import_record.path = try linker.generateImportPath( + source_dir, + resolve_result.path_pair.primary.text, + resolve_result.package_json_version, + ); } - return hash_key; - } + pub fn resolveResultHashKey(linker: *ThisLinker, resolve_result: *const Resolver.Result) string { + var hash_key = resolve_result.path_pair.primary.text; - pub fn enqueueResolveResult(linker: *Linker, resolve_result: *const Resolver.Resolver.Result) !void { - const hash_key = linker.resolveResultHashKey(resolve_result); + // Shorter hash key is faster to hash + if (strings.startsWith(resolve_result.path_pair.primary.text, linker.fs.top_level_dir)) { + hash_key = resolve_result.path_pair.primary.text[linker.fs.top_level_dir.len..]; + } + + return hash_key; + } - const get_or_put_entry = try linker.resolve_results.backing.getOrPut(hash_key); + pub fn enqueueResolveResult(linker: *ThisLinker, resolve_result: *const Resolver.Result) !void { + const hash_key = linker.resolveResultHashKey(resolve_result); - if (!get_or_put_entry.found_existing) { - get_or_put_entry.entry.value = resolve_result.*; - try linker.resolve_queue.writeItem(resolve_result.*); + const get_or_put_entry = try linker.resolve_results.backing.getOrPut(hash_key); + + if (!get_or_put_entry.found_existing) { + get_or_put_entry.entry.value = resolve_result.*; + try linker.resolve_queue.writeItem(resolve_result.*); + } } - } -}; + }; +} + +pub const Linker = NewLinker(_bundler.Bundler); +pub const ServeLinker = NewLinker(_bundler.ServeBundler); |