5 самых запутанных знаний в webpack

Webpack
5 самых запутанных знаний в webpack

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

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

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


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

Адрес исходного кода учебника на github


1. В вебпакеmodule,chunkиbundleВ чем разница?

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

Официальный сайт webpack сделал чанки и бандлыобъяснять, Честно говоря, это слишком абстрактно.Приведу пример.визуализироватьобъяснять.

Во-первых, мы пишем наш бизнес-код в каталоге src и вводим четыре файла: index.js, utils.js, common.js и index.css, Структура каталога выглядит следующим образом:

src/
├── index.css
├── index.html # 这个是 HTML 模板代码
├── index.js
├── common.js
└── utils.js

index.css напишите немного простой стиль:

body {
    background-color: red;
}

Файл utils.js записывает служебную функцию для возведения в квадрат:

export function square(x) {
    return x * x;
}

Файл common.js записывает служебную функцию журнала:

module.exports = {
  log: (msg) => {
    console.log('hello ', msg)
  }
}

Внесите несколько простых изменений в файл index.js и добавьте файл css и common.js:

import './index.css';
const { log } = require('./common');

log('webpack');

Конфигурация веб-пакета выглядит следующим образом:

{
    entry: {
        index: "../src/index.js",
        utils: '../src/utils.js',
    },
    output: {
        filename: "[name].bundle.js", // 输出 index.js 和 utils.js
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader, // 创建一个 link 标签
                    'css-loader', // css-loader 负责解析 CSS 代码, 处理 CSS 中的依赖
                ],
            },
        ]
    }
    plugins: [
        // 用 MiniCssExtractPlugin 抽离出 css 文件,以 link 标签的形式引入样式文件
        new MiniCssExtractPlugin({
            filename: 'index.bundle.css' // 输出的 css 文件名为 index.css
        }),
    ]
}

Запустим webpack и посмотрим результат упаковки:

Мы можем видеть, что index.css и common.js представлены в index.js, а упакованные и сгенерированные index.bundle.css и index.bundle.js принадлежат фрагменту 0. Поскольку utils.js упакован независимо, он генерирует utils.bundle.js принадлежит чанку 1.

Чувствуете себя немного неряшливо? Я сделал картинку, вы должны понять это с первого взгляда:

Из этой картинки понятно:

  1. Для кода с такой же логикой, когда мы пишем следующий файл, будь то ESM, commonJS или AMD, все ониmodule;
  2. Когда исходный файл модуля, который мы написали, передается веб-пакету для упаковки, веб-пакет генерирует его в соответствии с отношением ссылки на файл.chunkфайл, webpack выполнит некоторые операции с этим файлом фрагмента;
  3. После того, как webpack обработает файл фрагмента, он, наконец, выведетbundleфайл, этот пакет содержит окончательные исходные файлы, которые были загружены и скомпилированы, поэтому его можно запускать непосредственно в браузере.

Вообще говоря, чанк соответствует пакету, такому как на рисунке выше.utils.js -> chunks 1 -> utils.bundle.js; Но есть исключения, например, на картинке выше я используюMiniCssExtractPluginИзвлечено из чанков 0index.bundle.cssдокумент.

Краткое содержание одной фразы:

module,chunkиbundleНа самом деле один и тот же логический код имеет три названия в разных сценариях конвертации:

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


2.filenameиchunkFilenameразница

filename

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

{
    entry: {
        index: "../src/index.js"
    },
    output: {
        filename: "[name].min.js", // index.min.js
    }
}

chunkFilename

chunkFilenameзначит нет в спискеentry, но нужно упаковатьchunkИмя файла. Вообще говоря, этоchunkфайловые средстваленивая загрузкакод.

Например, мы написали ленивую загрузку в нашем бизнес-коде.lodashкод:

// 文件:index.js

// 创建一个 button
let btn = document.createElement("button");
btn.innerHTML = "click me";
document.body.appendChild(btn);

// 异步加载代码
async function getAsyncComponent() {
    var element = document.createElement('div');
    const { default: _ } = await import('lodash');

    element.innerHTML = _.join(['Hello!', 'dynamic', 'imports', 'async'], ' ');

    return element;
}

// 点击 button 时,懒加载 lodash,在网页上显示 Hello! dynamic imports async
btn.addEventListener('click', () => {
    getAsyncComponent().then(component => {
        document.body.appendChild(component);
    })
})

нашwebpackНе выполняйте никакую настройку или исходный код конфигурации:

{
    entry: {
        index: "../src/index.js"
    },
    output: {
        filename: "[name].min.js", // index.min.js
    }
}

Результат упаковки на данный момент следующий:

это1.min.jsзагружается асинхронноchunkдокумент.ДокументацияВот объяснение:

output.chunkFilenameИспользовать по умолчанию[id].jsили изoutput.filenameПредполагаемое значение в ([name]будет предварительно заменен на[id]или[id].)

Документация слишком абстрактна, мы могли бы также взглянуть на приведенный выше пример:

output.filenameИмя выходного файла[name].min.js,[name]в соответствии сentryПредполагается, что конфигурацияindex, поэтому выводindex.min.js;

так какoutput.chunkFilenameЕсли нет явной спецификации, он поместит[name]заменитьchunkдокументidНет, этот файлidчисло равно 1, поэтому имя файла1.min.js.

Если мы явно настроимchunkFilename, файл будет сгенерирован по названию конфигурации:

{
    entry: {
        index: "../src/index.js"
    },
    output: {
        filename: "[name].min.js",  // index.min.js
        chunkFilename: 'bundle.js', // bundle.js
    }
}

Краткое содержание одной фразы:

filenameСсылаться наперечислены в entry, имя выходного файла после упаковки.

chunkFilenameСсылаться нанет в списке entry, а имя файла, который необходимо запаковать.


3.webpackPrefetch,webpackPreloadиwebpackChunkNameДля чего это?

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

webpackChunkName

Асинхронная загрузка упоминалась ранееlodashнапример, мы наконец положилиoutput.chunkFilenameписать до смертиbundle.js. В нашем бизнес-коде невозможно загрузить только один файл асинхронно, поэтому его точно нельзя записать насмерть, а написать как[name].bundle.js, упакованные файлы представляют собой куски с неясным смыслом и низким уровнем распознаванияid.

{
    entry: {
        index: "../src/index.js"
    },
    output: {
        filename: "[name].min.js",  // index.min.js
        chunkFilename: '[name].bundle.js', // 1.bundle.js,chunk id 为 1,辨识度不高
    }
}

В этот моментwebpackChunkNameВот где это пригодится. мы можемimportфайл, вimportПсевдоним файла фрагмента в виде комментария:

async function getAsyncComponent() {
    var element = document.createElement('div');
  
    // 在 import 的括号里 加注释 /* webpackChunkName: "lodash" */ ,为引入的文件取别名
    const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash');

    element.innerHTML = _.join(['Hello!', 'dynamic', 'imports', 'async'], ' ');

    return element;
}

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

Теперь возникает вопрос,lodashэто имя, которое мы взяли, и оно должно быть сгенерировано логическиlodash.bundle.jsа, передvendors~Что это?

На самом деле ленивая загрузка веб-пакета использует встроенный плагин.SplitChunksPluginРеализовано, есть некоторые в этом плагинеЭлементы конфигурации по умолчанию, сказатьautomaticNameDelimiter, разделитель по умолчанию~, поэтому этот символ появится в конечном имени файла.Я не буду расширять это содержание.Заинтересованные студенты могут изучить его самостоятельно.

webpackPrefetch и webpackPreload

Одна из этих двух конфигураций называется Prefetch, а другая – Preload. Они немного отличаются друг от друга. Давайте сначала поговорим об этом.webpackPrefetch.

В приведенном выше коде отложенной загрузки асинхронная загрузка запускается, когда мы нажимаем кнопкуlodashдействие, на этот раз будет динамически генерироватьсяscriptэтикетка, загруженная вheadзаблаговременно:

если мыimportпри добавленииwebpackPrefetch:

...

const { default: _ } = await import(/* webpackChunkName: "lodash" */ /* webpackPrefetch: true */ 'lodash');

...

будет<link rel="prefetch" as="script">Предварительно загрузите код lodash в виде:

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

webpackPreloadзаключается в предварительной загрузке ресурсов, которые могут потребоваться в рамках текущей навигации, он иwebpackPrefetchОсновные отличия:

  • Блок предварительной загрузки начинает загружаться параллельно, когда загружается родительский блок. Блок предварительной выборки начнет загружаться после завершения загрузки родительского блока.
  • Блок предварительной загрузки имеет средний приоритет и загружается немедленно. Фрагмент предварительной выборки загружается, когда браузер бездействует.
  • Чанк предварительной загрузки будет запрошен сразу в родительском чанке на текущий момент. Блок предварительной выборки будет использоваться в какой-то момент в будущем.

Краткое содержание одной фразы:

webpackChunkNameявляется псевдонимом предварительно загруженного файла,webpackPrefetchФайл будет загружен, когда браузер бездействует,webpackPreloadФайлы загружаются параллельно, когда загружается родительский фрагмент.


4.hash,chunkhash,contenthashкакая разница?

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

hash

Вычисление хеша связано с построением всего проекта, сделаем простое демо.

Следуя демонстрационному коду случая 1, каталог файлов выглядит следующим образом:

src/
├── index.css
├── index.html
├── index.js
└── utils.js

Основная конфигурация webpack выглядит следующим образом (некоторая информация о конфигурации модуля опущена):

{
    entry: {
        index: "../src/index.js",
        utils: '../src/utils.js',
    },
    output: {
        filename: "[name].[hash].js",  // 改为 hash
    },
    
    ......
    
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'index.[hash].css' // 改为 hash
        }),
    ]
}

Имена сгенерированных файлов следующие:

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

chunkhash

Так как хэш это хеш-значение построения проекта, если будут какие-то изменения в проекте, то хеш обязательно изменится.Например я изменил код utils.js.Хотя код в index.js не изменился, все используют одну и ту же копию хэша. При изменении хэша кеш должен быть недействительным, поэтому нет возможности реализовать кеширование CDN и браузера.

chunkhash решает эту проблему: он анализирует зависимые файлы в соответствии с разными входными файлами (Entry), строит соответствующие чанки и генерирует соответствующие хеш-значения.

Возьмем другой пример, вносим изменения в файл utils.js:

export function square(x) {
    return x * x;
}

// 增加 cube() 求立方函数
export function cube(x) {
    return x * x * x;
}

Затем измените все хэши в webpack на chunkhash:

{
    entry: {
        index: "../src/index.js",
        utils: '../src/utils.js',
    },
    output: {
        filename: "[name].[chunkhash].js", // 改为 chunkhash
    },
          
    ......
    
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'index.[chunkhash].css' // // 改为 chunkhash
        }),
    ]
}

Результат сборки следующий:

Мы видим, что хэш чанка 0 такой же, а хэш чанка 1 отличается от приведенного выше.

Предположим, я удаляю функцию cube() из utils.js и снова упаковываю ее:

Для сравнения можно обнаружить, что изменился только хэш чанка 1, а хеш чанка 0 остался прежним.

contenthash

Идем дальше, index.js и index.css это один и тот же чанк, если содержимое index.js изменится, а index.css не изменится, то после упаковки изменится их хэш, который является своеобразным css файлом. напрасно тратить. Как решить эту проблему?

contenthash создаст уникальный хэш на основе содержимого ресурса, то есть, если содержимое файла останется прежним, хэш останется прежним.

Давайте изменим конфигурацию веб-пакета:

{
    entry: {
        index: "../src/index.js",
        utils: '../src/utils.js',
    },
    output: {
        filename: "[name].[chunkhash].js",
    },
      
    ......
    
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'index.[contenthash].css' // 这里改为 contenthash
        }),
    ]
}

Мы внесли три модификации в файл index.js (то есть было изменено содержимое вывода функции лога, и она слишком проста, чтобы не написать ее первой), а затем собрали их отдельно, скриншоты результатов такие: :

Мы можем обнаружить, что хэш файла css не изменился.

Краткое содержание одной фразы:

Вычисление хеша связано с построением всего проекта;

вычисление чанка связано с одним и тем же содержимым чанка;

Вычисление хэша содержимого зависит от самого содержимого файла.


5.sourse-mapсерединаeval,cheap,inlineиmoduleЧто каждый означает?

source-map, в нем есть карта, в этом должен быть смысл маппинга. исходная карта является копиейСопоставление файлов для исходного и преобразованного кода. Конкретных принципов много, и заинтересованные студенты могут поискать сами, здесь я не буду много говорить.

Давайте сначала заглянем на официальный сайт, чтобы увидеть, сколько типов исходной карты доступно:

эмммм, 13 видов, прощай.

Если вы присмотритесь, то увидите, что большинство из этих 13eval,cheap,inlineиmoduleДля расположения и сочетания этих 4-х слов я составил простенькую таблицу, которая намного проще, чем на официальном сайте:

параметр Описание параметра
eval Используются упакованные модулиeval()Выполнить, сопоставление линий может быть неточным; отдельный файл сопоставления не создается
cheap Карта карты показывает только строки, а не столбцы, игнорируя исходные карты из загрузчика.
inline Файл карты кодируется в формате base64, добавляется в конец файла пакета и не генерирует отдельный файл карты.
module Добавлено сопоставление исходной карты загрузчика и сторонних модулей.

Все еще не понимаете? Вы можете взглянуть на демо.

Мы делаем некоторую настройку для веб-пакета, а devtool специально настраивается для исходной карты.

......

{
    devtool: 'source-map',
}

......

В файле index.js для простоты пишем только одну строчку кода, а чтобы получить сообщение об ошибке, мы намеренно написали ее неправильно:

console.lg('hello source-map !') // log 写成 lg

Давайте попробуем некоторые общие конфигурации:

source-map

source-map — самый большой и полный, и он будет генерировать отдельные файлы карты:

Обратите внимание на положение курсора на рисунке ниже, source-map отобразит сообщение об ошибкерядыИнформация:

cheap-sourse-map

дешевый означает дешевый, он не генерирует сопоставление столбцов, и соответствующий объем будет намного меньше Мы сравниваем результаты упаковки source-map, только 1/4 оригинала.

eval-source-map

eval-source-map упакует и запустит модуль с помощью функции eval(), не создавая отдельный файл карты, он отобразит сообщение об ошибкерядыИнформация:

// index.bundle.js 文件

!function(e) {
    // ......
    // 省略不重要的代码
    // ......
}([function(module, exports) {
    eval("console.lg('hello source-map !');//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi4vc3JjL2luZGV4Mi5qcz9mNmJjIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsZyJdLCJtYXBwaW5ncyI6IkFBQUFBLE9BQU8sQ0FBQ0MsRUFBUixDQUFXLG9CQUFYIiwiZmlsZSI6IjAuanMiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zb2xlLmxnKCdoZWxsbyBzb3VyY2UtbWFwICEnKSJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///0\n")
}
]);

inline-source-map

Файл карты кодируется в формате base64 и добавляется в конец файла пакета, а не создает отдельный файл карты. После добавления файла карты мы можем ясно видеть, что размер пакета стал больше;

// index.bundle.js 文件

!function(e) {

}([function(e, t) {
    console.lg("hello source-map !")
}
]);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4uL3NyYy9pbmRleDIuanMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJ......

// base64 太长了,我删了一部分,领会精神

Общая конфигурация:

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

1.source-map

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

2.cheap-module-eval-source-map

Это обычно рекомендуется для среды разработки (dev) и обеспечивает хороший баланс в напоминании об ошибке скорости построения.

3.cheap-module-source-map

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

напиши в конце

На этом статья почти закончена, и позже я напишу несколько статей по оптимизации пакетов webapck.


Обновлено 2019-09-26:

Расширенные статьи по оптимизации пакетов webpack:

Я много работал, чтобы изучить конфигурацию dll webpack, и когда я оглядываюсь назад, мне не нужны React и Vue.


От изучения веб-пакета до этого результата прошло почти 2 недели. Лично я считаю, что веб-пакет является частью цепочки инструментов в окончательном анализе. Многие элементы конфигурации не нужно запоминать, как встроенные методы JavaScript. большой и заполните его самостоятельно.Демонстрация, если вы знаете, что может сделать элемент конфигурации, вы можете проверить его, когда захотите его использовать.

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


Напоследок хочу порекомендовать свой личный паблик «Лаборатория яиц в панировке». Обычно я делюсь некоторыми фронтенд-технологиями и контентом для анализа данных. Если вам интересно, то можете обратить внимание на: