Интерпретация исходного кода Webpack: разъяснение основного процесса компиляции

внешний интерфейс Webpack
Интерпретация исходного кода Webpack: разъяснение основного процесса компиляции

предисловие

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

На момент написания этой статьи последняя версия веб-пакетаwebpack 5.0.0-beta.1, то есть исходный код этой статьи взят из последнейwebpack v5.

В частности, исходный код, указанный в этой статье, был оптимизирован.Если вы хотите увидеть конкретный код, вы можете посетить официальную библиотеку веб-пакетов в соответствии с именем файла исходного кода, которое я определил. Сокращенная часть этой статьи:

  • Убран импорт модуля, т.е.const xxx = require('XXX');
  • Итоговый код исключения, хотя обработка исключений тоже очень важна, в этой статье в основном анализируется основной процесс нормальной работы вебпака, если нельзя игнорировать обработку исключений, я конкретно объясню;

Как отлаживать веб-пакет

Я всегда думаю, что изучение исходного кода — это не чтение кода построчно, для зрелого проекта с открытым исходным кодом должно быть много замысловатых ветвей. Попытка отладить код шаг за шагом, чтобы проследить путь программы, — это самый быстрый способ быстро понять базовую структуру проекта.

Идеальная функция Debugger в редакторе VS Code — лучший инструмент для отладки программ Node.

  1. Во-первых, чтобы изучить исходный код веб-пакета, вы должны сначала клонировать исходный код из библиотеки веб-пакета в локальный:
git clone https://github.com/webpack/webpack.git
  1. Установите зависимости проекта; VS Code открывает локальный репозиторий веб-пакетов
npm install
cd webpack/
code .
  1. Чтобы не загрязнять корневой каталог проекта, создайте новый в корневом каталогеdebugПапка для хранения кода отладки,debugСтруктура папок следующая:
debug-|
      |--dist    // 打包后输出文件
      |--src
         |--index.js   // 源代码入口文件
      |--package.json  // debug时需要安装一些loader和plugin
      |--start.js      // debug启动文件
      |--webpack.config.js  // webpack配置文件

Подробный код отладки выглядит следующим образом:

//***** debug/src/index.js *****
import is from 'object.is'  // 这里引入一个小而美的第三方库,以此观察webpack如何处理第三方包
console.log('很高兴认识你,webpack')
console.log(is(1,1))


//***** debug/start.js *****
const webpack = require('../lib/index.js')  // 直接使用源码中的webpack函数
const config = require('./webpack.config')
const compiler = webpack(config)
compiler.run((err, stats)=>{
    if(err){
        console.error(err)
    }else{
        console.log(stats)
    }
})


//***** debug/webpack.config.js *****
const path = require('path')
module.exports = {
    context: __dirname,
    mode: 'development',
    devtool: 'source-map',
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: ['babel-loader'],
                exclude: /node_modules/,
            }
        ]
    }
}
  1. в VS-кодеDebugbar добавить конфигурацию отладки:
{
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "启动webpack调试程序",
            "program": "${workspaceFolder}/debug/start.js"
        }
    ]
}

После завершения настройки попробуйте один раз щелкнуть ► (Пуск), чтобы проверить, правильно ли работает отладчик (в случае успеха вdebug/distупакуетmain.jsдокумент).

Если у вас есть время, я надеюсь, что вы сможете пройти процесс отладки веб-пакета самостоятельно, я думаю, вы что-то выиграете. Человеку свойственно исследовать.

Далее давайте пошагово проанализируем, как работает webpack, посредством отладки точки останова.

Интерпретация исходного кода

Как запустить вебпак

Есть два способа запустить вебпак:

  1. пройти черезwebpack-cliЛеса для начала, т.е. можноTerminalТерминал работает напрямую;
webpack ./debug/index.js --config ./debug/webpack.config.js

Этот метод является наиболее часто используемым и самым быстрым методом из коробки.

  1. пройти черезrequire('webpack')Выполнить, импортировав пакет;

Фактически, первый метод в конечном итоге будет использоватьсяrequireСпособ запуска вебпака, можете посмотреть если интересно./bin/webpack.jsдокумент.

Отправная точка для компиляции веб-пакета

все отconst compiler = webpack(config)Начинать.

исходный код функции webpack (./lib/webpack.js):

const webpack = (options, callback) => {
    let compiler = createCompiler(options)
    // 如果传入callback函数,则自启动
    if(callback){
        compiler.run((err, states) => {
            compiler.close((err2)=>{
                callbacl(err || err2, states)
            })
        })
    }
    return compiler
}

возврат после выполнения функции webpackcompilerObject, в webpack есть два очень важных основных объекта, а именноcompilerиcompilation, они широко используются в процессе компиляции.

  • Compilerсвоего рода(./lib/Compiler.js): основной механизм веб-пакета, полная информация о среде веб-пакета записывается в объект компилятора от начала до конца в веб-пакете,compilerбудет сгенерирован только один раз. ты сможешьcompilerчитать на объектеwebpack configИнформация,outputPathЖдать;
  • Compilationсвоего рода(./lib/Compilation.js): представляет сборку одной версии и ресурс сборки.compilationРабота компиляции может быть выполнена несколько раз, например WebPack работает вwatchВ режиме каждый раз, когда в исходном файле обнаруживается изменение, будет повторно создаваться новый экземпляр.compilationобъект. ОдинcompilationОбъекты представляют текущие ресурсы модуля, скомпилированные ресурсы, измененные файлы и информацию о состоянии отслеживаемых зависимостей.

Разница между ними?
Компилятор представляет неизменную среду webpack; компиляция представляет собой задание компиляции, и каждая компиляция может быть разной;

Возьмите каштан 🌰:
Компилятор подобен производственной линии мобильных телефонов, он может начать работать после включения питания, ожидая инструкций по производству мобильных телефонов; Комплиация похожа на производство мобильного телефона.Производственный процесс в основном такой же, но производимый мобильный телефон может быть телефоном Xiaomi или телефоном Meizu. Разные материалы имеют разную производительность.

Compilerкласс в функцииcreateCompilerэкземпляр в (./lib/index.js):

const createCompiler = options => {
    const compiler = new Compiler(options.context)
    // 注册所有的自定义插件
    if(Array.isArray(options.plugins)){
        for(const plugin of options.plugins){
            if(typeof plugin === 'function'){
                plugin.call(compiler, compiler)
            }else{
                plugin.apply(compiler)
            }
        }
    }
    compiler.hooks.environment.call()
    compiler.hooks.afterEnvironment.call()
    compiler.options = new WebpackOptionsApply().process(options, compiler)  // process中注册所有webpack内置的插件
    return compiler
}

CompilerПосле создания экземпляра класса, если функция веб-пакета получает обратный вызовcallback, затем выполнить напрямуюcompiler.run()метод, то webpack автоматически начинает процесс компиляции. если не указаноcallbackОбратный вызов, который должен быть вызван пользователемrunспособ инициировать компиляцию.

Из приведенного выше исходного кода можно извлечь некоторую информацию:

  • компиляторCompilerInstantiation, свойства и методы в нем будут упомянуты в следующем разделе, наиболее важными из которых являютсяcompiler.run()метод;

  • траверсwebpack configсерединамассив плагинов, вот я смелыйplugins数组, поэтому не настраивайте его как объект при настройке плагинов. (На самом деле опции делаются в функции webpackobject schemaКалибровка).

  • plugin: Если подключаемый модуль является функцией, вызовите его напрямую; если подключаемый модуль относится к другим типам (в основном типам объектов), выполните объект подключаемого модуля.applyметод. применить сигнатуру функции:(compiler) => {}.

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

    {
    plugins: [ new HtmlWebpackPlugin() ]
    }
    
  • Вызвать крючок:compiler.hooks.environment.call()а такжеcompiler.hooks.afterEnvironment.call()Это первый вызов хука, с которым мы столкнулись при чтении исходного кода, при последующем чтении вы встретите больше регистраций и вызовов хука. Чтобы понять применение хуков webpack, вам нужно сначала понятьTapable, который является основой для написания плагинов.

    Что касается Tapable, я с этим «разберусь». --> У меня есть это в файле, иди и посмотри«Написание собственного плагина веб-пакета начинается с понимания Tapable»

  • process(options): в конфиге вебпака, кромеpluginsЕсть много других полей, затемprocess(options)Роль состоит в том, чтобы обрабатывать эти поля одно за другим.

Пока что мы понимаем, какие приготовления делает webpack на этапе инициализации. когда горит предохранительcompiler.run()Когда наступит время, когда веб-пакет станет действительно мощным. "Войска и кони не двигаются, еда и трава идут первыми", перед этим нужно сначала посмотреть на этоnew WebpackOptionsApply().process(options, compiler)Какая бы подготовительная работа ни была проделана, она обеспечивает важную логистическую защиту для последующего этапа компиляции.

process(options, compiler)

WebpackOptionsApplyКлассная работа правильнаяwebpack optionsдля инициализации. Файл с открытым исходным кодомlib/WebpackOptionsApply.js, вы обнаружите, что все первые пятьдесят строк встроены в различные веб-пакеты.Plugin, то можно предположить, чтоprocessметоды должны быть разнымиnew SomePlugin().apply()операция, как есть.

Оптимизированный исходный код (lib/WebpackOptionsApply.js):

class WebpackOptionsApply extends OptionsApply {
    constructor() {
        super();
    }
    process(options, compiler){
    // 当传入的配置信息满足要求,处理与配置项相关的逻辑
        if(options.target) {
            new OnePlugin().apply(compiler)
        }
        if(options.devtool) {
            new AnotherPlugin().apply(compiler)
        }
        if ...
		
        new JavascriptModulesPlugin().apply(compiler);
        new JsonModulesPlugin().apply(compiler);
        new ...
		
        compiler.hooks.afterResolvers.call(compiler);
    }
}

в исходном коде...Многоточие пропускает многие подобные операции,processФункция очень длинная, почти 500 строк кода, и в основном она делает две вещи:

  1. newПолноPluginapplyOни.

    В предыдущем разделе мы знали, что подключаемый модуль веб-пакета на самом деле является классом, который предоставляет метод применения, который будет создан веб-пакетом и при необходимости выполнит метод применения. И метод apply получаетcompilerОбъект, удобный для прослушивания сообщений на хуках. в то же времяprocessКаждый экземпляр функцииPluginВсе они поддерживаются самим веб-пакетом, поэтому вы обнаружите, что в корневом каталоге проекта веб-пакета есть много файлов.Pluginконечный файл. Пользовательский подключаемый модуль был зарегистрирован ранее. Разные плагины имеют свои разные задачи, их задача — зацепитьcompiler.hooksВ сообщении, как только сообщение инициировано, обратные вызовы, зарегистрированные в сообщении, вызываются по очереди в соответствии с типом ловушки. Три способа так называемой «зацепки»:tap tapAsync tapPromise, ты должен знатьTapableО, как это работает.

  2. в соответствии сoptions.xxxэлементы конфигурации, выполнить работу по инициализации, и большая часть работы по инициализации по-прежнему выполняется выше 👆

Это резюме подводит итог:processПосле того, как функция выполнена, webpack регистрирует все сообщения ловушек, о которых он заботится, и ждет, пока последующий процесс компиляции будет запущен одно за другим.

воплощать в жизньprocessМетод Загрузите боеприпасы и дождитесь начала боя.

compiler.run()

Сначала вставьте исходный код (./lib/Compiler.js):

class Compiler {
    constructor(context){
    // 所有钩子都是由`Tapable`提供的,不同钩子类型在触发时,调用时序也不同
    this.hooks = {
            beforeRun: new AsyncSeriesHook(["compiler"]),
            run: new AsyncSeriesHook(["compiler"]),
            done: new AsyncSeriesHook(["stats"]),
            // ...
        }
    }
  
    // ...
	
    run(callback){
        const onCompiled = (err, compilation) => {
            if(err) return
            const stats = new Stats(compilation);
            this.hooks.done.callAsync(stats, err => {
                if(err) return
                callback(err, stats)
                this.hooks.afterDone.call(stats)
            })
        }
        this.hooks.beforeRun.callAsync(this, err => {
            if(err) return
            this.hooks.run.callAsync(this, err => {
                if(err) return
                this.compile(onCompiled)
            })
        })
    }
}

прочитатьrunфункции, вы обнаружите, что она перехватывает некоторые этапы процесса компиляции и вызывает предварительно зарегистрированную функцию-перехватчик на соответствующем этапе (this.hooks.xxxx.call(this)), эффект такой же, как у функции жизненного цикла в React. существуетrunХуки, которые появляются в функции:beforeRun --> run --> done --> afterDone. Сторонние плагины могут подключаться к различным жизненным циклам, получатьcompilerОбъекты, которые обрабатывают другую логику.

runФункция перехватывает ранние и поздние этапы компиляции веб-пакета, поэтому наиболее важный процесс компиляции кода в среднесрочной перспективе передается наthis.compile()заканчивать. существуетthis.comille(), еще один главный геройcompilationПоявились чернила.

compiler.compile()

compiler.compileФункции — это основное поле битвы при компиляции модулей.

compile(callback){
    const params = this.newCompilationParams()  // 初始化模块工厂对象
    this.hooks.beforeCompile.callAsync(params, err => {
        this.hooks.compile.call(params)
        // compilation记录本次编译作业的环境信息 
        const compilation = new Compilation(this)
        this.hooks.make.callAsync(compilation, err => {
            compilation.finish(err => {
                compilation.seal(err=>{
                    this.hooks.afterCompile.callAsync(compilation, err => {
                        return callback(null, compilation)
                    })
                })
            })
        })
    })
}

compileфункция иrunНапример, запустить серию функций ловушек, вcompileХуки, которые появляются в функции:beforeCompile --> compile --> make --> afterCompile.

вmakeЭто процесс компиляции, о котором мы заботимся. Но здесь он срабатывает только от хука, очевидно, что реальное выполнение компиляции регистрируется на обратном вызове этого хука.

веб-пакет, потому что естьTapableС благословения , написание кода очень гибкое, популярный механизм обратного вызова в узле (то есть ад обратного вызова) и непревзойденное использование вебпака, если вы отлаживаете с точками останова, его может быть непросто поймать. Здесь я использую метод поиска ключевых слов для обратного просмотраmakeГде прописан хук.

поиск по ключевым словамhooks.make.tapAsyncмы нашли вlib/EntryPlugin.jsнашел свою фигуру.

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

На данный момент, мы собираемся проверить этоEntryPluginКогда это было вызвано, продолжить ключевые словаnew EntryPluginискать вlib/EntryOptionPlugin.jsнашел его в , и в нем вы нашли знакомые "вещи":

if(typeof entry === "string" || Array.isArray(entry)){
	applyEntryPlugins(entry, "main")
}else if (typeof entry === "object") {
  for (const name of Object.keys(entry)) {
    applyEntryPlugins(entry[name], name);
  }
} else if (typeof entry === "function") {
  new DynamicEntryPlugin(context, entry).apply(compiler);
}

помнить вwebpack.config.jsсередина,entryКак настроены поля? В этот момент вы пойметеentryКогда это строка или массив, упакованные ресурсы коллективно называютсяmain.jsэто имя.

Наше отступление еще не закончено, продолжаем искать ключевые словаnew EntryOptionPlugin, К сожалению, искомый файлlib/WebpackOptionsApply.js. Итак, все ясно,makeзацепитьprocessФункция уже зарегистрирована и ждет вашего звонка.

назадlib/EntryPlugin.jsВзгляниcompiler.hooks.make.tapAsyncЧто вы наделали. На самом деле он запущенcompiliation.addEntryметод, продолжайте изучатьcompiliation.addEntry.

addEntry(context, entry, name, callback) {
    this.hooks.addEntry.call(entry, name);
    // entryDependencies中的每一项都代表了一个入口,打包输出就会有多个文件
    let entriesArray = this.entryDependencies.get(name)
	entriesArray.push(entry)
    this.addModuleChain(context, entry, (err, module) => {
        this.hooks.succeedEntry.call(entry, name, module);
        return callback(null, module);
    })
}

addEntryФункция состоит в том, чтобы передать входную информацию модуля в цепочку модулей, то естьaddModuleChain, а затем продолжить вызовcompiliation.factorizeModule, эти вызовы в конечном итогеentryВходная информация «переводится» в модуль (строго говоря, модульNormalModuleсозданный объект). Когда я читал этот исходный код, мне было немного трудно его понять.entryОбработка должна быть синхронной, позже выяснилосьprocess.nextTickИспользование многих обратных вызовов вызывается асинхронно. Здесь рекомендуется иметь больше точек останова и больше отладки, чтобы понять извилистые асинхронные обратные вызовы.

Здесь я перечисляю порядок вызова соответствующих функций:this.addEntry --> this.addModuleChain --> this.handleModuleCreation --> this.addModule --> this.buildModule --> this._buildModule --> module.build(this指代compiliation)`.

в конце концов пойдетNormalModuleобъект (./lib/NormalModule.js), выполнятьbuildметод.

существуетnormalModule.buildметод сначала вызовет сам себяdoBuildметод:

const { runLoaders } = require("loader-runner");
doBuild(options, compilation, resolver, fs, callback){
    // runLoaders从包'loader-runner'引入的方法
    runLoaders({
        resource: this.resource,  // 这里的resource可能是js文件,可能是css文件,可能是img文件
        loaders: this.loaders,
    }, (err, result) => {
        const source = result[0];
        const sourceMap = result.length >= 1 ? result[1] : null;
        const extraInfo = result.length >= 2 ? result[2] : null;
        // ...
    })
}

фактическиdoBuildзаключается в выборе подходящегоloaderзагрузитьresource, цель состоит в том, чтобы преобразовать этоresourceПреобразование в модули JS (причина в том, что webpack распознает только модули JS). Наконец, верните загруженный исходный файлsourceпродолжить обработку.

webpack очень хорошо справляется со стандартными модулями JS, но бессилен обрабатывать другие типы файлов (css, scss, json, jpg) и т. д. В данный момент ему нужна помощь загрузчика. Роль загрузчика — преобразовать исходный код в JS-модули, чтобы веб-пакет мог их правильно идентифицировать.loaderЭта функция похожа на конвейер потока информации в Linux: она получает исходный поток строк, обрабатывает его, а затем возвращает обработанную исходную строку следующему потоку.loaderПродолжить обработку.loaderОсновная парадигма:(code, sourceMap, meta) => string

прошедшийdoBuildПосле этого любые модули конвертируются в стандартные модули JS.

Вы можете попробовать внедрить css-код в js-код и понаблюдать за структурой данных преобразованного стандартного JS-модуля.

Следующим шагом будет компиляция стандартного кода JS. во входящемdoBuildЭто обрабатывается в функции обратного вызоваsource:

const result = this.parser.parse(source)

А вотthis.parserНа самом деле этоJavascriptParserОбъект экземпляра финалаJavascriptParserбудет вызывать сторонние пакетыacornкоторый предоставилparseМетод анализирует исходный код JS.

parse(code, options){
    // 调用第三方插件`acorn`解析JS模块
    let ast = acorn.parse(code)
    // 省略部分代码
    if (this.hooks.program.call(ast, comments) === undefined) {
        this.detectStrictMode(ast.body)
        this.prewalkStatements(ast.body)
        this.blockPrewalkStatements(ast.body)
        // 这里webpack会遍历一次ast.body,其中会收集这个模块的所有依赖项,最后写入到`module.dependencies`中
        this.walkStatements(ast.body)
    }
}

Есть онлайн гаджетAST explorerПреобразовать JS-код в синтаксическое дерево AST можно онлайн, выбрав в качестве парсераacornВот и все. будет отлаживать код./debug/src/index.jsиспользоватьacronРазбираем грамматику и получаем следующую структуру данных:

Вы можете быть немного сбиты с толку, обычно мы используем что-то вродеbabel-loaderПодождите, пока загрузчик предварительно обработает исходные файлы, затем веб-пакет здесьparseКакова конкретная роль?parseСамая большая роль заключается в сборе зависимостей модулей, таких как те, которые появляются в отладочном коде.import {is} from 'object-is'илиconst xxx = require('XXX')Оператор импорта модуля , webpack запишет эти зависимости, записанные вmodule.dependenciesв массиве.

compilation.seal()

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

в исполненииcompilation.seal(./lib/Compliation), вы можете нажать точку останова, чтобы просмотретьcompilation.modulesСлучай. В настоящее времяcompilation.modulesЕсть три подмодуля, а именно./src/index.js node_modules/object.is/index.jsа такжеnode_modules/object.is/is.is

compilation.sealЕсть много шагов, сначала закройте модуль, сгенерируйте ресурсы, эти ресурсы хранятся вcompilation.assets, compilation.chunks.

Вы увидите его в большинстве сторонних плагинов для веб-пакетов.compilation.assetsиcompilation.chunksфигура.

тогда позвониcompilation.createChunkAssetsМетод отображает все зависимости в конкатенированную строку через соответствующий шаблон:

createChunkAssets(callback){
    asyncLib.forEach(
        this.chunks,
        (chunk, callback) => {
            // manifest是数组结构,每个manifest元素都提供了 `render` 方法,提供后续的源码字符串生成服务。至于render方法何时初始化的,在`./lib/MainTemplate.js`中
            let manifest = this.getRenderManifest()
            asyncLib.forEach(
                manifest,
                (fileManifest, callback) => {
                    ...
                    source = fileManifest.render()
                    this.emitAsset(file, source, assetInfo)
                },
                callback
            )
        },
        callback
    )
}

допустимыйcreateChunkAssetsв теле методаthis.emitAsset(file, source, assetInfo)Поставьте точку останова на строку кода и наблюдайте за этим временем.sourceструктура данных в . существуетsource._sourceПоле впервые увидело прототип упакованного исходного кода:

Стоит отметить, чтоcreateChunkAssetsВ процессе выполнения он сначала прочитает, есть ли уже ресурс с таким хешем в кеше, и если есть, то вернет содержимое напрямую, в противном случае продолжит выполнение логики, сгенерированной модулем, и сохранит его в кеш.

compiler.hooks.emit.callAsync()

После выполнения запечатывания вся информация о модуле и запакованный исходный код сохраняются в памяти, и наступает время их вывода в виде файла. Далее идет серия обратных вызовов, и, наконец, мы приходим кcompiler.emitAssetsв теле метода. существуетcompiler.emitAssetsпозвонит первымthis.hooks.emitЖизненный цикл, а затем вывод файла в указанную папку в соответствии с атрибутом пути выходной конфигурации файла конфигурации веб-пакета. В этот момент вы можете./debug/distПросмотрите упакованный файл кода отладки в формате .

this.hooks.emit.callAsync(compilation, () => {
    outputPath = compilation.getPath(this.outputPath, {})
    mkdirp(this.outputFileSystem, outputPath, emitFiles)
 })

Суммировать

Большое спасибо, что дочитали до конца.Эта статья длинная и кратко описывает основной процесс компиляции модулей webpack:

  1. перечислитьwebpackфункция полученияconfigинформацию о конфигурации и инициализироватьcompiler, в этот период будетapplyВсе встроенные плагины webpack;
  2. перечислитьcompiler.runВойдите в стадию компиляции модуля;
  3. Каждая новая компиляция создает экземплярcompilationобъект, который записывает основную информацию этой компиляции;
  4. Входитьmakeстадия, т.е. триггерcompilation.hooks.makeкрючок, изentryдля входа: а. Вызовите соответствующийloaderПредварительно обработать исходный код модуля и преобразовать его в стандартный JS-модуль; б. Задействовать сторонние плагиныacornАнализирует стандартные модули JS и собирает зависимости модулей. В то же время он будет продолжать рекурсию каждой зависимости, собирать информацию о зависимости и продолжать рекурсию, в итоге будет получено дерево зависимостей 🌲;
  5. последний звонокcompilation.sealМодуль рендеринга интегрирует различные зависимости и, наконец, выводит один или несколько фрагментов;

Ниже приведена простая временная диаграмма:

Вышеупомянутый процесс не полностью суммирует весь процесс webpack.webpack.configКонфигурация становится все более и более сложной, и веб-пакет будет генерировать больше процессов для решения различных ситуаций.

Вебпак сложен? Очень сложный,TabableиNode回调Существуют различные направления для всего процесса, и благодаря своей системе плагинов webpack легко настраивается. Вебпак прост? Это также просто, он делает только одну вещь, компилирует и упаковывает модули JS, и делает это отлично.

Наконец

Кодовые слова непросты, если:

  • Эта статья полезна для вас, пожалуйста, не скупитесь на свои ручонки, чтобы понравиться мне;
  • Если есть что-то, что вы не понимаете или ошибаетесь, пожалуйста, прокомментируйте, и я активно отвечу или внесу опечатки;
  • Я с нетерпением жду продолжения изучения технических знаний вместе со мной, пожалуйста, следуйте за мной;
  • Пожалуйста, укажите источник;

Ваша поддержка и внимание - самая большая мотивация для меня продолжать творить!