Я создал простой сборщик JavaScript, и он оказался намного проще, чем я ожидал. В этом посте я поделюсь всем, что узнал.
При написании больших приложений рекомендуется разделить исходный код JavaScript на отдельные js-файлы, однако добавление этих файлов в ваш html-документ с использованием нескольких тегов сценария создает новые проблемы, такие как
загрязнение глобального пространства имен.
условия гонки.
Компондеры модулей объединяют исходный код из разных файлов в один большой файл, помогая нам воспользоваться преимуществами абстракций и избежать недостатков.
Компондеры модулей обычно делают это в два этапа.
Как упоминалось ранее, здесь мы
Вот как мы это сделаем (код JavaScript впереди)
Создайте файл Bundler.js в текстовом редакторе и добавьте следующий код:
const bundler = (entry)=>{ const graph = createDependencyGraph(entry) const bundle = createBundle(graph) return bundle }
Функция упаковщика — это основная запись нашего упаковщика. Он принимает путь к файлу (входной файл) и возвращает строку (пакет). Внутри него он генерирует граф зависимостей с помощью функции createDependencyGraph.
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) /* other code */ }
Функция createDependencyGraph принимает путь к файлу записи. Он использует функцию createModule для создания представления модуля этого файла.
let ID = 0 const createModule = (filename)=>{ const content = fs.readFileSync(filename) const ast = babylon.parse(content, {sourceType: “module”}) const {code} = babel.transformFromAst(ast, null, { presets: ['env'] }) const dependencies = [ ] const id = ID traverse(ast, { ImportDeclaration: ({node})=>{ dependencies.push(node.source.value) } } return { id, filename, code, dependencies } }
Функция createAsset берет путь к файлу и считывает его содержимое в строку. Затем эта строка анализируется в абстрактное синтаксическое дерево. Абстрактное синтаксическое дерево — это древовидное представление содержимого исходного кода. Его можно сравнить с деревом DOM html-документа. Это упрощает выполнение некоторых функций кода, таких как поиск и т. д.
Создаем ast из модуля с помощью парсера babylon.
Затем с помощью транспилятора ядра Babel мы конвертируем содержимое кода в синтаксис версии до es2015 для кросс-браузерной совместимости.
После этого ast просматривается с помощью специальной функции из Babel, чтобы найти каждое объявление импорта нашего исходного файла (зависимостей).
Затем мы помещаем эти зависимости (которые представляют собой текстовые строки относительных путей к файлам) в массив зависимостей.
Также мы создаем идентификатор для уникальной идентификации этого модуля и
Наконец, мы возвращаем объект, представляющий этот модуль. Этот модуль содержит идентификатор, содержимое нашего файла в строковом формате, массив зависимостей и абсолютный путь к файлу.
const createDependencyGraph = (path)=>{ const entryModule = createModule(path) const graph = [ entryModule ] for ( const module of graph) { module.mapping = { } module.dependencies.forEach((dep)=>{ let absolutePath = path.join(dirname, dep); let child = graph.find(mod=> mod.filename == dep) if(!child){ child = createModule(dep) graph.push(child) } module.mapping[dep] = child.id }) } return graph }
Вернувшись к функции createDependencyGraph, мы можем начать процесс создания нашего графа. Наш граф представляет собой массив объектов, каждый из которых представляет каждый исходный файл, используемый в нашем приложении.
Мы инициализируем наш граф с помощью входного модуля, а затем зацикливаем его. Хотя он содержит только один элемент, мы добавляем элементы в конец массива, обращаясь к массиву зависимостей входного модуля (и других модулей, которые мы добавим).
Массив зависимостей содержит относительные пути к файлам всех зависимостей модуля. Массив обрабатывается циклически, и для каждого относительного пути к файлу сначала разрешается абсолютный путь, который используется для создания нового модуля. Этот дочерний модуль перемещается в конец графа, и процесс начинается заново, пока все зависимости не будут преобразованы в модули.
Также каждый модуль предоставляет объект сопоставления, который просто сопоставляет каждый относительный путь зависимости с идентификатором дочернего модуля.
Для каждой зависимости выполняется проверка наличия модуля, чтобы предотвратить дублирование модулей и бесконечные циклические зависимости.
Наконец, мы возвращаем наш граф, который теперь содержит все модули нашего приложения.
После создания графа зависимостей создание пакета будет состоять из двух шагов
Нам необходимо преобразовать объекты нашего модуля в строки, чтобы иметь возможность записать их в файл Bundle.js. Мы делаем это, инициализируя модульString как пустую строку. Затем мы просматриваем наш граф, добавляя каждый модуль в строку модуля в виде пар ключ-значение, где идентификатор модуля является ключом, а массив содержит два элемента: во-первых, содержимое модуля, заключенное в функцию (чтобы дать ему область действия, как указано ранее). ) и, во-вторых, объект, содержащий отображение его зависимостей.
const wrapModules = (graph)=>{ let modules = ‘’ graph.forEach(mod => { modules = `${http://mod.id}: [ function (require, module, exports) { ${mod.code} }, ${JSON.stringify(mod.mapping)}, ],`; }); return modules }
Также следует отметить, что функция, обертывающая каждый модуль, принимает в качестве аргументов объекты require, экспорта и модуля. Это потому, что их нет в браузере, но поскольку они появляются в нашем коде, мы создадим их и передадим в эти модули.
Это код, который запускается сразу после загрузки пакета. Он предоставит нашим модулям объекты require, Module и Module.exports.
const bundle = (graph)=>{ let modules = wrapModules(graph) const result = ` (function(modules) { function require(id) { const [fn, mapping] = modules[id]; function localRequire(name) { return require(mapping[name]); } const module = { exports : {} }; fn(localRequire, module, module.exports); return module.exports; } require(0); })({${modules}})`; return result; }
Мы используем немедленно вызываемое функциональное выражение, которое принимает объект нашего модуля в качестве аргумента. Внутри него мы определяем нашу функцию require, которая получает модуль из нашего объекта модуля, используя его идентификатор.
Он создает функцию localRequire, специфичную для конкретного модуля, для сопоставления строки пути к файлу с идентификатором. И объект модуля с пустым свойством экспорта
Он запускает код нашего модуля, передавая объект localrequire, модуль и экспорт в качестве аргументов, а затем возвращает модуль.exports, как это сделал бы модуль js узла.
Наконец, мы вызываем require для нашего входного модуля (индекс 0).
Чтобы протестировать наш сборщик, в рабочем каталоге нашего файла packager.js создайте файл index.js и два каталога: src и общедоступный каталог.
В общедоступном каталоге создайте файл index.html и добавьте в тег body следующий код:
Module bundler
const name = «Дэвид»
экспортировать имя по умолчанию
also create a hello.js file and add the following code
импортировать имя из «./name.js»
const hello = document.getElementById(“root”)
hello.innerHTML = имя «привет»
Lastly in the index.js file of the root directory import our bundler, bundle the files and write it to a bundle.js file in the public directory
const createBundle = require(“./bundler.js”)
const run = (выход, ввод)=>{
let Bundle = creatBundle(entry)
fs.writeFileSync(bundle, 'utf-8')
}
run(“./public/bundle.js”, “./src/hello.js”)
Open our index.html file in the browser to see the magic. In this post we have illustrated how a simple module bundler works. This is a minimal bundler meant for understanding how these technologies work behind the hood. please like if you found this insightful and comment any questions you may have.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3