Fix importing empty .txt file

This commit is contained in:
pfg
2024-12-06 15:23:40 -08:00
parent fcca2cc398
commit 7cd823feeb
7 changed files with 228 additions and 223 deletions

View File

@@ -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.

View File

@@ -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) {

View 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);
"
`);
});

View File

@@ -0,0 +1,108 @@

View File

@@ -0,0 +1,5 @@