Фронтальные инструменты для сборки и рабочих процессов доступны в изобилии: Grunt , Gulp , Broccoli и Jake и многие другие. Эти инструменты могут автоматизировать практически все, что вы неоднократно делаете в проекте, от минимизации и объединения исходных файлов до запуска тестов или компиляции кода. Но вопрос в том, нужны ли они вам? Вы действительно хотите ввести дополнительную зависимость в ваш проект? Ответ — нет!». Существует бесплатная альтернатива, которая может выполнить большинство этих задач для вас, и она поставляется в комплекте с Node.js. Конечно я говорю о нпм .
В этой статье мы обсудим, на что способен npm как инструмент сборки. Если вы хотите быстро ознакомиться с npm перед началом работы, обратитесь к руководству для начинающих по npm . Если вы хотите следовать, вы можете найти код, используемый в этой статье на GitHub .
Сценарии npm
Чтобы начать наше обсуждение, мы собираемся создать каталог для нашего нового демонстрационного проекта, который мы назовем «buildtool». После этого мы переместимся в эту папку и затем запустим команду npm init
для создания файла package.json
:
$ mkdir ~/buildtool && cd ~/buildtool $ npm init
Вам будет задано несколько вопросов. Не стесняйтесь пропустить все или часть из них, поскольку вы замените окончательное содержимое файла package.json
следующим содержимым:
{ " 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
со property
info
. Значение info
будет выполнено в оболочке как команда. Мы можем увидеть список свойств scripts
(также называемых командами ) и значений, определенных в проекте, выполнив команду:
$ npm run
Если вы выполните предыдущую команду в нашей папке проекта, вы должны увидеть следующий результат:
Scripts available in buildtool via `npm run-script`: info echo 'npm as a build tool'
Если вы хотите запустить определенное свойство, вы можете запустить команду:
$ npm run <property>
Итак, чтобы запустить команду info
мы определили в файле package.json
, нам нужно написать:
$ npm run info
Он выдаст следующий вывод:
$ npm run info > buildtool@ 1.0 . 0 info /home/sitepoint/buildtool > echo 'npm as a build tool' npm as a build tool
Если вам нужен только вывод info
, вы можете использовать флаг -s
который отключает вывод из npm :
$ npm run info -s npm as a build tool
Пока мы использовали только простое echo
, но это очень мощная функция. Все в командной строке доступно нам, и мы можем быть очень креативными здесь. Итак, давайте основываемся на том, что мы уже рассмотрели, и установим несколько packages
чтобы создать некоторые общие рабочие процессы.
Общие рабочие процессы
Первое, что мы хотели бы реализовать, — это возможность линтинга для наших файлов JavaScript. Это включает в себя запуск программы, которая проанализирует наш код на предмет возможных ошибок. Для этого мы будем использовать JSHint , поэтому первым шагом является установка пакета через npm:
$ npm install jshint --save-dev
После выполнения этой команды вы увидите новую подпапку с именем node_modules
. Это где JSHint был загружен. Кроме того, нам также необходимо создать следующую структуру папок для нашего проекта:
├── assets │ ├── css │ │ └── main .css │ └── scripts │ └── main .js ├── dist ├── package .json ├── node_modules └── test └── test .js
В системе Unix это можно сделать с помощью следующей команды:
$ mkdir -p assets/css assets/scripts test && touch assets/css/main.css assets/scripts/main.js test/test.js
пыление
Теперь мы main.js
некоторые синтаксические ошибки в файле main.js
На данный момент файл пуст, поэтому откройте его и вставьте следующее содержимое:
"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
. Мы также прикрепляем несколько методов к свойству prototype
Author
что позволит нам хранить и перечислять статьи, которые автор написал. Обратите внимание на оператор exports
который сделает наш код доступным вне модуля, в котором он определен. Если вы хотите узнать больше об этом, обязательно прочитайте: Понимание module.exports и export в Node.js.
Затем мы должны добавить property
к нашему объекту scripts
в package.json
которое будет запускать jshint
. Для этого мы создадим свойство lint
следующим образом:
"scripts" : { " info ": "echo 'npm as a build tool'" , " lint ": "echo '=> linting' && jshint assets/scripts/*.js" }
Здесь мы используем оператор &&
для объединения команд и файловых глобусов (звездочка), которые обрабатываются как подстановочный знак, в этом случае сопоставляя любой файл с .js
в каталоге script
.
Примечание : командная строка Windows не поддерживает globs, но когда ей передается аргумент командной строки, такой как *.js
, Windows передает его дословно вызывающему приложению. Это означает, что поставщики могут устанавливать библиотеки совместимости для обеспечения функциональности, подобной глобальному Windows. Для этого JSHint использует библиотеку minimatch .
Теперь давайте скопируем код:
npm run lint -s
Это дает следующий вывод:
=> 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
Оно работает. Давайте исправим эти ошибки, перезапустим линтер, чтобы убедиться, а затем перейдем к тестированию:
( 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(); })();
Обратите внимание, как мы завернули все в немедленно вызванное выражение функции .
npm run lint -s => linting
Нет ошибок Были хороши!
тестирование
Для начала нам нужно установить пакет mocha . Mocha — это простая, но гибкая среда тестирования JavaScript для Node.js и браузера. Если вы хотите больше узнать об этом, эта статья — отличное место для начала: базовое фронтальное тестирование с Mocha & Chai
npm install mocha --save-dev
Далее мы собираемся создать несколько простых тестов для проверки методов, которые мы написали ранее. Откройте test.js
и добавьте следующий контент (обратите внимание на оператор require
который делает наш код доступным для 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()); }); }); });
Теперь давайте добавим test
задание в package.json
:
"scripts": { "info": "echo 'npm as a build tool'", "lint": "echo '=> linting' && jshint assets/scripts/*.js", "test": "echo '=> testing' && mocha test/" }
У npm есть несколько удобных ярлыков, а именно: npm test
npm start
npm stop
. Это псевдонимы для их эквивалентов выполнения, а это значит, что нам просто нужно запустить npm test
чтобы запустить npm test
в действие:
$ npm test -s => testing Author constructor ✓ should have a default name #writeArticle ✓ should store articles #listArticles ✓ should list articles 3 passing ( 5 ms)
До и после крючки
Это не было бы очень эффективно, если бы мы запустили наш набор тестов, и он сразу же вышел из-за синтаксической ошибки. К счастью, npm предоставляет нам pre
и post
хуки, поэтому, если вы запустите npm run test
он сначала выполнит npm run pretest
и npm run posttest
после его завершения. В этом случае мы хотим запустить скрипт lint
перед test
скриптом. Следующий сценарий pretest
делает это возможным.
"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" }
Представьте, что мы ранее не исправляли синтаксические ошибки в нашем скрипте. В этом случае приведенный выше сценарий pretest
test
завершится неудачно с ненулевым кодом завершения, и test
сценарий не запустится. Это именно то поведение, которое мы хотим.
$ npm test -s => linting assets/scripts/main.js: line 1 , col 1 , Use the function form of "use strict" . ... 6 errors
С исправленным кодом в main.js
:
=> linting => testing Author constructor ✓ should have a default name #writeArticle ✓ should store articles #listArticles ✓ should list articles 3 passing ( 6 ms)
Мы в зеленом!
Минификация кода
Для этого раздела нам нужно добавить каталог dist
в наш проект, а также несколько подкаталогов и файлов. Вот как выглядит структура папок:
├── dist │ └── public │ ├── css │ ├── index .html │ └── js
Команда для воссоздания этого на Unix-машине:
mkdir -p dist/public/css dist/public/js && touch dist/public/index.html
Содержимое index.html
просто.
<!DOCTYPE html> < 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 >
В настоящее время main.js
не минимизирован. Так и должно быть, потому что это файл, с которым мы работаем, и мы должны иметь возможность прочитать его. Однако, прежде чем загрузить его на работающий сервер, нам нужно уменьшить его размер и поместить его в каталог dist/public/js
. Для этого мы можем установить пакет uglify-js и создать новый скрипт.
$ npm install uglify-js --save-dev
Теперь мы можем сделать новый скрипт minify:js
в 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
И скрипт создает минимизированную версию нашего файла в нужном месте назначения. Мы сделаем то же самое для нашего CSS-файла, используя пакет clean-css .
$ npm install clean-css --save-dev
И создайте сценарий minify:css
.
"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" }
Давайте run
скрипт.
$ npm run minify:css -s => minify:css
В ожидании перемен
Grunt, Gulp и их коллеги отлично умеют наблюдать за набором файлов и повторно запускать определенную задачу, когда обнаруживается, что один из этих файлов изменился. Это особенно полезно в таких случаях, как это, поскольку было бы неудобно повторно запускать сценарии минификации вручную.
Хорошей новостью является то, что вы можете сделать это и в npm, используя такой пакет, как watch , который призван упростить управление просмотром файлов и деревьев каталогов.
$ npm install watch --save-dev
Затем в файле package.json необходимо указать задачи, которые будут выполняться при обнаружении изменения. В этом случае минимизация JavaScript и CSS:
"scripts" : { ... " watch ": "watch 'npm run minify:js && npm run minify:css' assets/scripts/ assets/css/" }
Запустите скрипт, используя:
$ npm run watch
Теперь, когда любой файл в assets/scripts/
или assets/css/
изменяется, скрипты минификации будут вызываться автоматически.
Сценарий сборки
К настоящему времени у нас есть несколько сценариев, которые мы можем объединить в цепочку для build
сценария build
который должен выполнять следующее: linting, тестирование и минимизация В конце концов, было бы больно каждый раз запускать эти задачи по отдельности. Чтобы создать этот скрипт сборки, измените объект скрипта в 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/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
дает нам следующий результат.
$ npm run build -s => building => linting => testing Author constructor ✓ should have a default name #writeArticle ✓ should store articles #listArticles ✓ should list articles 3 passing ( 6 ms) => minify:js => minify:css
Серверный скрипт
После того, как мы запустим наш скрипт build
было бы хорошо, если бы мы могли запустить сервер для нашего контента в dist
и проверить его в браузере. Мы можем сделать это с помощью пакета http-сервера .
$ npm install http-server -save-dev
Мы делаем server
скрипт.
"scripts" : { ... " server ": "http-server dist/public/" , }
И теперь мы можем run
наш сервер.
$ 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
скрипт может быть добавлен в скрипт build
, но я оставляю это как упражнение для читателя.
Вывод
Надеюсь, эта статья продемонстрировала, насколько гибким и мощным может быть npm в качестве инструмента для сборки. В следующий раз, когда вы начинаете новый проект, постарайтесь не связываться напрямую с такими инструментами, как Gulp или Grunt — попробуйте решить ваши потребности, используя только npm. Вы можете быть приятно удивлены.
Если у вас есть какие-либо вопросы или комментарии, я был бы рад услышать их в ветке ниже.