Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
39 changes: 33 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -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.');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is being thrown for me on secondary runs. I'm testing it by running two independent bundles. The first plugin is setup as a default: new AssetFingerprintPlugin('config/initializers', true) which writes ASSET_FINGERPRINT. The second build sets the plugin up with the optional argument: new AssetFingerprintPlugin('config/initializers', true, 'TEST_FINGERPRINT'). On the first run, it creates the file, adds the default and then the override as expected. But if I run that delegation script again it throws this error. Is there a more deterministic way to test/implement this that you're using?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our discussion, this was a local testing implementation issue. 👍

}
}

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));
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
114 changes: 114 additions & 0 deletions spec/indexSpec.js
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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);
Expand All @@ -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");
});
});
});
});