Это, пожалуй, самая доступная панорама знаний о веб-пакетах.

Webpack

webpack

Одной из лучших особенностей webpack является то, что в дополнение кJavaScript, также черезloaderпредставлятьлюбой другой тип документа.

Основные концепции Webpack:

  • Entry(Запись): первый шаг в выполнении сборки Webpack начнется с записи, которую можно абстрагировать во входные данные.
  • Output(экспорт): инструктирует веб-пакет, как экспортировать и куда экспортировать
  • Module(Модуль): В Webpack все является модулем, а модуль соответствует файлу. Webpack будет рекурсивно находить все зависимые модули, начиная с настроенного Entry.
  • Chunk(кодовый блок): кусокСостоит из нескольких модулей, для слияния и разделения кода.
  • Loader(Конвертер модулей): используется для преобразования исходного содержимого модуля в новое содержимое по мере необходимости.
  • Plugin(Расширенный плагин): соответствующие события будут транслироваться в определенное время в процессе сборки Webpack, и плагины могут прослушивать эти события и изменять выходные результаты.

элемент конфигурации

  1. Вход
entry: {
  a: "./app/entry-a",
  b: ["./app/entry-b1", "./app/entry-b2"]
},

Можно пройти через несколько входовHtmlWebpackPluginраздельный впрыск

plugins: [
  new HtmlWebpackPlugin({
    chunks: ['a'],
    filename: 'test.html',
    template: 'src/assets/test.html'
  })
]
  1. Экспорт выходных данных

Изменить путь, связанный

  • publicPath: это не повлияет на каталог сгенерированного файла, в основном для завершения соответствующего пути ресурсов, представленных на вашей странице.
  • filename: Можно изменить имя файла, а также изменить каталог файла

Экспорт библиотеки, связанной

  • library: имя экспортной библиотеки
  • libraryTarget: метод определения общего шаблона
  1. Модуль Модуль

Все в webpack представляет собой модуль, элемент конфигурации Module, который определяет различные операции модуля,

Основная конфигурация модуля:

  • loader: Различные преобразователи модулей
  • extensions: расширение используется
  • alias: Псевдоним, например: обычно используется vue-cli@отсюда
  1. разное
  • plugins: список плагинов
  • devServer: Конфигурация, связанная со средой разработки, напримерproxy
  • externals: пакет исключить модули
  • target: среда, в которой должен работать пакет, по умолчаниюweb

Процесс выполнения веб-пакета

От начала до конца webpack последовательно выполняет следующие процессы:

  1. Инициализация: парсинг параметров конфигурации webpack, производствоCompilerПример
  2. Зарегистрировать плагин: вызвать плагинapplyметод, переданный плагинуcompilerСсылка на экземпляр, подключаемый модуль вызывает API, предоставленный Webpack через компилятор, чтобы подключаемый модуль мог отслеживать все последующие узлы событий.
  3. запись: прочитать файл записи
  4. Разобрать файл: использоватьloaderРазобрать файл в абстрактное синтаксическое деревоAST
  5. Сгенерируйте граф зависимостей: узнайте зависимости каждого файла (обход)
  6. Вывод: В соответствии с преобразованным кодом сгенерироватьchunk
  7. Сгенерируйте окончательный упакованный файл

ps: Поскольку webpack динамически загружает все зависимости в соответствии с графом зависимостей, каждый модуль может четко выражать свои собственные зависимости, что позволяет избежать упаковки неиспользуемых модулей.

Babel

Babel — это набор инструментов, в основном используемый для преобразования кода версии ECMAScript 2015+ в обратно совместимый.JavaScriptсинтаксис для работы в текущих и старых браузерах или других средах:

Парсер, используемый Babel внутри, называется Babylon.

Основная функция

  • Преобразование синтаксиса
  • пройти черезPolyfillспособ добавить отсутствующие функции в целевой среде (через@babel/polyfillмодуль)
  • преобразование исходного кода (codemods)

основной модуль

  • @babel/parser: отвечает за разбор кода в абстрактное синтаксическое дерево.
  • @babel/traverse: Инструмент для обхода абстрактного синтаксического дерева, мы можем разобрать определенный узел в синтаксическом дереве, а затем выполнить некоторые операции.
  • @babel/core: преобразование кода, например, кода ES6 в режим ES5.

Результат объединения Webpack

В типичном приложении или сайте, созданном с помощью веб-пакета, есть три основных типа кода:

  1. Исходный код: исходный код, написанный вами или вашей командой.
  2. Зависимости: любые сторонние зависимости, от которых будет зависеть ваш исходный код.libraryили "vendor"код.
  3. Управление файлами:webpackизruntimeиспользоватьmanifestУправляйте всеми взаимодействиями модуля.

runtime: Требуется для подключения модулей при взаимодействии модулейЛогика загрузки и парсинга. В том числе подключение загруженных модулей в браузере и логика выполнения лениво загруженных модулей.

manifest: когда компилятор начинает выполнять, анализировать и отображать приложение, он сохраняет подробные маркеры всех модулей. Этот набор данных называется «Манифест», После упаковки и отправки в браузер модуль анализируется и загружается во время выполнения через манифест. Какой бы синтаксис модуля вы ни выбрали, эти операторы import или require теперь переводятся вwebpack_requireметод, который указывает на идентификатор модуля. Используя данные в манифесте, среда выполнения сможет запросить идентификатор модуля и получить за ним соответствующий модуль.

в:

  • importилиrequireзаявление преобразуется в__webpack_require__
  • Асинхронный импорт преобразуется вrequire.ensure(Обёртка промисов будет использоваться в Webpack 4)

Сравнивать

  • gulpЭто средство запуска задач: оно используется для автоматизации общих задач разработки, таких как проверка проекта (lint), сборка (build) и тестирование (test)
  • webpackЯвляется упаковщиком: Помогает взять JavaScript и таблицы стилей, готовые к развертыванию, и преобразовать их в удобный для браузера формат. Например, JavaScript может сжимать, разбивать фрагменты и откладывать загрузку.

реализовать загрузчик

loaderЭто файл js, который экспортирует один и возвращает одинbufferилиstringФункция;

Например:

// log-loader.js
module.exports = function (source) {
  console.log('test...', source)
  return source
}

При использовании, еслиlog-loaderне вnode_modules, то вы можете использовать импорт пути.

реализовать плагин

плагин: этоapplyметод.

Например:

class DemoWebpackPlugin {
    constructor () {
        console.log('初始化 插件')
    }
    apply (compiler) {
    }
}

module.exports = DemoWebpackPlugin

Метод применения получаетcompilerПараметры, то есть экземпляр webpack. Благодаря наличию этого параметра плагин может эффективно использовать хуки жизненного цикла веб-пакета и выполнять некоторые операции в разные моменты времени.

Обзор оптимизации Webpack

Способы ускорить упаковку с помощью Webpack

  1. использоватьincludeилиexcludeУскорить поиск файлов
  2. использоватьHappyPackвключить многопроцессорностьLoaderконвертировать
  3. использоватьParallelUglifyPluginВключить многопроцессное сжатие JS
  4. использоватьDllPlugin + DllReferencePluginОтдельная упаковка
    1. Будуа также项目代码Отдельная упаковка
    2. требуется файл сопоставления dll
  5. Настроить кеш (плагин идет с загрузчиком, если он не поддерживается, можно использовать егоcache-loader)

Способы ускорить выполнение кода с помощью Webpack

  1. сжатие кода
  2. Извлечь общие модули
  3. Модули ленивой загрузки
  4. Преобразуйте небольшие изображения в base64, чтобы уменьшить количество запросов
  5. Предварительная выборка(prefetch) || предварительная загрузка(preload)
  6. спрайт
  7. webpack-bundle-analyzerанализ кода

Детали оптимизации веб-пакета

В webpack 4.6.0+ добавлена ​​поддержка предварительной выборки и предварительной загрузки.

динамический импорт

  import(/* webpackChunkName: "lodash" */ 'lodash')

  // 注释中的使用webpackChunkName。
  // 这将导致我们单独的包被命名,lodash.bundle.js
  // 而不是just [id].bundle.js。

Предварительная выборка(prefetch): в будущем могут понадобиться некоторые навигационные ресурсы

  • пока отецchunkзагрузка завершена,webpackдобавитprefetch
  import(/* webpackPrefetch: true */ 'LoginModal');

  // 将<link rel="prefetch" href="login-modal-chunk.js">其附加在页面的开头

Предварительная загрузка(preload): ресурс может потребоваться во время текущей навигации

  • preloadЧанки начнут загружаться параллельно, когда будет загружен родительский чанк.
  • использовать неправильноwebpackPreloadухудшит производительность,
  import(/* webpackPreload: true */ 'ChartingLibrary');

  // 在加载父 chunk 的同时
  // 还会通过 <link rel="preload"> 请求 charting-library-chunk
DllPlugin + DllReferencePlugin

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

И DllReferencePlugin, и подключаемый модуль DLL DllPlugin используются в другой настройке веб-пакета.

DllPluginЭтот плагин создает пакет только для dll (dll-only-bundle) в дополнительной отдельной настройке веб-пакета. Этот плагин создаст файл с именем manifest.json, который используется для созданияDLLReferencePluginСопоставлены со связанными зависимостями.

webpack.vendor.config.js

  new webpack.DllPlugin({
    context: __dirname,
    name: "[name]_[hash]",
    path: path.join(__dirname, "manifest.json"),
  })

webpack.app.config.js

  new webpack.DllReferencePlugin({
    context: __dirname,
    manifest: require("./manifest.json"),
    name: "./my-dll.js",
    scope: "xyz",
    sourceType: "commonjs2"
  })
CommonsChunkPlugin

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

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

  entry: {
    vendor: ["jquery", "other-lib"], // 明确第三方库
    app: "./entry"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor",
      // filename: "vendor.js"
      // (给 chunk 一个不同的名字)

      minChunks: Infinity,
      // (随着 entry chunk 越来越多,
      // 这个配置保证没其它的模块会打包进 vendor chunk)
    })
  ]

  // 打包后的文件
  <script src="vendor.js" charset="utf-8"></script>
  <script src="app.js" charset="utf-8"></script>
UglifyJSPlugin

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

  const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
  //...
  plugins: [
      new UglifyJSPlugin({
          compress: {
              warnings: false,  //删除无用代码时不输出警告
              drop_console: true,  //删除所有console语句,可以兼容IE
              collapse_vars: true,  //内嵌已定义但只使用一次的变量
              reduce_vars: true,  //提取使用多次但没定义的静态值到变量
          },
          output: {
              beautify: false, //最紧凑的输出,不保留空格和制表符
              comments: false, //删除所有注释
          }
      })
  ]
ExtractTextPlugin + PurifyCSSPlugin

ExtractTextPlugin извлекает текст (CSS) из пакетов в отдельные файлы, PurifyCSSPlugin очищает CSS (это не очень полезно)

  module.exports = {
    module: {
      rules: [
        {
          test: /\.css$/,
          loader: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: [
              {
                loader: 'css-loader',
                options: {
                  localIdentName: 'purify_[hash:base64:5]',
                  modules: true
                }
              }
            ]
          })
        }
      ]
    },
    plugins: [
      ...,
      new PurifyCSSPlugin({
        purifyOptions: {
          whitelist: ['*purify*']
        }
      })
    ]
  };
DefinePlugin

DefinePlugin может автоматически обнаруживать изменения окружающей среды с высокой эффективностью.

При фронтальной разработке требуются разные конфигурации в разных средах приложений. Например: мокер API в среде разработки, подделка данных в процессе тестирования и печать отладочной информации. Ручная обработка этой информации о конфигурации не только обременительна, но и чревата ошибками.

использоватьDefinePluginНастроенные глобальные константы

Обратите внимание, что поскольку этот плагин выполняет замену текста напрямую, указанное значение должно содержать фактические кавычки внутри самой строки. Как правило, есть два способа добиться этого эффекта, используя' "production" 'или используйтеJSON.stringify('production').

    new webpack.DefinePlugin({

        // 当然,在运行node服务器的时候就应该按环境来配置文件
        // 下面模拟的测试环境运行配置

        'process.env':JSON.stringify('dev'),
        WP_CONF: JSON.stringify('dev'),
    }),

тестовое заданиеDefinePlugin:записывать

    if (WP_CONF === 'dev') {
        console.log('This is dev');
    } else {
        console.log('This is prod');
    }

после упаковкиWP_CONF === 'dev'Будет компилироватьсяfalse

    if (false) {
        console.log('This is dev');
    } else {
        console.log('This is prod');
    }
Очистить недостижимый код

при использованииDefinePluginПосле плагина упакованный код будет иметь много избыточности. в состоянии пройтиUglifyJsPluginОчистить недостижимый код.

    [
        new UglifyJsPlugin({
            uglifyOptions: {
            compress: {
                warnings: false, // 去除warning警告
                dead_code: true, // 去除不可达代码
            },
            warnings: false
            }
        })
    ]

Окончательная упаковка и код упаковки станутconsole.log('This is prod')

Прикрепленная документация Uglify:GitHub.com/Секретарь, о, о, УГ в…

Используйте DefinePlugin для различения сред + UglifyJsPlugin для очистки недоступного кода, чтобы уменьшить размер упакованного кода.

HappyPack

HappyPackМогуВключить преобразование многопроцессорного загрузчика, разбить задачу на несколько подпроцессов и, наконец, отправить результат основному процессу.

использовать

  exports.plugins = [
    new HappyPack({
      id: 'jsx',
      threads: 4,
      loaders: [ 'babel-loader' ]
    }),

    new HappyPack({
      id: 'styles',
      threads: 2,
      loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
    })
  ];

  exports.module.rules = [
    {
      test: /\.js$/,
      use: 'happypack/loader?id=jsx'
    },

    {
      test: /\.less$/,
      use: 'happypack/loader?id=styles'
    },
  ]
ParallelUglifyPlugin

ParallelUglifyPluginМогуВключить многопроцессорное сжатие файлов JS

  import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin';

  module.exports = {
    plugins: [
      new ParallelUglifyPlugin({
        test,
        include,
        exclude,
        cacheDir,
        workerCount,
        sourceMap,
        uglifyJS: {
        },
        uglifyES: {
        }
      }),
    ],
  };
BundleAnalyzerPlugin

Плагин анализа результатов упаковки webpack

  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
  module.exports = {
    plugins: [
      new BundleAnalyzerPlugin()
    ]
  }
test & include & exclude

Уменьшена область поиска файлов для повышения скорости.

Пример

  {
    test: /\.css$/,
    include: [
      path.resolve(__dirname, "app/styles"),
      path.resolve(__dirname, "vendor/styles")
    ]
  }
Внешние расширения (externals)

Это не плагин, это опция конфигурации wenpack

Параметр конфигурации externals позволяет «исключить зависимости из экспортируемого пакета». Вместо этого созданные пакеты зависят от тех зависимостей, которые существуют в среде потребителя. Эта функция обычно наиболее полезна для разработчиков библиотек, однако существуют различные приложения, которые ее используют.

  entry: {
    entry: './src/main.js',
    vendor: ['vue', 'vue-router', 'vuex']
  },
  externals: {
    // 从输出的 bundle 中排除 echarts 依赖
    echarts: 'echarts',
  }

Принципиальный анализ Webpack HMR

Горячая замена модуля (сокращенно HMR)

Содержит следующее:

  1. Карта обновления тепла
  2. Объяснение шагов горячего обновления

Первый шаг: webpack следит за файловой системой и упаковывает ее в память

webpack-dev-middleware вызывает API веб-пакета для наблюдения за файловой системой.Когда файл изменяется, веб-пакет перекомпилирует и упаковывает файл, а затем сохраняет его в памяти.

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

Все благодаряmemory-fs, memory-fs является зависимой библиотекой webpack-dev-middleware, webpack-dev-middleware заменяет исходную outputFileSystem из webpack экземпляром MemoryFileSystem, так что код будет выводиться в память.

Исходный код этой части в webpack-dev-middleware выглядит следующим образом:

  // compiler
  // webpack-dev-middleware/lib/Shared.js
  var isMemoryFs = !compiler.compilers &&
                  compiler.outputFileSystem instanceof MemoryFileSystem;
  if(isMemoryFs) {
      fs = compiler.outputFileSystem;
  } else {
      fs = compiler.outputFileSystem = new MemoryFileSystem();
  }
Шаг 2: devServer уведомляет браузер об изменении файла

При запуске devServersockjsМежду сервером и браузером устанавливается длинное соединение через веб-сокет, чтобы информировать браузер о состоянии каждого этапа компиляции и упаковки веб-пакета.Наиболее важным шагом является то, что webpack-dev-server вызывает API-интерфейс веб-пакета для прослушивания события done Когда компиляция завершена После этого webpack-dev-server отправляет хеш-значение скомпилированного и упакованного нового модуля в браузер через метод _sendStatus.

  // webpack-dev-server/lib/Server.js
  compiler.plugin('done', (stats) => {
    // stats.hash 是最新打包文件的 hash 值
    this._sendStats(this.sockets, stats.toJson(clientStats));
    this._stats = stats;
  });
  ...
  Server.prototype._sendStats = function (sockets, stats, force) {
    if (!force && stats &&
    (!stats.errors || stats.errors.length === 0) && stats.assets &&
    stats.assets.every(asset => !asset.emitted)
    ) { return this.sockWrite(sockets, 'still-ok'); }
    // 调用 sockWrite 方法将 hash 值通过 websocket 发送到浏览器端
    this.sockWrite(sockets, 'hash', stats.hash);
    if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); } 
    else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); }      else { this.sockWrite(sockets, 'ok'); }
  };
Шаг 3: webpack-dev-server/client получает сообщение сервера и отвечает

webpack-dev-server изменяет атрибут entry в конфигурации webpack и добавляет код webpack-dev-client, так что код для получения сообщений websocket будет в итоговом файле bundle.js.

Когда webpack-dev-server/client получает сообщение типа хэш, он временно сохраняет хеш-значение и выполняет операцию перезагрузки приложения после получения сообщения типа ok.

В операции перезагрузки webpack-dev-server/client решит, следует ли обновить браузер или выполнить горячее обновление (HMR) кода в соответствии с горячей конфигурацией. код показывает, как показано ниже:

  // webpack-dev-server/client/index.js
  hash: function msgHash(hash) {
      currentHash = hash;
  },
  ok: function msgOk() {
      // ...
      reloadApp();
  },
  // ...
  function reloadApp() {
    // ...
    if (hot) {
      log.info('[WDS] App hot update...');
      const hotEmitter = require('webpack/hot/emitter');
      hotEmitter.emit('webpackHotUpdate', currentHash);
      // ...
    } else {
      log.info('[WDS] App updated. Reloading...');
      self.location.reload();
    }
  }
Шаг 4: webpack получает последнюю проверку хеш-значения и запрашивает код модуля.

Сначала webpack/hot/dev-server (далее dev-сервер) слушает третий шаг webpack-dev-server/client.webpackHotUpdateсообщение, вызовите метод проверки в webpack/lib/HotModuleReplacement.runtime (сокращенно среда выполнения HMR), чтобы определить, есть ли новое обновление.

В процессе проверки используются два метода: hotDownloadManifest и hotDownloadUpdateChunk в файле webpack/lib/JsonpMainTemplate.runtime (называемый средой выполнения jsonp).

hotDownloadManifest должен вызвать AJAX, чтобы спросить сервер, есть ли обновленный файл, и если есть список обновленных файлов, он будет отправлен обратно в браузер. Этот метод возвращает последнее хеш-значение.

hotDownloadUpdateChunk запрашивает последний код модуля через jsonp, а затем возвращает код в среду выполнения HMR.Среда выполнения HMR выполнит дальнейшую обработку на основе возвращенного кода нового модуля, что может быть связано с обновлением страницы или горячим обновлением модуля. Этот метод возвращает блок кода, соответствующий последнему хеш-значению.

Наконец, новый блок кода возвращается в среду выполнения HMR для горячего обновления модуля.

Приложение: Почему код модуля обновления не отправляется напрямую в браузер через websocket на третьем шаге, а получается через jsonp?

Насколько я понимаю, разделение функциональных блоков, каждый модуль выполняет свои обязанности, dev-сервер/клиент отвечает только за передачу сообщений, а не за приобретение новых модулей, и эти задачи должна выполнять среда выполнения HMR. , а среда выполнения HMR должна быть местом для получения нового кода. И из-за того, что не используется webpack-dev-server, использование webpack-hot-middleware и webpack также может завершить процесс горячего обновления модуля.В использовании webpack-hot-middleware есть интересная вещь, он не использует websocket, и используется ли EventSource. Подводя итог, в рабочем процессе HMR новый код модуля не должен помещаться в сообщения веб-сокета.

Шаг 5: HotModuleReplacement.runtime для горячего обновления модуля

Этот шаг является ключевым во всем горячем обновлении модуля (HMR), а горячее обновление модуля происходит в методе hotApply среды выполнения HMR.

  // webpack/lib/HotModuleReplacement.runtime
  function hotApply() {
      // ...
      var idx;
      var queue = outdatedModules.slice();
      while(queue.length > 0) {
          moduleId = queue.pop();
          module = installedModules[moduleId];
          // ...
          // remove module from cache
          delete installedModules[moduleId];
          // when disposing there is no need to call dispose handler
          delete outdatedDependencies[moduleId];
          // remove "parents" references from all children
          for(j = 0; j < module.children.length; j++) {
              var child = installedModules[module.children[j]];
              if(!child) continue;
              idx = child.parents.indexOf(moduleId);
              if(idx >= 0) {
                  child.parents.splice(idx, 1);
              }
          }
      }
      // ...
      // insert new code
      for(moduleId in appliedUpdate) {
          if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
              modules[moduleId] = appliedUpdate[moduleId];
          }
      }
      // ...
  }

Обработка ошибок при горячем обновлении модуля. Если при горячем обновлении возникает ошибка, горячее обновление будет возвращаться к обновлению браузера. Эта часть кода находится в коде dev-сервера. Краткий код выглядит следующим образом:

  module.hot.check(true).then(function(updatedModules) {
    if(!updatedModules) {
        return window.location.reload();
    }
    // ...
  }).catch(function(err) {
      var status = module.hot.status();
      if(["abort", "fail"].indexOf(status) >= 0) {
          window.location.reload();
      }
  });
Шаг 6: Что должен делать бизнес-код?

После замены старого модуля кодом нового модуля наш бизнес-код не знает, что код изменился, то есть при изменении файла hello.js нам нужно вызвать метод accept HMR в файле index.js. file., добавьте функцию обработки после обновления модуля и вовремя вставьте возвращаемое значение метода hello на страницу. код показывает, как показано ниже

  // index.js
  if(module.hot) {
      module.hot.accept('./hello.js', function() {
          div.innerHTML = hello()
      })
  }

наконец

  1. Ставьте лайк, если считаете это полезным
  2. Содержание этой статьи взято изGitHub.com/Китай-США…
  3. Добро пожаловать, чтобы обратить внимание на общедоступную учетную запись «Продвинутый курс по интерфейсу», чтобы серьезно изучить интерфейс и продвигаться вместе.