задний план
С постоянным улучшением сложности внешнего интерфейса появилось много инструментов упаковки, таких как первыйgrunt
,gulp
. к более позднемуwebpack
иParcel
. Но есть много инструментов лесов, таких какvue-cli
Помог нам интегрировать использование некоторых инструментов сборки. Иногда мы можем не знать его внутреннего принципа реализации. Фактически, понимание того, как работают эти инструменты, может помочь нам лучше понять и использовать эти инструменты, а также облегчить наше применение в разработке проекта.
некоторые очки знаний
Прежде чем мы начнем строить колеса, нам нужно проделать некоторую резервную работу над некоторыми точками знаний.
модульное знание
Первое — это актуальные знания модуля, главное —es6 modules
иcommonJS
Модульная спецификация. Более подробную информацию можно найти здесьАнализ принципов CommonJS, AMD/CMD, модулей ES6 и веб-пакета. Теперь нам просто нужно понять:
-
es6 modules
Это способ определения зависимостей модуля во время компиляции. -
CommonJS
В спецификации модуля Node оборачивает содержимое заголовка и хвоста файла при компиляции JS-файла. , добавьте в шапку(function (export, require, modules, __filename, __dirname){\n
добавлено в конце\n};
. Таким образом, мы можем использовать эти параметры внутри одного JS-файла.
Основы АСТ
Что такое абстрактное синтаксическое дерево?
В информатике абстрактное синтаксическое дерево (сокращенно абстрактное синтаксическое дерево или AST) или синтаксическое дерево (синтаксическое дерево) представляет собой древовидное представление абстрактной синтаксической структуры исходного кода, в частности исходного кода языка программирования. . Каждый узел дерева представляет собой структуру в исходном коде. Грамматика называется «абстрактной», потому что грамматика здесь не представляет все детали, которые встречаются в реальной грамматике.
Каждый может пройтиEsprimaэтот сайт, чтобы преобразовать код вast
. Во-первых, абстрактное синтаксическое дерево, преобразованное в фрагмент кода, представляет собой объект, который будет иметь структуру верхнего уровня.type
АтрибутыProgram
, второе свойствоbody
представляет собой массив. Каждый элемент, хранящийся в массиве body, представляет собой объект, содержащий всю информацию описания оператора:
type:描述该语句的类型 --变量声明语句
kind:变量声明的关键字 -- var
declaration: 声明的内容数组,里面的每一项也是一个对象
type: 描述该语句的类型
id: 描述变量名称的对象
type:定义
name: 是变量的名字
init: 初始化变量值得对象
type: 类型
value: 值 "is tree" 不带引号
row: "\"is tree"\" 带引号
К точке
простая упаковка webpack
Обладая вышеуказанными базовыми знаниями, давайте взглянем на простойwebpack
В процессе упаковки сначала мы определяем 3 файла:
// index.js
import a from './test'
console.log(a)
// test.js
import b from './message'
const a = 'hello' + b
export default a
// message.js
const b = 'world'
export default b
Способ очень простой, определитеindex.js
Цитироватьtest.js
;test.js
внутренняя ссылкаmessage.js
. Взгляните на упакованный код:
(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__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {enumerable: true, get: getter});
}
};
// define __esModule on exports
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});
}
Object.defineProperty(exports, '__esModule', {value: true});
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function (value, mode) {
/******/
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', {enumerable: true, value: value});
if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) {
return value[key];
}.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() {
return module['default'];
} :
function getModuleExports() {
return module;
};
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ \"./src/test.js\");\n\n\nconsole.log(_test__WEBPACK_IMPORTED_MODULE_0__[\"default\"])\n\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/message.js": (function (module, __webpack_exports__, __webpack_require__) {
// ...
}),
"./src/test.js": (function (module, __webpack_exports__, __webpack_require__) {
// ...
})
});
Выглядишь грязным? Ничего страшного, повторим. На первый взгляд мы видим эту форму:
(function(modules) {
// ...
})({
// ...
})
Это легко понять, это самовыполняющаяся функция, которая передается вmodules
Объект, каков формат объекта модулей? Приведенный выше код уже дает нам ответ:
{
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
// ...
}),
"./src/message.js": (function (module, __webpack_exports__, __webpack_require__) {
// ...
}),
"./src/test.js": (function (module, __webpack_exports__, __webpack_require__) {
// ...
})
}
такой路径 --> 函数
Такая пара ключ-значение ключ-значение. А внутри функции находится код после передачи определенного нами файла в ES5:
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ \"./src/test.js\");\n\n\nconsole.log(_test__WEBPACK_IMPORTED_MODULE_0__[\"default\"])\n\n\n//# sourceURL=webpack:///./src/index.js?");
На этом этапе структура в основном анализируется, а затем мы смотрим на ее выполнение Код, выполняемый в начале самовыполняющейся функции:
__webpack_require__(__webpack_require__.s = "./src/index.js");
называется__webpack_require_
функционировать и передавать вmoduleId
параметр"./src/index.js"
. Давайте посмотрим на основную реализацию внутри функции:
// 定义 module 格式
var module = installedModules[moduleId] = {
i: moduleId, // moduleId
l: false, // 是否已经缓存
exports: {} // 导出对象,提供挂载
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
Здесь нас называютmodules
функция в и передается в__webpack_require__
Функция как вызов внутри функции.module.exports
Параметры экспортируются как внутри функции. так какindex.js
цитируется вtest.js
, так что это пройдет снова__webpack_require__
выполнитьtest.js
Загрузка:
var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test.js");
test.js
используется сноваmessage.js
так,test.js
Внутри он будет выполнятьmessage.js
нагрузка.message.js
После завершения выполнения из-за отсутствия зависимостей результат возвращается напрямую:
var b = 'world'
__webpack_exports__["default"] = (b)
После завершения выполнения вернуться к корневому файлу один уровень за другимindex.js
. Наконец, обработка всей файловой зависимости завершена.
В течение всего процесса мы продолжаем вводить число в виде дерева зависимостей, а когда результат будет возвращен, начинаем трассировку до корня.
Разработайте простой крошечный пакет
Посредством вышеупомянутых исследований давайте сначала рассмотрим, что может сделать базовый инструмент для упаковки и компиляции?
- Преобразование синтаксиса ES6 в ES5
- Обработка зависимостей загрузки модуля
- Создайте файл js, который можно загрузить и выполнить в браузере.
Первый вопрос, синтаксис преобразования, мы действительно можем передатьbabel
сделать это. Основные шаги:
- пройти через
babylon
Сгенерировать АСТ - пройти через
babel-core
Восстановите AST в исходный код
/**
* 获取文件,解析成ast语法
* @param filename // 入口文件
* @returns {*}
*/
function getAst (filename) {
const content = fs.readFileSync(filename, 'utf-8')
return babylon.parse(content, {
sourceType: 'module',
});
}
/**
* 编译
* @param ast
* @returns {*}
*/
function getTranslateCode(ast) {
const {code} = transformFromAst(ast, null, {
presets: ['env']
});
return code
}
Затем нам нужно разобраться с зависимостями модулей, затем нам нужно получить представление зависимостей. к счастьюbabel-traverse
обеспечивает проходимуюAST
просматривать и выполнять функцию обработки, черезImportDeclaration
Свойства зависимости можно получить:
function getDependence (ast) {
let dependencies = []
traverse(ast, {
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value);
},
})
return dependencies
}
/**
* 生成完整的文件依赖关系映射
* @param fileName
* @param entry
* @returns {{fileName: *, dependence, code: *}}
*/
function parse(fileName, entry) {
let filePath = fileName.indexOf('.js') === -1 ? fileName + '.js' : fileName
let dirName = entry ? '' : path.dirname(config.entry)
let absolutePath = path.join(dirName, filePath)
const ast = getAst(absolutePath)
return {
fileName,
dependence: getDependence(ast),
code: getTranslateCode(ast),
};
}
Пока что мы также просто получаем зависимости корневого файла и скомпилированного кода, как нашindex.js
зависел отtest.js
но мы не знаемtest.js
также необходимо зависеть отmessage.js
, их исходный код не компилируется. Итак, на этом этапе нам также нужно выполнить обход глубины, чтобы получить завершенные зависимости глубины:
/**
* 获取深度队列依赖关系
* @param main
* @returns {*[]}
*/
function getQueue(main) {
let queue = [main]
for (let asset of queue) {
asset.dependence.forEach(function (dep) {
let child = parse(dep)
queue.push(child)
})
}
return queue
}
Итак, на этом шаге мы завершили компиляцию и разбор всех файлов. Последний шаг заключается в том, что нам нужно следоватьwebpack
Идея некой упаковки исходного кода. Первым шагом является созданиеmodules
Объект:
function bundle(queue) {
let modules = ''
queue.forEach(function (mod) {
modules += `'${mod.fileName}': function (require, module, exports) { ${mod.code} },`
})
// ...
}
получитьmodules
После объекта следующий шаг — внешняя упаковка общего файла, регистрацияrequire
,module.exports
:
(function(modules) {
function require(fileName) {
// ...
}
require('${config.entry}');
})({${modules}})
Внутри функции он просто выполняет код JS каждого зависимого файла в цикле, завершите код:
function bundle(queue) {
let modules = ''
queue.forEach(function (mod) {
modules += `'${mod.fileName}': function (require, module, exports) { ${mod.code} },`
})
const result = `
(function(modules) {
function require(fileName) {
const fn = modules[fileName];
const module = { exports : {} };
fn(require, module, module.exports);
return module.exports;
}
require('${config.entry}');
})({${modules}})
`;
return result;
}
Это в основном введение здесь, давайте упакуем и попробуем:
(function (modules) {
function require(fileName) {
const fn = modules[fileName];
const module = {exports: {}};
fn(require, module, module.exports);
return module.exports;
}
require('./src/index.js');
})({
'./src/index.js': function (require, module, exports) {
"use strict";
var _test = require("./test");
var _test2 = _interopRequireDefault(_test);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
console.log(_test2.default);
}, './test': function (require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _message = require("./message");
var _message2 = _interopRequireDefault(_message);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
var a = 'hello' + _message2.default;
exports.default = a;
}, './message': function (require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var b = 'world';
exports.default = b;
},
})
Протестируйте еще раз:
Ну в принципе простойtinypack
.
Справочная статья
Абстрактное синтаксическое дерево
Абстрактное синтаксическое дерево JS, которое вы можете понять с первого взгляда
исходный код
Весь исходный код tinypack загруженgithub