diff --git a/CHANGELOG.md b/CHANGELOG.md index be7feee83b3..31a52dfb347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ Note that these old node versions are [currently in maintenance](https://nodejs.org/en/about/releases/). I recommend upgrading to a modern version of node if run-time performance is important to you. +* Paths starting with `node:` are implicitly external when bundling for node ([#1466](https://github.com/evanw/esbuild/issues/1466)) + + This replicates a new node feature where you can [prefix an import path with `node:`](https://nodejs.org/api/esm.html#esm_node_imports) to load a native node module by that name (such as `import fs from "node:fs/promises"`). These paths also [have special behavior](https://nodejs.org/api/modules.html#modules_core_modules): + + > Core modules can also be identified using the `node:` prefix, in which case it bypasses the `require` cache. For instance, `require('node:http')` will always return the built in HTTP module, even if there is `require.cache` entry by that name. + + With this release, esbuild's built-in resolver will now automatically consider all import paths starting with `node:` as external. This new behavior is only active when the current platform is set to node such as with `--platform=node`. If you need to customize this behavior, you can write a plugin to intercept these paths and treat them differently. + ## 0.12.15 * Fix a bug with `var()` in CSS color lowering ([#1421](https://github.com/evanw/esbuild/issues/1421)) diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go index d611eb58ed0..17d402ef444 100644 --- a/internal/bundler/bundler_default_test.go +++ b/internal/bundler/bundler_default_test.go @@ -2351,6 +2351,23 @@ func TestAutoExternal(t *testing.T) { }) } +func TestAutoExternalNode(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + // These URLs should be external automatically + import fs from "node:fs/promises"; + `, + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputDir: "/out", + Platform: config.PlatformNode, + }, + }) +} + func TestExternalWithWildcard(t *testing.T) { default_suite.expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 2ac6aa8c64f..53c57d41049 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -133,6 +133,12 @@ import "https://example.com/code.js"; import "//example.com/code.js"; import "data:application/javascript;base64,ZXhwb3J0IGRlZmF1bHQgMTIz"; +================================================================================ +TestAutoExternalNode +---------- /out/entry.js ---------- +// entry.js +import fs from "node:fs/promises"; + ================================================================================ TestAvoidTDZ ---------- /out.js ---------- diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 91e9354dce6..c4ddebd1c91 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -292,7 +292,10 @@ func (rr *resolver) Resolve(sourceDir string, importPath string, kind ast.Import strings.HasPrefix(importPath, "https://") || // "background: url(//example.com/images/image.png);" - strings.HasPrefix(importPath, "//") { + strings.HasPrefix(importPath, "//") || + + // "import fs from 'node:fs'" + (r.options.Platform == config.PlatformNode && strings.HasPrefix(importPath, "node:")) { if r.debugLogs != nil { r.debugLogs.addNote("Marking this path as implicitly external")