Сделал ночь анимации, пусть все разберутся в Webpack за десять минут

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

1. Что такое веб-пакет

Webpack — это инструмент для упаковки, и его цель — упаковать все статические ресурсы.

image-20210506104502010

2. Принципиальный анализ

Сначала мы рассмотрим прототип, который создает упакованный файл.

Предположим, есть два модуля js, здесь мы сначала предполагаем, что эти два модуля являются модулями es5, составляющими стандарт commomjs.

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

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

Например

// index.js
var add = require('add.js').default
console.log(add(1 , 2))

// add.js
exports.default = function(a,b) {return a + b}

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

Нам нужно смоделировать объект экспорта и метод require с помощью

1. Смоделируйте объект экспорта

Прежде всего, мы знаем, что если nodejs упакован, мы будем использовать fs.readfileSync() для чтения файлов js. В этом случае файл js будет строкой. А если вам нужно запустить код в строке, есть два метода, new Function и Eval.

Здесь мы выбираем eval с более высокой эффективностью выполнения.

exports = {}
eval('exports.default = function(a,b) {return a + b}') // node文件读取后的代码字符串
exports.default(1,3)

image-20210507102528679

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

var exports = {}
(function (exports, code) {
	eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')

2. Смоделируйте требуемую функцию

Функция функции require относительно проста и заключается в загрузке соответствующего модуля в соответствии с предоставленным именем файла.

Во-первых, давайте посмотрим, как писать, если есть только один фиксированный модуль.

Kapture 2021-05-09 at 11.20.31.gif

function require(file) {
	var exports = {};
	(function (exports, code) {
		eval(code)
	})(exports, 'exports.default = function(a,b){return a + b}')
  return exports
}
var add = require('add.js').default
console.log(add(1 , 2))

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

(function (list) {
  function require(file) {
    var exports = {};
    (function (exports, code) {
      eval(code);
    })(exports, list[file]);
    return exports;
  }
  require("index.js");
})({
  "index.js": `
    var add = require('add.js').default
    console.log(add(1 , 2))
        `,
  "add.js": `exports.default = function(a,b){return a + b}`,
});

Конечно, следует отметить, что файл bundle.js, созданный настоящим веб-пакетом, также должен увеличивать зависимости между модулями.

Вызываемый график зависимостей

Аналогично ситуации ниже.

{
  "./src/index.js": {
    "deps": { "./add.js": "./src/add.js" },
    "code": "....."
  },
  "./src/add.js": {
    "deps": {},
    "code": "......"
  }
}

Кроме того, поскольку большинство клиентских программ привыкли использовать синтаксис es6, также необходимо заранее преобразовать синтаксис es6 в синтаксис es5.

Подводя итог, упаковку webpack можно разделить на следующие три шага:

webpack原理.gif

  1. Зависимость анализа

  2. от ES6 до ES5

  3. Заменить экспорт и потребовать

Теперь войдите в стадию реализации функции.

3. Реализация функции

Наша цель — упаковать следующие два взаимозависимых модуля ES6Module в один JS-файл (bundle.js), который можно запустить в браузере.

  • Модульный процесс
  • Слияние и упаковка нескольких модулей — оптимизация сетевых запросов

/src/add.js

export default (a, b) => a + b 

/src/index.js

import add from "./add.js";
console.log(add(1 , 2))

1. Модуль анализа

Модуль анализа разделен на следующие три шага:

Анализ модуля эквивалентен разбору прочитанной строки кода файла. Этот шаг фактически аналогичен процессу компиляции языка высокого уровня. Модуль необходимо преобразовать в абстрактное синтаксическое дерево AST. Делаем это с помощью babel/parser.

AST (Abstract Syntax Tree) Абстрактное синтаксическое дерево В информатике или просто синтаксическое дерево — это абстрактное представление грамматической структуры исходного кода. Он представляет синтаксическую структуру языка программирования в виде дерева, и каждый узел в дереве представляет собой структуру в исходном коде. (АСТ explorer.net/)

yarn add @babel/parser
yarn add @babel/traverse
yarn add @babel/core
yarn add @babel/preset-env
  • прочитать файл

  • Собрать зависимости

  • Компиляция и разбор AST

const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");

function getModuleInfo(file) {
  // 读取文件
  const body = fs.readFileSync(file, "utf-8");

  // 转化AST语法树
  const ast = parser.parse(body, {
    sourceType: "module", //表示我们要解析的是ES模块
  });

  // 依赖收集
  const deps = {};
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirname = path.dirname(file);
      const abspath = "./" + path.join(dirname, node.source.value);
      deps[node.source.value] = abspath;
    },
  });

  // ES6转成ES5
  const { code } = babel.transformFromAst(ast, null, {
    presets: ["@babel/preset-env"],
  });
  const moduleInfo = { file, deps, code };
  return moduleInfo;
}
const info = getModuleInfo("./src/index.js");
console.log("info:", info);

Результаты приведены ниже:

![image-20210507152817221](/Users/xiaran/Library/Application Support/typora-user-images/image-20210507152817221.png)

2. Соберите зависимости

Функция, разработанная на предыдущем шаге, может независимо анализировать определенный модуль.На этом этапе нам нужно разработать функцию для рекурсивного анализа зависимостей от входного модуля. Наконец, зависимости формируются в граф зависимостей (Dependency Graph)

/**
 * 模块解析
 * @param {*} file 
 * @returns 
 */
function parseModules(file) {
  const entry = getModuleInfo(file);
  const temp = [entry];
  const depsGraph = {};

  getDeps(temp, entry);

  temp.forEach((moduleInfo) => {
    depsGraph[moduleInfo.file] = {
      deps: moduleInfo.deps,
      code: moduleInfo.code,
    };
  });
  return depsGraph;
}

/**
 * 获取依赖
 * @param {*} temp 
 * @param {*} param1 
 */
function getDeps(temp, { deps }) {
  Object.keys(deps).forEach((key) => {
    const child = getModuleInfo(deps[key]);
    temp.push(child);
    getDeps(temp, child);
  });
}

3. Создайте пакетный файл

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

function bundle(file) {
  const depsGraph = JSON.stringify(parseModules(file));
  return `(function (graph) {
        function require(file) {
            function absRequire(relPath) {
                return require(graph[file].deps[relPath])
            }
            var exports = {};
            (function (require,exports,code) {
                eval(code)
            })(absRequire,exports,graph[file].code)
            return exports
        }
        require('${file}')
    })(${depsGraph})`;
}


!fs.existsSync("./dist") && fs.mkdirSync("./dist");
fs.writeFileSync("./dist/bundle.js", content);

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

<script src="./dist/bundle.js"></script>

image-20210507154015030

ок плата за обучение.

Если вам это интересно позже, вы можете рассмотреть, как загрузить файлы css или изображения base64 Vue SFC .vue.

🔥Исходный код

Wechat 🔍Найдите и подпишитесь на «интерфейсную шину», ответьте «webpack», чтобы получить исходный код «vue»