From 8e615f6d87362ca78e05cfa773c5e4164a3f0aa7 Mon Sep 17 00:00:00 2001 From: Gabe Smith Date: Fri, 25 May 2018 07:42:42 -0700 Subject: [PATCH 1/3] Adding optional argument to append fingerprint hash with a new name --- README.md | 19 ++++++++++++++++++ index.js | 14 ++++++++----- spec/indexSpec.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f04b170..7ed9b86 100644 --- a/README.md +++ b/README.md @@ -108,5 +108,24 @@ And in your layout: That's it! Now if you use a cleaner as suggested, along with file watching, you'll be able to run this in `development` and have `bundle.js` rebuild without fingerprinting, and your Rails app with use that directly. Once you build for production, your Rails app and your `bundle.js` will use a fingerprinted version of the file. +### Managing multiple webpack builds + +If your build setup launches webpack multiple times a new hash will be generated with each run. This can cause problems when using the default +arguments. The last run will overwrite any previously written hash value. An additional parameter can be passed to tell the plugin to append a new named value that has the key from the subsequent run. + +Passing a name in the third paramenter will generate a new line in `asset_fingerprint.rb` having the name assigned to the new hash. For example: + +``` +AssetFingerprintPlugin(rubyConfigInitPath, true, 'CUSTOM_ASSET_FINGERPRINT') +``` + +will generate a new line in the file like this: + +``` +ASSET_FINGERPRINT = '0556f9ab7cbd607d10ed' +CUSTOM_ASSET_FINGERPRINT = '921dcffe35e5f2740ef5' +``` + + ### Credit Concept adapted from @samullen - http://samuelmullen.com/articles/replacing-the-rails-asset-pipeline-with-webpack-and-yarn/ diff --git a/index.js b/index.js index 7b6666e..9584f47 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,11 @@ const fs = require('fs'); -function AssetFingerprint(initializerDirectory, needsFingerprint = true) { +function AssetFingerprint(initializerDirectory, needsFingerprint = true, fingerprintName) { _validateInitializerDirectory(); this.initializerDirectory = initializerDirectory; this.needsFingerprint = needsFingerprint; + this.fingerprintName = fingerprintName || 'ASSET_FINGERPRINT'; function _validateInitializerDirectory() { if (initializerDirectory === undefined) { @@ -16,10 +17,13 @@ function AssetFingerprint(initializerDirectory, needsFingerprint = true) { AssetFingerprint.prototype.apply = function(compiler) { compiler.plugin('done', function(stats) { if (this.needsFingerprint) { - let output = `ASSET_FINGERPRINT = '${stats.hash}'`; - let initializerPath = `${this.initializerDirectory}/asset_fingerprint.rb`; - - fs.writeFileSync(initializerPath, output); + let defaultRun = this.fingerprintName === 'ASSET_FINGERPRINT'; + let initializerPath = `${this.initializerDirectory}/asset_fingerprint.rb`; + const newline = defaultRun ? '' : '\r\n'; + const fsMethod = defaultRun ? 'writeFileSync' : 'appendFileSync'; + let output = `${newline}${this.fingerprintName} = '${stats.hash}'`; + fs[fsMethod](initializerPath, output); + console.log(`asset-fingerprint-webpack-rails: updated file ${initializerPath} with ${this.fingerprintName} = ${stats.hash}`) } }.bind(this)); } diff --git a/spec/indexSpec.js b/spec/indexSpec.js index 0412bf7..717d409 100644 --- a/spec/indexSpec.js +++ b/spec/indexSpec.js @@ -1,5 +1,6 @@ describe("AssetFingerprint", function() { var AssetFingerprint = require('../index'); + var fs = require('fs'); var fingerprint; describe("initialization", function() { @@ -37,5 +38,55 @@ describe("AssetFingerprint", function() { expect(fingerprint).toThrowError('Please supply a directory path for your initializer, such as `config/initializers`.'); }); }); + + describe("fingerprint name", function() { + it("should use ASSET_FINGERPRINT when no args are passed", function () { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir); + expect(fingerprint.fingerprintName).toEqual("ASSET_FINGERPRINT"); + }); + + it("should use value when provided", function () { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir, true, "FINGERPRINT_VALUE"); + expect(fingerprint.fingerprintName).toEqual("FINGERPRINT_VALUE"); + }); + }); + + describe("apply", function() { + beforeEach(function() { + spyOn(fs, "writeFileSync"); + spyOn(fs, "appendFileSync"); + spyOn(console, "log"); + }); + + it("should call writeFileSync on default arguments", function() { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir); + fingerprint.apply({ + plugin: function(status, callback) { callback({hash: "A1B2C3D4"}) } + }); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + 'config/initializers/asset_fingerprint.rb', "ASSET_FINGERPRINT = 'A1B2C3D4'"); + + expect(console.log).toHaveBeenCalledWith( + "asset-fingerprint-webpack-rails: updated file config/initializers/asset_fingerprint.rb with ASSET_FINGERPRINT = A1B2C3D4"); + }); + + it("should call appendFileSync on provided fingerprint name argument", function() { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir, true, "FINGERPRINT_VALUE"); + fingerprint.apply({ + plugin: function(status, callback) { callback({hash: "Z1Y2X3W4"}) } + }); + + expect(fs.appendFileSync).toHaveBeenCalledWith( + 'config/initializers/asset_fingerprint.rb', "\r\nFINGERPRINT_VALUE = 'Z1Y2X3W4'"); + + expect(console.log).toHaveBeenCalledWith( + "asset-fingerprint-webpack-rails: updated file config/initializers/asset_fingerprint.rb with FINGERPRINT_VALUE = Z1Y2X3W4"); + }); + }); }); }); From ef931f17d68403474c62003557facb8f9323dc8f Mon Sep 17 00:00:00 2001 From: Gabe Smith Date: Wed, 30 May 2018 08:39:28 -0700 Subject: [PATCH 2/3] Adding input validation checks for fingerprintName --- index.js | 31 ++++++++++++++++--- spec/indexSpec.js | 79 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 9584f47..d217e6e 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,17 @@ const fs = require('fs'); function AssetFingerprint(initializerDirectory, needsFingerprint = true, fingerprintName) { _validateInitializerDirectory(); - this.initializerDirectory = initializerDirectory; + this.initializerPath = `${this.initializerDirectory}/asset_fingerprint.rb`; + + if(fingerprintName) { + _validateFingerprintName(fingerprintName); + + if(_checkForExistingFingerprint(fingerprintName, this.initializerPath)) { + throw new Error('The provided fingerprint name has already been used.'); + } + } + this.needsFingerprint = needsFingerprint; this.fingerprintName = fingerprintName || 'ASSET_FINGERPRINT'; @@ -12,18 +21,32 @@ function AssetFingerprint(initializerDirectory, needsFingerprint = true, fingerp throw new Error('Please supply a directory path for your initializer, such as `config/initializers`.'); } } + + function _validateFingerprintName(fingerprintName) { + if(!/^([A-Z]|_)*_FINGERPRINT$/.test(fingerprintName)) { + throw new Error('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + } + } + + function _checkForExistingFingerprint(fingerprintName, initializerPath) { + if(fs.existsSync(initializerPath)) { + let file = fs.readFileSync(initializerPath, "utf8"); + return file.indexOf(fingerprintName) >= 0; + } else { + return false; + } + } } AssetFingerprint.prototype.apply = function(compiler) { compiler.plugin('done', function(stats) { if (this.needsFingerprint) { let defaultRun = this.fingerprintName === 'ASSET_FINGERPRINT'; - let initializerPath = `${this.initializerDirectory}/asset_fingerprint.rb`; const newline = defaultRun ? '' : '\r\n'; const fsMethod = defaultRun ? 'writeFileSync' : 'appendFileSync'; let output = `${newline}${this.fingerprintName} = '${stats.hash}'`; - fs[fsMethod](initializerPath, output); - console.log(`asset-fingerprint-webpack-rails: updated file ${initializerPath} with ${this.fingerprintName} = ${stats.hash}`) + fs[fsMethod](this.initializerPath, output); + console.log(`asset-fingerprint-webpack-rails: updated file ${this.initializerPath} with ${this.fingerprintName} = ${stats.hash}`) } }.bind(this)); } diff --git a/spec/indexSpec.js b/spec/indexSpec.js index 717d409..980761d 100644 --- a/spec/indexSpec.js +++ b/spec/indexSpec.js @@ -5,6 +5,12 @@ describe("AssetFingerprint", function() { describe("initialization", function() { describe("needsFingerprint", function() { + + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); + }); + it("should default needsFingerprint = true when no args", function() { fingerprint = new AssetFingerprint(''); expect(fingerprint.needsFingerprint).toBeTruthy(); @@ -27,6 +33,11 @@ describe("AssetFingerprint", function() { }); describe("initializer path", function() { + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); + }); + it("should set when supplied", function() { var dir = 'config/initializers'; fingerprint = new AssetFingerprint(dir, true); @@ -40,6 +51,11 @@ describe("AssetFingerprint", function() { }); describe("fingerprint name", function() { + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); + }); + it("should use ASSET_FINGERPRINT when no args are passed", function () { var dir = 'config/initializers'; fingerprint = new AssetFingerprint(dir); @@ -48,8 +64,59 @@ describe("AssetFingerprint", function() { it("should use value when provided", function () { var dir = 'config/initializers'; - fingerprint = new AssetFingerprint(dir, true, "FINGERPRINT_VALUE"); - expect(fingerprint.fingerprintName).toEqual("FINGERPRINT_VALUE"); + fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); + expect(fingerprint.fingerprintName).toEqual("TEST_FINGERPRINT"); + }); + }); + + describe("fingerprint name", function() { + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(true); + spyOn(fs, "readFileSync").and.returnValue("TEST_FINGERPRINT"); + }); + + it("should validate for duplicate name.", function () { + var dir = 'config/initializers'; + fingerprint = function() { new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); } + expect(fingerprint).toThrowError('The provided fingerprint name has already been used.'); + }); + + it("should validate the file name characters.", function() { + var dir = 'config/initializers'; + fingerprint = function() { new AssetFingerprint(dir, true, "asset_FINGERPRINT"); } + expect(fingerprint).toThrowError('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + }); + + it("should validate the file name format.", function() { + var dir = 'config/initializers'; + fingerprint = function() { new AssetFingerprint(dir, true, "BAD_VALUE"); } + expect(fingerprint).toThrowError('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + }); + }); + + describe("fingerprint name", function() { + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(true); + spyOn(fs, "readFileSync").and.returnValue("ASSET_FINGERPRINT"); + }); + + it("should not validate for duplicate name with default arguments.", function () { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir); + expect(fingerprint.fingerprintName).toEqual("ASSET_FINGERPRINT"); + }); + }); + + describe("fingerprint name", function() { + beforeEach(function() { + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); + }); + + it("should not validate when file does not exist.", function () { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); + expect(fingerprint.fingerprintName).toEqual("TEST_FINGERPRINT"); }); }); @@ -57,6 +124,8 @@ describe("AssetFingerprint", function() { beforeEach(function() { spyOn(fs, "writeFileSync"); spyOn(fs, "appendFileSync"); + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); spyOn(console, "log"); }); @@ -76,16 +145,16 @@ describe("AssetFingerprint", function() { it("should call appendFileSync on provided fingerprint name argument", function() { var dir = 'config/initializers'; - fingerprint = new AssetFingerprint(dir, true, "FINGERPRINT_VALUE"); + fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); fingerprint.apply({ plugin: function(status, callback) { callback({hash: "Z1Y2X3W4"}) } }); expect(fs.appendFileSync).toHaveBeenCalledWith( - 'config/initializers/asset_fingerprint.rb', "\r\nFINGERPRINT_VALUE = 'Z1Y2X3W4'"); + 'config/initializers/asset_fingerprint.rb', "\r\nTEST_FINGERPRINT = 'Z1Y2X3W4'"); expect(console.log).toHaveBeenCalledWith( - "asset-fingerprint-webpack-rails: updated file config/initializers/asset_fingerprint.rb with FINGERPRINT_VALUE = Z1Y2X3W4"); + "asset-fingerprint-webpack-rails: updated file config/initializers/asset_fingerprint.rb with TEST_FINGERPRINT = Z1Y2X3W4"); }); }); }); From 345c3762cfdd8f713de888726417ee9620622745 Mon Sep 17 00:00:00 2001 From: Gabe Smith Date: Thu, 31 May 2018 10:13:15 -0700 Subject: [PATCH 3/3] Updated readme, version, adding note about fingerprint name format --- README.md | 2 ++ index.js | 2 +- package.json | 2 +- spec/indexSpec.js | 24 +++++++++--------------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7ed9b86..f8e2a5b 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ ASSET_FINGERPRINT = '0556f9ab7cbd607d10ed' CUSTOM_ASSET_FINGERPRINT = '921dcffe35e5f2740ef5' ``` +The format for the fingerprint name must uppercase separating words with underscore i.e. "CUSTOM_ASSET_FINGERPRINT". The name also needs to end with "_FINGERPRINT". + ### Credit Concept adapted from @samullen - http://samuelmullen.com/articles/replacing-the-rails-asset-pipeline-with-webpack-and-yarn/ diff --git a/index.js b/index.js index d217e6e..ff4c6c2 100644 --- a/index.js +++ b/index.js @@ -24,7 +24,7 @@ function AssetFingerprint(initializerDirectory, needsFingerprint = true, fingerp function _validateFingerprintName(fingerprintName) { if(!/^([A-Z]|_)*_FINGERPRINT$/.test(fingerprintName)) { - throw new Error('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + throw new Error('Please supply a fingerprint name that is all caps separating words by underscores ending with "_FINGERPRINT" (i.e. CUSTOM_ASSET_FINGERPRINT).'); } } diff --git a/package.json b/package.json index e4623cf..b013617 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asset-fingerprint-webpack-rails", - "version": "1.1.1", + "version": "1.2.1", "description": "A webpack plugin to fingerprint your JS for consumption by Rails", "main": "index.js", "scripts": { diff --git a/spec/indexSpec.js b/spec/indexSpec.js index 980761d..49f736e 100644 --- a/spec/indexSpec.js +++ b/spec/indexSpec.js @@ -67,9 +67,16 @@ describe("AssetFingerprint", function() { fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); expect(fingerprint.fingerprintName).toEqual("TEST_FINGERPRINT"); }); + + it("should not validate when file does not exist.", function () { + var dir = 'config/initializers'; + fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); + expect(fingerprint.fingerprintName).toEqual("TEST_FINGERPRINT"); + }); }); describe("fingerprint name", function() { + const formatErrorMessage = 'Please supply a fingerprint name that is all caps separating words by underscores ending with "_FINGERPRINT" (i.e. CUSTOM_ASSET_FINGERPRINT).'; beforeEach(function() { spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, "readFileSync").and.returnValue("TEST_FINGERPRINT"); @@ -84,13 +91,13 @@ describe("AssetFingerprint", function() { it("should validate the file name characters.", function() { var dir = 'config/initializers'; fingerprint = function() { new AssetFingerprint(dir, true, "asset_FINGERPRINT"); } - expect(fingerprint).toThrowError('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + expect(fingerprint).toThrowError(formatErrorMessage); }); it("should validate the file name format.", function() { var dir = 'config/initializers'; fingerprint = function() { new AssetFingerprint(dir, true, "BAD_VALUE"); } - expect(fingerprint).toThrowError('Please supply a fingerprint name that is all caps separating words by underscores (i.e. CUSTOM_ASSET_FINGERPRINT).'); + expect(fingerprint).toThrowError(formatErrorMessage); }); }); @@ -107,19 +114,6 @@ describe("AssetFingerprint", function() { }); }); - describe("fingerprint name", function() { - beforeEach(function() { - spyOn(fs, "existsSync").and.returnValue(false); - spyOn(fs, "readFileSync").and.returnValue(""); - }); - - it("should not validate when file does not exist.", function () { - var dir = 'config/initializers'; - fingerprint = new AssetFingerprint(dir, true, "TEST_FINGERPRINT"); - expect(fingerprint.fingerprintName).toEqual("TEST_FINGERPRINT"); - }); - }); - describe("apply", function() { beforeEach(function() { spyOn(fs, "writeFileSync");