Skip to content

Commit

Permalink
fix #1691: support "imports" in "package.json"
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 23, 2021
1 parent aac02ae commit 62a3943
Show file tree
Hide file tree
Showing 6 changed files with 589 additions and 158 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,47 @@

## Unreleased

* Add support for `imports` in `package.json` ([#1691](https://github.com/evanw/esbuild/issues/1691))

This release adds basic support for the `imports` field in `package.json`. It behaves similarly to the `exports` field but only applies to import paths that start with `#`. The `imports` field provides a way for a package to remap its own internal imports for itself, while the `exports` field provides a way for a package to remap its external exports for other packages. This is useful because the `imports` field respects the currently-configured conditions which means that the import mapping can change at run-time. For example:

```
$ cat entry.mjs
import '#example'

$ cat package.json
{
"imports": {
"#example": {
"foo": "./example.foo.mjs",
"default": "./example.mjs"
}
}
}

$ cat example.foo.mjs
console.log('foo is enabled')

$ cat example.mjs
console.log('foo is disabled')

$ node entry.mjs
foo is disabled

$ node --conditions=foo entry.mjs
foo is enabled
```

Now that esbuild supports this feature too, import paths starting with `#` and any provided conditions will be respected when bundling:

```
$ esbuild --bundle entry.mjs | node
foo is disabled

$ esbuild --conditions=foo --bundle entry.mjs | node
foo is enabled
```

* Fix using `npm rebuild` with the `esbuild` package ([#1703](https://github.com/evanw/esbuild/issues/1703))

Version 0.13.4 accidentally introduced a regression in the install script where running `npm rebuild` multiple times could fail after the second time. The install script creates a copy of the binary executable using [`link`](https://man7.org/linux/man-pages/man2/link.2.html) followed by [`rename`](https://www.man7.org/linux/man-pages/man2/rename.2.html). Using `link` creates a hard link which saves space on the file system, and `rename` is used for safety since it atomically replaces the destination.
Expand Down
159 changes: 159 additions & 0 deletions internal/bundler/bundler_packagejson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1929,3 +1929,162 @@ Users/user/project/src/entry.js: note: Import from "pkg/extra/other/file.js" to
`,
})
}

func TestPackageJsonImports(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#top-level'
import '#nested/path.js'
import '#star/c.js'
import '#slash/d.js'
`,
"/Users/user/project/src/package.json": `
{
"imports": {
"#top-level": "./a.js",
"#nested/path.js": "./b.js",
"#star/*": "./some-star/*",
"#slash/": "./some-slash/"
}
}
`,
"/Users/user/project/src/a.js": `console.log('a.js')`,
"/Users/user/project/src/b.js": `console.log('b.js')`,
"/Users/user/project/src/some-star/c.js": `console.log('c.js')`,
"/Users/user/project/src/some-slash/d.js": `console.log('d.js')`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestPackageJsonImportsRemapToOtherPackage(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#top-level'
import '#nested/path.js'
import '#star/c.js'
import '#slash/d.js'
`,
"/Users/user/project/src/package.json": `
{
"imports": {
"#top-level": "pkg/a.js",
"#nested/path.js": "pkg/b.js",
"#star/*": "pkg/some-star/*",
"#slash/": "pkg/some-slash/"
}
}
`,
"/Users/user/project/src/node_modules/pkg/a.js": `console.log('a.js')`,
"/Users/user/project/src/node_modules/pkg/b.js": `console.log('b.js')`,
"/Users/user/project/src/node_modules/pkg/some-star/c.js": `console.log('c.js')`,
"/Users/user/project/src/node_modules/pkg/some-slash/d.js": `console.log('d.js')`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestPackageJsonImportsErrorMissingRemappedPackage(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#foo'
`,
"/Users/user/project/src/package.json": `
{
"imports": {
"#foo": "bar"
}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
expectedScanLog: `Users/user/project/src/entry.js: error: Could not resolve "#foo" (mark it as external to exclude it from the bundle)
Users/user/project/src/package.json: note: The remapped path "bar" could not be resolved
`,
})
}

func TestPackageJsonImportsInvalidPackageConfiguration(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#foo'
`,
"/Users/user/project/src/package.json": `
{
"imports": "#foo"
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
expectedScanLog: `Users/user/project/src/entry.js: error: Could not resolve "#foo" (mark it as external to exclude it from the bundle)
Users/user/project/src/package.json: note: The package configuration has an invalid value here
Users/user/project/src/package.json: warning: The value for "imports" must be an object
`,
})
}

func TestPackageJsonImportsErrorEqualsHash(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#'
`,
"/Users/user/project/src/package.json": `
{
"imports": {}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
expectedScanLog: `Users/user/project/src/entry.js: error: Could not resolve "#" (mark it as external to exclude it from the bundle)
Users/user/project/src/package.json: note: This "imports" map was ignored because the module specifier "#" is invalid
`,
})
}

func TestPackageJsonImportsErrorStartsWithHashSlash(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import '#/foo'
`,
"/Users/user/project/src/package.json": `
{
"imports": {}
}
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
expectedScanLog: `Users/user/project/src/entry.js: error: Could not resolve "#/foo" (mark it as external to exclude it from the bundle)
Users/user/project/src/package.json: note: This "imports" map was ignored because the module specifier "#/foo" is invalid
`,
})
}
30 changes: 30 additions & 0 deletions internal/bundler/snapshots/snapshots_packagejson.txt
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,36 @@ var require_require = __commonJS({
// Users/user/project/src/entry.js
require_require();

================================================================================
TestPackageJsonImports
---------- /Users/user/project/out.js ----------
// Users/user/project/src/a.js
console.log("a.js");

// Users/user/project/src/b.js
console.log("b.js");

// Users/user/project/src/some-star/c.js
console.log("c.js");

// Users/user/project/src/some-slash/d.js
console.log("d.js");

================================================================================
TestPackageJsonImportsRemapToOtherPackage
---------- /Users/user/project/out.js ----------
// Users/user/project/src/node_modules/pkg/a.js
console.log("a.js");

// Users/user/project/src/node_modules/pkg/b.js
console.log("b.js");

// Users/user/project/src/node_modules/pkg/some-star/c.js
console.log("c.js");

// Users/user/project/src/node_modules/pkg/some-slash/d.js
console.log("d.js");

================================================================================
TestPackageJsonMain
---------- /Users/user/project/out.js ----------
Expand Down

0 comments on commit 62a3943

Please sign in to comment.