- PVSM.RU - https://www.pvsm.ru -
В этой статье хочу поделиться переводом статьи о нативных ECMAScript модулях [2], которые все больше и больше обсуждаются среди фронтендеров. Javascript ранее никогда не поддерживал нативно работу с модулями, и нам, фронтендерам, всегда приходилось использовать дополнительные инструменты для работы с модулями. Но вы только представьте, что в скором времени не нужно будет использовать Webpack для создания бандлов модулей. Представьте мир, в котором браузер будет собирать все за вас. Подробнее об этих перспективах я и хочу рассказать.
В 2016 году в браузеры и Nodejs было добавлено много интересных фич и полезностей из новых стандартов, в частности спецификации ECMAScript 2015 [3]. Сейчас мы сталкиваемся с ситуацией, когда поддержка среди браузеров близка к 100%:
Также фактически в стандарт введены ECMAScript модули [2] (часто называют ES/ES6 модули). Это единственная часть спецификации, которая требовала и требует наибольшего времени для реализации, и ни один браузер пока не выпустил их в стабильной версии.
Недавно в Safari 19 Technical Preview и Edge 15 добавили реализацию модулей без использования флагов. Уже близится то время, когда мы можем отказаться от использования привычных всем бандлов и транспиляции модулей.
Чтобы лучше понять, как мир фронтенда пришел к этому, давайте начнем с истории JS модулей, а затем взглянем на текущие преимущества и реализации ES6 модулей.
Было много способов подключения модулей. Приведу для примера наиболее типичные из них:
1. Просто длинный код внутри script тега. Например:
<!--html-->
<script type="application/javascript">
// module1 code
// module2 code
</script>
2. Разделение логики между файлами и подключение их с помощью тегов script:
/* js */
// module1.js
// module1 code
// module2.js
// module2 code
<!--html-->
<script type="application/javascript" src="PATH/module1.js" ></script>
<script type="application/javascript" src="PATH/module2.js" ></script>
3. Модуль как функция (например: модуль функция, которая возвращает что-то; самовызывающаяся функция или функция конструктор) + Application файл/модель, которые будут точкой входа для приложения:
// polyfill-vendor.js
(function(){
// polyfills-vendor code
}());
// module1.js
function module1(params){
// module1 code
return module1;
}
// module3.js
function module3(params){
this.a = params.a;
}
module3.prototype.getA = function(){
return this.a;
};
// app.js
var APP = {};
if(isModule1Needed){
APP.module1 = module1({param1:1});
}
APP.module3 = new module3({a: 42});
<!--html-->
<script type="application/javascript" src="PATH/polyfill-vendor.js" ></script>
<script type="application/javascript" src="PATH/module1.js" ></script>
<script type="application/javascript" src="PATH/module2.js" ></script>
<script type="application/javascript" src="PATH/app.js" ></script>
Ко всему этому Frontend сообщество изобрело много разновидностей и новых способов, которые добавляли разнообразие в этот праздник анархии.
Основная идея заключается в том, чтобы обеспечить систему, которая позволит вам просто подключить одну ссылку JS файла, вот так:
<!--html-->
<script type="application/javascript" src="PATH/app.js" ></script>
Но всё свелось к тому, что разработчики выбрали сторону бандлеров — систем сборки кода. Далее предлагается рассмотреть основные реализации модулей в JavaScript.
Такой подход широко реализуется в библиотеке RequireJS [5] и в инструментах, таких как r.js [6] для создания результирующего бандла. Общий синтаксис:
// polyfill-vendor.js
define(function () {
// polyfills-vendor code
});
// module1.js
define(function () {
// module1 code
return module1;
});
// module2.js
define(function (params) {
var a = params.a;
function getA(){
return a;
}
return {
getA: getA
}
});
// app.js
define(['PATH/polyfill-vendor'] , function () {
define(['PATH/module1', 'PATH/module2'] , function (module1, module2) {
var APP = {};
if(isModule1Needed){
APP.module1 = module1({param1:1});
}
APP.module2 = new module2({a: 42});
});
});
Это основной формат модулей в Node.js экосистеме. Одним из основных инструментов для создания бандлов для клиентских устройств является Browserify [8]. Особенность этого стандарта — обеспечение отдельной области видимости для каждого модуля. Это позволяет избежать непреднамеренной утечки в глобальную область видимости и глобальных переменных.
Пример:
// polyfill-vendor.js
// polyfills-vendor code
// module1.js
// module1 code
module.exports= module1;
// module2.js
module.exports= function(params){
const a = params.a;
return {
getA: function(){
return a;
}
};
};
// app.js
require('PATH/polyfill-vendor');
const module1 = require('PATH/module1');
const module2 = require('PATH/module2');
const APP = {};
if(isModule1Needed){
APP.module1 = module1({param1:1});
}
APP.module2 = new module2({a: 42});
Еще один способ работы с модулями пришел к нам с ES2015. В новом стандарте появился новый синтаксис и особенности, удовлетворяющие потребностям фронтенда, таким как:
Есть множество реализаций загрузчиков, компиляторов и подходов, которые поддерживают одну или несколько из этих систем. Например:
На сегодняшний день в JavaScript мы привыкли к использованию различных инструментов для объединения модулей. Если мы говорим о ECMAScript модулях, вы можете использовать один из следующих:
Как правило, инструмент предоставляет CLI интерфейс и возможность настроить конфигурацию для создания бандлов из ваших JS файлов. Он получает точки входа и набор файлов. Обычно такие инструменты автоматически добавляют “use strict”. Некоторые из этих инструментов также умеют транспилировать код, чтобы заставить его работать во всех окружениях, которые необходимо (старые браузеры, Node.js и т.д.).
Давайте посмотрим на упрощенной WebPack конфиг, который устанавливает точку входа и использует Babel для транспиляции JS файлов:
// webpack.config.js
const path = require('path');
module.exports = {
entry: path.resolve('src', 'webpack.entry.js'),
output: {
path: path.resolve('build'),
filename: 'main.js',
publicPath: '/'
},
module: {
loaders: {
"test": /.js?$/,
"exclude": /node_modules/,
"loader": "babel"
}
}
};
Конфиг состоит из основных частей:
В этом случае, как правило, файл index.html содержит следующее:
<script src="build/main.js"></script>
И ваше приложение использует бандлы/транспилируемый код JS. Это общий подход для работы с бандлерами, давайте посмотрим, как заставить его работать в браузере без каких-либо бандлов.
На сегодняшний день каждый из современных браузеров имеет поддержку модулей ES6:
Как вы видели, в настоящее время можно проверить нативные JS модули в Safari Technology Preview 19+ и EDGE 15 Preview Build 14342+. Давайте скачаем и попробуем модули в действии.
Вы можете скачать Firefox Nightly [24], а это означает, что скоро модули появятся в FF Developer Edition [25], а затем в стабильной версии браузера.
Чтобы включить ES модули:
И все, теперь вы у вас доступны ES модули в Firefox.
Если вы используете MacOS, достаточно просто загрузить последнюю версию Safari Technology Preview (TP) с developer.apple.com [26]. Установите и откройте его. Начиная с Safari Technology Preview версии 21+ [27], модули ES включены по умолчанию.
Если это Safari TP 19 или 20, убедитесь, что ES6 модули включены: откройте меню «Develop» -> «Experimental Features» -> «ES6 Modules».
Другой вариант — скачать последнюю Webkit Nightly [28] и играться с ним.
Вы можете скачать бесплатную виртуальную машину от Microsoft [29].
Просто выберите виртуальную машину (VM) «Microsoft EDGE на Win 10 Preview (15.XXXXX)» и, например, «Virtual Box» (также бесплатно) в качестве платформы.
Установите и запустите виртуальную машину, далее откройте браузер EDGE.
Зайдите на страницу about:flags и включите флаг «Включить экспериментальные функции JavaScript» (Enable experimental JavaScript features).
Вот и все, теперь у вас есть несколько сред, где вы можете играть с нативной реализацией модулей ECMAScript.
Давайте начнем с нативных особенностей модулей:
До сих пор мы не увидели особенно серьезные отличия от того, к чему мы привыкли с бандлерами. Большая разница в том, что точка входа должна быть предусмотрена в браузере. Вы должны предоставить script тег с конкретным атрибутом type=«module», например:
<script type= "module" scr= "PATH/file.js" ></script>
Это говорит браузеру, что ваш скрипт может содержать импорт других скриптов, и они должны быть соответствующим образом обработаны. Главный вопрос, который появляется здесь:
Почему интерпретатор JavaScript не может определять модули, если файл и так по сути является модулем?
Одна из причин [32] — нативные модули в строгом режиме по умолчанию, а классические script-ы нет:
Еще одна причина — тот же файл может быть валидным без строгого режима и невалидным с ним же. Тогда валидность зависит от того, как он интерпретируется, что и приводит к неожиданным проблемам.
Определение типа ожидаемой загрузки файла открывает множество способов для оптимизации (например, загрузка импортируемых файлов параллельно/до парсинга оставшейся части файла html). Вы можете найти некоторые примеры [33], используемые движками Microsoft Chakra JavaScript для модулей ES [33].
Node.js окружение отличается от браузеров и использовать тег script type=«module» не особо подходит. В настоящее время все еще продолжается спор, каким подходящим способом сделать это [34].
Некоторые решения были отклонены сообществом:
Другие варианты все еще находятся на рассмотрении (спасибо @bmeck [35] за подсказку):
Каждый метод имеет свои плюсы и минусы, и в настоящее время до сих пор нет четкого ответа, каким путем Node.js будет идти [23].
Во-первых, давайте создадим простую демку [36] (вы можете запустить его в браузерах, которые вы установили ранее, чтобы проверить модули). Так что это будет простой модуль, который импортирует другой и вызывает метод из него. Первый шаг — включить файл, используя:
<script type="module"/>
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<script type="module" src="main.js"></script>
</head>
<body>
</body>
</html>
Вот файл модуля:
// main.js
import utils from "./utils.js";
utils.alert(`
JavaScript modules work in this browser:
Adding JavaScript modules to the web platform [37]
`);
И, наконец, импортированные утилиты:
// utils.js
export default {
alert: (msg)=>{
alert(msg);
}
};
Как вы могли заметить, мы оставили расширение файла .js, когда используется директива import. Это еще одно отличие от поведения бандлеров — нативные модули не добавляют .js расширения по умолчанию.
Во-вторых, давайте проверим область видимости у модуля (демо [38]):
var x = 1;
alert(x === window.x);//false
alert(this === undefined);// true
В-третьих, мы проверим, что нативные модули в строгом режиме по умолчанию. Например, строгий режим запрещает удалять простые переменные. Следующее демо [39] показывает, что появляется сообщение об ошибке в модуле:
// module.js
var x;
delete x; // !!! syntax error
alert(`
THIS ALERT SHOULDN'T be executed,
the error is expected
as the module's scripts are in the strict mode by default
`);
// classic.js
var x;
delete x; // !!! syntax error
alert(`
THIS ALERT SHOULD be executed,
as you can delete variables outside of the strict mode
`);
Строгий режим нельзя обойти в нативных модулях.
Итого:
Как и обычные скрипты, вы можете встраивать код, вместо того, чтобы разделять их по отдельным файлам. В предыдущем демо вы можете просто вставить main.js непосредственно в тег script type=«module» что приведет к такому же поведению [40]:
<script type="module">
import utils from "./utils.js";
utils.alert(`
JavaScript modules work in this browser:
Adding JavaScript modules to the web platform [37]
`);
</script>
Итого:
Нативные модули (асинхронные) по умолчанию имеют поведение deffered скриптов. Чтобы понять это, мы можем представить каждый тег script type=«module» с атрибутом defer и без. Вот изображение из спецификации, которое объясняет поведение [41]:
Это означает, что по умолчанию скрипты в модулях не блокируют, загружаются параллельно и выполняются, когда страница завершает парсинг html. Вы можете изменить это поведение, добавив атрибут async, тогда скрипт будет выполнен, как только он загрузится.
Главное отличие нативных модулей от обычных скриптов заключается в том, что обычные скрипты загружаются и выполняются сразу же, блокируя парсинг html. Чтобы представить это, посмотрите демо с разными вариантами атрибутов в теге script [42], где первым будет выполнен обычный скрипт без атрибутов defer async:
<!DOCTYPE html>
<html>
<head>
<script type="module" src="./script1.js"></script>
<script src="./script2.js"></script>
<script defer src="./script3.js"></script>
<script async src="./script4.js"></script>
<script type="module" async src="./script5.js"></script>
</head>
<body>
</body>
</html>
Порядок загрузки зависит от реализации браузеров, размера скриптов, количества импортируемых скриптов и т. д.
Итого:
Мы вступаем в эпоху нативной поддержки модулей в JavaScript. JS прошел долгий путь становления, и, наконец-то, он добрался до этой точки. Наверное, это одна из самых долгожданных и востребованных фич. Никакой синтаксический сахар и новые языковые конструкции не идут в сравнение с этим новым стандартом.
Все вышесказанное дается для первого знакомства с нативными ECMAScript модулями. В следующей статье будут разобраны способы взаимодействия модулей, определение поддержки в браузерах, конкретные моменты и различия с обычными бандлами и т. д.
Если хотите узнать больше сейчас, предлагаю пройтись по ссылкам:
Честно говоря, когда я пробовал нативные модули в первый раз, и они заработали в браузере, я почувствовал то, чего не чувствовал с появлением таких языковых фич, как const/let/arrow functions и прочих новомодных фишек, когда они начали работать непосредственно в браузерах. Я надеюсь, что вы будете, как и я, рады добавлению нативного механизма работы с модулями в браузеры.
Я Frontend разработчик в команде Авиа в Tutu.ru [49]. Сейчас у нас в проектах используется Webpack в качестве бандлера. Есть легаси код и старые проекты с RequireJS. Нативные модули очень интересны и ждем их с нетерпением, тем более мы уже перевели все наши проекты на HTTP/2. Конечно, совсем без бандлеров обходиться мы не собираемся, так как у нас большое количество модулей во всех проектах. Но приход нативных модулей мог бы поменять воркфлоу сборки и деплоя.
Автор: nialvi
Источник [50]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/253113
Ссылки в тексте:
[1] Image: https://habrahabr.ru/company/tuturu/blog/326716/
[2] ECMAScript модулях: http://www.ecma-international.org/ecma-262/6.0/#sec-modules
[3] ECMAScript 2015: http://www.ecma-international.org/ecma-262/6.0/
[4] AMD: https://en.wikipedia.org/wiki/Asynchronous_module_definition
[5] RequireJS: http://requirejs.org
[6] r.js: https://github.com/requirejs/r.js
[7] CommonJS: https://en.wikipedia.org/wiki/CommonJS
[8] Browserify: http://browserify.org
[9] Webpack: https://webpack.github.io
[10] SystemJS: https://github.com/systemjs/systemjs
[11] JSPM: http://jspm.io
[12] Babel: https://babeljs.io
[13] UMD: https://github.com/umdjs/umd
[14] Rollup: https://github.com/rollup/rollup
[15] Traceur Compiler: https://github.com/google/traceur-compiler
[16] преобразования ES2015 модулей в CommonJS: https://babeljs.io/docs/plugins/transform-es2015-modules-commonjs/
[17] Webpack 2: https://webpack.github.io/docs/roadmap.html
[18] TypeScript: https://www.typescriptlang.org/
[19] реализованы, доступны под флагом в Firefox 54+: https://bugzilla.mozilla.org/show_bug.cgi?id=568953
[20] в стадии разработки: https://www.chromestatus.com/features/5365692190687232
[21] реализованы, доступны под флагом в EDGE 15 Preview Build 14342+: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/moduleses6/?q=module
[22] реализованы, доступны по умолчанию Safari Technology Preview 21+: https://webkit.org/status/#feature-modules
[23] на рассмотрении, требуют дополнительного обсуждения: https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md
[24] Firefox Nightly: https://www.mozilla.org/en-US/firefox/channel/desktop/
[25] FF Developer Edition: https://www.mozilla.org/en-US/firefox/developer/
[26] developer.apple.com: https://developer.apple.com/safari/download/
[27] Safari Technology Preview версии 21+: https://webkit.org/blog/7265/release-notes-for-safari-technology-preview-21/&usg=ALkJrhhr4bTdQdc6nyahpX_BoyjStLUXbg
[28] скачать последнюю Webkit Nightly: https://webkit.org/downloads/
[29] скачать бесплатную виртуальную машину от Microsoft: https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/&usg=ALkJrhjbzpNayQs6n4C61o2ygvZNJFqAIA
[30] import директивы: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/import&usg=ALkJrhhu317ObZLoOW9_S-p5MUHXxtJwGw
[31] export: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
[32] Одна из причин: https://www.nczonline.net/blog/2016/04/es6-module-loading-more-complicated-than-you-think/
[33] некоторые примеры: https://blogs.windows.com/msedgedev/2016/05/17/es6-modules-and-beyond/&usg=ALkJrhitcYsRsiGcwOcpyLBMED3zX1VA9Q
[34] спор, каким подходящим способом сделать это: https://github.com/nodejs/node/wiki/ES6-Module-Detection-in-Node
[35] @bmeck: https://github.com/bmeck
[36] простую демку: https://plnkr.co/edit/be0bb3%3Fp%3Dpreview&usg=ALkJrhiCFb4UZhxlLPIzPLeJji1aY2S3uw
[37] Adding JavaScript modules to the web platform: https://blog.whatwg.org/js-modules
[38] демо: https://plnkr.co/edit/i4rZ8U?p=preview
[39] Следующее демо: https://plnkr.co/edit/sulRpONj6EZM1Sph7FSd?p=preview
[40] приведет к такому же поведению: https://plnkr.co/edit/ghsd9Z?p=preview
[41] объясняет поведение: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
[42] демо с разными вариантами атрибутов в теге script: https://plnkr.co/edit/EuzEJ6?p=preview
[43] Modules part JavaScript books by Dr. Axel Rauschmayer: http://exploringjs.com/es6/ch_modules.html
[44] The proposal of `script type=”module” ` by Domenic Denicola: https://github.com/whatwg/html/pull/443
[45] HTML live standard, “Scripting” section: https://html.spec.whatwg.org/multipage/scripting.html#scripting-3
[46] Native ECMAScript modules: dynamic import(): https://blog.hospodarets.com/native-ecmascript-modules-dynamic-import
[47] Native ECMAScript modules: nomodule attribute for the migration: https://blog.hospodarets.com/native-ecmascript-modules-nomodule
[48] Native ECMAScript modules: the new features and differences from Webpack modules: https://blog.hospodarets.com/native-ecmascript-modules-new-features
[49] Tutu.ru: https://avia.tutu.ru/
[50] Источник: https://habrahabr.ru/post/326716/
Нажмите здесь для печати.