Repository: stealjs/steal (js)

Issue:
Add npmAlgorithm: nested
Since flat will become the default in 1.0.

Pull request:
Default npm algorithm = "flat"
for steal 1.0 npm algorithm will be flat which means that npm 3 or higher is recommended.
close #700 #701

Patch:
diff --git a/.gitignore b/.gitignore
index 88d3bbfe..3b47eb83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ bower_components/
 test/ext/
 test/bower/as_config/vendor/system-bower/
 test/npm-deep/node_modules/steal/
+!test/default-npm-algorithm/node_modules/
+!test/nested-npm-algorithm/node_modules/
+!test/nested-npm-algorithm/node_modules/some/node_modules/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 136fd46a..8837f887 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,8 @@
 language: node_js
-node_js: 0.10
+node_js:
+    - 0.12
+    - 4
+    - 5.5
 before_script: node node_modules/bower/bin/bower install
 script: npm test
 before_install:
diff --git a/docs/module-npm.md b/docs/module-npm.md
index 0cb62c39..752632a8 100644
--- a/docs/module-npm.md
+++ b/docs/module-npm.md
@@ -212,12 +212,16 @@ Even though `npmIgnore` is set to ignore all `devDependencies` the use of `npmDe
 
 ### package.system.npmAlgorithm
 
-Used to determine which algorithm is used to look up packages. [NPM 3](http://blog.npmjs.org/post/122450408965/npm-weekly-20-npm-3-is-here-ish) introduced a new flat file structure inside node_modules. If you are using NPM 3 set this option:
+Used to determine which algorithm is used to look up packages.
+
+The default algorithm is `flat`. **We assume that you are using NPM 3 or higher.** See [here](https://github.com/npm/npm/releases/tag/v3.0.0) more about the flat file structure of NPM 3.
+
+If you are using NPM 2 your dependencies of `node_modules` will be nested. StealJS can handle the lookup by setting `npmAlgorithm` to `nested`.
 
 ```js
 {
   "system": {
-    "npmAlgorithm": "flat"
+    "npmAlgorithm": "nested"
   }
 }
 ```
diff --git a/ext/dev.js b/ext/dev.js
index a4f5d045..8306fb03 100644
--- a/ext/dev.js
+++ b/ext/dev.js
@@ -58,7 +58,7 @@
 		warn: function( out ) {
 			var ll = steal.config('logLevel');
 			if(ll < 2){
-				Array.prototype.unshift.call(arguments, 'steal.js WARN:');
+				Array.prototype.unshift.call(arguments, 'steal.js WARNING:');
 				if ( window.console && console.warn ) {
 					this._logger( "warn", Array.prototype.slice.call(arguments) );
 				} else if ( window.console && console.log ) {
diff --git a/ext/npm-convert.js b/ext/npm-convert.js
index b31917c0..5191c360 100644
--- a/ext/npm-convert.js
+++ b/ext/npm-convert.js
@@ -166,7 +166,7 @@ function convertName (context, pkg, map, root, name, waiting) {
 				} else {
 					var requestedProject = crawl.getDependencyMap(context.loader, pkg, root)[parsed.packageName];
 					if(!requestedProject) {
-						if(root) warn(name);
+						if(root) utils.warnOnce("Could not find " + name + " in node_modules. Ignoring!");
 						return name;
 					}
 					requestedVersion = requestedProject.version;
@@ -354,17 +354,4 @@ function convertForPackage(context, pkg) {
 			delete conv[name];
 		}
 	}
-}
-
-var warn = (function(){
-	var warned = {};
-	return function(name){
-		if(!warned[name]) {
-			warned[name] = true;
-			var warning = "WARN: Could not find " + name + " in node_modules. Ignoring.";
-			if(typeof steal !== "undefined" && steal.dev && steal.dev.warn) steal.dev.warn(warning)
-			else if(console.warn) console.warn(warning);
-			else console.log(warning);
-		}
-	};
-})();
+}
\ No newline at end of file
diff --git a/ext/npm-utils.js b/ext/npm-utils.js
index e7d7d394..668374e7 100644
--- a/ext/npm-utils.js
+++ b/ext/npm-utils.js
@@ -67,10 +67,18 @@ var utils = {
 		var w = this._warnings = this._warnings || {};
 		if(w[msg]) return;
 		w[msg] = true;
-		if(typeof steal !== "undefined" && typeof console !== "undefined" &&
-		  console.warn) {
+		this.warn(msg);
+	},
+	warn: function(msg){
+		if(typeof steal !== "undefined" && typeof console !== "undefined" && console.warn) {
 			steal.done().then(function(){
-				console.warn(msg);
+				if(steal.dev && steal.dev.warn){
+					steal.dev.warn(msg)
+				} else if(console.warn) {
+					console.warn("steal.js WARNING: "+msg);
+				} else {
+					console.log(msg);
+				}
 			});
 		}
 	},
diff --git a/ext/npm.js b/ext/npm.js
index e2b377b0..f65b0d0e 100644
--- a/ext/npm.js
+++ b/ext/npm.js
@@ -41,13 +41,20 @@ exports.translate = function(load){
 		npmLoad: npmLoad,
 		crawl: crawl,
 		resavePackageInfo: resavePackageInfo,
-		forwardSlashMap: {}
+		forwardSlashMap: {},
+		// default file structure for npm 3 and higher
+		isFlatFileStructure: true
 	};
 	this.npmContext = context;
 	var pkg = {origFileUrl: load.address, fileUrl: utils.relativeURI(loader.baseURL, load.address)};
 	crawl.processPkgSource(context, pkg, load.source);
-	if(pkg.system && pkg.system.npmAlgorithm === "flat") {
-		context.isFlatFileStructure = true;
+
+	// backwards compatible for < npm 3
+	if(pkg.system && pkg.system.npmAlgorithm === "nested") {
+		context.isFlatFileStructure = false;
+	} else {
+		pkg.system = pkg.system || {};
+		pkg.system.npmAlgorithm = "flat";
 	}
 
 	return crawl.deps(context, pkg, true).then(function(){
diff --git a/package.json b/package.json
index f7315ae4..09bdf204 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
   "devDependencies": {
     "steal-css": "0.4.1",
     "steal-less": "0.2.2",
-    "system-npm": "0.6.16",
+    "system-npm": "^1.0.0-pre.0",
     "system-live-reload": "1.4.0",
     "system-trace": "0.2.5",
     "steal-env": "^1.0.0",


Test patch:
diff --git a/test/default-npm-algorithm/default.html b/test/default-npm-algorithm/default.html
new file mode 100644
index 00000000..3504efa4
--- /dev/null
+++ b/test/default-npm-algorithm/default.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<body>
+
+<script>
+    window.QUnit = window.parent.QUnit;
+    window.removeMyself = window.parent.removeMyself;
+</script>
+<script type="text/javascript" src='../../steal.js' main="default" config="package.json!npm"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/default-npm-algorithm/default.js b/test/default-npm-algorithm/default.js
new file mode 100644
index 00000000..7ff50d7a
--- /dev/null
+++ b/test/default-npm-algorithm/default.js
@@ -0,0 +1,14 @@
+System.import("some").then(function (module) {
+	if (typeof window !== "undefined" && window.QUnit) {
+		QUnit.strictEqual(System.npmContext.isFlatFileStructure, true, "default npm-algorithm is flat");
+		QUnit.equal(System.npmAlgorithm, 'flat');
+
+		QUnit.equal(module.some, 'some', "module loaded");
+		QUnit.equal(module.other, 'other', 'nested module loaded');
+
+		QUnit.start();
+		removeMyself();
+	} else {
+		console.log(steal.System);
+	}
+});
diff --git a/test/default-npm-algorithm/node_modules/other/main.js b/test/default-npm-algorithm/node_modules/other/main.js
new file mode 100644
index 00000000..018244f9
--- /dev/null
+++ b/test/default-npm-algorithm/node_modules/other/main.js
@@ -0,0 +1,1 @@
+module.exports = "other";
\ No newline at end of file
diff --git a/test/default-npm-algorithm/node_modules/other/package.json b/test/default-npm-algorithm/node_modules/other/package.json
new file mode 100644
index 00000000..ca971f8a
--- /dev/null
+++ b/test/default-npm-algorithm/node_modules/other/package.json
@@ -0,0 +1,5 @@
+{
+	"name": "other",
+	"version": "1.0.0",
+	"main": "main.js"
+}
diff --git a/test/default-npm-algorithm/node_modules/some/main.js b/test/default-npm-algorithm/node_modules/some/main.js
new file mode 100644
index 00000000..a6cc8c46
--- /dev/null
+++ b/test/default-npm-algorithm/node_modules/some/main.js
@@ -0,0 +1,3 @@
+var other = require('other');
+module.exports.some = "some";
+module.exports.other = other;
diff --git a/test/default-npm-algorithm/node_modules/some/package.json b/test/default-npm-algorithm/node_modules/some/package.json
new file mode 100644
index 00000000..de181b89
--- /dev/null
+++ b/test/default-npm-algorithm/node_modules/some/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "some",
+  "version": "1.0.0",
+  "main": "main.js",
+  "system": {
+  },
+  "dependencies": {
+    "other": "1.0.0"
+  }
+}
\ No newline at end of file
diff --git a/test/default-npm-algorithm/npm-algorithm.html b/test/default-npm-algorithm/npm-algorithm.html
new file mode 100644
index 00000000..a4d7b0c5
--- /dev/null
+++ b/test/default-npm-algorithm/npm-algorithm.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<body>
+
+<script>
+    window.QUnit = window.parent.QUnit;
+    window.removeMyself = window.parent.removeMyself;
+</script>
+<!-- note, that a "npm-algorithm" config attr does not overwrite "isFlatFileStructure". see test for more info -->
+<script type="text/javascript" src='../../steal.js' main="default" config="package.json!npm" npm-algorithm="nested"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/default-npm-algorithm/npm-algorithm.js b/test/default-npm-algorithm/npm-algorithm.js
new file mode 100644
index 00000000..6f82e35a
--- /dev/null
+++ b/test/default-npm-algorithm/npm-algorithm.js
@@ -0,0 +1,18 @@
+System.import("some").then(function (module) {
+	if (typeof window !== "undefined" && window.QUnit) {
+		QUnit.strictEqual(System.npmContext.isFlatFileStructure, true, "default npm-algorithm is flat");
+		// you cant config npmAlgorithm outside of a package.json config-file!
+		// npmAlgorithm is "nested", but "System.npmContext.isFlatFileStructure" is true because of the
+		// package-config. it is a wired to have a test like this, but we should figure out if this behaviour
+		// is wanted.
+		QUnit.equal(System.npmAlgorithm, 'nested');
+
+		QUnit.equal(module.some, 'some', "module loaded");
+		QUnit.equal(module.other, 'other', 'nested module loaded');
+
+		QUnit.start();
+		removeMyself();
+	} else {
+		console.log(steal.System);
+	}
+});
diff --git a/test/default-npm-algorithm/package.json b/test/default-npm-algorithm/package.json
new file mode 100644
index 00000000..44ecf364
--- /dev/null
+++ b/test/default-npm-algorithm/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "foobar",
+  "version": "1.0.0",
+  "system": {
+  },
+  "dependencies": {
+    "some": "1.0.0"
+  }
+}
diff --git a/test/nested-npm-algorithm/nested.html b/test/nested-npm-algorithm/nested.html
new file mode 100644
index 00000000..e03303d2
--- /dev/null
+++ b/test/nested-npm-algorithm/nested.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<body>
+
+<script>
+    window.QUnit = window.parent.QUnit;
+    window.removeMyself = window.parent.removeMyself;
+</script>
+<script type="text/javascript" src='../../steal.js' main="nested" config="package.json!npm"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/nested-npm-algorithm/nested.js b/test/nested-npm-algorithm/nested.js
new file mode 100644
index 00000000..8cf2f6f7
--- /dev/null
+++ b/test/nested-npm-algorithm/nested.js
@@ -0,0 +1,14 @@
+System.import("some").then(function (module) {
+	if (typeof window !== "undefined" && window.QUnit) {
+		QUnit.strictEqual(System.npmContext.isFlatFileStructure, false, "npm-algorithm is nested");
+		QUnit.equal(steal.System.npmAlgorithm, "nested", "npm-algorithm is nested");
+		QUnit.equal(module.some, 'some', "module loaded");
+		QUnit.equal(module.other, 'other', 'nested module loaded');
+
+		QUnit.start();
+		removeMyself();
+	} else {
+		console.log(steal.System);
+	}
+});
+
diff --git a/test/nested-npm-algorithm/node_modules/some/main.js b/test/nested-npm-algorithm/node_modules/some/main.js
new file mode 100644
index 00000000..a6cc8c46
--- /dev/null
+++ b/test/nested-npm-algorithm/node_modules/some/main.js
@@ -0,0 +1,3 @@
+var other = require('other');
+module.exports.some = "some";
+module.exports.other = other;
diff --git a/test/nested-npm-algorithm/node_modules/some/node_modules/other/main.js b/test/nested-npm-algorithm/node_modules/some/node_modules/other/main.js
new file mode 100644
index 00000000..018244f9
--- /dev/null
+++ b/test/nested-npm-algorithm/node_modules/some/node_modules/other/main.js
@@ -0,0 +1,1 @@
+module.exports = "other";
\ No newline at end of file
diff --git a/test/nested-npm-algorithm/node_modules/some/node_modules/other/package.json b/test/nested-npm-algorithm/node_modules/some/node_modules/other/package.json
new file mode 100644
index 00000000..ca971f8a
--- /dev/null
+++ b/test/nested-npm-algorithm/node_modules/some/node_modules/other/package.json
@@ -0,0 +1,5 @@
+{
+	"name": "other",
+	"version": "1.0.0",
+	"main": "main.js"
+}
diff --git a/test/nested-npm-algorithm/node_modules/some/package.json b/test/nested-npm-algorithm/node_modules/some/package.json
new file mode 100644
index 00000000..de181b89
--- /dev/null
+++ b/test/nested-npm-algorithm/node_modules/some/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "some",
+  "version": "1.0.0",
+  "main": "main.js",
+  "system": {
+  },
+  "dependencies": {
+    "other": "1.0.0"
+  }
+}
\ No newline at end of file
diff --git a/test/nested-npm-algorithm/package.json b/test/nested-npm-algorithm/package.json
new file mode 100644
index 00000000..43b014ae
--- /dev/null
+++ b/test/nested-npm-algorithm/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "foobar",
+  "version": "1.0.0",
+  "system": {
+    "npmAlgorithm": "nested"
+  },
+  "dependencies": {
+    "some": "1.0.0"
+  }
+}
diff --git a/test/node_test.js b/test/node_test.js
index d91ceb74..a4d0c51a 100644
--- a/test/node_test.js
+++ b/test/node_test.js
@@ -8,6 +8,31 @@ var makeSteal = function(config){
 	return localSteal;
 };
 
+describe("default cofiguration", function () {
+	this.timeout(20000);
+
+	it("without any configuration", function (done) {
+		var steal = makeSteal();
+		steal.startup().then(function(){
+			assert.equal(steal.System.transpiler, 'traceur');
+			assert.equal(steal.System.paths['@config'], "stealconfig.js");
+			done();
+		},done);
+	});
+
+	it("with a npm configuration", function (done) {
+		var steal = makeSteal({
+			config: __dirname+"/npm-deep/package.json!npm"
+		});
+		steal.startup().then(function(){
+			assert.equal(steal.System.transpiler, 'traceur');
+			assert.equal(steal.System.configMain, 'package.json!npm');
+			assert.strictEqual(steal.System.npmContext.isFlatFileStructure, true);
+			done();
+		},done);
+	});
+});
+
 describe("plugins", function(){
 	this.timeout(20000);
 
diff --git a/test/test.js b/test/test.js
index db20b587..faed2bdb 100644
--- a/test/test.js
+++ b/test/test.js
@@ -199,6 +199,18 @@ QUnit.config.testTimeout = 30000;
 		});
 	}
 
+	asyncTest("default npm-algorithm", function(){
+		makeIframe("default-npm-algorithm/default.html");
+	});
+
+	asyncTest("default npm-algorithm overwritten", function(){
+		makeIframe("default-npm-algorithm/npm-algorithm.html");
+	});
+
+	asyncTest("npm-algorithm less npm 3", function(){
+		makeIframe("nested-npm-algorithm/nested.html");
+	});
+
 	asyncTest("compat - production bundle works", function(){
 		makeIframe("production/prod.html");
 	});