don't use contents for file loader hash

This commit is contained in:
Evan Wallace
2020-06-14 17:35:21 -07:00
parent db6a811c23
commit 571967e03d
2 changed files with 85 additions and 20 deletions

View File

@@ -7,6 +7,7 @@ import (
"mime"
"net/http"
"sort"
"strings"
"sync"
"github.com/evanw/esbuild/internal/ast"
@@ -184,17 +185,10 @@ func parseFile(args parseArgs) {
result.file.ignoreIfUnused = true
case LoaderFile:
// Get the file name, making sure to use the "fs" interface so we do the
// right thing on Windows (Windows-style paths for the command-line
// interface and Unix-style paths for tests, even on Windows)
baseName := args.fs.Base(args.absPath)
// Add a hash to the file name to prevent multiple files with the same name
// but different contents from colliding
bytes := []byte(source.Contents)
hashBytes := sha1.Sum(bytes)
hash := base64.URLEncoding.EncodeToString(hashBytes[:])[:8]
baseName = baseName[:len(baseName)-len(extension)] + "." + hash + extension
// Add a hash to the file name to prevent multiple files with the same base
// name from colliding. Avoid using the absolute path to prevent build
// output from being different on different machines.
baseName := baseNameForAvoidingCollisions(args.fs, args.absPath)
// Determine the destination folder
targetFolder := args.bundleOptions.AbsOutputDir
@@ -218,7 +212,7 @@ func parseFile(args parseArgs) {
// the file if the module isn't removed due to tree shaking.
result.file.additionalFile = &OutputFile{
AbsPath: args.fs.Join(targetFolder, baseName),
Contents: bytes,
Contents: []byte(source.Contents),
jsonMetadataChunk: jsonMetadataChunk,
}
@@ -231,6 +225,30 @@ func parseFile(args parseArgs) {
args.results <- result
}
func baseNameForAvoidingCollisions(fs fs.FS, absPath string) string {
var toHash []byte
if relPath, ok := fs.RelativeToCwd(absPath); ok {
// Attempt to generate the same base name regardless of what machine or
// operating system we're on. We want to avoid absolute paths because they
// will have different home directories. We also want to avoid path
// separators because they are different on Windows.
toHash = []byte(strings.ReplaceAll(relPath, "\\", "/"))
} else {
// Just use the absolute path if this environment doesn't have a current
// directory. This is the case when running tests, for example.
toHash = []byte(absPath)
}
// Use "URLEncoding" instead of "StdEncoding" to avoid introducing "/"
hashBytes := sha1.Sum(toHash)
hash := base64.URLEncoding.EncodeToString(hashBytes[:])[:8]
// Insert the hash before the extension
base := fs.Base(absPath)
ext := fs.Ext(absPath)
return base[:len(base)-len(ext)] + "." + hash + ext
}
func ScanBundle(
log logging.Log, fs fs.FS, res resolver.Resolver, entryPaths []string,
parseOptions parser.ParseOptions, bundleOptions BundleOptions,

View File

@@ -2807,11 +2807,12 @@ func TestLoaderFile(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
console.log(require('./test.svg'))
console.log(require('./test3.svg'))
`,
// Use an SVG string that has a base64-encoded SHA1 has with a "/" in it
"/test.svg": "<svg>$</svg>",
// "/test3.svg" generates the file name "test3.0sKdZN/F.svg" if the
// standard base64 encoding is used instead of the URL base64 encoding
"/test3.svg": "<svg></svg>",
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
@@ -2826,14 +2827,60 @@ func TestLoaderFile(t *testing.T) {
},
},
expected: map[string]string{
"/out/test.1HOBn_hi.svg": "<svg>$</svg>",
"/out/entry.js": `// /test.svg
var require_test = __commonJS((exports, module) => {
module.exports = "test.1HOBn_hi.svg";
"/out/test3.0sKdZN_F.svg": "<svg></svg>",
"/out/entry.js": `// /test3.svg
var require_test3 = __commonJS((exports, module) => {
module.exports = "test3.0sKdZN_F.svg";
});
// /entry.js
console.log(require_test());
console.log(require_test3());
`,
},
})
}
func TestLoaderFileMultipleNoCollision(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
console.log(
require('./a/test.txt'),
require('./b/test.txt'),
)
`,
// Two files with the same contents but different paths
"/a/test.txt": "test",
"/b/test.txt": "test",
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
IsBundling: true,
},
bundleOptions: BundleOptions{
IsBundling: true,
AbsOutputFile: "/dist/out.js",
ExtensionToLoader: map[string]Loader{
".js": LoaderJS,
".txt": LoaderFile,
},
},
expected: map[string]string{
"/dist/test.d-VvEp_S.txt": "test",
"/dist/test.pL3kpHJC.txt": "test",
"/dist/out.js": `// /a/test.txt
var require_test = __commonJS((exports, module) => {
module.exports = "test.d-VvEp_S.txt";
});
// /b/test.txt
var require_test2 = __commonJS((exports, module) => {
module.exports = "test.pL3kpHJC.txt";
});
// /entry.js
console.log(require_test(), require_test2());
`,
},
})