diff --git a/src/bundler.zig b/src/bundler.zig index 5a11d98b28..f23bd9f483 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -635,204 +635,6 @@ pub const Bundler = struct { empty: bool = false, }; - pub fn buildWithResolveResult( - bundler: *Bundler, - resolve_result: _resolver.Result, - allocator: std.mem.Allocator, - loader: options.Loader, - comptime Writer: type, - writer: Writer, - comptime import_path_format: options.BundleOptions.ImportPathFormat, - file_descriptor: ?StoredFileDescriptorType, - filepath_hash: u32, - comptime WatcherType: type, - watcher: *WatcherType, - client_entry_point: ?*EntryPoints.ClientEntryPoint, - origin: URL, - comptime is_source_map: bool, - source_map_handler: ?js_printer.SourceMapHandler, - ) !BuildResolveResultPair { - if (resolve_result.is_external) { - return BuildResolveResultPair{ - .written = 0, - .input_fd = null, - }; - } - - errdefer bundler.resetStore(); - - var file_path = (resolve_result.pathConst() orelse { - return BuildResolveResultPair{ - .written = 0, - .input_fd = null, - }; - }).*; - - if (strings.indexOf(file_path.text, bundler.fs.top_level_dir)) |i| { - file_path.pretty = file_path.text[i + bundler.fs.top_level_dir.len ..]; - } else if (!file_path.is_symlink) { - file_path.pretty = allocator.dupe(u8, bundler.fs.relativeTo(file_path.text)) catch unreachable; - } - - const old_bundler_allocator = bundler.allocator; - bundler.allocator = allocator; - defer bundler.allocator = old_bundler_allocator; - const old_linker_allocator = bundler.linker.allocator; - defer bundler.linker.allocator = old_linker_allocator; - bundler.linker.allocator = allocator; - - switch (loader) { - .css => { - const CSSBundlerHMR = Css.NewBundler( - Writer, - @TypeOf(&bundler.linker), - @TypeOf(&bundler.resolver.caches.fs), - WatcherType, - @TypeOf(bundler.fs), - true, - import_path_format, - ); - - const CSSBundler = Css.NewBundler( - Writer, - @TypeOf(&bundler.linker), - @TypeOf(&bundler.resolver.caches.fs), - WatcherType, - @TypeOf(bundler.fs), - false, - import_path_format, - ); - - const written = brk: { - if (bundler.options.hot_module_reloading) { - break :brk (try CSSBundlerHMR.bundle( - file_path.text, - bundler.fs, - writer, - watcher, - &bundler.resolver.caches.fs, - filepath_hash, - file_descriptor, - allocator, - bundler.log, - &bundler.linker, - origin, - )).written; - } else { - break :brk (try CSSBundler.bundle( - file_path.text, - bundler.fs, - writer, - watcher, - &bundler.resolver.caches.fs, - filepath_hash, - file_descriptor, - allocator, - bundler.log, - &bundler.linker, - origin, - )).written; - } - }; - - return BuildResolveResultPair{ - .written = written, - .input_fd = file_descriptor, - }; - }, - else => { - var result = bundler.parse( - ParseOptions{ - .allocator = allocator, - .path = file_path, - .loader = loader, - .dirname_fd = resolve_result.dirname_fd, - .file_descriptor = file_descriptor, - .file_hash = filepath_hash, - .macro_remappings = bundler.options.macro_remap, - .emit_decorator_metadata = resolve_result.emit_decorator_metadata, - .jsx = resolve_result.jsx, - }, - client_entry_point, - ) orelse { - bundler.resetStore(); - return BuildResolveResultPair{ - .written = 0, - .input_fd = null, - }; - }; - - if (result.empty) { - return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true }; - } - - if (bundler.options.target.isBun()) { - if (!bundler.options.transform_only) { - try bundler.linker.link(file_path, &result, origin, import_path_format, false, true); - } - - return BuildResolveResultPair{ - .written = switch (result.ast.exports_kind) { - .esm => try bundler.printWithSourceMapMaybe( - result.ast, - &result.source, - Writer, - writer, - .esm_ascii, - is_source_map, - source_map_handler, - null, - ), - .cjs => try bundler.printWithSourceMapMaybe( - result.ast, - &result.source, - Writer, - writer, - .cjs, - is_source_map, - source_map_handler, - null, - ), - else => unreachable, - }, - .input_fd = result.input_fd, - }; - } - - if (!bundler.options.transform_only) { - try bundler.linker.link(file_path, &result, origin, import_path_format, false, false); - } - - return BuildResolveResultPair{ - .written = switch (result.ast.exports_kind) { - .none, .esm => try bundler.printWithSourceMapMaybe( - result.ast, - &result.source, - Writer, - writer, - .esm, - is_source_map, - source_map_handler, - null, - ), - .cjs => try bundler.printWithSourceMapMaybe( - result.ast, - &result.source, - Writer, - writer, - .cjs, - is_source_map, - source_map_handler, - null, - ), - else => unreachable, - }, - .input_fd = result.input_fd, - }; - }, - } - } - pub fn buildWithResolveResultEager( bundler: *Bundler, resolve_result: _resolver.Result, @@ -1329,16 +1131,16 @@ pub const Bundler = struct { return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty }; } - if (loader != .wasm and source.contents.len == 0 and source.contents.len < 33 and std.mem.trim(u8, source.contents, "\n\r ").len == 0) { - return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty }; - } - switch (loader) { .js, .jsx, .ts, .tsx, => { + if (source.contents.len == 0 or (source.contents.len < 33 and std.mem.trim(u8, source.contents, "\n\r ").len == 0)) { + return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty }; + } + // wasm magic number if (source.isWebAssembly()) { return ParseResult{ @@ -1453,6 +1255,10 @@ pub const Bundler = struct { }, // TODO: use lazy export AST inline .toml, .json => |kind| { + if (source.contents.len == 0 or (source.contents.len < 33 and std.mem.trim(u8, source.contents, "\n\r ").len == 0)) { + return ParseResult{ .source = source, .input_fd = input_fd, .loader = loader, .empty = true, .ast = js_ast.Ast.empty }; + } + var expr = if (kind == .json) // We allow importing tsconfig.*.json or jsconfig.*.json with comments // These files implicitly become JSONC files, which aligns with the behavior of text editors. diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index e633a9af65..0576a27d60 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3587,6 +3587,10 @@ pub const ParseTask = struct { ) !JSAst { switch (loader) { .jsx, .tsx, .js, .ts => { + if (strings.isAllWhitespace(source.contents)) return switch (opts.module_type == .esm) { + inline else => |as_undefined| try getEmptyAST(log, bundler, opts, allocator, source, if (as_undefined) E.Undefined else E.Object), + }; + const trace = tracer(@src(), "ParseJS"); defer trace.end(); return if (try resolver.caches.js.parse( @@ -3609,12 +3613,20 @@ pub const ParseTask = struct { }; }, .json => { + if (strings.isAllWhitespace(source.contents)) return switch (opts.module_type == .esm) { + inline else => |as_undefined| try getEmptyAST(log, bundler, opts, allocator, source, if (as_undefined) E.Undefined else E.Object), + }; + const trace = tracer(@src(), "ParseJSON"); defer trace.end(); const root = (try resolver.caches.json.parsePackageJSON(log, source, allocator, false)) orelse Expr.init(E.Object, E.Object{}, Logger.Loc.Empty); return JSAst.init((try js_parser.newLazyExportAST(allocator, bundler.options.define, opts, log, root, &source, "")).?); }, .toml => { + if (strings.isAllWhitespace(source.contents)) return switch (opts.module_type == .esm) { + inline else => |as_undefined| try getEmptyAST(log, bundler, opts, allocator, source, if (as_undefined) E.Undefined else E.Object), + }; + const trace = tracer(@src(), "ParseTOML"); defer trace.end(); const root = try TOML.parse(&source, log, allocator, false); @@ -3724,6 +3736,8 @@ pub const ParseTask = struct { return JSAst.init((try js_parser.newLazyExportAST(allocator, bundler.options.define, opts, log, root, &source, "")).?); }, .css => { + if (strings.isAllWhitespace(source.contents)) return try getEmptyCSSAST(log, bundler, opts, allocator, source); + if (bundler.options.experimental_css) { // const unique_key = std.fmt.allocPrint(allocator, "{any}A{d:0>8}", .{ bun.fmt.hexIntLower(unique_key_prefix), source.index.get() }) catch unreachable; // unique_key_for_additional_file.* = unique_key; @@ -4224,9 +4238,7 @@ pub const ParseTask = struct { } step.* = .parse; - const is_empty = strings.isAllWhitespace(entry.contents); - - const use_directive: UseDirective = if (!is_empty and bundler.options.server_components) + const use_directive: UseDirective = if (bundler.options.server_components) if (UseDirective.parse(entry.contents)) |use| use else @@ -4319,24 +4331,7 @@ pub const ParseTask = struct { task.jsx.parse = loader.isJSX(); var unique_key_for_additional_file: []const u8 = ""; - var ast: JSAst = if (!is_empty) - try getAST(log, bundler, opts, allocator, resolver, source, loader, task.ctx.unique_key, &unique_key_for_additional_file) - else switch (opts.module_type == .esm) { - inline else => |as_undefined| if (loader == .css and this.ctx.bundler.options.experimental_css) try getEmptyCSSAST( - log, - bundler, - opts, - allocator, - source, - ) else try getEmptyAST( - log, - bundler, - opts, - allocator, - source, - if (as_undefined) E.Undefined else E.Object, - ), - }; + var ast: JSAst = try getAST(log, bundler, opts, allocator, resolver, source, loader, task.ctx.unique_key, &unique_key_for_additional_file); ast.target = target; if (ast.parts.len <= 1 and ast.css == null) { diff --git a/test/regression/issue/15536.test.ts b/test/regression/issue/15536.test.ts new file mode 100644 index 0000000000..d91c48ed2f --- /dev/null +++ b/test/regression/issue/15536.test.ts @@ -0,0 +1,91 @@ +import { $ } from "bun"; +import { test, expect } from "bun:test"; +import { bunExe, bunEnv, tempDirWithFiles } from "harness"; + +import * as empty_text from "./15536/empty_text.html" with { type: "text" }; +import * as partial_text from "./15536/partial_text.html" with { type: "text" }; +import * as empty_script from "./15536/empty_script.js"; +import * as empty_script_2 from "./15536/empty_script_2.js"; + +test("empty files from import", () => { + expect( + JSON.stringify({ + empty_text, + partial_text, + empty_script, + empty_script_2, + }), + ).toMatchInlineSnapshot( + `"{"empty_text":{"default":""},"partial_text":{"default":"\\n\\n\\n\\n\\n"},"empty_script":{},"empty_script_2":{}}"`, + ); +}); + +test("empty files from build (#15536)", async () => { + const dir = tempDirWithFiles("15536", { + "demo": { + "a.js": 'import html from "./a.html";\nconsole.log(html);', + "a.html": "", + }, + "demo.js": `\ +const { outputs } = await Bun.build({ + loader: { + ".html": "text" + }, + entrypoints: ["./demo/a.js"] +}); + +console.log(await outputs[0].text());`, + }); + const result = Bun.spawnSync({ + cmd: [bunExe(), "demo.js"], + cwd: dir, + env: { ...bunEnv }, + stdio: ["inherit", "pipe", "inherit"], + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.toString().replaceAll(/\[parsetask\] ParseTask\(.+?, runtime\) callback\n/g, "")) + .toMatchInlineSnapshot(` +"// demo/a.html +var a_default = ""; + +// demo/a.js +console.log(a_default); + +" +`); +}); + +test("empty js file", async () => { + const dir = tempDirWithFiles("15536", { + "demo": { + "a.js": 'import value from "./empty.js";\nconsole.log(value);', + "empty.js": "", + }, + "demo.js": `\ +const { outputs } = await Bun.build({ + loader: { + ".html": "text" + }, + entrypoints: ["./demo/a.js"] +}); + +console.log(await outputs[0].text());`, + }); + const result = Bun.spawnSync({ + cmd: [bunExe(), "demo.js"], + cwd: dir, + env: { ...bunEnv }, + stdio: ["inherit", "pipe", "inherit"], + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.toString().replaceAll(/\[parsetask\] ParseTask\(.+?, runtime\) callback\n/g, "")) + .toMatchInlineSnapshot(` +"// demo/empty.js +var empty_default = {}; + +// demo/a.js +console.log(empty_default); + +" +`); +}); diff --git a/test/regression/issue/15536/empty_script.js b/test/regression/issue/15536/empty_script.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/regression/issue/15536/empty_script_2.js b/test/regression/issue/15536/empty_script_2.js new file mode 100644 index 0000000000..679c8b1b62 --- /dev/null +++ b/test/regression/issue/15536/empty_script_2.js @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/regression/issue/15536/empty_text.html b/test/regression/issue/15536/empty_text.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/regression/issue/15536/partial_text.html b/test/regression/issue/15536/partial_text.html new file mode 100644 index 0000000000..3f2ff2d6cc --- /dev/null +++ b/test/regression/issue/15536/partial_text.html @@ -0,0 +1,5 @@ + + + + +