Skip to content

Commit

Permalink
Make insertion happen as a broccoli plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanhammond committed Aug 29, 2016
1 parent 8f78eac commit 965f812
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 36 deletions.
60 changes: 60 additions & 0 deletions lib/asset-manifest-inserter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
var BroccoliCachingWriter = require('broccoli-caching-writer');
var path = require('path');
var fs = require('fs-extra');
var existsSync = require('exists-sync');
var metaReplacer = require('./meta-handler').replacer;

/**
* A Broccoli Plugin to modify index.html to include the asset manifest.
*
* @class AssetManifestInserter
* @extends BroccoliCachingWriter
*
* @private
* @param {Tree} tree
* @param {object} options
*/

AssetManifestInserter.prototype = Object.create(BroccoliCachingWriter.prototype);
AssetManifestInserter.prototype.constructor = AssetManifestInserter;
function AssetManifestInserter(inputNodes, options) {
options = options || {};
options.indexName = options.indexName || 'index.html';
options.cacheInclude = [options.indexName, 'tests/index.html', 'asset-manifest.json'];

BroccoliCachingWriter.call(this, inputNodes, {
annotation: options.annotation
});
this.options = options;
}

AssetManifestInserter.prototype.build = function() {
var sourceDir = this.inputPaths[0];
var manifestFilePath = path.join(sourceDir, 'asset-manifest.json');
var indexFilePath = path.join(sourceDir, this.options.indexName);
var testIndexFilePath = path.join(sourceDir, 'tests', 'index.html');

var manifest;
try {
manifest = fs.readJsonSync(manifestFilePath);
} catch (error) {
console.warn('\n\nWarning: Unable to read asset-manifest.json from build with error: ' + error)
console.warn('Warning: Proceeding without generated manifest; you will need to manually provide a manifest to the Asset Loader Service to load bundles at runtime. If this was intentional you can turn this message off via the `noManifest` flag.\n\n');
manifest = { bundles: {} };
}

if (existsSync(indexFilePath)) {
var indexFile = fs.readFileSync(indexFilePath, { encoding: 'utf8' });
var index = metaReplacer(indexFile, manifest);
fs.writeFileSync(path.join(this.outputPath, this.options.indexName), index);
}

if (existsSync(testIndexFilePath)) {
var testIndexFile = fs.readFileSync(testIndexFilePath, { encoding: 'utf8' });
var testIndex = metaReplacer(testIndexFile, manifest);
fs.mkdirSync(path.join(this.outputPath, 'tests'));
fs.writeFileSync(path.join(this.outputPath, 'tests', 'index.html'), testIndex);
}
};

module.exports = AssetManifestInserter;
17 changes: 17 additions & 0 deletions lib/insert-asset-manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var mergeTrees = require('broccoli-merge-trees');
var AssetManifestInserter = require('./asset-manifest-inserter');

/**
* Given a tree, this function will insert the asset manifest into index.html.
*
* The `options` object is passed directly into the broccoli plugin.
*
* @public
* @param {Tree} tree
* @param {object} options
* @return {Tree}
*/
module.exports = function insertAssetManifest(tree, options) {
var indicesWithManifests = new AssetManifestInserter([tree], options);
return mergeTrees([tree, indicesWithManifests], { overwrite: true });
};
47 changes: 12 additions & 35 deletions lib/manifest-generator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var path = require('path');
var fs = require('fs-extra');
var Addon = require('ember-cli/lib/models/addon');
var mergeTrees = require('broccoli-merge-trees');

/**
* A simple base class for other addons to extend when they want to generate an
Expand All @@ -27,47 +28,23 @@ var ManifestGenerator = Addon.extend({
* specified manifestOptions.
*/
postprocessTree: function(type, tree) {
if (type === 'all') {
var generateAssetManifest = require('./generate-asset-manifest'); // eslint-disable-line global-require
return generateAssetManifest(tree, this.manifestOptions);
}

return tree;
},
if (type !== 'all') { return tree; }

/**
* Replace the manifest placeholder with an escaped version of the manifest.
* We do this in both the app's index and test's index.
*/
postBuild: function(result) {
var options = this.app && this.app.options && this.app.options.assetLoader;
if (options && options.noManifest) {
return;
}
var options = this.app && this.app.options && this.app.options;
var assetLoaderOptions = options.assetLoader;

var manifestFile = path.join(result.directory, 'asset-manifest.json');
var manifest;
var generateAssetManifest = require('./generate-asset-manifest'); // eslint-disable-line global-require
var treeWithManifest = generateAssetManifest(tree, this.manifestOptions);

try {
manifest = fs.readJsonSync(manifestFile);
} catch (error) {
console.warn('\n\nWarning: Unable to read asset-manifest.json from build with error: ' + error)
console.warn('Warning: Proceeding without generated manifest; you will need to manually provide a manifest to the Asset Loader Service to load bundles at runtime. If this was intentional you can turn this message off via the `noManifest` flag.\n\n');
manifest = { bundles: {} };
if (assetLoaderOptions && assetLoaderOptions.noManifest) {
return treeWithManifest;
}

var escapedManifest = escape(JSON.stringify(manifest));

var indexFilePath = path.join(result.directory, 'index.html');
this.replaceAssetManifestPlaceholder(indexFilePath, escapedManifest);

var testsIndexFilePath = path.join(result.directory, 'tests/index.html')
this.replaceAssetManifestPlaceholder(testsIndexFilePath, escapedManifest);
},
var indexName = options.outputPaths.app.html;
var insertAssetManifest = require('./insert-asset-manifest'); // eslint-disable-line global-require
var treeWithInsertedManifest = insertAssetManifest(treeWithManifest, { indexName: indexName });

replaceAssetManifestPlaceholder: function(filePath, manifest) {
var resolvedFile = fs.readFileSync(filePath, { encoding: 'utf8' });
fs.outputFileSync(filePath, resolvedFile.replace(/%GENERATED_ASSET_MANIFEST%/, manifest));
return treeWithInsertedManifest;
}
});

Expand Down
22 changes: 22 additions & 0 deletions lib/meta-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Replace the manifest meta tag with an updated version of the manifest.
* We do this in both the app's and test's index.html file.
*/
var regex = /<meta name="([^"]*\/asset-manifest)" content=[^>]*>/;

function escaper(sourceJSON) {
return escape(JSON.stringify(sourceJSON));
}

function replacer(fileContents, manifest) {
if (!regex.test(fileContents)) {
return fileContents;
}

var metaName = regex.exec(fileContents)[1];
var metaString = '<meta name="' + metaName + '" content="' + escaper(manifest) + '" />';

return fileContents.replace(regex, metaString);
}

module.exports = { escaper, replacer };
61 changes: 61 additions & 0 deletions node-tests/asset-manifest-inserter-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var path = require('path');
var assert = require('assert');
var walk = require('walk-sync');
var fs = require('fs-extra');
var broccoli = require('broccoli');

var AssetManifestInserter = require('../lib/asset-manifest-inserter');
var metaHandler = require('../lib/meta-handler');

var fixturePath = path.join(__dirname, 'fixtures');
var inputTrees = [ path.join(fixturePath, 'insertion-test') ];

function build(assertion, options) {
return function() {
options = options || {};

var inserter = new AssetManifestInserter(inputTrees, options);
var builder = new broccoli.Builder(inserter);

return builder.build()
.then(assertion)
.then(function() {
builder.cleanup();
});
}
}

describe('asset-manifest-inserter', function() {
describe('build', function() {

it('only modifies index.html', build(function(results) {
var output = results.directory;
assert.deepEqual(walk(output), [ 'index.html', 'tests/', 'tests/index.html' ]);
})
);

it('uses the correct file', build(function(results) {
var output = results.directory;
assert.deepEqual(walk(output), [ 'extra.html', 'tests/', 'tests/index.html' ]);
}, { indexName: 'extra.html' })
);

it('successfully modifies the manifest', build(function(results) {
var output = results.directory;
var indexFilePath = path.join(output, 'index.html');
var testIndexFilePath = path.join(output, 'tests', 'index.html');
var manifestFilePath = path.join(inputTrees[0], 'asset-manifest.json')

var index = fs.readFileSync(indexFilePath, { encoding: 'utf8' });
var testIndex = fs.readFileSync(testIndexFilePath, { encoding: 'utf8' });
var assetManifest = fs.readJsonSync(manifestFilePath);

var needle = metaHandler.escaper(assetManifest);

assert.notEqual(index.indexOf(needle), -1);
assert.notEqual(testIndex.indexOf(needle), -1);
})
);

});
});
3 changes: 3 additions & 0 deletions node-tests/fixtures/insertion-test/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key": "value"
}
26 changes: 26 additions & 0 deletions node-tests/fixtures/insertion-test/extra.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Dummy</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">


<meta name="dummy/config/environment" content="%7B%22modulePrefix%22%3A%22dummy%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22ember-asset-loader%22%2C%22version%22%3A%22v0.1.1%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />

<link rel="stylesheet" href="/assets/vendor.css">
<link rel="stylesheet" href="/assets/dummy.css">

<meta name="dummy/asset-manifest" content="" />
</head>
<body>


<script src="/assets/vendor.js"></script>
<script src="/assets/dummy.js"></script>


</body>
</html>
26 changes: 26 additions & 0 deletions node-tests/fixtures/insertion-test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Dummy</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">


<meta name="dummy/config/environment" content="%7B%22modulePrefix%22%3A%22dummy%22%2C%22environment%22%3A%22development%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22ember-asset-loader%22%2C%22version%22%3A%22v0.1.1%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />

<link rel="stylesheet" href="/assets/vendor.css">
<link rel="stylesheet" href="/assets/dummy.css">

<meta name="dummy/asset-manifest" content="" />
</head>
<body>


<script src="/assets/vendor.js"></script>
<script src="/assets/dummy.js"></script>


</body>
</html>
40 changes: 40 additions & 0 deletions node-tests/fixtures/insertion-test/tests/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Dummy Tests</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">


<meta name="dummy/config/environment" content="%7B%22modulePrefix%22%3A%22dummy%22%2C%22environment%22%3A%22test%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22none%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%22LOG_ACTIVE_GENERATION%22%3Afalse%2C%22LOG_VIEW_LOOKUPS%22%3Afalse%2C%22rootElement%22%3A%22%23ember-testing%22%2C%22name%22%3A%22ember-asset-loader%22%2C%22version%22%3A%22v0.1.1%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />


<link rel="stylesheet" href="/assets/vendor.css">
<link rel="stylesheet" href="/assets/dummy.css">
<link rel="stylesheet" href="/assets/test-support.css">

<meta name="dummy/asset-manifest" content="" />

</head>
<body>

<div id="qunit"></div>
<div id="qunit-fixture"></div>

<div id="ember-testing-container">
<div id="ember-testing"></div>
</div>


<script src="/testem.js" integrity=""></script>
<script src="/assets/vendor.js"></script>
<script src="/assets/test-support.js"></script>
<script src="/assets/dummy.js"></script>
<script src="/assets/tests.js"></script>


<script>Ember.assert('The tests file was not loaded. Make sure your tests index.html includes "assets/tests.js".', EmberENV.TESTS_FILE_LOADED);</script>
</body>
</html>
61 changes: 61 additions & 0 deletions node-tests/meta-handler-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var assert = require('assert');
var metaHandler = require('../lib/meta-handler');

function testStringGenerator(sourceJSON) {
return '<meta name="testing/asset-manifest" content="'+ metaHandler.escaper(sourceJSON) +'" />';
}

describe('meta-handler', function() {
describe('replacer', function() {
it('does not throw on empty input', function() {
var result = metaHandler.replacer('', '');
assert.equal(result, '');
});

it('supports ">" in bundle', function() {
var replacement = 'result';

var before = testStringGenerator({ foo: '>' });
var after = testStringGenerator(replacement);

var result = metaHandler.replacer(before, replacement);
assert.equal(result, after);
});

it('only replaces the first occurrence', function() {
var replacement = 'result';

var before = [
testStringGenerator({ one: 1 }),
testStringGenerator({ two: 2 })
];

var after = [
testStringGenerator(replacement),
before[1]
];

var result = metaHandler.replacer(before.join(''), replacement);
assert.equal(result, after.join(''));
});

it('supports newlines', function() {
var replacement = 'result';

var before = [
"First line.",
testStringGenerator({ one: 1 }),
testStringGenerator({ two: 2 })
];

var after = [
before[0],
testStringGenerator(replacement),
before[2]
];

var result = metaHandler.replacer(before.join('\r\n'), replacement);
assert.equal(result, after.join('\r\n'));
});
});
});
Loading

0 comments on commit 965f812

Please sign in to comment.