9 тактика оптимизации веб-запада, вы можете не знать

Webpack
9 тактика оптимизации веб-запада, вы можете не знать

введение

webpackОптимизация упаковки .

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

В этой статье демонстрируется код,Адрес склада

Анализ скорости 🏂

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

yarn add -D speed-measure-webpack-plugin

Конфигурация тоже очень простая, ставитеwebpackОбъект конфигурации можно обернуть:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

const webpackConfig = smp.wrap({
  plugins: [
    new MyPlugin(),
    new MyOtherPlugin()
  ]
});

Давайте взглянем на введение в проектspeed-measure-webpack-pluginПосле упаковки:Как видно из рисунка выше, этот плагин в основном делает две вещи:

  • Подсчитайте общее время, затраченное на весь пакет
  • Анализируйте потребление времени каждым плагином и загрузчиком знать конкретныеloaderа такжеpluginситуации, отнимающей много времени, мы можем «прописать правильное лекарство»

Анализ объема 🎃

Оптимизация объема после упаковки — это момент, который можно оптимизировать.Например, некоторые библиотеки сторонних компонентов слишком велики.В настоящее время необходимо подумать, найти ли альтернативы.

используется здесьwebpack-bundle-analyzer, и это также плагин, который я чаще всего использую в своей работе.

Его можно отобразить с помощью интерактивной масштабируемой древовидной карты.webpackРазмер выходного файла. Очень удобно использовать.

Сначала установите плагин:

yarn add -D webpack-bundle-analyzer

установлен вwebpack.config.jsПростая конфигурация в:

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
    //  可以是`server`,`static`或`disabled`。
    //  在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。
    //  在“静态”模式下,会生成带有报告的单个HTML文件。
    //  在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
    analyzerMode: "server",
    //  将在“服务器”模式下使用的主机启动HTTP服务器。
    analyzerHost: "127.0.0.1",
    //  将在“服务器”模式下使用的端口启动HTTP服务器。
    analyzerPort: 8866,
    //  路径捆绑,将在`static`模式下生成的报告文件。
    //  相对于捆绑输出目录。
    reportFilename: "report.html",
    //  模块大小默认显示在报告中。
    //  应该是`stat`,`parsed`或者`gzip`中的一个。
    //  有关更多信息,请参见“定义”一节。
    defaultSizes: "parsed",
    //  在默认浏览器中自动打开报告
    openAnalyzer: true,
    //  如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成
    generateStatsFile: false,
    //  如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。
    //  相对于捆绑输出目录。
    statsFilename: "stats.json",
    //  stats.toJson()方法的选项。
    //  例如,您可以使用`source:false`选项排除统计文件中模块的来源。
    //  在这里查看更多选项:https:  //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
    statsOptions: null,
    logLevel: "info"
  )
  ]
}

Затем в инструменте командной строки введитеnpm run dev, он запустит локальный сервер с номером порта 8888 по умолчанию:На каждом рисунке четко виден объем ассемблерного кода, сторонних библиотек.

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

Многопроцесс / многоэкземное здание 🏈

каждый знаетwebpackработает наnodeсреда, при этомnodeявляется однониточным.webpackПроцесс упаковкиioИнтенсивные и ресурсоемкие операции, если и то, и другоеforkНесколько процессов обрабатывают каждую задачу параллельно, что эффективно сокращает время сборки.

Двумя наиболее часто используемыми являютсяthread-loaderа такжеHappyPack.

Давайте взглянемthread-loaderНу и этот тожеwebpack4официально рекомендуется.

thread-loader

Установить

yarn add -D thread-loader

thread-loaderбудет ли вашloaderпомещен вworkerЗапуск внутри пула для достижения многопоточных сборок.

возьми этоloaderразмещены в другихloaderперед (как в примере ниже) поместите этоloaderПослеloaderбудет в отдельномworkerбассейн (worker pool) для работы.

Пример

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // your expensive loader (e.g babel-loader)
        ]
      }
    ]
  }
}

HappyPack

Установить

yarn add -D happypack

HappyPackможет позволитьWebpackОбрабатывайте несколько задач одновременно и играйте в многоядерные игрыCPUВозможность декомпозиции задач на несколько подпроцессов для одновременного выполнения.После обработки подпроцессов результаты отправляются в основной процесс. Ускорьте сборку кода с помощью модели с несколькими процессами.

Пример

// webpack.config.js
const HappyPack = require('happypack');

exports.module = {
  rules: [
    {
      test: /.js$/,
      // 1) replace your original list of loaders with "happypack/loader":
      // loaders: [ 'babel-loader?presets[]=es2015' ],
      use: 'happypack/loader',
      include: [ /* ... */ ],
      exclude: [ /* ... */ ]
    }
  ]
};

exports.plugins = [
  // 2) create the plugin:
  new HappyPack({
    // 3) re-add the loaders you replaced above in #1:
    loaders: [ 'babel-loader?presets[]=es2015' ]
  })
];

Здесь следует отметить, чтоHappyPackАвтор заявил, что этот проект больше не поддерживается, это можно найти по адресуgithubСклад видит:Автор также рекомендует использоватьwebpackофициально предоставленthread-loader.

thread-loaderа такжеhappypackДля небольших проектов скорость упаковки мало влияет и может даже увеличить накладные расходы, поэтому рекомендуется максимально использовать ее в крупных проектах.

Многопроизводительный параллельный код сжатия 🛵

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

webpackпредоставляется по умолчаниюUglifyJSплагин для сжатияJSкод, но он использует однопоточный код сжатия, что означает несколькоjsФайл нужно сжать, его нужно сжать файл за файлом. Таким образом, очень медленно упаковывать сжатый код в формальную среду (из-за сжатияJSКод нужно сначала разобратьObjectабстрактно представленныйASTСинтаксическое дерево, а затем применять различные правила для анализа и обработкиAST, в результате чего процесс занимает очень много времени).

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

В настоящее время существует три основных схемы сжатия:

  • parallel-uglify-plugin
  • uglifyjs-webpack-plugin
  • terser-webpack-plugin

parallel-uglify-plugin

НадHappyPackИдея состоит в том, чтобы использовать несколько дочерних процессов для анализа и компиляцииJS,CSSи т. д., так что несколько подзадач могут обрабатываться параллельно.После выполнения нескольких подзадач результаты отправляются в основной процесс.С этой идеей,ParallelUglifyPluginПлагин создан.

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

Установить

yarn add -D webpack-parallel-uglify-plugin

Пример

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

module.exports = {
  plugins: [
    new ParallelUglifyPlugin({
      // Optional regex, or array of regex to match file against. Only matching files get minified.
      // Defaults to /.js$/, any file ending in .js.
      test,
      include, // Optional regex, or array of regex to include in minification. Only matching files get minified.
      exclude, // Optional regex, or array of regex to exclude from minification. Matching files are not minified.
      cacheDir, // Optional absolute path to use as a cache. If not provided, caching will not be used.
      workerCount, // Optional int. Number of workers to run uglify. Defaults to num of cpus - 1 or asset count (whichever is smaller)
      sourceMap, // Optional Boolean. This slows down the compilation. Defaults to false.
      uglifyJS: {
        // These pass straight through to uglify-js@3.
        // Cannot be used with uglifyES.
        // Defaults to {} if not neither uglifyJS or uglifyES are provided.
        // You should use this option if you need to ensure es5 support. uglify-js will produce an error message
        // if it comes across any es6 code that it can't parse.
      },
      uglifyES: {
        // These pass straight through to uglify-es.
        // Cannot be used with uglifyJS.
        // uglify-es is a version of uglify that understands newer es6 syntax. You should use this option if the
        // files that you're minifying do not need to run in older browsers/versions of node.
      }
    }),
  ],
};

webpack-parallel-uglify-pluginБольше не поддерживается, не рекомендуется здесь

uglifyjs-webpack-plugin

Установить

yarn add -D uglifyjs-webpack-plugin

Пример

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  plugins: [
    new UglifyJsPlugin({
      uglifyOptions: {
        warnings: false,
        parse: {},
        compress: {},
        ie8: false
      },
      parallel: true
    })
  ]
};

По сути, это то же самое, что и вышеparallel-uglify-pluginТочно так же вы также можете установитьparallel: trueВключить многопроцессорное сжатие.

terser-webpack-plugin

Я не знаю, нашли ли вы:webpack4уже поддерживается по умолчаниюES6Синтаксическое сжатие.

И это неотделимоterser-webpack-plugin.

Установить

yarn add -D terser-webpack-plugin

Пример

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4,
      }),
    ],
  },
};

Предварительно скомпилированные модули ресурсов 🚀

Что такое предварительно скомпилированный ресурсный модуль?

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

Тогда сторонняя библиотека упаковывается только один раз, когда она упаковывается в первый раз, и пока мы не обновим сторонний пакет в будущем, тогдаwebpackЭти библиотеки не будут упакованы, что может быстро повысить скорость упаковки. На самом деле, это预编译资源模块.

webpack, мы можем объединитьDllPluginа такжеDllReferencePluginплагин для реализации.

DllPluginчто это такое?

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

DLLPluginПлагин находится в дополнительном автономномwebpackВ настройках создайте толькоdllизbundle, то есть в корневом каталоге проекта, в дополнение кwebpack.config.js, также создаст новыйwebpack.dll.jsдокумент.

webpack.dll.jsРоль состоит в том, чтобы упаковать все зависимости сторонних библиотек в одинbundleизdllВ файле файл с именемmanifest.jsonдокумент. Долженmanifest.jsonиспользуется, чтобы сделатьDllReferencePluginСопоставлены со связанными зависимостями.

DllReferencePluginЧто это такое?

Этот плагин находится вwebpack.config.jsиспользуется в плагине, роль плагина заключается вwebpack.dll.jsупаковано вdllФайл ссылается на необходимые предварительно скомпилированные зависимости.

Что это значит? то естьwebpack.dll.jsНапример, после упаковки он сгенерируетvendor.dll.jsдокументы иvendor-manifest.jsonдокумент,vendor.dll.jsФайл содержит все файлы сторонних библиотек,vendor-manifest.jsonФайл будет содержать индекс всего кода библиотеки при использованииwebpack.config.jsупаковка файловDllReferencePluginПри использовании плагинаDllReferencePluginплагин читатьvendor-manifest.jsonфайл, чтобы узнать, существует ли сторонняя библиотека.

vendor-manifest.jsonФайл — это просто сопоставление сторонней библиотеки.

Как это использовать в проекте?

Столько всего было сказано выше, в основном для удобства всех预编译资源模块а такжеDllPluginа также,DllReferencePluginПонимание функции плагина

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

В основном в двух конфигурациях, а именноwebpack.dll.jsа такжеwebpack.config.js(соответствует здесь яwebpack.base.js)

webpack.dll.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    vendors: ['lodash', 'jquery'],
    react: ['react', 'react-dom']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, './dll'),
    library: '[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]',
      path: path.resolve(__dirname, './dll/[name].manifest.json')
    })
  ]
}

Здесь я разобрал две части:vendors(хранитсяlodash,jqueryи т.д.) иreact(Хранит связанные с реакцией библиотеки,react,react-domЖдать)

webpack.config.js(соответствует мне здесьwebpack.base.js)

const path = require("path");
const fs = require('fs');
// ...
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');

const plugins = [
  // ...
];

const files = fs.readdirSync(path.resolve(__dirname, './dll'));
files.forEach(file => {
  if(/.*\.dll.js/.test(file)) {
    plugins.push(new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, './dll', file)
    }))
  }
  if(/.*\.manifest.json/.test(file)) {
    plugins.push(new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, './dll', file)
    }))
  }
})

module.exports = {
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: []
  },
  plugins,

  output: {
    // publicPath: "./",
    path: path.resolve(__dirname, "dist")
  }
}

Здесь для демонстрации опущено много кода, полный код проектаэто здесь

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

Наконец вpackage.jsonПросто добавьте к нему скрипт:

"scripts": {
    "build:dll": "webpack --config ./webpack.dll.js",
  },

бегатьyarn build:dllБудет создана схема структуры проекта, размещенная в начале этого раздела~

Используйте кэш для увеличения второй скорости строительства

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

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

существуетwebpackОбычно существуют следующие идеи использования кеша в Китае:

  • babel-loaderвключить кеш
  • использоватьcache-loader
  • использоватьhard-source-webpack-plugin

babel-loader

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

можно добавитьcacheDirectoryПараметр для включения кэширования:

 {
    test: /\.js$/,
    exclude: /node_modules/,
    use: [{
      loader: "babel-loader",
      options: {
        cacheDirectory: true
      }
    }],
  },

cache-loader

В некоторых накладных расходахloaderдобавить это передloader, чтобы кэшировать результаты на диск.

Установить

yarn add -D cache-loader

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

cache-loaderКонфигурация очень простая, поставь в др.loaderдо. ИсправлятьWebpackКонфигурация выглядит следующим образом:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.ext$/,
        use: [
          'cache-loader',
          ...loaders
        ],
        include: path.resolve('src')
      }
    ]
  }
}

Обратите внимание, что сохранение и чтение этих файлов кеша потребует некоторого времени, поэтому используйте только ресурсоемкийloaderloader

hard-source-webpack-plugin

HardSourceWebpackPluginnode_modules/.cache/hard-source

hard-source-webpack-pluginПосле первого раза время сборки почти не меняется, но со второго раза время сборки может сократиться примерно80%о.

Установить

yarn add -D hard-source-webpack-plugin

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

// webpack.config.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
  entry: // ...
  output: // ...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

webpack5встроенныйhard-source-webpack-plugin.

Сузьте цели сборки/уменьшите поиск файлов 🍋

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

Минимизировать цель сборки

в основномexcludeа такжеincludeиспользование:

  • исключить: модули, которые не нужно разрешать
  • include: модули для разбора
// webpack.config.js
const path = require('path');
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // include: path.resolve('src'),
        use: ['babel-loader']
      }
    ]
  }

здесьbabel-loaderисключитnode_modulesсоответствующий следующемуjsсинтаксический анализ для повышения скорости сборки.

Уменьшить область поиска файлов

Это в основномresolveСвязанная конфигурация, используемая для установки способа анализа модуля. пройти черезresolveконфиг, который может помочьWebpackБыстро найти зависимости и заменить соответствующие зависимости.

  • resolve.modules:РассказыватьwebpackМодуль синтаксического анализа должен искать каталог
  • resolve.mainFields: когда изnpmПри импорте модуля в пакете (например,import * as React from 'react'), эта опция будет определять, когдаpackage.jsonКакое поле используется в модуле импорта. согласно сwebpackуказано в конфигурацииtargetотличается, значение по умолчанию будет другим
  • resolve.mainFiles: каталог для разбора имени файла, который вы хотите использовать по умолчанию.index
  • resolve.extensions: расширение файла
// webpack.config.js
const path = require('path');
module.exports = {
  ...
  resolve: {
    alias: {
      react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js')
    }, //直接指定react搜索模块,不设置默认会一层层的搜寻
    modules: [path.resolve(__dirname, 'node_modules')], //限定模块路径
    extensions: ['.js'], //限定文件扩展名
    mainFields: ['main'] //限定模块入口文件名

Сервис динамического полифилла 🦑

представлять动态PolyfillПрежде давайте посмотрим, чтоbabel-polyfill.

Что такое babel-полифилл?

babelОтвечает только за преобразование синтаксиса, например преобразованиеES6Преобразование синтаксисаES5. Но если некоторые объекты и методы не поддерживаются самим браузером, например:

  • глобальный объект:Promise,WeakMapЖдать.
  • Глобальная статическая функция:Array.from,Object.assignЖдать.
  • Методы экземпляра: например.Array.prototype.includesЖдать.

В этот момент необходимо представитьbabel-polyfillСмоделировать и реализовать эти объекты и методы.

Это также обычно называют垫片.

как использоватьbabel-polyfill?

Он также очень прост в использовании, вwebpack.config.jsФайл можно настроить следующим образом:

module.exports = {
  entry: ["@babel/polyfill", "./app/js"],
};

зачем использовать动态Polyfill?

babel-polyfillПотому что он импортируется сразуpolyfill, поэтому очень удобен в использовании, но в то же время приносит и большую проблему: файл очень большой, поэтому последующие решения оптимизированы под эту проблему.

Посмотрите на упаковку послеbabel-polyfillДоля:На его долю приходится 29,6%, что многовато!

По вышеуказанным причинам,动态PolyfillСлужба родилась. Узнай по картинкеPolyfill ServiceПринцип:

Каждый раз, когда вы открываете страницу, браузер отправляетPolyfill Serviceпослать запрос,Polyfill ServiceИдентифицироватьUser Agent, отправить разныеPolyfill, для загрузки по запросуPolyfillЭффект.

как использовать动态PolyfillСлужить?

Можно использовать официальный адрес службы:

//访问url,根据User Agent 直接返回浏览器所需的 polyfills
https://polyfill.io/v3/polyfill.min.js

Scope Hoisting🦁

чтоScope Hoisting?

Scope hoistingДословный перевод — «продвижение масштаба». знакомыйJavaScriptКаждый должен знать «подъем функций» и «подъем переменных»,JavaScriptподнимет объявления функций и переменных в верхнюю часть текущей области. "Подъем объема" тоже похож на это,webpackпредставитjsФайл "поднят на" те, что вверху его введения.

Scope Hoistingможет позволитьWebpackУпакованные файлы кода меньше по размеру и работают быстрее.

включитьScope Hoisting

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

// webpack.config.js
const webpack = require('webpack')

module.exports = mode => {
  if (mode === 'production') {
    return {}
  }

  return {
    devtool: 'source-map',
    plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
  }
}

включитьScope Hoistingсравнение после

Давайте сначала посмотрим, нет лиScope HoistingДоWebpackспособ упаковки.

Если сейчас два файла

  • constant.js:
export default 'Hello,Jack-cool';
  • входной файлmain.js:
import str from './constant.js';
console.log(str);

Для приведенного выше исходного кодаWebpackУпакованная часть кода выглядит следующим образом:

[
  (function (module, __webpack_exports__, __webpack_require__) {
    var __WEBPACK_IMPORTED_MODULE_0__constant_js__ = __webpack_require__(1);
    console.log(__WEBPACK_IMPORTED_MODULE_0__constant_js__["a"]);
  }),
  (function (module, __webpack_exports__, __webpack_require__) {
    __webpack_exports__["a"] = ('Hello,Jack-cool');
  })
]

при открытииScope HoistingПосле этого часть кода, выводимого из того же исходного кода, выглядит следующим образом:

[
  (function (module, __webpack_exports__, __webpack_require__) {
    var constant = ('Hello,Jack-cool');
    console.log(constant);
  })
]

Видно, что открытиеScope HoistingПосле этого объявление функции изменилось с двух на одно,constant.jsСодержимое, определенное в, вводится непосредственно вmain.jsв соответствующем модуле. Преимущества этого:

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

Scope HoistingПринцип реализации на самом деле очень прост: анализировать зависимости между модулями и максимально объединять разбросанные модули в одну функцию, но предпосылка заключается в том, что нельзя вызвать избыточность кода. Таким образом, только те модули, на которые есть ссылка один раз, могут быть объединены.

из-заScope HoistingЗависимости между модулями необходимо анализировать, поэтому исходный код должен использоватьES6Модульная инструкция, иначе не получится.

Ссылаться на

Geek Time [Играть с помощью веб-пакета]

❤️ Люблю тройное комбо

1. Если вы считаете, что эта статья неплохая, пожалуйста, помогитеподобноДавай, пусть больше людей увидят это~

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

3. На спецучастках носите маску и пользуйтесь средствами индивидуальной защиты.

4. Добавьте WeChatfs1263215592, пригласите вас в группу технического обмена, чтобы учиться вместе 🍻

В этой статье используетсяmdniceнабор текста