npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

grunt-and-sniff

v0.5.0

Published

This module is a toolkit that enhances the grunt-contrib-concat plugin to allow the usage of many kind of inclusions and insertions

Downloads

6

Readme

Grunt and Sniff

The grunt-and-sniff module is not a grunt task, but it is a toolkit that enhances the grunt-contrib-concat plugin to grant the usage of many kind of inclusions and insertions. Concatenating files is pretty usefull for JavaScript Frontend applications and grunt-and-sniff makes average or bigger project easier to export by providing an inclusion system.

grunt-and-sniff provides the following services:

  • A bunch of inclusion functions in the grunt template: include, includeAfter, includeLater, insert, insertOnce.
  • A specific syntax that uses the require keyword to improve the code readability.
  • The export of an HTML dependency report.
  • Creates a map.
  • Many information to the templates, including a $ object passed to the grunt templates in all files in which the user can write functions common to a project.
  • A way to also copy files in a folder.
  • And more features...

Starting with a simple example

This example shows the behavior of a very simple case, but Grunt and Sniff can deal with more complex situations.

Source: source/index.js

require("ui.js");

UI.button.addEventListener("click",
event =>
{
	logButton(event.target);
});

require("logButton.js");

Included 1: source/ui.js

const UI =
{
	button: document.createElement("button")
};

Included 2: source/logButton.js

function logButton(button)
{
	console.log(`Button clicked: ${button.innerHTML}`);
}

Result: destination/concat.js

const UI =
{
	button: document.createElement("button")
};

UI.button.addEventListener("click",
event =>
{
	logButton(event.target);
});

function logButton(button)
{
	console.log(`Button clicked: ${button.innerHTML}`);
}

Resulting map: map.filelist

/destination/full/source/ui.js
/destination/full/source/index.js
/destination/full/source/logButton.js

Using grunt-and-sniff

The grunt-and-sniff module exports the GSTask class that provides the process function that can be used in the grunt configuration.

The GSTask constructor takes three argument:

| Name | Type | Description | | --- | --- | --- | | grunt | Object | The grunt object passed to the function exported in GruntFile.js. | | sourceDir | String | The path of the source directory relative to the path of the project. It is used to provide all files as a relative files to the source root. | | options [optional] | Object | A set of options that are described in the "The Options chapter. |

Grunt configuration

// Initializes all paths
const SOURCE_DIR = "source";
const DEST_DIR = "destination";
const DEST_FULL_DIR = DEST_DIR + "/full";
const DEST = DEST_DIR + "/concat.js";

// Creates the GSTask from the grunt object,
// the source directory, and the options
const GSTask = require("grunt-and-sniff");
const gs = new GSTask(grunt, SOURCE_DIR,
{
	// This will also copy the files one per
	// one in the given destination directory.
	// Using the contrib-copy will not interpret
	// the insert and insertOnce statements
	// and the variables passed to the files
	// by grunt-and-sniff will not be defined.
	copyDest: DEST_FULL_DIR,

	// Indeed, grunt-and-sniff takes care of the
	// inclusions, so the separator, the headers
	// and the footers must be defined in the GSTask.

	// Separates the files
	// ("\n" is indeed the default value)
	separator: "\n",

	// The header and the footer are interpreted
	// with the grunt template in the same way as
	// the files, so <%=path%> will be 
	// replaced with the file path.
	header: "/* [---o---[ [FILE] <%=path%> ]---o---] */\n\n",
	footer: "\n\n/* [---x---[ [END FILE] <%=path%> ]---x---] */"
});

// Configures grunt
grunt.initConfig(
{
	// grunt-and-sniff works with the contrib-concat plugin
	concat:
	{
		options:
		{
			// This is what you need to do to use
			// grunt-and-sniff: GSTask provides
			// a file processor.
			process: gs.process
		},
		files:
		{
			src: SOURCE_DIR + "/**",
			dest: DEST
		}
	}
});

The process method

This method must be used as the process function in the grunt configuration file. It should not have any other usage, but here are the arguments:

process(source, filePath): String process(filePath): String

| Name | Type | Description | | --- | --- | --- | | source | String | The content of the file. | | filePath | String | The path of the file. |

The options

The grunt_and_sniff module offers many options in a GSOption object:

Surrounders (separator, header and footer)

Because grunt-and-sniff takes care of the inclusions, the separator, the header and the footer must be defined in the GSTask and not in the contrib-concat plugin.

The header and the footer are interpreted with the grunt template in the same way as the files. For example <%=path%> is replaced with the file path.

  • separator: String [optional="\n"]

    Added between two files.

  • header: String|Function [optional=""]

    Either a grunt template String or a callback (function(data:FileData): String) that returns a String. The result will be added at the begining of the file, after the before inclusions.

  • footer: String|Function [optional=""]

    The same as the header, but the result is copied at the end of the file, before the after and later inclusions.

  • insertSurrounder: String [optional=""]

    A "shf" case-insensitive flags for separator, header and footer that specify which type of surrounder are used for inserted files. By default, no surrounder is used for inserted files.

The "use strict"; statement

  • removeSourceUseStrict: Boolean [optional=true]

    Removes the "use strict" statement at the begining of the source files. It is recommended to leave this option as true because it is unatural to have "use strict" at the begining of each original file in the final concatened version. If a copyDest is supplied, removeSourceUseStrict does not affect the copied file because it does not make any sens, it only´ affects the concatenated file.

  • forceDestUseStrict: String|String[]|Boolean [optional=[".js"]]

    If true, or the extension of the file (case insensitive) matches the extension/one of the extensions in forceDestUseStrict: adds "use strict"; at the begining of the destination file: the concatenated file, and the copied files if a copyDest is supplied. It is not the opposite of the removeUseStrict option because removeUseStrict removes "use strict"; at the begining of the source files, and forceDestUseStrict adds it at the begining of the destination files.

Require

  • replaceAllRequires: Boolean [optional=true]

    If it is set to true, the includer will replace all require statement by considering than those who does not start with "insert:" or "include:" are single inclusions.

Format

  • trimmed: Boolean [optional=true]

    Removes the heading and leading spaces for each file before it is added in the concatenated file.

Copying

  • copyDest: String [optional=""]

    The directory where to copy the computed file. This is an additional feature to make a copy of a file by executing all the templates in an order that allows files to declares properties in $ that can be used in the parent files.

Process

  • verbose: integer [optional=1]

    1. Does not log anything but the errors in the console.
    2. Logs the necessary information (default)
    3. Logs more details about the process
  • $: object [optional={}]

    The object passed to every templates of the files.

Process callbacks

The process callback functions all takes the same argument and they must return a string. The source argument is a bit different for each processor because this is the result of different steps.

function(	src: String,
			data: GSFileData,
			fileOptions: GSOptions): String

Callback arguments:

  • source: String - The content of the file.
  • data: GSFileData - The data of the file (see the chapter).
  • options: GSOptions - The options described in this chapter.

Callback return: String - The callback must return the new source of the file.

  • preprocess: Function [optional=null]

    A callback that handles the content of a source file before the header's require statement replacements and returns the result that must be writen in the destination file.

  • process: Function [optional=null]

    A callback that handles the content of a source file after the header's require statement replacements and returns the result that must be writen in the destination file.

    If this callback is not set, grunt-and-sniff will nevertheless proceed the grunt template.

  • postprocess: Function [optional=null]

    A callback that handles the content of a source file after the header's require statement replacements and even after the header and footer was added, and then returns the result that must be writen in the destination file.

  • copyprocess: Function [optional=null]

    A callback that treats the content of a source file before it is copied if a copyDest option was supplied. If the "use strict"; statement must be added because the forceDestUseStrict option is set to true, it is added on the source sent to the callback.

The Inclusions

grunt-and-sniff provides five different types of inclusion that are detailed in the specific sub-chapter: before, after, later, insert and insertOnce.

For each type, there is a function in the grunt template and the corresponding require prefix.

Including using the grunt template

<%=include(	"./MyVarClass.class.js",
			{option: "something"})%>

const theVar = new MyVarClass();
function makeSuperVar()
{
	<%=const comment = {};
	%><%=insert("./theVarComment.insert.js",
			  // args: the arguments are passed into an object
			  {varName: "apple" /* Available in  "./theVarComment.insert.js" from args.apple */},
			  // Pushes the output in comment
			  comment)%>
	return new Super(theVar);
}

<%=includeAfter("./extendTheX.js")%>
<%=includeLater("./Super.class.js")%>

This kind of inclusion allows to pass some arguments to the included template: see the insert of "./theVarComment.insert.js" in the example.

The included template provides the passed arguments in the args variable and receives the output in the output variable (only accessible in insert, insertOnce and before inclusions, though available in every included templates).

Including using the require statements

require("./MyVarClass.class.js");

const theVar = new MyVarClass();
function makeSuperVar()
{
	require("insert:./theVarComment.insert.js");
	return new Super(theVar);
}

require("after:./extendTheX.js");
require("later:./Super.class.js");
// Or 
// require("./Super.class.js");

If the replaceAllRequires option is set to true (default), it is possible to use require without any prefix, it will be replaced either by include or by includeLater depending on the position of the statement.

This method does not allow arguments.

The different inclusion types

Generalities

There are two main ways to include files with grunt-and-sniff:

  1. The Gruntfile.js handles the inclusion of all required files: the files required in the body of the file are included before by using require("<path>") in the header.
  2. The Gruntfile.js only includes a few files, and the links between files must be done by the inclusions. It can be a solution to allow a project to be split into sub-modules: the files that are required in the body are included before by using require("<path>") in the header, and the files that are required inside the functions/methods are included later (i.e., after the root in the inclusion tree) by using require("<path>") in the footer.

Inclusion types

| Type | Syntax | Description | | --- | --- | --- | | before | <%=include("<path>")%> orrequire("<path>") orrequire("before:<path>")at the begining of the file.|If the file <path> was never included, it is included in order before the current file. | | later | includeLater("<path>") orrequire("<path>")(after some code, preferably at the end of the file) orrequire("later:<path>")| If the file <path> was never included, it is included later in the process, after the root of the inclusion tree was included. In other words, when grunt-and-sniff find a later inclusion, it waits for every files that depend into the root file to be included. This is usefull when B requires the content of C inside a function/method. | | | | | | after | <%=includeAfter("<path>")%> orrequire("after:<path>")anywhere in the file. | If the file <path> was never included, it is included in order right after the current file. This is usefull when C extends the content of B and is required immediately when B is required. | | insert | <%=insert("<path>")%> orrequire("insert:<path>")anywhere in the file. | Inserts the file at the position of the statement in the parent file, even if the file was already included. The inserted files are not copied to the copyDest folder. | | insertOnce | <%=insertOnce("<path>")%> orrequire("insertOnce:<path>")anywhere in the file. | Inserts the file at the position of the statement in the parent file, only if the file was never included. The inserted files are not copied to the copyDest folder. |

Examples

In the examples, an arrow between two files A and B means that A includes B: (A before→ B).

| Before | | --- | | A before→ B before→ C = C B A |

| Later | | --- | | A before→ B later→ C = B A C |

| After | | --- | | A before→ B after→ C = B C A |

| Insert | | --- | | A = var i = 0; require("insert:B") var k = 0;B = var j = 1;Result = var i = 0; var j = 1; var k = 0; |

| Insert Once | | --- | | A = var i = 0; require("insertOnce:B") var k = 0;B = var j = 1;Result (B was never included) = var i = 0; var j = 1; var k = 0;Result (B was included) = var i = 0; var k = 0; |

The inclusion statements file positions

The before inclusions that are found in the files after the begining of the file are converted to some later inclusions.

Advanced Tasks

The grunt-and-sniff module offers a task system that adds some features to your project. To use the task system, simply register a task of grunt-and-sniff into grunt by using the GSTask#task (or GSTask#registerTask) method:

			// The *grunt-and-sniff* task name
gstask.task("exportMap",
			// The options passed to the task
		   {dest: PATH.DEST + "/test.filelist"})
grunt.registerTask("test", ["concat", "exportMap"]);

Each grunt-and-sniff task takes specific options.

Task: exportMap

The exportMap task creates a text file with all the included files exported by grunt-and-sniff in order.

The options are:

  • [dest]: The path of the destination file. By default it is the copyDest option of the grunt-and-sniff with the name of the package and the .filelist extension: <copyDest> "/" <pkgName> ".filelist"
  • [dir = copyDest]: The path of the directory where the compiled files are exported. By default, it uses the copyDest option of the grunt-and-sniff GSTask object.

Template

Every file that are processed with grunt-and-sniff can access to the file data in a GSFileData object. This provides a lot of information and also the inclusion functions.

GSFileData

This object provides usefull information passed in the templates during the process, including the state of the file that is included/processed.

| Name | Type | Description | | --- | --- | --- | Paths | passedPath | String | The file path as passed to the require statement | | path | String | The file path relative to the source directory path | | cwdPath | String | The file path relative to the project root | | absPath | String | The absolute file path | | dir | String | The directory path relative to the project root | | dirPath | String | The full directory path | | sourceDir | String | The source directory of the project | File Info | parent | String/null | The path of the parent of the dependency, relative to the source directory path | | position | String | The position of the file, i.e. the include type: "before", "insert", "insertOnce", "after" or "later". | | inserted | uint | The number of files already included | Configuration | pkg | Object | The node.js project package | | config | Object | The grunt config | | options | GSOptions | The grunt-and-sniff options | Code Help | $ | Object | An object passed to every files to write common functions and data | | debug | Object | The debug function writes the message in the console | | tplOpen | String | The opening of a template ("<%") | Inclusions | include(path: string[,args: object[,output: object]]) | Function | Includes a file before the current one | | includeAfter(path: string[,args: object]) | Function | Includes a file right after the current one | | includeLater(path: string[,args: object]) | Function | Includes a file after the root file was written | | insert(path: string[,args: object[,output: object]]) | Function | Inserts a file a the given position | | insertOnce(path: string[,args: object[,output: object]]) | Function | Inserts a file a the given position only if it was never inserted/included | Functions | tplContext*(cb: Function[,args: object]) | Function | Return the result of the inline template. |

* Example of usage of tplContext:

$.createFunction = (name, args, code) =>
{
	return tplContext(() =>
	{
%>
function <%=args.name%>(<%=args.args%>)
{
<%=args.code%>
}
<%
	}, {name: name, args: args, code: code});
};

Grunt and Sniff Tasks

Provided tasks

| Name | Description | Options | | --- | --- | --- | | exportMap | Exports a text file with all the file paths in the order of inclusions. | String dest="<copyDest>/<package-name>.filelist": The destination file path.String dir="<copyDest>": The path used as the root of all files. By default, it is the copyDest folder. | | dependencyReport | Creates a dependency report with many usefull information, the ordered file list, the inclusion tree and the missing files. | String dest: The destination file path. By default, it is the <package-name>-dependency-report.html file in the copyDest folder.Object info={}: Additional information to add to the header of the report.Boolean ordered=true: Specifies whether or not the ordered file list (without insertions) is added to the report.Boolean tree=true: Specifies whether or not the inclusion tree (with insertions) is added to the report.Boolean missing=true: Specifies whether or not the missing files are added to the report. |

Use tasks

Use the task(name: string, taskOptions: object) function of the GSTask instance to register the task with grunt.registerTask.

// Register the default task with the concat task
// of grunt (required by grunt-and-sniff)
// and the exportMap and dependencyReport
// tasks of grunt-and-sniff.
grunt.registerTask("default", 
				[ "concat",
				// gs is a GSTask instance
				gs.task("exportMap",
				{
					dest: PATH.DEST + "/test.filelist"
				}),
				gs.task("dependencyReport",
				{
					dest: PATH.DEST + "/test-report.html"
				})]);

Create a task

Use the GSTask.addTask to create a new task with the following options:

  • String name: the name of the task in the form [a-z][a-zA-Z0-9]*.
  • String description: the description of the task.
  • Function task: The task function. The this keyword refers to this added task, so that it is possible to add additional functions to the task. Here are the arguments:
    • GSTask gsTask
    • GSMap map
    • Object options: the options passed when the task is registered with the task(name: string, taskOptions: object) function of the GSTask instance.
GSTask.addTask(
{
	name: "info",

	description: "Prints info in the console.",

	task: (	gsTask/*:GSTask*/,
			map/*:GSMap*/,
			options/*:Object*/) =>
	{
		const pkg  = gsTask.grunt.config.get("pkg");
		this.log("Project", pkg.name);
		this.log("Version", pkg.version);
		this.log("Copy Dest", gsTask.options.copyDest);
	},

	// It is allowed to set additional functions
	log: function(name, value)
	{
		console.log("\x1b[32m%s\x1b[0m: %s", name, value);
	}
});