Схема загрузки модулей серии ES6

GitHub JavaScript браузер Babel
Схема загрузки модулей серии ES6

предисловие

В этой статье мы сосредоточимся на следующих четырех спецификациях загрузки модулей:

  1. AMD
  2. CMD
  3. CommonJS
  4. Модули ES6

Наконец, я расширим принцип компиляции Babel и упаковки webpack.

require.js

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

Каталог проекта:

* project/
    * index.html
    * vender/
        * main.js
        * require.js
        * add.js
        * square.js
        * multiply.js

index.htmlСодержание следующее:

<!DOCTYPE html>
<html>
    <head>
        <title>require.js</title>
    </head>
    <body>
        <h1>Content</h1>
        <script data-main="vender/main" src="vender/require.js"></script>
    </body>
</html>

data-main="vender/main"Указывает, что основной модульvenderвнизmain.js.

main.jsКонфигурация выглядит следующим образом:

// main.js
require(['./add', './square'], function(addModule, squareModule) {
    console.log(addModule.add(1, 1))
    console.log(squareModule.square(3))
});

Первый параметр require представляет путь к зависимому модулю, а второй параметр представляет содержимое этого модуля.

Отсюда видно, что主模块полагатьсяadd 模块а такжеsquare 模块.

Давайте посмотримadd 模块которыйadd.jsСодержание:

// add.js
define(function() {
    console.log('加载了 add 模块');
    var add = function(x, y) {&emsp;
        return x + y;
    };

    return {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        add: add
    };
});

requirejsдобавлено глобальноdefineфункции, вам нужно только написать модуль таким образом.

Что делать, если зависимый модуль зависит от других модулей?

Давайте посмотрим主模块зависимыйsquare 模块,square 模块Роль состоит в том, чтобы найти количество квадратов, таких как вход 3, возврат 9, модуль, полагающийся на один乘法模块, модуль умноженияmultiply.jsКод выглядит следующим образом:

// multiply.js
define(function() {
    console.log('加载了 multiply 模块')
    var multiply = function(x, y) {&emsp;
        return x * y;
    };

    return {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        multiply: multiply
    };
});

а такжеsquare 模块собирается использоватьmultiply 模块, по сути способ написания такой же, как добавление зависимого модуля в main.js:

// square.js
define(['./multiply'], function(multiplyModule) {
    console.log('加载了 square 模块')
    return {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        square: function(num) {
            return multiplyModule.multiply(num, num)
        }
    };
});

require.js автоматически проанализирует зависимости и загрузит модули, которые необходимо загрузить правильно.

Демонстрационный адрес проекта requirejs:GitHub.com/ в настоящее время имеет бриз…

И если мы откроем его в браузереindex.html, порядок печати такой:

加载了 add 模块
加载了 multiply 模块
加载了 square 模块
2
9

AMD

В предыдущем разделе мы сказали следующее:

requirejsдобавлено глобальноdefineфункции, вам нужно только написать модуль таким образом.

Так что же означает это соглашение?

Относится к спецификации определения асинхронного модуля (AMD).

Так что на самом делеAMD — это канонический результат продвижения определений модулей RequireJS.

ты иди посмотриСпецификация AMDОсновное содержание состоит в том, чтобы определить, как написать функцию определения.Пока вы пишете модули и зависимости в соответствии с этой спецификацией, require.js может быть правильно проанализирован.

sea.js

В Китае CMD часто упоминается вместе с AMD Что такое CMD? мы начинаем сsea.jsначать с использования.

Каталог файла совпадает с каталогом проекта requirejs:

* project/
    * index.html
    * vender/
        * main.js
        * require.js
        * add.js
        * square.js
        * multiply.js

index.htmlСодержание следующее:

<!DOCTYPE html>
<html>
<head>
    <title>sea.js</title>
</head>
<body>
    <h1>Content</h1>
    <script src="vender/sea.js"></script>
    <script>
    // 在页面中加载主模块
    seajs.use("./vender/main");
    </script>
</body>

</html>

Содержимое main.js выглядит следующим образом:

// main.js
define(function(require, exports, module) {
    var addModule = require('./add');
    console.log(addModule.add(1, 1))

    var squareModule = require('./square');
    console.log(squareModule.square(3))
});

Содержимое add.js следующее:

// add.js
define(function(require, exports, module) {
    console.log('加载了 add 模块')
    var add = function(x, y) {&emsp;
        return x + y;
    };
    module.exports = {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        add: add
    };
});

Содержимое Square.js выглядит следующим образом:

define(function(require, exports, module) {
    console.log('加载了 square 模块')
    var multiplyModule = require('./multiply');
    module.exports = {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        square: function(num) {
            return multiplyModule.multiply(num, num)
        }
    };

});

Содержаниеmulti.js выглядит следующим образом:

define(function(require, exports, module) {
    console.log('加载了 multiply 模块')
    var multiply = function(x, y) {&emsp;
        return x * y;
    };
    module.exports = {&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;
        multiply: multiply
    };
});

Та же структура зависимостей, что и в первом примере, т.е. main зависит от сложения и квадрата, а квадрат зависит от умножения.

Адрес DEMO проекта Seajs:GitHub.com/ в настоящее время имеет бриз…

И если мы откроем его в браузереindex.html, порядок печати такой:

加载了 add 模块
2
加载了 square 模块
加载了 multiply 模块
9

CMD

Как и AMD, CMD на самом деле является стандартизированным выводом определения модуля SeaJS в процессе продвижения.

ты иди посмотриСпецификация CMDОсновное содержание состоит в том, чтобы описать, как определять модули, как импортировать модули и как экспортировать модули.Пока вы пишете код в соответствии с этой спецификацией, sea.js может правильно его анализировать.

Разница между AMD и CMD

На примере sea.js и require.js:

1. CMD уважаемыйзависит от ближайшего, рекомендованный AMDЗависит от фронта. Посмотрите на main.js в обоих проектах:

// require.js 例子中的 main.js
// 依赖必须一开始就写好
require(['./add', './square'], function(addModule, squareModule) {
    console.log(addModule.add(1, 1))
    console.log(squareModule.square(3))
});
// sea.js 例子中的 main.js
define(function(require, exports, module) {
    var addModule = require('./add');
    console.log(addModule.add(1, 1))

    // 依赖可以就近书写
    var squareModule = require('./square');
    console.log(squareModule.square(3))
});

2. Зависимость модуля AMD являетсявыполнить досрочно, CMDотложенное исполнение. Посмотрите на порядок печати в двух пунктах:

// require.js
加载了 add 模块
加载了 multiply 模块
加载了 square 模块
2
9
// sea.js
加载了 add 模块
2
加载了 square 模块
加载了 multiply 模块
9

AMD загружает модули, которые необходимо использовать, перед выполнением кода, в то время как CMD загружает файлы модулей, когда они требуются, а затем выполняет их после загрузки.

благодарный

Спасибо require.js и sea.js за продвижение модульности JavaScript.

CommonJS

AMD - это спецификация модулей и браузером CMD, а также сторона сервера, такая как узел, Somedjs Speciation используется.

Способ экспорта модуля:

var add = function(x, y) {&emsp;
    return x + y;
};

module.exports.add = add;

Как импортировать модули:

var add = require('./add.js');
console.log(add.add(1, 1));

Давайте изменим предыдущий пример на спецификацию CommonJS:

// main.js
var add = require('./add.js');
console.log(add.add(1, 1))

var square = require('./square.js');
console.log(square.square(3));
// add.js
console.log('加载了 add 模块')

var add = function(x, y) {&emsp;
    return x + y;
};

module.exports.add = add;
// multiply.js
console.log('加载了 multiply 模块')

var multiply = function(x, y) {&emsp;
    return x * y;
};

module.exports.multiply = multiply;
// square.js
console.log('加载了 square 模块')

var multiply = require('./multiply.js');

var square = function(num) {&emsp;
    return multiply.multiply(num, num);
};

module.exports.square = square;

Адрес демонстрации проекта CommonJS:GitHub.com/ в настоящее время имеет бриз…

если мы выполнимnode main.js, порядок печати такой:

加载了 add 模块
2
加载了 square 模块
加载了 multiply 模块
9

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

CommonJS и AMD

Цитата учителя Жуань ИфэнСтандартное справочное руководство по JavaScript (альфа-версия):

Спецификация CommonJS загружает модули синхронно, то есть только после завершения загрузки можно выполнять последующие операции.

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

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

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

ES6

ECMAScript2015 определяет новую схему загрузки модулей.

Способ экспорта модуля:

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

Как импортировать модули:

import {firstName, lastName, year} from './profile';

Давайте изменим приведенный выше пример на спецификацию ES6:

Структура каталогов такая же, как у requirejs и seajs.

<!DOCTYPE html>
<html>
    <head>
        <title>ES6</title>
    </head>
    <body>
        <h1>Content</h1>
        <script src="vender/main.js" type="module"></script>
    </body>
</html>

Уведомление! Браузер загружает модули ES6, также используя<script>тег, но добавитьtype="module"Атрибуты.

// main.js
import {add} from './add.js';
console.log(add(1, 1))

import {square} from './square.js';
console.log(square(3));
// add.js
console.log('加载了 add 模块')

var add = function(x, y) {
    return x + y;
};

export {add}
// multiply.js
console.log('加载了 multiply 模块')

var multiply = function(x, y) {&emsp;
    return x * y;
};

export {multiply}
// square.js
console.log('加载了 square 模块')

import {multiply} from './multiply.js';

var square = function(num) {&emsp;
    return multiply(num, num);
};

export {square}

Демонстрационный адрес проекта ES6-Module:GitHub.com/ в настоящее время имеет бриз…

Стоит отметить, что в Chrome, если вы откроете его напрямую, будет сообщено об ошибке между доменами.Вы должны открыть сервер, чтобы убедиться, что файл имеет то же происхождение, прежде чем он сможет быть эффективным.

Чтобы проверить этот эффект, вы можете:

cnpm install http-server -g

Затем перейдите в этот каталог и выполните

http-server

открыть в браузереhttp://localhost:8080/чтобы увидеть эффект.

Порядок печати такой:

加载了 add 模块
加载了 multiply 模块
加载了 square 模块
2
9

Это согласуется с результатом выполнения require.js, то есть сначала загружаются используемые модули, а затем выполняется код.

ES6 и CommonJS

Цитата учителя Жуань ИфэнНачало работы с ECMAScript 6:

У них есть два основных отличия.

  1. Модули CommonJS выводят копию значения, модули ES6 выводят ссылку на значение.

  2. Модули CommonJS загружаются во время выполнения, а модули ES6 представляют собой скомпилированные интерфейсы.

Второе отличие видно из результатов печати двух проектов, причина этого различия:

Поскольку CommonJS загружает объект (т. е. свойство module.exports), объект не создается до тех пор, пока не завершится выполнение скрипта. Модуль ES6 не является объектом, его внешний интерфейс — это просто статическое определение, которое будет сгенерировано на этапе статического анализа кода.

Сосредоточьтесь на объяснении первого отличия.

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

Например:

// 输出模块 counter.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
    counter: counter,
    incCounter: incCounter,
};
// 引入模块 main.js
var mod = require('./counter');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

Как только модуль counter.js загружен, его внутренние изменения не повлияют на вывод mod.counter. Это связано с тем, что mod.counter является примитивным значением и кешируется.

Но если вы измените счетчик на ссылочный тип:

// 输出模块 counter.js
var counter = {
    value: 3
};

function incCounter() {
    counter.value++;
}
module.exports = {
    counter: counter,
    incCounter: incCounter,
};
// 引入模块 main.js
var mod = require('./counter.js');

console.log(mod.counter.value); // 3
mod.incCounter();
console.log(mod.counter.value); // 4

значение может быть изменено. Но также можно сказать, что это «копия значения», за исключением того, что для ссылочных типов значение ссылается на ссылку.

И если мы изменим этот пример на ES6:

// counter.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './counter';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

Это потому что

Модули ES6 работают иначе, чем CommonJS. Когда движок JS статически анализирует скрипт, он генерирует ссылку только для чтения, когда встречает команду импорта загрузки модуля. Когда скрипт действительно выполняется, в соответствии с этой ссылкой только для чтения перейдите к загруженному модулю, чтобы получить значение. Другими словами, импорт в ES6 чем-то напоминает «символическую ссылку» в Unix-системах: исходное значение изменяется, и значение, загруженное импортом, также изменяется. Таким образом, на модули ES6 ссылаются динамически и они не кэшируют значения, а переменные в модулях привязаны к модулю, в котором они находятся.

Babel

Учитывая проблему поддержки браузера, если вы хотите использовать синтаксис ES6, вы обычно можете использовать Babel, вы можете использовать Babel, если вы можете использовать Babel?

Давайте посмотрим, как Babel компилирует синтаксис импорта и экспорта.

// ES6
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};
// Babel 编译后
'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

exports.firstName = firstName;
exports.lastName = lastName;
exports.year = year;

Это немного странно? Скомпилированный синтаксис больше похож на спецификацию CommonJS, а затем посмотрите на результат компиляции импорта:

// ES6
import {firstName, lastName, year} from './profile';
// Babel 编译后
'use strict';

var _profile = require('./profile');

Вы обнаружите, что Babel просто преобразует синтаксис модуля ES6 в синтаксис модуля CommonJS, но браузеры не поддерживают синтаксис этого модуля, поэтому запуск непосредственно в браузере сообщит об ошибке, если вы хотите запустить в браузере, вам все равно нужно использовать упаковку Код пакета инструментов.

webpack

После того, как Babel преобразует модули ES6 в CommonJS, как его упаковывает webpack? Как он упаковывает эти файлы вместе, чтобы зависимости правильно обрабатывались и запускались в браузере?

Почему синтаксис CommonJS вообще не поддерживается в браузерах?

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

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

Как это смоделировать?

В качестве примера возьмем Square.js в проекте CommonJS, который зависит от модуля умножения:

console.log('加载了 square 模块')

var multiply = require('./multiply.js');


var square = function(num) {&emsp;
    return multiply.multiply(num, num);
};

module.exports.square = square;

webpack завершит его и введет эти переменные:

function(module, exports, require) {
    console.log('加载了 square 模块');

    var multiply = require("./multiply");
    module.exports = {
        square: function(num) {
            return multiply.multiply(num, num);
        }
    };
}

Тогда во что webpack упакует код проекта CommonJS? Я написал упрощенный пример, который вы можете скопировать прямо в свой браузер, чтобы увидеть эффект:

// 自执行函数
(function(modules) {

    // 用于储存已经加载过的模块
    var installedModules = {};

    function require(moduleName) {

        if (installedModules[moduleName]) {
            return installedModules[moduleName].exports;
        }

        var module = installedModules[moduleName] = {
            exports: {}
        };

        modules[moduleName](module, module.exports, require);

        return module.exports;
    }

    // 加载主模块
    return require("main");

})({
    "main": function(module, exports, require) {

        var addModule = require("./add");
        console.log(addModule.add(1, 1))

        var squareModule = require("./square");
        console.log(squareModule.square(3));

    },
    "./add": function(module, exports, require) {
        console.log('加载了 add 模块');

        module.exports = {
            add: function(x, y) {
                return x + y;
            }
        };
    },
    "./square": function(module, exports, require) {
        console.log('加载了 square 模块');

        var multiply = require("./multiply");
        module.exports = {
            square: function(num) {
                return multiply.multiply(num, num);
            }
        };
    },

    "./multiply": function(module, exports, require) {
        console.log('加载了 multiply 模块');

        module.exports = {
            multiply: function(x, y) {
                return x * y;
            }
        };
    }
})

Окончательный результат выполнения:

加载了 add 模块
2
加载了 square 模块
加载了 multiply 模块
9

Ссылаться на

серия ES6

Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…

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

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