diff options
author | 2023-04-29 00:08:48 -0400 | |
---|---|---|
committer | 2023-04-28 21:08:48 -0700 | |
commit | 96e113f41c0dae1ccd58c6d1e3b6dd2c54769636 (patch) | |
tree | 1f8c0b88d2daa925abff610f4a458d744bc0bf36 | |
parent | bc0c0f7d203567a5538f271a3bc37c450eeaee46 (diff) | |
download | bun-96e113f41c0dae1ccd58c6d1e3b6dd2c54769636.tar.gz bun-96e113f41c0dae1ccd58c6d1e3b6dd2c54769636.tar.zst bun-96e113f41c0dae1ccd58c6d1e3b6dd2c54769636.zip |
bundler tests: rest of default.test.ts and starting jsx tests (#2765)
40 files changed, 1399 insertions, 776 deletions
diff --git a/completions/bun.bash b/completions/bun.bash index a42705789..7eb83c48b 100644 --- a/completions/bun.bash +++ b/completions/bun.bash @@ -119,7 +119,7 @@ _bun_completions() { --jsx-runtime) COMPREPLY=( $(compgen -W "automatic classic" -- "${cur_word}") ); return;; - --platform) + --target) COMPREPLY=( $(compgen -W "browser node bun" -- "${cur_word}") ); return;; -l|--loader) diff --git a/docs/api/transpiler.md b/docs/api/transpiler.md index 09e28ba0d..bfe0b5ee9 100644 --- a/docs/api/transpiler.md +++ b/docs/api/transpiler.md @@ -195,7 +195,7 @@ interface TranspilerOptions { // Default platform to target // This affects how import and/or require is used - platform?: "browser" | "bun" | "macro" | "node", + target?: "browser" | "bun" | "node", // Specify a tsconfig.json file as stringified JSON or an object // Use this to set a custom JSX factory, fragment, or import source diff --git a/docs/cli/build.md b/docs/cli/build.md index b4c7984dc..3ce34df1d 100644 --- a/docs/cli/build.md +++ b/docs/cli/build.md @@ -184,7 +184,7 @@ If the bundler encounters an import with an unrecognized extension, it treats th {% codetabs %} -```ts#Build_file +```ts#Build file await Bun.build({ entrypoints: ['./index.ts'], outdir: './out' @@ -315,7 +315,7 @@ Depending on the target, Bun will apply different module resolution rules and op - `bun` - For generating bundles that are intended to be run by the Bun runtime. In many cases, it isn't necessary to bundle server-side code; you can directly execute the source code without modification. However, bundling your server code can reduce startup times and improve running performance. - All bundles generated with `target: "bun"` are marked with a special `// @bun` pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution. This + All bundles generated with `target: "bun"` are marked with a special `// @bun` pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution. --- @@ -493,7 +493,6 @@ const result = await Bun.build({ console.log(result.manifest); ``` -The manifest takes the following form: {% details summary="Manifest structure" %} The manifest has the following form: @@ -571,7 +570,7 @@ $ bun build ./index.tsx --outdir ./out --sourcemap=inline --- - `"inline"` -- A sourcemap is generated and appended to the end of the generated bundle as a base64 payload inside a `//# sourceMappingURL= ` comment. +- A sourcemap is generated and appended to the end of the generated bundle as a base64 payload inside a `//# sourceMappingURL=` comment. --- @@ -705,12 +704,12 @@ Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`. await Bun.build({ entrypoints: ['./index.tsx'], outdir: './out', - naming: "./[dir]/[name].[ext]", // default + naming: "[dir]/[name].[ext]", // default }) ``` ```bash#CLI -n/a +$ bun build ./index.tsx --outdir ./out --entry-naming [dir]/[name].[ext] ``` {% /codetabs %} @@ -785,7 +784,7 @@ await Bun.build({ ``` ```bash#CLI -$ bun build ./index.tsx --outdir ./out --naming [name]-[hash].[ext] +$ bun build ./index.tsx --outdir ./out --entry-naming [name]-[hash].[ext] ``` {% /codetabs %} @@ -809,15 +808,16 @@ await Bun.build({ entrypoints: ['./index.tsx'], outdir: './out', naming: { - entrypoint: '[dir]/[name]-[hash].[ext]', - chunk: '[dir]/[name]-[hash].[ext]', - asset: '[dir]/[name]-[hash].[ext]', + // default values + entry: '[dir]/[name].[ext]', + chunk: '[name]-[hash].[ext]', + asset: '[name]-[hash].[ext]', }, }) ``` ```bash#CLI -n/a +$ bun build ./index.tsx --outdir ./out --entry-naming "[dir]/[name].[ext]" --chunk-naming "[name]-[hash].[ext]" --asset-naming "[name]-[hash].[ext]" ``` {% /codetabs %} @@ -973,19 +973,19 @@ The output file would now look something like this. ```ts await Bun.build({ entrypoints: string[]; // list of file path - outdir?: string; // output directory + outdir?: string; // default to in-memory build target?: "browser" | "bun" | "node"; // default: "browser" - splitting?: boolean, // default true, enable code splitting + splitting?: boolean; // default true plugins?: BunPlugin[]; - manifest?: boolean; // whether to return manifest - external?: Array<string>; + manifest?: boolean; // default false + external?: string[]; naming?: string | { - entrypoint?: string; - chunk?: string; - asset?: string; - }, // default './[dir]/[name].[ext]' + entry?: string; // default '[dir]/[name].[ext]' + chunk?: string; // default '[name]-[hash].[ext]' + asset?: string; // default '[name]-[hash].[ext]' + }; publicPath?: string; // e.g. http://mydomain.com/ - minify?: boolean | { + minify?: boolean | { // default false identifiers?: boolean; whitespace?: boolean; syntax?: boolean; diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 14b9d3a38..e14118680 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -711,21 +711,6 @@ declare module "bun" { ) => number | bigint; } - export type Platform = - /** - * When building for bun.js - */ - | "bun" - /** - * When building for the web - */ - | "browser" - /** - * When building for node.js - */ - | "node" - | "neutral"; - export type JavaScriptLoader = "jsx" | "js" | "ts" | "tsx"; /** @@ -776,7 +761,7 @@ declare module "bun" { /** What platform are we targeting? This may affect how import and/or require is used */ /** @example "browser" */ - platform?: Platform; + target?: Target; /** * TSConfig.json file as stringified JSON or an object @@ -974,7 +959,7 @@ declare module "bun" { | string | { chunk?: string; - entrypoint?: string; + entry?: string; asset?: string; }; // | string; // root?: string; // project root @@ -1000,7 +985,7 @@ declare module "bun" { outputs: Array<{ path: string; result: T }>; }; - function build(config: BuildConfig): BuildResult<Blob>; + function build(config: BuildConfig): Promise<BuildResult<Blob>>; /** * **0** means the message was **dropped** diff --git a/src/api/demo/schema.peechy b/src/api/demo/schema.peechy index 59cb1edf5..09d3c1fac 100644 --- a/src/api/demo/schema.peechy +++ b/src/api/demo/schema.peechy @@ -107,7 +107,7 @@ smol ResolveMode { bundle = 4; } -smol Platform { +smol Target { browser = 1; node = 2; bun = 3; @@ -326,7 +326,7 @@ message TransformOptions { LoaderMap loaders = 13; string[] main_fields = 14; - Platform platform = 15; + Target target = 15; bool serve = 16; @@ -550,4 +550,4 @@ message BunInstall { bool disable_manifest_cache = 16; string global_dir = 17; string global_bin_dir = 18; -}
\ No newline at end of file +} diff --git a/src/api/demo/schema.zig b/src/api/demo/schema.zig index e4871b902..748422ed8 100644 --- a/src/api/demo/schema.zig +++ b/src/api/demo/schema.zig @@ -792,7 +792,7 @@ pub const Api = struct { } }; - pub const Platform = enum(u8) { + pub const Target = enum(u8) { _none, /// browser browser, @@ -1708,8 +1708,8 @@ pub const Api = struct { /// main_fields main_fields: []const []const u8, - /// platform - platform: ?Platform = null, + /// target + target: ?Target = null, /// serve serve: ?bool = null, @@ -1796,7 +1796,7 @@ pub const Api = struct { this.main_fields = try reader.readArray([]const u8); }, 15 => { - this.platform = try reader.readValue(Platform); + this.target = try reader.readValue(Target); }, 16 => { this.serve = try reader.readValue(bool); @@ -1896,9 +1896,9 @@ pub const Api = struct { try writer.writeFieldID(14); try writer.writeArray([]const u8, main_fields); } - if (this.platform) |platform| { + if (this.target) |target| { try writer.writeFieldID(15); - try writer.writeEnum(platform); + try writer.writeEnum(target); } if (this.serve) |serve| { try writer.writeFieldID(16); diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index ed58db778..4114d951d 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -135,13 +135,13 @@ export const ResolveModeKeys: { 4: "bundle"; bundle: "bundle"; }; -export const enum Platform { +export const enum Target { browser = 1, node = 2, bun = 3, bun_macro = 4, } -export const PlatformKeys: { +export const TargetKeys: { 1: "browser"; browser: "browser"; 2: "node"; @@ -534,7 +534,7 @@ export interface TransformOptions { external?: string[]; loaders?: LoaderMap; main_fields?: string[]; - platform?: Platform; + target?: Target; serve?: boolean; extension_order?: string[]; generate_node_module_bundle?: boolean; diff --git a/src/api/schema.js b/src/api/schema.js index 3ec60fb4f..c4f2400ed 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -548,7 +548,7 @@ const ResolveModeKeys = { "dev": "dev", "bundle": "bundle", }; -const Platform = { +const Target = { "1": 1, "2": 2, "3": 3, @@ -558,7 +558,7 @@ const Platform = { "bun": 3, "bun_macro": 4, }; -const PlatformKeys = { +const TargetKeys = { "1": "browser", "2": "node", "3": "bun", @@ -1655,7 +1655,7 @@ function decodeTransformOptions(bb) { break; case 15: - result["platform"] = Platform[bb.readByte()]; + result["target"] = Target[bb.readByte()]; break; case 16: @@ -1825,11 +1825,11 @@ function encodeTransformOptions(message, bb) { } } - var value = message["platform"]; + var value = message["target"]; if (value != null) { bb.writeByte(15); - var encoded = Platform[value]; - if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + ' for enum "Platform"'); + var encoded = Target[value]; + if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + ' for enum "Target"'); bb.writeByte(encoded); } @@ -3321,8 +3321,8 @@ export { decodeFallbackMessageContainer }; export { encodeFallbackMessageContainer }; export { ResolveMode }; export { ResolveModeKeys }; -export { Platform }; -export { PlatformKeys }; +export { Target }; +export { TargetKeys }; export { CSSInJSBehavior }; export { CSSInJSBehaviorKeys }; export { JSXRuntime }; diff --git a/src/api/schema.peechy b/src/api/schema.peechy index a6c0fed8a..71e85d68e 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -111,7 +111,7 @@ smol ResolveMode { bundle = 4; } -smol Platform { +smol Target { browser = 1; node = 2; bun = 3; @@ -331,7 +331,7 @@ message TransformOptions { LoaderMap loaders = 13; string[] main_fields = 14; - Platform platform = 15; + Target target = 15; bool serve = 16; diff --git a/src/api/schema.zig b/src/api/schema.zig index 2629f1c5e..227d1296b 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -804,7 +804,7 @@ pub const Api = struct { } }; - pub const Platform = enum(u8) { + pub const Target = enum(u8) { _none, /// browser browser, @@ -1723,8 +1723,8 @@ pub const Api = struct { /// main_fields main_fields: []const []const u8, - /// platform - platform: ?Platform = null, + /// target + target: ?Target = null, /// serve serve: ?bool = null, @@ -1814,7 +1814,7 @@ pub const Api = struct { this.main_fields = try reader.readArray([]const u8); }, 15 => { - this.platform = try reader.readValue(Platform); + this.target = try reader.readValue(Target); }, 16 => { this.serve = try reader.readValue(bool); @@ -1917,9 +1917,9 @@ pub const Api = struct { try writer.writeFieldID(14); try writer.writeArray([]const u8, main_fields); } - if (this.platform) |platform| { + if (this.target) |target| { try writer.writeFieldID(15); - try writer.writeEnum(platform); + try writer.writeEnum(target); } if (this.serve) |serve| { try writer.writeFieldID(16); diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 1c774e1f4..022c83cb4 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -32,7 +32,7 @@ const TSConfigJSON = @import("../../resolver/tsconfig_json.zig").TSConfigJSON; const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON; const logger = bun.logger; const Loader = options.Loader; -const Platform = options.Platform; +const Target = options.Target; const JSAst = bun.JSAst; const JSParser = bun.js_parser; const JSPrinter = bun.js_printer; @@ -47,7 +47,7 @@ pub const JSBundler = struct { const OwnedString = bun.MutableString; pub const Config = struct { - target: options.Platform = options.Platform.browser, + target: Target = Target.browser, entry_points: bun.StringSet = bun.StringSet.init(bun.default_allocator), hot: bool = false, define: bun.StringMap = bun.StringMap.init(bun.default_allocator, true), @@ -84,7 +84,7 @@ pub const JSBundler = struct { errdefer this.deinit(allocator); errdefer if (plugins.*) |plugin| plugin.deinit(); - if (try config.getOptionalEnum(globalThis, "target", options.Platform)) |target| { + if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| { this.target = target; } @@ -175,7 +175,7 @@ pub const JSBundler = struct { this.names.entry_point.data = this.names.owned_entry_point.list.items; } } else if (naming.isObject()) { - if (try naming.getOptional(globalThis, "entrypoint", ZigString.Slice)) |slice| { + if (try naming.getOptional(globalThis, "entry", ZigString.Slice)) |slice| { defer slice.deinit(); this.names.owned_entry_point.appendSliceExact(slice.slice()) catch unreachable; this.names.entry_point.data = this.names.owned_entry_point.list.items; @@ -414,7 +414,7 @@ pub const JSBundler = struct { importer_source_index: ?u32 = null, import_record_index: u32 = 0, range: logger.Range = logger.Range.None, - original_platform: options.Platform, + original_target: Target, }; pub fn create( @@ -424,7 +424,7 @@ pub const JSBundler = struct { importer_source_index: u32, import_record_index: u32, source_file: []const u8 = "", - original_platform: options.Platform, + original_target: Target, record: *const bun.ImportRecord, }, }, @@ -443,7 +443,7 @@ pub const JSBundler = struct { .importer_source_index = file.importer_source_index, .import_record_index = file.import_record_index, .range = file.record.range, - .original_platform = file.original_platform, + .original_target = file.original_target, }, }, .completion = completion, diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index c4968a6ee..f1b00f191 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -32,7 +32,7 @@ const TSConfigJSON = @import("../../resolver/tsconfig_json.zig").TSConfigJSON; const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON; const logger = bun.logger; const Loader = options.Loader; -const Platform = options.Platform; +const Target = options.Target; const JSAst = bun.JSAst; const Transpiler = @This(); const JSParser = bun.js_parser; @@ -54,7 +54,7 @@ buffer_writer: ?JSPrinter.BufferWriter = null, const default_transform_options: Api.TransformOptions = brk: { var opts = std.mem.zeroes(Api.TransformOptions); opts.disable_hmr = true; - opts.platform = Api.Platform.browser; + opts.target = Api.Target.browser; opts.serve = false; break :brk opts; }; @@ -427,9 +427,9 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std } } - if (object.get(globalThis, "platform")) |platform| { - if (Platform.fromJS(globalThis, platform, exception)) |resolved| { - transpiler.transform.platform = resolved.toAPI(); + if (object.get(globalThis, "target")) |target| { + if (Target.fromJS(globalThis, target, exception)) |resolved| { + transpiler.transform.target = resolved.toAPI(); } if (exception.* != null) { @@ -471,7 +471,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std } transpiler.runtime.allow_runtime = false; - transpiler.runtime.dynamic_require = switch (transpiler.transform.platform orelse .browser) { + transpiler.runtime.dynamic_require = switch (transpiler.transform.target orelse .browser) { .bun, .bun_macro => true, else => false, }; diff --git a/src/bun.js/builtins/cpp/BundlerPluginBuiltins.cpp b/src/bun.js/builtins/cpp/BundlerPluginBuiltins.cpp index 1f2053ad5..552057d51 100644 --- a/src/bun.js/builtins/cpp/BundlerPluginBuiltins.cpp +++ b/src/bun.js/builtins/cpp/BundlerPluginBuiltins.cpp @@ -192,7 +192,7 @@ const char* const s_bundlerPluginRunOnResolvePluginsCode = const JSC::ConstructAbility s_bundlerPluginRunSetupFunctionCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_bundlerPluginRunSetupFunctionCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_bundlerPluginRunSetupFunctionCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_bundlerPluginRunSetupFunctionCodeLength = 3262; +const int s_bundlerPluginRunSetupFunctionCodeLength = 3786; static const JSC::Intrinsic s_bundlerPluginRunSetupFunctionCodeIntrinsic = JSC::NoIntrinsic; const char* const s_bundlerPluginRunSetupFunctionCode = "(function (setup) {\n" \ @@ -248,6 +248,19 @@ const char* const s_bundlerPluginRunSetupFunctionCode = " validate(filterObject, callback, onResolvePlugins);\n" \ " }\n" \ "\n" \ + " function onStart(callback) {\n" \ + " //\n" \ + " @throwTypeError(\"On-start callbacks are not implemented yet. See https:/\\/github.com/oven-sh/bun/issues/2771\");\n" \ + " }\n" \ + "\n" \ + " function onEnd(callback) {\n" \ + " @throwTypeError(\"On-end callbacks are not implemented yet. See https:/\\/github.com/oven-sh/bun/issues/2771\");\n" \ + " }\n" \ + "\n" \ + " function onDispose(callback) {\n" \ + " @throwTypeError(\"On-dispose callbacks are not implemented yet. See https:/\\/github.com/oven-sh/bun/issues/2771\");\n" \ + " }\n" \ + "\n" \ " const processSetupResult = () => {\n" \ " var anyOnLoad = false,\n" \ " anyOnResolve = false;\n" \ @@ -277,7 +290,7 @@ const char* const s_bundlerPluginRunSetupFunctionCode = " if (!existing) {\n" \ " onResolveObject.@set(namespace, callbacks);\n" \ " } else {\n" \ - " onResolveObject.@set(existing.concat(callbacks));\n" \ + " onResolveObject.@set(namespace, existing.concat(callbacks));\n" \ " }\n" \ " }\n" \ " }\n" \ @@ -294,7 +307,7 @@ const char* const s_bundlerPluginRunSetupFunctionCode = " if (!existing) {\n" \ " onLoadObject.@set(namespace, callbacks);\n" \ " } else {\n" \ - " onLoadObject.@set(existing.concat(callbacks));\n" \ + " onLoadObject.@set(namespace, existing.concat(callbacks));\n" \ " }\n" \ " }\n" \ " }\n" \ @@ -304,8 +317,11 @@ const char* const s_bundlerPluginRunSetupFunctionCode = " };\n" \ "\n" \ " var setupResult = setup({\n" \ + " onDispose,\n" \ + " onEnd,\n" \ " onLoad,\n" \ " onResolve,\n" \ + " onStart,\n" \ " });\n" \ "\n" \ " if (setupResult && @isPromise(setupResult)) {\n" \ diff --git a/src/bun.js/builtins/js/BundlerPlugin.js b/src/bun.js/builtins/js/BundlerPlugin.js index 9fbb323ed..4daa2dcbb 100644 --- a/src/bun.js/builtins/js/BundlerPlugin.js +++ b/src/bun.js/builtins/js/BundlerPlugin.js @@ -221,6 +221,19 @@ function runSetupFunction(setup) { validate(filterObject, callback, onResolvePlugins); } + function onStart(callback) { + // builtin generator thinks the // in the link is a comment and removes it + @throwTypeError("On-start callbacks are not implemented yet. See https:/\/github.com/oven-sh/bun/issues/2771"); + } + + function onEnd(callback) { + @throwTypeError("On-end callbacks are not implemented yet. See https:/\/github.com/oven-sh/bun/issues/2771"); + } + + function onDispose(callback) { + @throwTypeError("On-dispose callbacks are not implemented yet. See https:/\/github.com/oven-sh/bun/issues/2771"); + } + const processSetupResult = () => { var anyOnLoad = false, anyOnResolve = false; @@ -250,7 +263,7 @@ function runSetupFunction(setup) { if (!existing) { onResolveObject.@set(namespace, callbacks); } else { - onResolveObject.@set(existing.concat(callbacks)); + onResolveObject.@set(namespace, existing.concat(callbacks)); } } } @@ -267,7 +280,7 @@ function runSetupFunction(setup) { if (!existing) { onLoadObject.@set(namespace, callbacks); } else { - onLoadObject.@set(existing.concat(callbacks)); + onLoadObject.@set(namespace, existing.concat(callbacks)); } } } @@ -277,8 +290,11 @@ function runSetupFunction(setup) { }; var setupResult = setup({ + onDispose, + onEnd, onLoad, onResolve, + onStart, }); if (setupResult && @isPromise(setupResult)) { diff --git a/src/bun.js/config.zig b/src/bun.js/config.zig index b3614854e..6e304d526 100644 --- a/src/bun.js/config.zig +++ b/src/bun.js/config.zig @@ -42,6 +42,6 @@ pub fn configureTransformOptionsForBunVM(allocator: std.mem.Allocator, _args: Ap pub fn configureTransformOptionsForBun(_: std.mem.Allocator, _args: Api.TransformOptions) !Api.TransformOptions { var args = _args; - args.platform = Api.Platform.bun; + args.target = Api.Target.bun; return args; } diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index a0fd52546..06f833e3b 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -648,7 +648,7 @@ pub const VirtualMachine = struct { this.macro_event_loop.concurrent_tasks = .{}; } - this.bundler.options.platform = .bun_macro; + this.bundler.options.target = .bun_macro; this.bundler.resolver.caches.fs.use_alternate_source_cache = true; this.macro_mode = true; this.event_loop = &this.macro_event_loop; @@ -656,7 +656,7 @@ pub const VirtualMachine = struct { } pub fn disableMacroMode(this: *VirtualMachine) void { - this.bundler.options.platform = .bun; + this.bundler.options.target = .bun; this.bundler.resolver.caches.fs.use_alternate_source_cache = false; this.macro_mode = false; this.event_loop = &this.regular_event_loop; diff --git a/src/bundler.zig b/src/bundler.zig index 9030aaf8c..b6f9eac26 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -549,7 +549,7 @@ pub const Bundler = struct { return; } - if (this.options.platform == .bun_macro) { + if (this.options.target == .bun_macro) { this.options.env.behavior = .prefix; this.options.env.prefix = "BUN_"; } @@ -565,7 +565,7 @@ pub const Bundler = struct { defer js_ast.Stmt.Data.Store.reset(); if (this.options.framework) |framework| { - if (this.options.platform.isClient()) { + if (this.options.target.isClient()) { try this.options.loadDefines(this.allocator, this.env, &framework.client.env); } else { try this.options.loadDefines(this.allocator, this.env, &framework.server.env); @@ -607,7 +607,7 @@ pub const Bundler = struct { } if (this.options.areDefinesUnset()) { - if (this.options.platform.isClient()) { + if (this.options.target.isClient()) { this.options.env = framework.client.env; } else { this.options.env = framework.server.env; @@ -872,7 +872,7 @@ pub const Bundler = struct { return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true }; } - if (bundler.options.platform.isBun()) { + if (bundler.options.target.isBun()) { try bundler.linker.link(file_path, &result, origin, import_path_format, false, true); return BuildResolveResultPair{ .written = switch (result.ast.exports_kind) { @@ -989,7 +989,7 @@ pub const Bundler = struct { ) orelse { return null; }; - if (!bundler.options.platform.isBun()) + if (!bundler.options.target.isBun()) try bundler.linker.link( file_path, &result, @@ -1008,8 +1008,8 @@ pub const Bundler = struct { true, ); - output_file.size = switch (bundler.options.platform) { - .neutral, .browser, .node => try bundler.print( + output_file.size = switch (bundler.options.target) { + .browser, .node => try bundler.print( result, js_printer.FileWriter, js_printer.NewFileWriter(file), @@ -1150,7 +1150,7 @@ pub const Bundler = struct { .require_ref = ast.require_ref, .css_import_behavior = bundler.options.cssImportBehavior(), .source_map_handler = source_map_context, - .rewrite_require_resolve = bundler.options.platform != .node, + .rewrite_require_resolve = bundler.options.target != .node, .minify_whitespace = bundler.options.minify_whitespace, .minify_syntax = bundler.options.minify_syntax, .minify_identifiers = bundler.options.minify_identifiers, @@ -1172,7 +1172,7 @@ pub const Bundler = struct { .require_ref = ast.require_ref, .source_map_handler = source_map_context, .css_import_behavior = bundler.options.cssImportBehavior(), - .rewrite_require_resolve = bundler.options.platform != .node, + .rewrite_require_resolve = bundler.options.target != .node, .minify_whitespace = bundler.options.minify_whitespace, .minify_syntax = bundler.options.minify_syntax, .minify_identifiers = bundler.options.minify_identifiers, @@ -1180,7 +1180,7 @@ pub const Bundler = struct { }, enable_source_map, ), - .esm_ascii => switch (bundler.options.platform.isBun()) { + .esm_ascii => switch (bundler.options.target.isBun()) { inline else => |is_bun| try js_printer.printAst( Writer, writer, @@ -1376,18 +1376,18 @@ pub const Bundler = struct { }; } - const platform = bundler.options.platform; + const target = bundler.options.target; var jsx = this_parse.jsx; jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); opts.enable_legacy_bundling = false; - opts.legacy_transform_require_to_import = bundler.options.allow_runtime and !bundler.options.platform.isBun(); + opts.legacy_transform_require_to_import = bundler.options.allow_runtime and !bundler.options.target.isBun(); opts.features.allow_runtime = bundler.options.allow_runtime; opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); - opts.features.should_fold_typescript_constant_expressions = loader.isTypeScript() or platform.isBun() or bundler.options.inlining; - opts.features.dynamic_require = platform.isBun(); + opts.features.should_fold_typescript_constant_expressions = loader.isTypeScript() or target.isBun() or bundler.options.inlining; + opts.features.dynamic_require = target.isBun(); opts.transform_only = bundler.options.transform_only; // @bun annotation @@ -1403,7 +1403,7 @@ pub const Bundler = struct { // or you're running in SSR // or the file is a node_module opts.features.hot_module_reloading = bundler.options.hot_module_reloading and - platform.isNotBun() and + target.isNotBun() and (!opts.can_import_from_bundle or (opts.can_import_from_bundle and !path.isNodeModule())); opts.features.react_fast_refresh = opts.features.hot_module_reloading and @@ -1411,8 +1411,8 @@ pub const Bundler = struct { bundler.options.jsx.supports_fast_refresh; opts.filepath_hash_for_hmr = file_hash orelse 0; opts.features.auto_import_jsx = bundler.options.auto_import_jsx; - opts.warn_about_unbundled_modules = platform.isNotBun(); - opts.features.jsx_optimization_inline = opts.features.allow_runtime and (bundler.options.jsx_optimization_inline orelse (platform.isBun() and jsx.parse and + opts.warn_about_unbundled_modules = target.isNotBun(); + opts.features.jsx_optimization_inline = opts.features.allow_runtime and (bundler.options.jsx_optimization_inline orelse (target.isBun() and jsx.parse and !jsx.development)) and (jsx.runtime == .automatic or jsx.runtime == .classic); @@ -1432,12 +1432,12 @@ pub const Bundler = struct { opts.macro_context = &bundler.macro_context.?; if (comptime !JSC.is_bindgen) { - if (platform != .bun_macro) { + if (target != .bun_macro) { opts.macro_context.javascript_object = this_parse.macro_js_ctx; } } - opts.features.is_macro_runtime = platform == .bun_macro; + opts.features.is_macro_runtime = target == .bun_macro; opts.features.replace_exports = this_parse.replace_exports; return switch ((bundler.resolver.caches.js.parse( @@ -1530,7 +1530,7 @@ pub const Bundler = struct { }; }, .wasm => { - if (bundler.options.platform.isBun()) { + if (bundler.options.target.isBun()) { if (!source.isWebAssembly()) { bundler.log.addErrorFmt( null, diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 788404bd3..168ef3137 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -411,7 +411,7 @@ pub const BundleV2 = struct { pub fn runResolver( this: *BundleV2, import_record: bun.JSC.API.JSBundler.Resolve.MiniImportRecord, - platform: options.Platform, + target: options.Target, ) void { var resolve_result = this.bundler.resolver.resolve( Fs.PathName.init(import_record.source_file).dirWithTrailingSlash(), @@ -441,7 +441,7 @@ pub const BundleV2 = struct { if (!handles_import_errors) { if (isPackagePath(import_record.specifier)) { - if (platform.isWebLike() and options.ExternalModules.isNodeBuiltin(path_to_use)) { + if (target.isWebLike() and options.ExternalModules.isNodeBuiltin(path_to_use)) { addError( this.bundler.log, source, @@ -548,7 +548,7 @@ pub const BundleV2 = struct { task.jsx = this.bundler.options.jsx; task.task.node.next = null; task.tree_shaking = this.linker.options.tree_shaking; - task.known_platform = import_record.original_platform; + task.known_target = import_record.original_target; _ = @atomicRmw(usize, &this.graph.parse_pending, .Add, 1, .Monotonic); @@ -630,8 +630,8 @@ pub const BundleV2 = struct { heap: ?ThreadlocalArena, ) !*BundleV2 { var generator = try allocator.create(BundleV2); - bundler.options.mark_builtins_as_external = bundler.options.platform.isBun() or bundler.options.platform == .node; - bundler.resolver.opts.mark_builtins_as_external = bundler.options.platform.isBun() or bundler.options.platform == .node; + bundler.options.mark_builtins_as_external = bundler.options.target.isBun() or bundler.options.target == .node; + bundler.resolver.opts.mark_builtins_as_external = bundler.options.target.isBun() or bundler.options.target == .node; var this = generator; generator.* = BundleV2{ @@ -814,8 +814,8 @@ pub const BundleV2 = struct { const key = unique_key_for_additional_files[index]; if (key.len > 0) { var template = PathTemplate.asset; - if (this.bundler.options.asset_names.len > 0) - template.data = this.bundler.options.asset_names; + if (this.bundler.options.asset_naming.len > 0) + template.data = this.bundler.options.asset_naming; const source = &sources[index]; var pathname = source.path.name; // TODO: outbase @@ -1116,7 +1116,7 @@ pub const BundleV2 = struct { // // The file could be on disk. if (strings.eqlComptime(resolve.import_record.namespace, "file")) { - this.runResolver(resolve.import_record, resolve.import_record.original_platform); + this.runResolver(resolve.import_record, resolve.import_record.original_target); return; } @@ -1190,7 +1190,7 @@ pub const BundleV2 = struct { .module_type = .unknown, .loader = loader, .tree_shaking = this.linker.options.tree_shaking, - .known_platform = resolve.import_record.original_platform, + .known_target = resolve.import_record.original_target, }; task.task.node.next = null; @@ -1299,7 +1299,7 @@ pub const BundleV2 = struct { Api.TransformOptions{ .define = if (config.define.count() > 0) config.define.toAPI() else null, .entry_points = config.entry_points.keys(), - .platform = config.target.toAPI(), + .target = config.target.toAPI(), .absolute_working_dir = if (config.dir.list.items.len > 0) config.dir.toOwnedSliceLeaky() else null, .inject = &.{}, .external = config.external.keys(), @@ -1311,9 +1311,9 @@ pub const BundleV2 = struct { ); bundler.options.jsx = config.jsx; - bundler.options.entry_names = config.names.entry_point.data; - bundler.options.chunk_names = config.names.chunk.data; - bundler.options.asset_names = config.names.asset.data; + bundler.options.entry_naming = config.names.entry_point.data; + bundler.options.chunk_naming = config.names.chunk.data; + bundler.options.asset_naming = config.names.asset.data; bundler.options.public_path = config.public_path.list.items; @@ -1437,7 +1437,7 @@ pub const BundleV2 = struct { import_record: *const ImportRecord, source_file: []const u8, import_record_index: u32, - original_platform: ?options.Platform, + original_target: ?options.Target, ) bool { if (this.plugins) |plugins| { if (plugins.hasAnyMatches(&import_record.path, false)) { @@ -1456,7 +1456,7 @@ pub const BundleV2 = struct { .source_file = source_file, .import_record_index = import_record_index, .importer_source_index = source_index, - .original_platform = original_platform orelse this.bundler.options.platform, + .original_target = original_target orelse this.bundler.options.target, }, }, this.completion.?, @@ -1707,7 +1707,7 @@ pub const ParseTask = struct { source_index: Index = Index.invalid, task: ThreadPoolLib.Task = .{ .callback = &callback }, tree_shaking: bool = false, - known_platform: ?options.Platform = null, + known_target: ?options.Target = null, module_type: options.ModuleType = .unknown, ctx: *BundleV2, @@ -1997,7 +1997,7 @@ pub const ParseTask = struct { }; const source_dir = file_path.sourceDir(); - const platform = use_directive.platform(task.known_platform orelse bundler.options.platform); + const target = use_directive.target(task.known_target orelse bundler.options.target); var resolve_queue = ResolveQueue.init(bun.default_allocator); // TODO: server ESM condition @@ -2008,12 +2008,12 @@ pub const ParseTask = struct { opts.legacy_transform_require_to_import = false; opts.can_import_from_bundle = false; opts.features.allow_runtime = !source.index.isRuntime(); - opts.features.dynamic_require = platform.isBun(); + opts.features.dynamic_require = target.isBun(); opts.warn_about_unbundled_modules = false; opts.macro_context = &this.data.macro_context; opts.bundle = true; opts.features.top_level_await = true; - opts.features.jsx_optimization_inline = platform.isBun() and (bundler.options.jsx_optimization_inline orelse !task.jsx.development); + opts.features.jsx_optimization_inline = target.isBun() and (bundler.options.jsx_optimization_inline orelse !task.jsx.development); opts.features.auto_import_jsx = task.jsx.parse and bundler.options.auto_import_jsx; opts.features.trim_unused_imports = loader.isTypeScript() or (bundler.options.trim_unused_imports orelse false); opts.features.inlining = bundler.options.minify_syntax; @@ -2034,7 +2034,7 @@ pub const ParseTask = struct { else try getEmptyAST(log, bundler, opts, allocator, source); - ast.platform = platform; + ast.target = target; if (ast.parts.len <= 1) { task.side_effects = _resolver.SideEffects.no_side_effects__empty_ast; } @@ -2066,7 +2066,7 @@ pub const ParseTask = struct { continue; } - if (platform.isBun()) { + if (target.isBun()) { if (JSC.HardcodedModule.Aliases.get(import_record.path.text)) |replacement| { import_record.path.text = replacement.path; import_record.tag = replacement.tag; @@ -2110,7 +2110,7 @@ pub const ParseTask = struct { } } - if (this.ctx.enqueueOnResolvePluginIfNeeded(source.index.get(), import_record, source.path.text, @truncate(u32, i), platform)) { + if (this.ctx.enqueueOnResolvePluginIfNeeded(source.index.get(), import_record, source.path.text, @truncate(u32, i), target)) { continue; } @@ -2128,7 +2128,7 @@ pub const ParseTask = struct { if (!import_record.handles_import_errors) { last_error = err; if (isPackagePath(import_record.path.text)) { - if (platform.isWebLike() and options.ExternalModules.isNodeBuiltin(import_record.path.text)) { + if (target.isWebLike() and options.ExternalModules.isNodeBuiltin(import_record.path.text)) { try addError( log, &source, @@ -2220,9 +2220,9 @@ pub const ParseTask = struct { resolve_task.secondary_path_for_commonjs_interop = secondary_path_to_copy; if (use_directive != .none) { - resolve_task.known_platform = platform; - } else if (task.known_platform) |known_platform| { - resolve_task.known_platform = known_platform; + resolve_task.known_target = target; + } else if (task.known_target) |known_target| { + resolve_task.known_target = known_target; } resolve_task.jsx.development = task.jsx.development; @@ -2244,7 +2244,7 @@ pub const ParseTask = struct { std.debug.assert(use_directive == .none or bundler.options.react_server_components); step.* = .resolve; - ast.platform = platform; + ast.target = target; return Result.Success{ .ast = ast, @@ -3389,16 +3389,16 @@ const LinkerContext = struct { if (chunk.entry_point.is_entry_point) { chunk.template = PathTemplate.file; - if (this.resolver.opts.entry_names.len > 0) - chunk.template.data = this.resolver.opts.entry_names; + if (this.resolver.opts.entry_naming.len > 0) + chunk.template.data = this.resolver.opts.entry_naming; const pathname = Fs.PathName.init(this.graph.entry_points.items(.output_path)[chunk.entry_point.entry_point_id].slice()); chunk.template.placeholder.name = pathname.base; chunk.template.placeholder.ext = "js"; chunk.template.placeholder.dir = pathname.dir; } else { chunk.template = PathTemplate.chunk; - if (this.resolver.opts.chunk_names.len > 0) - chunk.template.data = this.resolver.opts.chunk_names; + if (this.resolver.opts.chunk_naming.len > 0) + chunk.template.data = this.resolver.opts.chunk_naming; } } @@ -5872,7 +5872,7 @@ const LinkerContext = struct { cross_chunk_prefix = js_printer.print( allocator, - c.resolver.opts.platform, + c.resolver.opts.target, print_options, cross_chunk_import_records.slice(), &[_]js_ast.Part{ @@ -5882,7 +5882,7 @@ const LinkerContext = struct { ).result.code; cross_chunk_suffix = js_printer.print( allocator, - c.resolver.opts.platform, + c.resolver.opts.target, print_options, &.{}, &[_]js_ast.Part{ @@ -5935,7 +5935,7 @@ const LinkerContext = struct { } } - if (chunk.entry_point.is_entry_point and ctx.c.graph.ast.items(.platform)[chunk.entry_point.source_index].isBun()) { + if (chunk.entry_point.is_entry_point and ctx.c.graph.ast.items(.target)[chunk.entry_point.source_index].isBun()) { j.push("// @bun\n"); } @@ -6488,7 +6488,7 @@ const LinkerContext = struct { .javascript = .{ .result = js_printer.print( allocator, - c.resolver.opts.platform, + c.resolver.opts.target, print_options, ast.import_records.slice(), &[_]js_ast.Part{ @@ -7758,7 +7758,7 @@ const LinkerContext = struct { return js_printer.printWithWriter( *js_printer.BufferPrinter, &printer, - ast.platform, + ast.target, print_options, ast.import_records.slice(), parts_to_print, diff --git a/src/cli.zig b/src/cli.zig index 86dce4dca..53b421282 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -82,11 +82,11 @@ const LoaderMatcher = strings.ExactSizeMatcher(4); const ColonListType = @import("./cli/colon_list_type.zig").ColonListType; pub const LoaderColonList = ColonListType(Api.Loader, Arguments.loader_resolver); pub const DefineColonList = ColonListType(string, Arguments.noop_resolver); -fn invalidPlatform(diag: *clap.Diagnostic, _platform: []const u8) noreturn { +fn invalidTarget(diag: *clap.Diagnostic, _target: []const u8) noreturn { @setCold(true); - diag.name.long = "--platform"; - diag.arg = _platform; - diag.report(Output.errorWriter(), error.InvalidPlatform) catch {}; + diag.name.long = "--target"; + diag.arg = _target; + diag.report(Output.errorWriter(), error.InvalidTarget) catch {}; std.process.exit(1); } pub const Arguments = struct { @@ -144,24 +144,23 @@ pub const Arguments = struct { pub const ParamType = clap.Param(clap.Help); const shared_public_params = [_]ParamType{ + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, clap.parseParam("-b, --bun Force a script or package to use Bun.js instead of Node.js (via symlinking node)") catch unreachable, clap.parseParam("--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, - clap.parseParam("-c, --config <PATH>? Config file to load bun from (e.g. -c bunfig.toml") catch unreachable, - clap.parseParam("--extension-order <STR>... defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable, + clap.parseParam("-c, --config <PATH>? Config file to load bun from (e.g. -c bunfig.toml") catch unreachable, + clap.parseParam("--extension-order <STR>... Defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable, clap.parseParam("--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable, clap.parseParam("--jsx-fragment <STR> Changes the function called when compiling JSX fragments") catch unreachable, clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable, clap.parseParam("--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime") catch unreachable, clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable, clap.parseParam("-r, --preload <STR>... Import a module before other modules are loaded") catch unreachable, - clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable, - clap.parseParam("--no-summary Don't print a summary (when generating .bun") catch unreachable, - clap.parseParam("-v, --version Print version and exit") catch unreachable, - clap.parseParam("--platform <STR> \"bun\" or \"browser\" or \"node\", used when building or bundling") catch unreachable, + clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --target dependent") catch unreachable, + clap.parseParam("--no-summary Don't print a summary (when generating .bun)") catch unreachable, + clap.parseParam("-v, --version Print version and exit") catch unreachable, clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable, clap.parseParam("-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\". Values are parsed as JSON.") catch unreachable, clap.parseParam("-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable, - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi") catch unreachable, clap.parseParam("-u, --origin <STR> Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable, clap.parseParam("-p, --port <STR> Port to serve bun's dev server on. Default: \"3000\"") catch unreachable, @@ -169,6 +168,7 @@ pub const Arguments = struct { clap.parseParam("--minify-syntax Minify syntax and inline data (experimental)") catch unreachable, clap.parseParam("--minify-whitespace Minify whitespace (experimental)") catch unreachable, clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable, + clap.parseParam("--target <STR> The intended execution environment for the bundle. \"browser\", \"bun\" or \"node\"") catch unreachable, clap.parseParam("<POS>... ") catch unreachable, }; @@ -205,13 +205,17 @@ pub const Arguments = struct { pub const params = public_params ++ debug_params; const build_only_params = [_]ParamType{ - clap.parseParam("--sourcemap <STR>? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, clap.parseParam("--outdir <STR> Default to \"dist\" if multiple files") catch unreachable, - clap.parseParam("--entry-names <STR> Pattern to use for entry point filenames") catch unreachable, clap.parseParam("--outfile <STR> Write to a file") catch unreachable, - clap.parseParam("--server-components Enable React Server Components (experimental)") catch unreachable, - clap.parseParam("--splitting Split up code!") catch unreachable, - clap.parseParam("--transform Do not bundle") catch unreachable, + clap.parseParam("--splitting Enable code splitting") catch unreachable, + // clap.parseParam("--manifest <STR> Write JSON manifest") catch unreachable, + // clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable, + clap.parseParam("--sourcemap <STR>? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, + clap.parseParam("--entry-naming <STR> Customize entry point filenames. Defaults to \"[dir]/[name].[ext]\"") catch unreachable, + clap.parseParam("--chunk-naming <STR> Customize chunk filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable, + clap.parseParam("--asset-naming <STR> Customize asset filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable, + clap.parseParam("--server-components Enable React Server Components (experimental)") catch unreachable, + clap.parseParam("--transform Single file transform, do not bundle") catch unreachable, }; // TODO: update test completions @@ -500,8 +504,16 @@ pub const Arguments = struct { ctx.bundler_options.code_splitting = true; } - if (args.option("--entry-names")) |entry_names| { - ctx.bundler_options.entry_names = entry_names; + if (args.option("--entry-naming")) |entry_naming| { + ctx.bundler_options.entry_naming = try strings.concat(allocator, &.{ "./", entry_naming }); + } + + if (args.option("--chunk-naming")) |chunk_naming| { + ctx.bundler_options.chunk_naming = try strings.concat(allocator, &.{ "./", chunk_naming }); + } + + if (args.option("--asset-naming")) |asset_naming| { + ctx.bundler_options.asset_naming = try strings.concat(allocator, &.{ "./", asset_naming }); } if (comptime FeatureFlags.react_server_components) { @@ -668,18 +680,18 @@ pub const Arguments = struct { else => {}, } - const PlatformMatcher = strings.ExactSizeMatcher(8); + const TargetMatcher = strings.ExactSizeMatcher(8); - if (args.option("--platform")) |_platform| { - opts.platform = opts.platform orelse switch (PlatformMatcher.match(_platform)) { - PlatformMatcher.case("browser") => Api.Platform.browser, - PlatformMatcher.case("node") => Api.Platform.node, - PlatformMatcher.case("macro") => if (cmd == .BuildCommand) Api.Platform.bun_macro else Api.Platform.bun, - PlatformMatcher.case("bun") => Api.Platform.bun, - else => invalidPlatform(&diag, _platform), + if (args.option("--target")) |_target| { + opts.target = opts.target orelse switch (TargetMatcher.match(_target)) { + TargetMatcher.case("browser") => Api.Target.browser, + TargetMatcher.case("node") => Api.Target.node, + TargetMatcher.case("macro") => if (cmd == .BuildCommand) Api.Target.bun_macro else Api.Target.bun, + TargetMatcher.case("bun") => Api.Target.bun, + else => invalidTarget(&diag, _target), }; - ctx.debug.run_in_bun = opts.platform.? == .bun; + ctx.debug.run_in_bun = opts.target.? == .bun; } ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun; @@ -922,7 +934,9 @@ pub const Command = struct { pub const BundlerOptions = struct { outdir: []const u8 = "", outfile: []const u8 = "", - entry_names: []const u8 = "./[name].[ext]", + entry_naming: []const u8 = "./[name].[ext]", + chunk_naming: []const u8 = "./[name]-[hash].[ext]", + asset_naming: []const u8 = "./[name]-[hash].[ext]", react_server_components: bool = false, code_splitting: bool = false, transform_only: bool = false, diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig index 196d43f23..0518c7b5a 100644 --- a/src/cli/build_command.zig +++ b/src/cli/build_command.zig @@ -44,8 +44,12 @@ pub const BuildCommand = struct { estimated_input_lines_of_code_ = 0; var this_bundler = try bundler.Bundler.init(allocator, log, ctx.args, null, null); - this_bundler.options.entry_names = ctx.bundler_options.entry_names; - this_bundler.resolver.opts.entry_names = ctx.bundler_options.entry_names; + this_bundler.options.entry_naming = ctx.bundler_options.entry_naming; + this_bundler.options.chunk_naming = ctx.bundler_options.chunk_naming; + this_bundler.options.asset_naming = ctx.bundler_options.asset_naming; + this_bundler.resolver.opts.entry_naming = ctx.bundler_options.entry_naming; + this_bundler.resolver.opts.chunk_naming = ctx.bundler_options.chunk_naming; + this_bundler.resolver.opts.asset_naming = ctx.bundler_options.asset_naming; this_bundler.options.output_dir = ctx.bundler_options.outdir; this_bundler.resolver.opts.output_dir = ctx.bundler_options.outdir; this_bundler.options.react_server_components = ctx.bundler_options.react_server_components; diff --git a/src/js_ast.zig b/src/js_ast.zig index d2cb467d1..410539761 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -5730,7 +5730,7 @@ pub const Ast = struct { redirect_import_record_index: ?u32 = null, /// Only populated when bundling - platform: bun.options.Platform = .browser, + target: bun.options.Target = .browser, const_values: ConstValuesMap = .{}, @@ -9698,7 +9698,7 @@ pub const UseDirective = enum { return .none; } - pub fn platform(this: UseDirective, default: bun.options.Platform) bun.options.Platform { + pub fn target(this: UseDirective, default: bun.options.Target) bun.options.Target { return switch (this) { .none => default, .@"use client" => .browser, @@ -9921,4 +9921,3 @@ pub const GlobalStoreHandle = struct { // Stmt | 192 // STry | 384 // -- ESBuild bit sizes - diff --git a/src/js_printer.zig b/src/js_printer.zig index 2dc1b7b74..8832ffb9d 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -5762,7 +5762,7 @@ pub fn printJSON( pub fn print( allocator: std.mem.Allocator, - platform: options.Platform, + target: options.Target, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, @@ -5774,7 +5774,7 @@ pub fn print( return printWithWriter( *BufferPrinter, &buffer_printer, - platform, + target, opts, import_records, parts, @@ -5785,17 +5785,17 @@ pub fn print( pub fn printWithWriter( comptime Writer: type, _writer: Writer, - platform: options.Platform, + target: options.Target, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, renamer: bun.renamer.Renamer, ) PrintResult { - return switch (platform.isBun()) { - inline else => |is_bun_platform| printWithWriterAndPlatform( + return switch (target.isBun()) { + inline else => |is_bun| printWithWriterAndPlatform( Writer, _writer, - is_bun_platform, + is_bun, opts, import_records, parts, @@ -5808,7 +5808,7 @@ pub fn printWithWriter( pub fn printWithWriterAndPlatform( comptime Writer: type, _writer: Writer, - comptime is_bun_platform: bool, + comptime is_bun: bool, opts: Options, import_records: []const ImportRecord, parts: []const js_ast.Part, @@ -5818,7 +5818,7 @@ pub fn printWithWriterAndPlatform( false, Writer, false, - is_bun_platform, + is_bun, false, false, ); diff --git a/src/linker.zig b/src/linker.zig index 27a1db2b6..6c5e0e50b 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -172,7 +172,7 @@ pub const Linker = struct { } pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker, origin: URL) string { - if (this.options.platform.isBun()) return "/node_modules.server.bun"; + if (this.options.target.isBun()) return "/node_modules.server.bun"; return std.fmt.allocPrint(this.allocator, "{s}://{}{s}", .{ origin.displayProtocol(), origin.displayHost(), this.options.node_modules_bundle.?.bundle.import_from_name }) catch unreachable; } @@ -334,7 +334,7 @@ pub const Linker = struct { import_record.range.loc, if (is_bun) JSC.JSGlobalObject.BunPluginTarget.bun - else if (linker.options.platform == .browser) + else if (linker.options.target == .browser) JSC.JSGlobalObject.BunPluginTarget.browser else JSC.JSGlobalObject.BunPluginTarget.node, @@ -731,12 +731,12 @@ pub const Linker = struct { had_resolve_errors = true; 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)) { + if (linker.options.target.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)", + "Could not resolve: \"{s}\". Try setting --target=\"node\"", .{import_record.path.text}, import_record.kind, err, @@ -998,7 +998,7 @@ pub const Linker = struct { import_record.path = try linker.generateImportPath( source_dir, - if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform.isNotBun()) path.pretty else path.text, + if (path.is_symlink and import_path_format == .absolute_url and linker.options.target.isNotBun()) path.pretty else path.text, loader == .file or loader == .wasm, path.namespace, origin, @@ -1026,7 +1026,7 @@ pub const Linker = struct { // it's more complicated // loader plugins could be executed between when this is called and the import is evaluated // but we want to preserve the semantics of "file" returning import paths for compatibiltiy with frontend frameworkss - if (!linker.options.platform.isBun()) { + if (!linker.options.target.isBun()) { import_record.print_mode = .import_path; } }, diff --git a/src/options.zig b/src/options.zig index 0ad92a5e1..3cb5c333c 100644 --- a/src/options.zig +++ b/src/options.zig @@ -112,7 +112,7 @@ pub const ExternalModules = struct { cwd: string, externals: []const string, log: *logger.Log, - platform: Platform, + target: Target, ) ExternalModules { var result = ExternalModules{ .node_modules = std.BufSet.init(allocator), @@ -120,7 +120,7 @@ pub const ExternalModules = struct { .patterns = default_wildcard_patterns[0..], }; - switch (platform) { + switch (target) { .node => { // TODO: fix this stupid copy result.node_modules.hash_map.ensureTotalCapacity(NodeBuiltinPatterns.len) catch unreachable; @@ -377,42 +377,37 @@ pub const ModuleType = enum { }); }; -pub const Platform = enum { - neutral, +pub const Target = enum { browser, bun, bun_macro, node, pub const Map = ComptimeStringMap( - Platform, + Target, .{ .{ - "neutral", - Platform.neutral, - }, - .{ "browser", - Platform.browser, + Target.browser, }, .{ "bun", - Platform.bun, + Target.bun, }, .{ "bun_macro", - Platform.bun_macro, + Target.bun_macro, }, .{ "node", - Platform.node, + Target.node, }, }, ); - pub fn fromJS(global: *JSC.JSGlobalObject, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Platform { + pub fn fromJS(global: *JSC.JSGlobalObject, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Target { if (!value.jsType().isStringLike()) { - JSC.throwInvalidArguments("platform must be a string", .{}, global, exception); + JSC.throwInvalidArguments("target must be a string", .{}, global, exception); return null; } @@ -424,60 +419,58 @@ pub const Platform = enum { const Eight = strings.ExactSizeMatcher(8); return switch (Eight.match(slice)) { - Eight.case("deno"), Eight.case("browser") => Platform.browser, - Eight.case("bun") => Platform.bun, - Eight.case("macro") => Platform.bun_macro, - Eight.case("node") => Platform.node, - Eight.case("neutral") => Platform.neutral, + Eight.case("deno"), Eight.case("browser") => Target.browser, + Eight.case("bun") => Target.bun, + Eight.case("macro") => Target.bun_macro, + Eight.case("node") => Target.node, else => { - JSC.throwInvalidArguments("platform must be one of: deno, browser, bun, macro, node, neutral", .{}, global, exception); + JSC.throwInvalidArguments("target must be one of: deno, browser, bun, macro, node", .{}, global, exception); return null; }, }; } - pub fn toAPI(this: Platform) Api.Platform { + pub fn toAPI(this: Target) Api.Target { return switch (this) { .node => .node, .browser => .browser, .bun => .bun, .bun_macro => .bun_macro, - else => ._none, }; } - pub inline fn isServerSide(this: Platform) bool { + pub inline fn isServerSide(this: Target) bool { return switch (this) { .bun_macro, .node, .bun => true, else => false, }; } - pub inline fn isBun(this: Platform) bool { + pub inline fn isBun(this: Target) bool { return switch (this) { .bun_macro, .bun => true, else => false, }; } - pub inline fn isNotBun(this: Platform) bool { + pub inline fn isNotBun(this: Target) bool { return switch (this) { .bun_macro, .bun => false, else => true, }; } - pub inline fn isClient(this: Platform) bool { + pub inline fn isClient(this: Target) bool { return switch (this) { .bun_macro, .bun => false, else => true, }; } - pub inline fn supportsBrowserField(this: Platform) bool { + pub inline fn supportsBrowserField(this: Target) bool { return switch (this) { - .neutral, .browser => true, + .browser => true, else => false, }; } @@ -485,17 +478,16 @@ pub const Platform = enum { const browser_define_value_true = "true"; const browser_define_value_false = "false"; - pub inline fn processBrowserDefineValue(this: Platform) ?string { + pub inline fn processBrowserDefineValue(this: Target) ?string { return switch (this) { .browser => browser_define_value_true, .bun_macro, .bun, .node => browser_define_value_false, - else => null, }; } - pub inline fn isWebLike(platform: Platform) bool { - return switch (platform) { - .neutral, .browser => true, + pub inline fn isWebLike(target: Target) bool { + return switch (target) { + .browser => true, else => false, }; } @@ -512,13 +504,13 @@ pub const Platform = enum { }; }; - pub fn outExtensions(platform: Platform, allocator: std.mem.Allocator) bun.StringHashMap(string) { + pub fn outExtensions(target: Target, allocator: std.mem.Allocator) bun.StringHashMap(string) { var exts = bun.StringHashMap(string).init(allocator); const js = Extensions.Out.JavaScript[0]; const mjs = Extensions.Out.JavaScript[1]; - if (platform == .node) { + if (target == .node) { exts.ensureTotalCapacity(Extensions.In.JavaScript.len * 2) catch unreachable; for (Extensions.In.JavaScript) |ext| { exts.put(ext, mjs) catch unreachable; @@ -535,8 +527,8 @@ pub const Platform = enum { return exts; } - pub fn from(plat: ?api.Api.Platform) Platform { - return switch (plat orelse api.Api.Platform._none) { + pub fn from(plat: ?api.Api.Target) Target { + return switch (plat orelse api.Api.Target._none) { .node => .node, .browser => .browser, .bun => .bun, @@ -555,8 +547,8 @@ pub const Platform = enum { // Older packages might use jsnext:main in place of module "jsnext:main", }; - pub const DefaultMainFields: std.EnumArray(Platform, []const string) = brk: { - var array = std.EnumArray(Platform, []const string).initUndefined(); + pub const DefaultMainFields: std.EnumArray(Target, []const string) = brk: { + var array = std.EnumArray(Target, []const string).initUndefined(); // Note that this means if a package specifies "module" and "main", the ES6 // module will not be selected. This means tree shaking will not work when @@ -572,7 +564,7 @@ pub const Platform = enum { // This is unfortunate but it's a problem on the side of those packages. // They won't work correctly with other popular bundlers (with node as a target) anyway. var list = [_]string{ MAIN_FIELD_NAMES[2], MAIN_FIELD_NAMES[1] }; - array.set(Platform.node, &list); + array.set(Target.node, &list); // Note that this means if a package specifies "main", "module", and // "browser" then "browser" will win out over "module". This is the @@ -584,24 +576,23 @@ pub const Platform = enum { var listc = [_]string{ MAIN_FIELD_NAMES[0], MAIN_FIELD_NAMES[1], MAIN_FIELD_NAMES[3], MAIN_FIELD_NAMES[2] }; var listd = [_]string{ MAIN_FIELD_NAMES[1], MAIN_FIELD_NAMES[2], MAIN_FIELD_NAMES[3] }; - array.set(Platform.browser, &listc); - array.set(Platform.bun, &listd); - array.set(Platform.bun_macro, &listd); + array.set(Target.browser, &listc); + array.set(Target.bun, &listd); + array.set(Target.bun_macro, &listd); // Original comment: - // The neutral platform is for people that don't want esbuild to try to + // The neutral target is for people that don't want esbuild to try to // pick good defaults for their platform. In that case, the list of main // fields is empty by default. You must explicitly configure it yourself. - - array.set(Platform.neutral, &listc); + // array.set(Target.neutral, &listc); break :brk array; }; - pub const DefaultConditions: std.EnumArray(Platform, []const string) = brk: { - var array = std.EnumArray(Platform, []const string).initUndefined(); + pub const DefaultConditions: std.EnumArray(Target, []const string) = brk: { + var array = std.EnumArray(Target, []const string).initUndefined(); - array.set(Platform.node, &[_]string{ + array.set(Target.node, &[_]string{ "node", "module", }); @@ -610,9 +601,9 @@ pub const Platform = enum { "browser", "module", }; - array.set(Platform.browser, &listc); + array.set(Target.browser, &listc); array.set( - Platform.bun, + Target.bun, &[_]string{ "bun", "worker", @@ -623,7 +614,7 @@ pub const Platform = enum { }, ); array.set( - Platform.bun_macro, + Target.bun_macro, &[_]string{ "bun", "worker", @@ -633,14 +624,13 @@ pub const Platform = enum { "browser", }, ); - // array.set(Platform.bun_macro, [_]string{ "bun_macro", "browser", "default", },); + // array.set(Target.bun_macro, [_]string{ "bun_macro", "browser", "default", },); // Original comment: - // The neutral platform is for people that don't want esbuild to try to + // The neutral target is for people that don't want esbuild to try to // pick good defaults for their platform. In that case, the list of main // fields is empty by default. You must explicitly configure it yourself. - - array.set(Platform.neutral, &listc); + // array.set(Target.neutral, &listc); break :brk array; }; @@ -965,7 +955,7 @@ pub const JSX = struct { &pragma.import_source.development, &[_]string{ pragma.package_name, - "jsx-dev-runtime", + "/jsx-dev-runtime", }, &.{ Defaults.ImportSourceDev, @@ -977,7 +967,7 @@ pub const JSX = struct { &pragma.import_source.production, &[_]string{ pragma.package_name, - "jsx-runtime", + "/jsx-runtime", }, &.{ Defaults.ImportSource, @@ -1093,8 +1083,7 @@ pub const DefaultUserDefines = struct { pub const Key = "process.env.NODE_ENV"; pub const Value = "\"development\""; }; - - pub const PlatformDefine = struct { + pub const ProcessBrowserDefine = struct { pub const Key = "process.browser"; pub const Value = []string{ "false", "true" }; }; @@ -1105,7 +1094,7 @@ pub fn definesFromTransformOptions( log: *logger.Log, _input_define: ?Api.StringMap, hmr: bool, - platform: Platform, + target: Target, loader: ?*DotEnv.Loader, framework_env: ?*const Env, NODE_ENV: ?string, @@ -1148,36 +1137,38 @@ pub fn definesFromTransformOptions( } } - if (NODE_ENV) |node_env| { - if (node_env.len > 0) { - var quoted_node_env: string = ""; - if ((strings.startsWithChar(node_env, '"') and strings.endsWithChar(node_env, '"')) or - (strings.startsWithChar(node_env, '\'') and strings.endsWithChar(node_env, '\''))) - { - quoted_node_env = node_env; - } else { + var quoted_node_env: string = brk: { + if (NODE_ENV) |node_env| { + if (node_env.len > 0) { + if ((strings.startsWithChar(node_env, '"') and strings.endsWithChar(node_env, '"')) or + (strings.startsWithChar(node_env, '\'') and strings.endsWithChar(node_env, '\''))) + { + break :brk node_env; + } + // avoid allocating if we can if (strings.eqlComptime(node_env, "production")) { - quoted_node_env = "\"production\""; + break :brk "\"production\""; } else if (strings.eqlComptime(node_env, "development")) { - quoted_node_env = "\"development\""; + break :brk "\"development\""; } else if (strings.eqlComptime(node_env, "test")) { - quoted_node_env = "\"test\""; + break :brk "\"test\""; } else { - quoted_node_env = try std.fmt.allocPrint(allocator, "\"{s}\"", .{node_env}); + break :brk try std.fmt.allocPrint(allocator, "\"{s}\"", .{node_env}); } } - - _ = try user_defines.getOrPutValue( - "process.env.NODE_ENV", - quoted_node_env, - ); - _ = try user_defines.getOrPutValue( - "process.env.BUN_ENV", - quoted_node_env, - ); } - } + break :brk "\"development\""; + }; + + _ = try user_defines.getOrPutValue( + "process.env.NODE_ENV", + quoted_node_env, + ); + _ = try user_defines.getOrPutValue( + "process.env.BUN_ENV", + quoted_node_env, + ); if (hmr) { try user_defines.put(DefaultUserDefines.HotModuleReloading.Key, DefaultUserDefines.HotModuleReloading.Value); @@ -1185,11 +1176,11 @@ pub fn definesFromTransformOptions( // Automatically set `process.browser` to `true` for browsers and false for node+js // This enables some extra dead code elimination - if (platform.processBrowserDefineValue()) |value| { - _ = try user_defines.getOrPutValue(DefaultUserDefines.PlatformDefine.Key, value); + if (target.processBrowserDefineValue()) |value| { + _ = try user_defines.getOrPutValue(DefaultUserDefines.ProcessBrowserDefine.Key, value); } - if (platform.isBun()) { + if (target.isBun()) { if (!user_defines.contains("window")) { _ = try environment_defines.getOrPutValue("window", .{ .valueless = true, @@ -1222,11 +1213,11 @@ const default_loader_ext = [_]string{ ".txt", ".text", }; -pub fn loadersFromTransformOptions(allocator: std.mem.Allocator, _loaders: ?Api.LoaderMap, platform: Platform) !bun.StringArrayHashMap(Loader) { +pub fn loadersFromTransformOptions(allocator: std.mem.Allocator, _loaders: ?Api.LoaderMap, target: Target) !bun.StringArrayHashMap(Loader) { var input_loaders = _loaders orelse std.mem.zeroes(Api.LoaderMap); var loader_values = try allocator.alloc(Loader, input_loaders.loaders.len); - if (platform.isBun()) { + if (target.isBun()) { for (loader_values, 0..) |_, i| { const loader = switch (input_loaders.loaders[i]) { .jsx => Loader.jsx, @@ -1274,7 +1265,7 @@ pub fn loadersFromTransformOptions(allocator: std.mem.Allocator, _loaders: ?Api. _ = try loaders.getOrPutValue(ext, defaultLoaders.get(ext).?); } - if (platform.isBun()) { + if (target.isBun()) { inline for (default_loader_ext_bun) |ext| { _ = try loaders.getOrPutValue(ext, defaultLoaders.get(ext).?); } @@ -1379,14 +1370,14 @@ pub const BundleOptions = struct { resolve_mode: api.Api.ResolveMode, tsconfig_override: ?string = null, - platform: Platform = Platform.browser, - main_fields: []const string = Platform.DefaultMainFields.get(Platform.browser), + target: Target = Target.browser, + main_fields: []const string = Target.DefaultMainFields.get(Target.browser), log: *logger.Log, external: ExternalModules = ExternalModules{}, entry_points: []const string, - entry_names: []const u8 = "", - asset_names: []const u8 = "", - chunk_names: []const u8 = "", + entry_naming: []const u8 = "", + asset_naming: []const u8 = "", + chunk_naming: []const u8 = "", public_path: []const u8 = "", extension_order: []const string = &Defaults.ExtensionOrder, esm_extension_order: []const string = &Defaults.ModuleExtensionOrder, @@ -1446,8 +1437,8 @@ pub const BundleOptions = struct { }; pub inline fn cssImportBehavior(this: *const BundleOptions) Api.CssInJsBehavior { - switch (this.platform) { - .neutral, .browser => { + switch (this.target) { + .browser => { if (this.framework) |framework| { return framework.client_css_in_js; } @@ -1471,7 +1462,7 @@ pub const BundleOptions = struct { this.log, this.transform_options.define, this.transform_options.serve orelse false, - this.platform, + this.target, loader_, env, if (loader_) |e| @@ -1540,9 +1531,9 @@ pub const BundleOptions = struct { .log = log, .resolve_mode = transform.resolve orelse .dev, .define = undefined, - .loaders = try loadersFromTransformOptions(allocator, transform.loaders, Platform.from(transform.platform)), + .loaders = try loadersFromTransformOptions(allocator, transform.loaders, Target.from(transform.target)), .output_dir = transform.output_dir orelse "out", - .platform = Platform.from(transform.platform), + .target = Target.from(transform.target), .write = transform.write orelse false, .external = undefined, .entry_points = transform.entry_points, @@ -1566,12 +1557,12 @@ pub const BundleOptions = struct { opts.extension_order = transform.extension_order; } - if (transform.platform) |plat| { - opts.platform = Platform.from(plat); - opts.main_fields = Platform.DefaultMainFields.get(opts.platform); + if (transform.target) |t| { + opts.target = Target.from(t); + opts.main_fields = Target.DefaultMainFields.get(opts.target); } - opts.conditions = try ESMConditions.init(allocator, Platform.DefaultConditions.get(opts.platform)); + opts.conditions = try ESMConditions.init(allocator, Target.DefaultConditions.get(opts.target)); if (transform.serve orelse false) { // When we're serving, we need some kind of URL. @@ -1596,7 +1587,7 @@ pub const BundleOptions = struct { } } - switch (opts.platform) { + switch (opts.target) { .node => { opts.import_path_format = .relative; opts.allow_runtime = false; @@ -1695,8 +1686,8 @@ pub const BundleOptions = struct { if (opts.framework == null and is_generating_bundle) opts.env.behavior = .load_all; - opts.external = ExternalModules.init(allocator, &fs.fs, fs.top_level_dir, transform.external, log, opts.platform); - opts.out_extensions = opts.platform.outExtensions(allocator); + opts.external = ExternalModules.init(allocator, &fs.fs, fs.top_level_dir, transform.external, log, opts.target); + opts.out_extensions = opts.target.outExtensions(allocator); if (transform.serve orelse false) { opts.preserve_extensions = true; @@ -1839,7 +1830,7 @@ pub const BundleOptions = struct { if (Environment.isWindows and opts.routes.static_dir_handle != null) { opts.routes.static_dir_handle.?.close(); } - opts.hot_module_reloading = opts.platform.isWebLike(); + opts.hot_module_reloading = opts.target.isWebLike(); if (transform.disable_hmr orelse false) opts.hot_module_reloading = false; @@ -1849,7 +1840,7 @@ pub const BundleOptions = struct { opts.sourcemap = SourceMapOption.fromApi(transform.source_map orelse Api.SourceMapMode._none); } - opts.tree_shaking = opts.serve or opts.platform.isBun() or opts.production or is_generating_bundle; + opts.tree_shaking = opts.serve or opts.target.isBun() or opts.production or is_generating_bundle; opts.inlining = opts.tree_shaking; if (opts.inlining) opts.minify_syntax = true; @@ -1863,7 +1854,7 @@ pub const BundleOptions = struct { opts.output_dir = try fs.getFdPath(opts.output_dir_handle.?.fd); } - opts.polyfill_node_globals = opts.platform != .node; + opts.polyfill_node_globals = opts.target != .node; Analytics.Features.framework = Analytics.Features.framework or opts.framework != null; Analytics.Features.filesystem_router = Analytics.Features.filesystem_router or opts.routes.routes_enabled; @@ -1871,7 +1862,7 @@ pub const BundleOptions = struct { Analytics.Features.public_folder = Analytics.Features.public_folder or opts.routes.static_dir_enabled; Analytics.Features.bun_bun = Analytics.Features.bun_bun or transform.node_modules_bundle_path != null; Analytics.Features.bunjs = Analytics.Features.bunjs or transform.node_modules_bundle_path_server != null; - Analytics.Features.macros = Analytics.Features.macros or opts.platform == .bun_macro; + Analytics.Features.macros = Analytics.Features.macros or opts.target == .bun_macro; Analytics.Features.external = Analytics.Features.external or transform.external.len > 0; Analytics.Features.single_page_app_routing = Analytics.Features.single_page_app_routing or opts.routes.single_page_app_routing; return opts; @@ -1908,8 +1899,8 @@ pub const TransformOptions = struct { resolve_paths: bool = false, tsconfig_override: ?string = null, - platform: Platform = Platform.browser, - main_fields: []string = Platform.DefaultMainFields.get(Platform.browser), + target: Target = Target.browser, + main_fields: []string = Target.DefaultMainFields.get(Target.browser), pub fn initUncached(allocator: std.mem.Allocator, entryPointName: string, code: string) !TransformOptions { assert(entryPointName.len > 0); @@ -1940,7 +1931,7 @@ pub const TransformOptions = struct { .define = define, .loader = loader, .resolve_dir = entryPoint.path.name.dir, - .main_fields = Platform.DefaultMainFields.get(Platform.browser), + .main_fields = Target.DefaultMainFields.get(Target.browser), .jsx = if (Loader.isJSX(loader)) JSX.Pragma{} else null, }; } @@ -1958,7 +1949,7 @@ pub const OutputFile = struct { mtime: ?i128 = null, // Depending on: - // - The platform + // - The target // - The number of open file handles // - Whether or not a file of the same name exists // We may use a different system call @@ -2389,9 +2380,9 @@ pub const Framework = struct { pub const fallback_html: string = @embedFile("./fallback.html"); - pub fn platformEntryPoint(this: *const Framework, platform: Platform) ?*const EntryPoint { - const entry: *const EntryPoint = switch (platform) { - .neutral, .browser => &this.client, + pub fn platformEntryPoint(this: *const Framework, target: Target) ?*const EntryPoint { + const entry: *const EntryPoint = switch (target) { + .browser => &this.client, .bun => &this.server, .node => return null, }; diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index 75b7a9e00..63bc5b20b 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -708,7 +708,7 @@ pub const PackageJSON = struct { } // Read the "browser" property, but only when targeting the browser - if (r.opts.platform.supportsBrowserField()) { + if (r.opts.target.supportsBrowserField()) { // We both want the ability to have the option of CJS vs. ESM and the // option of having node vs. browser. The way to do this is to use the // object literal form of the "browser" field like this: diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 61717ada9..48c6b2889 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -570,7 +570,7 @@ pub const Resolver = struct { .node_module_bundle = opts.node_modules_bundle, .log = log, .extension_order = opts.extension_order, - .care_about_browser_field = opts.platform.isWebLike(), + .care_about_browser_field = opts.target.isWebLike(), }; } @@ -3170,7 +3170,7 @@ pub const Resolver = struct { const main_field_values = pkg_json.main_fields; const main_field_keys = r.opts.main_fields; // TODO: check this works right. Not sure this will really work. - const auto_main = r.opts.main_fields.ptr == options.Platform.DefaultMainFields.get(r.opts.platform).ptr; + const auto_main = r.opts.main_fields.ptr == options.Target.DefaultMainFields.get(r.opts.target).ptr; if (r.debug_logs) |*debug| { debug.addNoteFmt("Searching for main fields in \"{s}\"", .{pkg_json.source.path.text}); diff --git a/src/router.zig b/src/router.zig index 5b4c56722..eab4b5351 100644 --- a/src/router.zig +++ b/src/router.zig @@ -963,7 +963,7 @@ pub const Test = struct { var opts = Options.BundleOptions{ .resolve_mode = .lazy, - .platform = .browser, + .target = .browser, .loaders = undefined, .define = undefined, .log = &logger, @@ -1020,7 +1020,7 @@ pub const Test = struct { var opts = Options.BundleOptions{ .resolve_mode = .lazy, - .platform = .browser, + .target = .browser, .loaders = undefined, .define = undefined, .log = &logger, diff --git a/test/bundler/bundler_browser.test.ts b/test/bundler/bundler_browser.test.ts index 422e860b5..be2e5a43e 100644 --- a/test/bundler/bundler_browser.test.ts +++ b/test/bundler/bundler_browser.test.ts @@ -52,7 +52,7 @@ describe("bundler", () => { console.log(typeof readFileSync); `, }, - platform: "browser", + target: "browser", run: { stdout: "function\nfunction\nundefined", }, @@ -137,7 +137,7 @@ describe("bundler", () => { console.log('zlib :', scan(zlib)) `, }, - platform: "browser", + target: "browser", onAfterBundle(api) { assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes"); const file = api.readFile("/out.js"); @@ -189,7 +189,7 @@ describe("bundler", () => { files: { "/entry.js": NodePolyfills.options.files["/entry.js"], }, - platform: "browser", + target: "browser", external: Object.keys(nodePolyfillList), onAfterBundle(api) { const file = api.readFile("/out.js"); @@ -211,7 +211,7 @@ describe("bundler", () => { "bun:dns": "error", "bun:test": "error", "bun:sqlite": "error", - "bun:wrap": "error", + // "bun:wrap": "error", "bun:internal": "error", "bun:jsc": "error", }; @@ -220,7 +220,7 @@ describe("bundler", () => { .filter(x => x[1] !== "error") .map(x => x[0]); - // segfaults the test runner + // all of them are set to error so this test doesnt make sense to run itBundled.skip("browser/BunPolyfill", { skipOnEsbuild: true, files: { @@ -233,7 +233,7 @@ describe("bundler", () => { ${nonErroringBunModules.map((x, i) => `console.log("${x.padEnd(12, " ")}:", scan(bun_${i}));`).join("\n")} `, }, - platform: "browser", + target: "browser", onAfterBundle(api) { assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes"); const file = api.readFile("/out.js"); @@ -257,7 +257,7 @@ describe("bundler", () => { .join("\n")} `, }, - platform: "browser", + target: "browser", bundleErrors: { "/entry.js": Object.keys(bunModules) .filter(x => bunModules[x] === "error") @@ -266,10 +266,10 @@ describe("bundler", () => { }); // not implemented right now - itBundled.skip("browser/BunPolyfillExternal", { + itBundled("browser/BunPolyfillExternal", { skipOnEsbuild: true, files: ImportBunError.options.files, - platform: "browser", + target: "browser", external: Object.keys(bunModules), onAfterBundle(api) { const file = api.readFile("/out.js"); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index cd4b57bc8..216e813d6 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -74,10 +74,11 @@ describe("bundler", () => { external: ["external"], mode: "transform", minifySyntax: true, - platform: "bun", + target: "bun", run: { file: "/entry.ts" }, }); itBundled("edgecase/TemplateStringIssue622", { + notImplemented: true, files: { "/entry.ts": /* js */ ` capture(\`\\?\`); @@ -85,7 +86,7 @@ describe("bundler", () => { `, }, capture: ["`\\\\?`", "hello`\\\\?`"], - platform: "bun", + target: "bun", }); itBundled("edgecase/StringNullBytes", { files: { @@ -121,7 +122,7 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"development"', "false", "true"], env: { // undefined will ensure this variable is not passed to the bundler @@ -136,7 +137,7 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"development"', "false", "true"], env: { NODE_ENV: "development", @@ -150,19 +151,40 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"production"', "true", "false"], env: { NODE_ENV: "production", }, }); + itBundled("edgecase/NodeEnvOptionalChaining", { + notImplemented: true, + files: { + "/entry.js": /* js */ ` + capture(process?.env?.NODE_ENV); + capture(process?.env?.NODE_ENV === 'production'); + capture(process?.env?.NODE_ENV === 'development'); + capture(process.env?.NODE_ENV); + capture(process.env?.NODE_ENV === 'production'); + capture(process.env?.NODE_ENV === 'development'); + capture(process?.env.NODE_ENV); + capture(process?.env.NODE_ENV === 'production'); + capture(process?.env.NODE_ENV === 'development'); + `, + }, + target: "browser", + capture: ['"development"', "false", "true", '"development"', "false", "true", '"development"', "false", "true"], + env: { + NODE_ENV: "development", + }, + }); itBundled("edgecase/ProcessEnvArbitrary", { files: { "/entry.js": /* js */ ` capture(process.env.ARBITRARY); `, }, - platform: "browser", + target: "browser", capture: ["process.env.ARBITRARY"], env: { ARBITRARY: "secret environment stuff!", @@ -228,4 +250,132 @@ describe("bundler", () => { stdout: "1", }, }); + itBundled("edgecase/ValidLoaderSeenAsInvalid", { + files: { + "/entry.js": /* js */ `console.log(1)`, + }, + outdir: "/out", + loader: { + ".a": "file", // segfaults + ".b": "text", // InvalidLoader + ".c": "toml", // InvalidLoader + ".d": "json", + ".e": "js", + ".f": "ts", + ".g": "jsx", + ".h": "tsx", + // ".i": "wasm", + // ".j": "napi", + // ".k": "base64", + // ".l": "dataurl", + // ".m": "binary", + // ".n": "empty", + // ".o": "copy", + }, + }); + itBundled("edgecase/InvalidLoaderSegfault", { + files: { + "/entry.js": /* js */ `console.log(1)`, + }, + outdir: "/out", + loader: { + ".cool": "wtf", + }, + bundleErrors: { + // todo: get the exact error + "<bun>": ["InvalidLoader"], + }, + }); + itBundled("edgecase/ScriptTagEscape", { + files: { + "/entry.js": /* js */ ` + console.log('<script></script>'); + console.log(await import('./text-file.txt')) + `, + "/text-file.txt": /* txt */ ` + <script></script> + `, + }, + outdir: "/out", + onAfterBundle(api) { + try { + expect(api.readFile("/out/entry.js")).not.toContain("</script>"); + } catch (error) { + console.error("Bundle contains </script> which will break if this bundle is placed in a script tag."); + throw error; + } + }, + }); + itBundled("edgecase/JSONDefaultImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(JSON.stringify(def)) + `, + "/test.json": `{ "hello": 234, "world": 123 }`, + }, + run: { + stdout: '{"hello":234,"world":123}', + }, + }); + itBundled("edgecase/JSONDefaultKeyImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(def.hello) + `, + "/test.json": `{ "hello": 234, "world": "REMOVE" }`, + }, + run: { + stdout: "234", + }, + }); + itBundled("edgecase/JSONDefaultAndNamedImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + import { hello } from './test.json' + console.log(def.hello, hello) + `, + "/test.json": `{ "hello": 234, "world": "REMOVE" }`, + }, + dce: true, + run: { + stdout: "234 234", + }, + }); + itBundled("edgecase/JSONWithDefaultKey", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(JSON.stringify(def)) + `, + "/test.json": `{ "default": 234 }`, + }, + dce: true, + run: { + stdout: '{"default":234}', + }, + }); + itBundled("edgecase/JSONWithDefaultKeyNamespace", { + files: { + "/entry.js": /* js */ ` + import * as ns from './test.json' + console.log(JSON.stringify(ns)) + `, + "/test.json": `{ "default": 234 }`, + }, + dce: true, + run: { + stdout: '{"default":234}', + }, + }); + itBundled("edgecase/RequireUnknownExtension", { + files: { + "/entry.js": /* js */ ` + require('./x.aaaa') + `, + "/x.aaaa": `x`, + }, + }); }); diff --git a/test/bundler/bundler_jsx.test.ts b/test/bundler/bundler_jsx.test.ts new file mode 100644 index 000000000..3129f06be --- /dev/null +++ b/test/bundler/bundler_jsx.test.ts @@ -0,0 +1,322 @@ +import assert from "assert"; +import dedent from "dedent"; +import { BundlerTestInput, itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +const helpers = { + "/node_modules/bun-test-helpers/index.js": /* js */ ` + export function print(arg) { + const replacer = (_, val) => { + if(typeof val === "function") { + if(val.name) return 'Function:' + val.name; + return val.toString(); + } + if(typeof val === "symbol") return val.toString(); + if(val === undefined) return "undefined"; + if(val === null) return "null"; + return val; + } + const stringified = JSON.stringify(arg, replacer); + if(!process.env.IS_TEST_RUNNER) { + console.log(arg); + } + console.log(stringified); + } + `, + "/node_modules/react/jsx-dev-runtime.js": /* js */ ` + const $$typeof = Symbol.for("jsxdev"); + export function jsxDEV(type, props, key, source, self) { + return { + $$typeof, type, props, key, source, self + } + } + export const Fragment = Symbol.for("jsxdev.fragment"); + `, + "/node_modules/react/jsx-runtime.js": /* js */ ` + const $$typeof = Symbol.for("jsx"); + export function jsx(type, props, key) { + return { + $$typeof, type, props, key + } + } + export const Fragment = Symbol.for("jsx.fragment"); + `, + "/node_modules/custom-jsx-dev/index.js": /* js */ ` + export function jsxDEV(type, props, key, source, self) { + return ['custom-jsx-dev', type, props, key, source, self] + } + export const Fragment = "CustomFragment" + `, + "/node_modules/custom-jsx/index.js": /* js */ ` + export function jsx(a, b, c) { + return ['custom-jsx', a, b, c] + } + export const Fragment = "CustomFragment" + `, + "/node_modules/custom-classic/index.js": /* js */ ` + export function createElement(type, props, ...children) { + return ['custom-classic', type, props, children] + } + export const Fragment = "CustomFragment" + export const something = "something" + `, + "/node_modules/custom-automatic/jsx-runtime.js": /* js */ ` + const $$typeof = Symbol.for("custom_jsx"); + export function jsx(type, props, key) { + return { + $$typeof, type, props, key + } + } + export const Fragment = Symbol.for("custom.fragment"); + `, + "/node_modules/custom-automatic/jsx-dev-runtime.js": /* js */ ` + const $$typeof = Symbol.for("custom_jsxdev"); + export function jsxDEV(type, props, key, source, self) { + return { + $$typeof, type, props, key, source, self + } + } + export const Fragment = Symbol.for("custom_dev.fragment"); + `, + "/node_modules/custom-automatic/index.js": /* js */ ` + export const Fragment = "FAILED" + `, + "/node_modules/react/index.js": /* js */ ` + export function createElement(type, props, ...children) { + return ['react', type, props, children] + } + export const Fragment = Symbol.for("react.fragment") + + export const fn = () => { + throw new Error('test failed') + } + export const something = 'test failed'; + `, + "/node_modules/custom-renamed/index.js": /* js */ ` + export function fn(type, props, ...children) { + return ['custom-renamed', type, props, children] + } + export const Fragment = "CustomFragment" + export const something = "something" + `, + "/node_modules/preact/index.js": /* js */ ` + export function h(type, props, ...children) { + return ['preact', type, props, children] + } + export const Fragment = "PreactFragment" + `, +}; + +function itBundledDevAndProd( + id: string, + opts: BundlerTestInput & { + devStdout?: string; + prodStdout?: string; + devNotImplemented?: boolean; + prodNotImplemented?: boolean; + }, +) { + const { devStdout, prodStdout, ...rest } = opts; + itBundled(id + "Dev", { + notImplemented: opts.devNotImplemented, + ...rest, + env: { + NODE_ENV: "development", + }, + run: devStdout + ? { + ...(rest.run === true ? {} : rest.run), + stdout: devStdout, + } + : rest.run, + }); + itBundled(id + "Prod", { + notImplemented: opts.prodNotImplemented, + ...rest, + env: { + NODE_ENV: "production", + }, + run: prodStdout + ? { + ...(rest.run === true ? {} : rest.run), + stdout: prodStdout, + } + : rest.run, + }); +} + +describe("bundler", () => { + itBundledDevAndProd("jsx/Automatic", { + files: { + "index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + const Component = 'hello' + print(<div>Hello World</div>) + print(<div className="container"><Component prop={2}><h1 onClick={() => 1}>hello</h1></Component></div>) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + {"$$typeof":"Symbol(jsxdev)","type":"div","props":{"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"} + {"$$typeof":"Symbol(jsxdev)","type":"div","props":{"className":"container","children":{"$$typeof":"Symbol(jsxdev)","type":"hello","props":{"prop":2,"children":{"$$typeof":"Symbol(jsxdev)","type":"h1","props":{"onClick":"Function:onClick","children":"hello"},"key":"undefined","source":false,"self":"undefined"}},"key":"undefined","source":false,"self":"undefined"}},"key":"undefined","source":false,"self":"undefined"} + `, + prodStdout: ` + {"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"children":"Hello World"},"_owner":"null"} + {"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"className":"container","children":{"$$typeof":"Symbol(react.element)","type":"hello","key":"null","ref":"null","props":{"prop":2,"children":{"$$typeof":"Symbol(react.element)","type":"h1","key":"null","ref":"null","props":{"onClick":"Function:onClick","children":"hello"},"_owner":"null"}},"_owner":"null"}},"_owner":"null"} + `, + }); + // bun does not do the production transform for fragments as good as it could be right now. + itBundledDevAndProd("jsx/AutomaticFragment", { + notImplemented: true, + files: { + "index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + const Component = 'hello' + print(<div>Hello World</div>) + print(<div className="container"><Component prop={2}><h1 onClick={() => 1}>hello</h1></Component></div>) + print(<>Fragment</>) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + {"$$typeof":"Symbol(jsxdev)","type":"Symbol(jsxdev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"} + `, + prodStdout: ` + {"$$typeof":"Symbol(react.element)","type":"Symbol("jsx.fragment")","key":"null","ref":"null","props":{"children":"Fragment"},"_owner":"null"} + `, + }); + itBundledDevAndProd("jsx/ImportSource", { + files: { + "/index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + importSource: "custom-automatic", + }, + devStdout: ` + [{"$$typeof":"Symbol(custom_jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(custom_jsxdev)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + `, + prodStdout: ` + [{"$$typeof":"Symbol(custom_jsx)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined"},{"$$typeof":"Symbol(custom_jsx)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined"}] + `, + }); + itBundledDevAndProd("jsx/Classic", { + files: { + "/index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + // not react to catch if bun auto imports or uses the global + import * as React from 'custom-classic' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + importSource: "ignore-me", + }, + run: { + stdout: ` + [["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + `, + }, + }); + itBundledDevAndProd("jsx/ClassicPragma", { + files: { + "/index.jsx": /* js*/ ` + // @jsx fn + // @jsxFrag something + import { print } from 'bun-test-helpers' + import { fn, something } from 'custom-renamed' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + importSource: "ignore-me", + }, + run: { + stdout: ` + [["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + `, + }, + }); + itBundledDevAndProd("jsx/PragmaMultiple", { + files: { + "/index.jsx": /* js*/ ` + import './classic.jsx' + import './classic-renamed.jsx' + import './automatic.jsx' + import './automatic-source2.jsx' + `, + "/classic.jsx": /* js*/ ` + /* @jsxRuntime classic */ + import { print } from 'bun-test-helpers' + // not react to catch if bun auto imports or uses the global + import * as React from 'custom-classic' + print(['classic.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + "/classic-renamed.jsx": /* js*/ ` + /* @jsxRuntime classic */ + // @jsx fn + // @jsxFrag something + import { print } from 'bun-test-helpers' + import { fn, something } from 'custom-renamed' + print(['classic-renamed.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + "/automatic.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + print(['automatic.jsx',<div props={123}>Hello World</div>, process.env.NODE_ENV === 'production' ? '' : <>Fragment</>]) + `, + "/automatic-source2.jsx": /* js*/ ` + // @jsxImportSource custom-automatic + import { print } from 'bun-test-helpers' + print(['automatic-source2.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + ["classic.jsx",["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + ["classic-renamed.jsx",["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + ["automatic.jsx",{"$$typeof":"Symbol(jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(jsxdev)","type":"Symbol(jsxdev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + ["automatic-source2.jsx",{"$$typeof":"Symbol(custom_jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(custom_jsxdev)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + `, + prodStdout: ` + ["classic.jsx",["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + ["classic-renamed.jsx",["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + ["automatic.jsx",{"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"props":123,"children":"Hello World"},"_owner":"null"},""] + ["automatic-source2.jsx",{"$$typeof":"Symbol(custom_jsx)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined"},{"$$typeof":"Symbol(custom_jsx)","type":"Symbol(custom.fragment)","props":{"children":"Fragment"},"key":"undefined"}] + `, + }); + itBundledDevAndProd("jsx/Factory", { + files: { + "/index.jsx": /* js*/ ` + const h = () => 'hello' + const Fragment = 123; + + import { print } from 'bun-test-helpers' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "h", + }, + run: { + stdout: ` + hello hello + `, + }, + }); +}); diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index cad991f2a..1bd255e46 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -52,7 +52,7 @@ describe("bundler", () => { "!1", ], minifySyntax: true, - platform: "bun", + target: "bun", }); itBundled("minify/FunctionExpressionRemoveName", { notImplemented: true, @@ -67,7 +67,7 @@ describe("bundler", () => { capture: ["function(", "function(", "function e("], minifySyntax: true, minifyIdentifiers: true, - platform: "bun", + target: "bun", }); itBundled("minify/PrivateIdentifiersNameCollision", { files: { @@ -123,4 +123,15 @@ describe("bundler", () => { assert([...code.matchAll(/var /g)].length === 1, "expected only 1 variable declaration statement"); }, }); + itBundled("minify/InlineArraySpread", { + files: { + "/entry.js": /* js */ ` + capture([1, 2, ...[3, 4], 5, 6, ...[7, ...[...[...[...[8, 9]]]]], 10, ...[...[...[...[...[...[...[11]]]]]]]]); + capture([1, 2, ...[3, 4], 5, 6, ...[7, [...[...[...[8, 9]]]]], 10, ...[...[...[...[...[...[...11]]]]]]]); + `, + }, + capture: ["[1,2,3,4,5,6,7,8,9,10,11]", "[1,2,3,4,5,6,7,[8,9],10,...11]"], + minifySyntax: true, + minifyWhitespace: true, + }); }); diff --git a/test/bundler/bundler_plugin.test.ts b/test/bundler/bundler_plugin.test.ts index bde2f180c..312c8f252 100644 --- a/test/bundler/bundler_plugin.test.ts +++ b/test/bundler/bundler_plugin.test.ts @@ -425,6 +425,152 @@ describe("bundler", () => { onAfterBundle(api) {}, }; }); + itBundled("plugin/TwoPluginBug", ({ root }) => { + return { + files: { + "index.ts": /* ts */ ` + import { foo } from "plugin1"; + console.log(foo); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /plugin1/ }, args => { + return { + path: "plugin1", + namespace: "plugin1", + }; + }); + builder.onLoad({ filter: /plugin1/, namespace: "plugin1" }, args => { + return { + contents: "export * from 'plugin2';", + loader: "js", + }; + }); + }, + }, + { + name: "2", + setup(builder) { + builder.onResolve({ filter: /plugin2/ }, args => { + return { + path: "plugin2", + namespace: "plugin2", + }; + }); + builder.onLoad({ filter: /plugin2/, namespace: "plugin2" }, args => { + return { + contents: "export const foo = 'foo';", + loader: "js", + }; + }); + }, + }, + ], + run: { + stdout: "foo", + }, + }; + }); + itBundled("plugin/LoadCalledOnce", ({ root }) => { + let resolveCount = 0; + let loadCount = 0; + return { + files: { + "index.ts": /* ts */ ` + import { foo } from "plugin:first"; + import { foo as foo2 } from "plugin:second"; + import { foo as foo3 } from "plugin:third"; + console.log(foo === foo2, foo === foo3); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /^plugin:/ }, args => { + resolveCount++; + return { + path: "plugin", + namespace: "plugin", + }; + }); + builder.onLoad({ filter: /^plugin$/, namespace: "plugin" }, args => { + loadCount++; + return { + contents: "export const foo = { };", + loader: "js", + }; + }); + }, + }, + ], + run: { + stdout: "true true", + }, + onAfterBundle(api) { + expect(resolveCount).toBe(3); + expect(loadCount).toBe(1); + }, + }; + }); + itBundled("plugin/ResolveManySegfault", ({ root }) => { + let resolveCount = 0; + let loadCount = 0; + return { + files: { + "index.ts": /* ts */ ` + import { foo as foo1 } from "plugin:100"; + console.log(foo1); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /^plugin:/ }, args => { + resolveCount++; + return { + path: args.path, + namespace: "plugin", + }; + }); + builder.onLoad({ filter: /^plugin:/, namespace: "plugin" }, args => { + loadCount++; + const number = parseInt(args.path.replace("plugin:", "")); + if (number > 1) { + const numberOfImports = number > 100 ? 100 : number; + const imports = Array.from({ length: numberOfImports }) + .map((_, i) => `import { foo as foo${i} } from "plugin:${number - i - 1}";`) + .join("\n"); + const exports = `export const foo = ${Array.from({ length: numberOfImports }) + .map((_, i) => `foo${i}`) + .join(" + ")};`; + return { + contents: `${imports}\n${exports}`, + loader: "js", + }; + } else { + return { + contents: `export const foo = 1;`, + loader: "js", + }; + } + }); + }, + }, + ], + run: { + stdout: "101 102", + }, + onAfterBundle(api) { + expect(resolveCount).toBe(103); + expect(loadCount).toBe(102); + }, + }; + }); }); // TODO: add async on resolve stuff diff --git a/test/bundler/esbuild/css.test.ts b/test/bundler/esbuild/css.test.ts index 75eeb21c5..f1bfde569 100644 --- a/test/bundler/esbuild/css.test.ts +++ b/test/bundler/esbuild/css.test.ts @@ -484,7 +484,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // }, // metafile: true, // entryPoints: ["/foo/entry.js", "/bar/entry.js"], -// entryNames: "[ext]/[hash]", +// entryNaming: "[ext]/[hash]", // outdir: "/", // }); // itBundled("css/DeduplicateRules", { diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 83822411f..03a7f1adf 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -494,11 +494,15 @@ describe("bundler", () => { itBundled("default/JSXSyntaxInJS", { files: { "/entry.mjs": `console.log(<div/>)`, + "/entry.cjs": `console.log(<div/>)`, }, bundleErrors: { // TODO: this could be a nicer error "/entry.mjs": [`Unexpected <`], + "/entry.cjs": [`Unexpected <`], }, + outdir: "/out", + entryPoints: ["/entry.mjs", "/entry.cjs"], }); itBundled("default/JSXConstantFragments", { notImplemented: true, // jsx in bun is too different to esbuild @@ -856,7 +860,7 @@ describe("bundler", () => { require.resolve(v ? y ? 'a' : 'b' : c) `, }, - platform: "node", + target: "node", format: "cjs", // esbuild seems to not need externals for require.resolve, but it should be specified external: ["a", "b", "c"], @@ -925,7 +929,7 @@ describe("bundler", () => { await import('./out/b'); `, }, - entryNames: "[name].[ext]", + entryNaming: "[name].[ext]", entryPoints: ["/a.js", "/b.js"], external: ["a", "b", "c"], run: [ @@ -1023,42 +1027,6 @@ describe("bundler", () => { stdout: "./test.txt", }, }); - itBundled("default/RequireWithoutCallPlatformNeutral", { - notImplemented: true, - // `require` on line one has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - const req = require - req('./entry') - capture(req) - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); - itBundled("default/NestedRequireWithoutCallPlatformNeutral", { - notImplemented: true, - // `require` on line one has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - (() => { - const req = require - req('./entry') - capture(req) - })() - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); itBundled("default/RequireWithCallInsideTry", { files: { "/entry.js": /* js */ ` @@ -1093,27 +1061,6 @@ describe("bundler", () => { }, run: [{ file: "/test1.js" }, { file: "/test2.js" }], }); - itBundled("default/RequireWithoutCallInsideTry", { - notImplemented: true, - // `require` has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - try { - oldLocale = globalLocale._abbr; - var aliasedRequire = require; - aliasedRequire('./locale/' + name); - getSetGlobalLocale(oldLocale); - capture(aliasedRequire) - } catch (e) {} - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); itBundled("default/RequirePropertyAccessCommonJS", { files: { "/entry.js": /* js */ ` @@ -1124,7 +1071,7 @@ describe("bundler", () => { delete require.extensions['.json'] `, }, - platform: "node", + target: "node", format: "cjs", onAfterBundle(api) { api.prependFile( @@ -1252,18 +1199,6 @@ describe("bundler", () => { assert(api.readFile("/out.js").startsWith("#!/usr/bin/env a"), "hashbang exists on bundle"); }, }); - itBundled("default/HashbangNoBundle", { - files: { - "/entry.js": /* js */ ` - #!/usr/bin/env node - process.exit(0); - `, - }, - mode: "transform", - onAfterBundle(api) { - assert(api.readFile("/out.js").startsWith("#!/usr/bin/env node"), "hashbang exists on bundle"); - }, - }); itBundled("default/HashbangBannerUseStrictOrder", { files: { "/entry.js": /* js */ ` @@ -1281,7 +1216,7 @@ describe("bundler", () => { files: { "/entry.js": `console.log(require('fs'))`, }, - platform: "browser", + target: "browser", run: { stdout: "[Function]", }, @@ -1291,7 +1226,7 @@ describe("bundler", () => { "/entry.js": `console.log('existsSync' in require('fs'))`, }, format: "cjs", - platform: "node", + target: "node", run: { stdout: "true", }, @@ -1302,7 +1237,7 @@ describe("bundler", () => { }, minifyWhitespace: true, format: "cjs", - platform: "node", + target: "node", run: { stdout: "true", }, @@ -1320,7 +1255,7 @@ describe("bundler", () => { run: { stdout: "[Function] undefined undefined", }, - platform: "browser", + target: "browser", }); itBundled("default/ImportFSNodeCommonJS", { files: { @@ -1332,7 +1267,7 @@ describe("bundler", () => { console.log('writeFileSync' in fs, readFileSync, 'writeFileSync' in defaultValue) `, }, - platform: "node", + target: "node", format: "cjs", run: { stdout: "true [Function: readFileSync] true", @@ -1348,7 +1283,7 @@ describe("bundler", () => { console.log('writeFileSync' in fs, readFileSync, 'writeFileSync' in defaultValue) `, }, - platform: "node", + target: "node", run: { stdout: "true [Function: readFileSync] true", }, @@ -1360,7 +1295,7 @@ describe("bundler", () => { export {readFileSync} from 'fs' `, }, - platform: "browser", + target: "browser", run: { file: "out.js", }, @@ -1380,7 +1315,7 @@ describe("bundler", () => { assert(module.readFileSync === fs.readFileSync, 'export {readFileSync} from "fs"; works') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1404,7 +1339,7 @@ describe("bundler", () => { assert(module.rfs === fs.readFileSync, 'export {rfs} works') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1428,7 +1363,7 @@ describe("bundler", () => { assert(mod.foo === 123, 'exports.foo') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1444,7 +1379,7 @@ describe("bundler", () => { `, }, format: "esm", - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1460,7 +1395,7 @@ describe("bundler", () => { `, }, format: "cjs", - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -2310,7 +2245,7 @@ describe("bundler", () => { }, }); itBundled("default/AutoExternalNode", { - notImplemented: true, + // notImplemented: true, files: { "/entry.js": /* js */ ` // These URLs should be external automatically @@ -2325,7 +2260,7 @@ describe("bundler", () => { import "node:what-is-this"; `, }, - platform: "node", + target: "node", treeShaking: true, onAfterBundle(api) { const file = api.readFile("/out.js"); @@ -2356,7 +2291,7 @@ describe("bundler", () => { import "bun:what-is-this"; `, }, - platform: "bun", + target: "bun", onAfterBundle(api) { const file = api.readFile("/out.js"); const imports = new Bun.Transpiler().scanImports(file); @@ -2613,7 +2548,7 @@ describe("bundler", () => { `, }, inject: ["/shims.js"], - platform: "node", + target: "node", run: { stdout: "function", }, @@ -3794,7 +3729,7 @@ describe("bundler", () => { `, "/present-file.js": ``, }, - platform: "node", + target: "node", format: "cjs", external: ["external-pkg", "@scope/external-pkg", "{{root}}/external-file"], }); @@ -4315,6 +4250,7 @@ describe("bundler", () => { }, }); itBundled("default/DefineOptionalChain", { + notImplemented: true, files: { "/entry.js": /* js */ ` log([ @@ -5205,7 +5141,7 @@ describe("bundler", () => { "/node_modules/second-path/index.js": `module.exports = 567`, }, external: ["*"], - platform: "browser", + target: "browser", format: "esm", outfile: "/out.mjs", run: { @@ -5232,7 +5168,7 @@ describe("bundler", () => { files: RequireShimSubstitutionBrowser.options.files, runtimeFiles: RequireShimSubstitutionBrowser.options.runtimeFiles, external: ["*"], - platform: "node", + target: "node", format: "esm", outfile: "/out.mjs", run: { @@ -5292,7 +5228,7 @@ describe("bundler", () => { "/node_modules/fs/index.js": `console.log('include this too')`, "/node_modules/fs/promises.js": `throw 'DO NOT INCLUDE THIS'`, }, - platform: "node", + target: "node", }); itBundled("default/EntryNamesNoSlashAfterDir", { // GENERATED @@ -5303,7 +5239,7 @@ describe("bundler", () => { }, entryPoints: ["/src/app1/main.ts", "/src/app2/main.ts", "/src/app3/main.ts"], outputPaths: ["/out/app1-main.js", "/out/app2-main.js", "/out/app3-main.js"], - entryNames: "[dir]-[name].[ext]", + entryNaming: "[dir]-[name].[ext]", }); // itBundled("default/EntryNamesNonPortableCharacter", { // // GENERATED @@ -5331,7 +5267,7 @@ describe("bundler", () => { // entryPoints: ["/src/entries/entry1.js", "/src/entries/entry2.js"], // outbase: "/src", // splitting: true, - // entryNames: "main/[ext]/[name]-[hash].[ext]", + // entryNaming: "main/[ext]/[name]-[hash].[ext]", // }); itBundled("default/MinifyIdentifiersImportPathFrequencyAnalysis", { files: { @@ -5350,10 +5286,22 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, onAfterBundle(api) { - let importFile = api.readFile("/out/import.js").replace(/remove\(.*?\)/g, "remove()"); - let requireFile = api.readFile("/out/require.js").replace(/remove\(.*?\)/g, "remove()"); - assert(!["W", "X", "Y", "Z"].some(x => importFile.includes(x))); - assert(!["A", "B", "C", "D"].some(x => requireFile.includes(x))); + let importFile = api + .readFile("/out/import.js") + .replace(/remove\(.*?\)/g, "remove()") + .replace(/Object\.[a-z]+\b/gi, "null"); + let requireFile = api + .readFile("/out/require.js") + .replace(/remove\(.*?\)/g, "remove()") + .replace(/Object\.[a-z]+\b/gi, "null"); + assert( + !["W", "X", "Y", "Z"].some(x => importFile.includes(x)), + 'import.js should not contain "W", "X", "Y", or "Z"', + ); + assert( + !["A", "B", "C", "D"].some(x => requireFile.includes(x)), + 'require.js should not contain "A", "B", "C", or "D"', + ); }, }); itBundled("default/ToESMWrapperOmission", { @@ -6199,28 +6147,6 @@ describe("bundler", () => { // NOTE: You can either keep the import assertion and only use the "default" import, or you can remove the import assertion and use the "prop" import (which is non-standard behavior). // `, */ // }); - return; - itBundled("default/ExternalPackages", { - // GENERATED - files: { - "/project/entry.js": /* js */ ` - import 'pkg1' - import './file' - import './node_modules/pkg2/index.js' - import '#pkg3' - `, - "/project/package.json": /* json */ ` - { - "imports": { - "#pkg3": "./libs/pkg3.js" - } - } - `, - "/project/file.js": `console.log('file')`, - "/project/node_modules/pkg2/index.js": `console.log('pkg2')`, - "/project/libs/pkg3.js": `console.log('pkg3')`, - }, - }); itBundled("default/MetafileVariousCases", { // GENERATED files: { @@ -6290,7 +6216,8 @@ describe("bundler", () => { `, }, entryPoints: ["/project/entry.js", "/project/entry.css"], - mode: "convertformat", + external: ["*"], + metafile: true, }); itBundled("default/MetafileVeryLongExternalPaths", { // GENERATED @@ -6321,7 +6248,7 @@ describe("bundler", () => { }, }); itBundled("default/CommentPreservation", { - // GENERATED + notImplemented: true, files: { "/entry.js": /* js */ ` console.log( @@ -6467,28 +6394,54 @@ describe("bundler", () => { for (a of /*foo*/b); if (/*foo*/a); - with (/*foo*/a); while (/*foo*/a); do {} while (/*foo*/a); switch (/*foo*/a) {} `, }, - format: "cjs", + external: ["foo"], + onAfterBundle(api) { + const commentCounts: Record<string, number> = { + before: 44, + after: 18, + "comment before": 4, + "comment after": 4, + foo: 21, + bar: 4, + a: 1, + b: 1, + c: 1, + }; + const file = api.readFile("/out.js"); + const comments = [...file.matchAll(/\/\*([^*]+)\*\//g), ...file.matchAll(/\/\/([^\n]+)/g)] + .map(m => m[1].trim()) + .filter(m => m && !m.includes("__PURE__")); + + for (const key in commentCounts) { + const count = comments.filter(c => c === key).length; + if (count !== commentCounts[key]) { + throw new Error(`Expected ${commentCounts[key]} comments with "${key}", got ${count}`); + } + } + }, }); itBundled("default/CommentPreservationImportAssertions", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` - import 'foo' /* before */ assert { type: 'json' } - import 'foo' assert /* before */ { type: 'json' } - import 'foo' assert { /* before */ type: 'json' } - import 'foo' assert { type: /* before */ 'json' } - import 'foo' assert { type: 'json' /* before */ } + import 'foo' /* a */ assert { type: 'json' } + import 'foo' assert /* b */ { type: 'json' } + import 'foo' assert { /* c */ type: 'json' } + import 'foo' assert { type: /* d */ 'json' } + import 'foo' assert { type: 'json' /* e */ } `, }, + external: ["foo"], }); itBundled("default/CommentPreservationTransformJSX", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` console.log( @@ -6518,6 +6471,7 @@ describe("bundler", () => { }); itBundled("default/CommentPreservationPreserveJSX", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` console.log( @@ -6545,19 +6499,4 @@ describe("bundler", () => { `, }, }); - itBundled("default/ErrorMessageCrashStdinESBuildIssue2913", { - // GENERATED - files: { - "/project/node_modules/fflate/package.json": `{ "main": "main.js" }`, - "/project/node_modules/fflate/main.js": ``, - }, - stdin: { - contents: `import "node_modules/fflate"`, - cwd: "/project", - }, - platform: "neutral", - /* TODO FIX expectedScanLog: `<stdin>: ERROR: Could not resolve "node_modules/fflate" - NOTE: You can mark the path "node_modules/fflate" as external to exclude it from the bundle, which will remove this error. - `, */ - }); }); diff --git a/test/bundler/esbuild/importstar_ts.test.ts b/test/bundler/esbuild/importstar_ts.test.ts index 5bbb0567e..0e39f0b29 100644 --- a/test/bundler/esbuild/importstar_ts.test.ts +++ b/test/bundler/esbuild/importstar_ts.test.ts @@ -7,7 +7,7 @@ import { RUN_UNCHECKED_TESTS, itBundled } from "../expectBundled"; // For debug, all files are written to $TEMP/bun-bundle-tests/ts describe("bundler", () => { - if (!RUN_UNCHECKED_TESTS) return; + return; itBundled("ts/TSImportStarUnused", { // GENERATED files: { diff --git a/test/bundler/esbuild/loader.test.ts b/test/bundler/esbuild/loader.test.ts index 93a4e5fff..0b946d0b3 100644 --- a/test/bundler/esbuild/loader.test.ts +++ b/test/bundler/esbuild/loader.test.ts @@ -7,8 +7,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // For debug, all files are written to $TEMP/bun-bundle-tests/loader describe("bundler", () => { - itBundled("loader/LoaderJSONCommonJSAndES6", { - // GENERATED + itBundled("loader/JSONCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_json = require('./x.json') @@ -20,19 +19,19 @@ describe("bundler", () => { "/y.json": `{"y1": true, "y2": false}`, "/z.json": /* json */ ` { - "big": "this is a big long line of text that should be discarded", - "small": "some small text", - "if": "test keyword imports" - } + "big": "this is a big long line of text that should be REMOVED", + "small": "some small text", + "if": "test keyword imports" + } `, }, + dce: true, run: { - stdout: '{"x":true} {} some small text test keyword imports', + stdout: '{"x":true} {"y1":true,"y2":false} some small text test keyword imports', }, }); - itBundled("loader/LoaderJSONSharedWithMultipleEntriesESBuildIssue413", { - // GENERATED + itBundled("loader/JSONSharedWithMultipleEntriesESBuildIssue413", { files: { "/a.js": /* js */ ` import data from './data.json' @@ -54,137 +53,192 @@ describe("bundler", () => { run: [ { file: "/out/a.js", - stdout: 'a: {"test":123} 123 true true true true {"test":123,"default":{"test":123}}', + stdout: 'a: {"test":123} 123 true true true true {"test":123}', }, { file: "/out/b.js", - stdout: 'b: {"test":123} 123 true true true true {"test":123,"default":{"test":123}}', + stdout: 'b: {"test":123} 123 true true true true {"test":123}', }, ], }); - if (!RUN_UNCHECKED_TESTS) return; - itBundled("loader/LoaderFile", { - // GENERATED + itBundled("loader/File", { files: { "/entry.js": `console.log(require('./test.svg'))`, "/test.svg": `<svg></svg>`, }, - outdir: "/out/", + outdir: "/out", + loader: { + // ".svg": "file", + }, + run: { + stdout: /\.\/test-.*\.svg/, + }, }); - itBundled("loader/LoaderFileMultipleNoCollision", { - // GENERATED + itBundled("loader/FileMultipleNoCollision", { files: { "/entry.js": /* js */ ` - console.log( - require('./a/test.txt'), - require('./b/test.txt'), - ) + console.log(require('./a/test.svg')) + console.log(require('./b/test.svg')) `, - "/a/test.txt": `test`, - "/b/test.txt": `test`, + "/a/test.svg": `<svg></svg>`, + "/b/test.svg": `<svg></svg>`, }, - outfile: "/dist/out.js", - }); - itBundled("loader/JSXSyntaxInJSWithJSXLoader", { - // GENERATED - files: { - "/entry.js": `console.log(<div/>)`, + loader: { + ".svg": "file", }, - }); - itBundled("loader/JSXPreserveCapitalLetter", { - // GENERATED - files: { - "/entry.jsx": /* jsx */ ` - import { mustStartWithUpperCaseLetter as Test } from './foo' - console.log(<Test/>) - `, - "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + outdir: "/out", + run: { + stdout: /\.\/test-.*\.svg\n\.\/test-.*\.svg/, }, }); - itBundled("loader/JSXPreserveCapitalLetterMinify", { + itBundled("loader/FileMultipleNoCollisionAssetNames", { files: { - "/entry.jsx": /* jsx */ ` - import { mustStartWithUpperCaseLetter as XYYYY } from './foo' - // This should be named "Y" due to frequency analysis - console.log(<XYYYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY />) + "/entry.js": /* js */ ` + console.log(require('./a/test.svg')) + console.log(require('./b/test.svg')) `, - "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + "/a/test.svg": `<svg></svg>`, + "/b/test.svg": `<svg></svg>`, }, - external: ["react"], - minifyIdentifiers: true, - }); - itBundled("loader/JSXPreserveCapitalLetterMinifyNested", { - files: { - "/entry.jsx": /* jsx */ ` - x = () => { - class RENAME_ME {} // This should be named "Y" due to frequency analysis - capture(RENAME_ME) - return <RENAME_ME YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY /> - } - `, + outdir: "/out", + assetNaming: "assets/[name]-[hash].[ext]", + loader: { + ".svg": "file", + }, + run: { + stdout: /\.\/test-.*\.svg \.\/test-.*\.svg/, }, - external: ["react"], - minifyIdentifiers: true, }); + itBundled("loader/JSXSyntaxInJSWithJSXLoader", { + files: { + "/entry.cjs": `console.log(<div/>)`, + }, + loader: { + ".cjs": "jsx", + }, + external: ["*"], + }); + // itBundled("loader/JSXPreserveCapitalLetter", { + // // GENERATED + // files: { + // "/entry.jsx": /* jsx */ ` + // import { mustStartWithUpperCaseLetter as Test } from './foo' + // console.log(<Test/>) + // `, + // "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + // }, + // }); + // itBundled("loader/JSXPreserveCapitalLetterMinify", { + // files: { + // "/entry.jsx": /* jsx */ ` + // import { mustStartWithUpperCaseLetter as XYYYY } from './foo' + // // This should be named "Y" due to frequency analysis + // console.log(<XYYYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY />) + // `, + // "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + // }, + // external: ["react"], + // minifyIdentifiers: true, + // }); + // itBundled("loader/JSXPreserveCapitalLetterMinifyNested", { + // files: { + // "/entry.jsx": /* jsx */ ` + // x = () => { + // class RENAME_ME {} // This should be named "Y" due to frequency analysis + // capture(RENAME_ME) + // return <RENAME_ME YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY /> + // } + // `, + // }, + // external: ["react"], + // minifyIdentifiers: true, + // }); itBundled("loader/RequireCustomExtensionString", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `#include <stdio.h>`, }, + loader: { + ".custom": "text", + }, + run: { + stdout: "#include <stdio.h>", + }, }); itBundled("loader/RequireCustomExtensionBase64", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `a\x00b\x80c\xFFd`, }, + loader: { + ".custom": "base64", + }, + run: { + stdout: "YQBiwoBjw79k", + }, }); itBundled("loader/RequireCustomExtensionDataURL", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `a\x00b\x80c\xFFd`, }, + loader: { + ".custom": "dataurl", + }, + run: { + stdout: "data:application/octet-stream,a\x00b\x80c\xFFd", + }, }); itBundled("loader/RequireCustomExtensionPreferLongest", { - // GENERATED files: { "/entry.js": `console.log(require('./test.txt'), require('./test.base64.txt'))`, "/test.txt": `test.txt`, "/test.base64.txt": `test.base64.txt`, }, + loader: { + ".txt": "text", + ".base64.txt": "base64", + }, + run: { + stdout: "test.txt dGVzdC5iYXNlNjQudHh0", + }, }); itBundled("loader/AutoDetectMimeTypeFromExtension", { - // GENERATED files: { "/entry.js": `console.log(require('./test.svg'))`, "/test.svg": `a\x00b\x80c\xFFd`, }, + loader: { + ".svg": "dataurl", + }, + run: { + stdout: "data:image/svg+xml,a\x00b\x80c\xFFd", + }, }); - itBundled("loader/LoaderJSONInvalidIdentifierES6", { - // GENERATED + itBundled("loader/JSONInvalidIdentifierES6", { files: { "/entry.js": /* js */ ` import * as ns from './test.json' import * as ns2 from './test2.json' - console.log(ns['invalid-identifier'], ns2) + console.log(ns['invalid-identifier'], JSON.stringify(ns2)) `, "/test.json": `{"invalid-identifier": true}`, "/test2.json": `{"invalid-identifier": true}`, }, + run: { + stdout: 'true {"invalid-identifier":true}', + }, }); - itBundled("loader/LoaderJSONMissingES6", { - // GENERATED + itBundled("loader/JSONMissingES6", { files: { "/entry.js": `import {missing} from './test.json'`, "/test.json": `{"present": true}`, }, - /* TODO FIX expectedCompileLog: `entry.js: ERROR: No matching export in "test.json" for import "missing" - `, */ + bundleErrors: { + "/entry.js": [`No matching export in "test.json" for import "missing"`], + }, }); - itBundled("loader/LoaderTextCommonJSAndES6", { - // GENERATED + itBundled("loader/TextCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_txt = require('./x.txt') @@ -194,9 +248,11 @@ describe("bundler", () => { "/x.txt": `x`, "/y.txt": `y`, }, + run: { + stdout: "x y", + }, }); - itBundled("loader/LoaderBase64CommonJSAndES6", { - // GENERATED + itBundled("loader/Base64CommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_b64 = require('./x.b64') @@ -206,9 +262,14 @@ describe("bundler", () => { "/x.b64": `x`, "/y.b64": `y`, }, + loader: { + ".b64": "base64", + }, + run: { + stdout: "eA== eQ==", + }, }); - itBundled("loader/LoaderDataURLCommonJSAndES6", { - // GENERATED + itBundled("loader/DataURLCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_url = require('./x.txt') @@ -218,9 +279,14 @@ describe("bundler", () => { "/x.txt": `x`, "/y.txt": `y`, }, + loader: { + ".txt": "dataurl", + }, + run: { + stdout: "data:text/plain;charset=utf-8,x data:text/plain;charset=utf-8,y", + }, }); - itBundled("loader/LoaderFileCommonJSAndES6", { - // GENERATED + itBundled("loader/FileCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_url = require('./x.txt') @@ -231,8 +297,7 @@ describe("bundler", () => { "/y.txt": `y`, }, }); - itBundled("loader/LoaderFileRelativePathJS", { - // GENERATED + itBundled("loader/FileRelativePathJS", { files: { "/src/entries/entry.js": /* js */ ` import x from '../images/image.png' @@ -241,20 +306,28 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - }); - itBundled("loader/LoaderFileRelativePathCSS", { - // GENERATED - files: { - "/src/entries/entry.css": /* css */ ` - div { - background: url(../images/image.png); - } - `, - "/src/images/image.png": `x`, + outdir: "/out", + outputPaths: ["/out/entries/entry.js"], + loader: { + ".png": "file", + }, + run: { + stdout: /^..\/image-.*\.png$/, }, - outbase: "/src", }); - itBundled("loader/LoaderFileRelativePathAssetNamesJS", { + // itBundled("loader/FileRelativePathCSS", { + // // GENERATED + // files: { + // "/src/entries/entry.css": /* css */ ` + // div { + // background: url(../images/image.png); + // } + // `, + // "/src/images/image.png": `x`, + // }, + // outbase: "/src", + // }); + itBundled("loader/FileRelativePathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -264,9 +337,17 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", + outdir: "/out", + outputPaths: ["/out/entries/entry.js"], + loader: { + ".png": "file", + }, + run: { + stdout: /^..\/images\/image-.*\.png$/, + }, }); - itBundled("loader/LoaderFileExtPathAssetNamesJS", { + itBundled("loader/FileExtPathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -278,9 +359,9 @@ describe("bundler", () => { "/src/uploads/file.txt": `y`, }, outbase: "/src", - assetNames: "[ext]/[name]-[hash]", + assetNaming: "[ext]/[name]-[hash]", }); - itBundled("loader/LoaderFileRelativePathAssetNamesCSS", { + itBundled("loader/FileRelativePathAssetNamesCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -291,9 +372,9 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFilePublicPathJS", { + itBundled("loader/FilePublicPathJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -305,7 +386,7 @@ describe("bundler", () => { outbase: "/src", publicPath: "https://example.com", }); - itBundled("loader/LoaderFilePublicPathCSS", { + itBundled("loader/FilePublicPathCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -318,7 +399,7 @@ describe("bundler", () => { outbase: "/src", publicPath: "https://example.com", }); - itBundled("loader/LoaderFilePublicPathAssetNamesJS", { + itBundled("loader/FilePublicPathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -329,9 +410,9 @@ describe("bundler", () => { }, outbase: "/src", publicPath: "https://example.com", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFilePublicPathAssetNamesCSS", { + itBundled("loader/FilePublicPathAssetNamesCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -343,9 +424,9 @@ describe("bundler", () => { }, outbase: "/src", publicPath: "https://example.com", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFileOneSourceTwoDifferentOutputPathsJS", { + itBundled("loader/FileOneSourceTwoDifferentOutputPathsJS", { // GENERATED files: { "/src/entries/entry.js": `import '../shared/common.js'`, @@ -359,7 +440,7 @@ describe("bundler", () => { entryPoints: ["/src/entries/entry.js", "/src/entries/other/entry.js"], outbase: "/src", }); - itBundled("loader/LoaderFileOneSourceTwoDifferentOutputPathsCSS", { + itBundled("loader/FileOneSourceTwoDifferentOutputPathsCSS", { // GENERATED files: { "/src/entries/entry.css": `@import "../shared/common.css";`, @@ -374,14 +455,14 @@ describe("bundler", () => { entryPoints: ["/src/entries/entry.css", "/src/entries/other/entry.css"], outbase: "/src", }); - itBundled("loader/LoaderJSONNoBundle", { + itBundled("loader/JSONNoBundle", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, }, mode: "transform", }); - itBundled("loader/LoaderJSONNoBundleES6", { + itBundled("loader/JSONNoBundleES6", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -390,7 +471,7 @@ describe("bundler", () => { unsupportedJSFeatures: "ArbitraryModuleNamespaceNames", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleES6ArbitraryModuleNamespaceNames", { + itBundled("loader/JSONNoBundleES6ArbitraryModuleNamespaceNames", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -398,7 +479,7 @@ describe("bundler", () => { format: "esm", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleCommonJS", { + itBundled("loader/JSONNoBundleCommonJS", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -406,7 +487,7 @@ describe("bundler", () => { format: "cjs", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleIIFE", { + itBundled("loader/JSONNoBundleIIFE", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -414,7 +495,7 @@ describe("bundler", () => { format: "iife", mode: "convertformat", }); - itBundled("loader/LoaderFileWithQueryParameter", { + itBundled("loader/FileWithQueryParameter", { // GENERATED files: { "/entry.js": /* js */ ` @@ -426,7 +507,7 @@ describe("bundler", () => { "/file.txt": `This is some text`, }, }); - itBundled("loader/LoaderFromExtensionWithQueryParameter", { + itBundled("loader/FromExtensionWithQueryParameter", { // GENERATED files: { "/entry.js": /* js */ ` @@ -436,7 +517,7 @@ describe("bundler", () => { "/file.abc": `This should not be base64 encoded`, }, }); - itBundled("loader/LoaderDataURLTextCSS", { + itBundled("loader/DataURLTextCSS", { // GENERATED files: { "/entry.css": /* css */ ` @@ -447,7 +528,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLTextCSSCannotImport", { + itBundled("loader/DataURLTextCSSCannotImport", { // GENERATED files: { "/entry.css": `@import "data:text/css,@import './other.css';";`, @@ -456,7 +537,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `<data:text/css,@import './other.css';>: ERROR: Could not resolve "./other.css" `, */ }); - itBundled("loader/LoaderDataURLTextJavaScript", { + itBundled("loader/DataURLTextJavaScript", { // GENERATED files: { "/entry.js": /* js */ ` @@ -467,7 +548,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLTextJavaScriptCannotImport", { + itBundled("loader/DataURLTextJavaScriptCannotImport", { // GENERATED files: { "/entry.js": `import "data:text/javascript,import './other.js'"`, @@ -476,13 +557,13 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `<data:text/javascript,import './other.js'>: ERROR: Could not resolve "./other.js" `, */ }); - itBundled("loader/LoaderDataURLTextJavaScriptPlusCharacter", { + itBundled("loader/DataURLTextJavaScriptPlusCharacter", { // GENERATED files: { "/entry.js": `import "data:text/javascript,console.log(1+2)";`, }, }); - itBundled("loader/LoaderDataURLApplicationJSON", { + itBundled("loader/DataURLApplicationJSON", { // GENERATED files: { "/entry.js": /* js */ ` @@ -496,7 +577,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLUnknownMIME", { + itBundled("loader/DataURLUnknownMIME", { // GENERATED files: { "/entry.js": /* js */ ` @@ -506,7 +587,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLExtensionBasedMIME", { + itBundled("loader/DataURLExtensionBasedMIME", { // GENERATED files: { "/entry.foo": /* foo */ ` @@ -555,7 +636,7 @@ describe("bundler", () => { "/example.xml": `xml`, }, }); - itBundled("loader/LoaderDataURLBase64VsPercentEncoding", { + itBundled("loader/DataURLBase64VsPercentEncoding", { // GENERATED files: { "/entry.js": /* js */ ` @@ -576,7 +657,7 @@ describe("bundler", () => { "/shouldUseBase64_2.txt": `\n\n\n\n\n\n`, }, }); - itBundled("loader/LoaderDataURLBase64InvalidUTF8", { + itBundled("loader/DataURLBase64InvalidUTF8", { // GENERATED files: { "/entry.js": /* js */ ` @@ -586,7 +667,7 @@ describe("bundler", () => { "/binary.txt": `\xFF`, }, }); - itBundled("loader/LoaderDataURLEscapePercents", { + itBundled("loader/DataURLEscapePercents", { // GENERATED files: { "/entry.js": /* js */ ` @@ -600,7 +681,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderCopyWithBundleFromJS", { + itBundled("loader/CopyWithBundleFromJS", { // GENERATED files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -611,7 +692,7 @@ describe("bundler", () => { }, outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithBundleFromCSS", { + itBundled("loader/CopyWithBundleFromCSS", { // GENERATED files: { "/Users/user/project/src/entry.css": /* css */ ` @@ -623,7 +704,7 @@ describe("bundler", () => { }, outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithBundleEntryPoint", { + itBundled("loader/CopyWithBundleEntryPoint", { // GENERATED files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -644,7 +725,7 @@ describe("bundler", () => { ], outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithTransform", { + itBundled("loader/CopyWithTransform", { // GENERATED files: { "/Users/user/project/src/entry.js": `console.log('entry')`, @@ -654,7 +735,7 @@ describe("bundler", () => { outbase: "/Users/user/project", mode: "passthrough", }); - itBundled("loader/LoaderCopyWithFormat", { + itBundled("loader/CopyWithFormat", { // GENERATED files: { "/Users/user/project/src/entry.js": `console.log('entry')`, @@ -734,7 +815,7 @@ describe("bundler", () => { "/what": `.foo { color: red }`, }, }); - itBundled("loader/LoaderCopyEntryPointAdvanced", { + itBundled("loader/CopyEntryPointAdvanced", { // GENERATED files: { "/project/entry.js": /* js */ ` @@ -757,20 +838,20 @@ describe("bundler", () => { }, }, */ }); - itBundled("loader/LoaderCopyUseIndex", { + itBundled("loader/CopyUseIndex", { // GENERATED files: { "/Users/user/project/src/index.copy": `some stuff`, }, }); - itBundled("loader/LoaderCopyExplicitOutputFile", { + itBundled("loader/CopyExplicitOutputFile", { // GENERATED files: { "/project/TEST FAILED.copy": `some stuff`, }, outfile: "/out/this.worked", }); - itBundled("loader/LoaderCopyStartsWithDotAbsPath", { + itBundled("loader/CopyStartsWithDotAbsPath", { // GENERATED files: { "/project/src/.htaccess": `some stuff`, @@ -779,7 +860,7 @@ describe("bundler", () => { }, entryPoints: ["/project/src/.htaccess", "/project/src/entry.js", "/project/src/.ts"], }); - itBundled("loader/LoaderCopyStartsWithDotRelPath", { + itBundled("loader/CopyStartsWithDotRelPath", { // GENERATED files: { "/project/src/.htaccess": `some stuff`, diff --git a/test/bundler/esbuild/packagejson.test.ts b/test/bundler/esbuild/packagejson.test.ts index c3b241561..f1bb556a2 100644 --- a/test/bundler/esbuild/packagejson.test.ts +++ b/test/bundler/esbuild/packagejson.test.ts @@ -417,7 +417,7 @@ describe("bundler", () => { } `, }, - platform: "browser", + target: "browser", run: { stdout: "345", }, @@ -451,7 +451,7 @@ describe("bundler", () => { } `, }, - platform: "node", + target: "node", run: { stdout: "123", }, @@ -493,7 +493,7 @@ describe("bundler", () => { } `, }, - platform: "browser", + target: "browser", run: { stdout: "456", }, @@ -535,7 +535,7 @@ describe("bundler", () => { } `, }, - platform: "node", + target: "node", run: { stdout: "123", }, @@ -957,65 +957,6 @@ describe("bundler", () => { stdout: "b", }, }); - itBundled("packagejson/NeutralNoDefaultMainFields", { - notImplemented: true, - files: { - "/Users/user/project/src/entry.js": /* js */ ` - import fn from 'demo-pkg' - console.log(fn()) - `, - "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` - { - "main": "./main.js", - "module": "./main.esm.js" - } - `, - "/Users/user/project/node_modules/demo-pkg/main.js": /* js */ ` - module.exports = function() { - return 123 - } - `, - "/Users/user/project/node_modules/demo-pkg/main.esm.js": /* js */ ` - export default function() { - return 123 - } - `, - }, - platform: "neutral", - /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: ERROR: Could not resolve "demo-pkg" - Users/user/project/node_modules/demo-pkg/package.json: NOTE: The "main" field here was ignored. Main fields must be configured explicitly when using the "neutral" platform. - NOTE: You can mark the path "demo-pkg" as external to exclude it from the bundle, which will remove this error. - `, */ - }); - itBundled("packagejson/NeutralExplicitMainFields", { - files: { - "/Users/user/project/src/entry.js": /* js */ ` - import fn from 'demo-pkg' - console.log(fn()) - `, - "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` - { - "hello": "./main.js", - "module": "./main.esm.js" - } - `, - "/Users/user/project/node_modules/demo-pkg/main.js": /* js */ ` - module.exports = function() { - return 123 - } - `, - "/Users/user/project/node_modules/demo-pkg/main.esm.js": /* js */ ` - export default function() { - return 234 - } - `, - }, - platform: "neutral", - mainFields: ["hello"], - run: { - stdout: "123", - }, - }); itBundled("packagejson/ExportsErrorInvalidModuleSpecifier", { files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -1264,7 +1205,7 @@ describe("bundler", () => { "/Users/user/project/node_modules/pkg/node.js": `console.log('FAILURE')`, "/Users/user/project/node_modules/pkg/browser.js": `console.log('SUCCESS')`, }, - platform: "browser", + target: "browser", outfile: "/Users/user/project/out.js", run: { stdout: "SUCCESS", @@ -1286,30 +1227,8 @@ describe("bundler", () => { "/Users/user/project/node_modules/pkg/browser.js": `console.log('FAILURE')`, "/Users/user/project/node_modules/pkg/node.js": `console.log('SUCCESS')`, }, - platform: "node", - outfile: "/Users/user/project/out.js", - run: { - stdout: "SUCCESS", - }, - }); - itBundled("packagejson/ExportsNeutral", { - files: { - "/Users/user/project/src/entry.js": `import 'pkg'`, - "/Users/user/project/node_modules/pkg/package.json": /* json */ ` - { - "exports": { - "node": "./node.js", - "browser": "./browser.js", - "default": "./default.js" - } - } - `, - "/Users/user/project/node_modules/pkg/node.js": `console.log('FAILURE')`, - "/Users/user/project/node_modules/pkg/browser.js": `console.log('FAILURE')`, - "/Users/user/project/node_modules/pkg/default.js": `console.log('SUCCESS')`, - }, + target: "node", outfile: "/Users/user/project/out.js", - platform: "neutral", run: { stdout: "SUCCESS", }, diff --git a/test/bundler/esbuild/splitting.test.ts b/test/bundler/esbuild/splitting.test.ts index 361b51ef6..c10a1a36f 100644 --- a/test/bundler/esbuild/splitting.test.ts +++ b/test/bundler/esbuild/splitting.test.ts @@ -549,7 +549,7 @@ describe("bundler", () => { }, outdir: "/out", splitting: true, - chunkNames: "[dir]/[name]-[hash].[ext]", + chunkNaming: "[dir]/[name]-[hash].[ext]", onAfterBundle(api) { assert( readdirSync(api.outdir + "/output-path/should-contain/this-text").length === 1, @@ -569,7 +569,7 @@ describe("bundler", () => { outdir: "/out", entryPoints: ["/src/index.js"], splitting: true, - platform: "browser", + target: "browser", runtimeFiles: { "/test.js": /* js */ ` import { A, B } from './out/index.js' diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index e029787c8..f6ad423f9 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -75,13 +75,13 @@ export interface BundlerTestInput { // bundler options alias?: Record<string, string>; - assetNames?: string; + assetNaming?: string; banner?: string; define?: Record<string, string | number>; /** Default is "[name].[ext]" */ - entryNames?: string; + entryNaming?: string; /** Default is "[name]-[hash].[ext]" */ - chunkNames?: string; + chunkNaming?: string; extensionOrder?: string[]; /** Replaces "{{root}}" with the file root */ external?: string[]; @@ -91,11 +91,10 @@ export interface BundlerTestInput { ignoreDCEAnnotations?: boolean; inject?: string[]; jsx?: { - factory?: string; - fragment?: string; - automaticRuntime?: boolean; - development?: boolean; - preserve?: boolean; + runtime?: "automatic" | "classic"; + importSource?: string; // for automatic + factory?: string; // for classic + fragment?: string; // for classic }; outbase?: string; /** Defaults to `/out.js` */ @@ -103,7 +102,7 @@ export interface BundlerTestInput { /** Defaults to `/out` */ outdir?: string; /** Defaults to "browser". "bun" is set to "node" when using esbuild. */ - platform?: "bun" | "node" | "neutral" | "browser"; + target?: "bun" | "node" | "browser"; publicPath?: string; keepNames?: boolean; legalComments?: "none" | "inline" | "eof" | "linked" | "external"; @@ -215,7 +214,7 @@ export interface BundlerTestRunOptions { /** Pass args to bun itself (before the filename) */ bunArgs?: string[]; /** match exact stdout */ - stdout?: string; + stdout?: string | RegExp; /** partial match stdout (toContain()) */ partialStdout?: string; /** match exact error message, example "ReferenceError: Can't find variable: bar" */ @@ -253,21 +252,23 @@ function expectBundled( ignoreFilter = false, ): Promise<BundlerTestRef> | BundlerTestRef { var { expect, it, test } = testForFile(callerSourceOrigin()); - if (!ignoreFilter && FILTER && id !== FILTER) return testRef(id, opts); + if (!ignoreFilter && FILTER && !filterMatches(id)) return testRef(id, opts); let { + notImplemented, bundleWarnings, bundleErrors, banner, backend, assertNotPresent, capture, - chunkNames, + assetNaming, + chunkNaming, cjs2esm, dce, dceKeepMarkerCount, define, - entryNames, + entryNaming, entryPoints, entryPointsRaw, env, @@ -292,7 +293,7 @@ function expectBundled( outdir, outfile, outputPaths, - platform, + target, plugins, publicPath, run, @@ -321,7 +322,7 @@ function expectBundled( // Resolve defaults for options and some related things mode ??= "bundle"; - platform ??= "browser"; + target ??= "browser"; format ??= "esm"; entryPoints ??= entryPointsRaw ? [] : [Object.keys(files)[0]]; if (run === true) run = {}; @@ -333,9 +334,6 @@ function expectBundled( if (!ESBUILD && format !== "esm") { throw new Error("formats besides esm not implemented in bun build"); } - if (!ESBUILD && platform === "neutral") { - throw new Error("platform=neutral not implemented in bun build"); - } if (!ESBUILD && metafile) { throw new Error("metafile not implemented in bun build"); } @@ -357,9 +355,6 @@ function expectBundled( if (!ESBUILD && mainFields) { throw new Error("mainFields not implemented in bun build"); } - if (!ESBUILD && loader) { - throw new Error("loader not implemented in bun build"); - } if (!ESBUILD && sourceMap) { throw new Error("sourceMap not implemented in bun build"); } @@ -369,11 +364,13 @@ function expectBundled( if (!ESBUILD && inject) { throw new Error("inject not implemented in bun build"); } - if (!ESBUILD && publicPath) { - throw new Error("publicPath not implemented in bun build"); - } - if (!ESBUILD && chunkNames) { - throw new Error("chunkNames is not implemented in bun build"); + if (!ESBUILD && loader) { + const loaderValues = [...new Set(Object.values(loader))]; + const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file"]; + const unsupportedLoaderTypes = loaderValues.filter(x => !supportedLoaderTypes.includes(x)); + if (unsupportedLoaderTypes.length) { + throw new Error(`loader '${unsupportedLoaderTypes.join("', '")}' not implemented in bun build`); + } } if (ESBUILD && skipOnEsbuild) { return testRef(id, opts); @@ -413,8 +410,8 @@ function expectBundled( } if (outdir) { - entryNames ??= "[name].[ext]"; - chunkNames ??= "[name]-[hash].[ext]"; + entryNaming ??= "[name].[ext]"; + chunkNaming ??= "[name]-[hash].[ext]"; } // Option validation @@ -469,31 +466,32 @@ function expectBundled( ...(entryPointsRaw ?? []), mode === "bundle" ? [outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`] : [], define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]), - `--platform=${platform}`, + `--target=${target}`, external && external.map(x => ["--external", x]), minifyIdentifiers && `--minify-identifiers`, minifySyntax && `--minify-syntax`, minifyWhitespace && `--minify-whitespace`, globalName && `--global-name=${globalName}`, // inject && inject.map(x => ["--inject", path.join(root, x)]), - jsx.preserve && "--jsx=preserve", - jsx.automaticRuntime === false && "--jsx=classic", - jsx.factory && `--jsx-factory=${jsx.factory}`, - jsx.fragment && `--jsx-fragment=${jsx.fragment}`, - jsx.development === false && `--jsx-production`, - // metafile && `--metafile=${metafile}`, + // jsx.preserve && "--jsx=preserve", + jsx.runtime && ["--jsx-runtime", jsx.runtime], + jsx.factory && ["--jsx-factory", jsx.factory], + jsx.fragment && ["--jsx-fragment", jsx.fragment], + jsx.importSource && ["--jsx-import-source", jsx.importSource], + // metafile && `--manifest=${metafile}`, // sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`, - entryNames && entryNames !== "[name].[ext]" && [`--entry-names`, entryNames], - // chunkNames && chunkNames !== "[name]-[hash].[ext]" && [`--chunk-names`, chunkNames], + entryNaming && entryNaming !== "[name].[ext]" && [`--entry-naming`, entryNaming], + chunkNaming && chunkNaming !== "[name]-[hash].[ext]" && [`--chunk-naming`, chunkNaming], + assetNaming && assetNaming !== "[name]-[hash].[ext]" && [`--asset-naming`, chunkNaming], // `--format=${format}`, // legalComments && `--legal-comments=${legalComments}`, splitting && `--splitting`, - // treeShaking && `--tree-shaking`, + // treeShaking === false && `--no-tree-shaking`, // ?? // outbase && `--outbase=${outbase}`, // keepNames && `--keep-names`, // mainFields && `--main-fields=${mainFields}`, - // loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}=${v}`]), - // publicPath && `--public-path=${publicPath}`, + loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}:${v}`]), + publicPath && `--public-path=${publicPath}`, mode === "transform" && "--transform", ] : [ @@ -501,7 +499,7 @@ function expectBundled( mode === "bundle" && "--bundle", outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`, `--format=${format}`, - `--platform=${platform === "bun" ? "node" : platform}`, + `--platform=${target === "bun" ? "node" : target}`, minifyIdentifiers && `--minify-identifiers`, minifySyntax && `--minify-syntax`, minifyWhitespace && `--minify-whitespace`, @@ -509,15 +507,18 @@ function expectBundled( external && external.map(x => `--external:${x}`), inject && inject.map(x => `--inject:${path.join(root, x)}`), define && Object.entries(define).map(([k, v]) => `--define:${k}=${v}`), - jsx.automaticRuntime && "--jsx=automatic", - jsx.preserve && "--jsx=preserve", + `--jsx=${jsx.runtime ?? "automatic"}`, + // jsx.preserve && "--jsx=preserve", jsx.factory && `--jsx-factory=${jsx.factory}`, jsx.fragment && `--jsx-fragment=${jsx.fragment}`, - jsx.development && `--jsx-dev`, - entryNames && entryNames !== "[name].[ext]" && `--entry-names=${entryNames.replace(/\.\[ext]$/, "")}`, - chunkNames && - chunkNames !== "[name]-[hash].[ext]" && - `--chunk-names=${chunkNames.replace(/\.\[ext]$/, "")}`, + env?.NODE_ENV !== "production" && `--jsx-dev`, + entryNaming && entryNaming !== "[name].[ext]" && `--entry-names=${entryNaming.replace(/\.\[ext]$/, "")}`, + chunkNaming && + chunkNaming !== "[name]-[hash].[ext]" && + `--chunk-names=${chunkNaming.replace(/\.\[ext]$/, "")}`, + assetNaming && + assetNaming !== "[name]-[hash].[ext]" && + `--asset-names=${assetNaming.replace(/\.\[ext]$/, "")}`, metafile && `--metafile=${metafile}`, sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`, banner && `--banner:js=${banner}`, @@ -758,33 +759,35 @@ function expectBundled( syntax: minifySyntax, }, naming: { - entrypoint: useOutFile ? path.basename(outfile!) : entryNames, - chunk: chunkNames, + entry: useOutFile ? path.basename(outfile!) : entryNaming, + chunk: chunkNaming, + asset: assetNaming, }, plugins: pluginArray, treeShaking, outdir: buildOutDir, sourcemap: sourceMap === true ? "external" : sourceMap || "none", splitting, - target: platform === "neutral" ? "browser" : platform, + target, + publicPath, } as BuildConfig; if (DEBUG) { if (_referenceFn) { const x = _referenceFn.toString().replace(/^\s*expect\(.*$/gm, "// $&"); const debugFile = `import path from 'path'; - import assert from 'assert'; - const {plugins} = (${x})({ root: ${JSON.stringify(root)} }); - const options = ${JSON.stringify({ ...buildConfig, plugins: undefined }, null, 2)}; - options.plugins = typeof plugins === "function" ? [{ name: "plugin", setup: plugins }] : plugins; - const build = await Bun.build(options); - if (build.logs) { - throw build.logs; - } - for (const blob of build.outputs) { - await Bun.write(path.join(options.outdir, blob.path), blob.result); - } - `; +import assert from 'assert'; +const {plugins} = (${x})({ root: ${JSON.stringify(root)} }); +const options = ${JSON.stringify({ ...buildConfig, plugins: undefined }, null, 2)}; +options.plugins = typeof plugins === "function" ? [{ name: "plugin", setup: plugins }] : plugins; +const build = await Bun.build(options); +if (build.logs) { + throw build.logs; +} +for (const blob of build.outputs) { + await Bun.write(path.join(options.outdir, blob.path), blob.result); +} +`; writeFileSync(path.join(root, "run.js"), debugFile); } else { console.log("TODO: generate run.js, currently only works if options are wrapped in a function"); @@ -795,14 +798,12 @@ function expectBundled( const build = await Bun.build(buildConfig); Bun.gc(true); + console.log(build); + if (build.logs) { console.log(build.logs); throw new Error("TODO: handle build logs, but we should make this api nicer"); } - - for (const blob of build.outputs) { - await Bun.write(path.join(buildOutDir, blob.path), blob.result); - } } else { await esbuild.build({ bundle: true, @@ -896,7 +897,7 @@ function expectBundled( } } else { // entryNames makes it so we cannot predict the output file - if (!entryNames || entryNames === "[name].[ext]") { + if (!entryNaming || entryNaming === "[name].[ext]") { for (const fullpath of outputPaths) { if (!existsSync(fullpath)) { throw new Error("Bundle was not written to disk: " + fullpath); @@ -1027,7 +1028,7 @@ function expectBundled( if (file) { file = path.join(root, file); } else if (entryPaths.length === 1) { - file = outfile; + file = outfile ?? outputPaths[0]; } else { throw new Error(prefix + "run.file is required when there is more than one entrypoint."); } @@ -1042,6 +1043,7 @@ function expectBundled( env: { ...bunEnv, FORCE_COLOR: "0", + IS_TEST_RUNNER: "1", }, stdio: ["ignore", "pipe", "pipe"], }); @@ -1100,18 +1102,34 @@ function expectBundled( if (run.stdout !== undefined) { const result = stdout!.toString("utf-8").trim(); - const expected = dedent(run.stdout).trim(); - if (expected !== result) { - console.log({ file }); + if (typeof run.stdout === "string") { + const expected = dedent(run.stdout).trim(); + if (expected !== result) { + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); + } + expect(result).toBe(expected); + } else { + if (!run.stdout.test(result)) { + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); + } + expect(result).toMatch(run.stdout); } - expect(result).toBe(expected); } if (run.partialStdout !== undefined) { const result = stdout!.toString("utf-8").trim(); const expected = dedent(run.partialStdout).trim(); if (!result.includes(expected)) { - console.log({ file }); + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); } expect(result).toContain(expected); } @@ -1137,7 +1155,7 @@ export function itBundled( const ref = testRef(id, opts); const { it } = testForFile(callerSourceOrigin()); - if (FILTER && id !== FILTER) { + if (FILTER && !filterMatches(id)) { return ref; } else if (!FILTER) { try { @@ -1165,7 +1183,7 @@ export function itBundled( return ref; } itBundled.skip = (id: string, opts: BundlerTestInput) => { - if (FILTER && id !== FILTER) { + if (FILTER && !filterMatches(id)) { return testRef(id, opts); } const { it } = testForFile(callerSourceOrigin()); @@ -1176,3 +1194,7 @@ itBundled.skip = (id: string, opts: BundlerTestInput) => { function formatError(err: { file: string; error: string; line?: string; col?: string }) { return `${err.file}${err.line ? " :" + err.line : ""}${err.col ? ":" + err.col : ""}: ${err.error}`; } + +function filterMatches(id: string) { + return FILTER === id || FILTER + "Dev" === id || FILTER + "Prod" === id; +} diff --git a/test/bundler/transpiler.test.js b/test/bundler/transpiler.test.js index 7af8f2d27..1fc599771 100644 --- a/test/bundler/transpiler.test.js +++ b/test/bundler/transpiler.test.js @@ -2969,4 +2969,12 @@ console.log(foo, array); expectPrinted_('const x = "``" + ``;', 'const x = "``"'); }); }); + + it("scan on empty file does not segfault", () => { + new Bun.Transpiler().scan(""); + }); + + it("scanImports on empty file does not segfault", () => { + new Bun.Transpiler().scanImports(""); + }); }); |