diff options
author | 2023-05-09 00:55:21 -0400 | |
---|---|---|
committer | 2023-05-08 21:55:21 -0700 | |
commit | 5e366872f659abf116b903e5cece999a04cd018b (patch) | |
tree | d06b5ccd28ea49a7a5e050868ff27e676e0d56f7 | |
parent | 1a411e201b71374f515d1f6cdbb1b36186ee48b0 (diff) | |
download | bun-5e366872f659abf116b903e5cece999a04cd018b.tar.gz bun-5e366872f659abf116b903e5cece999a04cd018b.tar.zst bun-5e366872f659abf116b903e5cece999a04cd018b.zip |
implement build api `define` and `loaders` (#2805)
* parse error logs
* clean up types
* remove --jsx-production. use NODE_ENV instead
* add define to js api
* add loaders to js api
* fixups
* sourcemap
* typo fix
* remove label, comment dir just for now
* test tweaks
* test work
* make optional enums actually optional.
allows `sourcemap: undefined`
* overload host ram test
* string tests
* tests
* test for 2815
* requested changes
* sort this list
* remove this test file now that it passes
* oops
* add --format
* finish ts tests
* doc typos related to define and loader
26 files changed, 1862 insertions, 732 deletions
diff --git a/docs/bundler/migration.md b/docs/bundler/migration.md index 260acf1ba..aac37479c 100644 --- a/docs/bundler/migration.md +++ b/docs/bundler/migration.md @@ -63,8 +63,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot --- -- `--loader` -- `--loader` +- `--loader:.ext=loader` +- `--loader .ext:loader` - Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. The syntax for `--loader` is slightly different. @@ -131,7 +131,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--watch` - n/a -- Not applicable. +- Not applicable --- @@ -143,7 +143,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--analyze` - n/a -- Not supported. Use `--manifest` to generate a manifest file. +- Not supported --- @@ -161,7 +161,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--certfile` - n/a -- Not applicable, Bun's bundler does +- Not applicable --- @@ -203,7 +203,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--global-name` - n/a -- Not applicable, Bun does not support `iife` output at this time. +- Not applicable, Bun does not support `iife` output at this time --- @@ -248,7 +248,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--jsx-side-effects` - n/a -- JSX is always assumed to be side-effect-free. +- JSX is always assumed to be side-effect-free --- @@ -313,7 +313,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot --- - `--metafile` -- `--manifest` +- n/a +- Not supported --- @@ -340,7 +341,6 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `--outbase` - `--root` -- Not supported --- @@ -515,7 +515,6 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - `define` - `define` -- Not supported in JS API --- @@ -637,8 +636,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot --- - `loader` -- n/a -- Not supported in JS API +- `loader` +- Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. --- @@ -685,8 +684,11 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot --- - `metafile` -- `manifest` -- When `manifest` is `true`, the result of `Bun.build()` will contain a `manifest` property. The manifest is compatible with esbuild's metafile format. +- n/a +- Not supported + +<!-- - `manifest` +- When `manifest` is `true`, the result of `Bun.build()` will contain a `manifest` property. The manifest is compatible with esbuild's metafile format. --> --- diff --git a/docs/cli/build.md b/docs/cli/build.md index f205969d5..01262944d 100644 --- a/docs/cli/build.md +++ b/docs/cli/build.md @@ -324,7 +324,7 @@ Depending on the target, Bun will apply different module resolution rules and op {% /table %} -<!-- ### `module` +### `format` Specifies the module format to be used in the generated bundles. @@ -714,6 +714,24 @@ var value = z.string().parse("Hello world!") console.log(_.upperCase(value)); ``` +To mark all imports as external, use the wildcard `*`: + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + external: ['*'], +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --external '*' +``` + +{% /codetabs %} + ### `naming` Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`. @@ -988,6 +1006,56 @@ The output file would now look something like this. + var logo = 'https://cdn.example.com/logo-a7305bdef.svg'; ``` +### `define` + +A map of global identifiers to be replaced at build time. Keys of this object are identifier names, and values are JSON strings that will be inlined. + +{% callout } +This is not needed to inline `process.env.NODE_ENV`, as Bun does this automatically. +{% /callout %} + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + define: { + STRING: JSON.stringify("value"), + "nested.boolean": "true", + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --define 'STRING="value"' --define "nested.boolean=true" +``` + +{% /codetabs %} + +### `loader` + +A map of file extensions to [built-in loader names](https://bun.sh/docs/bundler/loaders#built-in-loaders). This can be used to quickly customize how certain file files are loaded. + +{% codetabs %} + +```ts#JavaScript +await Bun.build({ + entrypoints: ['./index.tsx'], + outdir: './out', + loader: { + ".png": "dataurl", + ".txt": "file", + }, +}) +``` + +```bash#CLI +$ bun build ./index.tsx --outdir ./out --loader .png:dataurl --loader .txt:file +``` + +{% /codetabs %} + ## Reference ```ts @@ -1003,8 +1071,8 @@ interface BuildOptions { outdir?: string; // default: no write (in-memory only) target?: "browser" | "bun" | "node"; // "browser" splitting?: boolean; // true - plugins?: BunPlugin[]; // [] - loader?: { [k in string]: Loader }; + plugins?: BunPlugin[]; // [] // See https://bun.sh/docs/bundler/plugins + loader?: { [k in string]: Loader }; // See https://bun.sh/docs/bundler/loaders manifest?: boolean; // false external?: string[]; // [] sourcemap?: "none" | "inline" | "external"; // "none" diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 03c17001c..f00c2cd45 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -933,18 +933,19 @@ declare module "bun" { scanImports(code: StringOrBuffer): Import[]; } + export type ImportKind = + | "import-statement" + | "require-call" + | "require-resolve" + | "dynamic-import" + | "import-rule" + | "url-token" + | "internal" + | "entry-point"; + export interface Import { path: string; - - kind: - | "import-statement" - | "require-call" - | "require-resolve" - | "dynamic-import" - | "import-rule" - | "url-token" - | "internal" - | "entry-point"; + kind: ImportKind; } type ModuleFormat = "esm"; // later: "cjs", "iife" @@ -954,7 +955,6 @@ declare module "bun" { outdir?: string; // output directory target?: Target; // default: "browser" format?: ModuleFormat; // later: "cjs", "iife" - naming?: | string | { @@ -968,9 +968,10 @@ declare module "bun" { // manifest?: boolean; // whether to return manifest external?: Array<string>; publicPath?: string; + define?: Record<string, string>; // origin?: string; // e.g. http://mydomain.com - // loaders?: { [k in string]: Loader }; - // sourcemap?: "none" | "inline" | "external"; // default: "none" + loader?: { [k in string]: Loader }; + sourcemap?: "none" | "inline" | "external"; // default: "none" minify?: | boolean | { @@ -979,6 +980,19 @@ declare module "bun" { identifiers?: boolean; }; // treeshaking?: boolean; + + // jsx?: + // | "automatic" + // | "classic" + // | /* later: "preserve" */ { + // runtime?: "automatic" | "classic"; // later: "preserve" + // /** Only works when runtime=classic */ + // factory?: string; // default: "React.createElement" + // /** Only works when runtime=classic */ + // fragment?: string; // default: "React.Fragment" + // /** Only works when runtime=automatic */ + // importSource?: string; // default: "react" + // }; } type BuildResult<T = Blob> = { @@ -2513,7 +2527,19 @@ declare module "bun" { * The plugin will be applied to browser builds */ | "browser"; - type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml"; + /** https://bun.sh/docs/bundler/loaders */ + type Loader = + | "js" + | "jsx" + | "ts" + | "tsx" + | "json" + | "toml" + | "file" + | "napi" + | "wasm" + | "dataurl" + | "text"; interface PluginConstraints { /** @@ -2556,7 +2582,7 @@ declare module "bun" { * * "css" will be added in a future version of Bun. */ - loader: Loader; + loader?: Loader; } interface OnLoadResultObject { @@ -2593,6 +2619,14 @@ declare module "bun" { * ``` */ path: string; + /** + * The namespace of the module being loaded + */ + namespace: string; + /** + * The default loader for this file extension + */ + loader: Loader; } type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject; @@ -2609,6 +2643,16 @@ declare module "bun" { * The module that imported the module being resolved */ importer: string; + /** + * The namespace of the importer. + */ + namespace: string; + /** + * The kind of import this resolve is for. + */ + kind: ImportKind; + // resolveDir: string; + // pluginData: any; } interface OnResolveResult { @@ -2627,7 +2671,14 @@ declare module "bun" { namespace?: string; } - type OnResolveCallback = (args: OnResolveArgs) => OnResolveResult | void; + type OnResolveCallback = ( + args: OnResolveArgs, + ) => + | OnResolveResult + | Promise<OnResolveResult | void | undefined | null> + | void + | undefined + | null; interface PluginBuilder { /** diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index cccbca9db..55c224726 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -51,6 +51,7 @@ pub const JSBundler = struct { entry_points: bun.StringSet = bun.StringSet.init(bun.default_allocator), hot: bool = false, define: bun.StringMap = bun.StringMap.init(bun.default_allocator, true), + loaders: ?Api.LoaderMap = null, dir: OwnedString = OwnedString.initEmpty(bun.default_allocator), outdir: OwnedString = OwnedString.initEmpty(bun.default_allocator), serve: Serve = .{}, @@ -60,7 +61,6 @@ pub const JSBundler = struct { server_components: ServerComponents = ServerComponents{}, names: Names = .{}, - label: OwnedString = OwnedString.initEmpty(bun.default_allocator), external: bun.StringSet = bun.StringSet.init(bun.default_allocator), source_map: options.SourceMapOption = .none, public_path: OwnedString = OwnedString.initEmpty(bun.default_allocator), @@ -73,7 +73,6 @@ pub const JSBundler = struct { .external = bun.StringSet.init(allocator), .define = bun.StringMap.init(allocator, true), .dir = OwnedString.initEmpty(allocator), - .label = OwnedString.initEmpty(allocator), .outdir = OwnedString.initEmpty(allocator), .names = .{ .owned_entry_point = OwnedString.initEmpty(allocator), @@ -88,6 +87,20 @@ pub const JSBundler = struct { this.target = target; } + if (try config.getOptionalEnum(globalThis, "sourcemap", options.SourceMapOption)) |source_map| { + this.source_map = source_map; + } + + if (try config.getOptionalEnum(globalThis, "format", options.Format)) |format| { + switch (format) { + .esm => {}, + else => { + globalThis.throwInvalidArguments("Formats besides 'esm' are not implemented", .{}); + return error.JSException; + }, + } + } + // if (try config.getOptional(globalThis, "hot", bool)) |hot| { // this.hot = hot; // } @@ -150,17 +163,12 @@ pub const JSBundler = struct { } } - if (try config.getOptional(globalThis, "label", ZigString.Slice)) |slice| { - defer slice.deinit(); - this.label.appendSliceExact(slice.slice()) catch unreachable; - } - - if (try config.getOptional(globalThis, "dir", ZigString.Slice)) |slice| { - defer slice.deinit(); - this.dir.appendSliceExact(slice.slice()) catch unreachable; - } else { - this.dir.appendSliceExact(globalThis.bunVM().bundler.fs.top_level_dir) catch unreachable; - } + // if (try config.getOptional(globalThis, "dir", ZigString.Slice)) |slice| { + // defer slice.deinit(); + // this.dir.appendSliceExact(slice.slice()) catch unreachable; + // } else { + // this.dir.appendSliceExact(globalThis.bunVM().bundler.fs.top_level_dir) catch unreachable; + // } if (try config.getOptional(globalThis, "publicPath", ZigString.Slice)) |slice| { defer slice.deinit(); @@ -198,6 +206,84 @@ pub const JSBundler = struct { } } + if (try config.getObject(globalThis, "define")) |define| { + if (!define.isObject()) { + globalThis.throwInvalidArguments("define must be an object", .{}); + return error.JSException; + } + + var define_iter = JSC.JSPropertyIterator(.{ + .skip_empty_name = true, + .include_value = true, + }).init(globalThis, define.asObjectRef()); + defer define_iter.deinit(); + + while (define_iter.next()) |prop| { + const property_value = define_iter.value; + const value_type = property_value.jsType(); + + if (!value_type.isStringLike()) { + globalThis.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop}); + return error.JSException; + } + + var val = JSC.ZigString.init(""); + property_value.toZigString(&val, globalThis); + if (val.len == 0) { + val = JSC.ZigString.init("\"\""); + } + + try this.define.insert(prop.slice(), val.slice()); + } + } + + if (try config.getObject(globalThis, "loader")) |loaders| { + if (!loaders.isUndefinedOrNull()) { + if (!loaders.isObject()) { + globalThis.throwInvalidArguments("loader must be an object", .{}); + return error.JSException; + } + + var loader_iter = JSC.JSPropertyIterator(.{ + .skip_empty_name = true, + .include_value = true, + }).init(globalThis, loaders.asObjectRef()); + defer loader_iter.deinit(); + + var loader_names = try allocator.alloc(string, loader_iter.len); + var loader_values = try allocator.alloc(Api.Loader, loader_iter.len); + + while (loader_iter.next()) |prop| { + if (prop.len == 0 or prop.ptr[0] != '.') { + globalThis.throwInvalidArguments("loader property names must be file extensions, such as '.txt'", .{}); + return error.JSException; + } + + loader_names[loader_iter.i] = prop.slice(); + var property_value = loader_iter.value; + var value_type = property_value.jsType(); + if (!value_type.isStringLike()) { + globalThis.throwInvalidArguments("loader \"{s}\" must be a string", .{prop}); + return error.JSException; + } + + var val = JSC.ZigString.init(""); + property_value.toZigString(&val, globalThis); + if (options.Loader.fromString(val.slice())) |loader| { + loader_values[loader_iter.i] = loader.toAPI(); + } else { + globalThis.throwInvalidArguments("loader \"{s}\" is not a valid loader", .{val}); + return error.JSException; + } + } + + this.loaders = Api.LoaderMap{ + .extensions = loader_names, + .loaders = loader_values, + }; + } + } + if (try config.getArray(globalThis, "plugins")) |array| { var iter = array.arrayIterator(globalThis); while (iter.next()) |plugin| { @@ -355,7 +441,6 @@ pub const JSBundler = struct { self.serve.deinit(allocator); self.server_components.deinit(allocator); self.names.deinit(); - self.label.deinit(); self.outdir.deinit(); self.public_path.deinit(); } diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index f1b00f191..2ac6948d1 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -565,7 +565,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std } } else { var sourcemap = flag.toSlice(globalThis, allocator); - if (options.SourceMapOption.map.get(sourcemap.slice())) |source| { + if (options.SourceMapOption.Map.get(sourcemap.slice())) |source| { transpiler.transform.source_map = source.toAPI(); } else { JSC.throwInvalidArguments("sourcemap must be one of \"inline\", \"external\", or \"none\"", .{}, globalObject, exception); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 12cc81118..71409da4f 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -4009,9 +4009,10 @@ pub const JSValue = enum(JSValueReprInt) { pub fn getOptionalEnum(this: JSValue, globalThis: *JSGlobalObject, comptime property_name: []const u8, comptime Enum: type) !?Enum { if (get(this, globalThis, property_name)) |prop| { + if (prop.isEmptyOrUndefinedOrNull()) + return null; return try toEnum(prop, globalThis, property_name, Enum); } - return null; } diff --git a/src/bun.js/builtins/js/BundlerPlugin.js b/src/bun.js/builtins/js/BundlerPlugin.js index 64c655bbe..ec8fee397 100644 --- a/src/bun.js/builtins/js/BundlerPlugin.js +++ b/src/bun.js/builtins/js/BundlerPlugin.js @@ -66,7 +66,9 @@ function runOnResolvePlugins( path: inputPath, importer, namespace: inputNamespace, + // resolveDir kind, + // pluginData }); while ( @@ -368,6 +370,8 @@ function runOnLoadPlugins(internalID, path, namespace, defaultLoaderId) { var result = callback({ path, namespace, + // suffix + // pluginData loader: defaultLoader, }); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index aad40b961..6bb307381 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1555,6 +1555,7 @@ pub const BundleV2 = struct { ); bundler.options.jsx = config.jsx; + bundler.options.loaders = try options.loadersFromTransformOptions(allocator, config.loaders, config.target); 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; diff --git a/src/cli.zig b/src/cli.zig index 3f796e798..171593246 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -141,7 +141,6 @@ pub const Arguments = struct { 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 --target dependent") catch unreachable, @@ -194,6 +193,7 @@ pub const Arguments = struct { pub const params = public_params ++ debug_params; const build_only_params = [_]ParamType{ + clap.parseParam("--format <STR> Specifies the module format to build to. Only esm is supported.") catch unreachable, clap.parseParam("--outdir <STR> Default to \"dist\" if multiple files") catch unreachable, clap.parseParam("--outfile <STR> Write to a file") catch unreachable, clap.parseParam("--splitting Enable code splitting") catch unreachable, @@ -489,6 +489,20 @@ pub const Arguments = struct { } } + if (args.option("--format")) |format_str| { + const format = options.Format.fromString(format_str) orelse { + Output.prettyErrorln("<r><red>error<r>: Invalid format - must be esm, cjs, or iife", .{}); + Global.crash(); + }; + switch (format) { + .esm => {}, + else => { + Output.prettyErrorln("<r><red>error<r>: Formats besides 'esm' are not implemented", .{}); + Global.crash(); + }, + } + } + if (args.flag("--splitting")) { ctx.bundler_options.code_splitting = true; } @@ -569,9 +583,8 @@ pub const Arguments = struct { var jsx_fragment = args.option("--jsx-fragment"); var jsx_import_source = args.option("--jsx-import-source"); var jsx_runtime = args.option("--jsx-runtime"); - var jsx_production = args.flag("--jsx-production"); const react_fast_refresh = switch (comptime cmd) { - .DevCommand => !(args.flag("--disable-react-fast-refresh") or jsx_production), + .DevCommand => !args.flag("--disable-react-fast-refresh"), else => true, }; @@ -689,7 +702,7 @@ pub const Arguments = struct { jsx_fragment != null or jsx_import_source != null or jsx_runtime != null or - jsx_production or !react_fast_refresh) + !react_fast_refresh) { var default_factory = "".*; var default_fragment = "".*; @@ -700,7 +713,7 @@ pub const Arguments = struct { .fragment = constStrToU8(jsx_fragment orelse &default_fragment), .import_source = constStrToU8(jsx_import_source orelse &default_import_source), .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic, - .development = !jsx_production, + .development = false, .react_fast_refresh = react_fast_refresh, }; } else { @@ -709,7 +722,7 @@ pub const Arguments = struct { .fragment = constStrToU8(jsx_fragment orelse opts.jsx.?.fragment), .import_source = constStrToU8(jsx_import_source orelse opts.jsx.?.import_source), .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else opts.jsx.?.runtime, - .development = !jsx_production, + .development = false, .react_fast_refresh = react_fast_refresh, }; } diff --git a/src/options.zig b/src/options.zig index 958b36874..fee9035c4 100644 --- a/src/options.zig +++ b/src/options.zig @@ -636,6 +636,52 @@ pub const Target = enum { }; }; +pub const Format = enum { + esm, + cjs, + iife, + + pub const Map = ComptimeStringMap( + Format, + .{ + .{ + "esm", + Format.esm, + }, + .{ + "cjs", + Format.cjs, + }, + .{ + "iife", + Format.iife, + }, + }, + ); + + pub fn fromJS(global: *JSC.JSGlobalObject, format: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Format { + if (format.isUndefinedOrNull()) return null; + + if (!format.jsType().isStringLike()) { + JSC.throwInvalidArguments("Format must be a string", .{}, global, exception); + return null; + } + + var zig_str = JSC.ZigString.init(""); + format.toZigString(&zig_str, global); + if (zig_str.len == 0) return null; + + return fromString(zig_str.slice()) orelse { + JSC.throwInvalidArguments("Invalid format - must be esm, cjs, or iife", .{}, global, exception); + return null; + }; + } + + pub fn fromString(slice: string) ?Format { + return Map.getWithEql(slice, strings.eqlComptime); + } +}; + pub const Loader = enum(u8) { jsx, js, @@ -1271,7 +1317,7 @@ pub const SourceMapOption = enum { }; } - pub const map = ComptimeStringMap(SourceMapOption, .{ + pub const Map = ComptimeStringMap(SourceMapOption, .{ .{ "none", .none }, .{ "inline", .@"inline" }, .{ "external", .external }, diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index f88700372..58b7846d0 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -139,7 +139,6 @@ describe("bundler", () => { }, }); itBundled("cjs2esm/ModuleExportsEqualsRuntimeCondition", { - notImplemented: true, files: { "/entry.js": /* js */ ` import { foo } from 'lib'; diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 6020ba2ec..30721637f 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -50,6 +50,7 @@ describe("bundler", () => { run: true, }); itBundled("edgecase/BunPluginTreeShakeImport", { + notImplemented: true, // This only appears at runtime and not with bun build, even with --transform files: { "/entry.ts": /* js */ ` @@ -71,8 +72,7 @@ describe("bundler", () => { } `, }, - external: ["external"], - mode: "transform", + bundling: false, minifySyntax: true, target: "bun", run: { file: "/entry.ts" }, @@ -88,14 +88,6 @@ describe("bundler", () => { capture: ["`\\\\?`", "hello`\\\\?`"], target: "bun", }); - itBundled("edgecase/StringNullBytes", { - files: { - "/entry.ts": /* js */ ` - capture("Hello\0"); - `, - }, - capture: ['"Hello\0"'], - }); // https://github.com/oven-sh/bun/issues/2699 itBundled("edgecase/ImportNamedFromExportStarCJS", { files: { @@ -198,7 +190,7 @@ describe("bundler", () => { console.log(foo); `, }, - external: ["*"], + bundling: false, }); itBundled("edgecase/ImportNamespaceAndDefault", { files: { @@ -207,7 +199,7 @@ describe("bundler", () => { console.log(def2, JSON.stringify(ns2)) `, }, - external: ["*"], + bundling: false, runtimeFiles: { "/c.js": /* js */ ` export default 1 @@ -282,11 +274,11 @@ describe("bundler", () => { ".cool": "wtf", }, bundleErrors: { - // todo: get the exact error - "<bun>": ["InvalidLoader"], + "<bun>": ['invalid loader "wtf", expected one of:'], }, }); itBundled("edgecase/ScriptTagEscape", { + notImplemented: true, files: { "/entry.js": /* js */ ` console.log('<script></script>'); @@ -428,4 +420,220 @@ describe("bundler", () => { stdout: "123", }, }); + itBundled("edgecase/TSConfigPathsStarOnlyInLeft", { + files: { + "/entry.ts": /* ts */ ` + import test0 from 'test0/hello' + console.log(test0) + `, + "/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "test0/*": ["./test0-success.ts"] + } + } + } + `, + "/test0-success.ts": `export default 'success'`, + }, + run: { + stdout: "success", + }, + }); + itBundled("edgecase/TSConfigPathStarAnywhere", { + files: { + "/entry.ts": /* ts */ ` + import test0 from 'test3/foo' + console.log(test0) + `, + "/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "t*t3/foo": ["./test3-succ*s.ts"], + } + } + } + `, + "/test3-success.ts": `export default 'success'`, + }, + run: { + stdout: "success", + }, + }); + itBundled("edgecase/StaticClassNameIssue2806", { + files: { + "/entry.ts": /* ts */ ` + new class C { + set baz(x) { + C.foo = x; + C.bar; + } + static get bar() { + console.log(C.foo); + } + }().baz = "PASS"; + + new class C { + set baz(x) { + C.foo = x; + C.bar; + } + static get bar() { + console.log(C.foo); + } + }().baz = "Hello World"; + `, + }, + minifyIdentifiers: true, + run: { + stdout: "PASS\nHello World", + }, + }); + itBundled("edgecase/DCEVarRedeclarationIssue2814A", { + files: { + "/entry.ts": /* ts */ ` + var a = 1; + if (false) { + var a; + } + console.log(a); + `, + }, + target: "bun", + run: { + stdout: `1`, + }, + }); + itBundled("edgecase/DCEVarRedeclarationIssue2814B", { + files: { + "/entry.ts": /* ts */ ` + var a = 1; + switch ("foo") { + case "foo": + var a; + } + console.log(a); + `, + }, + target: "bun", + run: { + stdout: `1`, + }, + }); + itBundled("edgecase/DCEVarRedeclarationIssue2814C", { + files: { + "/entry.ts": /* ts */ ` + "use strict"; + var a = 1; + { + var a; + } + console.log(a); + `, + }, + target: "bun", + run: { + stdout: `1`, + }, + }); + itBundled("edgecase/DCEVarRedeclarationIssue2814", { + files: { + "/entry.ts": /* ts */ ` + "use strict"; + var a = 1, b = 2; + switch (b++) { + case b: + var c = a; + var a; + break; + } + console.log(a); + + var x = 123, y = 45; + switch (console) { + case 456: + var x = 789, y = 0; + } + var y = 67; + console.log(x, y); + + var z = 123; + switch (console) { + default: + var z = typeof z; + } + console.log(z); + + var A = 1, B = 2; + switch (A) { + case A: + var B; + break; + case B: + break; + } + console.log(B); + `, + }, + target: "bun", + run: { + stdout: ` + 1 + 123 67 + number + 2 + `, + }, + }); + itBundled("edgecase/DCEVarRedeclarationIssue2815", { + files: { + "/entry.ts": /* ts */ ` + var x = 1; + try { + console.blog; + } catch (x) { + var x = 2; + } + console.log(x); + + var e = 3; + try { + console.log("try2"); + } catch (e) { + var e = 4; + } + console.log(e); + + try { + var z = 5; + throw "try3"; + } catch (w) { + z += w; + var w = 6; + } + console.log(z); + + var c = 8; + try { + "try4"; + } catch (c) { + var c = 9; + } + console.log(c); + `, + }, + target: "bun", + run: { + stdout: ` + 1 + 123 67 + number + 2 + `, + }, + }); }); diff --git a/test/bundler/bundler_plugin.test.ts b/test/bundler/bundler_plugin.test.ts index 74c5650cc..a22ec49e3 100644 --- a/test/bundler/bundler_plugin.test.ts +++ b/test/bundler/bundler_plugin.test.ts @@ -57,6 +57,20 @@ describe("bundler", () => { stdout: "HELLO WORLD", }, }); + itBundled("plugin/LoadImplicitLoader", { + files: loadFixture, + plugins(builder) { + builder.onLoad({ filter: /\.magic$/ }, async args => { + const text = await Bun.file(args.path).text(); + return { + contents: `export const foo = ${JSON.stringify(text.toUpperCase())};`, + }; + }); + }, + run: { + stdout: "HELLO WORLD", + }, + }); // Load Plugin Errors itBundled("plugin/LoadThrow", { @@ -66,14 +80,21 @@ describe("bundler", () => { throw new Error("error here"); }); }, + bundleErrors: { + "/foo.magic": [`error here`], + }, }); itBundled("plugin/LoadThrowPrimative", { files: loadFixture, + notImplemented: true, plugins(builder) { builder.onLoad({ filter: /\.magic$/ }, args => { throw "123"; }); }, + bundleErrors: { + "/foo.magic": [`123`], + }, }); itBundled("plugin/LoadThrowAsync", { files: loadFixture, @@ -82,14 +103,21 @@ describe("bundler", () => { throw new Error("error here"); }); }, + bundleErrors: { + "/foo.magic": [`error here`], + }, }); itBundled("plugin/LoadThrowPrimativeAsync", { files: loadFixture, + notImplemented: true, plugins(builder) { builder.onLoad({ filter: /\.magic$/ }, async args => { throw 123; }); }, + bundleErrors: { + "/foo.magic": [`123`], + }, }); // Load Plugin Errors @@ -100,14 +128,44 @@ describe("bundler", () => { throw new Error("error here"); }); }, + bundleErrors: { + "/index.ts": [`error here`], + }, }); itBundled("plugin/ResolveThrowPrimative", { files: resolveFixture, + notImplemented: true, plugins(builder) { builder.onResolve({ filter: /\.magic$/ }, args => { throw "123"; }); }, + bundleErrors: { + "/index.ts": [`123`], + }, + }); + itBundled("plugin/ResolveThrowAsync", { + files: resolveFixture, + plugins(builder) { + builder.onResolve({ filter: /\.magic$/ }, async args => { + throw new Error("error here"); + }); + }, + bundleErrors: { + "/index.ts": [`error here`], + }, + }); + itBundled("plugin/ResolveThrowPrimativeAsync", { + files: resolveFixture, + notImplemented: true, + plugins(builder) { + builder.onResolve({ filter: /\.magic$/ }, async args => { + throw 123; + }); + }, + bundleErrors: { + "/index.ts": [`123`], + }, }); // @@ -223,7 +281,6 @@ describe("bundler", () => { builder.onLoad({ filter: /namespace_path/, namespace: "my_namespace" }, args => { expect(args.path).toBe("namespace_path"); expect(args.namespace).toBe("my_namespace"); - expect(args.suffix).toBeFalsy(); return { contents: "export const foo = 'foo';", @@ -240,6 +297,8 @@ describe("bundler", () => { }; }); itBundled("plugin/ResolveAndLoadNamespaceNested", ({ root }) => { + let counter1 = 0; + let counter2 = 0; return { files: { "index.ts": /* ts */ ` @@ -251,6 +310,9 @@ describe("bundler", () => { `, }, plugins(builder) { + builder.onResolve({ filter: /.*/ }, args => { + counter1++; + }); builder.onResolve({ filter: /magic:some_string/ }, args => { return { path: "namespace_path", @@ -259,21 +321,32 @@ describe("bundler", () => { }); // the path given is already resolved, so it should not re-resolve builder.onResolve({ filter: /namespace_path/, namespace: "my_namespace" }, args => { - throw new Error("SHOULD NOT BE CALLED"); + throw new Error("SHOULD NOT BE CALLED 1, " + JSON.stringify(args)); }); builder.onResolve({ filter: /namespace_path/ }, args => { - throw new Error("SHOULD NOT BE CALLED"); + throw new Error("SHOULD NOT BE CALLED 2, " + JSON.stringify(args)); }); - builder.onLoad({ filter: /namespace_path/, namespace: "my_namespace" }, args => { + // load + builder.onLoad({ filter: /.*/, namespace: "my_namespace" }, args => { expect(args.path).toBe("namespace_path"); expect(args.namespace).toBe("my_namespace"); - expect(args.suffix).toBeFalsy(); return { contents: "import 'nested_import';export const foo = 'foo';", loader: "js", }; }); + // nested_import should not be resolved as a file namespace + builder.onResolve({ filter: /nested_import/, namespace: "file" }, args => { + throw new Error("SHOULD NOT BE CALLED 3, " + JSON.stringify(args)); + }); + builder.onResolve({ filter: /nested_import/, namespace: "my_namespace" }, args => { + expect(args.path).toBe("nested_import"); + expect(args.namespace).toBe("my_namespace"); + // gonna let this passthrough + counter2 += 1; + }); + // but it can be resolved with no namespace filter builder.onResolve({ filter: /nested_import/ }, args => { expect(args.path).toBe("nested_import"); expect(args.namespace).toBe("my_namespace"); @@ -282,10 +355,20 @@ describe("bundler", () => { namespace: "file", }; }); + builder.onResolve({ filter: /.*/ }, args => { + // entrypoint should hit this but this is a catch all + if (args.kind === "import-statement") { + throw new Error("SHOULD NOT BE CALLED 4, " + JSON.stringify(args)); + } + }); }, run: { stdout: "foo", }, + onAfterBundle(api) { + expect(counter1).toBe(3); + expect(counter2).toBe(1); + }, }; }); itBundled("plugin/ResolveOverrideFile", ({ root }) => { @@ -314,9 +397,10 @@ describe("bundler", () => { }, }; }); - itBundled("plugin/ResolveTwoImportsOnce", ({ root }) => { + itBundled("plugin/ResolveOnceWhenSameFile", ({ root }) => { let onResolveCount = 0; return { + notImplemented: true, files: { "index.ts": /* ts */ ` import * as foo from "./foo.ts"; @@ -383,7 +467,7 @@ describe("bundler", () => { stdout: "this string should exist once this string should exist once", }, onAfterBundle(api) { - expect(importers).toEqual([root + "/one.ts", root + "/two.ts"]); + expect(importers.sort()).toEqual([root + "/one.ts", root + "/two.ts"]); expect(onResolveCount).toBe(2); const contents = api.readFile("/out.js"); expect([...contents.matchAll(/this string should exist once/g)].length).toBe(1); @@ -562,15 +646,111 @@ describe("bundler", () => { }, }, ], + run: true, + onAfterBundle(api) { + expect(resolveCount).toBe(5050); + expect(loadCount).toBe(101); + }, + }; + }); + itBundled("plugin/ManyPlugins", ({ root }) => { + const pluginCount = 4000; + let resolveCount = 0; + let loadCount = 0; + return { + files: { + "index.ts": /* ts */ ` + import { foo as foo1 } from "plugin1:file"; + import { foo as foo2 } from "plugin4000:file"; + console.log(foo1, foo2); + `, + }, + plugins: Array.from({ length: pluginCount }).map((_, i) => ({ + name: `${i}`, + setup(builder) { + builder.onResolve({ filter: new RegExp(`^plugin${i}:file$`) }, args => { + resolveCount++; + return { + path: `plugin${i}:file`, + namespace: `plugin${i}`, + }; + }); + builder.onLoad({ filter: new RegExp(`^plugin${i}:file$`), namespace: `plugin${i}` }, args => { + loadCount++; + return { + contents: `export const foo = ${i};`, + loader: "js", + }; + }); + }, + })), run: { - stdout: "101 102", + stdout: `${pluginCount - 1} ${pluginCount - 1}`, }, onAfterBundle(api) { - expect(resolveCount).toBe(103); - expect(loadCount).toBe(102); + expect(resolveCount).toBe(pluginCount * 2); + expect(loadCount).toBe(pluginCount); + }, + }; + }); + itBundled("plugin/NamespaceOnLoadBug", () => { + return { + files: { + "index.ts": /* ts */ ` + import { foo } from "plugin:file"; + console.log(foo); + `, + }, + plugins(build) { + build.onResolve({ filter: /^plugin:/ }, args => { + return { + path: args.path, + namespace: "this", + }; + }); + build.onLoad({ filter: /.*/, namespace: "that" }, args => { + return { + contents: "export const foo = 'FAILED';", + loader: "js", + }; + }); + build.onLoad({ filter: /.*/, namespace: "this" }, args => { + return { + contents: `export const foo = '${args.namespace}';`, + loader: "js", + }; + }); + }, + }; + }); + itBundled("plugin/EntrypointResolve", ({ root }) => { + return { + files: {}, + entryPointsRaw: ["plugin"], + plugins(build) { + build.onResolve({ filter: /^plugin$/ }, args => { + expect(args.path).toBe("plugin"); + expect(args.importer).toBe(""); + expect(args.kind).toBe("entry-point"); + expect(args.namespace).toBe(""); + // expect(args.pluginData).toEqual(undefined); + // expect(args.resolveDir).toEqual(root); + return { + path: args.path, + namespace: "plugin", + }; + }); + build.onLoad({ filter: /.*/, namespace: "plugin" }, args => { + console.log(args); + return { + contents: `console.log("it works")`, + }; + }); + }, + run: { + file: "./out/plugin.js", + stdout: "it works", }, }; }); }); - -// TODO: add async on resolve stuff diff --git a/test/bundler/bundler_string.test.ts b/test/bundler/bundler_string.test.ts new file mode 100644 index 000000000..fadd52a92 --- /dev/null +++ b/test/bundler/bundler_string.test.ts @@ -0,0 +1,137 @@ +import assert from "assert"; +import dedent from "dedent"; +import { itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +interface TemplateStringTest { + expr: string; + print?: string | boolean; // expect stdout + capture?: string | boolean; // expect literal transpilation + captureRaw?: string; // expect raw transpilation +} + +const templateStringTests: Record<string, TemplateStringTest> = { + // note for writing tests: .print is .trim()'ed due to how run.stdout works + Empty: { expr: '""', captureRaw: '""' }, + NullByte: { expr: '"hello\0"', captureRaw: '"hello\0"' }, + EmptyTemplate: { expr: "``", captureRaw: "``" }, + ConstantTemplate: { expr: "`asdf`", captureRaw: "`asdf`" }, + AddConstant: { expr: "`${7 + 6}`", capture: true }, + AddConstant2: { expr: "`${7 + 6 + 96}`", capture: true }, + AddConstant3: { expr: "`${0.1 + 0.2}`", print: true }, + SubtractConstant: { expr: "`${7 - 6}`", capture: true }, + SubtractConstant2: { expr: "`${7 - 6 - 10}`", capture: true }, + MultiplyConstant: { expr: "`${7 * 6}`", capture: true }, + MultiplyConstant2: { expr: "`${7 * 6 * 2}`", capture: true }, + MultiplyConstant3: { expr: "`${7.5 * 6.02}`", print: true }, + DivideConstant: { expr: "`${7 / 6}`", print: true }, + DivideConstant2: { expr: "`${7 / 6 / 2}`", print: true }, + DivideConstant3: { expr: "`${7.5 / 6.02}`", print: true }, + Exponent1: { expr: "`${1e0}`", capture: true }, + Exponent2: { expr: "`${1e1}`", capture: true }, + Exponent3: { expr: "`${0e1337}`", capture: true }, + Exponent4: { expr: "`${-1e0}`", capture: true }, + BigExponent1: { expr: "`${1e20}`", print: "100000000000000000000" }, + BigExponent2: { expr: "`${1e21}`", print: "1e+21" }, + BigNumber1: { expr: "`${999999999999999934463.9999999}`", print: "999999999999999900000" }, + BigNumber2: { expr: "`${999999999999999934464.0000000}`", print: "1e+21" }, + True: { expr: "`${true}`", capture: true }, + False: { expr: "`${false}`", capture: true }, + BigInt: { expr: "`${1n}`", print: "1" }, + LongBigInt: { + expr: "`${-" + "1234".repeat(1000) + "n}`", + print: "-" + "1234".repeat(1000), + }, + BigIntAdd: { expr: "`${1n + 2n}`", print: "3" }, + BigIntSubtract: { expr: "`${1n - 2n}`", print: "-1" }, + BigIntMultiply: { expr: "`${2n * 3n}`", print: "6" }, + BigIntDivide: { expr: "`${6n / 2n}`", print: "3" }, + BigIntModulo: { expr: "`${6n % 4n}`", print: "2" }, + BigIntExponent: { expr: "`${2n ** 3n}`", print: "8" }, + ArrowFunction: { expr: "`${() => 123}`", captureRaw: "`${(" }, // capture is weird in this scenario + Function: { expr: "`${function() { return 123; }}`", captureRaw: "`${function(" }, + Identifier: { expr: "`${ident}`", captureRaw: "`${ident}`" }, + IdentifierAdd: { expr: "`${ident + ident}`", captureRaw: "`${ident+ident}`" }, + IdentifierConstAdd: { expr: "`${2 + ident}`", captureRaw: "`${ident+ident}`" }, + EscapeIssue1: { + expr: `\`\\abc\${ident}\``, + captureRaw: `\`abc\${ident}\``, + }, + EscapeIssue2: { + expr: `\`\\abc\${ident}\``, + captureRaw: `\`abc\${ident}\``, + }, + TernaryWithEscapeVariable: { + expr: '`${"1"}\\${${VARIABLE ? "SOMETHING" : ""}`', + captureRaw: '`${"1"}\\${${VARIABLE ? "SOMETHING" : ""}`', + }, + TernaryWithEscapeTrue: { + expr: '`${"1"}\\${${true ? "SOMETHING" : ""}`', + captureRaw: '`${"1"}\\${${"SOMETHING"}`', + }, + TernaryWithEscapeFalse: { + expr: '`${"1"}\\${${false ? "SOMETHING" : ""}`', + captureRaw: '`${"1"}\\${${""}`', + }, + Fold: { expr: "`a${'b'}c${'d'}e`", capture: true }, + FoldNested1: { expr: "`a${`b`}c${`${'d'}`}e`", capture: true }, + FoldNested2: { expr: "`a${`b`}c${`1${'d'}`}e`", capture: true }, + FoldNested3: { expr: "`a${`b`}c${`${'1'}${'d'}`}e`", capture: true }, + FoldNested4: { expr: "`a${`b`}c${`${`${`${'d'}`}`}`}e`", capture: true }, + FoldNested5: { expr: "`\\$${`d`}`", print: true }, // could be captured + FoldNested6: { expr: "`a\0${5}c\\${{$${`d`}e`", capture: true }, + EscapedDollar: { expr: "`\\${'a'}`", captureRaw: "`\\${'a'}`" }, + EscapedDollar2: { expr: "`\\${'a'}\\${'b'}`", captureRaw: "`\\${'a'}\\${'b'}`" }, +}; + +describe("bundler", () => { + for (const key in templateStringTests) { + const test = templateStringTests[key]; + if ([test.capture, test.captureRaw, test.print].filter(x => x !== undefined).length !== 1) { + throw new Error(`Exactly one of capture or print must be defined for 'template/${key}'`); + } + let captureRaw = test.captureRaw; + if (test.capture === true) captureRaw = JSON.stringify(eval(test.expr)) as string; + else if (test.capture !== undefined) captureRaw = JSON.stringify(test.capture); + if (test.print === true) test.print = eval(test.expr) as string; + + itBundled( + `string/${key}`, + captureRaw !== undefined + ? { + files: { + "index.ts": dedent` + capture(${test.expr}); + `, + }, + capture: [captureRaw], + minifySyntax: true, + minifyWhitespace: true, + } + : { + files: { + "index.ts": dedent` + const capture = x => x; + console.log(capture(${test.expr})); + `, + }, + run: { + stdout: test.print as string, + }, + minifySyntax: true, + minifyWhitespace: true, + onAfterBundle(api) { + const capture = api.captureFile("out.js"); + if (capture[0] === JSON.stringify(test.print)) { + // this is to tell the dev to change the test to use .capture + // as that is a more strict test (checking literal output) + // and if the test passes with this, we should be testing for that. + throw new Error( + `Test 'string/${key}': Passes capture test when the test only defines print. Rename .print to .capture on the test to fix this.`, + ); + } + }, + }, + ); + } +}); diff --git a/test/bundler/esbuild/dce.test.ts b/test/bundler/esbuild/dce.test.ts index de68742c6..a35fb68ca 100644 --- a/test/bundler/esbuild/dce.test.ts +++ b/test/bundler/esbuild/dce.test.ts @@ -889,9 +889,15 @@ describe("bundler", () => { run: { stdout: "unused import", }, + assetNaming: "[name].[ext]", + outdir: "/out", loader: { ".data": "file", }, + onAfterBundle(api) { + const fs = require("fs"); + expect(fs.readdirSync(api.outdir)).toEqual(["entry.js"]); + }, }); itBundled("dce/RemoveUnusedImportMeta", { files: { @@ -1038,7 +1044,6 @@ describe("bundler", () => { }, }); itBundled("dce/DeadCodeFollowingJump", { - notImplemented: true, files: { "/entry.js": /* js */ ` function testReturn() { @@ -1273,7 +1278,7 @@ describe("bundler", () => { let POSSIBLE_REMOVAL_1 = class { [{ toString() {} }] = 'x' } `, }, - mode: "transform", + bundling: false, treeShaking: true, dce: true, }); @@ -1309,7 +1314,7 @@ describe("bundler", () => { let POSSIBLE_REMOVAL_1 = class { static [{ toString() {} }] = 'x' } `, }, - mode: "transform", + bundling: false, treeShaking: true, dce: true, }); @@ -1392,7 +1397,6 @@ describe("bundler", () => { format: "iife", }); itBundled("dce/TreeShakingNoBundleESM", { - notImplemented: true, files: { "/entry.js": /* js */ ` function keep() {} @@ -1401,7 +1405,7 @@ describe("bundler", () => { `, }, format: "esm", - mode: "transform", + bundling: false, treeShaking: true, dce: true, }); @@ -1416,7 +1420,7 @@ describe("bundler", () => { dce: true, format: "cjs", treeShaking: true, - mode: "transform", + bundling: false, }); itBundled("dce/TreeShakingNoBundleIIFE", { files: { @@ -1429,7 +1433,7 @@ describe("bundler", () => { dce: true, format: "iife", treeShaking: true, - mode: "transform", + bundling: false, }); itBundled("dce/TreeShakingInESMWrapper", { files: { @@ -1692,8 +1696,7 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", - external: ["a", "b", "c"], + bundling: false, dce: true, }); itBundled("dce/RemoveUnusedImportsEvalTS", { @@ -1707,7 +1710,7 @@ describe("bundler", () => { }, dce: true, minifySyntax: true, - mode: "transform", + bundling: false, }); itBundled("dce/DCEClassStaticBlocks", { files: { @@ -2192,7 +2195,7 @@ describe("bundler", () => { " delete id_REMOVE((foo(), bar())),\n" + "]", }, - mode: "transform", + bundling: false, minifySyntax: true, treeShaking: true, dce: true, @@ -2561,7 +2564,7 @@ describe("bundler", () => { "/ts-namespace-no-eval.ts", "/ts-namespace-eval.ts", ], - mode: "transform", + bundling: false, minifySyntax: true, dce: true, dceKeepMarkerCount: { @@ -2683,7 +2686,6 @@ describe("bundler", () => { dce: true, }); itBundled("dce/MultipleDeclarationTreeShaking", { - notImplemented: true, files: { "/var2.js": /* js */ ` var x = 1 @@ -2722,7 +2724,6 @@ describe("bundler", () => { ], }); itBundled("dce/MultipleDeclarationTreeShakingMinifySyntax", { - notImplemented: true, files: { "/var2.js": /* js */ ` var x = 1 diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 03a7f1adf..39cb293be 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -233,7 +233,7 @@ describe("bundler", () => { }, entryPoints: ["/a.js", "/b.js", "/c.js", "/d.js", "/e.js"], mode: "bundle", - external: ["*"], + bundling: false, runtimeFiles: { "./out/f.js": /* js */ ` export const f = 987; @@ -309,11 +309,10 @@ describe("bundler", () => { } `, }, - // mode: "transform", run: { file: "/test.js", }, - external: ["*"], + bundling: false, } as const; itBundled("default/ImportFormsWithNoBundle", { ...importFormsConfig, @@ -435,59 +434,59 @@ describe("bundler", () => { "/foo.js": [`Detected cycle while resolving import "b"`, `Detected cycle while resolving import "d"`], }, }); - itBundled("default/JSXImportsCommonJS", { - notImplemented: true, // jsx in bun is too different to esbuild - files: { - "/entry.jsx": /* jsx */ ` - import {elem, frag} from './custom-react' - console.log(<div/>, <>fragment</>) - `, - "/custom-react.js": /* js */ ` - module.exports = { - elem: (...args) => console.log('elem', ...args), - frag: 'frag', - }; - `, - }, - jsx: { - factory: "elem", - fragment: "frag", - automaticRuntime: true, - }, - run: { - stdout: ` - elem div null - elem frag null fragment - undefined undefined - `, - }, - }); - itBundled("default/JSXImportsES6", { - notImplemented: true, // jsx in bun is too different to esbuild - files: { - "/entry.jsx": /* jsx */ ` - import {elem, frag} from './custom-react' - console.log(<div/>, <>fragment</>) - `, - "/custom-react.js": /* js */ ` - export function elem(...args) { - console.log('elem', ...args) - } - export const frag = "frag"; - `, - }, - jsx: { - factory: "elem", - fragment: "frag", - }, - run: { - stdout: ` - elem div null - elem frag null fragment - undefined undefined - `, - }, - }); + // itBundled("default/JSXImportsCommonJS", { + // notImplemented: true, // jsx in bun is too different to esbuild + // files: { + // "/entry.jsx": /* jsx */ ` + // import {elem, frag} from './custom-react' + // console.log(<div/>, <>fragment</>) + // `, + // "/custom-react.js": /* js */ ` + // module.exports = { + // elem: (...args) => console.log('elem', ...args), + // frag: 'frag', + // }; + // `, + // }, + // jsx: { + // factory: "elem", + // fragment: "frag", + // automaticRuntime: true, + // }, + // run: { + // stdout: ` + // elem div null + // elem frag null fragment + // undefined undefined + // `, + // }, + // }); + // itBundled("default/JSXImportsES6", { + // notImplemented: true, // jsx in bun is too different to esbuild + // files: { + // "/entry.jsx": /* jsx */ ` + // import {elem, frag} from './custom-react' + // console.log(<div/>, <>fragment</>) + // `, + // "/custom-react.js": /* js */ ` + // export function elem(...args) { + // console.log('elem', ...args) + // } + // export const frag = "frag"; + // `, + // }, + // jsx: { + // factory: "elem", + // fragment: "frag", + // }, + // run: { + // stdout: ` + // elem div null + // elem frag null fragment + // undefined undefined + // `, + // }, + // }); // note: esbuild treats .js as non-jsx // bun treats js as jsx // so the extension has to be .mjs or .cjs to disable JSX. @@ -504,105 +503,105 @@ describe("bundler", () => { outdir: "/out", entryPoints: ["/entry.mjs", "/entry.cjs"], }); - itBundled("default/JSXConstantFragments", { - notImplemented: true, // jsx in bun is too different to esbuild - files: { - "/entry.js": /* js */ ` - import './default' - import './null' - import './boolean' - import './number' - import './string-single-empty' - import './string-double-empty' - import './string-single-punctuation' - import './string-double-punctuation' - import './string-template' - `, - "/default.jsx": `console.log(<></>)`, - "/null.jsx": `console.log(<></>) // @jsxFrag null`, - "/boolean.jsx": `console.log(<></>) // @jsxFrag true`, - "/number.jsx": `console.log(<></>) // @jsxFrag 123`, - "/string-single-empty.jsx": `console.log(<></>) // @jsxFrag ''`, - "/string-double-empty.jsx": `console.log(<></>) // @jsxFrag ""`, - "/string-single-punctuation.jsx": `console.log(<></>) // @jsxFrag '['`, - "/string-double-punctuation.jsx": `console.log(<></>) // @jsxFrag "["`, - "/string-template.jsx": "console.log(<></>) // @jsxFrag ``", + // itBundled("default/JSXConstantFragments", { + // notImplemented: true, // jsx in bun is too different to esbuild + // files: { + // "/entry.js": /* js */ ` + // import './default' + // import './null' + // import './boolean' + // import './number' + // import './string-single-empty' + // import './string-double-empty' + // import './string-single-punctuation' + // import './string-double-punctuation' + // import './string-template' + // `, + // "/default.jsx": `console.log(<></>)`, + // "/null.jsx": `console.log(<></>) // @jsxFrag null`, + // "/boolean.jsx": `console.log(<></>) // @jsxFrag true`, + // "/number.jsx": `console.log(<></>) // @jsxFrag 123`, + // "/string-single-empty.jsx": `console.log(<></>) // @jsxFrag ''`, + // "/string-double-empty.jsx": `console.log(<></>) // @jsxFrag ""`, + // "/string-single-punctuation.jsx": `console.log(<></>) // @jsxFrag '['`, + // "/string-double-punctuation.jsx": `console.log(<></>) // @jsxFrag "["`, + // "/string-template.jsx": "console.log(<></>) // @jsxFrag ``", - "/test.js": /* js */ ` - globalThis.React = { - createElement: (x) => x, - Fragment: 'frag' - } - await import('./out.js'); - `, - }, - jsx: { - fragment: "']'", - }, - bundleWarnings: { - "/string-template.jsx": ["Invalid JSX fragment: ``"], - }, - run: { - file: "/test.js", - stdout: "]\nnull\ntrue\n123\n\n\n[\n[\n]", - }, - }); - itBundled("default/JSXAutomaticImportsCommonJS", { - files: { - "/entry.jsx": /* jsx */ ` - import {jsx, Fragment} from './custom-react' - console.log(<div jsx={jsx}/>, <><Fragment/></>) - `, - "/custom-react.js": `module.exports = { jsx: 'jsx', Fragment: 'fragment2' }`, - }, - jsx: { - automaticRuntime: true, - }, - external: ["react"], - run: { - stdout: ` - <div jsx="jsx" /> <> - <fragment2 /> - </> - `, - }, - }); - itBundled("default/JSXAutomaticImportsES6", { - files: { - "/entry.jsx": /* jsx */ ` - import {jsx, Fragment} from './custom-react' - console.log(<div jsx={jsx}/>, <><Fragment/></>) - `, - "/custom-react.js": /* js */ ` - export const jsx = 'jsx function' - export const Fragment = 'fragment' - `, - }, - jsx: { - automaticRuntime: true, - }, - external: ["react"], - run: { - stdout: ` - <div jsx="jsx function" /> <> - <fragment /> - </> - `, - }, - }); - itBundled("default/JSXAutomaticSyntaxInJS", { - files: { - "/entry.mjs": `console.log(<div/>)`, - }, - jsx: { - automaticRuntime: true, - }, - external: ["react"], - bundleErrors: { - // TODO: this could be a nicer error - "/entry.mjs": [`Unexpected <`], - }, - }); + // "/test.js": /* js */ ` + // globalThis.React = { + // createElement: (x) => x, + // Fragment: 'frag' + // } + // await import('./out.js'); + // `, + // }, + // jsx: { + // fragment: "']'", + // }, + // bundleWarnings: { + // "/string-template.jsx": ["Invalid JSX fragment: ``"], + // }, + // run: { + // file: "/test.js", + // stdout: "]\nnull\ntrue\n123\n\n\n[\n[\n]", + // }, + // }); + // itBundled("default/JSXAutomaticImportsCommonJS", { + // files: { + // "/entry.jsx": /* jsx */ ` + // import {jsx, Fragment} from './custom-react' + // console.log(<div jsx={jsx}/>, <><Fragment/></>) + // `, + // "/custom-react.js": `module.exports = { jsx: 'jsx', Fragment: 'fragment2' }`, + // }, + // jsx: { + // automaticRuntime: true, + // }, + // external: ["react"], + // run: { + // stdout: ` + // <div jsx="jsx" /> <> + // <fragment2 /> + // </> + // `, + // }, + // }); + // itBundled("default/JSXAutomaticImportsES6", { + // files: { + // "/entry.jsx": /* jsx */ ` + // import {jsx, Fragment} from './custom-react' + // console.log(<div jsx={jsx}/>, <><Fragment/></>) + // `, + // "/custom-react.js": /* js */ ` + // export const jsx = 'jsx function' + // export const Fragment = 'fragment' + // `, + // }, + // jsx: { + // automaticRuntime: true, + // }, + // external: ["react"], + // run: { + // stdout: ` + // <div jsx="jsx function" /> <> + // <fragment /> + // </> + // `, + // }, + // }); + // itBundled("default/JSXAutomaticSyntaxInJS", { + // files: { + // "/entry.mjs": `console.log(<div/>)`, + // }, + // jsx: { + // automaticRuntime: true, + // }, + // external: ["react"], + // bundleErrors: { + // // TODO: this could be a nicer error + // "/entry.mjs": [`Unexpected <`], + // }, + // }); itBundled("default/NodeModules", { files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -784,7 +783,6 @@ describe("bundler", () => { }, }); itBundled("default/RequireAndDynamicImportInvalidTemplate", { - notImplemented: true, files: { "/entry.cjs": ` require(tag\`./b\`) @@ -832,7 +830,7 @@ describe("bundler", () => { `, }, format: "cjs", - mode: "transform", + bundling: false, onAfterBundle(api) { api.expectFile("/out.js").toContain('import("foo")'); api.expectFile("/out.js").toContain("import(foo())"); @@ -846,7 +844,7 @@ describe("bundler", () => { `, }, format: "cjs", - mode: "transform", + bundling: false, minifyWhitespace: true, onAfterBundle(api) { api.expectFile("/out.js").toContain('import("foo")'); @@ -1152,11 +1150,31 @@ describe("bundler", () => { `, "/Users/user/project/src/bar.js": `export function bar() { console.log('hi') }`, }, - outfile: "/Users/user/project/out.js", - sourceMap: true, + outdir: "/Users/user/project/out", + sourceMap: "external", onAfterBundle(api) { - api.assertFileExists("/Users/user/project/out.js.map"); - api.expectFile("/Users/user/project/out.js").toContain("//# sourceMappingURL=out.js.map"); + const json = JSON.parse(api.readFile("/Users/user/project/out/entry.js.map")); + api.expectFile("/Users/user/project/out/entry.js").toContain(`//# debugId=${json.debugId}`); + }, + run: { + stdout: "hi", + }, + }); + itBundled("default/SourceMapInline", { + files: { + "/Users/user/project/src/entry.js": /* js */ ` + import {bar} from './bar' + function foo() { bar() } + foo() + `, + "/Users/user/project/src/bar.js": `export function bar() { console.log('hi') }`, + }, + outdir: "/Users/user/project/out", + sourceMap: "inline", + onAfterBundle(api) { + api + .expectFile("/Users/user/project/out/entry.js") + .toContain(`//# sourceMappingURL=data:application/json;base64,`); }, run: { stdout: "hi", @@ -1469,12 +1487,13 @@ describe("bundler", () => { files: { "/entry.js": /* js */ ` function __require() { return 123 } - console.log(__require()) + console.log(__require(), typeof (require('fs'))) `, }, - mode: "transform", + bundling: false, + target: "bun", run: { - stdout: "123", + stdout: "123 object", }, }); itBundled("default/TopLevelReturnForbiddenImport", { @@ -1513,7 +1532,7 @@ describe("bundler", () => { export var foo `, }, - mode: "transform", + bundling: false, bundleErrors: { "/entry.js": ["Top-level return cannot be used inside an ECMAScript module"], }, @@ -1522,7 +1541,7 @@ describe("bundler", () => { files: { "/entry.js": `return await foo`, }, - mode: "transform", + bundling: false, bundleErrors: { "/entry.js": ["Top-level return cannot be used inside an ECMAScript module"], }, @@ -1774,7 +1793,7 @@ describe("bundler", () => { `, }, minifyIdentifiers: true, - mode: "transform", + bundling: false, onAfterBundle(api) { assert(!api.readFile("/out.js").includes("foo"), 'bundle shouldnt include "foo"'); assert(!api.readFile("/out.js").includes("let bar"), 'bundle shouldnt include "let bar"'); @@ -1804,7 +1823,7 @@ describe("bundler", () => { ); }, minifyIdentifiers: true, - mode: "transform", + bundling: false, }); itBundled("default/ArgumentsSpecialCaseNoBundle", { files: { @@ -1904,7 +1923,6 @@ describe("bundler", () => { format: "iife", outfile: "/out.js", minifyIdentifiers: true, - // mode: "transform", }); itBundled("default/WithStatementTaintingNoBundle", { files: { @@ -1938,7 +1956,7 @@ describe("bundler", () => { }, format: "iife", minifyIdentifiers: true, - mode: "transform", + bundling: false, run: { runtime: "node", stdout: ` @@ -1997,7 +2015,7 @@ describe("bundler", () => { `, }, minifyIdentifiers: true, - mode: "transform", + bundling: false, format: "cjs", onAfterBundle(api) { const text = api.readFile("/out.js"); @@ -2332,7 +2350,7 @@ describe("bundler", () => { files: { "/entry.js": `import "foo"`, }, - external: ["*"], + bundling: false, }); itBundled("default/ManyEntryPoints", { files: Object.fromEntries([ @@ -2384,7 +2402,6 @@ describe("bundler", () => { }); // These labels should all share the same minified names itBundled("default/MinifySiblingLabelsNoBundle", { - notImplemented: true, files: { "/entry.js": /* js */ ` foo: { @@ -2442,30 +2459,14 @@ describe("bundler", () => { }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a') }}}}}}}}}}}}}}}}}}}}}}}}}}} `; - itBundled("default/NestedLabelsBundle", { + // these tests are flaky. at least if i run it just on its own, i get a crash. in a row its fine + itBundled.skip("default/NestedLabelsBundle", { notImplemented: true, files: { "/entry.js": crazyNestedLabelFile, }, }); - itBundled("default/NestedLabelsNoBundle", { - notImplemented: true, - files: { - "/entry.js": crazyNestedLabelFile, - }, - mode: "transform", - }); - itBundled("default/MinifyNestedLabelsNoBundle", { - notImplemented: true, - files: { - "/entry.js": crazyNestedLabelFile, - }, - minifyWhitespace: true, - minifyIdentifiers: true, - minifySyntax: true, - mode: "transform", - }); - itBundled("default/MinifyNestedLabelsBundle", { + itBundled.skip("default/MinifyNestedLabelsBundle", { notImplemented: true, files: { "/entry.js": crazyNestedLabelFile, @@ -2527,7 +2528,7 @@ describe("bundler", () => { format: "iife", minifySyntax: true, minifyWhitespace: true, - mode: "transform", + bundling: false, onAfterBundle(api) { assert(api.readFile("/out.js").includes('"use strict";'), '"use strict"; was emitted'); }, @@ -2741,7 +2742,7 @@ describe("bundler", () => { file: "/test.js", stdout: "foo bar", }, - external: ["*"], + bundling: false, }); itBundled("default/ImportMetaCommonJS", { files: { @@ -2769,7 +2770,7 @@ describe("bundler", () => { files: { "/entry.js": `console.log(import.meta.url, import.meta.path)`, }, - mode: "transform", + bundling: false, run: { stdout: "url_here path_here", bunArgs: ["--define", 'import.meta.url="url_here"', "--define", 'import.meta.path="path_here"'], @@ -3440,7 +3441,7 @@ describe("bundler", () => { for await (foo of bar) ; `, }, - mode: "transform", + bundling: false, }); itBundled("default/TopLevelAwaitForbiddenRequire", { notImplemented: true, @@ -3902,7 +3903,7 @@ describe("bundler", () => { }, dce: true, treeShaking: true, - mode: "transform", + bundling: false, run: { stdout: ` side effects @@ -4085,7 +4086,7 @@ describe("bundler", () => { export let bar = 123 `, }, - mode: "transform", + bundling: false, runtimeFiles: { "/test.js": /* js */ ` import * as mod from './out'; @@ -4571,7 +4572,7 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", + bundling: false, onAfterBundle(api) { const code = api.readFile("/out.js"); expect(code).not.toContain("const"); @@ -4681,7 +4682,6 @@ describe("bundler", () => { // `, // }, // entryPoints: ["/js.js", "/ts.ts", "/jsx-components.jsx", "/jsx-a.jsx", "/jsx-b.jsx", "/jsx-c.jsx"], - // mode: "transform", // external: ["a", "b", "c", "react/jsx-dev-runtime"], // }); // I cant get bun to use `this` as the JSX runtime. It's a pretty silly idea anyways. @@ -5140,7 +5140,7 @@ describe("bundler", () => { "/node_modules/some-path/index.js": `module.exports = 123`, "/node_modules/second-path/index.js": `module.exports = 567`, }, - external: ["*"], + bundling: false, target: "browser", format: "esm", outfile: "/out.mjs", @@ -5167,7 +5167,7 @@ describe("bundler", () => { notImplemented: true, files: RequireShimSubstitutionBrowser.options.files, runtimeFiles: RequireShimSubstitutionBrowser.options.runtimeFiles, - external: ["*"], + bundling: false, target: "node", format: "esm", outfile: "/out.mjs", @@ -5209,7 +5209,6 @@ describe("bundler", () => { minifySyntax: true, }); itBundled("default/BuiltInNodeModulePrecedence", { - // GENERATED notImplemented: true, files: { "/entry.js": /* js */ ` @@ -5222,13 +5221,28 @@ describe("bundler", () => { // These are not node core modules require('fs/abc'), require('fs/'), - ]) + ].map(x => typeof x).join(',')) `, "/node_modules/fs/abc.js": `console.log('include this')`, "/node_modules/fs/index.js": `console.log('include this too')`, "/node_modules/fs/promises.js": `throw 'DO NOT INCLUDE THIS'`, }, target: "node", + runtimeFiles: { + "/node_modules/node_foo/index.js": `console.log('include this too')`, + }, + onAfterBundle(api) { + api.writeFile("/out.js", api.readFile("/out.js").replace(/node:foo/g, "node_foo")); + }, + run: { + runtime: "node", + stdout: ` + include this too + include this + include this too + object,object,object,object,object + `, + }, }); itBundled("default/EntryNamesNoSlashAfterDir", { // GENERATED @@ -5340,7 +5354,7 @@ describe("bundler", () => { `, }, format: "cjs", - external: ["*"], + bundling: false, }); itBundled("default/NamedFunctionExpressionArgumentCollision", { files: { @@ -5422,7 +5436,7 @@ describe("bundler", () => { `, }, entryPoints: ["/entry1.js", "/entry2.js"], - external: ["*"], + bundling: false, mangleProps: /_$/, }); itBundled("default/ManglePropsMinify", { @@ -5471,7 +5485,7 @@ describe("bundler", () => { entryPoints: ["/entry1.js", "/entry2.js"], mangleProps: /_$/, minifyIdentifiers: true, - external: ["*"], + bundling: false, }); itBundled("default/ManglePropsKeywordPropertyMinify", { // GENERATED @@ -5485,7 +5499,7 @@ describe("bundler", () => { mangleProps: /./, minifyIdentifiers: true, minifySyntax: true, - external: ["*"], + bundling: false, }); itBundled("default/ManglePropsOptionalChain", { // GENERATED @@ -5504,7 +5518,7 @@ describe("bundler", () => { `, }, mangleProps: /_$/, - external: ["*"], + bundling: false, }); itBundled("default/ManglePropsLoweredOptionalChain", { // GENERATED @@ -5523,7 +5537,7 @@ describe("bundler", () => { `, }, mangleProps: /_$/, - external: ["*"], + bundling: false, }); itBundled("default/ReserveProps", { // GENERATED @@ -5536,7 +5550,7 @@ describe("bundler", () => { `, }, mangleProps: /_$/, - external: ["*"], + bundling: false, }); itBundled("default/ManglePropsImportExport", { // GENERATED @@ -5552,7 +5566,7 @@ describe("bundler", () => { }, entryPoints: ["/esm.js", "/cjs.js"], mangleProps: /_$/, - external: ["*"], + bundling: false, }); itBundled("default/ManglePropsImportExportBundled", { // GENERATED @@ -5991,7 +6005,7 @@ describe("bundler", () => { // // preserve: true, // }, // // minifySyntax: true, - // external: ["*"], + // bundling: false, // }); itBundled("default/PackageAlias", { files: { @@ -6216,7 +6230,7 @@ describe("bundler", () => { `, }, entryPoints: ["/project/entry.js", "/project/entry.css"], - external: ["*"], + bundling: false, metafile: true, }); itBundled("default/MetafileVeryLongExternalPaths", { @@ -6425,7 +6439,7 @@ describe("bundler", () => { } }, }); - itBundled("default/CommentPreservationImportAssertions", { + itBundled.skip("default/CommentPreservationImportAssertions", { // GENERATED notImplemented: true, files: { @@ -6439,7 +6453,7 @@ describe("bundler", () => { }, external: ["foo"], }); - itBundled("default/CommentPreservationTransformJSX", { + itBundled.skip("default/CommentPreservationTransformJSX", { // GENERATED notImplemented: true, files: { @@ -6469,7 +6483,7 @@ describe("bundler", () => { `, }, }); - itBundled("default/CommentPreservationPreserveJSX", { + itBundled.skip("default/CommentPreservationPreserveJSX", { // GENERATED notImplemented: true, files: { diff --git a/test/bundler/esbuild/importstar.test.ts b/test/bundler/esbuild/importstar.test.ts index f65e0f8f4..6c8edee2c 100644 --- a/test/bundler/esbuild/importstar.test.ts +++ b/test/bundler/esbuild/importstar.test.ts @@ -247,11 +247,10 @@ describe("bundler", () => { console.log(foo) `, }, - mode: "transform", runtimeFiles: { "/foo.js": `console.log('foo')`, }, - external: ["./foo"], + bundling: false, run: { stdout: "foo\n234", }, @@ -264,7 +263,6 @@ describe("bundler", () => { console.log(JSON.stringify(ns), ns.foo, foo) `, }, - mode: "transform", runtimeFiles: { "/foo.js": `export const foo = 123`, }, @@ -282,7 +280,6 @@ describe("bundler", () => { `, }, external: ["./foo"], - mode: "transform", runtimeFiles: { "/foo.js": `export const foo = 123`, }, @@ -300,7 +297,6 @@ describe("bundler", () => { }, minifySyntax: true, external: ["./foo"], - mode: "transform", runtimeFiles: { "/foo.js": `console.log('foo')`, }, @@ -317,7 +313,6 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", external: ["./foo"], runtimeFiles: { "/foo.js": `export const foo = 123`, @@ -335,7 +330,6 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", external: ["./foo"], runtimeFiles: { "/foo.js": `export const foo = 123`, diff --git a/test/bundler/esbuild/importstar_ts.test.ts b/test/bundler/esbuild/importstar_ts.test.ts index 0e39f0b29..394c4260f 100644 --- a/test/bundler/esbuild/importstar_ts.test.ts +++ b/test/bundler/esbuild/importstar_ts.test.ts @@ -211,7 +211,7 @@ describe("bundler", () => { console.log(foo) `, }, - mode: "transform", + bundling: false, }); itBundled("ts/TSImportStarNoBundleCapture", { // GENERATED @@ -222,7 +222,7 @@ describe("bundler", () => { console.log(ns, ns.foo, foo) `, }, - mode: "transform", + bundling: false, }); itBundled("ts/TSImportStarNoBundleNoCapture", { // GENERATED @@ -233,7 +233,7 @@ describe("bundler", () => { console.log(ns.foo, ns.foo, foo) `, }, - mode: "transform", + bundling: false, }); itBundled("ts/TSImportStarMangleNoBundleUnused", { // GENERATED @@ -245,7 +245,7 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", + bundling: false, }); itBundled("ts/TSImportStarMangleNoBundleCapture", { // GENERATED @@ -257,7 +257,7 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", + bundling: false, }); itBundled("ts/TSImportStarMangleNoBundleNoCapture", { // GENERATED @@ -269,7 +269,7 @@ describe("bundler", () => { `, }, minifySyntax: true, - mode: "transform", + bundling: false, }); itBundled("ts/TSReExportTypeOnlyFileES6", { // GENERATED diff --git a/test/bundler/esbuild/loader.test.ts b/test/bundler/esbuild/loader.test.ts index 0b946d0b3..628fc4ca8 100644 --- a/test/bundler/esbuild/loader.test.ts +++ b/test/bundler/esbuild/loader.test.ts @@ -116,7 +116,7 @@ describe("bundler", () => { loader: { ".cjs": "jsx", }, - external: ["*"], + bundling: false, }); // itBundled("loader/JSXPreserveCapitalLetter", { // // GENERATED @@ -460,7 +460,7 @@ describe("bundler", () => { files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, }, - mode: "transform", + bundling: false, }); itBundled("loader/JSONNoBundleES6", { // GENERATED diff --git a/test/bundler/esbuild/lower.test.ts b/test/bundler/esbuild/lower.test.ts index df68a6f51..b73c30b08 100644 --- a/test/bundler/esbuild/lower.test.ts +++ b/test/bundler/esbuild/lower.test.ts @@ -18,7 +18,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2018", - mode: "transform", + bundling: false, }); itBundled("lower/LowerObjectSpreadNoBundle", { // GENERATED @@ -41,7 +41,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2017", - mode: "transform", + bundling: false, }); itBundled("lower/LowerExponentiationOperatorNoBundle", { // GENERATED @@ -79,7 +79,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2015", - mode: "transform", + bundling: false, /* TODO FIX expectedScanLog: `entry.js: ERROR: Big integer literals are not available in the configured target environment `, */ }); @@ -117,7 +117,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2015", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldAssignments2019NoBundle", { // GENERATED @@ -153,7 +153,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2019", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldAssignments2020NoBundle", { // GENERATED @@ -189,7 +189,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2020", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldAssignmentsNextNoBundle", { // GENERATED @@ -224,7 +224,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldOptionalChain2019NoBundle", { // GENERATED @@ -241,7 +241,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2019", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldOptionalChain2020NoBundle", { // GENERATED @@ -258,7 +258,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2020", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateFieldOptionalChainNextNoBundle", { // GENERATED @@ -274,7 +274,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerPrivateFieldOptionalChain2015NoBundle", { // GENERATED @@ -291,7 +291,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2015", - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerPrivateStaticMembers2015NoBundle", { // GENERATED @@ -311,7 +311,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2015", - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerPrivateFieldAndMethodAvoidNameCollision2015", { // GENERATED @@ -623,7 +623,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2020", - mode: "transform", + bundling: false, }); itBundled("lower/LowerPrivateMethodWithModifiers2020", { // GENERATED @@ -668,7 +668,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2016", - mode: "transform", + bundling: false, }); itBundled("lower/LowerAsync2017NoBundle", { // GENERATED @@ -696,7 +696,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2017", - mode: "transform", + bundling: false, }); itBundled("lower/LowerAsyncThis2016CommonJS", { // GENERATED @@ -790,7 +790,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2017", - mode: "transform", + bundling: false, }); itBundled("lower/LowerAsyncSuperES2016NoBundle", { // GENERATED @@ -833,7 +833,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2016", - mode: "transform", + bundling: false, }); itBundled("lower/LowerStaticAsyncSuperES2021NoBundle", { // GENERATED @@ -876,7 +876,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2021", - mode: "transform", + bundling: false, }); itBundled("lower/LowerStaticAsyncSuperES2016NoBundle", { // GENERATED @@ -919,7 +919,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2016", - mode: "transform", + bundling: false, }); itBundled("lower/LowerStaticSuperES2021NoBundle", { // GENERATED @@ -962,7 +962,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2021", - mode: "transform", + bundling: false, }); itBundled("lower/LowerStaticSuperES2016NoBundle", { // GENERATED @@ -1005,7 +1005,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2016", - mode: "transform", + bundling: false, }); itBundled("lower/LowerAsyncArrowSuperES2016", { // GENERATED @@ -1238,7 +1238,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2020", - mode: "transform", + bundling: false, }); itBundled("lower/LowerClassFieldNextNoBundle", { // GENERATED @@ -1256,7 +1256,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerClassField2020NoBundle", { // GENERATED @@ -1275,7 +1275,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2020", - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerClassPrivateFieldNextNoBundle", { // GENERATED @@ -1293,7 +1293,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, }); itBundled("lower/LowerClassFieldStrictTsconfigJson2020", { // GENERATED @@ -1406,7 +1406,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2017", - mode: "transform", + bundling: false, }); itBundled("lower/TSLowerObjectRest2018NoBundle", { // GENERATED @@ -1449,7 +1449,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2018", - mode: "transform", + bundling: false, }); itBundled("lower/ClassSuperThisESBuildIssue242NoBundle", { // GENERATED @@ -1470,7 +1470,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2019", - mode: "transform", + bundling: false, }); itBundled("lower/LowerExportStarAsNameCollisionNoBundle", { // GENERATED @@ -1482,7 +1482,7 @@ describe("bundler", () => { `, }, unsupportedJSFeatures: "es2019", - mode: "transform", + bundling: false, }); itBundled("lower/LowerExportStarAsNameCollision", { // GENERATED diff --git a/test/bundler/esbuild/packagejson.test.ts b/test/bundler/esbuild/packagejson.test.ts index f1bb556a2..3c3a675c4 100644 --- a/test/bundler/esbuild/packagejson.test.ts +++ b/test/bundler/esbuild/packagejson.test.ts @@ -73,7 +73,6 @@ describe("bundler", () => { }, }); itBundled("packagejson/SyntaxErrorTrailingComma", { - notImplemented: true, files: { "/Users/user/project/src/entry.js": /* js */ ` import fn from 'demo-pkg' @@ -1212,7 +1211,6 @@ describe("bundler", () => { }, }); itBundled("packagejson/ExportsNode", { - notImplemented: true, files: { "/Users/user/project/src/entry.js": `import 'pkg'`, "/Users/user/project/node_modules/pkg/package.json": /* json */ ` diff --git a/test/bundler/esbuild/splitting.test.ts b/test/bundler/esbuild/splitting.test.ts index c10a1a36f..fa38e80ea 100644 --- a/test/bundler/esbuild/splitting.test.ts +++ b/test/bundler/esbuild/splitting.test.ts @@ -263,7 +263,7 @@ describe("bundler", () => { { file: "/out/b.js", stdout: "[null]" }, ], bundleWarnings: { - "/empty.js": [`Import "missing" will always be undefined because the file "empty.js" has no exports`], + "/common.js": [`Import "missing" will always be undefined because there is no matching export in "empty.js"`], }, }); itBundled("splitting/ReExportESBuildIssue273", { diff --git a/test/bundler/esbuild/ts.test.ts b/test/bundler/esbuild/ts.test.ts index 2eb894cfe..cafba8f5f 100644 --- a/test/bundler/esbuild/ts.test.ts +++ b/test/bundler/esbuild/ts.test.ts @@ -391,7 +391,7 @@ describe("bundler", () => { minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, - mode: "transform", + bundling: false, onAfterBundle(api) { const a = api.readFile("/out.js"); api.writeFile("/out.edited.js", a.replace(/capture\((.*?)\)/, `export const Foo = $1`)); @@ -434,7 +434,7 @@ describe("bundler", () => { minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, - mode: "transform", + bundling: false, onAfterBundle(api) { const b = api.readFile("/out.js"); @@ -505,7 +505,7 @@ describe("bundler", () => { minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, - mode: "transform", + bundling: false, unsupportedJSFeatures: ["logical-assignment"], onAfterBundle(api) { const a = api.readFile("/out/a.js"); @@ -526,7 +526,7 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, outdir: "/", - mode: "transform", + bundling: false, unsupportedJSFeatures: ["arrow"], onAfterBundle(api) { const a = api.readFile("/a.js"); @@ -596,7 +596,7 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, outdir: "/", - mode: "transform", + bundling: false, unsupportedJSFeatures: ["logical-assignment"], onAfterBundle(api) { const a = api.readFile("/a.js"); @@ -629,7 +629,7 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, outdir: "/", - mode: "transform", + bundling: false, unsupportedJSFeatures: ["arrow"], onAfterBundle(api) { const a = api.readFile("/a.js"); @@ -759,7 +759,7 @@ describe("bundler", () => { }, treeShaking: false, dce: true, - mode: "transform", + bundling: false, external: ["pkg"], }); itBundled("ts/ImportEqualsTreeShakingTrue", { @@ -774,7 +774,7 @@ describe("bundler", () => { dce: true, treeShaking: true, external: ["pkg"], - mode: "transform", + bundling: false, }); itBundled("ts/ImportEqualsBundle", { notImplemented: true, @@ -1028,7 +1028,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, onAfterBundle(api) { const capturedCalls = api.captureFile("/out.js", "dec"); expect(capturedCalls).toEqual([ @@ -1457,7 +1457,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, runtimeFiles: { "/test.js": /* js */ ` globalThis.nested = true; @@ -1525,7 +1525,7 @@ describe("bundler", () => { } `, }, - mode: "transform", + bundling: false, runtimeFiles: { "/test.js": /* js */ ` globalThis.nested = true; @@ -1574,7 +1574,7 @@ describe("bundler", () => { `, }, useDefineForClassFields: false, - mode: "transform", + bundling: false, run: { stdout: ` [null,{},"x1",null] @@ -1621,7 +1621,7 @@ describe("bundler", () => { `, }, useDefineForClassFields: true, - mode: "transform", + bundling: false, run: { stdout: ` [null,{},"x1",null] @@ -1668,7 +1668,7 @@ describe("bundler", () => { `, }, useDefineForClassFields: true, - mode: "transform", + bundling: false, run: { stdout: ` [null,{},"x1",null] @@ -1712,7 +1712,7 @@ describe("bundler", () => { (() => new Foo())() `, }, - mode: "transform", + bundling: false, useDefineForClassFields: true, }); itBundled("ts/ImportMTS", { @@ -1781,7 +1781,7 @@ describe("bundler", () => { `, }, entryPoints: ["/let.ts", "/function.ts", "/class.ts", "/namespace.ts", "/enum.ts"], - mode: "transform", + bundling: false, runtimeFiles: { "/test.js": /* js */ ` import assert from 'assert' @@ -1802,6 +1802,9 @@ describe("bundler", () => { // GENERATED files: { "/number.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.z = 2345'); + export enum x { y, yy = y } export enum x { z = y + 1 } @@ -1810,6 +1813,9 @@ describe("bundler", () => { console.log(x.y, x.z) `, "/string.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.z = 2345'); + export enum x { y = 'a', yy = y } export enum x { z = y } @@ -1829,6 +1835,8 @@ describe("bundler", () => { console.log(a.b, a['b'], x.g, x['g']) `, "/nested-number.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.z = 2345'); export namespace foo { export enum x { y, yy = y } } export namespace foo { export enum x { z = y + 1 } } @@ -1839,6 +1847,9 @@ describe("bundler", () => { } `, "/nested-string.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.z = 2345'); + export namespace foo { export enum x { y = 'a', yy = y } } export namespace foo { export enum x { z = y } } @@ -1872,23 +1883,29 @@ describe("bundler", () => { "/nested-string.ts", "/nested-propagation.ts", ], - mode: "passthrough", + run: [ + { file: "/out/number.js", stdout: "1234 2345\n0 1" }, + { file: "/out/string.js", stdout: "1234 2345\na a" }, + { file: "/out/propagation.js", stdout: "100 100 625 625" }, + { file: "/out/nested-number.js", stdout: "1234 2345\n0 1" }, + { file: "/out/nested-string.js", stdout: "1234 2345\na a" }, + { file: "/out/nested-propagation.js", stdout: "100 100 100 625 625 625" }, + ], }); itBundled("ts/EnumTreeShaking", { - // GENERATED files: { "/simple-member.ts": /* ts */ ` - enum x { y = 123 } - console.log(x.y) + enum x_DROP { y_DROP = 123 } + console.log(x_DROP.y_DROP) `, "/simple-enum.ts": /* ts */ ` enum x { y = 123 } - console.log(x) + console.log(JSON.stringify(x)) `, "/sibling-member.ts": /* ts */ ` - enum x { y = 123 } - enum x { z = y * 2 } - console.log(x.y, x.z) + enum drop_x { drop_y = 123 } + enum drop_x { drop_z = drop_y * 2 } + console.log(drop_x.drop_y, drop_x.drop_z) `, "/sibling-enum-before.ts": /* ts */ ` console.log(x) @@ -1897,21 +1914,27 @@ describe("bundler", () => { `, "/sibling-enum-middle.ts": /* ts */ ` enum x { y = 123 } - console.log(x) + console.log(JSON.stringify(x)) enum x { z = y * 2 } `, "/sibling-enum-after.ts": /* ts */ ` enum x { y = 123 } enum x { z = y * 2 } - console.log(x) + console.log(JSON.stringify(x)) `, "/namespace-before.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.x = 2345'); + namespace x { console.log(x, y) } enum x { y = 123 } `, "/namespace-after.ts": /* ts */ ` + (0, eval)('globalThis.y = 1234'); + (0, eval)('globalThis.x = 2345'); + enum x { y = 123 } - namespace x { console.log(x, y) } + namespace x { console.log(JSON.stringify(x), y) } `, }, entryPoints: [ @@ -1924,91 +1947,138 @@ describe("bundler", () => { "/namespace-before.ts", "/namespace-after.ts", ], + dce: true, + run: [ + { file: "/out/simple-member.js", stdout: "123" }, + { file: "/out/simple-enum.js", stdout: '{"123":"y","y":123}' }, + { file: "/out/sibling-member.js", stdout: "123 246" }, + { file: "/out/sibling-enum-before.js", stdout: "undefined" }, + { file: "/out/sibling-enum-middle.js", stdout: '{"123":"y","y":123}' }, + { file: "/out/sibling-enum-after.js", stdout: '{"123":"y","246":"z","y":123,"z":246}' }, + { file: "/out/namespace-before.js", stdout: "{} 1234" }, + { file: "/out/namespace-after.js", stdout: '{"123":"y","y":123} 1234' }, + ], }); itBundled("ts/EnumJSX", { - // GENERATED + notImplemented: true, files: { "/element.tsx": /* tsx */ ` + import { create } from 'not-react' + export enum Foo { Div = 'div' } - console.log(<Foo.Div />) + console.log(JSON.stringify(<Foo.Div />)) `, "/fragment.tsx": /* tsx */ ` + import { create } from 'not-react' + export enum React { Fragment = 'div' } - console.log(<>test</>) + console.log(JSON.stringify(<>test</>)) `, "/nested-element.tsx": /* tsx */ ` + import { create } from 'not-react' + namespace x.y { export enum Foo { Div = 'div' } } - namespace x.y { console.log(<x.y.Foo.Div />) } + namespace x.y { console.log(JSON.stringify(<Foo.Div />)) } `, "/nested-fragment.tsx": /* tsx */ ` + import { create } from 'not-react' + namespace x.y { export enum React { Fragment = 'div' } } - namespace x.y { console.log(<>test</>) } + namespace x.y { console.log(JSON.stringify(<>test</>)) } `, }, entryPoints: ["/element.tsx", "/fragment.tsx", "/nested-element.tsx", "/nested-fragment.tsx"], - mode: "passthrough", + outputPaths: ["/out/element.js", "/out/fragment.js", "/out/nested-element.js", "/out/nested-fragment.js"], + external: ["*"], + jsx: { + runtime: "classic", + factory: "create", + }, + runtimeFiles: { + "/node_modules/not-react/index.js": /* js */ ` + export const create = (tag, props, ...children) => [tag, props, children] + `, + }, + run: [ + { file: "/out/element.js", stdout: '["div",null,[]]' }, + { file: "/out/fragment.js", stdout: '["div",null,["test"]]' }, + { file: "/out/nested-element.js", stdout: '["div",null,[]]' }, + { file: "/out/nested-fragment.js", stdout: '["div",null,["test"]]' }, + ], }); itBundled("ts/EnumDefine", { - // GENERATED + notImplemented: true, files: { - "/entry.ts": `enum a { b = 123, c = d }`, + "/entry.ts": ` + enum a { b = 123, c = d } + console.log(a.b, a.c) + `, }, - mode: "passthrough", + define: { + d: "b", + }, + run: { stdout: "123 123" }, }); itBundled("ts/EnumSameModuleInliningAccess", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` - enum a { x = 123 } - enum b { x = 123 } + enum a_drop { x = 123 } + enum b_drop { x = 123 } enum c { x = 123 } enum d { x = 123 } enum e { x = 123 } - console.log([ - a.x, - b['x'], + console.log(JSON.stringify([ + a_drop.x, + b_drop['x'], c?.x, d?.['x'], e, - ]) + ])) `, }, + dce: true, + run: { stdout: '[123,123,123,123,{"123":"x","x":123}]' }, }); itBundled("ts/EnumCrossModuleInliningAccess", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` - import { a, b, c, d, e } from './enums' - console.log([ - a.x, - b['x'], + import { drop_a, drop_b, c, d, e } from './enums' + console.log(JSON.stringify([ + drop_a.x, + drop_b['x'], c?.x, d?.['x'], e, - ]) + ])) `, "/enums.ts": /* ts */ ` - export enum a { x = 123 } - export enum b { x = 123 } + export enum drop_a { x = 123 } + export enum drop_b { x = 123 } export enum c { x = 123 } export enum d { x = 123 } export enum e { x = 123 } `, }, + dce: true, }); itBundled("ts/EnumCrossModuleInliningDefinitions", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` import { a } from './enums' - console.log([ - a.implicit_number, - a.explicit_number, - a.explicit_string, + (0, eval)('globalThis.capture = x => x'); + console.log(JSON.stringify([ + capture(a.implicit_number), + capture(a.explicit_number), + capture(a.explicit_string), a.non_constant, - ]) + ])) `, "/enums.ts": /* ts */ ` + (0, eval)('globalThis.foo = 321'); + export enum a { implicit_number, explicit_number = 123, @@ -2017,18 +2087,21 @@ describe("bundler", () => { } `, }, + onAfterBundle(api) { + expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["0", "123", '"xyz"']); + }, }); itBundled("ts/EnumCrossModuleInliningReExport", { - // GENERATED + notImplemented: true, files: { "/entry.js": /* js */ ` import { a } from './re-export' import { b } from './re-export-star' import * as ns from './enums' console.log([ - a.x, - b.x, - ns.c.x, + capture(a.x), + capture(b.x), + capture(ns.c.x), ]) `, "/re-export.js": `export { a } from './enums'`, @@ -2039,9 +2112,12 @@ describe("bundler", () => { export enum c { x = 'c' } `, }, + onAfterBundle(api) { + expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(['"a"', '"b"', '"c"']); + }, }); itBundled("ts/EnumCrossModuleTreeShaking", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` import { @@ -2051,25 +2127,19 @@ describe("bundler", () => { } from './enums' console.log([ - a_DROP.x, - b_DROP['x'], - c_DROP.x, + capture(a_DROP.x), + capture(b_DROP['x']), + capture(c_DROP.x), ]) - import { - a_keep, - b_keep, - c_keep, - d_keep, - e_keep, - } from './enums' + import { a, b, c, d, e } from './enums' console.log([ - a_keep.x, - b_keep.x, - c_keep, - d_keep.y, - e_keep.x, + capture(a.x), + capture(b.x), + capture(c), + capture(d.y), + capture(e.x), ]) `, "/enums.ts": /* ts */ ` @@ -2077,16 +2147,28 @@ describe("bundler", () => { export enum b_DROP { x = 2 } // test an index access export enum c_DROP { x = '' } // test a string enum - export enum a_keep { x = false } // false is not inlinable - export enum b_keep { x = foo } // foo has side effects - export enum c_keep { x = 3 } // this enum object is captured - export enum d_keep { x = 4 } // we access "y" on this object - export let e_keep = {} // non-enum properties should be kept + export enum a { x = false } // false is not inlinable + export enum b { x = foo } // foo has side effects + export enum c { x = 3 } // this enum object is captured + export enum d { x = 4 } // we access "y" on this object + export let e = {} // non-enum properties should be kept `, }, + onAfterBundle(api) { + expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual([ + "1", + "2", + '""', + "a.x", + "b.x", + "c", + "d.y", + "e.x", + ]); + }, }); itBundled("ts/EnumExportClause", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` import { @@ -2097,100 +2179,185 @@ describe("bundler", () => { } from './enums' console.log([ - A.A, - B.B, - c.C, - dd.D, + capture(A.A), + capture(B.B), + capture(c.C), + capture(dd.D), ]) `, "/enums.ts": /* ts */ ` export enum A { A = 1 } - enum B { B = 2 } - export enum C { C = 3 } - enum D { D = 4 } - export { B, D as d } + enum B { B = 2 } + export enum C { C = 3 } + enum D { D = 4 } + export { B, D as d } `, }, - }); - itBundled("ts/ThisIsUndefinedWarning", { - // GENERATED - files: { - "/warning1.ts": `export var foo = this`, - "/warning2.ts": `export var foo = this || this.foo`, - "/warning3.ts": `export var foo = this ? this.foo : null`, - "/silent1.ts": `export var foo = this && this.foo`, - "/silent2.ts": `export var foo = this && (() => this.foo)`, - }, - entryPoints: ["/warning1.ts", "/warning2.ts", "/warning3.ts", "/silent1.ts", "/silent2.ts"], - /* TODO FIX expectedScanLog: `warning1.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module - warning1.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here: - warning2.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module - warning2.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here: - warning3.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module - warning3.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here: - `, */ - }); - itBundled("ts/CommonJSVariableInESMTypeModule", { - // GENERATED - files: { - "/entry.ts": `module.exports = null`, - "/package.json": `{ "type": "module" }`, - }, - /* TODO FIX expectedScanLog: `entry.ts: WARNING: The CommonJS "module" variable is treated as a global variable in an ECMAScript module and may not work as expected - package.json: NOTE: This file is considered to be an ECMAScript module because the enclosing "package.json" file sets the type of this file to "module": - NOTE: Node's package format requires that CommonJS files in a "type": "module" package use the ".cjs" file extension. If you are using TypeScript, you can use the ".cts" file extension with esbuild instead. - `, */ - }); + onAfterBundle(api) { + expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["1", "2", "3", "4"]); + }, + }); + // itBundled("ts/CommonJSVariableInESMTypeModule", { + // // GENERATED + // files: { + // "/entry.ts": `module.exports = null`, + // "/package.json": `{ "type": "module" }`, + // }, + // /* TODO FIX expectedScanLog: `entry.ts: WARNING: The CommonJS "module" variable is treated as a global variable in an ECMAScript module and may not work as expected + // package.json: NOTE: This file is considered to be an ECMAScript module because the enclosing "package.json" file sets the type of this file to "module": + // NOTE: Node's package format requires that CommonJS files in a "type": "module" package use the ".cjs" file extension. If you are using TypeScript, you can use the ".cts" file extension with esbuild instead. + // `, */ + // }); itBundled("ts/EnumRulesFrom_TypeScript_5_0", { // GENERATED files: { - "/supported.ts": /* ts */ ` + "/supported.ts": + ` // From https://github.com/microsoft/TypeScript/pull/50528: - // "An expression is considered a constant expression if it is - const enum Foo { - // a number or string literal, - X0 = 123, - X1 = 'x', - - // a unary +, -, or ~ applied to a numeric constant expression, - X2 = +1, - X3 = -2, - X4 = ~3, - - // a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions, - X5 = 1 + 2, - X6 = 1 - 2, - X7 = 2 * 3, - X8 = 1 / 2, - X9 = 3 % 2, - X10 = 2 ** 3, - X11 = 1 << 2, - X12 = -9 >> 1, - X13 = -9 >>> 1, - X14 = 5 | 12, - X15 = 5 & 12, - X16 = 5 ^ 12, - - // a binary + applied to two constant expressions whereof at least one is a string, - X17 = 'x' + 0, - X18 = 0 + 'x', - X19 = 'x' + 'y', - X20 = '' + NaN, - X21 = '' + Infinity, - X22 = '' + -Infinity, - X23 = '' + -0, - - // a template expression where each substitution expression is a constant expression, - X24 = \` + "\`A\$00}B\$0'x'}C\$01 + 3 - 4 / 2 * 5 ** 6}D\`" + + // "An expression is considered a constant expression if it is + const enum DROP { + // a number or string literal, + X0 = 123, + X1 = 'x', + + // a unary +, -, or ~ applied to a numeric constant expression, + X2 = +1, + X3 = -2, + X4 = ~3, + + // a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions, + X5 = 1 + 2, + X6 = 1 - 2, + X7 = 2 * 3, + X8 = 1 / 2, + X9 = 3 % 2, + X10 = 2 ** 3, + X11 = 1 << 2, + X12 = -9 >> 1, + X13 = -9 >>> 1, + X14 = 5 | 12, + X15 = 5 & 12, + X16 = 5 ^ 12, + + // a binary + applied to two constant expressions whereof at least one is a string, + X17 = 'x' + 0, + X18 = 0 + 'x', + X19 = 'x' + 'y', + X20 = '' + NaN, + X21 = '' + Infinity, + X22 = '' + -Infinity, + X23 = '' + -0, + + // a template expression where each substitution expression is a constant expression, + X24 = ` + + "`A${0}B${'x'}C${1 + 3 - 4 / 2 * 5 ** 6}D`" + + `, + + // a parenthesized constant expression, + X25 = (321), + + // a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation, + /* (we don't implement this one) */ + + // a dotted name that references an enum member with an enum literal type, or + X26 = X0, + X27 = X0 + 'x', + X28 = 'x' + X0, + X29 = ` + + "`a${X0}b`" + + `, + X30 = DROP.X0, + X31 = DROP.X0 + 'x', + X32 = 'x' + DROP.X0, + X33 = ` + + "`a${DROP.X0}b`" + + `, + + // a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type." + X34 = X1, + X35 = X1 + 'y', + X36 = 'y' + X1, + X37 = ` + + "`a${X1}b`" + + `, + X38 = DROP['X1'], + X39 = DROP['X1'] + 'y', + X40 = 'y' + DROP['X1'], + X41 = ` + + "`a${DROP['X1']}b`" + + `, + } + + console.log(JSON.stringify([ + // a number or string literal, + DROP.X0, + DROP.X1, + + // a unary +, -, or ~ applied to a numeric constant expression, + DROP.X2, + DROP.X3, + DROP.X4, + + // a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions, + DROP.X5, + DROP.X6, + DROP.X7, + DROP.X8, + DROP.X9, + DROP.X10, + DROP.X11, + DROP.X12, + DROP.X13, + DROP.X14, + DROP.X15, + DROP.X16, + + // a template expression where each substitution expression is a constant expression, + DROP.X17, + DROP.X18, + DROP.X19, + DROP.X20, + DROP.X21, + DROP.X22, + DROP.X23, + + // a template expression where each substitution expression is a constant expression, + DROP.X24, + + // a parenthesized constant expression, + DROP.X25, + + // a dotted name that references an enum member with an enum literal type, or + DROP.X26, + DROP.X27, + DROP.X28, + DROP.X29, + DROP.X30, + DROP.X31, + DROP.X32, + DROP.X33, + + // a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type." + DROP.X34, + DROP.X35, + DROP.X36, + DROP.X37, + DROP.X38, + DROP.X39, + DROP.X40, + DROP.X41, + ])) `, "/not-supported.ts": /* ts */ ` + (0, eval)('globalThis.capture = x => x'); + const enum NonIntegerNumberToString { SUPPORTED = '' + 1, UNSUPPORTED = '' + 1.5, } console.log( - NonIntegerNumberToString.SUPPORTED, - NonIntegerNumberToString.UNSUPPORTED, + capture(NonIntegerNumberToString.SUPPORTED), + capture(NonIntegerNumberToString.UNSUPPORTED), ) const enum OutOfBoundsNumberToString { @@ -2198,8 +2365,8 @@ describe("bundler", () => { UNSUPPORTED = '' + 1_000_000_000_000, } console.log( - OutOfBoundsNumberToString.SUPPORTED, - OutOfBoundsNumberToString.UNSUPPORTED, + capture(OutOfBoundsNumberToString.SUPPORTED), + capture(OutOfBoundsNumberToString.UNSUPPORTED), ) const enum TemplateExpressions { @@ -2209,27 +2376,70 @@ describe("bundler", () => { FALSE = '' + false, BIGINT = '' + 123n, } + console.log( - TemplateExpressions.NULL, - TemplateExpressions.TRUE, - TemplateExpressions.FALSE, - TemplateExpressions.BIGINT, + capture(TemplateExpressions.NULL), + capture(TemplateExpressions.TRUE), + capture(TemplateExpressions.FALSE), + capture(TemplateExpressions.BIGINT), ) `, }, + // dce: true, entryPoints: ["/supported.ts", "/not-supported.ts"], + run: [ + { + file: "/out/supported.js", + stdout: + '[123,"x",1,-2,-4,3,-1,6,0.5,1,8,4,-5,2147483643,13,4,9,"x0","0x","xy","NaN","Infinity","-Infinity","0","A0BxC-31246D",321,123,"123x","x123","a123b",123,"123x","x123","a123b","x","xy","yx","axb","x","xy","yx","axb"]', + }, + { + file: "/out/not-supported.js", + stdout: ` + 1 1.5 + 1000000000 1000000000000 + null true false 123 + `, + }, + ], + onAfterBundle(api) { + // expect(api.captureFile("/out/not-supported.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual([ + // '"1"', + // "NonIntegerNumberToString.UNSUPPORTED", + // '"1000000000"', + // "OutOfBoundsNumberToString.UNSUPPORTED", + // "TemplateExpressions.NULL", + // "TemplateExpressions.TRUE", + // "TemplateExpressions.FALSE", + // "TemplateExpressions.BIGINT", + // ]); + }, }); itBundled("ts/EnumUseBeforeDeclare", { - // GENERATED + notImplemented: true, files: { "/entry.ts": /* ts */ ` + before(); + after(); + export function before() { - console.log(Foo.FOO) + console.log(JSON.stringify(Foo), Foo.FOO) } enum Foo { FOO } export function after() { - console.log(Foo.FOO) + console.log(JSON.stringify(Foo), Foo.FOO) } + + before(); + after(); + `, + }, + run: { + stdout: ` + undefined 0 + undefined 0 + {"0":"FOO","FOO":0} 0 + {"0":"FOO","FOO":0} 0 `, }, }); diff --git a/test/bundler/esbuild/tsconfig.test.ts b/test/bundler/esbuild/tsconfig.test.ts index accd0ca7e..b698ad07a 100644 --- a/test/bundler/esbuild/tsconfig.test.ts +++ b/test/bundler/esbuild/tsconfig.test.ts @@ -7,16 +7,14 @@ var { describe, test, expect } = testForFile(import.meta.path); // For debug, all files are written to $TEMP/bun-bundle-tests/tsconfig describe("bundler", () => { - return; - itBundled("tsconfig/TsConfigPaths", { - // GENERATED + itBundled("tsconfig/Paths", ({ root }) => ({ files: { - "/Users/user/project/entry.ts": /* ts */ ` + "/entry.ts": /* ts */ ` import baseurl_dot from './baseurl_dot' import baseurl_nested from './baseurl_nested' - console.log(baseurl_dot, baseurl_nested) + console.log(JSON.stringify({baseurl_dot, baseurl_nested})) `, - "/Users/user/project/baseurl_dot/index.ts": /* ts */ ` + "/baseurl_dot/index.ts": /* ts */ ` import test0 from 'test0' import test1 from 'test1/foo' import test2 from 'test2/foo' @@ -40,7 +38,7 @@ describe("bundler", () => { absoluteOutStar, } `, - "/Users/user/project/baseurl_dot/tsconfig.json": /* json */ ` + "/baseurl_dot/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": ".", @@ -53,24 +51,24 @@ describe("bundler", () => { "test5/*": ["./test5-first/*", "./test5-second/*"], "/virtual-in/test": ["./actual/test"], "/virtual-in-star/*": ["./actual/*"], - "/virtual-out/test": ["/Users/user/project/baseurl_dot/actual/test"], - "/virtual-out-star/*": ["/Users/user/project/baseurl_dot/actual/*"], + "/virtual-out/test": ["${root}/baseurl_dot/actual/test"], + "/virtual-out-star/*": ["${root}/baseurl_dot/actual/*"], } } } `, - "/Users/user/project/baseurl_dot/test0-success.ts": `export default 'test0-success'`, - "/Users/user/project/baseurl_dot/test1-success.ts": `export default 'test1-success'`, - "/Users/user/project/baseurl_dot/test2-success/foo.ts": `export default 'test2-success'`, - "/Users/user/project/baseurl_dot/test3-success.ts": `export default 'test3-success'`, - "/Users/user/project/baseurl_dot/test4-first/foo.ts": `export default 'test4-success'`, - "/Users/user/project/baseurl_dot/test5-second/foo.ts": `export default 'test5-success'`, - "/Users/user/project/baseurl_dot/absolute-in.ts": `export {default} from '/virtual-in/test'`, - "/Users/user/project/baseurl_dot/absolute-in-star.ts": `export {default} from '/virtual-in-star/test'`, - "/Users/user/project/baseurl_dot/absolute-out.ts": `export {default} from '/virtual-out/test'`, - "/Users/user/project/baseurl_dot/absolute-out-star.ts": `export {default} from '/virtual-out-star/test'`, - "/Users/user/project/baseurl_dot/actual/test.ts": `export default 'absolute-success'`, - "/Users/user/project/baseurl_nested/index.ts": /* ts */ ` + "/baseurl_dot/test0-success.ts": `export default 'test0-success'`, + "/baseurl_dot/test1-success.ts": `export default 'test1-success'`, + "/baseurl_dot/test2-success/foo.ts": `export default 'test2-success'`, + "/baseurl_dot/test3-success.ts": `export default 'test3-success'`, + "/baseurl_dot/test4-first/foo.ts": `export default 'test4-success'`, + "/baseurl_dot/test5-second/foo.ts": `export default 'test5-success'`, + "/baseurl_dot/absolute-in.ts": `export {default} from '/virtual-in/test'`, + "/baseurl_dot/absolute-in-star.ts": `export {default} from '/virtual-in-star/test'`, + "/baseurl_dot/absolute-out.ts": `export {default} from '/virtual-out/test'`, + "/baseurl_dot/absolute-out-star.ts": `export {default} from '/virtual-out-star/test'`, + "/baseurl_dot/actual/test.ts": `export default 'absolute-success'`, + "/baseurl_nested/index.ts": /* ts */ ` import test0 from 'test0' import test1 from 'test1/foo' import test2 from 'test2/foo' @@ -94,7 +92,7 @@ describe("bundler", () => { absoluteOutStar, } `, - "/Users/user/project/baseurl_nested/tsconfig.json": /* json */ ` + "/baseurl_nested/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": "nested", @@ -107,34 +105,37 @@ describe("bundler", () => { "test5/*": ["./test5-first/*", "./test5-second/*"], "/virtual-in/test": ["./actual/test"], "/virtual-in-star/*": ["./actual/*"], - "/virtual-out/test": ["/Users/user/project/baseurl_nested/nested/actual/test"], - "/virtual-out-star/*": ["/Users/user/project/baseurl_nested/nested/actual/*"], + "/virtual-out/test": ["${root}/baseurl_nested/nested/actual/test"], + "/virtual-out-star/*": ["${root}/baseurl_nested/nested/actual/*"], } } } `, - "/Users/user/project/baseurl_nested/nested/test0-success.ts": `export default 'test0-success'`, - "/Users/user/project/baseurl_nested/nested/test1-success.ts": `export default 'test1-success'`, - "/Users/user/project/baseurl_nested/nested/test2-success/foo.ts": `export default 'test2-success'`, - "/Users/user/project/baseurl_nested/nested/test3-success.ts": `export default 'test3-success'`, - "/Users/user/project/baseurl_nested/nested/test4-first/foo.ts": `export default 'test4-success'`, - "/Users/user/project/baseurl_nested/nested/test5-second/foo.ts": `export default 'test5-success'`, - "/Users/user/project/baseurl_nested/absolute-in.ts": `export {default} from '/virtual-in/test'`, - "/Users/user/project/baseurl_nested/absolute-in-star.ts": `export {default} from '/virtual-in/test'`, - "/Users/user/project/baseurl_nested/absolute-out.ts": `export {default} from '/virtual-out/test'`, - "/Users/user/project/baseurl_nested/absolute-out-star.ts": `export {default} from '/virtual-out-star/test'`, - "/Users/user/project/baseurl_nested/nested/actual/test.ts": `export default 'absolute-success'`, + "/baseurl_nested/nested/test0-success.ts": `export default 'test0-success'`, + "/baseurl_nested/nested/test1-success.ts": `export default 'test1-success'`, + "/baseurl_nested/nested/test2-success/foo.ts": `export default 'test2-success'`, + "/baseurl_nested/nested/test3-success.ts": `export default 'test3-success'`, + "/baseurl_nested/nested/test4-first/foo.ts": `export default 'test4-success'`, + "/baseurl_nested/nested/test5-second/foo.ts": `export default 'test5-success'`, + "/baseurl_nested/absolute-in.ts": `export {default} from '/virtual-in/test'`, + "/baseurl_nested/absolute-in-star.ts": `export {default} from '/virtual-in/test'`, + "/baseurl_nested/absolute-out.ts": `export {default} from '/virtual-out/test'`, + "/baseurl_nested/absolute-out-star.ts": `export {default} from '/virtual-out-star/test'`, + "/baseurl_nested/nested/actual/test.ts": `export default 'absolute-success'`, }, - }); - itBundled("tsconfig/TsConfigPathsNoBaseURL", { - // GENERATED + run: { + stdout: + '{"baseurl_dot":{"test0":"test0-success","test1":"test1-success","test2":"test2-success","test3":"test3-success","test4":"test4-success","test5":"test5-success","absoluteIn":"absolute-success","absoluteInStar":"absolute-success","absoluteOut":"absolute-success","absoluteOutStar":"absolute-success"},"baseurl_nested":{"test0":"test0-success","test1":"test1-success","test2":"test2-success","test3":"test3-success","test4":"test4-success","test5":"test5-success","absoluteIn":"absolute-success","absoluteInStar":"absolute-success","absoluteOut":"absolute-success","absoluteOutStar":"absolute-success"}}', + }, + })); + itBundled("tsconfig/PathsNoBaseURL", { files: { - "/Users/user/project/entry.ts": /* ts */ ` + "/entry.ts": /* ts */ ` import simple from './simple' import extended from './extended' - console.log(simple, extended) + console.log(JSON.stringify({simple, extended})) `, - "/Users/user/project/simple/index.ts": /* ts */ ` + "/simple/index.ts": /* ts */ ` import test0 from 'test0' import test1 from 'test1/foo' import test2 from 'test2/foo' @@ -152,7 +153,7 @@ describe("bundler", () => { absolute, } `, - "/Users/user/project/simple/tsconfig.json": /* json */ ` + "/simple/tsconfig.json": /* json */ ` { "compilerOptions": { "paths": { @@ -167,15 +168,15 @@ describe("bundler", () => { } } `, - "/Users/user/project/simple/test0-success.ts": `export default 'test0-success'`, - "/Users/user/project/simple/test1-success.ts": `export default 'test1-success'`, - "/Users/user/project/simple/test2-success/foo.ts": `export default 'test2-success'`, - "/Users/user/project/simple/test3-success.ts": `export default 'test3-success'`, - "/Users/user/project/simple/test4-first/foo.ts": `export default 'test4-success'`, - "/Users/user/project/simple/test5-second/foo.ts": `export default 'test5-success'`, - "/Users/user/project/simple/absolute.ts": `export {default} from '/virtual/test'`, - "/Users/user/project/simple/actual/test.ts": `export default 'absolute-success'`, - "/Users/user/project/extended/index.ts": /* ts */ ` + "/simple/test0-success.ts": `export default 'test0-success'`, + "/simple/test1-success.ts": `export default 'test1-success'`, + "/simple/test2-success/foo.ts": `export default 'test2-success'`, + "/simple/test3-success.ts": `export default 'test3-success'`, + "/simple/test4-first/foo.ts": `export default 'test4-success'`, + "/simple/test5-second/foo.ts": `export default 'test5-success'`, + "/simple/absolute.ts": `export {default} from '/virtual/test'`, + "/simple/actual/test.ts": `export default 'absolute-success'`, + "/extended/index.ts": /* ts */ ` import test0 from 'test0' import test1 from 'test1/foo' import test2 from 'test2/foo' @@ -193,12 +194,12 @@ describe("bundler", () => { absolute, } `, - "/Users/user/project/extended/tsconfig.json": /* json */ ` + "/extended/tsconfig.json": /* json */ ` { "extends": "./nested/tsconfig.json" } `, - "/Users/user/project/extended/nested/tsconfig.json": /* json */ ` + "/extended/nested/tsconfig.json": /* json */ ` { "compilerOptions": { "paths": { @@ -213,18 +214,24 @@ describe("bundler", () => { } } `, - "/Users/user/project/extended/nested/test0-success.ts": `export default 'test0-success'`, - "/Users/user/project/extended/nested/test1-success.ts": `export default 'test1-success'`, - "/Users/user/project/extended/nested/test2-success/foo.ts": `export default 'test2-success'`, - "/Users/user/project/extended/nested/test3-success.ts": `export default 'test3-success'`, - "/Users/user/project/extended/nested/test4-first/foo.ts": `export default 'test4-success'`, - "/Users/user/project/extended/nested/test5-second/foo.ts": `export default 'test5-success'`, - "/Users/user/project/extended/absolute.ts": `export {default} from '/virtual/test'`, - "/Users/user/project/extended/nested/actual/test.ts": `export default 'absolute-success'`, + "/extended/nested/test0-success.ts": `export default 'test0-success'`, + "/extended/nested/test1-success.ts": `export default 'test1-success'`, + "/extended/nested/test2-success/foo.ts": `export default 'test2-success'`, + "/extended/nested/test3-success.ts": `export default 'test3-success'`, + "/extended/nested/test4-first/foo.ts": `export default 'test4-success'`, + "/extended/nested/test5-second/foo.ts": `export default 'test5-success'`, + "/extended/absolute.ts": `export {default} from '/virtual/test'`, + "/extended/nested/actual/test.ts": `export default 'absolute-success'`, + }, + run: { + stdout: + '{"simple":{"test0":"test0-success","test1":"test1-success","test2":"test2-success","test3":"test3-success","test4":"test4-success","test5":"test5-success","absolute":"absolute-success"},"extended":{"test0":"test0-success","test1":"test1-success","test2":"test2-success","test3":"test3-success","test4":"test4-success","test5":"test5-success","absolute":"absolute-success"}}', }, }); - itBundled("tsconfig/TsConfigBadPathsNoBaseURL", { + // TODO: warnings shouldnt stop build? + itBundled("tsconfig/BadPathsNoBaseURL", { // GENERATED + notImplemented: true, files: { "/Users/user/project/entry.ts": `import "should-not-be-imported"`, "/Users/user/project/should-not-be-imported.ts": ``, @@ -236,22 +243,22 @@ describe("bundler", () => { ".", "..", "./good", - ".\\good", + ".\\\\good", "../good", - "..\\good", + "..\\\\good", "/good", - "\\good", + "\\\\good", "c:/good", - "c:\\good", + "c:\\\\good", "C:/good", - "C:\\good", + "C:\\\\good", "bad", "@bad/core", ".*/bad", "..*/bad", - "c*:\\bad", - "c:*\\bad", + "c*:\\\\bad", + "c:*\\\\bad", "http://bad" ] } @@ -270,7 +277,7 @@ describe("bundler", () => { Users/user/project/tsconfig.json: WARNING: Non-relative path "http://bad" is not allowed when "baseUrl" is not set (did you forget a leading "./"?) `, */ }); - itBundled("tsconfig/TsConfigPathsOverriddenBaseURL", { + itBundled("tsconfig/PathsOverriddenBaseURL", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -296,9 +303,11 @@ describe("bundler", () => { } `, }, + run: { + stdout: "123", + }, }); - itBundled("tsconfig/TsConfigPathsOverriddenBaseURLDifferentDir", { - // GENERATED + itBundled("tsconfig/PathsOverriddenBaseURLDifferentDir", { files: { "/Users/user/project/src/entry.ts": /* ts */ ` import test from '#/test' @@ -323,9 +332,11 @@ describe("bundler", () => { } `, }, + run: { + stdout: "123", + }, }); - itBundled("tsconfig/TsConfigPathsMissingBaseURL", { - // GENERATED + itBundled("tsconfig/PathsMissingBaseURL", { files: { "/Users/user/project/src/entry.ts": /* ts */ ` import test from '#/test' @@ -349,11 +360,12 @@ describe("bundler", () => { } `, }, - /* TODO FIX expectedScanLog: `Users/user/project/src/entry.ts: ERROR: Could not resolve "#/test" - NOTE: You can mark the path "#/test" as external to exclude it from the bundle, which will remove this error. - `, */ + bundleErrors: { + "/Users/user/project/src/entry.ts": [`Could not resolve: "#/test". Maybe you need to "bun install"?`], + }, }); - itBundled("tsconfig/TsConfigPathsTypeOnly", { + return; + itBundled("tsconfig/PathsTypeOnly", { // GENERATED files: { "/Users/user/project/entry.ts": /* ts */ ` @@ -382,7 +394,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigJSX", { + itBundled("tsconfig/JSX", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, @@ -396,7 +408,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigNestedJSX", { + itBundled("tsconfig/NestedJSX", { // GENERATED files: { "/Users/user/project/entry.ts": /* ts */ ` @@ -432,7 +444,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigReactJSX", { + itBundled("tsconfig/ReactJSX", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, @@ -447,7 +459,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsConfigReactJSXDev", { + itBundled("tsconfig/ReactJSXDev", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, @@ -461,7 +473,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsConfigReactJSXWithDevInMainConfig", { + itBundled("tsconfig/ReactJSXWithDevInMainConfig", { // GENERATED files: { "/Users/user/project/entry.tsx": `console.log(<><div/><div/></>)`, @@ -478,7 +490,7 @@ describe("bundler", () => { development: true, }, }); - itBundled("tsconfig/TsconfigJsonBaseUrl", { + itBundled("tsconfig/JsonBaseUrl", { // GENERATED files: { "/Users/user/project/src/app/entry.js": /* js */ ` @@ -520,7 +532,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonAbsoluteBaseUrl", { + itBundled("tsconfig/JsonAbsoluteBaseUrl", { // GENERATED files: { "/Users/user/project/src/app/entry.js": /* js */ ` @@ -541,7 +553,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonCommentAllowed", { + itBundled("tsconfig/JsonCommentAllowed", { // GENERATED files: { "/Users/user/project/src/app/entry.js": /* js */ ` @@ -563,7 +575,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonTrailingCommaAllowed", { + itBundled("tsconfig/JsonTrailingCommaAllowed", { // GENERATED files: { "/Users/user/project/src/app/entry.js": /* js */ ` @@ -584,7 +596,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonExtends", { + itBundled("tsconfig/JsonExtends", { // GENERATED files: { "/entry.jsx": `console.log(<div/>, <></>)`, @@ -606,8 +618,8 @@ describe("bundler", () => { `, }, }); - bundlerTest.skip("tsconfig/TsconfigJsonExtendsAbsolute", () => { - expectBundled("tsconfig/TsconfigJsonExtendsAbsoluteUnix", { + test.skip("tsconfig/JsonExtendsAbsolute", () => { + expectBundled("tsconfig/JsonExtendsAbsoluteUnix", { // GENERATED host: "unix", files: { @@ -630,7 +642,7 @@ describe("bundler", () => { `, }, }); - expectBundled("tsconfig/TsconfigJsonExtendsAbsoluteWindows", { + expectBundled("tsconfig/JsonExtendsAbsoluteWindows", { // GENERATED host: "windows", files: { @@ -654,7 +666,7 @@ describe("bundler", () => { }, }); }); - itBundled("tsconfig/TsconfigJsonExtendsThreeLevels", { + itBundled("tsconfig/JsonExtendsThreeLevels", { // GENERATED files: { "/Users/user/project/src/entry.jsx": /* jsx */ ` @@ -689,7 +701,7 @@ describe("bundler", () => { "/Users/user/project/src/path2/works/import.js": `console.log('works')`, }, }); - itBundled("tsconfig/TsconfigJsonExtendsLoop", { + itBundled("tsconfig/JsonExtendsLoop", { // GENERATED files: { "/entry.js": `console.log(123)`, @@ -707,7 +719,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `base.json: WARNING: Base config file "./tsconfig" forms cycle `, */ }); - itBundled("tsconfig/TsconfigJsonExtendsPackage", { + itBundled("tsconfig/JsonExtendsPackage", { // GENERATED files: { "/Users/user/project/src/app/entry.jsx": `console.log(<div/>)`, @@ -725,7 +737,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonOverrideMissing", { + itBundled("tsconfig/JsonOverrideMissing", { // GENERATED files: { "/Users/user/project/src/app/entry.ts": `import 'foo'`, @@ -754,7 +766,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsconfigJsonOverrideNodeModules", { + itBundled("tsconfig/JsonOverrideNodeModules", { // GENERATED files: { "/Users/user/project/src/app/entry.ts": `import 'foo'`, @@ -784,7 +796,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsconfigJsonOverrideInvalid", { + itBundled("tsconfig/JsonOverrideInvalid", { // GENERATED files: { "/entry.ts": ``, @@ -792,7 +804,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `ERROR: Cannot find tsconfig file "this/file/doesn't/exist/tsconfig.json" `, */ }); - itBundled("tsconfig/TsconfigJsonNodeModulesImplicitFile", { + itBundled("tsconfig/JsonNodeModulesImplicitFile", { // GENERATED files: { "/Users/user/project/src/app/entry.tsx": `console.log(<div/>)`, @@ -811,7 +823,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigJsonInsideNodeModules", { + itBundled("tsconfig/JsonInsideNodeModules", { // GENERATED files: { "/Users/user/project/src/app/entry.tsx": `import 'foo'`, @@ -825,7 +837,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigWarningsInsideNodeModules", { + itBundled("tsconfig/WarningsInsideNodeModules", { // GENERATED files: { "/Users/user/project/src/entry.tsx": /* tsx */ ` @@ -840,7 +852,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/foo/tsconfig.json: WARNING: Cannot find base config file "extends for foo" `, */ }); - itBundled("tsconfig/TsconfigRemoveUnusedImports", { + itBundled("tsconfig/RemoveUnusedImports", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -856,7 +868,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigPreserveUnusedImports", { + itBundled("tsconfig/PreserveUnusedImports", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -873,7 +885,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsconfigImportsNotUsedAsValuesPreserve", { + itBundled("tsconfig/ImportsNotUsedAsValuesPreserve", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -894,7 +906,7 @@ describe("bundler", () => { outfile: "/Users/user/project/out.js", mode: "convertformat", }); - itBundled("tsconfig/TsconfigPreserveValueImports", { + itBundled("tsconfig/PreserveValueImports", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -921,7 +933,7 @@ describe("bundler", () => { outfile: "/Users/user/project/out.js", mode: "convertformat", }); - itBundled("tsconfig/TsconfigPreserveValueImportsAndImportsNotUsedAsValuesPreserve", { + itBundled("tsconfig/PreserveValueImportsAndImportsNotUsedAsValuesPreserve", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -949,7 +961,7 @@ describe("bundler", () => { outfile: "/Users/user/project/out.js", mode: "convertformat", }); - itBundled("tsconfig/TsconfigTarget", { + itBundled("tsconfig/Target", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1007,7 +1019,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/es4/tsconfig.json: WARNING: Unrecognized target environment "ES4" `, */ }); - itBundled("tsconfig/TsconfigTargetError", { + itBundled("tsconfig/TargetError", { // GENERATED files: { "/Users/user/project/src/entry.ts": `x = 123n`, @@ -1024,7 +1036,7 @@ describe("bundler", () => { Users/user/project/src/tsconfig.json: NOTE: The target environment was set to "ES2019" here: `, */ }); - itBundled("tsconfig/TsconfigTargetIgnored", { + itBundled("tsconfig/TargetIgnored", { // GENERATED files: { "/Users/user/project/src/entry.ts": `x = 123n`, @@ -1038,7 +1050,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsconfigUseDefineForClassFieldsES2020", { + itBundled("tsconfig/UseDefineForClassFieldsES2020", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1055,7 +1067,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigUseDefineForClassFieldsESNext", { + itBundled("tsconfig/UseDefineForClassFieldsESNext", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1072,7 +1084,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsconfigUnrecognizedTargetWarning", { + itBundled("tsconfig/UnrecognizedTargetWarning", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1099,7 +1111,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/a/tsconfig.json: WARNING: Unrecognized target environment "es3" `, */ }); - itBundled("tsconfig/TsconfigTargetWarning", { + itBundled("tsconfig/TargetWarning", { // GENERATED files: { "/Users/user/project/src/entry.ts": `await 0`, @@ -1117,7 +1129,7 @@ describe("bundler", () => { Users/user/project/src/tsconfig.json: NOTE: The target environment was set to "es6" here: `, */ }); - itBundled("tsconfig/TsconfigOverriddenTargetWarning", { + itBundled("tsconfig/OverriddenTargetWarning", { // GENERATED files: { "/Users/user/project/src/entry.ts": `await 0`, @@ -1135,7 +1147,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/entry.ts: ERROR: Top-level await is not available in the configured target environment (es2020) `, */ }); - itBundled("tsconfig/TsConfigNoBaseURLExtendsPaths", { + itBundled("tsconfig/NoBaseURLExtendsPaths", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1163,7 +1175,7 @@ describe("bundler", () => { NOTE: You can mark the path "foo" as external to exclude it from the bundle, which will remove this error. `, */ }); - itBundled("tsconfig/TsConfigBaseURLExtendsPaths", { + itBundled("tsconfig/BaseURLExtendsPaths", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1190,7 +1202,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigPathsExtendsBaseURL", { + itBundled("tsconfig/PathsExtendsBaseURL", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1217,7 +1229,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigModuleSuffixesInsert", { + itBundled("tsconfig/ModuleSuffixesInsert", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1243,7 +1255,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigModuleSuffixesNoInsert", { + itBundled("tsconfig/ModuleSuffixesNoInsert", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1267,7 +1279,7 @@ describe("bundler", () => { `, }, }); - itBundled("tsconfig/TsConfigModuleSuffixesNoEmpty", { + itBundled("tsconfig/ModuleSuffixesNoEmpty", { // GENERATED files: { "/Users/user/project/src/entry.ts": /* ts */ ` @@ -1287,7 +1299,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/entry.ts: ERROR: Could not resolve "./bar" `, */ }); - itBundled("tsconfig/TsConfigWithStatementAlwaysStrictFalse", { + itBundled("tsconfig/WithStatementAlwaysStrictFalse", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1301,7 +1313,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsConfigWithStatementAlwaysStrictTrue", { + itBundled("tsconfig/WithStatementAlwaysStrictTrue", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1317,7 +1329,7 @@ describe("bundler", () => { Users/user/project/tsconfig.json: NOTE: TypeScript's "alwaysStrict" setting was enabled here: `, */ }); - itBundled("tsconfig/TsConfigWithStatementStrictFalse", { + itBundled("tsconfig/WithStatementStrictFalse", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1331,7 +1343,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsConfigWithStatementStrictTrue", { + itBundled("tsconfig/WithStatementStrictTrue", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1347,7 +1359,7 @@ describe("bundler", () => { Users/user/project/tsconfig.json: NOTE: TypeScript's "strict" setting was enabled here: `, */ }); - itBundled("tsconfig/TsConfigWithStatementStrictFalseAlwaysStrictTrue", { + itBundled("tsconfig/WithStatementStrictFalseAlwaysStrictTrue", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1364,7 +1376,7 @@ describe("bundler", () => { Users/user/project/tsconfig.json: NOTE: TypeScript's "alwaysStrict" setting was enabled here: `, */ }); - itBundled("tsconfig/TsConfigWithStatementStrictTrueAlwaysStrictFalse", { + itBundled("tsconfig/WithStatementStrictTrueAlwaysStrictFalse", { // GENERATED files: { "/Users/user/project/src/entry.ts": `with (x) y`, @@ -1379,7 +1391,7 @@ describe("bundler", () => { }, outfile: "/Users/user/project/out.js", }); - itBundled("tsconfig/TsConfigAlwaysStrictTrueEmitDirectivePassThrough", { + itBundled("tsconfig/AlwaysStrictTrueEmitDirectivePassThrough", { // GENERATED files: { "/Users/user/project/src/implicit.ts": `console.log('this file should start with "use strict"')`, @@ -1398,7 +1410,7 @@ describe("bundler", () => { entryPoints: ["/Users/user/project/src/implicit.ts", "/Users/user/project/src/explicit.ts"], mode: "passthrough", }); - itBundled("tsconfig/TsConfigAlwaysStrictTrueEmitDirectiveFormat", { + itBundled("tsconfig/AlwaysStrictTrueEmitDirectiveFormat", { // GENERATED files: { "/Users/user/project/src/implicit.ts": `console.log('this file should start with "use strict"')`, @@ -1417,7 +1429,7 @@ describe("bundler", () => { entryPoints: ["/Users/user/project/src/implicit.ts", "/Users/user/project/src/explicit.ts"], mode: "convertformat", }); - itBundled("tsconfig/TsConfigAlwaysStrictTrueEmitDirectiveBundleIIFE", { + itBundled("tsconfig/AlwaysStrictTrueEmitDirectiveBundleIIFE", { // GENERATED files: { "/Users/user/project/src/implicit.ts": `console.log('this file should start with "use strict"')`, @@ -1436,7 +1448,7 @@ describe("bundler", () => { entryPoints: ["/Users/user/project/src/implicit.ts", "/Users/user/project/src/explicit.ts"], outdir: "/Users/user/project/out", }); - itBundled("tsconfig/TsConfigAlwaysStrictTrueEmitDirectiveBundleCJS", { + itBundled("tsconfig/AlwaysStrictTrueEmitDirectiveBundleCJS", { // GENERATED files: { "/Users/user/project/src/implicit.ts": `console.log('this file should start with "use strict"')`, @@ -1455,7 +1467,7 @@ describe("bundler", () => { entryPoints: ["/Users/user/project/src/implicit.ts", "/Users/user/project/src/explicit.ts"], outdir: "/Users/user/project/out", }); - itBundled("tsconfig/TsConfigAlwaysStrictTrueEmitDirectiveBundleESM", { + itBundled("tsconfig/AlwaysStrictTrueEmitDirectiveBundleESM", { // GENERATED files: { "/Users/user/project/src/implicit.ts": `console.log('this file should not start with "use strict"')`, @@ -1474,7 +1486,7 @@ describe("bundler", () => { entryPoints: ["/Users/user/project/src/implicit.ts", "/Users/user/project/src/explicit.ts"], outdir: "/Users/user/project/out", }); - itBundled("tsconfig/TsConfigExtendsDotWithoutSlash", { + itBundled("tsconfig/ExtendsDotWithoutSlash", { // GENERATED files: { "/Users/user/project/src/main.ts": `console.log(123n)`, @@ -1497,7 +1509,7 @@ describe("bundler", () => { Users/user/project/src/tsconfig.json: NOTE: The target environment was set to "ES6" here: `, */ }); - itBundled("tsconfig/TsConfigExtendsDotDotWithoutSlash", { + itBundled("tsconfig/ExtendsDotDotWithoutSlash", { // GENERATED files: { "/Users/user/project/src/main.ts": `console.log(123n)`, @@ -1519,7 +1531,7 @@ describe("bundler", () => { Users/user/project/tsconfig.json: NOTE: The target environment was set to "ES6" here: `, */ }); - itBundled("tsconfig/TsConfigExtendsDotWithSlash", { + itBundled("tsconfig/ExtendsDotWithSlash", { // GENERATED files: { "/Users/user/project/src/main.ts": `console.log(123n)`, @@ -1541,7 +1553,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `Users/user/project/src/foo.json: WARNING: Cannot find base config file "./" `, */ }); - itBundled("tsconfig/TsConfigExtendsDotDotWithSlash", { + itBundled("tsconfig/ExtendsDotDotWithSlash", { // GENERATED files: { "/Users/user/project/src/main.ts": `console.log(123n)`, diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index 9d4d0a33a..63f9f8e30 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -12,6 +12,8 @@ import type { Expect } from "bun:test"; import { PluginBuilder } from "bun"; import * as esbuild from "esbuild"; +let currentFile: string | undefined; + type BunTestExports = typeof import("bun:test"); export function testForFile(file: string): BunTestExports { if (file.startsWith("file://")) { @@ -20,7 +22,42 @@ export function testForFile(file: string): BunTestExports { var testFile = testFiles.get(file); if (!testFile) { - testFile = (Bun as any).jest(file); + const native = (Bun as any).jest(file); + const notImplemented: BundlerTestRef[] = []; + testFile = { + it: native.it, + test: native.test, + expect: native.expect, + addNotImplemented: (ref: BundlerTestRef) => notImplemented.push(ref), + }; + testFile.describe = function (name: string, fn: () => void) { + native.describe(name, function () { + if (currentFile) { + throw new Error("please don't nest describe blocks in the bundler tests."); + } + currentFile = file; + fn(); + currentFile = undefined; + if (!FILTER && !process.env.BUN_BUNDLER_TEST_NO_CHECK_SKIPPED) { + native.test(`"${path.basename(file)}" has proper notImplemented markers`, async () => { + console.log(`\n Checking if any of the ${notImplemented.length} not implemented tests work...`); + const implemented = []; + for (const ref of notImplemented) { + try { + await expectBundled(ref.id, { ...ref.options, notImplemented: false }, false, true); + implemented.push({ id: ref.id, success: true }); + } catch (e) {} + } + if (implemented.length) { + throw ( + '"notImplemented" can only be used on failing tests. the following tests pass:\n' + + implemented.map(x => " - " + x.id).join("\n") + ); + } + }); + } + }); + }; testFiles.set(file, testFile); } return testFile; @@ -63,8 +100,8 @@ export interface BundlerTestInput { entryPointsAdvanced?: Array<{ input: string; output?: string }>; /** These are not path resolved. Used for `default/RelativeEntryPointError` */ entryPointsRaw?: string[]; - /** Defaults to bundle */ - mode?: "bundle" | "transform"; + /** Defaults to true */ + bundling?: boolean; /** Used for `default/ErrorMessageCrashStdinESBuildIssue2913`. */ stdin?: { contents: string; cwd: string }; /** Use when doing something weird with entryPoints and you need to check other output paths. */ @@ -122,7 +159,7 @@ export interface BundlerTestInput { unsupportedJSFeatures?: string[]; /** if set to true or false, create or edit tsconfig.json to set compilerOptions.useDefineForClassFields */ useDefineForClassFields?: boolean; - sourceMap?: boolean | "inline" | "external"; + sourceMap?: "inline" | "external" | "none"; plugins?: BunPlugin[] | ((builder: PluginBuilder) => void | Promise<void>); // pass subprocess.env @@ -203,7 +240,7 @@ export interface BundlerTestBundleAPI { */ captureFile(file: string, fnName?: string): string[]; - warnings: Record<string, { file: string; error: string; line?: string; col?: string }[]>; + warnings: Record<string, ErrorMeta[]>; options: BundlerTestInput; } @@ -240,6 +277,13 @@ export interface BundlerTestRef { options: BundlerTestInput; } +interface ErrorMeta { + file: string; + error: string; + line?: string; + col?: string; +} + var testFiles = new Map(); function testRef(id: string, options: BundlerTestInput): BundlerTestRef { @@ -252,18 +296,18 @@ function expectBundled( dryRun = false, ignoreFilter = false, ): Promise<BundlerTestRef> | BundlerTestRef { - var { expect, it, test } = testForFile(callerSourceOrigin()); + var { expect, it, test } = testForFile(currentFile ?? callerSourceOrigin()); if (!ignoreFilter && FILTER && !filterMatches(id)) return testRef(id, opts); let { - notImplemented, - bundleWarnings, - bundleErrors, - banner, - backend, assertNotPresent, - capture, assetNaming, + backend, + banner, + bundleErrors, + bundleWarnings, + bundling, + capture, chunkNaming, cjs2esm, dce, @@ -286,23 +330,23 @@ function expectBundled( matchesReference, metafile, minifyIdentifiers, - serverComponents = false, minifySyntax, minifyWhitespace, - mode, + notImplemented, onAfterBundle, outbase, outdir, outfile, outputPaths, - target, plugins, publicPath, run, runtimeFiles, + serverComponents = false, skipOnEsbuild, sourceMap, splitting, + target, treeShaking, unsupportedCSSFeatures, unsupportedJSFeatures, @@ -327,7 +371,7 @@ function expectBundled( } // Resolve defaults for options and some related things - mode ??= "bundle"; + bundling ??= true; target ??= "browser"; format ??= "esm"; entryPoints ??= entryPointsRaw ? [] : [Object.keys(files)[0]]; @@ -337,6 +381,10 @@ function expectBundled( if (bundleWarnings === true) bundleWarnings = {}; const useOutFile = outfile ? true : outdir ? false : entryPoints.length === 1; + if (bundling === false) { + // https://github.com/oven-sh/bun/issues/2821 + external = ["*"]; + } if (!ESBUILD && format !== "esm") { throw new Error("formats besides esm not implemented in bun build"); } @@ -353,7 +401,7 @@ function expectBundled( throw new Error("unsupportedCSSFeatures not implemented in bun build"); } if (!ESBUILD && outbase) { - throw new Error("outbase not implemented in bun build"); + throw new Error("outbase/root not implemented in bun build"); } if (!ESBUILD && keepNames) { throw new Error("keepNames not implemented in bun build"); @@ -361,9 +409,6 @@ function expectBundled( if (!ESBUILD && mainFields) { throw new Error("mainFields not implemented in bun build"); } - if (!ESBUILD && sourceMap) { - throw new Error("sourceMap not implemented in bun build"); - } if (!ESBUILD && banner) { throw new Error("banner not implemented in bun build"); } @@ -372,7 +417,7 @@ function expectBundled( } if (!ESBUILD && loader) { const loaderValues = [...new Set(Object.values(loader))]; - const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file"]; + const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file", "wtf"]; const unsupportedLoaderTypes = loaderValues.filter(x => !supportedLoaderTypes.includes(x)); if (unsupportedLoaderTypes.length) { throw new Error(`loader '${unsupportedLoaderTypes.join("', '")}' not implemented in bun build`); @@ -408,9 +453,6 @@ function expectBundled( : entryPaths.map(file => path.join(outdir!, path.basename(file))) ).map(x => x.replace(/\.ts$/, ".js")); - if (mode === "transform" && !outfile && !ESBUILD) { - throw new Error("transform mode requires one single outfile"); - } if (cjs2esm && !outfile && !minifySyntax && !minifyWhitespace) { throw new Error("cjs2esm=true requires one output file, minifyWhitespace=false, and minifySyntax=false"); } @@ -456,12 +498,15 @@ function expectBundled( } // Run bun build cli. In the future we can move to using `Bun.Bundler` - let warningReference: Record<string, { file: string; error: string; line?: string; col?: string }[]> = {}; + let warningReference: Record<string, ErrorMeta[]> = {}; + const expectedErrors = bundleErrors + ? Object.entries(bundleErrors).flatMap(([file, v]) => v.map(error => ({ file, error }))) + : null; + if (backend === "cli") { if (plugins) { throw new Error("plugins not possible in backend=CLI"); } - const cmd = ( !ESBUILD ? [ @@ -470,40 +515,39 @@ function expectBundled( "build", ...entryPaths, ...(entryPointsRaw ?? []), - mode === "bundle" ? [outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`] : [], + outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`, define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]), `--target=${target}`, + // `--format=${format}`, 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.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}` : ""}`, + sourceMap && `--sourcemap=${sourceMap}`, 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`, serverComponents && "--server-components", + outbase && `--outbase=${outbase}`, + // inject && inject.map(x => ["--inject", path.join(root, x)]), + // jsx.preserve && "--jsx=preserve", + // legalComments && `--legal-comments=${legalComments}`, // 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}`, - mode === "transform" && "--transform", ] : [ ESBUILD_PATH, - mode === "bundle" && "--bundle", + bundling && "--bundle", outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`, `--format=${format}`, `--platform=${target === "bun" ? "node" : target}`, @@ -514,7 +558,7 @@ 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=${jsx.runtime ?? "automatic"}`, + `--jsx=${jsx.runtime === "classic" ? "transform" : "automatic"}`, // jsx.preserve && "--jsx=preserve", jsx.factory && `--jsx-factory=${jsx.factory}`, jsx.fragment && `--jsx-fragment=${jsx.fragment}`, @@ -527,7 +571,7 @@ function expectBundled( assetNaming !== "[name]-[hash].[ext]" && `--asset-names=${assetNaming.replace(/\.\[ext]$/, "")}`, metafile && `--metafile=${metafile}`, - sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`, + sourceMap && `--sourcemap=${sourceMap}`, banner && `--banner:js=${banner}`, legalComments && `--legal-comments=${legalComments}`, splitting && `--splitting`, @@ -598,10 +642,6 @@ function expectBundled( }); // Check for errors - const expectedErrors = bundleErrors - ? Object.entries(bundleErrors).flatMap(([file, v]) => v.map(error => ({ file, error }))) - : null; - if (!success) { if (!ESBUILD) { const errorText = stderr.toString("utf-8"); @@ -689,11 +729,6 @@ function expectBundled( throw new Error("Errors were expected while bundling:\n" + expectedErrors.map(formatError).join("\n")); } - if (mode === "transform" && !ESBUILD) { - mkdirSync(path.dirname(outfile!), { recursive: true }); - Bun.write(outfile!, stdout); - } - // Check for warnings if (!ESBUILD) { const warningRegex = /^warn: (.*?)\n.*?\n\s*\^\s*\n(.*?)\n/gms; @@ -773,7 +808,7 @@ function expectBundled( plugins: pluginArray, treeShaking, outdir: buildOutDir, - sourcemap: sourceMap === true ? "external" : sourceMap || "none", + sourcemap: sourceMap, splitting, target, publicPath, @@ -805,11 +840,90 @@ for (const blob of build.outputs) { const build = await Bun.build(buildConfig); Bun.gc(true); - console.log(build); + const buildLogs = (build as any).logs; + + if (buildLogs) { + const rawErrors = buildLogs instanceof AggregateError ? buildLogs.errors : [buildLogs]; + const allErrors: ErrorMeta[] = []; + for (const error of rawErrors) { + const str = error.message ?? String(error); + if (str.startsWith("\u001B[2mexpect(") || str.startsWith("expect(")) { + throw error; + } + + // undocuemnted types + const position = error.position as { + lineText: string; + file: string; + namespace: string; + line: number; + column: number; + offset: number; + }; + + const filename = position?.file + ? position.namespace === "file" + ? "/" + path.relative(root, position.file) + : `${position.namespace}:${position.file.replace(root, "")}` + : "<bun>"; + + allErrors.push({ + file: filename, + error: str, + col: position?.column !== undefined ? String(position.column) : undefined, + line: position?.line !== undefined ? String(position.line) : undefined, + }); + } - if (build.logs) { - console.log(build.logs); - throw new Error("TODO: handle build logs, but we should make this api nicer"); + if (DEBUG && allErrors.length) { + console.log("REFERENCE ERRORS OBJECT"); + console.log("bundleErrors: {"); + const files: any = {}; + for (const err of allErrors) { + files[err.file] ??= []; + files[err.file].push(err); + } + for (const [file, errs] of Object.entries(files)) { + console.log(' "' + file + '": ['); + for (const err of errs as any) { + console.log(" `" + err.error + "`,"); + } + console.log(" ],"); + } + console.log("},"); + } + + if (expectedErrors) { + const errorsLeft = [...expectedErrors]; + let unexpectedErrors = []; + + for (const error of allErrors) { + const i = errorsLeft.findIndex(item => error.file === item.file && error.error.includes(item.error)); + if (i === -1) { + unexpectedErrors.push(error); + } else { + errorsLeft.splice(i, 1); + } + } + + if (unexpectedErrors.length) { + throw new Error( + "Unexpected errors reported while bundling:\n" + + [...unexpectedErrors].map(formatError).join("\n") + + "\n\nExpected errors:\n" + + expectedErrors.map(formatError).join("\n"), + ); + } + + if (errorsLeft.length) { + throw new Error("Errors were expected while bundling:\n" + errorsLeft.map(formatError).join("\n")); + } + + return testRef(id, opts); + } + throw new Error("Bundle Failed\n" + [...allErrors].map(formatError).join("\n")); + } else if (expectedErrors && expectedErrors.length > 0) { + throw new Error("Errors were expected while bundling:\n" + expectedErrors.map(formatError).join("\n")); } } else { await esbuild.build({ @@ -914,7 +1028,7 @@ for (const blob of build.outputs) { } if (dce) { const content = readFileSync(fullpath, "utf8"); - const dceFails = [...content.matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; + const dceFails = [...content.replace(/\/\*.*?\*\//g, "").matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; const key = fullpath.slice(root.length); if (dceFails.length) { throw new Error("DCE test did not remove all expected code in " + key + "."); @@ -1160,7 +1274,7 @@ export function itBundled( opts._referenceFn = fn; } const ref = testRef(id, opts); - const { it } = testForFile(callerSourceOrigin()); + const { it, addNotImplemented } = testForFile(currentFile ?? callerSourceOrigin()) as any; if (FILTER && !filterMatches(id)) { return ref; @@ -1173,19 +1287,11 @@ export function itBundled( } } - if (opts.notImplemented) { + if (opts.notImplemented && !FILTER) { if (!HIDE_SKIP) it.skip(id, () => {}); - // commented out because expectBundled was made async and this is no longer possible in a sense. - // try { - // expectBundled(id, opts); - // it(id, () => { - // throw new Error( - // `Test ${id} passes but was marked as "notImplemented"\nPlease remove "notImplemented: true" from this test.`, - // ); - // }); - // } catch (error: any) {} + addNotImplemented({ id, options: opts }); } else { - it(id, () => expectBundled(id, opts)); + it(id, () => expectBundled(id, opts as any)); } return ref; } @@ -1193,13 +1299,13 @@ itBundled.skip = (id: string, opts: BundlerTestInput) => { if (FILTER && !filterMatches(id)) { return testRef(id, opts); } - const { it } = testForFile(callerSourceOrigin()); + const { it } = testForFile(currentFile ?? callerSourceOrigin()); if (!HIDE_SKIP) it.skip(id, () => expectBundled(id, opts)); return testRef(id, opts); }; -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 formatError(err: ErrorMeta) { + return `${err.file}${err.line ? ":" + err.line : ""}${err.col ? ":" + err.col : ""}: ${err.error}`; } function filterMatches(id: string) { diff --git a/test/bundler/report-bundler-test-progress.sh b/test/bundler/report-bundler-test-progress.sh index 7c0266c59..52358a7f3 100644 --- a/test/bundler/report-bundler-test-progress.sh +++ b/test/bundler/report-bundler-test-progress.sh @@ -12,7 +12,7 @@ total_skip=0 for test in $tests; do defined=$(grep "^import" $test -v | grep -v expectBundled.md | grep -Ec "expectBundled|itBundled") - output=$(BUN_BUNDLER_TEST_LOOSE=false bun test $test 2>&1 | tail -n 5) + output=$(BUN_BUNDLER_TEST_LOOSE=false BUN_BUNDLER_TEST_NO_CHECK_SKIPPED=true bun test $test 2>&1 | tail -n 5) pass=$(echo "$output" | grep "pass" | cut -d " " -f 2) fail=$(echo "$output" | grep "fail" | cut -d " " -f 2) skip=$(echo "$output" | grep "skip" | cut -d " " -f 2) |