Skip to content

Commit

Permalink
use the file namespace for file entry points
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 28, 2021
1 parent 3702817 commit 22956af
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@

Previously esbuild would determine the output path for an entry point by looking at the post-resolved path. For example, running `esbuild --bundle react --outdir=out` would generate the output path `out/index.js` because the input path `react` was resolved to `node_modules/react/index.js`. With this release, the output path is now determined by looking at the pre-resolved path. For example, For example, running `esbuild --bundle react --outdir=out` now generates the output path `out/react.js`. If you need to keep using the output path that esbuild previously generated with the old behavior, you can use the custom output path feature (described above).

* Use the `file` namespace for file entry points ([#791](https://github.com/evanw/esbuild/issues/791))

Plugins that contain an `onResolve` callback with the `file` filter don't apply to entry point paths because it's not clear that entry point paths are files. For example, you could potentially bundle an entry point of `https://www.example.com/file.js` with a HTTP plugin that automatically downloads data from the server at that URL. But this behavior can be unexpected for people writing plugins.

With this release, esbuild will do a quick check first to see if the entry point path exists on the file system before running plugins. If it exists as a file, the namespace will now be `file` for that entry point path. This only checks the exact entry point name and doesn't attempt to search for the file, so for example it won't handle cases where you pass a package path as an entry point or where you pass an entry point without an extension. Hopefully this should help improve this situation in the common case where the entry point is an exact path.

## Unreleased

* Warn about mutation of private methods ([#1067](https://github.com/evanw/esbuild/pull/1067))
Expand Down
64 changes: 42 additions & 22 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ func parseFile(args parseArgs) {
&args.caches.FSCache,
&source,
record.Range,
source.KeyPath.Namespace,
record.Path.Text,
record.Kind,
absResolveDir,
Expand Down Expand Up @@ -698,6 +699,7 @@ func runOnResolvePlugins(
fsCache *cache.FSCache,
importSource *logger.Source,
importPathRange logger.Range,
importNamespace string,
path string,
kind ast.ImportKind,
absResolveDir string,
Expand All @@ -709,10 +711,14 @@ func runOnResolvePlugins(
Kind: kind,
PluginData: pluginData,
}
applyPath := logger.Path{Text: path}
applyPath := logger.Path{
Text: path,
Namespace: importNamespace,
}
if importSource != nil {
resolverArgs.Importer = importSource.KeyPath
applyPath.Namespace = importSource.KeyPath.Namespace
} else {
resolverArgs.Importer.Namespace = importNamespace
}

// Apply resolver plugins in order until one succeeds
Expand Down Expand Up @@ -975,6 +981,7 @@ type scanner struct {
type EntryPoint struct {
InputPath string
OutputPath string
IsFile bool
}

func ScanBundle(
Expand Down Expand Up @@ -1263,27 +1270,34 @@ func (s *scanner) addEntryPoints(entryPoints []EntryPoint) []entryMeta {
})
}

// Entry point paths without a leading "./" are interpreted as package
// paths. This happens because they go through general path resolution
// like all other import paths so that plugins can run on them. Requiring
// a leading "./" for a relative path simplifies writing plugins because
// entry points aren't a special case.
//
// However, requiring a leading "./" also breaks backward compatibility
// and makes working with the CLI more difficult. So attempt to insert
// "./" automatically when needed. We don't want to unconditionally insert
// a leading "./" because the path may not be a file system path. For
// example, it may be a URL. So only insert a leading "./" when the path
// is an exact match for an existing file.
// Check each entry point ahead of time to see if it's a real file
entryPointAbsResolveDir := s.fs.Cwd()
for i, entryPoint := range entryPoints {
if !s.fs.IsAbs(entryPoint.InputPath) && resolver.IsPackagePath(entryPoint.InputPath) {
absPath := s.fs.Join(entryPointAbsResolveDir, entryPoint.InputPath)
dir := s.fs.Dir(absPath)
base := s.fs.Base(absPath)
if entries, err := s.fs.ReadDirectory(dir); err == nil {
if entry, _ := entries.Get(base); entry != nil && entry.Kind(s.fs) == fs.FileEntry {
entryPoints[i].InputPath = "./" + entryPoint.InputPath
for i := range entryPoints {
entryPoint := &entryPoints[i]
absPath := entryPoint.InputPath
if !s.fs.IsAbs(absPath) {
absPath = s.fs.Join(entryPointAbsResolveDir, absPath)
}
dir := s.fs.Dir(absPath)
base := s.fs.Base(absPath)
if entries, err := s.fs.ReadDirectory(dir); err == nil {
if entry, _ := entries.Get(base); entry != nil && entry.Kind(s.fs) == fs.FileEntry {
entryPoint.IsFile = true

// Entry point paths without a leading "./" are interpreted as package
// paths. This happens because they go through general path resolution
// like all other import paths so that plugins can run on them. Requiring
// a leading "./" for a relative path simplifies writing plugins because
// entry points aren't a special case.
//
// However, requiring a leading "./" also breaks backward compatibility
// and makes working with the CLI more difficult. So attempt to insert
// "./" automatically when needed. We don't want to unconditionally insert
// a leading "./" because the path may not be a file system path. For
// example, it may be a URL. So only insert a leading "./" when the path
// is an exact match for an existing file.
if !s.fs.IsAbs(entryPoint.InputPath) && resolver.IsPackagePath(entryPoint.InputPath) {
entryPoint.InputPath = "./" + entryPoint.InputPath
}
}
}
Expand All @@ -1297,6 +1311,11 @@ func (s *scanner) addEntryPoints(entryPoints []EntryPoint) []entryMeta {
entryPointWaitGroup.Add(len(entryPoints))
for i, entryPoint := range entryPoints {
go func(i int, entryPoint EntryPoint) {
namespace := ""
if entryPoint.IsFile {
namespace = "file"
}

// Run the resolver and log an error if the path couldn't be resolved
resolveResult, didLogError, debug := runOnResolvePlugins(
s.options.Plugins,
Expand All @@ -1306,6 +1325,7 @@ func (s *scanner) addEntryPoints(entryPoints []EntryPoint) []entryMeta {
&s.caches.FSCache,
nil,
logger.Range{},
namespace,
entryPoint.InputPath,
ast.ImportEntryPoint,
entryPointAbsResolveDir,
Expand Down
19 changes: 19 additions & 0 deletions scripts/plugin-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,25 @@ let pluginTests = {
assert.strictEqual(result.outputFiles[3].text, `// virtual-ns:input a/b/c.d.e\nconsole.log("input a/b/c.d.e");\n`)
},

async entryPointFileNamespace({ esbuild, testDir }) {
const input = path.join(testDir, 'in.js')
let worked = false
await writeFileAsync(input, 'stuff')
await esbuild.build({
entryPoints: [input],
write: false,
plugins: [{
name: 'name',
setup(build) {
build.onResolve({ filter: /.*/, namespace: 'file' }, () => {
worked = true
})
},
}],
})
assert(worked)
},

async stdinImporter({ esbuild, testDir }) {
const output = path.join(testDir, 'out.js')
await esbuild.build({
Expand Down

0 comments on commit 22956af

Please sign in to comment.