Подробное объяснение смешанного использования import, require, export и module.exports.

внешний интерфейс NPM Webpack

Обновлено 29.07.2020 Команда электронной коммерции Hangzhou Youzan срочно нуждается в 10+ HC, охватывающих интерфейс, Java, тестирование, если вы заинтересованы, пожалуйста, свяжитесь с lvdada@youzan.com или напрямую свяжитесь с wx: wsldd225

предисловие

Поскольку модульная система es6 используется в различных местах, ее с удовольствием используют.import export default, но вы также увидите использование спецификации commonjs в старом проекте.require module.exports. Есть даже случаи, когда эти два часто используются взаимозаменяемо. В использовании проблем нет, но взаимосвязь и разница непонятны, и это сбивает с толку. Например:

  1. Почему некоторые места используютсяrequireЧтобы сослаться на модуль, вам нужно добавитьdefault?require('xx').default
  2. Инструкции часто можно увидеть в документах, на которые ссылаются основные компоненты пользовательского интерфейса.import { button } from 'xx-ui'Это принесет весь компонентный контент, требующий дополнительной настройки babel, такой какbabel-plugin-component?
  3. Почему я могу использовать импорт es6 для ссылки на модули, определенные спецификацией commonjs, или наоборот?
  4. Когда мы просматриваем некоторые модули компонентов пользовательского интерфейса, загруженные npm (например, в lib-файле element-ui), все, что мы видим, — это файлы js, скомпилированные webpack, на которые можно ссылаться с помощью import или require. Но наш обычный скомпилированный js больше не может быть импортирован другими модулями, почему?
  5. Какую роль играет Babel в модульной сцене? а вебпак? Кто из них сыграл ключевую роль?
  6. Я слышал, что es6 также имеетtree-shakingфункция, как я могу использовать эту функцию?

Если вы знаете все эти вопросы, то можете закрывать эту статью, если сомневаетесь, эта статья для вас!

Роль webpack и babel в модуляризации

Принцип модульности webpack

Сам webpack поддерживает модульную систему, совместимую со всеми спецификациями модулей в рамках внешнего исторического процесса, включаяamd commonjs es6д., в этой статье основное внимание уделяетсяcommonjs es6Спецификация. Реализация модульности фактически находится в финальном скомпилированном файле.

Я написал демо, чтобы лучше показать эффект.

// webpack

const path = require('path');

module.exports = {
  entry: './a.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  }
};


// a.js
import a from './c';

export default 'a.js';
console.log(a);


// c.js

export default 333;

(function(modules) {

  
  function __webpack_require__(moduleId) {
    var module =  {
      i: moduleId,
      l: false,
      exports: {}
    };
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    return module.exports;
  }

  return __webpack_require__(0);
})([
  (function (module, __webpack_exports__, __webpack_require__) {

    // 引用 模块 1
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1);

/* harmony default export */ __webpack_exports__["default"] = ('a.js');
console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]);

  }),
  (function (module, __webpack_exports__, __webpack_require__) {

    // 输出本模块的数据
    "use strict";
    /* harmony default export */ __webpack_exports__["a"] = (333);
  })
]);

Приведенный выше код js представляет собой код, скомпилированный с использованием веб-пакета (упрощенный), который содержит код времени выполнения веб-пакета, относящийся к реализации модуля.

Если мы снова упростим код, мы обнаружим, что это самовыполняющаяся функция.

(function(modules) {

})([]);

Входным параметром самовыполняющейся функции является массив, этот массив содержит все модули, завернутые в функцию.

Логика в теле самовыполняющейся функции — это логика модуля обработки. Ключ__webpack_require__функция, эта функцияrequireилиimportВместо этого мы видим, что функция сначала определяется в теле функции, а затем вызывается. Здесь будетmoduleId, который в этом примере равен 0, который является нашим входным модулемa.jsСодержание.

Посмотрим__webpack_require__выполнено в течение

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;

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

  • module
  • module.exports
  • webpack_require

Давайте посмотрим на логику (упрощенную) первой функции (т.е. входного модуля):

function (module, __webpack_exports__, __webpack_require__) {

/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1);

    /* harmony default export */ __webpack_exports__["default"] = ('a.js');
    console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]);

  }

Мы видим, что входной модуль вызывается снова__webpack_require__(1)для ссылки на вторую функцию в группе параметров.

Затем участвующие__webpack_exports__объект добавленdefaultсвойства и присваивать значения.

Здесь мы видим принцип модульности, здесь__webpack_exports__это этот модульmodule.exportsПередача параметров по ссылке на объект косвенно добавляет свойства в module.exports.

Наконец, будет возвращен module.exports. Это завершено__webpack_require__миссия функции.

Например, в модуле ввода он снова вызывается__webpack_require__(1), вы получите возврат этого модуляmodule.exports.

** Но в основе этой самовыполняющейся функцииwebpackВывод модуля ввода также будет возвращен **

return __webpack_require__(0);

В настоящее время это скомпилированный js, вывод входного модуля (т.е.module.exports) для вывода не влияет, только текущая область. Этот js не может использоваться другими модулями для продолженияrequireилиimportспособ цитирования.

Роль Бабеля

Само собой разумеется, что модульная схема веб-пакета превратила модульность es6 в модульность веб-пакета, но с остальной частью синтаксиса es6 все еще необходимо работать для обеспечения совместимости. babel специально разработан для обработки переходов с es6 на es5. Конечно, это также включает преобразование синтаксиса модуля es6.

На самом деле идеи конвертации у них похожи, разница в том, что нативная конвертация webpack может выполнять еще один статический анализ и использовать технологию tree-shaking (описано ниже).

Babel может заранее преобразовывать ключевые слова модулей, такие как импорт es6, в спецификации commonjs. Таким образом, веб-пакету не нужно выполнять какую-либо обработку, и он напрямую использует определение, определенное во время выполнения веб-пакета.__webpack_require__иметь дело с.

объяснил здесьВопрос 5.

Какую роль играет Babel в модульной сцене? а вебпак? Кто из них сыграл ключевую роль?

Итак, как же Babel преобразует синтаксис модуля es6?

модуль экспорта

Модуль экспорта es6 записывается как

export default 123;

export const a = 123;

const b = 3;
const c = 4;
export { b, c };

Babel преобразует все это в экспорт commonjs.

exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.c = 4;
exports.__esModule = true;

Логика вывода модуля babel, конвертирующего es6, очень проста, то есть назначение всего вывода на экспорт с флагом__esModuleУказывает, что это вывод commonjs, преобразованный из es6.

После того как babel преобразует экспорт модуля в спецификацию commonjs, он также преобразует импорт в спецификацию commonjs. То есть используйте require для ссылки на модуль, а затем выполните некоторую обработку, что соответствует намерению использования es6.

Ввести значение по умолчанию

для самых распространенных

import a from './a.js';

Первоначальное намерение import a from './a.js' в es6 — импортировать вывод по умолчанию в модуль es6.

После преобразования по бабелю получаемvar a = require(./a.js)Результирующий объект — это весь объект, что определенно не является целью оператора es6, поэтому требуются некоторые изменения в a.

Как мы упоминали в экспорте, вывод по умолчанию будет назначен свойству по умолчанию экспортируемого объекта.

exports.default = 123;

Итак, Babel добавил помощь_interopRequireDefaultфункция.

function _interopRequireDefault(obj) {
    return obj && obj.__esModule
        ? obj
        : { 'default': obj };
}

var _a = require('assert');
var _a2 = _interopRequireDefault(_a);

var a = _a2['default'];

Итак, последняя переменная здесь является свойством по умолчанию значения require. Если изначально это был модуль спецификации commonjs, то это экспортируемый объект этого модуля.

Введите подстановочный знак *

Мы используемimport * as a from './a.js'Первоначальная цель синтаксиса es6 состоит в том, чтобы упаковать весь именованный вывод и вывод по умолчанию модуля es6 в объект и присвоить его переменной a.

Известен экспорт со спецификацией commonjs:

exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.__esModule = true;

Затем для преобразованного вывода es6 черезvar a = require('./a.js')Импорт этого объекта уже соответствует намерению.

Поэтому верните этот объект напрямую.

if (obj && obj.__esModule) {
   return obj;
}

Если это изначально модуль спецификации commonjs, при экспорте нет атрибута по умолчанию, вам нужно добавить атрибут по умолчанию, и снова присвоить всему объекту модуля атрибут по умолчанию.

function _interopRequireWildcard(obj) {
    if (obj && obj.__esModule) {
        return obj;
    }
    else {
        var newObj = {}; // (A)
        if (obj != null) {
            for (var key in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
            }
        }
        newObj.default = obj;
        return newObj;
    }
}

import { a } from './a.js'

Преобразовать напрямую вrequire('./a.js').aВот и все.

Суммировать

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

Вопрос 3 объясняется здесь

Почему я могу использовать импорт es6 для ссылки на модули, определенные спецификацией commonjs, или наоборот?

babel5 & babel6

Выше мы упоминали преобразование модулей экспорта в Babel, es6export defaultбудет преобразован вexports.default, даже если модуль имеет только один выход.

Это также объясняет вопрос 1

Почему некоторые места используютсяrequireЧтобы сослаться на модуль, вам нужно добавитьdefault?require('xx').default

Мы часто используем экспорт es6 по умолчанию для вывода модулей, и этот вывод является единственным выводом этого модуля, мы ошибочно думаем, что этот метод записи выводит вывод модуля по умолчанию.

// a.js

export default 123;
// b.js 错误

var foo = require('./a.js')

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

Результат здесь нужно изменить наvar foo = require('./a.js').default

Этот сценарий часто встречается при написании логики разделения кода веб-пакета.

require.ensure([], (require) => {
   callback(null, [
     require('./src/pages/profitList').default,
   ]);
 });

Это изменение в babel6, которого не было в babel5.

Babel — это .IO/docs/plugin…

В эпоху babel5 большинство людей использовали требование ссылаться на вывод по умолчанию es6, но рассматривали вывод по умолчанию только как вывод по умолчанию модуля, поэтому babel5 взломал эту логику.Если модуль es6 имеет только один вывод по умолчанию, то в При преобразовании в commonjs он также назначаетсяmodule.exports, то есть всему объекту экспорта присваивается значение, соответствующее умолчанию.

Таким образом, вам не нужно добавлять значение по умолчанию,require('./a.js')Значение является желаемым значением по умолчанию.

Но это противоречит определению es6.В определении es6 default — это просто имя, не имеющее никакого значения.

export default = 123;
export const a = 123;

Они имеют одинаковое значение и выходные переменные с именами default и a соответственно.

Также есть очень важная проблема: как только в файл a.js будет добавлен именованный вывод, у импортера возникнут проблемы.

// a.js

export default 123;

export const a = 123; // 新增
// b.js 

var foo = require('./a.js');

// 由之前的 输出 123
// 变成 { default: 123, a: 123 }

Так что babel6 удалил этот хак, что является правильным решением.Проблема несовместимости после обновления babel6 может быть введена введениемbabel-plugin-add-module-exportsрешать.

Как другие модули могут ссылаться на скомпилированные js веб-пакета?

На js, скомпилированный конфигурацией веб-пакета, приведенной в главе о модульных принципах веб-пакета, не могут ссылаться другие модули.output.libraryTargetКонфигурация указывает цель сборки js.

переменная по умолчанию

если указаноoutput.library = 'test'Модуль module.exports, возвращаемый модулем ввода, подвергается воздействию глобальной переменной var test = return_module_exports.

commonjs

Если библиотека: 'spon-ui' Модуль module.exports, возвращаемый модулем ввода, назначается для экспорта ['spon-ui']

commonjs2

Модуль module.exports, возвращаемый модулем ввода, назначается module.exports.

Таким образом, метод построения element-ui принимает commonjs2, и js экспортируемых компонентов, наконец, будут назначены module.exports для ссылки другими модулями.

Вопрос 4 объясняется здесь

Когда мы просматриваем некоторые модули компонентов пользовательского интерфейса, загруженные npm (например, в lib-файле element-ui), все, что мы видим, — это файлы js, скомпилированные webpack, на которые можно ссылаться с помощью import или require. Но наш обычный скомпилированный js больше не может быть импортирован другими модулями, почему?

Модульно-зависимая оптимизация

Принцип загрузки по требованию

Мы будем представлены при использовании основных библиотек компонентов пользовательского интерфейса. Чтобы не вводить все файлы, используйтеbabel-plugin-componentи т. д. плагин Babel.

import { Button, Select } from 'element-ui'

Как видно из предыдущей статьи, сначала импорт будет конвертирован в commonjs, т.е.

var a = require('element-ui');
var Button = a.Button;
var Select = a.Select;

var a = require('element-ui');В этом процессе будут задействованы все компоненты.

такbabel-plugin-componentсделал одну вещь,import { Button, Select } from 'element-ui'конвертировано в

import Button from 'element-ui/lib/button'
import Select from 'element-ui/lib/select'

Даже если он преобразован в спецификацию commonjs, он вводит только js своего собственного компонента, сводя количество введения к минимуму.

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

|-lib
||--component1
||--component2
||--component3
|-index.common.js

index.common.jsдаватьimport element from 'element-ui'Эта форма вызывает все компоненты.

Компоненты в lib используются для справки по запросу.

Вопрос 2 объясняется здесь

Инструкции часто можно увидеть в документах, на которые ссылаются основные компоненты пользовательского интерфейса.import { button } from 'xx-ui'Это принесет весь компонентный контент, требующий дополнительной настройки babel, такой какbabel-plugin-component?

tree-shaking

Webpack2 начал внедрять технологию tree-shaking.Благодаря статическому анализу синтаксиса es6 можно удалять неиспользуемые модули. Это работает только для модулей es6, поэтому, как только babel преобразует модули es6 в commonjs, webpack2 не сможет использовать эту оптимизацию. Таким образом, чтобы использовать эту технологию, мы можем использовать только обработку модулей webpack, а также возможности преобразования es6 в babel (необходимо отключить преобразование модулей).

Самый удобный способ его использования — модифицировать конфигурацию babel.

use: {
     loader: 'babel-loader',
     options: {
       presets: [['babel-preset-es2015', {modules: false}]],
     }
   }

Изменить начальную демонстрацию

// webpack

const path = require('path');

module.exports = {
  entry: './a.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['babel-preset-es2015', {modules: false}]],
          }
        }
      }
    ]
  }
};


// a.js
import a from './c';

export default 'a.js';
console.log(a);


// c.js

export default 333;

const foo = 123;
export { foo };

Суть модификации состоит в том, чтобы добавить Babel и закрыть функцию его модулей. Затем добавьте вывод в c.jsexport { foo }, но он не упоминается в a.js.

Наконец, в скомпилированном js модуль c.js выглядит следующим образом:


"use strict";
/* unused harmony export foo */
/* harmony default export */ __webpack_exports__["a"] = (333);

var foo = 123;

Переменная foo помечается как неиспользуемая, и этот сегмент удаляется при окончательном сжатии.

Следует отметить, что даже если при импорте модуля используется es6, импортируемый модуль использует для вывода commonjs, который не может использовать tree-shaking.

Большинство сторонних библиотек следуют спецификации commonjs, что также делает введение сторонних библиотек неспособным сократить количество ненужных внедрений.

Так что на будущее сторонние библиотеки должны публиковать модули в формате commonjs и формате es6 одновременно. Запись модуля es6 определяется полем модуля package.json. А в основном поле по-прежнему указывается commonjs.

Вопрос 6 объясняется здесь

Я слышал, что es6 также имеетtree-shakingфункция, как я могу использовать эту функцию?

Цель на 2019 год: исключить английский язык! Я открыл новый публичный аккаунт, чтобы записывать курс программиста, изучающего английский язык

Друзья, которые хотят улучшить свой английский, могут обратить внимание на официальный аккаунт: csenglish программисты изучают английский, тратят 10 минут на сдачу домашних заданий каждый день, учи английский со мной