From d7ff0d438277960295d7788615a0cde2aa40847d Mon Sep 17 00:00:00 2001 From: Luca Guidi Date: Fri, 8 Sep 2023 17:08:03 +0200 Subject: [PATCH] Fix: static files were not copied after the first `watch` execution (#8) --- dist/hanami-assets.js | 3 ++- dist/hanami-esbuild-plugin.d.ts | 3 ++- dist/hanami-esbuild-plugin.js | 33 +++++++++++++++------------- src/hanami-assets.ts | 6 ++--- src/hanami-esbuild-plugin.ts | 39 +++++++++++++++++++-------------- test/hanami-assets.test.ts | 18 +++++++++++++-- 6 files changed, 63 insertions(+), 39 deletions(-) diff --git a/dist/hanami-assets.js b/dist/hanami-assets.js index 1539acf..bbb8292 100755 --- a/dist/hanami-assets.js +++ b/dist/hanami-assets.js @@ -94,9 +94,9 @@ var sriAlgorithms = []; if (args['sri']) { sriAlgorithms = args['sri'].split(','); } -const options = { ...hanami_esbuild_plugin_2.defaults, sriAlgorithms: sriAlgorithms }; if (watch) { touchManifest(dest); + const options = { ...hanami_esbuild_plugin_2.defaults, hash: false }; const watchBuildOptions = { bundle: true, outdir: outDir, @@ -119,6 +119,7 @@ if (watch) { }); } else { + const options = { ...hanami_esbuild_plugin_2.defaults, sriAlgorithms: sriAlgorithms }; const config = { bundle: true, outdir: outDir, diff --git a/dist/hanami-esbuild-plugin.d.ts b/dist/hanami-esbuild-plugin.d.ts index 6534698..d50b6ac 100644 --- a/dist/hanami-esbuild-plugin.d.ts +++ b/dist/hanami-esbuild-plugin.d.ts @@ -5,7 +5,8 @@ export interface HanamiEsbuildPluginOptions { destDir: string; manifestPath: string; sriAlgorithms: Array; + hash: boolean; } -export declare const defaults: Pick; +export declare const defaults: Pick; declare const hanamiEsbuild: (options?: HanamiEsbuildPluginOptions) => Plugin; export default hanamiEsbuild; diff --git a/dist/hanami-esbuild-plugin.js b/dist/hanami-esbuild-plugin.js index 20ac82a..ae9b348 100644 --- a/dist/hanami-esbuild-plugin.js +++ b/dist/hanami-esbuild-plugin.js @@ -14,6 +14,7 @@ exports.defaults = { destDir: path_1.default.join('public', 'assets'), manifestPath: path_1.default.join('public', 'assets.json'), sriAlgorithms: [], + hash: true, }; const hanamiEsbuild = (options = { ...exports.defaults }) => { return { @@ -41,9 +42,12 @@ const hanamiEsbuild = (options = { ...exports.defaults }) => { return `${algorithm}-${hash}`; }; // Inspired by https://github.com/evanw/esbuild/blob/2f2b90a99d626921d25fe6d7d0ca50bd48caa427/internal/bundler/bundler.go#L1057 - const calculateHash = (hashBytes) => { - const hash = node_crypto_1.default.createHash('sha256').update(hashBytes).digest('hex'); - return hash.slice(0, 8).toUpperCase(); + const calculateHash = (hashBytes, hash) => { + if (!hash) { + return null; + } + const result = node_crypto_1.default.createHash('sha256').update(hashBytes).digest('hex'); + return result.slice(0, 8).toUpperCase(); }; function extractEsbuildInputs(inputData) { const inputs = {}; @@ -57,22 +61,23 @@ const hanamiEsbuild = (options = { ...exports.defaults }) => { } return inputs; } + // TODO: profile the current implementation vs blindly copying the asset const copyAsset = (srcPath, destPath) => { if (fs_extra_1.default.existsSync(destPath)) { const srcStat = fs_extra_1.default.statSync(srcPath); const destStat = fs_extra_1.default.statSync(destPath); + // File already exists and is up-to-date, skip copying if (srcStat.mtimeMs <= destStat.mtimeMs) { - // File already exists and is up-to-date, skip copying - return false; + return; } } if (!fs_extra_1.default.existsSync(path_1.default.dirname(destPath))) { fs_extra_1.default.mkdirSync(path_1.default.dirname(destPath), { recursive: true }); } fs_extra_1.default.copyFileSync(srcPath, destPath); - return true; + return; }; - const processAssetDirectory = (pattern, inputs) => { + const processAssetDirectory = (pattern, inputs, options) => { const dirPath = path_1.default.dirname(pattern); const files = fs_extra_1.default.readdirSync(dirPath); const assets = []; @@ -82,19 +87,17 @@ const hanamiEsbuild = (options = { ...exports.defaults }) => { if (inputs.hasOwnProperty(srcPath)) { return; } - const fileHash = calculateHash(fs_extra_1.default.readFileSync(srcPath)); + const fileHash = calculateHash(fs_extra_1.default.readFileSync(srcPath), options.hash); const fileExtension = path_1.default.extname(srcPath); const baseName = path_1.default.basename(srcPath, fileExtension); - const destFileName = `${baseName}-${fileHash}${fileExtension}`; + const destFileName = [baseName, fileHash].filter(item => item !== null).join("-") + fileExtension; const destPath = path_1.default.join(options.destDir, path_1.default.relative(dirPath, srcPath).replace(file, destFileName)); if (fs_extra_1.default.lstatSync(srcPath).isDirectory()) { - assets.push(...processAssetDirectory(destPath, inputs)); + assets.push(...processAssetDirectory(destPath, inputs, options)); } else { - const copied = copyAsset(srcPath, destPath); - if (copied) { - assets.push(destPath); - } + copyAsset(srcPath, destPath); + assets.push(destPath); } }); return assets; @@ -105,7 +108,7 @@ const hanamiEsbuild = (options = { ...exports.defaults }) => { const inputs = extractEsbuildInputs(outputs); const copiedAssets = []; externalDirs.forEach((pattern) => { - copiedAssets.push(...processAssetDirectory(pattern, inputs)); + copiedAssets.push(...processAssetDirectory(pattern, inputs, options)); }); const assetsToProcess = Object.keys(outputs).concat(copiedAssets); for (const assetToProcess of assetsToProcess) { diff --git a/src/hanami-assets.ts b/src/hanami-assets.ts index ca2ec8f..c3d2323 100644 --- a/src/hanami-assets.ts +++ b/src/hanami-assets.ts @@ -103,16 +103,15 @@ const entryPoints = globSync([ const entryPointsMatcher = /(app\/assets\/javascripts\/|slices\/(.*\/)assets\/javascripts\/)/ const mappedEntryPoints = mapEntryPoints(entryPoints); const externalDirs = externalEsbuildDirectories(); -var sriAlgorithms = [] as Array; +var sriAlgorithms : Array = []; if (args['sri']) { sriAlgorithms = args['sri'].split(','); } -const options: HanamiEsbuildPluginOptions = { ...defaults, sriAlgorithms: sriAlgorithms }; - if (watch) { touchManifest(dest); + const options: HanamiEsbuildPluginOptions = { ...defaults, hash: false }; const watchBuildOptions: Partial = { bundle: true, outdir: outDir, @@ -135,6 +134,7 @@ if (watch) { process.exit(1); }); } else { + const options: HanamiEsbuildPluginOptions = { ...defaults, sriAlgorithms: sriAlgorithms }; const config: Partial = { bundle: true, outdir: outDir, diff --git a/src/hanami-esbuild-plugin.ts b/src/hanami-esbuild-plugin.ts index 2f182f0..b59a9d8 100644 --- a/src/hanami-esbuild-plugin.ts +++ b/src/hanami-esbuild-plugin.ts @@ -15,14 +15,16 @@ export interface HanamiEsbuildPluginOptions { destDir: string; manifestPath: string; sriAlgorithms: Array; + hash: boolean; } -export const defaults: Pick = { +export const defaults: Pick = { root: '', publicDir: 'public', destDir: path.join('public', 'assets'), manifestPath: path.join('public', 'assets.json'), sriAlgorithms: [], + hash: true, }; interface Asset { @@ -65,10 +67,14 @@ const hanamiEsbuild = (options: HanamiEsbuildPluginOptions = { ...defaults }): P } // Inspired by https://github.com/evanw/esbuild/blob/2f2b90a99d626921d25fe6d7d0ca50bd48caa427/internal/bundler/bundler.go#L1057 - const calculateHash = (hashBytes: Uint8Array): string => { - const hash = crypto.createHash('sha256').update(hashBytes).digest('hex'); + const calculateHash = (hashBytes: Uint8Array, hash: boolean): string | null => { + if (!hash) { + return null; + } + + const result = crypto.createHash('sha256').update(hashBytes).digest('hex'); - return hash.slice(0, 8).toUpperCase(); + return result.slice(0, 8).toUpperCase(); } function extractEsbuildInputs(inputData: Record): Record { @@ -87,14 +93,15 @@ const hanamiEsbuild = (options: HanamiEsbuildPluginOptions = { ...defaults }): P return inputs; } - const copyAsset = (srcPath: string, destPath: string): boolean => { + // TODO: profile the current implementation vs blindly copying the asset + const copyAsset = (srcPath: string, destPath: string): void => { if (fs.existsSync(destPath)) { const srcStat = fs.statSync(srcPath); const destStat = fs.statSync(destPath); + // File already exists and is up-to-date, skip copying if (srcStat.mtimeMs <= destStat.mtimeMs) { - // File already exists and is up-to-date, skip copying - return false; + return; } } @@ -104,10 +111,10 @@ const hanamiEsbuild = (options: HanamiEsbuildPluginOptions = { ...defaults }): P fs.copyFileSync(srcPath, destPath); - return true; + return; }; - const processAssetDirectory = (pattern: string, inputs: Record): string[] => { + const processAssetDirectory = (pattern: string, inputs: Record, options: HanamiEsbuildPluginOptions): string[] => { const dirPath = path.dirname(pattern); const files = fs.readdirSync(dirPath); const assets: string[] = []; @@ -120,19 +127,17 @@ const hanamiEsbuild = (options: HanamiEsbuildPluginOptions = { ...defaults }): P return; } - const fileHash = calculateHash(fs.readFileSync(srcPath)); + const fileHash = calculateHash(fs.readFileSync(srcPath), options.hash); const fileExtension = path.extname(srcPath); const baseName = path.basename(srcPath, fileExtension); - const destFileName = `${baseName}-${fileHash}${fileExtension}`; + const destFileName = [baseName, fileHash].filter(item => item !== null).join("-") + fileExtension; const destPath = path.join(options.destDir, path.relative(dirPath, srcPath).replace(file, destFileName)); if (fs.lstatSync(srcPath).isDirectory()) { - assets.push(...processAssetDirectory(destPath, inputs)); + assets.push(...processAssetDirectory(destPath, inputs, options)); } else { - const copied = copyAsset(srcPath, destPath); - if (copied) { - assets.push(destPath); - } + copyAsset(srcPath, destPath); + assets.push(destPath); } }); @@ -146,7 +151,7 @@ const hanamiEsbuild = (options: HanamiEsbuildPluginOptions = { ...defaults }): P const inputs = extractEsbuildInputs(outputs); const copiedAssets: string[] = []; externalDirs.forEach((pattern) => { - copiedAssets.push(...processAssetDirectory(pattern, inputs)); + copiedAssets.push(...processAssetDirectory(pattern, inputs, options)); }); const assetsToProcess = Object.keys(outputs).concat(copiedAssets); diff --git a/test/hanami-assets.test.ts b/test/hanami-assets.test.ts index 7dcc8be..af6a884 100644 --- a/test/hanami-assets.test.ts +++ b/test/hanami-assets.test.ts @@ -168,10 +168,15 @@ describe('hanami-assets', () => { }); test("watch", async () => { + const images = path.join(dest, "app", "assets", "images"); + await fs.ensureDir(images); + fs.copySync(path.join(__dirname, "fixtures", "todo", "app", "assets", "images", "background.jpg"), path.join(images, "background.jpg")); + const entryPoint = path.join(dest, "app", "assets", "javascripts", "app.js"); await fs.writeFile(entryPoint, "console.log('Hello, World!');"); const appAsset = path.join(dest, "public", "assets", "app.js"); + const imageAsset = path.join(dest, "public", "assets", "background.jpg"); watchProcess = spawn(binPath, ["--watch"], {cwd: dest}); await fs.writeFile(entryPoint, "console.log('Hello, Watch!');"); @@ -187,8 +192,6 @@ describe('hanami-assets', () => { resolve(true); } - // console.log("Waiting for asset to be generated...", elapsedTime, dest, appAsset); - elapsedTime += intervalTime; if (elapsedTime >= timeout) { clearInterval(interval); @@ -201,12 +204,23 @@ describe('hanami-assets', () => { const found = await appAssetExists(); expect(found).toBe(true); + expect(fs.existsSync(imageAsset)).toBe(true); + // Read the asset file const assetContent = await fs.readFile(appAsset, "utf-8"); // Check if the asset has the expected contents expect(assetContent).toMatch("console.log(\"Hello, Watch!\");"); + const manifestExists = await fs.pathExists(path.join(dest, 'public/assets.json')); + expect(manifestExists).toBe(true); + + // Read and parse the manifest file + const manifestContent = await fs.readFile(path.join(dest, 'public/assets.json'), 'utf-8'); + const manifest = JSON.parse(manifestContent); + + expect(manifest["background.jpg"]).toEqual({"url": "/assets/background.jpg"}) + // childProcess.kill("SIGHUP"); }, watchTimeout + 1000); });