Skip to content

Commit

Permalink
fix #264: dynamic import from parent directory
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 19, 2020
1 parent 08e355f commit 578f895
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

Using a dynamic `import()` expression automatically adds the imported path as an entry point. However, manually adding the imported path to the bundler entry point list resulted in a build failure. This case is now handled.

* Fix dynamic imports from a parent directory ([#264](https://github.com/evanw/esbuild/issues/264))

The nested output directory feature interacted badly with the code splitting feature when an entry point contained a dynamic `import()` to a file from a directory that was a parent directory to all entry points. This caused esbuild to generate output paths starting with `../` which stepped outside of the output directory.

The directory structure of the input files is mirrored in the output directory relative to the [lowest common ancestor](https://en.wikipedia.org/wiki/Lowest_common_ancestor) among all entry point paths. However, code splitting introduces a new entry point for each dynamic import. These additional entry points are not in the original entry point list so they were ignored by the lowest common ancestor algorithm. The fix is to make sure all entry points are included, user-specified and dynamic.

## 0.6.3

* Fix `/* @__PURE__ */` IIFEs at start of statement ([#258](https://github.com/evanw/esbuild/issues/258))
Expand Down
43 changes: 32 additions & 11 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,14 +594,7 @@ func (b *Bundle) Compile(log logging.Log, options config.Options) []OutputFile {
}

// Determine the lowest common ancestor of all entry points
entryPointAbsPaths := make([]string, 0, len(b.entryPoints))
for _, entryPoint := range b.entryPoints {
keyPath := b.sources[entryPoint].KeyPath
if keyPath.IsAbsolute {
entryPointAbsPaths = append(entryPointAbsPaths, keyPath.Text)
}
}
lcaAbsPath := lowestCommonAncestorDirectory(b.fs, entryPointAbsPaths)
lcaAbsPath := b.lowestCommonAncestorDirectory(options.CodeSplitting)

type linkGroup struct {
outputFiles []OutputFile
Expand Down Expand Up @@ -687,15 +680,43 @@ func (b *Bundle) Compile(log logging.Log, options config.Options) []OutputFile {
return outputFiles
}

func lowestCommonAncestorDirectory(fs fs.FS, absPaths []string) string {
func (b *Bundle) lowestCommonAncestorDirectory(codeSplitting bool) string {
isEntryPoint := make(map[uint32]bool)
for _, entryPoint := range b.entryPoints {
isEntryPoint[entryPoint] = true
}

// If code splitting is enabled, also treat dynamic imports as entry points
if codeSplitting {
for _, sourceIndex := range findReachableFiles(b.sources, b.files, b.entryPoints) {
file := b.files[sourceIndex]
for _, part := range file.ast.Parts {
for _, importRecordIndex := range part.ImportRecordIndices {
if record := &file.ast.ImportRecords[importRecordIndex]; record.SourceIndex != nil && record.Kind == ast.ImportDynamic {
isEntryPoint[*record.SourceIndex] = true
}
}
}
}
}

// Ignore any paths for virtual modules (that don't exist on the file system)
absPaths := make([]string, 0, len(isEntryPoint))
for entryPoint := range isEntryPoint {
keyPath := b.sources[entryPoint].KeyPath
if keyPath.IsAbsolute {
absPaths = append(absPaths, keyPath.Text)
}
}

if len(absPaths) == 0 {
return ""
}

lowestAbsDir := fs.Dir(absPaths[0])
lowestAbsDir := b.fs.Dir(absPaths[0])

for _, absPath := range absPaths[1:] {
absDir := fs.Dir(absPath)
absDir := b.fs.Dir(absPath)
lastSlash := 0
a := 0
b := 0
Expand Down
34 changes: 34 additions & 0 deletions internal/bundler/bundler_splitting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,37 @@ export {
},
})
}

func TestSplittingDynamicImportOutsideSourceTreeIssue264(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry1.js": `
import('package')
`,
"/Users/user/project/src/entry2.js": `
import('package')
`,
"/Users/user/project/node_modules/package/index.js": `
console.log('imported')
`,
},
entryPaths: []string{"/Users/user/project/src/entry1.js", "/Users/user/project/src/entry2.js"},
options: config.Options{
IsBundling: true,
CodeSplitting: true,
OutputFormat: config.FormatESModule,
AbsOutputDir: "/out",
},
expected: map[string]string{
"/out/src/entry1.js": `// /Users/user/project/src/entry1.js
import("../node_modules/package/index.js");
`,
"/out/src/entry2.js": `// /Users/user/project/src/entry2.js
import("../node_modules/package/index.js");
`,
"/out/node_modules/package/index.js": `// /Users/user/project/node_modules/package/index.js
console.log("imported");
`,
},
})
}

0 comments on commit 578f895

Please sign in to comment.