Руководство по оптимизации упаковки WebPack Packaging (секция теории)

внешний интерфейс Webpack
Руководство по оптимизации упаковки WebPack Packaging (секция теории)

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

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

ссылка на веб-пакет, не забудьте зайти и продолжить чтение после прочтения

базовая конфигурация webpack5

Давайте сначала рассмотрим базовую конфигурацию, файл webpack.config.js, основанный на webpack5.

/*
* webpack.config.js
*/
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // 模式
  mode: "development",
  // 入口
  entry: "./src/index.js",
  // 出口
  output: {
    filename: "built.js",
    path: resolve(__dirname, "build"),
    // 自定义输出 静态资源文件名(图片)
    assetModuleFilename: "assets/[hash][ext]",
  },
  // 模块
  module: {
    rules: [
      // loader的配置
      {
        test: /\.css$/,
        // 使用loader对文件进行处理
        /**
         * use数组中的执行顺序是 从右到左  从下到上 依次执行
         * style-loader  创建style标签,将样式文件引入到header中
         * css-loader 将css模块变成commonjs模块加载到js中, 文件内容是样式字符串
         */
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          // 将less文件编译成css文件
          // 需要下载 less-loader和less
          "less-loader",
        ],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        // webpack 5 内置了资源类型,已经废弃了之前的 url-loader 和 file-loader
        type: "asset/resource",
      },
      {
        test: /\.html$/,
        // 处理html中的图片文件 引入img文件进而让url-loader处理
        loader: "html-loader",
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      },
    ],
  },
  // 插件
  plugins: [
    // HtmlWebpackPlugin
    // 默认会创建一个空的html文件,会自动引入打包完成的所有资源。
    // 需要有结构的html文件
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  /**
   * 下载 yarn add webpack-dev-server --dev
   * 运行  npx webpack serve
   */
  devServer: {
    // 项目构建后的路径
    contentBase: resolve(__dirname, "build"),
    // 自动打开浏览器
    open: true,
    // 端口号
    port: 5555,
    // 开启gzip压缩
    compress: true,
  },
};

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

Горячее обновление HMR

Модуль Heat Module Replace позволяет заменять, добавлять или удалять модули во время работы приложения без перезагрузки всей страницы.

преимущество:

  1. Сохранить состояние приложения, потерянное во время полной перезагрузки страницы.
  2. Обновляйте только изменения, чтобы сэкономить драгоценное время разработки.
  3. Когда CSS/JS изменяется в исходном коде, он будет немедленно обновлен в браузере, что почти эквивалентно изменению стиля непосредственно в инструментах разработки браузера.

картинка проблемы

HRM问题.gif

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

код

Согласно приведенному выше вопросу, нам нужно только изменить код на devServer.

/*
* webpack.config.js
*/
...
module.exports = {
...
  devServer: {
    // 项目构建后的路径
    contentBase: resolve(__dirname, "build"),
    // 自动打开浏览器
    open: true,
    // 端口号
    port: 5555,
    // 开启gzip压缩
    compress: true,
    // 新增---> 开启热更新
    // 模块热替换功能会在程序运行过程中,替换,添加或删除模块,而无需重新加载整个页面。
    hot: true,
  },
};

Изображение эффекта

HRM解决.gif

режим отладки исходного кода devtool

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

  1. Отладка исходного кода для среды разработки

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

  2. Отладка исходного кода для производственной среды

    (нет) (опция devtool опущена) — не создавать исходные карты. Это хороший выбор.

Rule.oneOf соответствует правилам

правила сопоставления загрузчика.

/*
* webpack.config.js
*/
...
module.exports = {
...
// 模块
  module: {
    rules: [
      // loader的配置
      {
        oneOf: [
          // 以下的loader只会执行匹配的文件一次。
          {
            test: /\.css$/,
            // 使用loader对文件进行处理
            /**
             * use数组中的执行顺序是 从右到左  从下到上 依次执行
             * style-loader  创建style标签,将样式文件引入到header中
             * css-loader 将css模块变成commonjs模块加载到js中, 文件内容是样式字符串
             */
            use: ["style-loader", "css-loader"],
          },
          {
            test: /\.less$/,
            use: [
              "style-loader",
              "css-loader",
              // 将less文件编译成css文件
              // 需要下载 less-loader和less
              "less-loader",
            ],
          },
          {
            test: /\.(png|svg|jpg|jpeg|gif)$/i,
            // webpack 5 内置了资源类型,已经废弃了之前的 url-loader 和 file-loader
            type: "asset/resource",
          },
          {
            test: /\.html$/,
            // 处理html中的图片文件 引入img文件进而让url-loader处理
            loader: "html-loader",
          },
          {
            // 处理其他资源
            exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i,
            type: "asset/resource",
          },
        ],
      },
    ],
  },
...
}

Таким образом, согласование загрузчика во время упаковки улучшит скорость.

файловый кеш

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

Сначала мы должны загрузить следующий загрузчик:

yarn add babel-loader @babel/core @babel/preset-env mini-css-extract-plugin --dev

Шаги оптимизации

  1. Извлеките файл css из связанного файла как отдельный файл.
  2. Используйте кеш babel для файлов js.

Извлечь css и кеш файловых ресурсов

/*
* webpack.config.js
*/
...
// 新增插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
...
// 模式
mode: "production",
output: {
    filename: "built.[contenthash:10].js",
    ...
  },
...
  // 模块
  module: {
    rules: [
      // loader的配置
      {
        oneOf: [
          // 以下的loader只会执行匹配的文件一次。
          {
            test: /\.css$/,
            // 将 style-loader 替换
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
          {
            test: /\.less$/,
            use: [
            // 将 style-loader 替换
              MiniCssExtractPlugin.loader,
              "css-loader",
              "less-loader",
            ],
          },
         ...
         // 新增js的loader
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-env"],
                // 开启babel缓存
                // 第二次构建时,会读取之前的缓存
                // 用来缓存 loader的执行结果。之后的webpack 构建,将会尝试读取缓存,
                // 来避免在每次执行时,可能产生的、高性能消耗的 Babel 
                // 重新编译过程(recompilation process)。
                cacheDirectory: true,
              },
            },
          },
        ],
      },
    ],
  },
  // 插件
  plugins: [
    ...
    //新增插件
    new MiniCssExtractPlugin({
      filename: "css/built.[contenthash:10].css",
    }),
  ],
};
  1. hash: уникальное значение хеш-функции генерируется каждый раз при сборке wepack.

    • Проблема: потому что и js, и css используют хеш-значение.
    • Переупаковка сделает недействительными все кеши. (Возможно, я изменил только один файл)
  2. chunkhash: хеш-значение, сгенерированное в соответствии с чанком. Если пакет исходит из одного и того же фрагмента, значение хеш-функции будет таким же.

    • Проблема: значение хеш-функции js и css остается одинаковым
    • Поскольку css представлен в js, он принадлежит к тому же фрагменту
  3. ContentHash: генерировать хеш-значение на основе содержимого файла.

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

Добавьте jsloader и установите кеш

/*
* webpack.config.js
*/
...
module.exports = {
...
  // 模块
  module: {
    rules: [
      // loader的配置
      {
        oneOf: [
         ...
         // 新增js的loader
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: "babel-loader",
              options: {
                presets: ["@babel/preset-env"],
                // 开启babel缓存
                // 第二次构建时,会读取之前的缓存
                // 用来缓存 loader的执行结果。之后的webpack 构建,将会尝试读取缓存,
                // 来避免在每次执行时,可能产生的、高性能消耗的 Babel 
                // 重新编译过程(recompilation process)。
                cacheDirectory: true,
              },
            },
          },
        ],
      },
    ],
  },
...
};

Далее пишем тестовый код. Создайте новый файл server.js и создайте новый сервер для тестирования. Скачать экспресс-фреймворк.

yarn add express --dev

/*
* server.js
*/
const express = require("express");

const server = express();

// 缓存一个小时
server.use(express.static("build", { maxAge: 1000 * 3600 }));

server.listen(5555, () => {
  console.log("服务器启动成功!", "http://localhost:5555/");
});

Проверить шаги кеша

  1. Упаковать веб-пакет в веб-пакет
  2. запустите узел сервера server.js
  3. http://localhost:5555/

Изображение эффекта

文件缓存.gif

встряхивание дерева удаляет бесполезный код

Предпосылки для использования

  1. Должен использовать модульность ES6
  2. Открытая производственная среда

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

дрожание дерева Пока мы соблюдаем два предыдущих условия, веб-пакет будет автоматически удалять недопустимый код при сборке.

Добавить тестовый файл

/*
* testTreeShaking.js
*/
export const test1 = () => {
  console.log("test1");
};

export const test2 = () => {
  console.log("test2");
};

Изменить файл

/*
* index.js
*/
...
import { test1 } from "./testTreeShaking";
test1();
// 未引入test2函数。打包时会忽略。
/*
* webpack.config.js
*/
...
module.exports = {
...
// 修改打包模式
mode: 'production',
...
};

Конфигурация в package.json

/*
* ackage.json
*/
"sideEffects": false 
// 没有副作用(都可以进行tree shaking)
// 可能会把css / @babel/polyfill (副作用)文件干掉
"sideEffects": ["*.css", "*.less"]

Пакет webpack, просмотрите файл пакета, как показано ниже:

image.png

Как видите, функция test2 не упакована в файл пакета.

код сплит код сплит

Существует три типа разделения кода:

  1. Файлы с несколькими записями будут автоматически выполнять разделение кода
  2. Оптимизация.splitChunks управляет разделением кода.
  3. Оптимизация.splitChunks + import() для разделения кода

преимущество: Разделение кода может эффективно отклонять файлы js, которые слишком велики.

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

/*
* webpack.config.js
*/
...
module.exports = {
...
entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: "./src/index.js",
    test: "./src/testTreeShaking.js",
  },
...
};

Эффект проверки пакета webpack, как показано ниже:

image.png

Оптимизация.splitChunks управляет разделением кода.

/*
* webpack.config.js
*/
...
module.exports = {
...
// 新增代码
optimization: {
    /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  */
    splitChunks: {
      //这表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为 all,async 和 initial。
      //设置为 all 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享。
      chunks: "all",
    },
  },
...
};

Эффект проверки пакета webpack, как показано ниже:

image.png

Оптимизация.splitChunks + import() для разделения кода

/*
* webpack.config.js
*/
...
module.exports = {
...
// 新增代码
optimization: {
    /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  */
    splitChunks: {
      //这表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为 all,async 和 initial。
      //设置为 all 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享。
      chunks: "all",
    },
  },
...
};
/*
* index.js
*/
/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包
*/
import("./testTreeShaking").then(
  (res) => {
    console.log("res", res);
    res.test1();
  }
);

Эффект проверки пакета webpack, как показано ниже:

image.png

ленивая загрузка файлов предварительная загрузка

разница

  1. Перезагрузить с помощью файла
  2. Загружать первой, когда браузер бездействует

Изменить файл

/*
*index.html
*/
...
<button id="btn">加载testTreeShaking文件</button>
...
/*
*index.js
*/
console.log("加载index文件");
/*
*testTreeShaking.js
*/
export const test1 = () => {
  console.log("test1");
};

export const test2 = () => {
  console.log("test2");
};
console.log("加载index文件");

ленивая загрузка файла

/*
*index.js
*/
console.log("加载index文件");
document.getElementById("btn").onclick = function () {
  // 懒加载:当文件需要使用时才加载
  import("./testTreeShaking").then(({ test1 }) => {
    test1();
  });
};

Ленивая загрузка рендеринга

懒加载.gif

Предварительная загрузка

/*
*index.js
*/
console.log("加载index文件");
document.getElementById("btn").onclick = function () {
  // 预加载 prefetch:会在使用之前,提前加载js文件
  // 正常加载可以认为是并行加载(同一时间加载多个文件)
  // 预加载 prefetch: webpackPrefetch 等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackPrefetch: true */ "./testTreeShaking").then(({ test1 }) => {
    test1();
  });
};

Предварительная загрузка визуализаций

预加载.gif

Прогрессивные веб-приложения PWA

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

Рендеринг PWA от Taobao

Добавьте плагин workbox-webpack-plugin, затем настройте файл webpack.config.js:

yarn add workbox-webpack-plugin --dev

Изменить webpack.config.js

/*
* webpack.config.js
*/
...
// 新增插件
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
module.exports = {
...
  // 插件
  plugins: [
    ...
    //新增插件
    new WorkboxWebpackPlugin.GenerateSW({
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
};

Зарегистрировать сервисного работника

/*
* index.js
*/
/*
   sw代码必须运行在服务器上
      --> nodejs
      -->
        npm server 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
// 处理兼容性问题
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/service-worker.js")
      .then(() => {
        console.log("sw注册成功了~");
      })
      .catch(() => {
        console.log("sw注册失败了~");
      });
  });
}

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

  1. упаковать первым
  2. узел server.js запускает сервер
  3. http://localhost:5555/

Проверьте изображение эффекта PWA

PWA.gif

Мультипроцессная упаковка

yarn add thread-loader --dev

Измените файл webpack.config.js:

/*
* webpack.config

.js
*/
...
module.exports = {
...
  // 模块
  module: {
    rules: [
      // loader的配置
      {
        oneOf: [
         ...
         // 新增js的loader
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              /* 
                新增代码
                开启多进程打包。 
                进程启动大概为600ms,进程通信也有开销。
                只有工作消耗时间比较长,才需要多进程打包
              */
              {
                loader: "thread-loader",
                options: {
                  // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
                  // 在 require('os').cpus() 是 undefined 时回退至 1
                  workers: 2, // 进程2个
                },
              },
              ...
            ],
          },
        ],
      },
    ],
  },
...
};

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

Многопроцессные визуализации упаковки без вскрытия

image.png

Включите визуализацию многопроцессорной упаковки

image.png

externals

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

Измените файл webpack.config.js и добавьте свойство externals.

/*
* webpack.config.js
*/
...
module.exports = {
...
  externals: {
    jquery: "jQuery",
  },
};

Выберите ссылку CDN JQ и добавьте ее в index.html.

Бесплатный CDN-адрес

/*
* index.html
*/ 
<!DOCTYPE html>
<html lang="zn">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>webpack打包优化</title>
  <!-- 新增代码 -->
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</head>

<body>
  <h1>webpack 打包优化</h1>
  <div class="color_1"></div>
  <div class="color_2"></div>
  <img src="./img/zp.jpg" />
</body>

</html>
/*
* index.js
*/
import $ from "jquery";
console.log("$", $);

webpack упакован для тестирования.

Изображение эффекта

外部扩展.gif

Динамическая библиотека DLL

Реализовано разделение пакетов, а также значительно повышена скорость сборки. Термин «DLL» означает библиотеку динамической компоновки, первоначально представленную Microsoft.

Добавлен файл webpack.dll.js для отдельной настройки DllPlugin.

/*
* webpack.dll.js
*/
const { resolve } = require("path");
const webpack = require("webpack");

module.exports = {
  entry: {
    // 最终打包生成的[name] --> dllFile
    // ['jquery',"lodash"] --> 要打包的库是jquery lodash
    dllFile: ["jquery", "lodash"],
  },
  output: {
    filename: "[name].js",
    path: resolve(__dirname, "dll"),
    library: "[name]_[hash]", // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery lodash映射
    new webpack.DllPlugin({
      name: "[name]_[hash]", // 映射库的暴露的内容名称
      path: resolve(__dirname, "dll/manifest.json"), // 输出文件路径
    }),
  ],
  mode: "production",
};

бегатьwebpack --config webpack.dll.jsВыполните этот файл, чтобы сгенерировать следующие файлы:

image.png

Измените файл webpack.config.js, чтобы настроить подключаемый модуль DllReferencePlugin AddAssetHtmlWebpackPlugin.

скачатьyarn add --dev add-asset-html-webpack-plugin

/*
* webpack.config.js
*/
...
// 新增代码
const webpack = require("webpack");
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin");
module.exports = {
...
  plugins: [
   ...
     // 新增代码
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, "dll/manifest.json"),
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, "dll/dllFile.js"),
      outputPath: "dll", // 如果设置,将用作文件的输出目录。
      publicPath: "dll", // 如果设置,将用作脚本或链接标记的公共路径。
    }),
  ],
};

Измените файл index.js для проверки:

/*
* index.js
*/
...
import $ from "jquery";
console.log("$--->jquery", $);
import _ from "lodash";
console.log("_---->lodash", _);

Запустите webpack для сборки. Сгенерируйте следующие файлы:

image.png

Проверьте беговой эффект

屏幕录制2021-05-14 11.gif

Это конец теории оптимизации упаковки webpack.На самом деле, есть еще много моментов по упаковке.Вы можете оставлять комментарии в области комментариев, и мы будем расти вместе! !

Учащиеся, желающие просмотреть исходный код, щелкните здесь, чтобы получить его.

Следующая статья Практика упаковки.

Категории