diff --git a/README.md b/README.md index f04b170..f8e2a5b 100644 --- a/README.md +++ b/README.md @@ -108,5 +108,26 @@ 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' +``` + +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 7b6666e..ff4c6c2 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,52 @@ const fs = require('fs'); -function AssetFingerprint(initializerDirectory, needsFingerprint = true) { +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'; function _validateInitializerDirectory() { if (initializerDirectory === undefined) { 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 ending with "_FINGERPRINT" (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 output = `ASSET_FINGERPRINT = '${stats.hash}'`; - let initializerPath = `${this.initializerDirectory}/asset_fingerprint.rb`; - - fs.writeFileSync(initializerPath, output); + let defaultRun = this.fingerprintName === 'ASSET_FINGERPRINT'; + const newline = defaultRun ? '' : '\r\n'; + const fsMethod = defaultRun ? 'writeFileSync' : 'appendFileSync'; + let output = `${newline}${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/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 0412bf7..49f736e 100644 --- a/spec/indexSpec.js +++ b/spec/indexSpec.js @@ -1,9 +1,16 @@ describe("AssetFingerprint", function() { var AssetFingerprint = require('../index'); + var fs = require('fs'); var fingerprint; 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(); @@ -26,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); @@ -37,5 +49,107 @@ describe("AssetFingerprint", function() { expect(fingerprint).toThrowError('Please supply a directory path for your initializer, such as `config/initializers`.'); }); }); + + 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); + expect(fingerprint.fingerprintName).toEqual("ASSET_FINGERPRINT"); + }); + + it("should use value when provided", function () { + var dir = 'config/initializers'; + 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"); + }); + + 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(formatErrorMessage); + }); + + it("should validate the file name format.", function() { + var dir = 'config/initializers'; + fingerprint = function() { new AssetFingerprint(dir, true, "BAD_VALUE"); } + expect(fingerprint).toThrowError(formatErrorMessage); + }); + }); + + 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("apply", function() { + beforeEach(function() { + spyOn(fs, "writeFileSync"); + spyOn(fs, "appendFileSync"); + spyOn(fs, "existsSync").and.returnValue(false); + spyOn(fs, "readFileSync").and.returnValue(""); + 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, "TEST_FINGERPRINT"); + fingerprint.apply({ + plugin: function(status, callback) { callback({hash: "Z1Y2X3W4"}) } + }); + + expect(fs.appendFileSync).toHaveBeenCalledWith( + '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 TEST_FINGERPRINT = Z1Y2X3W4"); + }); + }); }); });