Браузеры уже изначально поддерживают модули ES, что это значит для фронтенд-разработки?

внешний интерфейс JavaScript браузер HTML
Браузеры уже изначально поддерживают модули ES, что это значит для фронтенд-разработки?

В 2017 году основные браузеры начали нативно поддерживать модули ES2015, а это значит — пришло время заново выучить теги скриптов. И я обещаю, что это не очередная «менструальная» статья, в которой говорится только о синтаксисе модуля ES, а не о практике.

Вы все еще помните Hello World, который вы написали, когда только начинали заниматься фронтенд-разработкой? В начале мы создали файл HTML, в<body>Напишите содержимое веб-страницы в теге, когда вам нужно будет изучить логику взаимодействия со страницей позже, добавьте его в HTML-разметку.<script src="script.js">Тег вводит внешний код script.js, а script.js отвечает за логику взаимодействия со страницей.

С развитием модульности JavaScript в интерфейсном сообществе наша текущая привычка состоит в том, чтобы разделить модули кода JS и упаковать их в файл bundle.js с помощью Webpack, а затем использовать их в HTML.<script src="bundle.js">Тег импортирует упакованный JS. Это означает, что наш рабочий процесс фронтенд-разработки перешел из «каменного века» в «индустриальный век», но в браузере нет качественных изменений, код, который он загружает, по-прежнему представляет собой bundle.js, такой же, как когда мы были в Hello World Способ загрузки скрипта ничем не отличается.

- пока встроенная поддержка браузером стандарта модуля ES не изменила это. В настоящее время большинство браузеров поддерживают<script type="module">Способ загрузить стандартные модули ES, нам пора заново изучить связанные со скриптами знания.

Обзор: тупо перепутали defer и async?

Пожалуйста, послушайте вопрос:

В: Есть два элемента скрипта: загрузка lodash из CDN, загрузка script.js из другого локального, при условии, что локальный скрипт загружается быстрее, а затем следующие plain.html, async.html и defer.html, что выводит?

// script.js
try {
    console.log(_.VERSION);
} catch (error) {
    console.log('Lodash Not Available');
}
console.log(document.body ? 'YES' : 'NO');
// A. plain.html
<head>
	<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
    <script src="script.js"></script>
</head>

// B. async.html
<head>
	<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js" async></script>
    <script src="script.js" async></script>
</head>

// C. defer.html
<head>
	<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js" defer></script>
    <script src="script.js" defer></script>
</head>

Если вы знаете ответ, поздравляю, вы можете пропустить этот раздел, в противном случае пришло время просмотреть его.

Сначала вывод A.plain.html:

4.17.10
NO

То есть, когда script.js выполняется, lodash был загружен и выполнен, но document.body еще не загружен.

До появления атрибутов defer и async первоначальные сценарии загрузки браузера использовали синхронную модель. Парсер браузера анализирует HTML-теги сверху вниз, при обнаружении тега скрипта он приостанавливает анализ других тегов в документе и считывает тег скрипта. В настоящее время:

  • Если тег script не имеет атрибута src и является встроенным скриптом, синтаксический анализатор будет напрямую читать textContent тега, а код JS будет выполняться интерпретатором JS.
  • Если скрипт имеет атрибут src, делается сетевой запрос на загрузку скрипта с URI, указанного в src, а затем скрипт выполняется интерпретатором JS.

В любом случае анализатор браузера будет заблокирован. Я только что упомянул, что браузер анализирует HTML Markup сверху вниз, поэтому эта функция блокировки определяет, что когда сценарий в теге сценария выполняется, он находится над тегом сценария. Дом DOM доступен, элементы DOM ниже оно недоступны.

Такая блокировка имеет смысл, если выполнение нашего скрипта требует манипулирования предыдущими элементами DOM, а загрузка и отрисовка последующих элементов DOM зависит от выполнения скрипта. Но если наоборот, то выполнение скрипта только замедлит отрисовку страницы.

Из-за этого в Советах по оптимизации веб-сайтов Yahoo за 2006 год есть известное правило:

поместите скрипт внизу тела

Но современные браузеры уже поддерживают<script>Теги с атрибутами отсрочки или асинхронности имеют общее то, что они не блокируют синтаксический анализатор HTML.

Когда в документе есть только один тег скрипта, отсрочка существенно не отличается от асинхронной. Но когда есть несколько тегов script, они ведут себя по-разному:

  • каждый асинхронный скрипт выполняется сразу после завершения загрузки, независимо от порядка появления тегов скрипта.
  • Скрипты defer будут выполняться последовательно в соответствии с порядком тегов script.

Таким образом, в приведенном выше вопросе последние два случая выводятся отдельно:

// B. async.html
Lodash Not Available
YES

// C. defer.html
4.17.10
YES

Поскольку script.js в async.html меньше и загружается быстрее, время выполнения также меньше, чем при загрузке lodash из CDN, поэтому_.VERSIONне доступен на выходеLodash Not Available; а script.js в defer.html выполняется не сразу после загрузки, а только после загрузки и выполнения lodash.

На следующем рисунке можно интуитивно увидеть различия в методах загрузки Default, defer и async для трех разных скриптов.Голубой цвет — этап загрузки скрипта, а желтый — этап выполнения скрипта.

One more thing...

Выше анализируется только тег скрипта, содержащий атрибут src, то есть когда сетевой запрос должен быть инициирован для загрузки скрипта извне, тогда когда встроенный<script>А как насчет тегов с атрибутами async и defer?

Ответ простне поддерживается, прописывание атрибутов async и defer в теге script следующим образом не дает никакого эффекта, а это означает, что встроенный JS-скрипт должен выполняться синхронно.

// defer attribute is useless
<script defer>
    console.log(_.VERSION)
</script>

// async attribute is useless
<script async>
    console.log(_.VERSION)
</script>

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

игра меняется<script type=module>

TLDR;

  • Добавление атрибута модуля типа = в тег сценария позволяет браузеру загружать сценарий в виде модуля ES ES
  • Тег type=module поддерживает как встроенные, так и загрузочные скрипты.
  • Скрипты ES по умолчанию откладываются, будь то встроенные или структурированные
  • Явно укажите тег скриптаasyncАтрибут, который может переопределить поведение отсрочки по умолчанию.
  • Один и тот же модуль выполняется только один раз
  • Удаленный скрипт использует URL-адрес в качестве ключа для определения уникальности.
  • Политика безопасности более строгая, а загрузка скриптов с разными источниками ограничена политикой CORS.
  • Когда серверная сторона предоставляет ресурсы модуля ES, она должна возвращать действительный заголовок Content-Type типа JavaScript.

#1 ES Module 101

импорт и экспорт

Использование стандартного модуля ESimport,exportРеализовать импорт и экспорт модулей.

export может экспортировать любой доступный идентификатор JavaScript (идентификатор), явные методы экспорта включают оператор объявления (объявления) иexport { idendifier as name }Два пути.

// lib/math.js
export function sum(x, y) {
    return x + y;
}
export let pi = 3.141593;
export const epsilon = Number.EPSILON;
export { pi as PI };

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

  • import * as math from ...Импортируйте весь модуль и вызовите его через пространство имен math.
  • import { pi, epsilon } from ...Частичный импорт, может напрямую вызывать пи, эпсилон и другие переменные
// app.js
import * as math from './lib/math.js';
import { pi, PI, epsilon } from './lib/math.js';
console.log(`2π = ${math.sum(math.pi, math.pi)}`);
console.log(`epsilon = ${epsilon}`);
console.log(`PI = ${PI}`);

default

Поддержка модуля ESdefaultКлючевое слово реализует безымянный импорт, магия в том, что его можно комбинировать с другими явнымиexportпеременные импортируются одновременно.

// lib/math.js
export function sum(x, y) {
    return x + y;
}
export default 123;

Для такого типа модуля существует два способа импорта модуля, первый — импортировать значение по умолчанию по умолчанию.

import oneTwoThree from './lib/math.js';
// 此时 oneTwoThree 为 123

Второйimport *способ импортировать значения по умолчанию и другие переменные.

import * as allDeps from './lib/math.js'
// 此时 allDeps 是一个包含了 sum 和 default 的对象,allDeps.default 为 123
// { sum: ..., default: 123}

Синтаксические ограничения

Спецификация модуля ES требует, чтобы импорт и экспорт были написаны на верхнем уровне файла скрипта, потому что в отличие от module.exports в CommonJS, экспорт и импорт не являются традиционными операторами JavaScript.

  • Вы не можете писать код экспорта в условных блоках кода, таких как CommonJS.

    // ./lib/logger.js
    
    // 正确
    const isError = true;
    let logFunc;
    if (isError) {
        logFunc = (message) => console.log(`%c${message}`, 'color: red');
    } else {
        logFunc = (message) => console.log(`%c${message}`, 'color: green');
    }
    export { logFunc as log };
    
    const isError = true;
    const greenLog = (message) => console.log(`%c${message}`, 'color: green');
    const redLog = (message) => console.log(`%c${message}`, 'color: red');
    // 错误!
    if (isError) {
        export const log = redLog;
    } else {
        export const log = greenLog;
    }
    
  • Невозможно поместить импорт и экспорт в инструкцию try catch

    // 错误!
    try {
        import * as logger from './lib/logger.js';
    } catch (e) {
        console.log(e);
    }
    

Кроме того, путь импорта в спецификации модуля ES должен быть допустимым относительным путем или абсолютным путем (URI), а использование выражений в качестве путей URI не поддерживается.

// 错误:不支持类 npm 的“模块名” 导入
import * from 'lodash'

// 错误:必须为纯字符串表示,不支持表达式形式的动态导入
import * from './lib/' + vendor + '.js'

#2 Давайте знакомитьсяtype=moduleБар

Вышеупомянутое является базовыми знаниями стандартных модулей ES, это относится к стандарту в первую очередь, реализация отстает, и поддержка браузера не поспевает сразу. Но, как я сказал в начале этой статьи, хорошая новость заключается в том, что поддерживаются последние популярные браузеры Chrome, Firefox, Safari и Microsoft Edge.<script>Новый атрибут для тегов: type=module.

пока регулярный<script>отметить, добавитьtype=moduleатрибут, браузер будет рассматривать этот сценарий как стандартный модуль ES, а также загружать и выполнять его модульным способом.

Простой Hello World выглядит так:

<!-- type-module.html -->
<html>
    <head>
        <script type=module src="./app.js"></script>
    </head>
    <body>
    </body>
</html>
// ./lib/math.js
const PI = 3.14159;
export { PI as PI };

// app.js
function sum (a, b) {
    return a + b;
}
import * as math from './lib/math.js';
document.body.innerHTML = `PI = ${math.PI}`;

Откройте index.html, и вы обнаружите, что содержимое страницы выглядит следующим образом:

Вы можете увидеть процесс запроса ресурсов из панели Network.Браузер загружает app.js из script.src.В Инициаторе видно, что app.js:1 инициирует запрос math.js, то есть первая строка app.js выполняется Загрузите зависимый модуль math.js во время оператора импорта.

Выполнение операторов JavaScript в скриптах модулей такое же, как и в скриптах, загружаемых обычными скриптами, и могут использоваться такие интерфейсы, как DOM API и BOM API.Однако стоит отметить, что скрипты, загруженные как модули, не будут такими же, как обычные скрипты.загрязнять глобальную область.

Например, в нашем коде app.js определяет функциюsum, math.js определяет константыPI, если вы откроете консоль и войдете в обозреватель PI или суммы, появится сообщение об ошибке ReferenceError.

(Ну наконец то...)

# 3 type=module Модули поддерживают встраивание

В приведенном выше примере кода, если код app.js, указанный в type-module.html, изменить на встроенный JavaScript, эффект будет таким же.

<!-- type-module.html -->
<html>
    <head>
        <script type=module>
            import * as math from './lib/math.js';
	        document.body.innerHTML = `PI = ${math.PI}`;
        </script>
    </head>
    <body>
    </body>
</html>

Конечно, встроенный сценарий модуля имеет смысл только тогда, когда он загружается как «входной» сценарий. Это позволяет избежать HTTP-запроса на загрузку app.js. В настоящее время путь math.js, на который ссылается оператор импорта, естественно, должен быть изменен, чтобы быть относительно пути к type-module.html.

#4 Отсрочка по умолчанию, поддержка асинхронности

Вы могли заметить, что тег script в нашем примере Hello World записан в теге head, который используетdocument.body.innerHTMLAPI для работы с телом, но независимо от того, загружен ли скрипт извне или встроен в тег скрипта, браузер может нормально выполняться без ошибок.

Это потому что<script type=module>Принадлежит по умолчаниюаналогичный deferповедение, поэтомуВыполнение скрипта не блокирует отрисовку страницы, поэтому он будет ждать, пока будет доступен document.body.

зачем говоритьаналогичныйdefer вместо ok, потому что я пытаюсь проверить атрибут defer элемента сценария по умолчанию в консоли браузера (выполнитьscript.defer), результат будет ложным, а не истинным.

Это означает, что если есть несколько<script type=module>Скрипт, браузер может выполнять скрипт не сразу после его загрузки, а выполнять в порядке введения.

Кроме того, подобно традиционным тегам скрипта, мы можем использовать<script>Атрибут async прописан в теге, чтобы браузер загружал модуль асинхронно —Выполнить, как только загрузка будет завершена.

#5 Выполнить один и тот же модуль один раз

Когда на модуль ES ссылаются несколько раз, он будет выполнен только один раз, а содержимое, полученное путем выполнения нескольких операторов импорта, будет одинаковым. для HTML<script>То же самое верно и для тегов, два тега сценария импортируют один и тот же модуль один за другим и будут выполняться только один раз.

Например, следующий сценарий считывает значение счетчика и увеличивает его на единицу:

// app.js
const el = document.getElementById('count');
const count = parseInt(el.innerHTML.trim(), 10);
el.innerHTML = count + 1;

При повторном введении<script src="app.js">Скрипт app.js будет выполнен только один раз, и на странице отобразитсяcount: 1:

<!-- type-module.html -->
<html>
    <head>
        <script type=module src="app.js"></script>
        <script type=module src="app.js"></script>
    </head>
    <body>
        count: <span id="count">0</span>
    </body>
</html>

Приходит вопрос? Как определить «тот же модуль», ответ — тот же URL, включая не только путь, но и?Строка параметров в начале, поэтому, если мы добавим разные параметры в один и тот же скрипт, браузер подумает, что это два разных модуля, и выполнит его дважды.

Если вы добавите параметр url ко второму файлу app.js в приведенном выше HTML-коде:

<script type=module src="app.js"></script>
<script type=module src="app.js?foo=bar"></script>

Браузер дважды выполнит скрипт app.js, и на странице отобразитсяcount: 2:

# 6 Междоменные ограничения CORS

Мы знаем, что важной особенностью обычных тегов скриптов является то, что они не ограничиваются CORS, а script.src может быть любым ресурсом скрипта другого происхождения. Именно по этой причине мы «изобрели» схему JSONP для достижения «междоменного» использования этой функции в первые годы.

Однако тег скрипта type=module усиливает политику безопасности в этом отношении.Когда браузер загружает ресурсы скриптов разных доменов, если сервер не возвращает действительныйAllow-OriginСвязанные коорс заголовки, браузер загрузит запрет на изменение скрипта.

Следующий HTML-код передается на порт 5501 для загрузки скрипта app.js на порт 8082:

<!-- http://localhost:5501/type-module.html -->
<html>
    <head>
        <script type=module src="http://localhost:8082/app.js"></script>
    </head>
    <body>
        count: <span id="count">0</span>
    </body>
</html>

Браузер предотвратит загрузку скрипта app.js.

7. MIME-типы

Когда браузер запрашивает удаленный ресурс, он может вернутьContent-TypeОпределяет MIME-тип загружаемого ресурса (сценарий, HTML, формат изображения и т. д.).

Поскольку браузер всегда был терпим к обычному тегу Script, даже если серверная сторона не возвращает заголовок Content-Type, указанный типом сценария как JavaScript, браузер также будет анализировать сценарий как анализ и выполнение JavaScript.

Но для типа = Type Type Type Type, браузер больше не является терпимым. Если сервер завершает тип MIME удаленного скрипта, не является действительным типом JavaScript, браузер отключает сценарий.

Давайте будем честными: если мы переименуем app.js в app.xyz, мы обнаружим, что страница отключит выполнение скрипта. Поскольку на панели «Сеть» вы можете видеть, что заголовок Content-Type, возвращаемый браузером,chemical/x-xyz, недопустимый тип JavaScript, например:text/javascript.

<html>
<head>
    <script type="module" src="app.xyz"></script>
</head>
<body>
    count: <span id="count">0</span>
</body>
</html>

Содержимое страницы по-прежнемуcount: 0, значение не было изменено, вы можете увидеть соответствующую информацию в консоли и сети:

Модуль ES практика в реальном мире

Программа обратной совместимости

ОК Теперь давайте поговорим о реальности - проблемы совместимости со старыми браузерами, браузеры имеют очень умные решения совместимости при работе с модулями ES.

Впервые встречается на этапе синтаксического анализа HTML-разметки в старых браузерах.<script type="module">тег, браузер думает, что это формат скрипта, который он не может поддерживать, и будет напрямую игнорировать тег; из-за всепрощения браузера он не сообщит об ошибке, а молча продолжит парсить остальную часть HTML.

Поэтому для старых браузеров нам все равно нужно добавить традиционный<script>теги загружают сценарии JS для обратной совместимости.

Второй, и этот второй для обратной совместимости<script>Теги должны игнорироваться новыми браузерами, чтобы новые браузеры не повторяли ту же бизнес-логику.

Чтобы решить эту проблему, тег script добавляет новыйnomoduleАтрибуты. Версии браузеров, которые уже поддерживают type=module, должны игнорировать тег скрипта с атрибутом nomodule, в то время как более старые браузеры не распознают этот атрибут, поэтому это бессмысленно и не будет мешать нормальной логике браузера загружать скрипт тега script в .

<script type="module" src="app.js"></script>
<script nomodule src="fallback.js"></script>

Как показано в приведенном выше коде, новая версия браузера загружает первый тег script и игнорирует второй; старая версия не поддерживает type=module Браузер игнорирует первый тег и загружает второй.

Довольно элегантно, правда? Вам не нужно самостоятельно писать JS-код для обнаружения функций, вы можете напрямую использовать свойства скрипта.

Исходя из этого, поразмыслив, можно смело сделать следующие выводы:

Без характеристического тестирования мыготов к использованию в производствеиспользовать<script type=module>

преимущества

На данный момент пришло время подумать о практических преимуществах, которые может принести нам встроенная поддержка модулей ES в браузере.

#1 Упрощенный рабочий процесс разработки

Сегодня, когда фронтенд-инжиниринг популярен, фронтенд-модульная разработка стала стандартным рабочим процессом. Однако, когда браузер не поддерживает type=module для загрузки шаблонов ES, мы все равно не можем обойтись без webpack в качестве основного инструмента упаковки для упаковки локального модульного кода в пакет и его загрузки.

Но из-за последних браузеров<script type=module>естественная поддержка,теоретическиНаш локальный процесс разработки может быть полностью отделен от сборщиков JS, таких как webpack, просто сделайте следующее:

  1. Используйте файл entry.js напрямую<script>Ссылка на тег
  2. Начиная с entry.js и заканчивая кодами всех зависимых модулей, все они реализованы с использованием решения модуля ES.

Конечно, почемутеоретически, потому что первый пункт легко сделать, а второй пункт требует от нас использовать схему модуляризации ES для всего нашего зависимого кода.В текущей экосистеме внешнего интерфейса наше управление зависимостями использует npm, и большинство пакетов npm Принять стандарт CommonJS и несовместим со стандартом ES.

Но само собой разумеется, что местное развитие может быть легко достигнуто, если соблюдаются два вышеуказанных пункта.настоящий модульный, это для нашегоОпыт отладкиявляется значительным улучшением,webpack --watch, исходная карта или что-то еще.

Теперь вы открываете панель исходного кода в devtools и можете напрямую прерывать своих друзей! Просто отладьте это!

#2 Как способ проверить поддержку новых функцийуровень воды

Модули ES можно использовать в качестве естественного и очень надежного средства проверки версии браузера, которое может сыграть роль в проверке поддержки многих других новых функций.уровень водыэффект.

Логика здесь на самом деле очень проста, мы можем использовать caniuse, чтобы узнать пару браузера<script type="module">Статус поддержки, очевидно, очень требователен к версии браузера.

~> caniuse typemodule
JavaScript modules via script tag ✔ 70.94% ◒ 0.99% [WHATWG Living Standard]
  Loading JavaScript module scripts using `<script type="module">` Includes support for the `nomodule` attribute. #JS

  IE ✘
  Edge ✘ 12+ ✘ 15+¹ ✔ 16+
  Firefox ✘ 2+ ✘ 54+² ✔ 60+
  Chrome ✘ 4+ ✘ 60+¹ ✔ 61+
  Safari ✘ 3.1+ ◒ 10.1+⁴ ✔ 11+
  Opera ✘ 9+ ✘ 47+¹ ✔ 48+

    ¹Support can be enabled via `about:flags`
    ²Support can be enabled via `about:config`
    ⁴Does not support the `nomodule` attribute

PS: порекомендуйте инструмент npm:caniuse-cmd,передачаnpm i -g caniuse-cmdВы можете использовать командную строку для быстрого запроса caniuse, поддержки нечеткого поиска

Это означает, что если браузер поддерживает загрузку модулей ES, его номера версий должны быть больше, чем указанные в таблице выше.

Взяв за пример Chrome, подумайте дальше, а это значит, что в коде шаблона ES мы можемИз полифиллаВсе Chrome 61 поддерживают эту функцию. Этот список содержит множествоновыйФункции, многие из которых мы не осмеливаемся использовать непосредственно в производственной среде, но с<script type=module>Гарантия, любой Service Worker, Promise, Cache API, Fetch API и т.д. можно смело поднимать.

Вот выступление инженера Google Сэма Торогуда на Polymer Summit 2017.ES6 Modules in the Real WorldСкриншот слайдов примерно описывает сравнительную таблицу поддержки type=module и других общих новых функций нескольких основных браузеров того времени, что может помочь нам получить общее представление.

Задача — переосмысление интерфейсных сборок

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

#1 Увеличение количества запросов

Для браузеров, которые уже поддерживают шаблоны ES, если мы введем ES-модуль из тега script, мы, естественно, столкнемся с такой проблемой. Предполагая, что у нас есть такая цепочка зависимостей, это означает, что браузер должен загрузить 6 модулей один за другим:

entry.js
├──> logger.js -> util.js -> lodash.js
├──> constants.js
└──> event.js -> constants.js

Для традиционного HTTP-сайта это будет означать отправку 6 отдельных HTTP-запросов, что противоречит нашим обычным методам настройки производительности.

Так что противоречие здесь на самом делеУменьшите количество HTTP-запросова такжеУлучшить степень повторного использования модуляПротиворечия между:

  • В модульном режиме разработки модулей будет все больше и больше по мере естественного роста кода.
  • Чем больше модулей, тем больше запросов должен сделать браузер

Столкнувшись с этим противоречием, нам необходимо подумать о стратегиях оптимизации, основанных на характеристиках бизнеса, и принять компромиссные решения или компромиссы.

Направление, о котором стоит подумать, — оптимизация загрузки модулей с помощью технологии HTTP 2.

С помощью технологии Server Push вы можете выбрать наиболее часто повторно используемые общедоступные модули в приложении и как можно раньше отправить эти модули в браузер. Например, при запросе HTML сервер использует одно и то же соединение для передачи модулей util.js, lodash.js, Constants.js в приведенном выше примере и HTML-документа на сторону браузера, чтобы, когда браузеру нужно загрузить эти модули, он может избежать повторного активного инициирования запроса и выполнить его напрямую.

PS: Настоятельно рекомендуется прочитать статью Джейка Арчибальда:HTTP/2 push is tougher than I thought

С помощью запроса на слияние HTTP/2 и функции сжатия заголовков также можно решить проблему медленной загрузки, вызванную увеличением количества запросов.

Конечно, использование HTTP/2 представляет собой проблему для наших серверных поставщиков HTTP-сервисов, и, конечно же, это также может быть использовано как возможность побудить нас изучить и применить протокол HTTP/2.

PS: в других статьях также обсуждается использование механизма кэширования предварительной выборки для оптимизации загрузки ресурсов, что можно дополнительно изучить как направление.

# 2 Остерегайтесь ада зависимостей — Управление версиями и кэшем

В программной инженерии есть известная шутка:

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

Видимое управление кешем — это то, что нельзя недооценивать.

Как мы традиционно выполняем развертывание JS-скриптов с контролем версий? В сочетании с механизмом HTTP-кэширования рекомендуется следующее:

  • имя файла плюс номер версии
  • Установить максимальный срок хранения кеша
  • При наличии обновления версии измените часть номера версии в имени файла и измените путь script.src.

Если мы введем только один или два устойчивых*.jsБиблиотечный сценарий, а затем представить бизнес-сценарийbundle.xxx.jsТакая практика можно сказать, что не большая проблема.

Но представьте, что теперь мы должны перейти непосредственно к новому модулю SHIP ES для браузера.По мере развития бизнеса мы должны управлять более чем дюжиной или десятками зависимых модулей, для большого сайта десятки страниц, сотни модулей не являются неожиданными .

С таким сложным графом зависимостей и таким количеством модулей так легко управлять кешем и обновлять версии JS-файлов?

Например, у нас есть такой график зависимостей:

./page-one/entry.js
├──> logger.js -> util.js -> lodash.js
├──> constants.js
├──> router.js -> util.js
└──> event.js -> util.js

./page-two/entry.js
├──> logger.js -> util.js -> lodash.js
└──> router.js -> constants.js

Теперь мы изменили общий компонентutil.js, в рабочей среде на стороне браузера стоит старая версияutil-1.0.0.jsдлинный кеш, но поскольку регистратор, маршрутизатор и компоненты событий зависят от компонента util, это означает, что мы генерируемutil-1.1.0.jsверсии, вам необходимо соответствующим образом изменить операторы импорта в других компонентах, а также изменить HTML в<script>Этикетка.

// router-2.0.0.js -> router-2.1.0.js
import * as util from './util-1.1.0.js'

// page-one/entry-3.1.2.js -> page-one/entry-3.2.0.js
import * as util from './util-1.1.0.js'

// page-one.html
<script type="module" src="./page-one/entry-3.2.0.js">

// ... page-two 相关脚本也要一起修改

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

# 3 Должен оставаться обратно совместимым

Мы упоминали об этом выше. На практике мы должны помнить, что при развертывании в производственной среде нам все еще нужно упаковать файл bundle.js, который доступен для старых браузеров. Этот шаг является существующим рабочим процессом. Добавьте атрибут nomodule в файл тег сценария.

Дальше возникает проблема, иногда для того, чтобы максимально уменьшить количество запросов страниц, мы будем делать ключевые JS-скрипты напрямуюв линиюС разметкой HTML по сравнению<script src=...>Внедрение внешнего скрипта, кстати, еще раз уменьшило запрос.

Если мы возьмем<nomodule>Тег script атрибута будет игнорироваться новой версией браузера, поэтому для новой версии браузера лучше не встраивать сюда содержимое скрипта nomodule, иначе размер файла увеличится, но эта часть скрипт не будет выполняться, зачем заморачиваться?

так вот<script nomodule>Решение о том, является ли сценарий встроенным или внешним, по-прежнему остается за разработчиком.

#4 Обновите модули CommonJS до стандартных модулей ES

Если мы используем тег script для внедрения стандартных модулей ES в рабочую среду, то мы должны реорганизовать весь код как зависимые модули и зависимые библиотеки в модули ES.В настоящее время текущий статус внешней экосистемы:

  • Большинство модулей библиотеки зависимостей совместимы со стандартом CommonJS, а некоторые — со стандартом ES.
  • Зависимости развертываются в npm и устанавливаются в каталог node_modules.
  • Существующий бизнес-код принимаетrequire(${npm模块名})способ ссылки на пакеты в node_modules.

Перед нами стоят следующие задачи:

  • Большое количество модулей CommonJS необходимо преобразовать в стандартные модули ES, что требует большой работы.
  • Необходимо провести рефакторинг node_modulesМешокСсылочный метод, используйте ссылку относительного пути.

#5 Не забудьте сжать файлы модуля ES

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

И если мы хотим отправить собственные модули ES в новые браузеры, мы не можем игнорировать сжатие файлов модулей ES.

OK Мы думаем о хорошо известном uglify, который обычно используется при работе с кодом ES5.К сожалению, поддержка минимизации uglify для кода ES6 не оптимистична. В настоящее время общий сценарий uglify заключается в том, что сначала мы используем babel для экранирования кода ES6, чтобы получить код ES5, а затем используем uglify для минимизации кода ES5.

Для минимизации кода ES6 лучший вариант — от команды babel.babel-minify(ранее Бабили).

#6. Заключение?

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

Итак, мое отношение к загрузке модулей ES в браузере:

  • На этапе разработки, пока браузер его поддерживает, используйте его агрессивно! Просто сделай это!
  • Не теряйте набор пакетов локальной сборки webpack, локальная сборка по-прежнему остается и будет ядром фронтенд-инжиниринга в течение длительного времени.
  • Даже если производственная среда напрямую обслуживает нативные модули, процесс сборки все равно требуется.
  • Не используйте его вслепую в производственной среде. Во-первых, вы должны разработать хорошую схему управления зависимостью и Cache Update, а также развертывая поддержку задней части HTTP / 2.

Будущий модуль ES?

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

Но в области фронтальной модульности за модулями ES, несомненно, будущее.

Комитет стандарта EcmaScript TC39 также продвигает обновление стандарта модуля.Студенты, которые заинтересованы в разработке стандарта, могут продолжить изучение.Некоторые моменты, о которых стоит упомянуть, включают:

  • tc39/proposal-dynamic-importПоддержка функции динамического импорта вступила в стадию 3.
  • tc39/proposal-import-metaУкажите import.meta для программного получения метаинформации, связанной с модулем, в коде.
  • tc39/tc39-module-keysПри использовании для ссылок на сторонние модули улучшения безопасности теперь находятся на этапе 1.
  • tc39/proposal-modules-pragmaПодобно директиве «user strict» для указания строгого режима, используйте директиву «use module», чтобы указать обычный файл длямодульЗагрузка схемы, сейчас на этапе 1
  • tc39/proposal-module-getАналогично Object.defineProperty для определения геттера для свойства, позволяющегоexport get prop() { return ... }Этот синтаксис реализует динамический экспорт

Справочные ресурсы

Примечание. Заглавное изображение взято изContentful