предисловие
[Серия практических занятий] в основном позволяет нам углубить наше понимание некоторых принципов посредством практики.
[Практическая серия] Маршрутизация внешнего интерфейса
[Серия упражнений] Принцип Вавилона
[Серия упражнений] На этот раз потренируйтесь, досконально изучите механизм кэширования браузера.
[Серия упражнений] Можете ли вы написать обещание от руки? Да, я обещаю.
Заинтересованные студенты могут подписаться[Серия упражнений]. спрашивай звезду спрашивай следуй~
Эта статья понимает его принцип упаковки, реализуя простой веб-пакет, и я не понимаю его после прочтения!!!
Что такое вебпак?
По сути, webpack — это сборщик статических модулей для современных приложений JavaScript. Когда webpack обрабатывает приложение, он рекурсивно строит граф зависимостей, содержащий каждый модуль, необходимый приложению, а затем объединяет все эти модули в один или несколько пакетов.
webpack похож на производственную линию, которая проходит ряд этапов обработки перед преобразованием исходных файлов в выходные результаты. Ответственность каждого процесса обработки на этой производственной линии едина, и между несколькими процессами существует отношение зависимости.Только после завершения текущей обработки он может быть передан следующему процессу для обработки. Плагин похож на функцию, встроенную в производственную линию, обрабатывающую ресурсы на производственной линии в определенное время.
Webpack использует Tapable для организации этой сложной производственной линии. Webpack будет транслировать события во время выполнения процесса, а плагину нужно только прослушивать те события, о которых он заботится, а затем его можно добавить в производственную линию, чтобы изменить работу производственной линии. Механизм потока событий webpack обеспечивает упорядоченность плагинов, делая всю систему очень масштабируемой. -- Углубленный веб-пакет Ву Хаолинь
основные концепции веб-пакета
Entry
Точка входа указывает, какой модуль веб-пакет должен использовать в качестве начала построения своего внутреннего графа зависимостей.
После входа в точку входа webpack узнает, какие модули и библиотеки (напрямую и косвенно) зависят от точки входа.
Затем каждая зависимость обрабатывается и выводится в файлы, называемые пакетами.
Output
Свойство output сообщает webpack, куда выводить созданные им пакеты и как называть эти файлы.Значение по умолчанию — ./dist.
По сути, вся структура приложения будет скомпилирована в папку, которую вы укажете в выходном пути.
Module
Модули, в Webpack все является модулем, модуль соответствует файлу. Webpack будет рекурсивно находить все зависимые модули, начиная с настроенного Entry.
Chunk
Блок кода, фрагмент состоит из нескольких модулей для слияния и разделения кода.
Loader
Загрузчики позволяют веб-пакету обрабатывать файлы, отличные от JavaScript (сам веб-пакет понимает только JavaScript).
Загрузчики могут преобразовывать все типы файлов в действительные модули, которые может обрабатывать веб-пакет, а затем вы можете воспользоваться возможностями упаковки веб-пакета и обработать их.
По сути, загрузчик веб-пакетов преобразует все типы файлов в модули, на которые может напрямую ссылаться граф зависимостей приложения (и, в конечном счете, пакет).
Plugin
Загрузчики используются для преобразования определенных типов модулей, а плагины могут использоваться для выполнения более широкого круга задач.
Плагины варьируются от оптимизации упаковки и сжатия до переопределения переменных в среде. Интерфейс подключаемого модуля является чрезвычайно мощным и может использоваться для решения широкого круга задач.
процесс сборки вебпака
Текущий процесс Webpack — это последовательный процесс, и следующие процессы будут выполняться последовательно от начала до конца:
Параметры инициализации: чтение и объединение параметров из файлов конфигурации и операторов оболочки для получения окончательных параметров.
Начать компиляцию: Инициализируйте объект Compiler с параметрами, полученными на предыдущем шаге, загрузите все настроенные плагины и выполните метод запуска объекта, чтобы начать компиляцию.
Определите запись: Найдите все файлы записей в соответствии с записью в конфигурации.
Скомпилируйте модуль: начиная с файла записи, вызовите все настроенные загрузчики для перевода модуля, затем найдите модули, от которых зависит модуль, а затем повторите этот шаг, пока все файлы, от которых зависит запись, не будут обработаны на этом шаге. .
Завершение компиляции модуля: После перевода всех модулей с помощью Loader на шаге 4 получается окончательное содержимое каждого модуля после перевода и зависимости между ними.
Выходные ресурсы: в соответствии с зависимостями между записью и модулем соберите их в чанки, содержащие несколько модулей, а затем преобразуйте каждый чанк в отдельный файл и добавьте его в выходной список.Этот шаг — последний шанс изменить выходное содержимое. . . .
Вывод завершен: после определения содержимого вывода определите путь вывода и имя файла в соответствии с конфигурацией и запишите содержимое файла в файловую систему.
В приведенном выше процессе Webpack будет транслировать определенное событие в определенный момент времени, подключаемый модуль будет выполнять определенную логику после прослушивания интересующего события, а подключаемый модуль может вызывать API, предоставленный Webpack, для изменения текущего результат вебпака.
Практикуйтесь, чтобы углубить понимание и создать простой веб-пакет
1. Определите класс компилятора
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {}
// 重写 require函数,输出bundle
generate() {}
}
2. Разберите файл записи и получите AST
Здесь мы используем @babel/parser, который является инструментом babel7, помогающим нам анализировать внутренние грамматики, включая es6, и возвращать абстрактное синтаксическое дерево AST.
// webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'main.js'
}
}
//
const fs = require('fs')
const parser = require('@babel/parser')
const options = require('./webpack.config')
const Parser = {
getAst: path => {
// 读取入口文件
const content = fs.readFileSync(path, 'utf-8')
// 将文件内容转为AST抽象语法树
return parser.parse(content, {
sourceType: 'module'
})
}
}
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {
const ast = Parser.getAst(this.entry)
}
// 重写 require函数,输出bundle
generate() {}
}
new Compiler(options).run()
3. Найдите все зависимые модули
Babel предоставляет метод @babel/traverse (обход) для поддержания общего состояния дерева AST, который мы используем здесь, чтобы помочь нам найти зависимые модули.
const fs = require('fs')
const path = require('path')
const options = require('./webpack.config')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const Parser = {
getAst: path => {
// 读取入口文件
const content = fs.readFileSync(path, 'utf-8')
// 将文件内容转为AST抽象语法树
return parser.parse(content, {
sourceType: 'module'
})
},
getDependecies: (ast, filename) => {
const dependecies = {}
// 遍历所有的 import 模块,存入dependecies
traverse(ast, {
// 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
// 保存依赖模块路径,之后生成依赖关系图需要用到
const filepath = './' + path.join(dirname, node.source.value)
dependecies[node.source.value] = filepath
}
})
return dependecies
}
}
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {
const { getAst, getDependecies } = Parser
const ast = getAst(this.entry)
const dependecies = getDependecies(ast, this.entry)
}
// 重写 require函数,输出bundle
generate() {}
}
new Compiler(options).run()
4. Преобразование AST в код
Преобразуйте синтаксическое дерево AST в исполняемый код браузера, здесь мы используем @babel/core и @babel/preset-env.
const fs = require('fs')
const path = require('path')
const options = require('./webpack.config')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
const Parser = {
getAst: path => {
// 读取入口文件
const content = fs.readFileSync(path, 'utf-8')
// 将文件内容转为AST抽象语法树
return parser.parse(content, {
sourceType: 'module'
})
},
getDependecies: (ast, filename) => {
const dependecies = {}
// 遍历所有的 import 模块,存入dependecies
traverse(ast, {
// 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
// 保存依赖模块路径,之后生成依赖关系图需要用到
const filepath = './' + path.join(dirname, node.source.value)
dependecies[node.source.value] = filepath
}
})
return dependecies
},
getCode: ast => {
// AST转换为code
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return code
}
}
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {
const { getAst, getDependecies, getCode } = Parser
const ast = getAst(this.entry)
const dependecies = getDependecies(ast, this.entry)
const code = getCode(ast)
}
// 重写 require函数,输出bundle
generate() {}
}
new Compiler(options).run()
5. Рекурсивно разрешите все зависимости и сгенерируйте граф зависимостей
const fs = require('fs')
const path = require('path')
const options = require('./webpack.config')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
const Parser = {
getAst: path => {
// 读取入口文件
const content = fs.readFileSync(path, 'utf-8')
// 将文件内容转为AST抽象语法树
return parser.parse(content, {
sourceType: 'module'
})
},
getDependecies: (ast, filename) => {
const dependecies = {}
// 遍历所有的 import 模块,存入dependecies
traverse(ast, {
// 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
// 保存依赖模块路径,之后生成依赖关系图需要用到
const filepath = './' + path.join(dirname, node.source.value)
dependecies[node.source.value] = filepath
}
})
return dependecies
},
getCode: ast => {
// AST转换为code
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return code
}
}
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {
// 解析入口文件
const info = this.build(this.entry)
this.modules.push(info)
this.modules.forEach(({ dependecies }) => {
// 判断有依赖对象,递归解析所有依赖项
if (dependecies) {
for (const dependency in dependecies) {
this.modules.push(this.build(dependecies[dependency]))
}
}
})
// 生成依赖关系图
const dependencyGraph = this.modules.reduce(
(graph, item) => ({
...graph,
// 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
[item.filename]: {
dependecies: item.dependecies,
code: item.code
}
}),
{}
)
}
build(filename) {
const { getAst, getDependecies, getCode } = Parser
const ast = getAst(filename)
const dependecies = getDependecies(ast, filename)
const code = getCode(ast)
return {
// 文件路径,可以作为每个模块的唯一标识符
filename,
// 依赖对象,保存着依赖模块路径
dependecies,
// 文件内容
code
}
}
// 重写 require函数,输出bundle
generate() {}
}
new Compiler(options).run()
6. Перепишите функцию require для вывода пакета
const fs = require('fs')
const path = require('path')
const options = require('./webpack.config')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
const Parser = {
getAst: path => {
// 读取入口文件
const content = fs.readFileSync(path, 'utf-8')
// 将文件内容转为AST抽象语法树
return parser.parse(content, {
sourceType: 'module'
})
},
getDependecies: (ast, filename) => {
const dependecies = {}
// 遍历所有的 import 模块,存入dependecies
traverse(ast, {
// 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
// 保存依赖模块路径,之后生成依赖关系图需要用到
const filepath = './' + path.join(dirname, node.source.value)
dependecies[node.source.value] = filepath
}
})
return dependecies
},
getCode: ast => {
// AST转换为code
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return code
}
}
class Compiler {
constructor(options) {
// webpack 配置
const { entry, output } = options
// 入口
this.entry = entry
// 出口
this.output = output
// 模块
this.modules = []
}
// 构建启动
run() {
// 解析入口文件
const info = this.build(this.entry)
this.modules.push(info)
this.modules.forEach(({ dependecies }) => {
// 判断有依赖对象,递归解析所有依赖项
if (dependecies) {
for (const dependency in dependecies) {
this.modules.push(this.build(dependecies[dependency]))
}
}
})
// 生成依赖关系图
const dependencyGraph = this.modules.reduce(
(graph, item) => ({
...graph,
// 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
[item.filename]: {
dependecies: item.dependecies,
code: item.code
}
}),
{}
)
this.generate(dependencyGraph)
}
build(filename) {
const { getAst, getDependecies, getCode } = Parser
const ast = getAst(filename)
const dependecies = getDependecies(ast, filename)
const code = getCode(ast)
return {
// 文件路径,可以作为每个模块的唯一标识符
filename,
// 依赖对象,保存着依赖模块路径
dependecies,
// 文件内容
code
}
}
// 重写 require函数 (浏览器不能识别commonjs语法),输出bundle
generate(code) {
// 输出文件路径
const filePath = path.join(this.output.path, this.output.filename)
// 懵逼了吗? 没事,下一节我们捋一捋
const bundle = `(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependecies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code);
return exports;
}
require('${this.entry}')
})(${JSON.stringify(code)})`
// 把文件内容写入到文件系统
fs.writeFileSync(filePath, bundle, 'utf-8')
}
}
new Compiler(options).run()
7. Прочитав этот раздел, досконально разберитесь с реализацией пакета.
Давайте используем следующий пример, чтобы объяснить, сначала смотрите на смерть в течение 30 секунд.
;(function(graph) {
function require(moduleId) {
function localRequire(relativePath) {
return require(graph[moduleId].dependecies[relativePath])
}
var exports = {}
;(function(require, exports, code) {
eval(code)
})(localRequire, exports, graph[moduleId].code)
return exports
}
require('./src/index.js')
})({
'./src/index.js': {
dependecies: { './hello.js': './src/hello.js' },
code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
},
'./src/hello.js': {
dependecies: {},
code:
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
}
})
шаг 1: начать выполнение из файла ввода
// 定义一个立即执行函数,传入生成的依赖关系图
;(function(graph) {
// 重写require函数
function require(moduleId) {
console.log(moduleId) // ./src/index.js
}
// 从入口文件开始执行
require('./src/index.js')
})({
'./src/index.js': {
dependecies: { './hello.js': './src/hello.js' },
code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
},
'./src/hello.js': {
dependecies: {},
code:
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
}
})
шаг 2: выполнить код, используя eval
// 定义一个立即执行函数,传入生成的依赖关系图
;(function(graph) {
// 重写require函数
function require(moduleId) {
;(function(code) {
console.log(code) // "use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));
eval(code) // Uncaught TypeError: Cannot read property 'code' of undefined
})(graph[moduleId].code)
}
// 从入口文件开始执行
require('./src/index.js')
})({
'./src/index.js': {
dependecies: { './hello.js': './src/hello.js' },
code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
},
'./src/hello.js': {
dependecies: {},
code:
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
}
})
Как видите, мы сообщили об ошибке при выполнении кода в файле «./src/index.js», так как index.js ссылается на зависимость hello.js, а мы не обрабатывали зависимость. к зависимости, которую нужно обработать.
Шаг 3. Используйте сопоставление адресации объектов для получения объекта экспорта.
// 定义一个立即执行函数,传入生成的依赖关系图
;(function(graph) {
// 重写require函数
function require(moduleId) {
// 找到对应moduleId的依赖对象,调用require函数,eval执行,拿到exports对象
function localRequire(relativePath) {
return require(graph[moduleId].dependecies[relativePath]) // {__esModule: true, say: ƒ say(name)}
}
// 定义exports对象
var exports = {}
;(function(require, exports, code) {
// commonjs语法使用module.exports暴露实现,我们传入的exports对象会捕获依赖对象(hello.js)暴露的实现(exports.say = say)并写入
eval(code)
})(localRequire, exports, graph[moduleId].code)
// 暴露exports对象,即暴露依赖对象对应的实现
return exports
}
// 从入口文件开始执行
require('./src/index.js')
})({
'./src/index.js': {
dependecies: { './hello.js': './src/hello.js' },
code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
},
'./src/hello.js': {
dependecies: {},
code:
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
}
})
Теперь вы должны понять ~ Вы можете напрямую скопировать приведенный выше код в вывод консоли ~
Полный код адреса штампуйте меня 👈
Суммировать
Webpack — это огромное приложение Node.js. Если вы прочтете его исходный код, то обнаружите, что для реализации полноценного Webpack требуется написать много кода. Но вам не нужно разбираться во всех деталях, достаточно понять его общую архитектуру и некоторые детали.
Для пользователей Webpack это простой и мощный инструмент, для разработчиков Webpack это очень расширяемая система.
Причина успеха Webpack заключается в том, что он скрывает сложную реализацию и предоставляет пользователям только простой инструмент, позволяющий пользователям быстро достигать своих целей. В то же время общий дизайн архитектуры разумен, масштабируемость высока, а разработка и расширение несложны.Большое количество недостающих функций дополняется сообществом, так что Webpack можно использовать практически в любом сценарии.
Ссылаться на
документация webpack на китайском
постскриптум
Если вам нравится передняя часть так же, как и мне, и вы любите подбрасывать руками, пожалуйста, следуйте за мной и поиграйте со мной~ ❤️
адрес github, добро пожаловать в подписку~
блог
Мой блог, нажми на звездочку, не теряйся~
публика
передний момент