Недавно я бродил по фронтенд-форуму и видел несколько статей о парцеллах и веб-пакетах, и вдруг мне стало интересно узнать об инструментах упаковки, которые используются каждый день, и каковы их принципы упаковки. Только зная это, вы сможете найти наиболее подходящий среди множества упаковочных инструментов. Прежде чем разобраться в принципах упаковки, давайте в нескольких главах объясним, почему мы используем инструменты упаковки.
0. Модульная система
Предоставление интерфейсных продуктов основано на браузере.Эти ресурсы запускаются в браузере путем постепенной загрузки.Как организовать эти фрагментированные коды и ресурсы в среде разработки и обеспечить их быструю и изящную загрузку в браузере.И для обновлений требуется модульная система. Эта идеальная модульная система представляет собой головоломку, над которой инженеры-конструкторы работали годами.
Модульная система в основном решает определение, зависимости и экспорт модулей.оригинальный<script>
Метод загрузки этикетки имеет некоторые общие недостатки: например, легко вызвать конфликты переменных в глобальной области видимости; файлы могут быть<script>
загружаются в том порядке, в котором они написаны; разработчики должны субъективно разрешать зависимости модулей и кодовой базы и т. д.
Поэтому выведено множество модульных решений:
1.CommonJs: Плюсы: Модули на стороне сервера легко использовать повторно. Недостатки: синхронный метод загрузки модуля не подходит для среды браузера, синхронный означает блокировку загрузки, а ресурсы браузера загружаются асинхронно.
2.AMD: зависит от префикса. Достоинства: подходит для асинхронной загрузки в среде браузера Недостатки: сложно читать и писать.
3.CMD: Зависимость рядом, отложить выполнение. Преимущества: легко запускать в узле; недостатки: полагаясь на упаковку SPM, логика загрузки модулей тяжелая.
4. Модуль ES6:: Старайтесь быть как можно более статичными, чтобы зависимости модулей и переменных ввода и вывода можно было определить во время компиляции. Модули CommonJS и AMD могут определять эти вещи только во время выполнения. Плюсы: Простота выполнения статического анализа Минусы: Родные браузеры не реализуют стандарт.
Когда дело доходит до загрузки и передачи модуля, если каждый файл запрашивается отдельно, это приведет к слишком большому количеству запросов и медленному запуску. Если все упаковано в одну часть и запрошено только один раз, трафик будет потрачен впустую, а процесс инициализации будет медленным.Поэтому лучшим решением будет передача по частям, выполнение ленивой загрузки по требованию и постепенное обновление определенных модулей, когда они фактически используются.. Чтобы обеспечить загрузку модулей по запросу, необходимо загрузить модуль во всей кодовой базе.Статический анализ, компиляция и упаковкапроцесс. Webpack появился в ответ на такой спрос.
Примечание. Чтобы обратить внимание на концепцию, все представляет собой модуль. Модулями можно считать стили, изображения, шрифты, шаблоны HTML и многие другие ресурсы.
1. Сборщик модулей: webpack
Webpack — это сборщик модулей. Он выполнит статический анализ в соответствии с зависимостями модулей, а затем сгенерирует соответствующие статические ресурсы для этих модулей в соответствии с заданными правилами. Итак, вопрос в том, может ли Webpack действительно выполнять статический анализ, компиляцию и упаковку, упомянутые выше? Давайте сначала посмотрим, что может сделать webpack:
1. Разделение кодаВ Webpack есть два способа организации зависимостей модулей: синхронный и асинхронный. Асинхронные зависимости служат точками разделения, формируя новый блок. После оптимизации дерева зависимостей каждый асинхронный фрагмент упаковывается в файл.
2.LoaderСам Webpack может обрабатывать только собственные модули JavaScript, но преобразователи загрузчика могут преобразовывать различные типы ресурсов в модули JavaScript. Таким образом, любой ресурс может стать модулем, с которым может работать Webpack.
3. Интеллектуальный анализВ Webpack есть интеллектуальный парсер, который может работать практически с любой сторонней библиотекой, независимо от того, является ли форма их модуля CommonJS, AMD или простыми файлами JS.
4. Система плагиновWebpack также имеет многофункциональную систему плагинов. Большая часть функциональности контента основана на этой системе плагинов, также можно разрабатывать и использовать плагины Webpack с открытым исходным кодом для удовлетворения различных потребностей.
5. Беги быстроWebpack работает эффективно, используя асинхронный ввод-вывод и многоуровневое кэширование, что позволяет Webpack выполнять невероятно быстрые инкрементные компиляции.
Выше приведены пять основных особенностей веб-пакета, но после прочтения я все еще чувствую себя немного туманно: как веб-пакет объединяет разбросанные маленькие модули в большие модули? Как быть с зависимостями каждого модуля? Давайте возьмем мини-пакет проекта с открытым исходным кодом разработчика ядра пакета @ronami в качестве примера, чтобы проиллюстрировать вышеуказанные проблемы.
2. Основные принципы упаковочных инструментов — в качестве примера возьмем мини-упаковку.
Инструмент упаковки — это инструмент, отвечающий за объединение нескольких разбросанных мелких модулей в большой модуль по определенным правилам. В то же время инструмент упаковки также будет обрабатывать зависимости между модулями и запускать проект на платформе. Проблема, которую проект минипака хочет объяснить больше всего, также является основной частью инструмента упаковки, а именно, как с ней справиться.зависимости между модулями.
Во-первых, инструмент упаковки начнет с файла входа, проанализирует зависимости в нем и далее проанализирует зависимости в зависимостях. Мы создаем три новых файла и строим зависимости:
/* name.js */
export const name = 'World'
/* message.js */
import { name } from './name.js'
export default `Hello ${name}!`
/* entry.js */
import message from './message.js'
console.log(message)
Сначала ознакомьтесь с необходимыми инструментами
/* minipack.js */
const fs = require('fs')
const path = require('path')
const babylon = require('babylon')
const traverse = require('babel-traverse').default
const { transformFromAst } = require('babel-core')
Далее мы создадим функцию, которая принимает путь к файлу в качестве аргумента, считывает содержимое файла и извлекает его зависимости.
function createAsset(filename) {
// 以字符串形式读取文件的内容.
const content = fs.readFileSync(filename, 'utf-8');
// 现在我们试图找出这个文件依赖于哪个文件。虽然我们可以通过查看其内容来获取import字符串. 然而,这是一个非常笨重的方法,我们将使用JavaScript解析器来代替。
// JavaScript解析器是可以读取和理解JavaScript代码的工具,它们生成一个更抽象的模型,称为`ast (抽象语法树)(https://astexplorer.net)`。
const ast = babylon.parse(content, {
sourceType: 'module',
});
// 定义数组,这个数组将保存这个模块依赖的模块的相对路径.
const dependencies = [];
// 我们遍历`ast`来试着理解这个模块依赖哪些模块,要做到这一点,我们需要检查`ast`中的每个 `import` 声明。
// `Ecmascript`模块相当简单,因为它们是静态的. 这意味着你不能`import`一个变量,或者有条件地`import`另一个模块。每次我们看到`import`声明时,我们都可以将其数值视为`依赖性`。
traverse(ast, {
ImportDeclaration: ({node}) =>
// 我们将依赖关系存入数组
dependencies.push(node.source.value);
},
});
// 我们还通过递增简单计数器为此模块分配唯一标识符.
const id = ID++;
// 我们使用`Ecmascript`模块和其他JavaScript,可能不支持所有浏览器。
// 为了确保我们的程序在所有浏览器中运行,
// 我们将使用[babel](https://babeljs.io)来进行转换。
// 我们可以用`babel-preset-env``将我们的代码转换为浏览器可以运行的东西.
const {code} = transformFromAst(ast, null, {
presets: ['env'],
});
// 返回有关此模块的所有信息.
return {
id,
filename,
dependencies,
code,
};
}
Теперь, когда мы можем извлечь зависимости одного модуля, мы извлечем зависимости каждой из его зависимостей и будем выполнять цикл, пока не поймем каждый модуль в приложении и то, как они зависят друг от друга.
function createGraph(entry) {
// 首先解析整个文件.
const mainAsset = createAsset(entry);
// 我们将使用queue来解析每个asset的依赖关系.
// 我们正在定义一个只有entry asset的数组.
const queue = [mainAsset];
// 我们使用一个`for ... of`循环遍历 队列.
// 最初 这个队列 只有一个asset,但是当我们迭代它时,我们会将额外的assert推入到queue中.
// 这个循环将在queue为空时终止.
for (const asset of queue) {
// 我们的每一个asset都有它所依赖模块的相对路径列表.
// 我们将重复它们,用我们的`createAsset() `函数解析它们,并跟踪此模块在此对象中的依赖关系.
asset.mapping = {};
// 这是这个模块所在的目录.
const dirname = path.dirname(asset.filename);
// 我们遍历其相关路径的列表
asset.dependencies.forEach(relativePath => {
// 我们可以通过将相对路径与父资源目录的路径连接,将相对路径转变为绝对路径.
const absolutePath = path.join(dirname, relativePath);
// 解析asset,读取其内容并提取其依赖关系.
const child = createAsset(absolutePath);
// 了解`asset`依赖取决于`child`这一点对我们来说很重要.
// 通过给`asset.mapping`对象增加一个新的属性(值为child.id)来表达这种一一对应的关系.
asset.mapping[relativePath] = child.id;
// 最后,我们将`child`这个资产推入队列,这样它的依赖关系也将被迭代和解析.
queue.push(child);
});
}
return queue;
}
Затем мы определяем функцию, передаем график из предыдущего шага и возвращаем пакет, который можно запустить в браузере.
function bundle(graph) {
let modules = '';
// 在我们到达该函数的主体之前,我们将构建一个作为该函数的参数的对象.
// 请注意,我们构建的这个字符串被两个花括号 ({}) 包裹,因此对于每个模块,
// 我们添加一个这种格式的字符串: `key: value,`.
graph.forEach(mod => {
// 图表中的每个模块在这个对象中都有一个entry. 我们用模块的id`作为`key`,用数组作为`value`
// 第一个参数是用函数包装的每个模块的代码. 这是因为模块应该被限定范围: 在一个模块中定义变量不会影响其他模块或全局范围.
// 对于第二个参数,我们用`stringify`解析模块及其依赖之间的关系(也就是上文的asset.mapping). 解析后的对象看起来像这样: `{'./relative/path': 1}`.
// 这是因为我们模块的被转换后会通过相对路径来调用`require()`. 当调用这个函数时,我们应该能够知道依赖图中的哪个模块对应于该模块的相对路径.
modules += `${mod.id}: [
function (require, module, exports) { ${mod.code} },
${JSON.stringify(mod.mapping)},
],`;
/ 最后,使用`commonjs`,当模块需要被导出时,它可以通过改变exports对象来暴露模块的值.
// require函数最后会返回exports对象.
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;
});
бегать!
const graph = createGraph('./example/entry.js');
const result = bundle(graph);
//得到结果,开心!
console.log(result);
Для получения дополнительной информации посетите проектгитхаб-адрес
3. Резюме
Webpack решает проблему потенциальных циклических зависимостей между пакетами и в то же время объединяет статические файлы по запросу, чтобы избежать проблем с параллелизмом браузера на этапе выборки по сети. В дополнение к упаковке могут быть дополнительно реализованы сжатие (уменьшение сетевой передачи) и компиляция (обратная совместимость с ES6, JSX и другими синтаксисами).
Исходя из конфигурации файла webpack.config.js, принцип работы упаковки можно обобщить следующим образом: берем логику страницы в целом, через заданный файл входа, вебпак стартует с этого файла и находит все зависимые файлы, упаковка , компиляция, сжатие и, наконец, вывод распознаваемого браузером JS-файла.
Инструмент упаковки модуля, первый шаг начнется с входного файла и выполнит для него анализ зависимостей, второй шаг снова рекурсивно проанализирует все его зависимости, третий шаг создаст атлас зависимостей модуля, а последний шаг будет основан на атлас зависимостей.Окончательный код построен с использованием спецификации CommonJS.
4. URL-адрес ссылки
https://mp.weixin.qq.com/s/w-oXmHNSyu0Y_IlfmDwJKQ
https://github.com/chinanf-boy/minipack-explain/blob/master/src/minipack.js
https://zhaoda.net/webpack-handbook/configuration.html