on
Italy
- Get link
- X
- Other Apps
npm init to create a package.json file:$ mkdir ~/buildtool && cd ~/buildtool
$ npm init
package.json file with the following content:{
"name": "buildtool",
"version": "1.0.0",
"description": "npm as a build tool",
"dependencies": {},
"devDependencies": {},
"scripts":
{
"info":
"echo 'npm as a build tool'"
},
"author": "SitePoint",
"license": "ISC"
}
scripts object with a property called info. The value of info is going to be executed in the shell as a command. We can see a list of the scripts properties (also known as commands) and values defined in a project by running the command:$ npm run
Scripts available in buildtool via `npm run-script`:
info
echo 'npm as a build tool'
In case you want to run a specific property, you can run the command:$ npm run
info command we defined in the package.json file, we have to write:$ npm run info
It’ll produce the following output:$ npm run info
> buildtool@1.0.0 info /home/sitepoint/buildtool
> echo 'npm as a build tool'
npm as a build tool
If you only want the output of info, you can use the -s flag which silences output from npm:$ npm run info -s
npm as a build tool
echo so far, but this is a
very powerful feature. Everything on the command line is available to
us and we can be very creative here. So let’s build on what we’ve
covered up to this point and install some packages to create some common workflows.$ npm install jshint --save-dev
node_modules. This is where JSHint has been downloaded. In addition, we also need to create the following folder structure for our project:├── assets
│ ├── css
│ │ └── main.css
│ └── scripts
│ └── main.js
├── dist
├── package.json
├── node_modules
└── test
└── test.js
On a Unix system, this can be done with the following command: $ mkdir -p assets/css assets/scripts test && touch assets/css/main.css assets/scripts/main.js test/test.js
main.js file. At the moment the file is empty, so open it and paste in the following content:"use strict";
var Author = new function(name){
this.name = name || "Anonymous";
this.articles = new Array();
}
Author.prototype.writeArticle = function(title){
this.articles.push(title);
};
Author.prototype.listArticles = function(){
return this.name + " has written: " + this.articles.join(", ");
};
exports.Author = Author;
var peter = new Author("Peter");
peter.writeArticle("A Beginners Guide to npm");
peter.writeArticle("Using npm as a build tool");
peter.listArticles();
Author objects. We also attach a couple of methods to Author’s prototype property which will allow us to store and list the articles an author has written. Notice the exports
statement which will make our code available outside of the module in
which it is defined. If you’re interested in finding out more about
this, be sure to read: Understanding module.exports and exports in Node.js.property to our scripts object in package.json that will trigger jshint. To do that, we’ll create a lint property as follows:"scripts": {
"info": "echo 'npm as a build tool'",
"lint":
"echo '=> linting' && jshint assets/scripts/*.js"
}
Here we’re taking advantage of the && operator
to chain the commands and file globs (the asterisk) which gets treated
as a wildcard, in this case matching any file with a .js ending within the script directory. *.js,
Windows passes it verbatim to the calling application. This means that
vendors can install compatibility libraries to give Windows glob like
functionality. JSHint uses the minimatch library for this purpose.npm run lint -s
This produces the following output:=> linting
assets/scripts/main.js: line 1, col 1, Use the function form of "use strict".
assets/scripts/main.js: line 5, col 28, The array literal notation [] is preferable.
assets/scripts/main.js: line 3, col 14, Weird construction. Is 'new' necessary?
assets/scripts/main.js: line 6, col 1, Missing '()' invoking a constructor.
assets/scripts/main.js: line 6, col 2, Missing semicolon.
assets/scripts/main.js: line 16, col 1, 'exports' is not defined.
6 errors
It works. Let’s clean up those errors, re-run the linter to make sure, then move on to some testing:(function(){
"use strict";
var Author = function(name){
this.name = name || "Anonymous";
this.articles = [];
};
Author.prototype.writeArticle = function(title){
this.articles.push(title);
};
Author.prototype.listArticles = function(){
return this.name + " has written: " + this.articles.join(", ");
};
exports.Author = Author;
var peter = new Author("Peter");
peter.writeArticle("A Beginners Guide to npm");
peter.writeArticle("Using npm as a build tool");
peter.listArticles();
})();
Notice how we have wrapped everything in an immediately invoked function expression.npm run lint -s
=> linting
No errors. We’re good!npm install mocha --save-dev
Next we are going to create some simple tests to test the methods we wrote previously. Open up test.js and add the following content (notice the require statement which makes our code available to mocha):var assert = require("assert");
var Author = require("../assets/scripts/main.js").Author;
describe("Author", function(){
describe("constructor", function(){
it("should have a default name", function(){
var author = new Author();
assert.equal("Anonymous", author.name);
});
});
describe("#writeArticle", function(){
it("should store articles", function(){
var author = new Author();
assert.equal(0, author.articles.length);
author.writeArticle("test article");
assert.equal(1, author.articles.length);
});
});
describe("#listArticles", function(){
it("should list articles", function(){
var author = new Author("Jim");
author.writeArticle("a great article");
assert.equal(
"Jim has written: a great article"
, author.listArticles());
});
});
});
Now let’s add a test task to package.json:"scripts": {
"info": "echo 'npm as a build tool'",
"lint": "echo '=> linting' && jshint assets/scripts/*.js",
"test": "echo '=> testing' && mocha test/"
}
npm test, npm start and npm stop. These are are all aliases for their run equivalents, which means we just need to run npm test to kick mocha into action:$ npm test -s
=> testing
Author
constructor
✓ should have a default name
✓ should store articles
✓ should list articles
3 passing (5ms)
pre and post hooks, so if you run npm run test it will first execute npm run pretest and npm run posttest when it finishes. In this case we want to run the lint script before the test script. The following pretest script makes this possible."scripts": {
"info": "echo 'npm as a build tool'",
"lint":
"echo '=> linting' && jshint assets/scripts/*.js"
,
"test":
"echo '=> testing' && mocha test/"
,
"pretest": "npm run lint -s"
}
Imagine we hadn’t have corrected the syntax errors in our script previously. In this case, the above pretest script will fail with a non-zero exit code and the test script won’t run. That is exactly the behavior we want.$ npm test -s
=> linting
assets/scripts/main.js: line 1, col 1, Use the function form of "use strict".
...
6 errors
With the corrected code in main.js:=> linting
=> testing
Author
constructor
✓ should have a default name
✓ should store articles
✓ should list articles
3 passing (6ms)
We are in the green !dist directory to our project, as well as several sub directories and files. This is how the folder structure looks: ├── dist
│ └── public
│ ├── css
│ ├── index.html
│ └── js
The command to recreate this on a Unix machine is:mkdir -p dist/public/css dist/public/js && touch dist/public/index.html
The contents of index.html is simple.
<html>
<head>
<meta charset="utf-8" />
<title>npm as a build tool</title>
<link href='css/main.min.css' rel='stylesheet'>
</head>
<body>
<h2>npm as a build tool</h2>
<script src='js/main.min.js'></script>
</body>
</html>
Currently main.js is not minified. This is as it should
be, because it is the file we are working in and we need to be able to
read it. However, before we upload it to the live server, we need to
reduce its size and place it in the dist/public/js directory. To do this we can install the uglify-js package and make a new script.$ npm install uglify-js --save-dev
We can now make a new minify:js script in package.json:"scripts": {
"info": "echo 'npm as a build tool'",
"lint":
"echo '=> linting' && jshint assets/scripts/*.js"
,
"test":
"echo '=> testing' && mocha test/"
,
"minify:js":
"echo '=> minify:js' && uglifyjs assets/scripts/main.js -o dist/public/js/main.min.js"
,
"pretest": "npm run lint -s"
}
$ npm run minify:js -s
=> minify:js
And the script creates a minified version of our file in the right destination. We will do the same for our CSS file using the clean-css package.$ npm install clean-css --save-dev
And create the minify:css script."scripts": {
"info": "echo 'npm as a build tool'",
"lint":
"echo '=> linting' && jshint assets/scripts/*.js"
,
"test":
"echo '=> testing' && mocha test/"
,
"minify:js":
"echo '=> minify:js' && uglifyjs assets/scripts/main.js -o dist/public/js/main.min.js"
,
"minify:css":
"echo '=> minify:css' && cleancss assets/css/main.css -o dist/public/css/main.min.css"
,
"pretest": "npm run lint -s"
}
Let’s run the script.$ npm run minify:css -s
=> minify:css
$ npm install watch --save-dev
Then in package.json, you need to specify the tasks to run when a
change is detected. In this case JavaScript and CSS minification:"scripts": {
...
"watch":
"watch 'npm run minify:js && npm run minify:css' assets/scripts/ assets/css/"
}
Start the script using:$ npm run watch
Now, whenever any file in assets/scripts/ or assets/css/ changes, the minification scripts will be called automatically.build
script which should do the following: linting, testing and minifying.
It would, after all, be a pain to have to run these tasks individually
time after time. To create this build script, alter the script object in
package.json, thus:"scripts": {
"info": "echo 'npm as a build tool'",
"lint":
"echo '=> linting' && jshint assets/scripts/*.js"
,
"test":
"echo '=> testing' && mocha test/"
,
"minify:js":
"echo '=> minify:js' && uglifyjs assets/scripts/main.js -o dist/public/js/jquery.min.js"
,
"minify:css":
"echo '=> minify:css' && cleancss assets/css/main.css -o dist/public/css/main.min.css"
,
"build":
"echo '=> building' && npm run test -s && npm run minify:js -s && npm run minify:css -s"
,
"pretest": "npm run lint -s"
}
build script gives us the following output.$ npm run build -s
=> building
=> linting
=> testing
Author
constructor
✓ should have a default name
✓ should store articles
✓ should list articles
3 passing (6ms)
=> minify:js
=> minify:css
build script it would be nice if we could start a server for our content in dist and check it in the browser. We can do this using the http-server package.$ npm install http-server -save-dev
server script."scripts": {
...
"server": "http-server dist/public/",
}
And now we can run our server.$ npm run server
Starting up http-server, serving dist/public/ on: http://0.0.0.0:8080
Hit CTRL-C to stop the server
_
server script can be added to the build script, but I leave that as an exercise for the reader.
Comments
Post a Comment