Графический веб-пакет (модульный — CommonJS)

Webpack

в предыдущей статье«Модульная серия» подробно разъясняет AMD, CommonJS, CMD, UMD, ES6., мы можем изучить различные модульные механизмы. Затем давайте проанализируем механизм модульности webpack. (в основном о части JS)

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

webpack-2

Эта статья о打包 - CommonJSМодуль в основном разделен на две части

  • Роль веб-пакета
  • Механизм модульности и реализация webpack

Роль веб-пакета

В сегодняшней диверсификации нашего внешнего интерфейса многие инструменты стали очень большими, чтобы удовлетворить наши растущие потребности в разработке, такие как webpack. По нашему мнению, он объединяет все функции, связанные с разработкой, упаковкой модулей, понижением версии кода, оптимизацией файлов, проверкой кода и так далее. Именно из-за такого огромного инструмента мы обескуражены.Конечно, другой момент в том, что частый апгрейд вебпака и неразбериха с поддерживаемыми версиями окружающих его экологических плагинов также усугубляют наш страх перед ним.

Так должны ли мы задуматься о том, что принесло нам появление веб-пакета? Зачем нам это нужно? Все вышеперечисленные понижения версии кода (преобразование Babel), компиляция SCSS и обнаружение спецификаций кода выигрывают от его системы подключаемых модулей и механизма загрузки и не принадлежат ему полностью.

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

  • Изоляция модуля
  • загрузка зависимости модуля

Изоляция модуля

Если мы не используем метод упаковки, все наши модули напрямую выставляются глобально, то есть монтируются наwindow/globalэтот объект. Может быть, это приемлемо, когда объем кода небольшой, и проблем не будет так много. Особенно в случае увеличения кода и совместной работы нескольких человек влияние на глобальное пространство непредсказуемо, если вам нужно обратиться к каждой разработке, чтобы узнать, используют ли они текущие имена переменных.

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

image-20200626231748187

├── bar.js    function bar(){}
├── baz.js    function baz(){}
└── foo.js	function foo(){}

Однако с бизнес итерацией сложность проекта возрастает, и приходит пользователь 2. В это время пользователю 2 необходимо разработать бизнес foo, в котором также есть модуль baz.Код также быстро пишется, и выглядит как следующий.

├── bar.js    function bar(){}
├── baz.js    function baz(){}
├── foo
│   └── baz.js	function baz(){}
└── foo.js	function foo(){}

Но в это время к пользователю пришел начальник 2. Почему первоначальный бизнес пошел не так после добавления нового бизнеса? В это время было обнаружено, что новый модуль, написанный пользователем2, перезаписал модуль пользователя1, что и вызвало аварию.

image-20200626220806881

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

Таким образом, первая основная функция, привносимая вебпаком, — это изоляция.Каждый модуль оборачивается в новый модуль в виде замыкания и помещается в локальную область видимости, а все объявления функций не выставляются напрямую в глобальную.

image-20200626220851909

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

baz.js
module.exports = function baz (){}

foo/baz.js
module.exports = function baz (){}

main.js
var baz = require('./baz.js');
var fooBaz = require('./foo/baz.js');

baz();
fooBaz();

Возможно, способ, который вы сказали ранее, также можно изменить, изменив способ имени функции, но исходная область действия — это весь проект, вы должны убедиться, что текущее имя не конфликтует во всем проекте, теперь вам нужно только убедитесь, что имя находится в одном файле. Не конфликтуйте. (Также очень легко найти конфликты для зависимостей верхнего уровня)

image-20200627140818771

загрузка зависимости модуля

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

User1 написал 3 модуля, из которых baz зависит от bar.

image-20200627000240836

После написания user1 выходит в сеть, используя порядок указания зависимостей.

<script src="./bar.js"></script>
<script src="./baz.js"></script>
<script src="./foo.js"></script>

Но вскоре после этого user2 взял на себя бизнес. Пользователь 2 обнаружил, что разработанный им модуль abc можно быстро разработать, опираясь на модуль bar. Но небрежный user2 не совсем понимает зависимости. Расположение abc было указано произвольно, из-за чего модуль bar не мог быть найден при запуске abc.

image-20200627000713100

<script src="./abc.js"></script>
<script src="./bar.js"></script>
<script src="./baz.js"></script>
<script src="./foo.js"></script>

Итак, здесь webpack использует для обработки спецификацию модулей CommonJS/ES. Сделайте так, чтобы каждый модуль ссылался друг на друга, не учитывая порядок окончательной фактической презентации. В конечном итоге он будет упакован как пакетный модуль, и нет необходимости вручную импортировать по порядку.

baz.js
const bar = require('./bar.js');
module.exports = function baz (){
	...
	bar();
	...
}

abc.js
const bar = require('./bar.js');
module.exports = function baz (){
	...
	bar();
	...
}
<script src="./bundle.js"></script>

image-20200627003815071

Механизм модульности и реализация webpack

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

  • Чтобы максимально снизить сложность написания и стоимость понимания, я не использовал парсинг AST, (конечно, AST не сложен, я объясню, что такое AST и процесс реализации парсера AST в следующих статьях .
  • Реализована только поддержка CommonJS

Как работают пакеты

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

Исходный файл

// index.js
const b = require('./b');
b();
// b.js
module.exports = function () {
    console.log(11);
}

после сборки(удален некоторый мешающий код)

(function(modules) {
  var installedModules = {};
  function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {},
    });
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__
    );
    module.l = true;
    return module.exports;
  }
  return __webpack_require__((__webpack_require__.s = 0));
})([
  /* 0 */
  function(module, exports, __webpack_require__) {
    var b = __webpack_require__(1);
    b();
  },
  /* 1 */
  function(module, exports) {
    module.exports = function() {
      console.log(11);
    };
  },
]);

image-20200627135324956

Так работают связки. Из приведенной выше блок-схемы мы видим, что есть четыре ключевых момента.

  • Зарегистрированные модули (хранит зарегистрированные модули)
  • Список модулей (используется для хранения всех упакованных модулей)
  • Поиск модулей (от исходных древовидных зависимостей модулей до плоского поиска)
  • Упаковка модуля (все оригинальные модули упакованы один раз)

реализация веб-пакета

При анализе пакетов все, что нам нужно сделать, это 4 вещи.

  • перебрать все модули
  • модульная упаковка
  • Предоставляет зарегистрированные модули, переменные списка модулей и импортированные функции.
  • Постоянный экспорт

обход модулей

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

interface GraphStruct {
    context: string;
    moduleId: string;
}
{
	"context": `function(module, exports, require) {
    const bar = require('./bar.js');
		const foo = require('./foo.js');
		console.log(bar());
		foo();
  }`,
  "moduleId": "./example/index.js"
}

Далее мы объясним получение файла входа.Когда мы получаем файл входа, нам нужно проанализировать его зависимости. Проще говоря, получитьrequireзначение, чтобы мы могли перейти к следующему модулю. Так как я не хочу вводить дополнительные знания в эту часть, я также упомянул об этом в начале.ASTразбор, чтобы получитьrequireмодуль, здесь мы используем регулярное выражение.

用来匹配全局的 require 
const REQUIRE_REG_GLOBAL = /require\(("|')(.+)("|')\)/g;
用来匹配 require 中的内容
const REQUIRE_REG_SINGLE = /require\(("|')(.+)("|')\)/;
const context = `
const bar = require('./bar.js');
const foo = require('./foo.js');
console.log(bar());
foo();
`;
console.log(context.match(REQUIRE_REG_GLOBAL));
// ["require('./bar.js')", "require('./foo.js')"]

image-20200627202427794

Так как обход модуля представляет собой не только простую однослойную структуру, это вообще древовидная структура, поэтому здесь я использую глубокий обход. В основном через обычные, чтобы соответствоватьrequireЗависимости в , а затем рекурсивно получить модули, и, наконец, модули, которые были пройдены через глубину, сохраняются в виде массива. (Не понимаю глубокий обход, его можно понимать как модуль рекурсивного сбора данных)

image-20200627142130902

Ниже приведена реализация кода

...
private entryPath: string
private graph: GraphStruct[]
...
createGraph(rootPath: string, relativePath: string) {
    // 通过获取文件内容
    const context = fs.readFileSync(rootPath, 'utf-8');
    // 匹配出依赖关系
    const childrens = context.match(REQUIRE_REG_GLOBAL);
  	// 将当前的模块存储下来
    this.graph.push({
        context,
        moduleId: relativePath,
    })
    const dirname = path.dirname(rootPath);
    if (childrens) {
       // 如有有依赖,就进行递归
        childrens.forEach(child => {
            const childPath = child.match(REQUIRE_REG_SINGLE)[2];
            this.createGraph(path.join(dirname, childPath), childPath);
        });
    }
}

модульная упаковка

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

function (module, exports, require){
    ...
},

Предоставляет зарегистрированные модули, переменные списка модулей и импортированные функции.

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

/* modules = {
  "./example/index.js": function (module, exports, require) {
    const a = require("./a.js");
    const b = require("./b.js");

    console.log(a());
    b();
  },
  ...
};*/
bundle(graph: GraphStruct[]) {
    let modules = '';
    graph.forEach(module => {
        modules += `"${module.moduleId}":function (module, exports, require){
        ${module.context}
        },`;
    });
    const bundleOutput = `
    (function(modules) {
        var installedModules = {};
        // 导入函数
        function require(moduleId) {
            // 检查是否已经注册该模块
            if (installedModules[moduleId]) {
                return installedModules[moduleId].exports;
            }
           // 没有注册则从模块列表获取模块进行注册
            var module = (installedModules[moduleId] = {
                i: moduleId,
                l: false,
                exports: {},
            });
           // 执行包装函数,执行后更新模块的内容
            modules[moduleId].call(
                module.exports,
                module,
                module.exports,
                require
            );
            // 设置标记已经注册
            module.l = true;
            // 返回实际模块
            return module.exports;
        }
        require("${graph[0].moduleId}");
    })({${modules}})
    `;
    return bundleOutput;
}

Постоянный экспорт

Наконец, настойчиво запишите сгенерированный пакет на диск, и все готово.

fs.writeFileSync('bundle.js', this.bundle(this.graph))

Полный код состоит менее чем из 100 строк. Подробности можно посмотреть в полном примере ниже.

адрес гитхаба:GitHub.com/flower1995116/…

конец

Вышеупомянутое представляет только мое личное понимание, я надеюсь, что это поможет вам в вашем понимании веб-пакета.Если что-то не так, пожалуйста, укажите больше.

Добро пожаловать в публичный аккаунт«Записки осеннего ветра»., в основном записывайте инструменты, которые интересны в повседневной жизни, и делитесь практиками разработки, сохраняя глубину и направленность. ОтвечатьwebpackПолучить обзорную картуxmind 原图.

weixin-gongzhonghao

FAQ

Вопрос: Почему вы решили написать эту статью?

Р: На самом деле, это в основном для рисования, что является совершенно новым.

В: Будет ли следующая статья?

Р: Да, следующая статья ориентировочно связана с модулем ES и разделением кода.