Примечание. В этой статье cjs используется для представления модуля commonjs. Используйте esm для модуля es.
предисловие
Для нашего текущего проекта веб-разработки мы с удовольствием используем экспорт/импорт для модульной разработки. Кажется, все так просто~
Но мы также знаем, что esm был введен в спецификацию только после es6. До этого в спецификации языка js не было модульности (конечно, раньше веб не был таким сложным). cjs - это продукт до того, как не будет esm. Именно простая и эффективная модульная конструкция cjs делает cjs любимым многими разработчиками. Хотя, esm выдвигал модульную спецификацию на уровне языка, но до появления es6 уже было большое количество модулей cjs, поэтому требовать мигрировать все модули для использования esm невозможно. Модульная реализация cjs и esm по-прежнему сильно различается, но в проекте неизбежно, что esm вызывает cjs, так как же это достигается?
переводить
Esm и cjs — это, по сути, две вещи, и спецификация esm описывает только поведение модуля es, а совместимость с cjs не включена. Причина, по которой мы можем счастливо называть друг друга, зависит от поддержки компилятора позади вас (нет такой вещи, как спокойное время, просто кто-то несет за вас бремя [doge]). Общая идея состоит в том, чтобы преобразовать esm в форму cjs, а затем единообразно их обработать. Во-первых, давайте избавимся от Babel, Сверните эти вещи, подумайте о себе как о разработчике компилятора, как бы вы справились с проблемой esm, вызывающей cjs.
Предположим, есть модуль esm:
//lib.js
export const a = 1;
export const b = 2;
export default () => {
return 3;
};
Теперь дело за вами, вам нужно обработать этот код в форму cjs. Сначала мы видим, что lib js имеет два экспорта, этот простой
Сделаем взаимно однозначное соответствие между exports.xx модуля commonjs и export const xx модуля es. Затем идет экспорт по умолчанию.Проверив информацию, мы знаем, что в модуле es мы можем думать, чтоexport default
Является ли экспортировать переменную или метод по умолчанию. Это легко, просто экспортируйте.default = xx напрямую, поэтому я получаю следующий код cjs:
// lib.js [cjs]
module.exports {
a: 1,
b: 2,
default: function(){return 3;}
}
Процесс перевода выглядит нормально, и мы с радостью сохраняем cjs-версию lib.js. В это время кто-то использовал lib.js в проекте в виде модуля es:
import fn, { a, b } from 'lib';
console.log(a);
console.log(b);
console.log(fn());
Так уж получилось, что эта славная работа по переводу все еще зависит от вас.Когда вы смотрите на название, это непросто, просто сделайте это с небольшим импортом. Итак, преобразование выполнено:
const { a, b } = require('lib');
const fn = require('lib').default;
console.log(a);
console.log(b);
console.log(fn());
好像看上去没什么问题。因为代码逻辑上,完全正确啊。但是注意这里的 default, 我们把 default 当成 exports 的一个属性导出。如果使用这种转换规则的话, 那么在 react 项目中使用这个编译器将会收到一堆报错。 потому чтоimport react from 'react'
будет переведено наconst react = require('react').default;
, но для объекта, экспортируемого модулем реакции, нет свойства по умолчанию. Причем не только реагируют, многие cjs-либы
Экспортируйте только такой метод или класс:module.exports = function() {}
. Затем, чтобы использовать эти библиотеки cjs напрямую, вы можете написать только:import * as React from 'react'
, экспортировать в целом. Это тоже
То, как typescript обрабатывал это раньше.
Однако включите в проекте typescriptesModuleInterop
Или в проекте babel вы импортируете модуль cjs следующим образом:import react from 'react'
Этим, благодаря этим волшебным компиляторам.
Babel обрабатывает esm с помощью cjs
Давайте воспользуемся Babel Online напрямую, чтобы увидеть, как он обрабатывает наш lib.js выше:
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = exports.b = exports.a = void 0;
var a = 1;
exports.a = a;
var b = 2;
exports.b = b;
var _default = 3;
exports.default = _default;
Здесь это ничем не отличается от того, как мы с этим справляемся. Просто повесьте свойство __esModule на экспорт. Потом смотрим импорт, сначала смотрим самый простой:
import { a, b } from 'lib';
console.log(a);
console.log(b);
После обработки бабелем получается вот так:
'use strict';
var _lib = require('lib');
console.log(_lib.a);
console.log(_lib.b);
Когда Babel обрабатывает эту часть, он использует общий импорт. А как насчет импорта по умолчанию?
'use strict';
var _lib = _interopRequireDefault(require('lib'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
console.log(_lib.default);
Здесь отмечается, что хотя babel по-прежнему использует форму общего импорта, он обертывает слой _interopRequireDefault и отдельно обрабатывает форму импорта по умолчанию. Мы также видели ранее, что модуль es будет в
При экспорте монтируется атрибут __esModule.При импорте, если это es модуль, то он будет возвращен напрямую, если нет, то будет обработан cjs, и весь модуль будет навешан на дефолтный атрибут объекта, чтобы последующие операции были унифицированы. Вот почему
Вы можете использовать babel в проекте сimport react from 'react'
используется форма.
накопитель обрабатывает esm с помощью cjs
Большинство операций по свертыванию для преобразования esm в cjs одинаковы, и свертка lib.js будет обрабатывать их следующим образом:
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const a = 1;
const b = 2;
var module_2 = 3;
exports.a = a;
exports.b = b;
exports.default = module_2;
импорт обрабатывается следующим образом:
function _interopDefault(ex) {
return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
}
var c = require('lib');
var c__default = _interopDefault(c);
console.log(c.a);
console.log(c.b);
console.log(c__default);
Когда накопительный пакет обрабатывает импорт по умолчанию, он использует_interopDefault
Метод заключается в том, чтобы узнать, есть ли в модуле атрибут по умолчанию. Обратите внимание, что сам накопительный пакет не использует флаг __esModule для работы со значением по умолчанию, но при компиляции esm этот флаг добавляется, так что даже если вы представляете собой пакет, созданный накопительным пакетом, его можно успешно использовать в проекте babel.
При использовании свертки следует отметить, что если есть только один экспорт по умолчанию, свертка будет обрабатываться по-другому:
// esm
export default function add(a, b) {
console.log(a + b);
}
// rollup 处理成 cjs
function add(a, b) {
console.log(a + b);
}
module.exports = add;
Мы видим, что этот накопитель напрямую связывает экспорт по умолчанию с module.exports. И Babel по-прежнему монтируется на exports.default через идентификатор __esModule. Это место требует особого внимания, если перед ним есть библиотека.
Обработанный с помощью babel, пользователь cjs может использовать толькоrequire('lib').default
форма для использования. Однажды автор этой библиотеки решил использовать rollup, после чего пользователи cjs захотели использовать новую библиотеку, только чтобы изменить исходный код.
наконец
Чтобы успешно использовать модули cjs в esm, более распространенной формой является преобразование в cjs для унифицированной обработки.В процессе преобразования esm в cjs основная проблема отражается в экспорте по умолчанию, что усложняет ситуацию. В нашей обычной разработке мы можем привыкнуть к экспорту по умолчанию, особенно в реактивных проектах, мы, естественно, пишемexport default myComponent
. Если ваши собственные проекты построены в модульной системе es, экспорт по умолчанию, безусловно, является
Очень удобный способ. Однако, если вам нужно поддерживать cjs и esm одновременно, и проблема взаимных вызовов задействована, то вы должны тщательно продуматьdefault export
. Потому что то, как esm и cjs успешно используют друг друга, зависит не от вас, а от инструментов, которые помогут вам их упаковать.