Недавно вышла Node 12 с кодовым именем Erbium, долгосрочная поддержка которой (LTS) продлится с октября 2019 по апрель 2022.


В новой версии много вкусностей и улучшений рантайма. Помимо этого, учитывая, что под капотом V8, нода также получит все улучшения движка.




Поддержка import/export


Нода входит в 3 фазу на пути к ECMAScript Modules. Изначально эта фича была доступна лишь с флагом --experimental-modules. К моменту перехода Node на LTS планируется убрать необходимость использования этого флага.


Синтаксис с использованием import/export стал предпочтительным при работу с модулями у js разработчиков с момента стандартизации в ES6, и команда, стоящая за Node, старательно работала над нативной поддержкой. Экспериментально эта возможность была доступна с Node 8.0 с 0 фазы. Текущий релиз — это большой шаг в этом направлении. Большинство популярных браузеров уже поддерживают эту фичу с помощью <script type="module">.


С 3 фазы будет поддержка трёх вариантов import из ES моделуей:


// экспорт по умолчанию
import module from 'module'

// именованный экспорт
import { namedExport } from 'module'

// экспорт всего пространства имён
import * as module from 'module'

Ванильные модули можно экспортировать только дефолтным способом:



import module from 'cjs-library'

Можно использовать import() для загрузки в рантайме. import() возвращает Promise и работает как с ES моделями, так и с CommonJS библиотеками.


V8


Node 12 будет первоначально работать на V8 7.4 и в конечном итоге обновится до 7.6. Команда V8 согласилась предоставить ABI (Application Binary Interface). Заметными улучшениями в V8 7.4 являются улучшения производительности для более быстрого исполнения JavaScript, лучшего управления памятью и расширенной поддержки синтаксиса ECMAScript.



Async stack traces


Давайте рассмотрим этот код:



async function testAsyncStacktrace() {
    await killme();
    return 42;
}

async function killme() {
    await Promise.resolve();
    throw new Error('#Feelsbadman');
}

testAsyncStacktrace().catch(error => console.log(error.stack));

На более старых версиях вы получите примерно такой стектрейс:


Error: #Feelsbadman
    at killme (test.js:8:11)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:721:11)
    at startup (internal/bootstrap/node.js:228:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:576:3)

Как видно, в сообщении вообще нигде не упоминается testAsyncStacktrace. А теперь флаг --async-stack-traces включен по умолчанию, и лог будет выглядеть так:


Error: #Feelsbadman
    at killme (test.js:8:11)
    at async testAsyncStacktrace (test.js:2:5)

Ускоренный вызов при несоответствии числа аргументов


В JavaScript вполне допустимо вызывать функции с меньшим/большим числом аргументов (т.е. передавать меньше или больше объявленных формальных параметров). В первом случае это under-application, а во втором over-application. В случае недостаточным числом аргументов параметры будут иметь значение undefined, тогда как в случае с большим числом параметров они просто игнорируются.


Однако функции JavaScript всё еще могут получить фактические параметры с помощью объекта arguments, rest parameters или даже с использованием нестандартизованного Function.prototype.arguments в sloppy mode функциях. В результате, движки JavaScript должны предоставлять способ получения фактических параметров. В V8 это делается с помощью методики arguments adaption. К сожалению, подобные методы сказываюся на производительности.


В некоторых сценариях V8 полностью пропускает arguments adaption, сокращая накладные расходы на вызовы до 60%.



Подробности можно узнать в документе.


Ускоренный парсинг


В Chrome достаточно большие скрипты парсятся потоково в рабочих потоках во время их загрузки. В V8 7.4 исправили проблему с производительностью декодирования UTF-8, что привело к ускорению на 8%.



Каждое падение на диаграмме представляет собой одно из улучшений производительности в потоковом анализаторе.


Улучшена работа await


Вместе с флагом --async-stack-traces теперь по дефолту включен флаг --harmony-await-optimization. Подробности тут.


Приватные поля классов


Доступная в V8 возможность использовать приватные поля перекочевала и в ноду. Такие поля недоступны вне класса. Для создания оных нужно перед переменной указать #.



class HelloThere {
  #hidden = 'Hidden';

  get hidden() {
    return this.#hidden;
  }

  set hidden(txt) {
    this.#hidden = txt;
  }

  hi() {
    console.log(`Hello, ${this.#hidden}`);
  }
}

При попытке обратиться к #hidden извне получите синтаксическую ошибку.



const hello = new HelloThere();
hello.#hidden = 'Visible'; // -> SyntaxError
console.log(hello.#hidden); // -> SyntaxError

Быстрый старт


Node 12 будет использовать кеш для встроенных библиотек перед сборкой и встраивать как бинарники. За счёт использования этого кеша основным потоком время старта сократится на 30%.


TLS и безопасность


Нода теперь поддерживает TLS 1.3, предлагающий повышенную безопасность и уменьшающий задержку. TLS 1.3 сильно изменил протокол и активно интегрируется в сеть. Благодаря внедрению TLS 1.3 повысится конфиденциальность данных пользователей, а также ускорится обработка запросов за счёт снижения времени на handshake в HTTPS. Кроме того, TLS 1.0 и 1.1 отключены по умолчанию, а из crypto убрали deprecated методы.


Динамичный размер хипа


Ранее использовался дефолтный размер кучи V8, составляющий 700МБ (32-разрядные системы) или 1400МБ (64-разрядные системы). Теперь нода будет определять размеры кучи на основе доступной памяти, чтобы оптимизировать используемые ресурсы машины.


Возможность дампить хип


Node 12 предоставляет возможность дампить кучи, облегчая обнаружение проблем с памятью. Подробности можно посмотреть тут и тут.


Экспериментальные диагностические репорты


Нода предлагает более мощный инструменты по диагностике проблем (производительности, загрузка ЦП, памяти, сбои и т.д) прям внутри приложений, предоставляя экспериментальную фичу по отчётам.


Улучшения при работе с нативными модулями


Node 12 продолжает тренд по упрощению работы с N-API. В этой версии улучшена поддержка, в частности при работе с worker threads.


Заключение


В Node 12 много улучшений. Полный CHANGELOG можно посмотреть на Гитхабе и на самом сайте.

Комментарии (25)


  1. DanilaLetunovskiy
    02.05.2019 04:51
    -1

    Меня очень бесит, что перед каждой переменной и функцией в модулях нужно писать слово export. Поэтому я написал небольшой скрипт, который проходит по всем .mjs файлам, собирает эти имена переменных и функций, и вверху одной строчкой их все экспортирует.

    auto_exports.js:

    let fs = require("fs");
    let path = require("path");
    
    function getFiles(dir, files_){
      files_ = files_ || [];
    	let files = fs.readdirSync(dir);
    
    	for(let f of files){
    		let name = dir + "/" + f;
    		if(fs.statSync(name).isDirectory()){
    			if(f == "node_modules") continue;
    			getFiles(name, files_);
    		}else{
    			files_.push(name);
    		}
    	}
    	return files_;
    }
    
    for(let name of getFiles(path.resolve()).filter(f => f.slice(-3) == "mjs")){
    	let f = fs.readFileSync(name, "utf8");
    	let lm = f.split("\n");
    	let words = [];
    	for(let l of lm){
    		let ks = ["let", "const", "function", "function*", "async function", "class"];
    		for(let s of ks){
    			s += " ";
    			if(l.slice(0,s.length) == s){
    				let r = l.slice(s.length).match(/^\ *([a-zA-Zа-яА-ЯёЁ_0-9]+)/);
    				if(r) words.push(r[0]);
    			}
    		}
    	}
    
    	lm[0] = "export {" + words.join(",") + "};";
    	fs.writeFile(name, lm.join("\n"), function(err){});
    }
    


    и чтобы он запустился, ещё в package.json надо вставить строчку
    "scripts": {
    	"start": "node server/utils/auto_exports.js && node --experimental-modules server.mjs"
    },
    


    1. Djaler
      02.05.2019 09:10
      +3

      Очень странное решение, ибо так вы отдаёте всё наружу, не разделяя на публичное и приватное


      1. DanilaLetunovskiy
        04.05.2019 02:39

        лучше бы разработчики node.js сделали всё открытое, а кому уже нужно закрывать, те бы писали private перед переменными.

        но это также как моральная дилема вагонетки, одни считают что человек который не дёрнул рычаг виноват, совершил преступление бездействием, а другая половина людей считают что не виноват.
        у нас разные мировозрения, и мы всёравно друг с другом никогда не согласимся ;)


        1. justboris
          04.05.2019 11:50

          лучше бы разработчики node.js сделали всё открытое, а кому уже нужно закрывать, те бы писали private перед переменными.

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


          Поэтому текущий модульный подход с необходимостью писать явный export оказывается лучше и удобнее для большинства разработчиков. Мы изначально высвечиваем из модуля его публичное API, а потом уже модифицируем его внутри как хотим, и нам не нужно писать private каждый раз, когда мы рефакторим и создаем внутри новые функции и переменные.


          1. DanilaLetunovskiy
            04.05.2019 19:20

            нет ты не прав.

            ты спутал совсем разные вещи, модули, и просто когда всё было в одной области видимости.

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


            1. justboris
              04.05.2019 20:09
              +1

              Ваше предложение сделать все открытым для импорта по умолчанию страдает от той же проблемы, что и до-модульный Javascript с глобальной областью видимости. Очень легко ошибиться и нечаянно оставить открытыми функции, которые не предназначались для использования снаружи.

              Именно поэтому, на опыте прошлых ошибок, JS-модули сделаны с явным экспортом, чтобы не было случайных утечек приватных функций


              1. Zenitchik
                05.05.2019 13:33

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

                Это мелочи. Гораздо хуже то, что засоряется глобальное пространство имён. Приходится вводить префиксы, чтобы модули не конфликтовали.


            1. Zenitchik
              05.05.2019 13:40

              я должен писать список ещё и при экспортировании.

              А я ещё и комментарий пишу с рекомендуемым способом импортирования этого модуля.
              Потому что это удобнее, чем каждый раз вспоминать, что и под какими именами из него импортируется.


    1. megahertz
      02.05.2019 09:12
      +27

      Public Morozov, теперь и на JS


    1. beat
      02.05.2019 10:05
      +2

      [a-zA-Zа-яА-ЯёЁ_0-9] — имена перемеренных, констант, классов кириллицей?


      1. kost
        02.05.2019 19:15
        +1

        Emoji и другие алфавиты не учтены.


        1. isden
          03.05.2019 18:51

          Вот вы ржоте, а у нас некоторые клиенты хотят эмоджи во всяких странных и не предназначенных для этого местах :(


    1. serf
      02.05.2019 10:24
      +7

      Еще перепишите автозамену let на const и сразу в продакшен заливайте, только заранее найдите новое место работы.


    1. EvilGenius18
      02.05.2019 13:14
      +3

      Зачем же перед каждой переменной и функцией писать export, если можно это сделать в конце файла?:

      export {
        name1,
        name2,
        ...
      }
      


      1. DanilaLetunovskiy
        04.05.2019 02:33

        какраз-таки мой скрипт так и делает, мы же не на C++ программируем, чтобы весь код два раза дублировать, там объявления переменных, а там весь остальной код.


    1. xF0x9Fx92x83
      02.05.2019 13:15
      +4

      Меня очень бесит, что перед каждой переменной и функцией в модулях нужно писать слово export.

      И правильно бесит, потому что не нужно так делать.


  1. justboris
    02.05.2019 12:27
    +3

    Экспериментально эта возможность была доступна с Node 8.0 с 0 фазы. Текущий релиз — это большой шаг в этом направлении

    Неплохо было бы раскрыть, что именно произошло здесь. Попробую это сделать.


    8 версия содержала в себе старую версию реализации. У этой реализации были недостатки, поэтому было решено переделать ее. Текущая версия является обладает новыми возможностями, например доступом к классическому require() из es-модуля.


    Кроме этого, новая реализация позволяет использовать расширение .js для es-модулей (вместо специального .mjs), предлагая использовать поле "type": "module" в package.json, чтобы авторы npm-модулей могли сами определять, является ли их пакет es-модулями, или старыми commonjs


  1. sultan99
    02.05.2019 17:41
    +4

    Эх почему не `private` вместо `#`?


    1. vvadzim
      02.05.2019 17:59

      Вы имеете ввиду нечто подобное?

      class A {
        private x
        isEqual(b) { return x === b.x }
      }
      

      Насколько я помню, обычно (например в C++ и Java, если не ошибаюсь), private даёт доступ к приватным полям объектов того же класса. В JS на этапе компиляции нельзя понять, какого класса будет объект, а в рантайме — дополнительные сложности.
      Т.е. приведенный выше пример интерпретатор фактически должен будет понимать как
      class A {
        private x
        isEqual(b) { return x === (b instanceOf A ? b.private x : b.x) }
      }
      

      И так с каждой операцией взятия свойства. Ещё как-то надо разрулить b['x'] и b[variable].
      Как-то сложно получается. Проще #.


      1. lllypynby
        02.05.2019 22:07
        +1

        Неужели это нельзя было зашить в интерпритатор? Пусть хоть там в ___x преодбразует какая разница. Просто язык превращается в набор символов, который нужно запоминать, а к реальности никакого отношения.


        1. vvadzim
          02.05.2019 22:19
          +2

          Как именно преобразовывать?
          Вы можете хотя бы примерно алгоритм указать?
          Люди вот не смогли найти алгоритма, как скомпилировать класс со служебным словом private в js.

          Предложите хотя бы преобразование для первого примера из моего комментария.
          Так-то приватные поля ещё не приняты окончательно. Можно переиграть, если ваше предложение подойдёт.


          1. sultan99
            03.05.2019 06:08

            С обратной совместимостью скорее никак, скорее тут надо вводить новое ключевое слово private как const, static на уровне интерпретатора.


    1. justboris
      02.05.2019 22:26
      +1

      Ответ на этот вопрос есть в официальном FAQ. Ещё вот тут в комментариях на Хабре эту тему поднимали.


      А если совсем коротко, то не так уж просто добавить полноценный private keyword в динамический язык с 20-летней историей


    1. olexandr17
      02.05.2019 22:32

      Меня интересует, как обстоит с этим дело в TypeScript?
      На данный момент, насколько я понимаю, не весь JS-код будет валидным для TS?


  1. ReklatsMasters
    03.05.2019 01:50
    +1

    Самая мегафича для меня лично это асинхронные стектрейсы. Это просто в разы упростит дебаггинг. А если ещё и process.nextTick будет поддерживать async stack trace, это бомба. Просто проблема в том, что не все разработчики грамотно проектируют свои интерфейсы, из за этого иногда функции внутри nextTick кидают исключения и поиск источника исключения превращается в боль. Внутри ноды, кстати, эта проблема обходится через события.