+
module.exports = function(grunt) {
+ path = require("path");
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
}
},
concat: {
- options: {
- separator: ';'
- },
- dist: {
- src: ['src/**/*.js'],
- dest: 'dist/<%= pkg.name %>.js'
- },
css: {
src: ['src/**/*.css'],
dest: 'dist/<%= pkg.name %>.css'
+ },
+ js: { // Wrap subtask
+ options: {
+ banner: "$(function(){\n",
+ footer: "\n});\n",
+ },
+ src: 'dist/<%= pkg.name %>.unwrapped.js',
+ dest: 'dist/<%= pkg.name %>.js',
+ }
+ },
+ concat_in_order: {
+ options: {
+ separator: ';',
+ extractRequired: function(filepath, filecontent) {
+ var workingdir = path.normalize(filepath).split(path.sep);
+ workingdir.pop();
+
+ var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent);
+ deps.forEach(function(dep, i) {
+ var dependency = workingdir.concat([dep]);
+ deps[i] = path.join.apply(null, dependency);
+ });
+ return deps;
+ },
+ extractDeclared: function(filepath) {
+ return [filepath];
+ },
+ onlyConcatRequiredFiles: true
+ },
+ dist: {
+ src: ['src/flowy.js'],
+ dest: 'dist/<%= pkg.name %>.unwrapped.js'
}
},
copy: {
grunt.loadNpmTasks('grunt-bower-concat');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-concat');
+ grunt.loadNpmTasks('grunt-concat-in-order');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
- grunt.registerTask('default', ['jshint', 'bower_concat', 'concat', 'copy']);
+ grunt.registerTask('default', ['jshint', 'bower_concat', 'concat_in_order', 'concat', 'copy']);
};
-$(function() {
+$(function(){
+var TodoView = Backbone.View.extend({
+ tagName: 'div',
+ className: 'todo',
+ events: {
+ "click > .text": "toggleComplete",
+ },
+ initialize: function() {
+ this.listenTo(this.model, "change", this.render);
+ this.listenTo(this.model, 'destroy', this.remove);
+ },
+ toggleComplete: function(e) {
+ this.model.toggleComplete();
+ e.stopPropagation();
+ },
+ template: "<div class=\"text\">{{text}}</div><div class=\"bullets\"></div>",
+ addChild: function(el, position) {
+ if(typeof position === 'undefined') {
+ console.log("TodoView:addChild called without a position");
+ }
+ this.$el.find("> .bullets").append(el);
+ return this;
+ },
+ render: function() {
+ var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers
+ this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes
+ this.$el.toggleClass('completed', this.model.get('completed'));
+ this.$el.toggleClass('collapsed', this.model.get('collapsed'));
+ this.$el.find("> .bullets").append(oldChildren);
+ return this;
+ },
+});
var TodoModel = Backbone.Model.extend({
defaults: function() {
},
});
+/**
+ * @depend ../models/todo.js
+ */
+
flowyDocDefaults = { rootId: null };
var FlowyDocModel = Backbone.Collection.extend({
initialize: function(options) {
comparator: 'order',
});
+/**
+ * @depend ../views/todo.js
+ * @depend ../models/flowyDoc.js
+ * @depend ../models/todo.js
+ */
+
var todos = new FlowyDocModel({
id: "master",
default: [
],
});
-var TodoView = Backbone.View.extend({
- tagName: 'div',
- className: 'todo',
- events: {
- "click > .text": "toggleComplete",
- },
- initialize: function() {
- this.listenTo(this.model, "change", this.render);
- this.listenTo(this.model, 'destroy', this.remove);
- },
- toggleComplete: function(e) {
- this.model.toggleComplete();
- e.stopPropagation();
- },
- template: "<div class=\"text\">{{text}}</div><div class=\"bullets\"></div>",
- addChild: function(el, position) {
- if(typeof position === 'undefined') {
- console.log("TodoView:addChild called without a position");
- }
- this.$el.find("> .bullets").append(el);
- return this;
- },
- render: function() {
- var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers
- this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes
- this.$el.toggleClass('completed', this.model.get('completed'));
- this.$el.toggleClass('collapsed', this.model.get('collapsed'));
- this.$el.find("> .bullets").append(oldChildren);
- return this;
- },
-});
-
var appDefaults = { list: todos };
var AppView = Backbone.View.extend({
el: $("#todo-app"),
},
});
-var flowyView = new AppView();
+/**
+ * @depend views/app.js
+ */
+
+$(function() {
+ var flowyView = new AppView();
+});
});
-$(function() {
+var TodoView = Backbone.View.extend({
+ tagName: 'div',
+ className: 'todo',
+ events: {
+ "click > .text": "toggleComplete",
+ },
+ initialize: function() {
+ this.listenTo(this.model, "change", this.render);
+ this.listenTo(this.model, 'destroy', this.remove);
+ },
+ toggleComplete: function(e) {
+ this.model.toggleComplete();
+ e.stopPropagation();
+ },
+ template: "<div class=\"text\">{{text}}</div><div class=\"bullets\"></div>",
+ addChild: function(el, position) {
+ if(typeof position === 'undefined') {
+ console.log("TodoView:addChild called without a position");
+ }
+ this.$el.find("> .bullets").append(el);
+ return this;
+ },
+ render: function() {
+ var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers
+ this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes
+ this.$el.toggleClass('completed', this.model.get('completed'));
+ this.$el.toggleClass('collapsed', this.model.get('collapsed'));
+ this.$el.find("> .bullets").append(oldChildren);
+ return this;
+ },
+});
var TodoModel = Backbone.Model.extend({
defaults: function() {
},
});
+/**
+ * @depend ../models/todo.js
+ */
+
flowyDocDefaults = { rootId: null };
var FlowyDocModel = Backbone.Collection.extend({
initialize: function(options) {
comparator: 'order',
});
+/**
+ * @depend ../views/todo.js
+ * @depend ../models/flowyDoc.js
+ * @depend ../models/todo.js
+ */
+
var todos = new FlowyDocModel({
id: "master",
default: [
],
});
-var TodoView = Backbone.View.extend({
- tagName: 'div',
- className: 'todo',
- events: {
- "click > .text": "toggleComplete",
- },
- initialize: function() {
- this.listenTo(this.model, "change", this.render);
- this.listenTo(this.model, 'destroy', this.remove);
- },
- toggleComplete: function(e) {
- this.model.toggleComplete();
- e.stopPropagation();
- },
- template: "<div class=\"text\">{{text}}</div><div class=\"bullets\"></div>",
- addChild: function(el, position) {
- if(typeof position === 'undefined') {
- console.log("TodoView:addChild called without a position");
- }
- this.$el.find("> .bullets").append(el);
- return this;
- },
- render: function() {
- var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers
- this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes
- this.$el.toggleClass('completed', this.model.get('completed'));
- this.$el.toggleClass('collapsed', this.model.get('collapsed'));
- this.$el.find("> .bullets").append(oldChildren);
- return this;
- },
-});
-
var appDefaults = { list: todos };
var AppView = Backbone.View.extend({
el: $("#todo-app"),
},
});
-var flowyView = new AppView();
+/**
+ * @depend views/app.js
+ */
+$(function() {
+ var flowyView = new AppView();
});
--- /dev/null
+{\r
+ "curly": true,\r
+ "eqeqeq": true,\r
+ "immed": true,\r
+ "latedef": true,\r
+ "newcap": true,\r
+ "noarg": true,\r
+ "sub": true,\r
+ "undef": true,\r
+ "boss": true,\r
+ "eqnull": true,\r
+ "node": true\r
+}\r
--- /dev/null
+node_modules\r
+npm-debug.log\r
+tmp\r
+.idea\r
--- /dev/null
+/*\r
+ * grunt-concat-in-order\r
+ * https://github.com/miensol/grunt-concat-in-order\r
+ *\r
+ * Copyright (c) 2013 Piotr Mionskowski\r
+ * Licensed under the MIT license.\r
+ */\r
+\r
+'use strict';\r
+\r
+module.exports = function(grunt) {\r
+ var path = require('path');\r
+\r
+ // Project configuration.\r
+ grunt.initConfig({\r
+ jshint: {\r
+ all: [\r
+ 'Gruntfile.js',\r
+ 'tasks/*.js',\r
+ '<%= nodeunit.tests %>'\r
+ ],\r
+ options: {\r
+ jshintrc: '.jshintrc'\r
+ }\r
+ },\r
+\r
+ // Before generating any new files, remove any previously-created files.\r
+ clean: {\r
+ tests: ['tmp']\r
+ },\r
+\r
+ // Configuration to be run (and then tested).\r
+ concat_in_order: {\r
+ default_options: {\r
+ files: {\r
+ 'tmp/default_options.js': ['test/default/**/*.js']\r
+ }\r
+ },\r
+ cycle_options: {\r
+ files: {\r
+ 'tmp/cycle_options.js': ['test/cycle/**/*.js']\r
+ }\r
+ },\r
+ missing_options: {\r
+ files: {\r
+ 'tmp/missing_options.js': ['test/missing/**/*.js']\r
+ }\r
+ },\r
+ filebased_options: {\r
+ files: {\r
+ 'tmp/filebased_options.js': ['test/filebased/AUsingBaseBAndBaseA.js']\r
+ },\r
+ options: {\r
+ extractRequired: function(filepath, filecontent) {\r
+ var workingdir = path.normalize(filepath).split(path.sep);\r
+ workingdir.pop();\r
+\r
+ var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent);\r
+ deps.forEach(function(dep, i) {\r
+ var dependency = workingdir.concat([dep]);\r
+ deps[i] = path.join.apply(null, dependency);\r
+ });\r
+ return deps;\r
+ },\r
+ extractDeclared: function(filepath) {\r
+ return [filepath];\r
+ },\r
+ onlyConcatRequiredFiles: true\r
+ }\r
+ }\r
+ },\r
+\r
+ // Unit tests.\r
+ nodeunit: {\r
+ tests: ['test/*_test.js']\r
+ }\r
+\r
+ });\r
+\r
+ // Actually load this plugin's task(s).\r
+ grunt.loadTasks('tasks');\r
+\r
+ // These plugins provide necessary tasks.\r
+ grunt.loadNpmTasks('grunt-contrib-jshint');\r
+ grunt.loadNpmTasks('grunt-contrib-clean');\r
+ grunt.loadNpmTasks('grunt-contrib-nodeunit');\r
+\r
+ // Whenever the "test" task is run, first clean the "tmp" dir, then run this\r
+ // plugin's task(s), then test the result.\r
+ grunt.registerTask('test', ['clean', 'concat_in_order:default_options', 'concat_in_order:filebased_options', 'nodeunit']);\r
+\r
+ // By default, lint and run all tests.\r
+ grunt.registerTask('default', ['jshint', 'test']);\r
+\r
+};\r
--- /dev/null
+Copyright (c) 2013 Piotr Mionskowski\r
+\r
+Permission is hereby granted, free of charge, to any person\r
+obtaining a copy of this software and associated documentation\r
+files (the "Software"), to deal in the Software without\r
+restriction, including without limitation the rights to use,\r
+copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+copies of the Software, and to permit persons to whom the\r
+Software is furnished to do so, subject to the following\r
+conditions:\r
+\r
+The above copyright notice and this permission notice shall be\r
+included in all copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+OTHER DEALINGS IN THE SOFTWARE.\r
--- /dev/null
+# grunt-concat-in-order\r
+\r
+> Concatenates files respecting dependencies.\r
+\r
+## Getting Started\r
+This plugin requires Grunt `~0.4.1`\r
+\r
+If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:\r
+\r
+```shell\r
+npm install grunt-concat-in-order --save-dev\r
+```\r
+\r
+Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:\r
+\r
+```js\r
+grunt.loadNpmTasks('grunt-concat-in-order');\r
+```\r
+\r
+## The "concat_in_order" task\r
+\r
+### Overview\r
+\r
+The `concat_in_order` task extracts declared required dependencies as well as provided modules/classes from your javascript (or any other text) files. Having this [dependency graph](http://en.wikipedia.org/wiki/Dependency_graph) the task will perform [topological sort](http://en.wikipedia.org/wiki/Topological_sorting) and concatenate file so that all modules will be put after their required dependencies.\r
+\r
+In your project's Gruntfile, add a section named `concat_in_order` to the data object passed into `grunt.initConfig()`.\r
+\r
+```js\r
+grunt.initConfig({\r
+ concat_in_order: {\r
+ your_target: {\r
+ options: {\r
+ /*\r
+ this is a default function that extracts required dependencies/module names from file content\r
+ (getMatches - function that pick groups from given regexp)\r
+ extractRequired: function (filepath, filecontent) {\r
+ return this.getMatches(/require\(['"]([^'"]+)['"]/g, filecontent);\r
+ },\r
+ this is a default function that extracts declared modules names from file content\r
+ extractDeclared: function (filepath, filecontent) {\r
+ return this.getMatches(/declare\(['"]([^'"]+)['"]/g, filecontent);\r
+ }\r
+ */\r
+ },\r
+ files: {\r
+ 'build/concatenated.js': ['lib/**/*.js']\r
+ }\r
+ }\r
+ }\r
+})\r
+```\r
+### Sample\r
+Let's say you have 4 files in a `lib` directory\r
+\r
+- AUsingBaseBAndBaseA.js\r
+\r
+```js\r
+/*start AUsingBaseBAndBaseA*/\r
+framwork.require('module.BaseB');\r
+framwork.require('module.BaseA');\r
+framework.declare('module.UsingBaseBAndBaseA');\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/\r
+```\r
+\r
+- AUsingBaseA.js\r
+\r
+```js\r
+/*start AUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/\r
+```\r
+\r
+\r
+- BaseA.js\r
+\r
+```js\r
+/*start BaseA*/\r
+framework.declare('module.BaseA');\r
+var first = function firstFunction(){};\r
+/*end BaseA*/\r
+```\r
+\r
+- BaseBUsingBaseA.js\r
+\r
+```js\r
+/*start BaseBUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+framework.declare('module.BaseBUsingBaseA');\r
+framework.declare('module.BaseB');\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/\r
+```\r
+Given the above configuration the task will produce `build/concatenated.js` file with following content:\r
+\r
+```js\r
+/*start BaseA*/\r
+framework.declare('module.BaseA');\r
+var first = function firstFunction(){};\r
+/*end BaseA*/\r
+/*start BaseBUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+framework.declare('module.BaseBUsingBaseA');\r
+framework.declare('module.BaseB');\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/\r
+/*start AUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/\r
+/*start AUsingBaseBAndBaseA*/\r
+framwork.require('module.BaseB');\r
+framwork.require('module.BaseA');\r
+framework.declare('module.UsingBaseBAndBaseA');\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/\r
+```\r
+\r
+### File based\r
+You can enable automatic addition of files with the following example. (notice the onlyConcatRequiredFiles : true) This is the same way of declaring dependencies used by [juicer](https://github.com/cjohansen/juicer)\r
+\r
+ files: {\r
+ 'dist/mybuild.js': ['js/src/main.js']\r
+ },\r
+ options: {\r
+ extractRequired: function(filepath, filecontent) {\r
+ var workingdir = path.normalize(filepath).split(path.sep);\r
+ workingdir.pop();\r
+\r
+ var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent);\r
+ deps.forEach(function(dep, i) {\r
+ var dependency = workingdir.concat([dep]);\r
+ deps[i] = path.join.apply(null, dependency);\r
+ });\r
+ return deps;\r
+ },\r
+ extractDeclared: function(filepath) {\r
+ return [filepath];\r
+ },\r
+ onlyConcatRequiredFiles: true\r
+ }\r
+\r
+This will declare all files as modules using their filenames. In main.js you will typically have these depend statements:\r
+\r
+ /**\r
+ * @depend ../lib/jquery.js\r
+ * @depend otherfile.js\r
+ * @depend calculator/add.js\r
+ */\r
+\r
+You only need to specify the main.js and the other dependencies will be added automatically. As well as their dependencies etc.\r
+\r
+If you want to add a file that isn't referenced anywhere you need to add it manually.\r
+\r
+ files: {\r
+ 'dist/mybuild.js': ['js/src/main.js', 'js/src/unReferencedButWanted.js']\r
+ },\r
+\r
+The option onlyConcatRequiredFiles will only work if modules are declared and required with their actual filenames.\r
+\r
+## Contributing\r
+In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).\r
+\r
+## Release History\r
+\r
+- 0.1.6 - @mokkabonna updated documenation and fixed path splitting in sample\r
+- 0.1.4 - @mokkabonna added ability to concat only files that are required by some module\r
--- /dev/null
+{
+ "name": "grunt-concat-in-order",
+ "description": "Concatenates files respecting declared, required dependencies order.",
+ "version": "0.1.6",
+ "homepage": "https://github.com/miensol/grunt-concat-in-order",
+ "author": {
+ "name": "Piotr Mionskowski",
+ "email": "piotr.mionskowski@gmail.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/miensol/grunt-concat-in-order.git"
+ },
+ "bugs": {
+ "url": "https://github.com/miensol/grunt-concat-in-order/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "https://github.com/miensol/grunt-concat-in-order/blob/master/LICENSE-MIT"
+ }
+ ],
+ "main": "Gruntfile.js",
+ "engines": {
+ "node": ">= 0.8.0"
+ },
+ "scripts": {
+ "test": "grunt test"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "grunt-contrib-jshint": "~0.6.0",
+ "grunt-contrib-clean": "~0.4.0",
+ "grunt-contrib-nodeunit": "~0.2.0",
+ "grunt": "~0.4.1"
+ },
+ "peerDependencies": {
+ "grunt": "~0.4.1"
+ },
+ "keywords": [
+ "gruntplugin",
+ "concat",
+ "concat-in-order",
+ "topological sort",
+ "dependency tree",
+ "concat in order",
+ "resolve file order",
+ "sort dependencies",
+ "dependency"
+ ],
+ "readme": "# grunt-concat-in-order\r\n\r\n> Concatenates files respecting dependencies.\r\n\r\n## Getting Started\r\nThis plugin requires Grunt `~0.4.1`\r\n\r\nIf you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:\r\n\r\n```shell\r\nnpm install grunt-concat-in-order --save-dev\r\n```\r\n\r\nOnce the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:\r\n\r\n```js\r\ngrunt.loadNpmTasks('grunt-concat-in-order');\r\n```\r\n\r\n## The \"concat_in_order\" task\r\n\r\n### Overview\r\n\r\nThe `concat_in_order` task extracts declared required dependencies as well as provided modules/classes from your javascript (or any other text) files. Having this [dependency graph](http://en.wikipedia.org/wiki/Dependency_graph) the task will perform [topological sort](http://en.wikipedia.org/wiki/Topological_sorting) and concatenate file so that all modules will be put after their required dependencies.\r\n\r\nIn your project's Gruntfile, add a section named `concat_in_order` to the data object passed into `grunt.initConfig()`.\r\n\r\n```js\r\ngrunt.initConfig({\r\n concat_in_order: {\r\n your_target: {\r\n options: {\r\n /*\r\n this is a default function that extracts required dependencies/module names from file content\r\n (getMatches - function that pick groups from given regexp)\r\n extractRequired: function (filepath, filecontent) {\r\n return this.getMatches(/require\\(['\"]([^'\"]+)['\"]/g, filecontent);\r\n },\r\n this is a default function that extracts declared modules names from file content\r\n extractDeclared: function (filepath, filecontent) {\r\n return this.getMatches(/declare\\(['\"]([^'\"]+)['\"]/g, filecontent);\r\n }\r\n */\r\n },\r\n files: {\r\n 'build/concatenated.js': ['lib/**/*.js']\r\n }\r\n }\r\n }\r\n})\r\n```\r\n### Sample\r\nLet's say you have 4 files in a `lib` directory\r\n\r\n- AUsingBaseBAndBaseA.js\r\n\r\n```js\r\n/*start AUsingBaseBAndBaseA*/\r\nframwork.require('module.BaseB');\r\nframwork.require('module.BaseA');\r\nframework.declare('module.UsingBaseBAndBaseA');\r\nvar forth = function fourthFunction(){};\r\n/*end AUsingBaseBAnddBaseA*/\r\n```\r\n\r\n- AUsingBaseA.js\r\n\r\n```js\r\n/*start AUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nvar second = function secondFunction(){};\r\n/*end AUsingBaseA*/\r\n```\r\n\r\n\r\n- BaseA.js\r\n\r\n```js\r\n/*start BaseA*/\r\nframework.declare('module.BaseA');\r\nvar first = function firstFunction(){};\r\n/*end BaseA*/\r\n```\r\n\r\n- BaseBUsingBaseA.js\r\n\r\n```js\r\n/*start BaseBUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nframework.declare('module.BaseBUsingBaseA');\r\nframework.declare('module.BaseB');\r\nvar third = function thirdFunction(){};\r\n/*end BaseBUsingBaseA*/\r\n```\r\nGiven the above configuration the task will produce `build/concatenated.js` file with following content:\r\n\r\n```js\r\n/*start BaseA*/\r\nframework.declare('module.BaseA');\r\nvar first = function firstFunction(){};\r\n/*end BaseA*/\r\n/*start BaseBUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nframework.declare('module.BaseBUsingBaseA');\r\nframework.declare('module.BaseB');\r\nvar third = function thirdFunction(){};\r\n/*end BaseBUsingBaseA*/\r\n/*start AUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nvar second = function secondFunction(){};\r\n/*end AUsingBaseA*/\r\n/*start AUsingBaseBAndBaseA*/\r\nframwork.require('module.BaseB');\r\nframwork.require('module.BaseA');\r\nframework.declare('module.UsingBaseBAndBaseA');\r\nvar forth = function fourthFunction(){};\r\n/*end AUsingBaseBAnddBaseA*/\r\n```\r\n\r\n### File based\r\nYou can enable automatic addition of files with the following example. (notice the onlyConcatRequiredFiles : true) This is the same way of declaring dependencies used by [juicer](https://github.com/cjohansen/juicer)\r\n\r\n files: {\r\n 'dist/mybuild.js': ['js/src/main.js']\r\n },\r\n options: {\r\n extractRequired: function(filepath, filecontent) {\r\n var workingdir = path.normalize(filepath).split(path.sep);\r\n workingdir.pop();\r\n\r\n var deps = this.getMatches(/\\*\\s*@depend\\s(.*\\.js)/g, filecontent);\r\n deps.forEach(function(dep, i) {\r\n var dependency = workingdir.concat([dep]);\r\n deps[i] = path.join.apply(null, dependency);\r\n });\r\n return deps;\r\n },\r\n extractDeclared: function(filepath) {\r\n return [filepath];\r\n },\r\n onlyConcatRequiredFiles: true\r\n }\r\n\r\nThis will declare all files as modules using their filenames. In main.js you will typically have these depend statements:\r\n\r\n /**\r\n * @depend ../lib/jquery.js\r\n * @depend otherfile.js\r\n * @depend calculator/add.js\r\n */\r\n\r\nYou only need to specify the main.js and the other dependencies will be added automatically. As well as their dependencies etc.\r\n\r\nIf you want to add a file that isn't referenced anywhere you need to add it manually.\r\n\r\n files: {\r\n 'dist/mybuild.js': ['js/src/main.js', 'js/src/unReferencedButWanted.js']\r\n },\r\n\r\nThe option onlyConcatRequiredFiles will only work if modules are declared and required with their actual filenames.\r\n\r\n## Contributing\r\nIn lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).\r\n\r\n## Release History\r\n\r\n- 0.1.6 - @mokkabonna updated documenation and fixed path splitting in sample\r\n- 0.1.4 - @mokkabonna added ability to concat only files that are required by some module\r\n",
+ "readmeFilename": "README.md",
+ "_id": "grunt-concat-in-order@0.1.6",
+ "dist": {
+ "shasum": "26e0d2b6ac9fdcbd6611b05937df78d68c5a8b0e",
+ "tarball": "http://registry.npmjs.org/grunt-concat-in-order/-/grunt-concat-in-order-0.1.6.tgz"
+ },
+ "_from": "grunt-concat-in-order@*",
+ "_npmVersion": "1.2.21",
+ "_npmUser": {
+ "name": "miensol",
+ "email": "piotr.mionskowski@gmail.com"
+ },
+ "maintainers": [
+ {
+ "name": "miensol",
+ "email": "piotr.mionskowski@gmail.com"
+ }
+ ],
+ "directories": {},
+ "_shasum": "26e0d2b6ac9fdcbd6611b05937df78d68c5a8b0e",
+ "_resolved": "https://registry.npmjs.org/grunt-concat-in-order/-/grunt-concat-in-order-0.1.6.tgz"
+}
--- /dev/null
+/*\r
+ * grunt-concat-in-order\r
+ * https://github.com/miensol/grunt-concat-in-order\r
+ *\r
+ * Copyright (c) 2013 Piotr Mionskowski\r
+ * Licensed under the MIT license.\r
+ */\r
+\r
+'use strict';\r
+\r
+var path = require('path'),\r
+ EOL = require('os').EOL;\r
+\r
+module.exports = function (grunt) {\r
+\r
+ var defaultOptions = {\r
+ getMatches: function (regex, string, index) {\r
+ var matches = [], match;\r
+ if(arguments.length < 3){\r
+ index = 1;\r
+ }\r
+ while (match = regex.exec(string)) {\r
+ matches.push(match[index]);\r
+ }\r
+ return matches;\r
+ },\r
+ extractRequired: function (filepath, filecontent) {\r
+ return this.getMatches(/require\(['"]([^'"]+)['"]/g, filecontent);\r
+ },\r
+ extractDeclared: function (filepath, filecontent) {\r
+ return this.getMatches(/declare\(['"]([^'"]+)['"]/g, filecontent);\r
+ }\r
+ }, getExistingFiles = function (files) {\r
+ return files.src.filter(function (filepath) {\r
+ // Warn on and remove invalid source files (if nonull was set).\r
+ if (!grunt.file.exists(filepath)) {\r
+ grunt.log.warn('Source file "' + filepath + '" not found.');\r
+ return false;\r
+ } else {\r
+ return true;\r
+ }\r
+ });\r
+ }, ensureNoCycleExists = function (depsTree) {\r
+ var graph = {};\r
+ depsTree.forEach(function (item) {\r
+ graph[item.file] = item;\r
+ });\r
+ depsTree.forEach(function (item) {\r
+ var visited = {};\r
+ ensureNoCycleExistsDfs(item, visited, graph);\r
+ });\r
+ }, ensureNoCycleExistsDfs = function (item, visited, graph) {\r
+ var graphKeys = Object.keys(graph),\r
+ graphArray = graphKeys.map(function (key) {\r
+ return graph[key];\r
+ }),\r
+ findItemsDeclaringIgnoreCase = function(declaration){\r
+ return graphArray.filter(function (anyItem) {\r
+ return anyItem.declared.map(function(declared){\r
+ return declared.toLowerCase();\r
+ }).indexOf(declaration.toLowerCase()) !== -1;\r
+ });\r
+ },\r
+ findItemsDeclaring = function (declaration) {\r
+ return graphArray.filter(function (anyItem) {\r
+ return anyItem.declared.indexOf(declaration) !== -1;\r
+ });\r
+ },\r
+ findItemDeclaring = function (declaration) {\r
+ return findItemsDeclaring(declaration)[0];\r
+ };\r
+ visited[item.file] = item;\r
+ item.required.forEach(function (requiredItem) {\r
+ var message,\r
+ declaringItem = findItemDeclaring(requiredItem);\r
+ if (!declaringItem) {\r
+ message = findItemsDeclaringIgnoreCase(requiredItem).map(function(matching){\r
+ return matching.declared.join(', ') + ' in ' + matching.file;\r
+ }).join(', ');\r
+ if(message.length){\r
+ message = "\nMaybe " + message;\r
+ }\r
+ grunt.fail.fatal("Dependency required in " + item.file + " does not exist: " + requiredItem + message);\r
+ }\r
+\r
+ if (!visited[declaringItem.file]) {\r
+ ensureNoCycleExistsDfs(declaringItem, visited, graph);\r
+ } else {\r
+ message = Object.keys(visited).join(', ');\r
+ grunt.fail.fatal("Cycle found! Current item is " + item.file + '\nVisited nodes are ' + message);\r
+ }\r
+ });\r
+ delete visited[item.file];\r
+ }, writeArray = function(array, prefix){\r
+ grunt.verbose.writeln(prefix +':');\r
+ if(array.length){\r
+ array.forEach(function(item){\r
+ grunt.verbose.writeln('\t' + item);\r
+ });\r
+ } else {\r
+ grunt.verbose.writeln('\tNo items.');\r
+ }\r
+ };\r
+\r
+ grunt.registerMultiTask('concat_in_order', 'Concatenates files in order', function () {\r
+\r
+ var options = this.options(defaultOptions);\r
+\r
+ this.files.forEach(function (fileSet) {\r
+ grunt.verbose.writeln('Extracting dependencies from "' + fileSet.src + '".');\r
+\r
+ var depsTree = [], ordered = [], current, countOfPreviouslySatisfiedDependencies,\r
+ previouslyDeclared = [],\r
+ previouslyDeclaredFilter = function (dep) {\r
+ return previouslyDeclared.filter(function (previous) {\r
+ return previous === dep;\r
+ }).length > 0;\r
+ };\r
+\r
+ getExistingFiles(fileSet).map(function extractAndAddDependencies(filepath) {\r
+ //do not process this file again if already added\r
+ if (depsTree.some(function(item) {\r
+ return item.file === path.normalize(filepath);\r
+ })) {\r
+ return;\r
+ }\r
+\r
+ var content = grunt.file.read(filepath),\r
+ required = options.extractRequired(filepath, content),\r
+ declared = options.extractDeclared(filepath, content);\r
+\r
+ grunt.verbose.writeln('File %s', filepath);\r
+ writeArray(required, 'required');\r
+ writeArray(declared, 'declared');\r
+ if (options.onlyConcatRequiredFiles) {\r
+ required.map(extractAndAddDependencies);\r
+ }\r
+\r
+ depsTree.push({\r
+ content: content,\r
+ file: path.normalize(filepath),\r
+ required: required,\r
+ declared: declared\r
+ });\r
+ });\r
+\r
+ ensureNoCycleExists(depsTree);\r
+\r
+ while (depsTree.length) {\r
+ current = depsTree.shift();\r
+ countOfPreviouslySatisfiedDependencies = current.required.filter(previouslyDeclaredFilter).length;\r
+ if (countOfPreviouslySatisfiedDependencies === current.required.length) {\r
+ previouslyDeclared.push.apply(previouslyDeclared, current.declared);\r
+ ordered.push(current);\r
+ } else {\r
+ depsTree.push(current);\r
+ }\r
+ }\r
+\r
+ grunt.file.write(fileSet.dest, ordered.map(function (item) {\r
+ return item.content;\r
+ }).join(EOL));\r
+\r
+ grunt.log.writeln('File "' + fileSet.dest + '" created.');\r
+ });\r
+ });\r
+\r
+};\r
--- /dev/null
+'use strict';\r
+\r
+var grunt = require('grunt');\r
+\r
+/*\r
+ ======== A Handy Little Nodeunit Reference ========\r
+ https://github.com/caolan/nodeunit\r
+\r
+ Test methods:\r
+ test.expect(numAssertions)\r
+ test.done()\r
+ Test assertions:\r
+ test.ok(value, [message])\r
+ test.equal(actual, expected, [message])\r
+ test.notEqual(actual, expected, [message])\r
+ test.deepEqual(actual, expected, [message])\r
+ test.notDeepEqual(actual, expected, [message])\r
+ test.strictEqual(actual, expected, [message])\r
+ test.notStrictEqual(actual, expected, [message])\r
+ test.throws(block, [error], [message])\r
+ test.doesNotThrow(block, [error], [message])\r
+ test.ifError(value)\r
+*/\r
+\r
+exports.concat_in_order = {\r
+ setUp: function(done) {\r
+ // setup here if necessary\r
+ done();\r
+ },\r
+ default_options: function(test) {\r
+ test.expect(1);\r
+\r
+ var actual = grunt.file.read('tmp/default_options.js');\r
+ var expected = grunt.file.read('test/expected/default_options.js');\r
+ test.equal(actual, expected, 'should concatenate files respecting file order');\r
+\r
+ test.done();\r
+ },\r
+ filebased_options : function(test) {\r
+ test.expect(1);\r
+\r
+ var actual = grunt.file.read('tmp/filebased_options.js');\r
+ var expected = grunt.file.read('test/expected/filebased_options.js');\r
+ test.equal(actual, expected, 'should include files automatically when filebased');\r
+\r
+ test.done();\r
+ }\r
+};\r
--- /dev/null
+declare('A');\r
+require('B');
\ No newline at end of file
--- /dev/null
+declare('B');\r
+require('C');
\ No newline at end of file
--- /dev/null
+declare('C');\r
+require('A');
\ No newline at end of file
--- /dev/null
+/*start AUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/
\ No newline at end of file
--- /dev/null
+/*start AUsingBaseBAndBaseA*/\r
+framwork.require('module.BaseB');\r
+framwork.require('module.BaseA');\r
+framework.declare('module.UsingBaseBAndBaseA');\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/
\ No newline at end of file
--- /dev/null
+/*start BaseA*/\r
+framework.declare('module.BaseA');\r
+var first = function firstFunction(){};\r
+/*end BaseA*/
\ No newline at end of file
--- /dev/null
+/*start BaseBUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+framework.declare('module.BaseBUsingBaseA');\r
+framework.declare('module.BaseB');\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/
\ No newline at end of file
--- /dev/null
+Testing: 1 2 3 !!!
\ No newline at end of file
--- /dev/null
+/*start BaseA*/\r
+framework.declare('module.BaseA');\r
+var first = function firstFunction(){};\r
+/*end BaseA*/\r
+/*start BaseBUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+framework.declare('module.BaseBUsingBaseA');\r
+framework.declare('module.BaseB');\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/\r
+/*start AUsingBaseA*/\r
+framwork.require('module.BaseA');\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/\r
+/*start AUsingBaseBAndBaseA*/\r
+framwork.require('module.BaseB');\r
+framwork.require('module.BaseA');\r
+framework.declare('module.UsingBaseBAndBaseA');\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/
\ No newline at end of file
--- /dev/null
+/*start BaseA*/\r
+var first = function firstFunction(){};\r
+/*end BaseA*/\r
+\r
+/*start AUsingBaseA*/\r
+/**\r
+ * @depend BaseA.js\r
+ */\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/\r
+\r
+/*start BaseBUsingBaseA*/\r
+/**\r
+ * @depend AUsingBaseA.js\r
+ */\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/\r
+\r
+/*start AUsingBaseBAndBaseA*/\r
+/**\r
+ * @depend BaseBUsingBaseA.js\r
+ * @depend BaseA.js\r
+ *\r
+ */\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/\r
--- /dev/null
+/*start AUsingBaseA*/\r
+/**\r
+ * @depend BaseA.js\r
+ */\r
+var second = function secondFunction(){};\r
+/*end AUsingBaseA*/\r
--- /dev/null
+/*start AUsingBaseBAndBaseA*/\r
+/**\r
+ * @depend BaseBUsingBaseA.js\r
+ * @depend BaseA.js\r
+ *\r
+ */\r
+var forth = function fourthFunction(){};\r
+/*end AUsingBaseBAnddBaseA*/\r
--- /dev/null
+/*start BaseA*/\r
+var first = function firstFunction(){};\r
+/*end BaseA*/\r
--- /dev/null
+/*start BaseBUsingBaseA*/\r
+/**\r
+ * @depend AUsingBaseA.js\r
+ */\r
+var third = function thirdFunction(){};\r
+/*end BaseBUsingBaseA*/\r
--- /dev/null
+declare('lowercase');
\ No newline at end of file
--- /dev/null
+require('LOWERCASE');
\ No newline at end of file
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-watch": "^0.6.1"
},
- "devDependencies": {},
+ "devDependencies": {
+ "grunt-concat-in-order": "^0.1.6"
+ },
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
--- /dev/null
+/**
+ * @depend views/app.js
+ */
+
+$(function() {
+ var flowyView = new AppView();
+});
--- /dev/null
+/**
+ * @depend ../models/todo.js
+ */
+
+flowyDocDefaults = { rootId: null };
+var FlowyDocModel = Backbone.Collection.extend({
+ initialize: function(options) {
+ options = _.defaults({}, options, flowyDocDefaults);
+ this.id = options.id;
+ this.default = options.default;
+ this.rootId = options.rootId;
+ },
+ model: TodoModel,
+ localStorage: new Backbone.LocalStorage("todos-backbone"),
+ fetch: function(options) { // TODO: Move to base class as default behavior for localStorage
+ // If during the initial fetch, the collection is not present, instead populate it using this.default
+ options = options ? _.clone(options) : {};
+ var error = options.error;
+ var collection = this;
+ options.error = function(_, resp, __) {
+ if (resp ==="Record Not Found" && collection.default) {
+ var method = options.reset ? 'reset' : 'set';
+ collection[method](collection.default);
+ }
+ if (error) error(collection, resp, options);
+ };
+ Backbone.Collection.prototype.fetch.call(this, options);
+ },
+ nextOrder: function() {
+ if (!this.length) return 1;
+ return this.last().get('order') + 1;
+ },
+ comparator: 'order',
+});
--- /dev/null
+var TodoModel = Backbone.Model.extend({
+ defaults: function() {
+ return {
+ completed: false,
+ collapsed: false,
+ text: "Should never be visible",
+ bullets: [],
+ parent: null,
+ };
+ },
+ toggleComplete: function() {
+ this.save({completed: !this.get("completed")});
+ },
+ toggleCollapsed: function() {
+ this.save({collapsed: !this.get("collapsed")});
+ },
+ setText: function(text) {
+ this.save({text: text});
+ },
+ getChildren: function(collection) {
+ return _.map(this.get("bullets"), function(id) {
+ return collection.get(id);
+ }, this);
+ },
+ getParent: function(collection) {
+ return collection.get(this.get("parent"));
+ },
+ isTopLevel: function(collection) {
+ return this.get("parent") === collection.rootId;
+ },
+ isParentLoaded: function(collection, collectionView) {
+ return this.isTopLevel(collection) ? true : (this.getParent(collection) && collectionView.getView(this.getParent(collection)));
+ },
+});
--- /dev/null
+/**
+ * @depend ../views/todo.js
+ * @depend ../models/flowyDoc.js
+ * @depend ../models/todo.js
+ */
+
+var todos = new FlowyDocModel({
+ id: "master",
+ default: [
+ new TodoModel({
+ parent: null,
+ id: 1,
+ text: "Daily todos",
+ bullets: [2,3,4],
+ }),
+ new TodoModel({
+ parent: 1,
+ id: 2,
+ text: "Shave",
+ completed: true
+ }),
+ new TodoModel({
+ parent: 1,
+ id: 3,
+ text: "Check t-mail",
+ completed: true
+ }),
+ new TodoModel({
+ parent: 1,
+ id: 4,
+ text: "Eat green eggs and ham",
+ }),
+ new TodoModel({
+ parent: null,
+ id: 5,
+ text: "To do this year",
+ collapsed: true,
+ bullets: [6],
+ }),
+ new TodoModel({
+ parent: 5,
+ id: 6,
+ text: "Save the world",
+ bullets: [7],
+ }),
+ new TodoModel({
+ parent: 6,
+ id: 7,
+ text: "Save California",
+ }),
+ ],
+});
+
+var appDefaults = { list: todos };
+var AppView = Backbone.View.extend({
+ el: $("#todo-app"),
+ initialize: function(options) {
+ options = _.defaults({}, options, appDefaults);
+ this.listenTo(todos, 'add', this.addOne);
+ this.listenTo(todos, 'reset', this.addAll);
+ this.list = options.list;
+ this.views = {}; // A list of views for each element in the collection
+ this.list.fetch();
+ },
+ render: function() {
+ return this;
+ },
+ addOne: function(todo) {
+ this.renderTodo(todo);
+ },
+ renderTodo: function(todo) {
+ if (this.getView(todo)) {
+ console.log("View rendered more than once for todo #" + todo.id);
+ return;
+ }
+ var view = new TodoView({model: todo});
+ this.setView(todo, view);
+ if (todo.isTopLevel(this.list)) {
+ this.$("#todo-list").append(view.render().el);
+ } else if (todo.isParentLoaded(this.list, this)) {
+ var parent = todo.getParent(this.list);
+ var parentView = this.getView(parent);
+ parentView.addChild(view.render().el);
+ }
+
+ // Find unrendered descendents and render them, too.
+ _.each(todo.getChildren(this.list), function(child) {
+ if (child && !this.getView(child)) {
+ this.renderTodo(child);
+ }
+ }, this);
+ return this;
+ },
+ setView: function(model, view) {
+ this.views[model.id] = view;
+ },
+ getView: function(model) {
+ return this.views[model.id];
+ },
+ addAll: function() {
+ this.list.each(this.addOne, this);
+ },
+});
--- /dev/null
+var TodoView = Backbone.View.extend({
+ tagName: 'div',
+ className: 'todo',
+ events: {
+ "click > .text": "toggleComplete",
+ },
+ initialize: function() {
+ this.listenTo(this.model, "change", this.render);
+ this.listenTo(this.model, 'destroy', this.remove);
+ },
+ toggleComplete: function(e) {
+ this.model.toggleComplete();
+ e.stopPropagation();
+ },
+ template: "<div class=\"text\">{{text}}</div><div class=\"bullets\"></div>",
+ addChild: function(el, position) {
+ if(typeof position === 'undefined') {
+ console.log("TodoView:addChild called without a position");
+ }
+ this.$el.find("> .bullets").append(el);
+ return this;
+ },
+ render: function() {
+ var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers
+ this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes
+ this.$el.toggleClass('completed', this.model.get('completed'));
+ this.$el.toggleClass('collapsed', this.model.get('collapsed'));
+ this.$el.find("> .bullets").append(oldChildren);
+ return this;
+ },
+});