Skip to content

Commit c7c5a86

Browse files
committed
fix #3041: allow injecting copied files
1 parent ab15c70 commit c7c5a86

File tree

6 files changed

+82
-11
lines changed

6 files changed

+82
-11
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
}
4242
```
4343

44+
* Support `--inject` with a file loaded using the `copy` loader ([#3041](https://github.com/evanw/esbuild/issues/3041))
45+
46+
This release now allows you to use `--inject` with a file that is loaded using the `copy` loader. The `copy` loader copies the imported file to the output directory verbatim and rewrites the path in the `import` statement to point to the copied output file. When used with `--inject`, this means the injected file will be copied to the output directory as-is and a bare `import` statement for that file will be inserted in any non-copy output files that esbuild generates.
47+
48+
Note that since esbuild doesn't parse the contents of copied files, esbuild will not expose any of the export names as usable imports when you do this (in the way that esbuild's `--inject` feature is typically used). However, any side-effects that the injected file has will still occur.
49+
4450
## 0.17.15
4551

4652
* Allow keywords as type parameter names in mapped types ([#3033](https://github.com/evanw/esbuild/issues/3033))

internal/bundler/bundler.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,17 @@ func parseFile(args parseArgs) {
530530
// "options" object to populate the "InjectedFiles" field. So we must
531531
// only send on the "inject" channel after we're done using the "options"
532532
// object so we don't introduce a data race.
533+
isCopyLoader := loader == config.LoaderCopy
534+
if isCopyLoader && args.skipResolve {
535+
// This is not allowed because the import path would have to be rewritten,
536+
// but import paths are not rewritten when bundling isn't enabled.
537+
args.log.AddError(nil, logger.Range{},
538+
fmt.Sprintf("Cannot inject %q with the \"copy\" loader without bundling enabled", source.PrettyPath))
539+
}
533540
args.inject <- config.InjectedFile{
534-
Source: source,
535-
Exports: exports,
541+
Source: source,
542+
Exports: exports,
543+
IsCopyLoader: isCopyLoader,
536544
}
537545
}
538546

internal/bundler_tests/bundler_loader_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,3 +1498,42 @@ func TestLoaderCopyStartsWithDotRelPath(t *testing.T) {
14981498
},
14991499
})
15001500
}
1501+
1502+
func TestLoaderCopyWithInjectedFileNoBundle(t *testing.T) {
1503+
loader_suite.expectBundled(t, bundled{
1504+
files: map[string]string{
1505+
"/src/entry.ts": `console.log('in entry.ts')`,
1506+
"/src/inject.js": `console.log('in inject.js')`,
1507+
},
1508+
entryPaths: []string{"/src/entry.ts"},
1509+
options: config.Options{
1510+
AbsOutputDir: "/out",
1511+
InjectPaths: []string{"/src/inject.js"},
1512+
ExtensionToLoader: map[string]config.Loader{
1513+
".ts": config.LoaderTS,
1514+
".js": config.LoaderCopy,
1515+
},
1516+
},
1517+
expectedScanLog: `ERROR: Cannot inject "src/inject.js" with the "copy" loader without bundling enabled
1518+
`,
1519+
})
1520+
}
1521+
1522+
func TestLoaderCopyWithInjectedFileBundle(t *testing.T) {
1523+
loader_suite.expectBundled(t, bundled{
1524+
files: map[string]string{
1525+
"/src/entry.ts": `console.log('in entry.ts')`,
1526+
"/src/inject.js": `console.log('in inject.js')`,
1527+
},
1528+
entryPaths: []string{"/src/entry.ts"},
1529+
options: config.Options{
1530+
Mode: config.ModeBundle,
1531+
AbsOutputDir: "/out",
1532+
InjectPaths: []string{"/src/inject.js"},
1533+
ExtensionToLoader: map[string]config.Loader{
1534+
".ts": config.LoaderTS,
1535+
".js": config.LoaderCopy,
1536+
},
1537+
},
1538+
})
1539+
}

internal/bundler_tests/snapshots/snapshots_loader.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,15 @@ TestLoaderCopyWithFormat
342342

343343
---------- /out/assets/some.file ----------
344344
stuff
345+
================================================================================
346+
TestLoaderCopyWithInjectedFileBundle
347+
---------- /out/inject-IFR6YGWW.js ----------
348+
console.log('in inject.js')
349+
---------- /out/entry.js ----------
350+
// src/entry.ts
351+
import "./inject-IFR6YGWW.js";
352+
console.log("in entry.ts");
353+
345354
================================================================================
346355
TestLoaderCopyWithTransform
347356
---------- /out/src/entry.js ----------

internal/config/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,9 +559,10 @@ type InjectedDefine struct {
559559
}
560560

561561
type InjectedFile struct {
562-
Exports []InjectableExport
563-
DefineName string
564-
Source logger.Source
562+
Exports []InjectableExport
563+
DefineName string // For injected files generated when you "--define" a non-literal
564+
Source logger.Source
565+
IsCopyLoader bool // If you set the loader to "copy" (see https://github.com/evanw/esbuild/issues/3041)
565566
}
566567

567568
type InjectableExport struct {

internal/js_parser/js_parser.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15394,7 +15394,7 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
1539415394
if p.options.ts.Parse && foundImports && isUnusedInTypeScript && (p.options.unusedImportFlagsTS&config.UnusedImportKeepStmt) == 0 {
1539515395
// Ignore import records with a pre-filled source index. These are
1539615396
// for injected files and we definitely do not want to trim these.
15397-
if !record.SourceIndex.IsValid() {
15397+
if !record.SourceIndex.IsValid() && !record.CopySourceIndex.IsValid() {
1539815398
record.Flags |= ast.IsUnused
1539915399
continue
1540015400
}
@@ -15879,7 +15879,11 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast
1587915879
}
1588015880
}
1588115881

15882-
before = p.generateImportStmt(file.Source.KeyPath.Text, exportsNoConflict, &file.Source.Index, before, symbols)
15882+
if file.IsCopyLoader {
15883+
before = p.generateImportStmt(file.Source.KeyPath.Text, exportsNoConflict, before, symbols, nil, &file.Source.Index)
15884+
} else {
15885+
before = p.generateImportStmt(file.Source.KeyPath.Text, exportsNoConflict, before, symbols, &file.Source.Index, nil)
15886+
}
1588315887
}
1588415888

1588515889
// Bind symbols in a second pass over the AST. I started off doing this in a
@@ -16326,9 +16330,10 @@ func (p *parser) computeCharacterFrequency() *js_ast.CharFreq {
1632616330
func (p *parser) generateImportStmt(
1632716331
path string,
1632816332
imports []string,
16329-
sourceIndex *uint32,
1633016333
parts []js_ast.Part,
1633116334
symbols map[string]js_ast.LocRef,
16335+
sourceIndex *uint32,
16336+
copySourceIndex *uint32,
1633216337
) []js_ast.Part {
1633316338
var loc logger.Loc
1633416339
isFirst := true
@@ -16347,6 +16352,9 @@ func (p *parser) generateImportStmt(
1634716352
if sourceIndex != nil {
1634816353
p.importRecords[importRecordIndex].SourceIndex = ast.MakeIndex32(*sourceIndex)
1634916354
}
16355+
if copySourceIndex != nil {
16356+
p.importRecords[importRecordIndex].CopySourceIndex = ast.MakeIndex32(*copySourceIndex)
16357+
}
1635016358
declaredSymbols[0] = js_ast.DeclaredSymbol{Ref: namespaceRef, IsTopLevel: true}
1635116359

1635216360
// Create per-import information
@@ -16396,7 +16404,7 @@ func (p *parser) toAST(before, parts, after []js_ast.Part, hashbang string, dire
1639616404
if len(p.runtimeImports) > 0 && !p.options.omitRuntimeForTests {
1639716405
keys := sortedKeysOfMapStringLocRef(p.runtimeImports)
1639816406
sourceIndex := runtime.SourceIndex
16399-
before = p.generateImportStmt("<runtime>", keys, &sourceIndex, before, p.runtimeImports)
16407+
before = p.generateImportStmt("<runtime>", keys, before, p.runtimeImports, &sourceIndex, nil)
1640016408
}
1640116409

1640216410
// Insert an import statement for any jsx runtime imports we generated
@@ -16411,14 +16419,14 @@ func (p *parser) toAST(before, parts, after []js_ast.Part, hashbang string, dire
1641116419
path = path + "/jsx-runtime"
1641216420
}
1641316421

16414-
before = p.generateImportStmt(path, keys, nil, before, p.jsxRuntimeImports)
16422+
before = p.generateImportStmt(path, keys, before, p.jsxRuntimeImports, nil, nil)
1641516423
}
1641616424

1641716425
// Insert an import statement for any legacy jsx imports we generated (i.e., createElement)
1641816426
if len(p.jsxLegacyImports) > 0 && !p.options.omitJSXRuntimeForTests {
1641916427
keys := sortedKeysOfMapStringLocRef(p.jsxLegacyImports)
1642016428
path := p.options.jsx.ImportSource
16421-
before = p.generateImportStmt(path, keys, nil, before, p.jsxLegacyImports)
16429+
before = p.generateImportStmt(path, keys, before, p.jsxLegacyImports, nil, nil)
1642216430
}
1642316431

1642416432
// Generated imports are inserted before other code instead of appending them

0 commit comments

Comments
 (0)