Skip to content

Commit

Permalink
feat(imports): add doT template engine (#1024)
Browse files Browse the repository at this point in the history
This patch adds the [`doT`](https://npmjs.org/dot) template engine to `axe.imports`. This is required for a runtime localization (a WIP feature).

A lot of hackery was required to get this working, since `doT` doesn't expose a UMD wrapper that our build system supports. Rather than attempting to get our existing tooling to "unwrap" `doT`'s custom UMD, I've decided to save a lot of time/effort and just wrap it in an IIFE. There are likely 1,000,000s of variations of UMD in the wild and it's unreasonable for us to attempt to support them all. The added IIFE method works in cases where the code functions exactly the same regardless of environment (this does not include libraries like Axios). IMHO the only reasonable solution to this problem is to use a proper bundler for building `axe-core`.
  • Loading branch information
stephenmathieson authored Jul 31, 2018
1 parent ce6b5b4 commit f6f08d4
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
7 changes: 6 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,12 @@ module.exports = function(grunt) {
'generate-imports': {
// list of external dependencies, which needs to be added to axe.imports object
data: {
axios: './node_modules/axios/dist/axios.js'
axios: './node_modules/axios/dist/axios.js',
doT: {
file: './node_modules/dot/doT.js',
umd: false,
global: 'doT'
}
}
},
'aria-supported': {
Expand Down
74 changes: 67 additions & 7 deletions build/tasks/generate-imports.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*eslint-env node */
const UglifyJS = require('uglify-js');
const assert = require('assert');

module.exports = grunt => {
grunt.registerMultiTask(
Expand Down Expand Up @@ -57,28 +58,87 @@ module.exports = grunt => {
/**
* Process a given library to unwrapped UMD module if exists, and return the factory
* @param {string} libName name of the library
* @param {string} sourceUrl path to the distributable of the library
* @param {string} sourceCode source code for the library
* @param {Object} [options] Optional options
* @property {Boolean} umd Does the library contain a UMD wrapper
* @property {String} global The library's global (`window.myLibrary`)
*/
const processImport = (libName, sourceUrl) => {
const sourceCode = grunt.file.read(sourceUrl);
if (hasUmdWrapper(sourceCode)) {
const processImport = (libName, sourceCode, options) => {
const hasUMD = options ? options.umd : hasUmdWrapper(sourceCode);
const global = options && options.global;

if (hasUMD) {
// If the library has a "standard" UMD wrapper, we'll remove it
// and expose the library directly.
unwrappedCode(sourceCode, (err, factory) => {
if (err) {
// running uglifyjs transform in a try block, this is to catch any errors from the transform.
throw new Error(err);
}
writeLibrary(libName, factory);
});
} else if (global) {
// The global variable exposed by the library. This is not necessarily the same as "libName".
const libraryGlobal = global;

// We wrap the library's source code in an IFFE which voids
// existing globals (module, define, process, etc.) forces and
// forces it to export a global.
//
// This method should only be used for "universal" code that
// follows the same code paths for all environments (Node,
// browser, etc). If there are different paths for different
// envs, the UMD method should be used instead.
const wrappedLibrary = `
(function (module, exports, define, require, process) {
// Get a reference to the "true" global scope. This works in
// ES5's "strict mode", browsers, node.js and other environments.
var global = Function('return this')();
// If there was a global prior to our script, make sure we
// "save" it (think "$.noConflict()").
var __old_global__ = global["${libraryGlobal}"];
${sourceCode}
// Preserve a reference to the library and remove it from
// the global scope.
var lib = global["${libraryGlobal}"];
delete global["${libraryGlobal}"];
// Reset a previous global when applicable.
if (__old_global__) {
global["${libraryGlobal}"] = __old_global__;
}
// Return the library to populate "axe.imports".
return lib;
})();
`;
writeLibrary(libName, wrappedLibrary);
} else {
// assumption is that the library returns an IIFE
writeLibrary(libName, sourceCode);
}
};

// Iterate through each library to import and process the code
Object.keys(this.data).forEach(key => {
processImport(key, this.data[key]);
});
for (const name in this.data) {
const val = this.data[name];
if (typeof val === 'string') {
// Provided a path to a file with no options
const sourceCode = grunt.file.read(val);
processImport(name, sourceCode);
} else if (typeof val === 'object') {
// Provided an object with options
const { file, umd, global } = val;
assert(file, 'File required');
const sourceCode = grunt.file.read(file);
processImport(name, sourceCode, { umd, global });
} else {
grunt.fail.warn(`Unsupported generate-import: "${name}"`);
}
}
}
);
};
6 changes: 6 additions & 0 deletions test/integration/full/umd/umd-module-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ describe('UMD module.export', function() {
it('should ensure axe source includes axios', function() {
assert.isTrue(axe.source.includes(axe.imports.axios.toString()));
});

it('should include doT', function() {
var doT = axe.imports.doT;
assert(doT, 'doT is registered on axe.imports');
assert.equal(doT.name, 'doT');
});
});

0 comments on commit f6f08d4

Please sign in to comment.