Skip to content

Commit

Permalink
Imported code from ember-cli-d3-shape to load D3 v4
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanvanderbyl committed Jul 22, 2016
1 parent 1d8a85a commit bed0e94
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 15 deletions.
118 changes: 103 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,118 @@
/* jshint node: true */
/* jshint esnext: false */
'use strict';

var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');
var path = require('path');
var rename = require('broccoli-stew').rename;
var AMDDefineFilter = require('./lib/amd-define-filter');
var fs = require('fs');

function lookupPackage(packageName) {
var modulePath = require.resolve(packageName);
var i = modulePath.lastIndexOf(path.sep + 'build');
return modulePath.slice(0, i);
}

module.exports = {
name: 'ember-d3',

included: function(app) {
this._super.included(app);
if (process.env.EMBER_CLI_FASTBOOT !== 'true') {
isDevelopingAddon: function () {
return false;
},

d3Modules: [

// Imported from package.json
],

/**
* `import()` taken from ember-cli 2.7 to allow nested addon usage.
*/
import: function (asset, options) {
var app = this.app;
while (app.app) {
app = app.app;
}

app.import(asset, options);
},

// Fix for loading it in addons/engines
if (typeof app.import !== 'function' && app.app) {
app = app.app;
included: function (app) {
this._super.included && this._super.included.apply(this, arguments);
this.app = app;

while (app.app) {
app = app.app;
}

var pkg = require(path.join(lookupPackage('d3'), 'package.json'));

// Find all dependencies of `d3`
this.d3Modules = Object.keys(pkg.dependencies).filter(function (name) {
return /^d3\-/.test(name);
});

// This essentially means we'll skip importing this package twice, if it's
// a dependency of another package.
if (!app.import) {
if (this.isDevelopingAddon()) {
console.log('[ember-cli-d3-shape] skipping included hook for', app.name);
}

app.import(app.bowerDirectory + '/d3/d3.js');
app.import('vendor/ember-d3/ember-d3-shim.js', {
exports: {
d3: ['default']
}
});
return;
}

var _this = this;
this.d3Modules.forEach(function (packageName) {
_this.import(path.join('vendor', packageName, packageName + '.js'));
});
},

treeForVendor: function() {
return path.join(__dirname, 'client');
}
treeForApp() {
return null;
},

treeForAddon() {
return null;
},

treeForVendor: function () {
var trees = [];
var d3PackagePath = lookupPackage('d3');

this.d3Modules.forEach(function (packageName) {
// Import existing builds from node d3 packages, which are UMD packaged.
var packageBuildPath = path.join('build', packageName + '.js');

var d3PathToSrc = path.join(d3PackagePath, 'node_modules', packageName);

try {
fs.statSync(path.join(d3PathToSrc)).isDirectory();
} catch (err) {
d3PathToSrc = lookupPackage(packageName);
}

try {
fs.statSync(path.join(d3PathToSrc, packageBuildPath)).isFile();
} catch (err) {
console.error('[ERROR] D3 Package (' + packageName + ') is not built as expected, cannot continue. Please report this as a bug.');
return;
}

var tree = new Funnel(d3PathToSrc, {
include: [packageBuildPath],
destDir: '/' + packageName,
annotation: 'Funnel: D3 Source [' + packageName + ']',
});

var srcTree = new AMDDefineFilter(tree, packageName);
trees.push(rename(srcTree, function () {
return '/' + packageName + '/' + packageName + '.js';
}));
});

return mergeTrees(trees);
},
};
30 changes: 30 additions & 0 deletions lib/amd-define-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* jshint node: true */
'use strict';

var Filter = require('broccoli-filter');
var rewriteAMDFunction = require('./rewrite-amd-definition');

/**
* Loading an actual AMD package using Ember's loader.js
* requires some hacks. Basically proper AMD packages check for define.amd, which
* loader.js doesn't define (because reasons).
*
* To get around this we define our own definition in the same way each Ember
* package does.
*/

function AMDDefineFilter(inputNode, packageName, options) {
options = options || {};
Filter.call(this, inputNode, {
annotation: options.annotation || 'Rewriting package: ' + packageName,
});
this.packageName = packageName;
}

AMDDefineFilter.prototype = Object.create(Filter.prototype);
AMDDefineFilter.prototype.constructor = AMDDefineFilter;
AMDDefineFilter.prototype.processString = function (code) {
return rewriteAMDFunction(code, this.packageName);
};

module.exports = AMDDefineFilter;
88 changes: 88 additions & 0 deletions lib/rewrite-amd-definition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* jshint node: true */
'use strict';

/*
# Rewrite UMD -> AMD definition
This library take a module definition in the UMD format and returns an Ember
compatible AMD module containing the same module body.
# Why do we need this?
The UMD module definitions that D3 uses do a few checks to decide which loader
it needs to support. When detecting AMD, it does this:
`typeof define === 'function' && define.amd`
Unfortunately Ember's define doesn't report being fully AMD compatible, as such
`define.amd // undefined`.
*/

var recast = require('recast');
var types = recast.types;
var namedTypes = types.namedTypes;
var b = recast.types.builders;

function buildExportDefaultDefinition(packageName, deps, node) {
return b.expressionStatement(
b.callExpression(
b.identifier('define'), [
b.literal(packageName),
b.arrayExpression(deps.map(function (name) { return b.literal(name); })),

node,
]
)
);
}

function getDependenciesForDefine(node) {
if (namedTypes.CallExpression.check(node)) {
return node.arguments[0].elements.map(function (e) { return e.value; });
}else {
return [];
}
}

function isAMDFunctionBody(node) {
return namedTypes.FunctionExpression.check(node) &&
node.id === null &&
!!node.params &&
namedTypes.Identifier.check(node.params[0]) &&
node.params[0].name === 'exports';
}

function isDefineCallExpression(node) {
return namedTypes.CallExpression.check(node) &&
node.callee.name === 'define' &&
!!node.arguments &&
namedTypes.ArrayExpression.check(node.arguments[0]) &&
node.arguments[0].elements[0].value === 'exports' &&
node.arguments[1].name === 'factory';
}

module.exports = function rewriteAMDFunction(code, packageName) {
var ast = recast.parse(code);
var amdDependencies;
var amdFunctionBody;

types.visit(ast, {
visitCallExpression: function (path) {
if (isDefineCallExpression(path.node)) {
amdDependencies = getDependenciesForDefine(path.node);
}

this.traverse(path);
},

visitFunctionExpression: function (path) {
if (isAMDFunctionBody(path.node)) {
amdFunctionBody = path.node;
}

this.traverse(path);
},
});

var newAST = buildExportDefaultDefinition(packageName, amdDependencies, amdFunctionBody);
return recast.print(newAST).code;
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
"d3"
],
"dependencies": {
"broccoli-stew": "^1.3.2",
"d3": "^4.1.1",
"ember-cli-babel": "^5.1.6"
},
"ember-addon": {
Expand Down
Empty file added tests/integration/.gitkeep
Empty file.
Loading

0 comments on commit bed0e94

Please sign in to comment.