Skip to content

Commit

Permalink
Preserve path in referenced file manifest keys
Browse files Browse the repository at this point in the history
This means that if there is a “images/a/a.png” and a “images/b/b.png”, they’ll both have consistent manifest keys (“a/a.png” and “b/b.png” respectively), even in cases where esbuild is directly bundling one of the two files.
  • Loading branch information
timriley committed Mar 29, 2024
1 parent 1c829ee commit bc46ffd
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 18 deletions.
44 changes: 38 additions & 6 deletions dist/esbuild-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,45 @@ const hanamiEsbuild = (options) => {
}
// Add files already bundled by esbuild into the manifest
const fileHashRegexp = /(-[A-Z0-9]{8})(\.\S+)$/;
for (const outputFile of outputFiles(outputs)) {
// Convert "public/assets/app-2TLUHCQ6.js" to "app.js"
let sourceUrl = outputFile
.replace(options.destDir + "/", "")
.replace(fileHashRegexp, "$2");
const sourceAssetsDir = path.join(options.sourceDir, assetsDirName); // TODO make better
for (const outputFile in outputs) {
// Ignore esbuild's generated map files
if (outputFile.endsWith(".map")) {
continue;
}
const outputAttrs = outputs[outputFile];
const inputFiles = Object.keys(outputAttrs.inputs);
// Determine the manifest key for the esbuild output file
let manifestKey;
if (!(outputFile.endsWith(".js") || outputFile.endsWith(".css")) &&
inputFiles.length == 1 &&
inputFiles[0].startsWith(sourceAssetsDir + path.sep)) {
// A non-JS/CSS output with a single input will be an asset file that has been been
// referenced from JS/CSS.
//
// In this case, preserve the original input file's path in the manifest key, so it
// matches any other files copied over from that path via processAssetDirectory.
//
// For example, given the input file "app/assets/images/icons/some-icon.png", return a
// manifest key of "icons/some-icon.png".
manifestKey = inputFiles[0]
.substring(sourceAssetsDir.length + 1) // + 1 to account for the sep
.split(path.sep)
.slice(1)
.join(path.sep);
}
else {
// For all other outputs, determine the manifest key based on the output file name,
// stripping away the hash suffix added by esbuild.
//
// For example, given the output "public/assets/app-2TLUHCQ6.js", return an manifest
// key of "app.js".
manifestKey = outputFile
.replace(options.destDir + path.sep, "")
.replace(fileHashRegexp, "$2");
}
const destinationUrl = calulateDestinationUrl(outputFile);
assetsManifest[sourceUrl] = prepareAsset(outputFile, destinationUrl);
assetsManifest[manifestKey] = prepareAsset(outputFile, destinationUrl);
}
// Add copied assets into the manifest
for (const copiedAsset of copiedAssets) {
Expand Down
47 changes: 41 additions & 6 deletions src/esbuild-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,50 @@ const hanamiEsbuild = (options: PluginOptions): Plugin => {

// Add files already bundled by esbuild into the manifest
const fileHashRegexp = /(-[A-Z0-9]{8})(\.\S+)$/;
for (const outputFile of outputFiles(outputs)) {
// Convert "public/assets/app-2TLUHCQ6.js" to "app.js"
let sourceUrl = outputFile
.replace(options.destDir + "/", "")
.replace(fileHashRegexp, "$2");
const sourceAssetsDir = path.join(options.sourceDir, assetsDirName); // TODO make better
for (const outputFile in outputs) {
// Ignore esbuild's generated map files
if (outputFile.endsWith(".map")) {
continue;
}

const outputAttrs = outputs[outputFile];
const inputFiles = Object.keys(outputAttrs.inputs);

// Determine the manifest key for the esbuild output file
let manifestKey: string;
if (
!(outputFile.endsWith(".js") || outputFile.endsWith(".css")) &&
inputFiles.length == 1 &&
inputFiles[0].startsWith(sourceAssetsDir + path.sep)
) {
// A non-JS/CSS output with a single input will be an asset file that has been been
// referenced from JS/CSS.
//
// In this case, preserve the original input file's path in the manifest key, so it
// matches any other files copied over from that path via processAssetDirectory.
//
// For example, given the input file "app/assets/images/icons/some-icon.png", return a
// manifest key of "icons/some-icon.png".
manifestKey = inputFiles[0]
.substring(sourceAssetsDir.length + 1) // + 1 to account for the sep
.split(path.sep)
.slice(1)
.join(path.sep);
} else {
// For all other outputs, determine the manifest key based on the output file name,
// stripping away the hash suffix added by esbuild.
//
// For example, given the output "public/assets/app-2TLUHCQ6.js", return an manifest
// key of "app.js".
manifestKey = outputFile
.replace(options.destDir + path.sep, "")
.replace(fileHashRegexp, "$2");
}

const destinationUrl = calulateDestinationUrl(outputFile);

assetsManifest[sourceUrl] = prepareAsset(outputFile, destinationUrl);
assetsManifest[manifestKey] = prepareAsset(outputFile, destinationUrl);
}

// Add copied assets into the manifest
Expand Down
14 changes: 8 additions & 6 deletions test/hanami-assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,23 @@ describe("hanami-assets", () => {
test("handles references to files outside js/ and css/ directories", async () => {
const entryPoint = path.join(dest, "app/assets/js/app.js");
await fs.writeFile(entryPoint, 'import "../css/app.css";');
// const entryPoint2 = path.join(dest, "app/assets/js/nested/app.js");
// await fs.writeFile(entryPoint2, "");
const cssFile = path.join(dest, "app/assets/css/app.css");
await fs.writeFile(
cssFile,
'@font-face { font-family: "comic-mono"; src: url("../fonts/comic-mono.ttf"); }',
'@font-face { font-family: "comic-mono"; src: url("../fonts/comic-mono/comic-mono.ttf"); }',
);
const fontFile = path.join(dest, "app/assets/fonts/comic-mono.ttf");
await fs.ensureDir(path.join(dest, "app/assets/fonts/comic-mono"));
const fontFile = path.join(dest, "app/assets/fonts/comic-mono/comic-mono.ttf");
await fs.writeFile(fontFile, "");

await assets.run({ root: dest, argv: ["--path=app", "--dest=public/assets"] });

const entryPointExists = await fs.pathExists(path.join("public/assets/app-6PW7FGD5.js"));
expect(entryPointExists).toBe(true);
const cssExists = await fs.pathExists(path.join("public/assets/app-LI4JR7XG.css"));
const cssExists = await fs.pathExists(path.join("public/assets/app-GIY6HCGO.css"));
expect(cssExists).toBe(true);
// NOT comic-mono-E3B0C442.ttf - it is a duplicate; this is what our manual asset copying creates.
const fontExists = await fs.pathExists(path.join("public/assets/comic-mono-55DNWN2R.ttf"));
expect(fontExists).toBe(true);

Expand All @@ -162,11 +164,11 @@ describe("hanami-assets", () => {
"app.js": {
url: "/assets/app-6PW7FGD5.js",
},
"comic-mono.ttf": {
"comic-mono/comic-mono.ttf": {
url: "/assets/comic-mono-55DNWN2R.ttf",
},
"app.css": {
url: "/assets/app-LI4JR7XG.css",
url: "/assets/app-GIY6HCGO.css",
},
});
});
Expand Down

0 comments on commit bc46ffd

Please sign in to comment.