Разработка

Описание современного JavaScript для динозавров

Natalia Bass 07 ноября 2017

Это перевод статьи Питера Янга Modern JavaScript Explained For Dinosaurs. Она познакомит вас с инфраструктурой современной фронтэнд-разработки.

Наш курс JS: Настройка окружения выполняет схожую задачу, но чуть глубже, последовательно и без привязки только к фронтэнду. Этим темам также посвящена значительная часть первого проекта в JS Backend и JS Frontend. Там мы на деле устанавливаем и настраиваем среду для разработки, проверки, публикации, сборки и запуска своих приложений.


Изучать современный JavaScript — болезненно, если вы не знакомы с ним с самого его рождения. Экосистема разрастается и меняется с такой скоростью, что сложно разобраться с тем, какие проблемы пытаются решить разные инструменты. Я начал программировать в 1998 году, но к серьёзному изучению JavaScript приступил только в 2014. В то время я помню как анализировал Browserify и изумлённо смотрел на его слоган:

> Browserify позволяет запрашивать (require) модули в браузере, объединяя все зависимости.

Можно сказать, я не понимал ни слова в этом предложении, и с трудом осознавал, насколько это может быть полезно мне, как разработчику.

Цель этой статьи — показать исторический контекст развития инструментов JavaScript до их уровня в 2017. Начнём с самых первых моментов и построим шаблон веб-сайта, как бы это сделали динозавры — без инструментов, чистый HTML и JavaScript. Затем мы будем пошагово вводить различные инструменты, чтобы на практике видеть, какие задачи они решают — поочерёдно. Благодаря историческому контексту у вас будет больше возможностей изучить и лучше адаптироваться к бесконечно меняющемуся JavaScript. Давайте начнём!

Использование JavaScript старомодным способом

Давайте начнём со старомодного веб-сайта, написанного на HTML и JavaScript, что включает загрузку и помещение ссылок на файлы вручную. Вот простой index.html файл, который ссылается на JavaScript файл:






  JavaScript Example



  Hello from HTML!


Строчка `ссылается на отдельный JavaScript-файл с названиемindex.js` в той же директории:

// index.js
console.log("Hello from JavaScript!");

Это всё, что вам нужно, чтобы сделать веб-сайт!

Теперь давайте предположим, что вы хотите добавить библиотеку, которую написал кто-то другой, вроде moment.js (библиотеку, которая помогает превращать даты в понятные для чтения человеком). Например, вы можете использовать функцию moment в JavaScript вот так:

moment().startOf('day').fromNow();        // 20 hours ago

Но только при условии, что вы включите moment.js в код своего веб-сайта! На домашней странице moment.js вы найдёте такие инструкции:

img

Хмм, как много всего происходит в секции Install справа. Но давайте пока проигнорируем это — мы можем добавить moment.js на веб-сайт, загрузив файл moment.min.js в ту же директорию и включив его в файл index.html.






  Example





  Hello from HTML!


Заметьте, что moment.min.js загружается перед index.js, а это значит вы можете использовать функцию moment в index.js таким способом:

// index.js
console.log("Hello from JavaScript!");
console.log(moment().startOf('day').fromNow());

И вот таким образом мы раньше делали веб-сайты с JavaScript-библиотеками! Положительный момент в том, что всё было достаточно легко понимать. Отрицательный — было муторно искать и загружать новые версии библиотек каждый раз, когда они обновлялись.

Использование пакетного менеджера JavaScript (npm)

Начиная примерно с 2010 появилось несколько конкурирующих пакетных менеджеров JavaScript для автоматизации процесса загрузки и обновления библиотек из центрального репозитория. Bower был, возможно, самым популярным в 2013, но в итоге, примерно в 2015 его настиг npm. (Стоит отметить, что с конца 2016 yarn привлёк много внимания, как альтернатива интерфейсу npm, но он всё ещё использует npm пакеты).

Заметьте, что npm был изначально пакетным менеджером, созданным специально для node.js — среды исполнения JavaScript, предназначенной для запуска на сервере, а не во фронтенде. Что делает его очень странным выбором для фронтенд-пакетного менеджера JavaScript библиотек, предназначенных запускаться в браузере.

Заметьте: Использование пакетного менеджера обычно требует работы с командной строкой, что в прошлом никогда не требовалось от фронтенд-разработки. Если вы никогда ей не пользовались, можете прочитать этот туториал, чтобы составить представление о том, как начать. Так или иначе, умение пользоваться командной строкой — важная часть современного JavaScript (и ещё она открывает двери в другие области разработки).

На Хекслете как раз есть бесплатный курс Bash: Основы командной строки, — прим. ред.

Давайте взглянем как использовать npm, чтобы установить пакет moment.js автоматически, вместо того, чтобы загружать его вручную. Если у вас установлен node.js, у вас уже есть и npm, а это значит, что вы можете через командную строку перейти к папке, в которой находится файл index.html и ввести:

$ npm init

Будет выведено несколько вопросов (можно оставить умолчания: нажимать "Enter" после каждого вопроса) и сгенерируется новый файл с именем package.json. Это конфигурационный файл, который npm использует, чтобы хранить всю информацию о проекте. С настройками по-умолчанию содержимое package.json должно выглядеть подобно этому:

{
  "name": "your-project-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Чтобы установить JavaScript-пакет moment.js, мы теперь можем следовать инструкциям npm на их домашней странице, введя в командную строку:

$ npm install moment --save

Эта команда выполняет две задачи: в начале она загружает весь код из пакета moment.js в папку, называемую node_modules. Потом автоматически модифицирует файл package.json, чтобы мониторить moment.js как зависимость (зависимости — это пакеты и библиотеки, от которых зависит наше приложение).

{
  "name": "modern-javascript-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment": "^2.19.1"
  }
}

Это будет полезно в будущем, когда вместо расшаривания проекта с остальными — а именно папки node_modules (которая может сильно разрастись) — вам понадобится расшарить только файл package.json, и другие разработчики смогут установить необходимые пакеты автоматически с помощью команды npm install.

Теперь мы больше не должны загружать moment.js вручную с веб-сайта, потому что можем сделать это автоматически и обновить, используя npm. Заглянув внутрь папки node_modules можно увидеть файл moment.min.js в директории node_modules/moment/min. Это значит, мы можем сослаться на npm загружаемую версию moment.min.js в файле index.html, как видно ниже:






  JavaScript Example




  Hello from HTML!


Хорошие новости в том, что теперь мы можем использовать npm, чтобы загружать и обновлять пакеты с помощью командной строки. Плохие в том, что прямо сейчас мы обшариваем папку node_modules в поисках местоположения каждого пакета и вручную включаем их в HTML. Это достаточно неудобно, и следующее, что мы сделаем, это посмотрим как автоматизировать и этот процесс.

Использование модульного упаковщика JavaScript (webpack)

У большинства языков программирования есть способ импорта кода из одного файла в другой. JavaScript изначально не имел этой функции, потому что был создан только для работы в браузере, без доступа к файловой системе компьютера клиента (по соображениям безопасности). Поэтому очень долго организация кода JavaScript в нескольких файлах требовала загрузки каждого файла с глобальными переменными.

Это именно то, что мы делаем в приведенном выше примере с moment.js — весь moment.min.js файл загружается в HTML, что определяет глобальную переменную moment, которая становится доступна любому файлу, загруженному после moment (независимо от того, нужен ли к нему доступ).

В 2009 году был запущен проект CommonJS, определяющий экосистему для JavaScript за пределами браузера. Большая часть CommonJS была спецификацией для модулей и она, наконец, позволила JavaScript делать импорт и экспорт между файлами, как в большинстве языков программирования, без использования глобальных переменных. Самая известная реализация модулей CommonJS — это node.js.

node js

Как говорилось ранее, node.js — это среда выполнения JavaScript, предназначенная для запуска на сервере. Вот как выглядел более ранний пример с использованием модулей node.js. Вместо того, чтобы загружать все moment.min.js с помощью HTML script тега, вы можете загрузить его напрямую в файле JavaScript вот так:

// index.js
var moment = require('moment');
console.log("Hello from JavaScript!");
console.log(moment().startOf('day').fromNow());

Опять же, так работает загрузка модуля в node.js, и это здорово, поскольку node.js — это серверный язык с доступом к файловой системе компьютера. Node.js также знает расположение каждого пути модуля npm, поэтому вместо необходимости писать require('./node_modules/moment/min/moment.min.js), можно использовать require('moment') — чудно.

Всё это подходит для node.js, но если вы попытаетесь использовать написанный выше код в браузере, вы получите сообщение об ошибке, в котором будет указано require not defined (не определено). Браузер не имеет доступа к файловой системе, это значит, что загрузка модулей таким способом очень проблематична — загрузка файлов должна выполняться динамически, синхронно (что замедляет исполнение), или асинхронно (что может вызвать проблемы с синхронизацией).

Тут вступает в действие упаковщик модулей. Упаковщик модулей JavaScript — это инструмент, который решает задачу с помощью этапа сборки (он имеет доступ к файловой системе) для создания конечно результата, совместимого с браузером (уже не нужен доступ к файловой системе). В этом случае нам нужен упаковщик модулей для поиска всех утверждений require (а это недопустимый синтаксис JavaScript для браузера) и замены их актуальным содержимым каждого требуемого файла. Конечный результат — один бандл (упакованный файл) JavaScript (без инструкций require)!

Самым популярным упаковщиком модулей был Browserify, он вышел в 2011 году и первым использовал стиль node.js для require во фронтенде (что, по сути, помогло npm стать самым используемым менеджером пакетов для фронтенда). Примерно в 2015 году более популярным стал упаковщик модулей webpack (его подпитывала популярность фроненд-фреймворка React, который максимально пользовался различными функциями webpack).

Давайте посмотрим, как использовать webpack, чтобы заставить показанный выше пример с require('moment') работать в браузере. Сначала нам нужно установить webpack в проект. Сам webpack — это npm-пакет, поэтому мы можем установить его из командной строки:

$ npm install webpack --save-dev

Обратите внимание на аргумент --save-dev, он сохраняется как development-зависимость. Это означает, что это пакет вам нужен в среде разработки, но не на production-сервере. Это можно наглядно увидеть в файле package.json, который был автоматически обновлен:

{
  "name": "modern-javascript-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment": "^2.19.1"
  },
  "devDependencies": {
    "webpack": "^3.7.1"
  }
}

Теперь у нас есть webpack, установленный в папку node_modules, как один из пакетов. Вы можете вызывать webpack из командной строки:

$ ./node_modules/.bin/webpack index.js bundle.js

Эта команда запустит инструмент webpack, который был установлен в папке node_modules, начнет с файла index.js, найдет любые require утверждения и заменит их соответствующим кодом, чтобы создать один выходной файл с именем bundle.js.

Это значит, что мы больше не будем использовать index.js в браузере, так как в нем содержатся недопустимые require утверждения. Вместо этого мы будем использовать вывод bundle.js в браузере, что должно будет отразиться в файле index.html:






  JavaScript Example



  Hello from HTML!


Если вы обновите браузер, увидите, что всё работает, как работало раньше!

Обратите внимание, что нам нужно будет запускать команду webpack каждый раз, когда мы будем менять index.js. Это утомительно и станет ещё утомительней, когда мы станем использовать более сложные фичи webpack (например, генерацию source maps, чтобы помочь отлаживать исходный код из преобразованного кода). Webpack может считывать параметры из файла конфигурации в корневом каталоге проекта с именем webpack.config.js, который в нашем случае будет выглядеть так:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js'
  }
};

Теперь каждый раз, когда мы меняем index.js, мы можем запускать webpack с помощью команды:

$ ./node_modules/.bin/webpack

Нам больше не нужно указывать параметры index.js и bundle.js, так как webpack загружает эти параметры из файла webpack.config.js. Уже лучше, но все же утомительно вводить эту команду при каждом изменении кода — позже мы сделаем этот процесс более плавным.

В целом, может казаться, что тут нет ничего особенного, но в этой рабочей последовательности есть несколько огромных плюсов. Мы больше не загружаем внешние скрипты через глобальные переменные. Любые новые библиотеки JavaScript будут добавляться через require в JavaScript, в отличие от добавления новых тегов `` в HTML. Наличие одного бандла часто лучше для производительности. И теперь, когда мы добавили шаг сборки, мы можем добавить несколько мощных фич в процесс разработки!

Transpiling для новых фич языка (babel)

"Transpiling" кода — преобразование исходного кода на одном языке в исходный код на другом схожем языке. Это важный сегмент frontend-разработки: поскольку браузеры медленно добавляют новые фичи, были созданы новые языки с экспериментальными функциями, которые преобразуются (transpile) в совместимый с браузерами код.

Для CSS есть Sass, Less и Stylus. Для JavaScript самым популярным транспайлером какое-то время был CoffeeScript (вышел где-то в 2010 году), а в настоящее время большинство людей используют babel или TypeScript. CoffeeScript — это язык, ориентированный на улучшение JavaScript, через значительное его изменение — опциональные круглые скобки, отступы и т. д.

Babel — это не новый язык, а транспайлер, который преобразует JavaScript нового поколения с фичами, недоступными всем браузерам (ES2015 и выше) в старшую, более совместимую версию JavaScript (ES5). TypeScript — это язык, который практически идентичен JavaScript нового поколения, но в него добавлена опциональная статическая типизация. Многие люди предпочитают использовать babel, потому что он ближе всего к чистому JavaScript.

Давайте рассмотрим пример использования babel с нашим существующим этапом сборки webpack. Сначала мы устанавливаем babel (а это npm-пакет) в проект из командной строки:

$ npm install babel-core babel-preset-env babel-loader --save-dev

Обратите внимание, что мы устанавливаем 3 отдельных пакета как dev зависимости — babel-core — основная часть babel, babel-preset-env — пресет, определяющий, какие новые фичи JavaScript преобразовывать, а babel-loader — это пакет, позволяющий babel работать с webpack. Мы можем настроить webpack для использования babel-loader, отредактировав файл webpack.config.js, как показано ниже:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env']
          }
        }
      }
    ]
  }
};

Этот синтаксис может ломать мозг (к счастью, это не то, что мы будем часто редактировать). Мы просим webpack искать любые .js-файлы (исключая те, что в папке node_modules) и применять преобразование babel с помощью babel-loader через пресет babel-preset-env. Здесь вы можете узнать больше о конфигурационном синтаксисе webpack.

У нас есть пример полностью настроенного пакета со всеми описанными штуками — Hexlet Boilerplates: nodejs, — прим. ред.

Теперь, когда все настроено, мы можем начать писать фичи ES2015 в JavaScript! Ниже пример ES2015 template string в файле index.js:

// index.js
var moment = require('moment');

console.log("Hello from JavaScript!");
console.log(moment().startOf('day').fromNow());

var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

Мы также можем использовать импорты ES2015 вместо require для загрузки модулей. Это то, что вы сегодня увидите во многих базах кода:

// index.js
import moment from 'moment';

console.log("Hello from JavaScript!");
console.log(moment().startOf('day').fromNow());

var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

В этом примере синтаксис import не сильно отличается от синтаксиса require, но import имеет дополнительную гибкость для более сложных случаев. Поскольку мы изменили index.js, нам нужно снова запустить webpack в командной строке:

$ ./node_modules/.bin/webpack

Теперь вы можете обновить index.html в браузере. На момент написания этой статьи большинство современных браузеров поддерживают все фичи ES2015, поэтому иногда трудно определить, выполнил ли свою работу babel. Вы можете протестировать его в браузере более старой версии, вроде IE9, или покопаться в bundle.js и найти строку транспайлированного кода:

// bundle.js
// ...
console.log('Hello ' + name + ', how are you ' + time + '?');
// ...

Здесь видно, как babel преобразовал интерполированную строку ES2015 в обычную конкатенацию чтобы сохранить совместимость с браузером. Хотя этот пример не слишком захватывающий, способность преобразовать код — очень мощный инструмент. В JavaScript есть некоторые интересные языковые фичи, вроде async/await, которые вы можете начать использовать прямо сейчас, чтобы писать код лучше. И хотя transpilation иногда выглядит утомительным и болезненным занятием, оно привело к резким улучшениям в языке за последние несколько лет, так как сегодня люди тестируют завтрашние фичи.

Мы почти закончили, но в нашем воркфлоу все еще есть неотполированные грани. Если нас беспокоит производительность, мы должны минимизировать бандл-файл, что должно быть уже достаточно лёгкой задачей, поскольку этап сборки уже встроен. Нам также нужно повторно запускать команду webpack каждый раз, когда мы меняем JavaScript. Поэтому следующее, что мы рассмотрим — это несколько удобных инструментов для решения этих проблем.

Использование task runner (скрипты npm)

Теперь, когда мы вложились в использование этапа сборки для работы с модулями JavaScript, имеет смысл использовать task runner, инструмент, который автоматизирует различные части процесса сборки. Для фронтенд разработки в задачи включена минимизация кода, оптимизация изображений, прогон тестов и т.д.

В 2013 году Grunt был самым популярным таск-раннером во фронтенде, чуть позже появился Gulp. Оба полагаются на плагины, которые охватывают другие инструменты командной строки. Сегодня более популярный выбор — использовать возможности скриптов, встроенных в сам менеджер пакетов npm, который не использует плагины, а напрямую работает с другими инструментами командной строки.

Давайте напишем несколько npm-скриптов, чтобы упростить использование webpack. Это простое изменение файла package.json как в примере ниже:

{
  "name": "modern-javascript-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --progress -p",
    "watch": "webpack --progress --watch"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment": "^2.19.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "webpack": "^3.7.1"
  }
}

Здесь мы добавили два новых скрипта, build и watch. Чтобы запустить скрипт сборки, вы можете ввести в командной строке:

$ npm run build

Это запустит webpack (используя конфигурацию из webpack.config.js, которую мы сделали ранее) с опцией --progress, чтобы показать прогресс в процентах и опцию -p, чтобы минимизировать код для production. Чтобы запустить сценарий watch:

$ npm run watch

Используется опция -watch для автоматического перезапуска webpack каждый раз, когда изменяется любой JavaScript-файл, что отлично подходит для разработки.

Обратите внимание, что скрипты в package.json могут запускать webpack без указания полного пути ./node_modules/.bin/webpack, так как node.js знает расположение каждого пути модуля npm. Это круто! Мы можем всё сделать ещё круче, установив webpack-dev-server, отдельный инструмент, который предоставляет простой веб-сервер с живой перезагрузкой. Чтобы установить его как зависимость, введите команду:

$ npm install webpack-dev-server --save-dev

Затем добавьте скрипт npm в package.json:

{
  "name": "modern-javascript-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --progress -p",
    "watch": "webpack --progress --watch",
    "server": "webpack-dev-server --open"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment": "^2.19.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "webpack": "^3.7.1"
  }
}

Теперь вы можете запустить свой dev-сервер выполнив команду:

$ npm run server

Это автоматически откроет сайт index.html в вашем браузере с адресом localhost: 8080 (по умолчанию). Каждый раз, когда вы будете менять JavaScript в index.js, webpack-dev-server будет перестраивать собственный упакованный JavaScript и автоматически обновлять браузер. Это удивительно полезная экономия времени, поскольку у вас есть возможность сосредоточиться на коде, а не на постоянном переключении контекстов между кодом и браузером, чтобы увидеть изменения.

И это только верхушка, есть еще много вариантов с webpack и webpack-dev-server (о которых вы можете прочитать здесь). Конечно, вы можете создавать npm-скрипты для выполнения других задач, таких как преобразование Sass в CSS, сжатие изображений, прогон тестов всего, к чему имеет доступ инструмент командной строки — всё имеет смысл. Также есть несколько отличных расширенных опций и манёвров с самими скриптами npm. Это выступление Кейт Хадсон — отличное начало.

Вывод

Вот, что представляет из себя современный JavaScript в двух словах. Мы перешли с простого HTML и JS к менеджеру пакетов, чтобы автоматически загружать сторонние пакеты, упаковщику модулей для создания единого скрипт-файла, транспайлеру для использования будущих фич JavaScript и таск раннеру для автоматизации различных частей процесса сборки. Определенно тут много движимых частей, особенно для новичков. Веб-разработка когда-то была отличным стартом для новичков в программировании, именно потому что было легко начать работать; сейчас это довольно сложно, особенно потому, что различные инструменты склонны к быстрым изменениям.

Тем не менее, всё не так плохо, как кажется. Всё уравновешивается, особенно после внедрениея экосистемы node, как стабильного способа работы с фронтендом. Довольно приятно использовать npm в качестве менеджера пакетов, require или import для модулей, и npm-скрипты для запуска задач. Это значительно упрощает рабочий процесс по сравнению с тем, что было год или два назад!

Дополнительная польза и для начинающих, и для опытных разработчиков в том, что фреймворки сегодня содержат инструменты, которые упрощают процесс и начать теперь намного легче. У Ember есть ember-cli, что очень сильно повлияло на angular-cli от Angular, create-react-app от React, vue-cli от Vue и т. д. Все эти инструменты создают проект с полным набором того, что вам нужно — всё, что вам нужно сделать, это начать писать код. Тем не менее, эти инструменты не волшебные, они просто задают правильную базовую структуру, но вы можете оказаться в ситуации, когда вам потребуется выполнить дополнительную настройку с помощью webpack, babel и т.д. Поэтому очень важно понять, что делает каждый из этих инструментов, что мы и сделали в этой статье.

Работа с современным JavaScript — не всегда самая приятная штука, потому что он постоянно меняется и развивается со скоростью звука. Но, хоть иногда некоторые моменты и кажутся изобретением колеса, быстрая эволюция JavaScript продвинула инновации вроде горячей перезагрузки, инструмента статического анализа и отладка в контексте времени. Сегодня быть разработчиком — захватывающе, и я надеюсь, что эта информация может послужить путеводителем, который поможет вам в вашем путешествии!

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP и Java.

Хекслет

Подробнее о том, почему наше обучение работает →