Что нового в Node 12

    Недавно вышла 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 можно посмотреть на Гитхабе и на самом сайте.

    Поддержать автора
    Поделиться публикацией

    Похожие публикации

    Комментарии 28

      –21
      Меня очень бесит, что перед каждой переменной и функцией в модулях нужно писать слово 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"
      },
      
        +5
        Очень странное решение, ибо так вы отдаёте всё наружу, не разделяя на публичное и приватное
          0
          лучше бы разработчики node.js сделали всё открытое, а кому уже нужно закрывать, те бы писали private перед переменными.

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

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


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

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

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

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

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

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

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

                    А я ещё и комментарий пишу с рекомендуемым способом импортирования этого модуля.
                    Потому что это удобнее, чем каждый раз вспоминать, что и под какими именами из него импортируется.
              +29
              Public Morozov, теперь и на JS
                +3
                [a-zA-Zа-яА-ЯёЁ_0-9] — имена перемеренных, констант, классов кириллицей?
                  +1
                  Emoji и другие алфавиты не учтены.
                    0
                    Вот вы ржоте, а у нас некоторые клиенты хотят эмоджи во всяких странных и не предназначенных для этого местах :(
                  +10
                  Еще перепишите автозамену let на const и сразу в продакшен заливайте, только заранее найдите новое место работы.
                    +3
                    Зачем же перед каждой переменной и функцией писать export, если можно это сделать в конце файла?:

                    export {
                      name1,
                      name2,
                      ...
                    }
                    

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

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

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

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


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


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

                        +6
                        Эх почему не `private` вместо `#`?
                          0
                          Вы имеете ввиду нечто подобное?
                          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].
                          Как-то сложно получается. Проще #.
                            +2
                            Неужели это нельзя было зашить в интерпритатор? Пусть хоть там в ___x преодбразует какая разница. Просто язык превращается в набор символов, который нужно запоминать, а к реальности никакого отношения.
                              +2
                              Как именно преобразовывать?
                              Вы можете хотя бы примерно алгоритм указать?
                              Люди вот не смогли найти алгоритма, как скомпилировать класс со служебным словом private в js.

                              Предложите хотя бы преобразование для первого примера из моего комментария.
                              Так-то приватные поля ещё не приняты окончательно. Можно переиграть, если ваше предложение подойдёт.
                                0
                                С обратной совместимостью скорее никак, скорее тут надо вводить новое ключевое слово private как const, static на уровне интерпретатора.
                            +1

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


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

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

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

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

                              Самое читаемое