Skip to content

Commit

Permalink
Merge pull request #3 from ruzz311/master
Browse files Browse the repository at this point in the history
Performance improvements using symlink instead of npm link
  • Loading branch information
doug-martin committed Jul 18, 2014
2 parents 9b971f6 + cb0d06e commit 19720a1
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 51 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ In lieu of a formal styleguide, take care to maintain the existing coding style.

## Release History

2014-07-16 v0.2.0
- Using symlinks instead of `npm link` to improve performance.
- Improved logging.

2012-12-30 v0.0.1 Initial release.

## License
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
{
"name": "grunt-link",
"description": "Grunt task to handle the linking of local dependencies",
"version": "0.1.2",
"version": "0.2.0",
"homepage": "http://doug-martin.github.com/grunt-link",
"author": {
"name": "Doug Martin",
"email": "[email protected]",
"url": "http://blog.dougamartin.com"
},
"contributors": [
{
"name": "Nick Nisi",
"url": "http://nicknisi.com"
},{
"name": "Russell Madsen",
"email": "[email protected]",
"url": "http://madsendev.com"
}
],
"repository": {
"type": "git",
"url": "doug-martin/grunt-link.git"
Expand Down
158 changes: 123 additions & 35 deletions tasks/lib/linker.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
"use strict";


module.exports = function (grunt, options) {

module.exports = function (grunt, npm, options) {

var util = require("util"),
path = require("path"),
fs = require("fs"),
comb = require("comb"),
helper = require("./link_helper")(options.dir || process.cwd()),
getPackageName = helper.getPackageName,
shouldLink = helper.shouldLink,
normalizeName = helper.normalizeName,
isBoolean = comb.isBoolean,
execP = comb.wrap(require("child_process").exec),
execP = require("child_process").spawn,
rimraf = comb.wrap(require("rimraf")),
log = grunt.log,
verbose = grunt.verbose,
sortedDeps = helper.findSortAndNormalizeDeps(),
cyclic = sortedDeps.cyclic,
errors = [],
Expand All @@ -27,53 +25,143 @@ module.exports = function (grunt, options) {
}
return comb.async.array(cmds).forEach(function (cmd) {
if (cmd) {
verbose.ok(util.format("Executing %s", cmd));
return execP(cmd);
log.debug(util.format("Executing %s %s", cmd.cmd, cmd.args.join(" ")));
var child = execP(cmd.cmd, cmd.args, { stdio: "inherit" }),
ret = new comb.Promise();

child.on("close", function (code) {
if (code !== 0) {
ret.errback(new Error("ps process exited with code " + code));
} else {
ret.callback();
}
});
child.on("error", ret.errback);
return ret;
}
}, 1);
}

function createModulesDir (dir, deps) {
var statP = new comb.Promise();
if (deps.length < 1) {
log.debug("No linked dependencies needed");
statP.callback();
} else {
fs.stat(dir, function (err, stats) {
if (!comb.isNull(err)) {
fs.mkdirSync(dir);
log.debug("Created " + dir);
}
statP.callback();
});
}
return statP;
}

if (cyclic.length && !options.ignoreCyclic) {
errors.push(["Cyclic dependency detected please check your dependency graph."]);
cyclic.forEach(function (cyc) {
errors.push(util.format("%s => %s", cyc.pkg, cyc.deps.join(" ")));
});
return new comb.Promise().errback(new Error(errors.join("\n")));
function chdir(location) {
var newLoc = path.resolve(cwd, normalizeName(location))
log.debug("Changing directory to " + newLoc);
process.chdir(newLoc);
}

log.writeln("Linking packages");
return comb.async.array(sortedDeps.links).forEach(function (pkg) {
function cleanUpOldLinks(pkg) {
//clean up old links
try {
var cmds = [],
location = pkg[0],
var location = pkg[0],
deps = pkg[1],
install = isBoolean(pkg[2]) ? pkg[2] : true,
loc = path.resolve(cwd, normalizeName(location));
log.ok("Linking " + getPackageName(loc));
process.chdir(loc);
loc = path.resolve(cwd, normalizeName(location)),
removeDirs = [],
cleanPromise;
if (deps.length) {
cmds.push("npm link " + deps.join(" "));
}
if (options.install && install) {
cmds.push("npm install");
}
if (shouldLink(location)) {
cmds.push("npm link");
if (deps.length) {
deps.forEach(function (dir) {
removeDirs.push(path.resolve(loc, "./node_modules", dir));
});
}
}
if (install) {
return options.clean ? rimraf(path.resolve(loc, "./node_modules")).chain(function () {
return exec(cmds);
}) : exec(cmds);
if (options.clean && install) {
log.debug(util.format("Executing rimraf %s", loc+"/node_modules"));
cleanPromise = rimraf(path.resolve(loc, "./node_modules"));
} else {
return exec(cmds);
cleanPromise = comb.async.array(removeDirs).forEach(function (dir) {
log.debug(util.format("Executing rimraf %s", dir));
return rimraf(dir);
});
}
return cleanPromise.chain(function () {
if (shouldLink(pkg[0])) {
return exec([
{cmd: "npm", args: ["unlink", getPackageName(pkg[0]), "--global"]}
]);
}
});
} catch (e) {
console.log(e.stack);
throw e;
}
}, 1);

};
}

// detect cyclic references
if (cyclic.length && !options.ignoreCyclic) {
errors.push(["Cyclic dependency detected please check your dependency graph."]);
cyclic.forEach(function (cyc) {
errors.push(util.format("%s => %s", cyc.pkg, cyc.deps.join(" ")));
});
return new comb.Promise().errback(new Error(errors.join("\n")));
}

log.subhead("Cleaning modules");
return comb.async.array(sortedDeps.links)
.forEach(function (pkg) {
// clean links
return cleanUpOldLinks(pkg);
}, 1)
.forEach(function (pkg, i) {
// create links
if (i === 0) {
log.subhead("Linking modules");
}
if (isBoolean(pkg[2]) ? pkg[2] : true) {
return exec({ cmd: "ln", args: ["-s", getPackageName(pkg[0]), npm.globalDir]});
}
}, 1)
.forEach(function (pkg, i) {
//install pkg
if (i === 0) {
log.subhead("Installing linked modules");
}
if (isBoolean(pkg[2]) ? pkg[2] : true && options.install) {
log.debug(util.format("==== %s ====", getPackageName(pkg[0])));
chdir(pkg[0]);
return exec({cmd: "npm", args: ["install"]});
}
}, 1)
.forEach(function (pkg, i) {
//install link deps
var linkP, modulesDir = path.join(pkg[0], "node_modules");
if (i === 0) {
log.subhead("Installing linked dependencies");
}
log.debug(util.format("==== %s ====", getPackageName(pkg[0])));
if (shouldLink(pkg[0])) {
linkP = createModulesDir(modulesDir, pkg[1]);
} else {
//return comb.resolved();
linkP = comb.resolved();
}
return linkP.chain(function () {
//link deps
var cmds = [], deps = pkg[1];
if (deps.length) {
deps.forEach(function (dep) {
var src = path.resolve(npm.globalDir, dep),
dest = path.join(modulesDir, dep);
cmds.push({cmd: "ln", args: ["-s", src, dest]});
});
}
return exec(cmds);
});
},1);
};
25 changes: 14 additions & 11 deletions tasks/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

module.exports = function (grunt) {
var linker = require("./lib/linker.js"),
path = require("path");
path = require("path"),
npm = require("npm");

// Please see the grunt documentation for more information regarding task and
// helper creation: https://github.com/gruntjs/grunt/blob/master/docs/toc.md
Expand All @@ -19,7 +20,7 @@ module.exports = function (grunt) {
// TASKS
// ==========================================================================

grunt.registerTask('link', 'Your task description goes here.', function () {
grunt.registerTask('link', 'Symlink local node_module dependencies.', function () {
var options = grunt.util._.extend({ignoreCyclic: false, dir: process.cwd(), install: true, clean: true}, grunt.config("link")),
done,
cwd;
Expand All @@ -28,15 +29,17 @@ module.exports = function (grunt) {
}
done = this.async();
cwd = process.cwd();
linker(grunt, options).classic(function (err) {
process.chdir(cwd);
if (err) {
grunt.log.error("Error linking pacakages");
grunt.log.error(err.stack);
done(false);
} else {
done();
}
npm.load(function () {
linker(grunt, npm, options).classic(function (err) {
process.chdir(cwd);
if (err) {
grunt.log.error("Error linking packages");
grunt.log.error(err.stack);
done(false);
} else {
done();
}
});
});
});
};
5 changes: 3 additions & 2 deletions test/link_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

var grunt = require('grunt'),
var grunt = require("grunt"),
fs = require("fs"),
path = require("path");

Expand All @@ -10,12 +10,13 @@ function isLink(loc) {

exports.link = {
'link': function (test) {
test.expect(4);
test.expect(5);

// tests here
test.ok(isLink("test/test-project/moduleB/node_modules/module-a"));
test.ok(isLink("test/test-project/moduleC/node_modules/module-a"));
test.ok(isLink("test/test-project/moduleC/node_modules/module-b"));
test.ok(isLink("test/test-project/moduleC/node_modules/module-d"));
test.equal(grunt.file.expand("test/test-project/moduleA/node_modules").length, 0);
test.done();
}
Expand Down
4 changes: 2 additions & 2 deletions test/test-project/moduleC/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "module-b",
"name": "module-c",
"version": "0.0.1",
"linkDependencies": ["module-a", "module-b"]
"linkDependencies": ["module-a", "module-b", "!module-d"]
}
5 changes: 5 additions & 0 deletions test/test-project/moduleD/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "module-d",
"version": "0.0.1",
"linkDependencies": ["module-b", "module-c"]
}

0 comments on commit 19720a1

Please sign in to comment.