Webpack4+Babel7 оптимизирует скорость на 70%

Webpack Babel
Webpack4+Babel7 оптимизирует скорость на 70%
Автор DBCdouble

Исходный код проекта Demo:кликните сюда

Введение

Прошло много времени с тех пор, как 15 февраля 2018 года вышел webpack 4.0.0.webpackПолагаться на "Нулевая конфигурация","До 98 % быстрее«Я успешно привлек бесчисленное количество поклонников. Для меня, который страдал от слишком долгого времени упаковки проекта, я, несомненно, увидел свет, поэтому я решил начать тестирование воды.

2. Структура проекта и окружающая среда

Перед обновлением:
  • Node: v8.11.4
  • webpack: ^1.12.9
  • связанные с Вавилоном: ^6.x
  • react: ^0.14.8 (Когда я впервые увидел версию для реакции, я был немного сбит с толку, но это правда, ха-ха, я не могу не восхищаться тем, что человек, который первоначально создал этот проект, должен быть боссом реакции, и я обновлю статью, чтобы перейти на react16.x)
  • react-router: ^2.6.1 (позже статья будет обновлена ​​до react-router4.x)
  • Связанные загрузчики
  • Компоненты роутинга (страниц): 130 (в проекте используются SPA-приложения, и на данный момент существует 130 страниц роутинга, поэтому, если скорость построения можно успешно улучшить или уменьшить размер файла на достаточно большом приложении, то обновление версии webpack Появится 4.0. Значительно)

После обновления:

  • Node: v8.11.4
  • webpack: ^4.29.5
  • связанные с Вавилоном: ^7.x
  • react: ^0.14.8
  • react-router: ^2.6.1
  • Связанные загрузчики (обновленные загрузчики будут подробно описаны позже)
  • Компоненты маршрутизации (страница) количество

3. Фон

С непрерывной итерацией проекта количество файлов стилей и js файлов увеличивается, что приводит к тому, что все больше и больше времени тратится на упаковку веб-пакета.В среде разработки часто необходимо часто отлаживать определенный фрагмент кода ctrl+s будет появляются долго Явление ожидания (ждать надоедает) со временем накапливается и тратит слишком много времени на ожидание упаковки. Не говоря уже о производственной среде, средняя продолжительность составляет около 100–120 с.В нормальных условиях после входа в npm run deploy to package я выбираю выйти и выкурить сигарету. И если ситуация заключается в устранении онлайн-ошибок, каждая секунда на счету, поэтому необходимо оптимизировать время упаковки.

4. Анализ

процесс сборки вебпака

  • Инициализировать: начать сборку, прочитать и объединить параметры конфигурации, загрузитьPlugin, экземплярCompiler.
  • Компилировать: изEntryвыдается за каждыйModuleпоследовательный вызов, соответствующийLoaderПерейдите к переводу содержимого файла, а затем найдитеModuleзависимыйModule, который компилируется рекурсивно.
  • Вывод: объедините скомпилированный модуль вChunk,Пучок ChunkПреобразуйте его в файл и выведите локально.

Анализ упаковки

Время, проведенное в производственной среде webpack2.x: 104,145 с.


Время, проведенное в среде разработки webpack2.x: 68099 мс


Хотя мы можем интуитивно увидеть время, затраченное на упаковку webpack2, мы не знаем, какие этапы прошла упаковка webpack и на что потрачено много времени. можно использовать здесьspeed-measure-webpack-pluginУпаковка Webpack обнаружена во время каждой части потраченного времени, при вводе команды в терминал монтируется.

npm install speed-measure-webpack-plugin -D

После завершения установки настраиваем его в конфигурационном файле вебпака

webpack.config.js



Обратитесь к использованию плагина speed-measure-webpack-plugin,проверить здесь

После настройки запустите проект (здесь анализируется только среда разработки), как показано ниже


Как видно из рисунка выше, большую часть времени в процессе упаковки вебпака тратится на загрузчик, который является вторым звеном в процессе построения вебпака, этап компиляции. Обратите внимание, что также видно, что ProgressPlugin занял 28,87 с, поэтому после того, как нам не нужно анализировать время, затраченное на процесс упаковки веб-пакета, мы можем прокомментировать его в webpack.config.js.

Пять, установка и настройка

1. веб-пакет

Сначала удалите предыдущий веб-пакет, webpack-cli, webpack-dev-server

npm uninstall webpack webpack-dev-server webpack-cli &&  npm uninstall webpacl-cli -g

Установите последнюю версию веб-пакета, webpack-cli (webpack4 извлекает шаблоны webpack-cli из webpack, поэтому необходимо установить webpack-cli), webpack-dev-server

npm install webpack webpack-dev-server webpack-cli -D

Кстати, я обновлю соответствующие плагины вебпака до последней версии, т.к. в вебпак внесено много изменений по сравнению с вебпакс2, на случай, если плагины старой версии не совместимы с вебпак4, поэтому буду ставить все модули плагинов, связанных с веб-пакетом в проекте, здесь. Сначала удалите его, чтобы вы могли анализировать ошибки при обновлении

npm uninstall extract-text-webpack-plugin html-webpack-plugin webpack-dev-middleware webpack-hot-middleware

2. Обновите бабел7

Удалите предыдущие модули, связанные с Babel.

npm uninstall babel-core babel-loader babel-cli babel-eslint babel-plugin-react-transform babel-plugin-transform-runtime babel-preset-es2015 babel-preset-react babel-preset-stage-0 babel-runtime

установить бабел7

npm install @babel/cli @babel/core babel-loader @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-export-default-from @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react

  • @babel/cli: Инструменты лесов Бабела
  • @babel/core: babel-core существует как ядро ​​babel.Основной API-интерфейс babel находится в этом модуле, например: transform, который используется для транскодирования строк для получения AST
  • babel-loader: используется для компиляции кода JavaScript.
  • @babel/preset-env: Официальное объяснение «Для подготовки следующего поколения JavaScript Compiler», скомпилированном в JavaScript Standard Browser знать
  • @babel/preset-react
  • @babel/plugin-proposal-class-properties: Разобрать свойства класса class

  • @babel/plugin-proposal-decorators: Разобрать синтаксис шаблона декоратора, например @connect, с помощью react-redux.

  • @babel/plugin-proposal-export-default-from: Разобрать экспорт xxx из синтаксиса 'xxx'


.babelrcФайл представляет собой файл конфигурации babel (я настроил его непосредственно в параметрах babel-loader в webpack.config.js. Обратите внимание, что файл .babelrc необходимо преобразовать в формат json, а имя атрибута должно быть двойным) цитируется)


3. Установите ESlint

В корневой каталог проекта установитеeslintа такжеeslint-loader

npm install eslint eslint-loader -D


.eslintrcЭто файл конфигурации eslint, нам нужно увеличить в корне проекта.eslintrcдокумент.

{
  "parser": "babel-eslint",
  "env": {
      "browser": true,
      "es6": true,
      "node": true
  },
  "globals" : {
    "Action"       : false,
    "__DEV__"      : false,
    "__PROD__"     : false,
    "__DEBUG__"    : false,
    "__DEBUG_NEW_WINDOW__" : false,
    "__BASENAME__" : false
  },
  "parserOptions": {
      "ecmaVersion": 6,
      "sourceType": "module"
  },
  "extends": "airbnb",
  "rules": {
      "semi": [0],
      "react/jsx-filename-extension": [0]
  }}

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

webpack.config.js



Обратите внимание, что здесь isEslint использует параметр eslint, передаваемый сценариями npm, чтобы определить, нужно ли текущей среде выполнять проверку формата кода, чтобы у разработчиков было больше возможностей для выбора, а eslint-loader должен быть настроен перед babel-loader, поэтому здесь используйте unshift для добавления эслинт-погрузчик

packack.json

Добавьте следующую команду в файл package.json

{
    "scripts": {
        "eslint": "eslint --ext .js --ext .jsx src/"
    }
}

На этом этапе вы можете определить формат кода в файле src, выполнив npm run eslint.

4. Для установки и упаковки необходимы плагины

npm install webpack-merge yargs-parser clean-webpack-plugin progress-bar-webpack-plugin webpack-build-notifier html-webpack-plugin mini-css-extract-plugin add-asset-html-webpack-plugin uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin friendly-errors-webpack-plugin happypack

  • webpack-merge: Общая конфигурация и конфигурация среды для слияния webpack (слияние webpack.config.js и webpack.development.js или webpack.production.js)
  • yargs-parser: используется для преобразования параметров командной строки в наших npm-скриптах в пары ключ-значение, например --mode development будет разобрано в пары ключ-значение mode: "development", что удобно для получения параметров в файле конфигурации
  • clean-webpack-plugin: Используется для очистки локальных файлов.При упаковке в производственной среде, если папка dist не очищена, то каждая упаковка будет генерировать разные файлы js или css и накапливаться в папке, потому что каждая упаковка будет генерировать разные хэши Значение вызывает имя файла, сгенерированное каждым пакетом, должно отличаться от имени последнего пакета и не будет перезаписывать файлы, оставшиеся от последнего пакета.
  • progress-bar-webpack-plugin: При упаковке и компиляции отслеживайте процесс упаковки в виде индикатора выполнения.
  • webpack-build-notifier: когда вы переключаетесь на другую страницу после упаковки, в локальной системе появляется всплывающее окно, информирующее вас о результате упаковки (успешно, неудачно или с предупреждением) по завершении.
  • html-webpack-plugin: автоматически генерировать html и импортировать упакованные js и css в html-файл по умолчанию.
  • mini-css-extract-plugin: по умолчанию в файле стиля упаковки webpack код файла стиля будет упакован в bundle.js,mini-css-extract-pluginЭтот плагин может извлекать файл стиля из bundle.js в файл и поддерживает фрагмент css.

  • add-asset-html-webpack-plugin: Как видно из названия, его роль заключается во введении статических ресурсов css или js вhtml-webpack-pluginв сгенерированном html файле

  • uglifyjs-webpack-plugin: сокращение кода, используемое для сжатия js (вы можете вызвать поток системы для выполненияМногопоточное сжатие, Оптимизация веб-пакатаСкорость сжатия)

  • optimize-css-assets-webpack-plugin: сжатие css, в основном используетсяcssnanoКомпрессор (среда выполнения webpack4 имеет встроенный cssnano, поэтому его не нужно устанавливать)

  • friendly-errors-webpack-plugin: можно лучше видеть предупреждения и ошибки webapck, работающего в терминале.
  • happypack: Многопоточная компиляция, ускорить компиляцию (ускорить компиляцию загрузчика), обратите внимание, что thread-loader нельзя использовать в сочетании с mini-css-extract-plugin.

  • splitChunks: потомок CommonChunkPlugin, используемый для фрагментирования bundle.js (встроенный плагин webpack).
  • DllPlugin: Модули предварительно скомпилированы, он будет компилироваться в первый раз, нужно будет предварительно сконфигурировать модули, скомпилированные в кеше, второй раз скомпилировать, разрешить эти модули напрямую с помощью кеша, а не компилировать эти модули (встроенные модули webpack )
  • DllReferencePlugin: Свяжите предварительно скомпилированные модули с текущей компиляцией.Когда веб-пакет анализирует эти модули, предварительно скомпилированные модули (встроенные плагины веб-пакета) будут использоваться напрямую.
  • HotModuleReplacementPlugin: реализовать локальную горячую загрузку (обновление), которая отличается от глобального обновления в webpack-dev-server (встроенный плагин webpack).

5. Конфигурация файла, связанная с Webpack

Следующие файлы могут быть использованы непосредственно в вашей копии проекта

webpack.config.js

const path = require('path')
const webpack = require('webpack')
const os = require('os')
const merge = require('webpack-merge')
const argv = require('yargs-parser')(process.argv.slice(2))
const mode = argv.mode || 'development'
const interface = argv.interface || 'development'
const isEslint = !!argv.eslint 
const isDev = mode === 'development'
const mergeConfig = require(`./config/webpack.${mode}.js`)
const CleanWebpackPlugin = require('clean-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const WebpackBuildNotifierPlugin = require('webpack-build-notifier')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin')
const HappyPack = require('happypack')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
const smp = new SpeedMeasurePlugin()
const loading = {  html:"加载中..."}
const apiConfig = {
  development: 'http://xxxxx/a',
  production: 'http://xxx/b'
}
let commonConfig = {
  module: {
    rules: [{
      test: /\.js$/,
      loaders: ['happypack/loader?id=babel'],
      include: path.resolve(__dirname, 'src'),
      exclude: /node_modules/
    },{
      test: /\.css$/,
      loaders: [
        MiniCssExtractPlugin.loader,
        'css-loader'
      ]
    },{
      test: /\.less$/,
      loaders: [
        isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
        'css-loader',
        {
          loader:'less-loader?sourceMap=true',
          options:{
              javascriptEnabled: true
          },
        }
        // include: path.resolve(__dirname, 'src')
      ]
    },{
        test: /\.(png|svg|jpg|gif)$/,
        use: [
            'url-loader'
        ]
    },{
        test: /\.(woff|woff2|eot|ttf|otf|ico)$/,
        use: [
            'file-loader'
        ]
    },{
        test: /\.(csv|tsv)$/,
        use: [
            'csv-loader'
        ]
    },{
        test: /\.xml$/,
        use: [
            'xml-loader'
        ]
    },{
        test: /\.md$/,
        use: [
            "html-loader",
             "markdown-loader"
        ]
    }]
  },
  //解析  resolve: {
      extensions: ['.js', '.jsx'], // 自动解析确定的扩展
  },
  plugins: [
    new HappyPack({
      id: 'babel',
      loaders: [{
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,
          presets: ['@babel/preset-env', '@babel/preset-react'],
          plugins: [
            ['@babel/plugin-proposal-decorators', { "legacy": true }],
            '@babel/plugin-proposal-class-properties',
            '@babel/plugin-proposal-export-default-from',
            '@babel/plugin-transform-runtime',
            // 'react-hot-loader/babel',
            // 'dynamic-import-webpack',
            ['import',{
              libraryName:'antd',
              libraryDirectory: 'es',
              style:true
            }]
          ]
        }
      }],
      //共享进程池
      threadPool: happyThreadPool,
      //允许 HappyPack 输出日志
      verbose: true,
    }),
    new CleanWebpackPlugin(['dist']),
    new ProgressBarPlugin(),
    new WebpackBuildNotifierPlugin({
      title: "xxx后台管理系统🍎",
      logo: path.resolve(__dirname, "src/static/favicon.ico"),
      suppressSuccess: true
    }),
    new webpack.DefinePlugin({
      'process.env'  : {
        'NODE_ENV' : JSON.stringify(mode)
      },
      'NODE_ENV'     : JSON.stringify(mode),
      'baseUrl': JSON.stringify(apiConfig[interface]),
      '__DEV__'      : mode === 'development',
      '__PROD__'     : mode === 'production',
      '__TEST__'     : mode === 'test',
      '__DEBUG__'    : mode === 'development' && !argv.no_debug,
      '__DEBUG_NEW_WINDOW__' : !!argv.nw,
      '__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
    }),
    new FirendlyErrorePlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'public/index.html'),
      favicon: path.resolve(__dirname, 'public/favicon.ico'),
      filename: 'index.html',
      loading
    }),
    new MiniCssExtractPlugin({
      filename: isDev ? 'styles/[name].[hash:4].css' : 'styles/[name].[hash:8].css',
      chunkFilename:isDev ? 'styles/[name].[hash:4].css' : 'styles/[name].[hash:8].css'
    }),
    // 告诉 Webpack 使用了哪些动态链接库
    new webpack.DllReferencePlugin({
      // 描述 vendor 动态链接库的文件内容
      manifest: require('./public/vendor/vendor.manifest.json')
    }),
    // 该插件将把给定的 JS 或 CSS 文件添加到 webpack 配置的文件中,并将其放入资源列表 html webpack插件注入到生成的 html 中。
    new AddAssetHtmlPlugin([
        {
            // 要添加到编译中的文件的绝对路径,以及生成的HTML文件。支持 globby 字符串
            filepath: require.resolve(path.resolve(__dirname, 'public/vendor/vendor.dll.js')),
            // 文件输出目录
            outputPath: 'vendor',
            // 脚本或链接标记的公共路径
            publicPath: 'vendor'
        }
    ]),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    host: 'localhost',
    port: 8080,
    historyApiFallback: true,
    overlay: {//当出现编译器错误或警告时,就在网页上显示一层黑色的背景层和错误信息
      errors: true
    },
    inline: true,
    open: true,
    hot: true
  },
  performance: {
    // false | "error" | "warning" // 不显示性能提示 | 以错误形式提示 | 以警告...
    hints: false,    // 开发环境设置较大防止警告
    // 根据入口起点的最大体积,控制webpack何时生成性能提示,整数类型,以字节为单位
    maxEntrypointSize: 50000000,
    // 最大单个资源体积,默认250000 (bytes)
    maxAssetSize: 30000000
  }
}
if (isEslint) {
    commonConfig.module.rules.unshift[{
        //前置(在执行编译之前去执行eslint-loader检查代码规范,有报错就不执行编译)
        enforce: 'pre',
        test: /.(js|jsx)$/,
        loaders: ['eslint-loader'],
        exclude: /node_modules/
    }]
}
module.exports = merge(commonConfig, mergeConfig)

Примечание: Плагин speed-measure-webpack-plugin не используется при окончательном экспорте конфигурации, потому что будет сообщено об ошибке Я не знаю, может ли это быть из-за несовместимости с happypack. interface используется для определения адреса текущего упакованного сетевого запроса js, isEslint определяет, нужно ли выполнять обнаружение кода, isDev используется для определения того, является ли текущая среда выполнения разработкой или производством, а конкретная проблема зависит от кода


webpack.config.dll.js

const path = require('path');
const webpack = require('webpack');
const CleanWebpaclPlugin = require('clean-webpack-plugin');
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin');
module.exports = {
    mode: 'production',
    entry: {
        // 将 lodash 模块作为入口编译成动态链接库
        vendor: ['react', 'react-dom', 'react-router', 'react-redux', 'react-router-redux']
    },
    output: {
        // 指定生成文件所在目录
        // 由于每次打包生产环境时会清空 dist 文件夹,因此这里我将它们存放在了 public 文件夹下
        path: path.resolve(__dirname, 'public/vendor'),
        // 指定文件名
        filename: '[name].dll.js',
        // 存放动态链接库的全局变量名称,例如对应 vendor 来说就是 vendor_dll_lib        // 这个名称需要与 DllPlugin 插件中的 name 属性值对应起来
        // 之所以在前面 _dll_lib 是为了防止全局变量冲突
        library: '[name]_dll_lib'
    },
    plugins: [
        new CleanWebpaclPlugin(['vendor'], {
            root: path.resolve(__dirname, 'public')
        }),
        new FirendlyErrorePlugin(),                // 接入 DllPlugin
        new webpack.DllPlugin({
            // 描述动态链接库的 manifest.json 文件输出时的文件名称
            // 由于每次打包生产环境时会清空 dist 文件夹,因此这里我将它们存放在了 public 文件夹下
            path: path.join(__dirname, 'public', 'vendor', '[name].manifest.json'),
            // 动态链接库的全局变量名称,需要和 output.library 中保持一致
            // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
            // 例如 vendor.manifest.json 中就有 "name": "vendor_dll_lib"            name: '[name]_dll_lib'
        })
    ],
    performance: {
        // false | "error" | "warning" // 不显示性能提示 | 以错误形式提示 | 以警告...
        hints: "warning",        // 开发环境设置较大防止警告
        // 根据入口起点的最大体积,控制webpack何时生成性能提示,整数类型,以字节为单位
        maxEntrypointSize: 5000000,         // 最大单个资源体积,默认250000 (bytes)
        maxAssetSize: 3000000
    }}

бегатьnpm run dllПосле инструкции видно, что в публичном каталоге проекта есть дополнительная папка vendor, и видно, что в ней два файла:

  • vendor.dll.jsсодержитreact react-dom react-router react-redux react-router-reduxБазовая операционная среда , поместите эти основные модули в пакет, пока версия пакета этих пакетов не будет обновлена, вам не нужно перекомпилировать эти модули каждый раз, когда вы упаковываете, повышая скорость упаковки.
  • vendor.manifest.jsonОн также генерируется DllPlugin для описания того, какие модули включены в файл библиотеки динамической компоновки.

config/webpack.development.js

module.exports = {
  mode: 'development',
  //devtool: 'cheap-module-source-map',
  devtool: 'eval',
  output: {
    filename: 'scripts/[name].bundle.[hash:4].js'
  }
}

В среде разработки мы не делаем сжатие js и сжатие css для повышения скорости отладки и сохранения упаковки страниц в среде разработки


config/webpack.production.js

const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); //开启多核压缩
const OptmizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const os = require('os');
module.exports = {
  mode: 'production',
  devtool: 'hidden-source-map',
  output: {
    filename: 'scripts/[name].bundle.[hash:8].js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',   // initial、async和all
      minSize: 30000,   // 形成一个新代码块最小的体积
      maxAsyncRequests: 5,   // 按需加载时候最大的并行请求数
      maxInitialRequests: 3,   // 最大初始化请求数
      automaticNameDelimiter: '~',   // 打包分割符
      name: true,
      cacheGroups: {
        vendors: { // 项目基本框架等
          chunks: 'all',
          test: /antd/,
          priority: 100,
          name: 'vendors',
        }
      }
    },
    minimizer: [
      new UglifyJsPlugin({
        parallel: os.cpus().length,
        cache:true,
        sourceMap:true,
        uglifyOptions: {
          compress: {
              // 在UglifyJs删除没有用到的代码时不输出警告
              warnings: false,
              // 删除所有的 `console` 语句,可以兼容ie浏览器
              drop_console: true,
              // 内嵌定义了但是只用到一次的变量
              collapse_vars: true,
              // 提取出出现多次但是没有定义成变量去引用的静态值
              reduce_vars: true,
          },
          output: {
              // 最紧凑的输出
              beautify: false,
              // 删除所有的注释
              comments: false,
          }
        }
      }),
      new OptmizeCssAssetsWebpackPlugin({
        assetNameRegExp: /\.css$/g,
        cssProcessor: require('cssnano'),
        cssProcessorOptions: {
           safe: true,
           discardComments: {
             removeAll: true
          }
        }
      })
    ],
  }}

В конфигурации производственной среды выполняется сжатие js и сжатие css, а antd отделяется от упакованного файла записи с помощью splitChunks для уменьшения размера bundle.js.


public/index.html

<!DOCTYPE html>
<html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
    </body>
</html>

package.json



6. Пакетные приложения

1. Выполните npm run dll, чтобы сгенерировать общедоступный/поставщик (вам не нужно выполнять эту команду после упаковки, если версия пакета в поставщике не изменилась)


2. Выполните npm run start:dev для автоматического запуска локального сервера webpack-dev.


3. Выполните npm run deploy, чтобы упаковать производственную среду.


4. Сравнение и анализ времени упаковки


Используйте метод разделенного кода для асинхронной загрузки компонентов для оптимизации объема, см.«Webpack загружает приложение за считанные секунды по требованию»(самый важный шаг)