Skip to content

Commit

Permalink
feat: add support --conditions flag (oven-sh#9106)
Browse files Browse the repository at this point in the history
* feat(options): add possibility to append a custom esm condition

* feat(cli): parse --conditions flag

* test: add case for custom conditions

* fix(cli): not get short-hand --conditions flag

* test: add case using cjs with custom condition

* fix(options): address possible memory issues for esm conditions

* refactor(cli): remove -c alias for --conditions flag

* test: add cases for multiple --conditions specified

* test(bundler): add support to test --conditions

* chore(cli): fix grammar mistakes in --conditions

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
  • Loading branch information
igorwessel and Jarred-Sumner authored Mar 2, 2024
1 parent 3134ae1 commit dcf6f24
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 2 deletions.
12 changes: 12 additions & 0 deletions src/api/schema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,9 @@ pub const Api = struct {
/// source_map
source_map: ?SourceMapMode = null,

/// conditions
conditions: []const []const u8,

pub fn decode(reader: anytype) anyerror!TransformOptions {
var this = std.mem.zeroes(TransformOptions);

Expand Down Expand Up @@ -1839,6 +1842,9 @@ pub const Api = struct {
25 => {
this.source_map = try reader.readValue(SourceMapMode);
},
26 => {
this.conditions = try reader.readArray([]const u8);
},
else => {
return error.InvalidMessage;
},
Expand Down Expand Up @@ -1948,6 +1954,12 @@ pub const Api = struct {
try writer.writeFieldID(25);
try writer.writeEnum(source_map);
}

if (this.conditions) |conditions| {
try writer.writeFieldID(26);
try writer.writeArray([]const u8, conditions);
}

try writer.endMessage();
}
};
Expand Down
1 change: 1 addition & 0 deletions src/bundler/bundle_v2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,7 @@ pub const BundleV2 = struct {
.main_fields = &.{},
.extension_order = &.{},
.env_files = &.{},
.conditions = &.{},
},
completion.env,
);
Expand Down
8 changes: 8 additions & 0 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ pub const Arguments = struct {
clap.parseParam("--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm") catch unreachable,
clap.parseParam("-p, --port <STR> Set the default port for Bun.serve") catch unreachable,
clap.parseParam("-u, --origin <STR>") catch unreachable,
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,
};

const auto_only_params = [_]ParamType{
Expand Down Expand Up @@ -229,6 +230,7 @@ pub const Arguments = struct {
clap.parseParam("--minify-whitespace Minify whitespace") catch unreachable,
clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable,
clap.parseParam("--dump-environment-variables") catch unreachable,
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,
};
pub const build_params = build_only_params ++ transpiler_params_ ++ base_params_;

Expand Down Expand Up @@ -516,6 +518,12 @@ pub const Arguments = struct {

ctx.passthrough = args.remaining();

if (cmd == .AutoCommand or cmd == .RunCommand or cmd == .BuildCommand) {
if (args.options("--conditions").len > 0) {
opts.conditions = args.options("--conditions");
}
}

// runtime commands
if (cmd == .AutoCommand or cmd == .RunCommand or cmd == .TestCommand or cmd == .RunAsNodeCommand) {
const preloads = args.options("--preload");
Expand Down
16 changes: 16 additions & 0 deletions src/options.zig
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,18 @@ pub const ESMConditions = struct {
.require = require_condition_map,
};
}

pub fn appendSlice(self: *ESMConditions, conditions: []const string) !void {
try self.default.ensureUnusedCapacity(conditions.len);
try self.import.ensureUnusedCapacity(conditions.len);
try self.require.ensureUnusedCapacity(conditions.len);

for (conditions) |condition| {
self.default.putAssumeCapacityNoClobber(condition, {});
self.import.putAssumeCapacityNoClobber(condition, {});
self.require.putAssumeCapacityNoClobber(condition, {});
}
}
};

pub const JSX = struct {
Expand Down Expand Up @@ -1685,6 +1697,10 @@ pub const BundleOptions = struct {

opts.conditions = try ESMConditions.init(allocator, Target.DefaultConditions.get(opts.target));

if (transform.conditions.len > 0) {
opts.conditions.appendSlice(transform.conditions) catch bun.outOfMemory();
}

switch (opts.target) {
.node => {
opts.import_path_format = .relative;
Expand Down
5 changes: 3 additions & 2 deletions test/bundler/esbuild/packagejson.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1381,8 +1381,9 @@ describe("bundler", () => {
"/Users/user/project/node_modules/pkg1/custom2.js": `console.log('SUCCESS')`,
},
outfile: "/Users/user/project/out.js",
bundleErrors: {
"/Users/user/project/src/entry.js": [`Could not resolve: "pkg1". Maybe you need to "bun install"?`],
conditions: ["custom2"],
run: {
stdout: "SUCCESS",
},
});
itBundled("packagejson/ExportsNotExactMissingExtension", {
Expand Down
7 changes: 7 additions & 0 deletions test/bundler/expectBundled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ export interface BundlerTestInput {
assetNaming?: string;
banner?: string;
define?: Record<string, string | number>;

/** Use for resolve custom conditions */
conditions?: String[];

/** Default is "[name].[ext]" */
entryNaming?: string;
/** Default is "[name]-[hash].[ext]" */
Expand Down Expand Up @@ -397,6 +401,7 @@ function expectBundled(
unsupportedCSSFeatures,
unsupportedJSFeatures,
useDefineForClassFields,
conditions,
// @ts-expect-error
_referenceFn,
...unknownProps
Expand Down Expand Up @@ -580,6 +585,7 @@ function expectBundled(
`--target=${target}`,
// `--format=${format}`,
external && external.map(x => ["--external", x]),
conditions && conditions.map(x => ["--conditions", x]),
minifyIdentifiers && `--minify-identifiers`,
minifySyntax && `--minify-syntax`,
minifyWhitespace && `--minify-whitespace`,
Expand Down Expand Up @@ -616,6 +622,7 @@ function expectBundled(
minifyWhitespace && `--minify-whitespace`,
globalName && `--global-name=${globalName}`,
external && external.map(x => `--external:${x}`),
conditions && `--conditions=${conditions.join(",")}`,
inject && inject.map(x => `--inject:${path.join(root, x)}`),
define && Object.entries(define).map(([k, v]) => `--define:${k}=${v}`),
`--jsx=${jsx.runtime === "classic" ? "transform" : "automatic"}`,
Expand Down
133 changes: 133 additions & 0 deletions test/js/bun/resolve/import-custom-condition.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { it, expect, beforeAll } from "bun:test";
import { writeFileSync } from "fs";
import { bunExe, bunEnv, tempDirWithFiles } from "harness";

let dir: string;

beforeAll(() => {
dir = tempDirWithFiles("customcondition", {
"./node_modules/custom/index.js": "export const foo = 1;",
"./node_modules/custom/not_allow.js": "throw new Error('should not be imported')",
"./node_modules/custom/package.json": JSON.stringify({
name: "custom",
exports: {
"./test": {
first: "./index.js",
default: "./not_allow.js",
},
},
}),

"./node_modules/custom2/index.cjs": "module.exports.foo = 5;",
"./node_modules/custom2/index.mjs": "export const foo = 1;",
"./node_modules/custom2/not_allow.js": "throw new Error('should not be imported')",
"./node_modules/custom2/package.json": JSON.stringify({
name: "custom2",
exports: {
"./test": {
first: {
import: "./index.mjs",
require: "./index.cjs",
default: "./index.mjs",
},
default: "./not_allow.js",
},
"./test2": {
second: {
import: "./index.mjs",
require: "./index.cjs",
default: "./index.mjs",
},
default: "./not_allow.js",
},
"./test3": {
third: {
import: "./index.mjs",
require: "./index.cjs",
default: "./index.mjs",
},
default: "./not_allow.js",
},
},
type: "module",
}),
});

writeFileSync(`${dir}/test.js`, `import {foo} from 'custom/test';\nconsole.log(foo);`);
writeFileSync(`${dir}/test.cjs`, `const {foo} = require("custom2/test");\nconsole.log(foo);`);
writeFileSync(
`${dir}/multiple-conditions.js`,
`const pkg1 = require("custom2/test");\nconst pkg2 = require("custom2/test2");\nconst pkg3 = require("custom2/test3");\nconsole.log(pkg1.foo, pkg2.foo, pkg3.foo);`,
);

writeFileSync(
`${dir}/package.json`,
JSON.stringify(
{
name: "hello",
imports: {
custom: "custom",
custom2: "custom2",
},
},
null,
2,
),
);
});

it("custom condition 'import' in package.json resolves", async () => {
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--conditions=first", `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});

expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("1\n");
});

it("custom condition 'require' in package.json resolves", async () => {
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--conditions=first", `${dir}/test.cjs`],
env: bunEnv,
cwd: import.meta.dir,
});

expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("5\n");
});

it("multiple conditions in package.json resolves", async () => {
const { exitCode, stdout } = Bun.spawnSync({
cmd: [bunExe(), "--conditions=first", "--conditions=second", "--conditions=third", `${dir}/multiple-conditions.js`],
env: bunEnv,
cwd: import.meta.dir,
});

expect(exitCode).toBe(0);
expect(stdout.toString("utf8")).toBe("5 5 5\n");
});

it("multiple conditions when some not specified should resolves to fallback", async () => {
const { exitCode, stderr } = Bun.spawnSync({
cmd: [bunExe(), "--conditions=first", "--conditions=second", `${dir}/multiple-conditions.js`],
env: bunEnv,
cwd: import.meta.dir,
});

expect(exitCode).toBe(1);

// not_allow.js is the fallback for third condition, so it should be in stderr
expect(stderr.toString("utf8")).toMatch("new Error('should not be imported')");
});

it("custom condition when don't match condition should resolves to default", async () => {
const { exitCode } = Bun.spawnSync({
cmd: [bunExe(), "--conditions=first1", `${dir}/test.js`],
env: bunEnv,
cwd: import.meta.dir,
});

expect(exitCode).toBe(1);
});

0 comments on commit dcf6f24

Please sign in to comment.