mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Fix importing empty .txt file
This commit is contained in:
210
src/bundler.zig
210
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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
91
test/regression/issue/15536.test.ts
Normal file
91
test/regression/issue/15536.test.ts
Normal file
@@ -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);
|
||||
|
||||
"
|
||||
`);
|
||||
});
|
||||
0
test/regression/issue/15536/empty_script.js
Normal file
0
test/regression/issue/15536/empty_script.js
Normal file
108
test/regression/issue/15536/empty_script_2.js
Normal file
108
test/regression/issue/15536/empty_script_2.js
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
0
test/regression/issue/15536/empty_text.html
Normal file
0
test/regression/issue/15536/empty_text.html
Normal file
5
test/regression/issue/15536/partial_text.html
Normal file
5
test/regression/issue/15536/partial_text.html
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user