Используйте WebPack4 для улучшения скорости компиляции на 180%

Webpack

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

Итак, что снижает эффективность упаковки webpack и что мы можем сделать, чтобы улучшить ее?

webpack в настоящее время является очень популярным инструментом для создания пакетов.По состоянию на 6 дней назад webpack4 был обновлен до4.28.3версии, за 10 месяцев минорная версия обновлялась десятки раз, что свидетельствует о процветании сообщества.

Когда был выпущен webpack4, официальные лица также заявили, что скорость его компиляции была увеличена с 60% до 98%.

интерфейс

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

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

Ниже приведены скриншоты пятикратного запуска в двух сценариях: webpack@3.12.0 и webpack@4.26.1.

Анализ данных выглядит следующим образом (единицы мс):

1-й 2-й 3-й раз 4-й 5-й средний повышение скорости
webpack3 58293 60971 57263 58993 60459 59195.8 -
webpack4 42346 40386 40138 40330 40323 40704.6 45%

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

В любом случае, улучшение скорости компиляции почти на 50% стоит того, чтобы попробовать обновить webpack4! Конечно, оптимизация только начинается, так что читайте дальше.

новые особенности

Чтобы обновление webpack4 было более плавным, мы должны сначала понять его.

Хотя webpack4 значительно повышает эффективность компиляции, он вводит множество новых функций:

  1. Вдохновленный Parcel, поддерживает 0 проектов запуска конфигурации, больше не требует файла конфигурации webpack.config.js, запись по умолчанию./src/каталог, запись по умолчанию./src/index.js, вывод по умолчанию./distкаталог, выходной файл по умолчанию./dist/main.js.

  2. Готовый WebAssembly, webpack4 обеспечивает поддержку wasm, и теперь вы можете импортировать и экспортировать любой модуль WebAssembly, или вы можете написать загрузчик для импорта C++, C и Rust. (Примечание: модули WebAssembly можно использовать только в асинхронных фрагментах)

  3. Предоставьте атрибут режима, установленный вdevelopmentполучит лучший опыт разработки, настроенный наproductionСосредоточимся на компиляции и развертывании проекта, например, на включении функций Scope hoisting и Tree-shaking.

  4. Совершенно новая система плагинов, которая предоставляет новые API для плагинов и хуков. Изменения заключаются в следующем:

    • Все хуки единообразно управляются объектом hooks, который рассматривает все хуки как расширяемые свойства класса.
    • При добавлении плагина необходимо указать имя
    • При разработке плагина вы можете выбрать тип плагина (один из sync/callback/promise)
    • Зарегистрируйте хуки с помощью this.hooks = { myHook: new SyncHook(…) }

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

Садись в машину, подготовься к обновлению

Во-первых, необходимо обновить до последней версии плагин webpack-dev-server, в то же время, поскольку webpack-cli берет на себя функции, связанные с командной строкой webpack4, требуется и webpack-cli.

Отличие от прошлого в том, что обязательно должен быть указан атрибут режима, в противном случае согласно约定优于配置принципе, по умолчанию будетproductionРабочая среда скомпилирована, и ниже приведен исходный текст предупреждения.

WARNING in configuration
Параметр «режим» не был установлен, для этого значения веб-пакет будет использовать «производство». Установите для параметра «режим» значение «разработка» или «производство», чтобы включить значения по умолчанию для каждой среды.
Вы также можете установить значение «Нет», чтобы отключить любое поведение по умолчанию.Подробнее:Веб-пакет. Просто .org/concepts/mo…

Есть два способа присоединиться к конфигурации режима.

  • Укажите --mode в скрипте package.json:

    "scripts": {
        "dev": "webpack-dev-server --mode development --inline --progress --config build/webpack.dev.config.js",
        "build": "webpack --mode production --progress --config build/webpack.prod.config.js"
    }
    
    • Добавьте атрибут режима в файл конфигурации

      module.exports = {
        mode: 'production' // 或 development
      };
      

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

      • CommonsChunkPlugin устарел, его заменили наOptimise.splitChunks и Оптимизация.runtimeChunk, первый разделяет код, второй извлекает код времени выполнения. Когда исходный CommonsChunkPlugin будет генерировать модули, он будет содержать дублирующийся код и не сможет оптимизировать асинхронные модули. Настройка minchunk также сложна. SplitChunks решает эту проблему, кроме того, установите для option.runtimeChunk значение true (или {name: "manifest" }) , часть времени выполнения в модуле ввода может быть извлечена.
      • Плагин NoEmitOnErrorsPlugin устарел и заменен на оптимизацию.noEmitOnErrors, которая включена по умолчанию в рабочей среде.
      • Плагин NamedModulesPlugin устарел и заменен на оптимизацию.namedModules, которая включена по умолчанию в рабочей среде.
      • ModuleConcatenationPlugin устарел, его заменил оптимизация.concatenateModules, которая включена по умолчанию в рабочей среде.
      • optimize.UglifyJsPlugin устарел и заменен на optimize.minimize, который включен по умолчанию в производственной среде.

      Мало того, оптимизация также предоставляет следующую конфигурацию по умолчанию:

      optimization: {
          minimize: env === 'production' ? true : false, // 开发环境不压缩
          splitChunks: {
              chunks: "async", // 共有三个值可选:initial(初始模块)、async(按需加载模块)和all(全部模块)
              minSize: 30000, // 模块超过30k自动被抽离成公共模块
              minChunks: 1, // 模块被引用>=1次,便分割
              maxAsyncRequests: 5,  // 异步加载chunk的并发请求数量<=5
              maxInitialRequests: 3, // 一个入口并发加载的chunk数量<=3
              name: true, // 默认由模块名+hash命名,名称相同时多个模块将合并为1个,可以设置为function
              automaticNameDelimiter: '~', // 命名分隔符
              cacheGroups: { // 缓存组,会继承和覆盖splitChunks的配置
                  default: { // 模块缓存规则,设置为false,默认缓存组将禁用
                      minChunks: 2, // 模块被引用>=2次,拆分至vendors公共模块
                      priority: -20, // 优先级
                      reuseExistingChunk: true, // 默认使用已有的模块
                  },
                  vendors: {
                      test: /[\\/]node_modules[\\/]/, // 表示默认拆分node_modules中的模块
                      priority: -10
                  }
              }
          }
      }
      

        splitChunks находится в центре внимания оптимизации распаковки.Если ваш проект содержит сторонние компоненты, такие как element-ui (компоненты большие), рекомендуется распаковывать их отдельно, как показано ниже.

        splitChunks: {
            // ...
            cacheGroups: {    
                elementUI: {
                    name: "chunk-elementUI", // 单独将 elementUI 拆包
                    priority: 15, // 权重需大于其它缓存组
                    test: /[\/]node_modules[\/]element-ui[\/]/
                }
            }
        }
        

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

          Обновление руководства по предотвращению ямы

          Webpack4 больше не поддерживает Node 4. Из-за нового синтаксиса JavaScript Тобиас, один из основателей Webpack, рекомендует пользователям использовать версию Node >= 8.94, чтобы обеспечить максимальную производительность.

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

          vue-loader v15 необходимо добавить плагин VueLoaderPlugin в webpack, см. следующее.

          const { VueLoaderPlugin } = require("vue-loader"); // const VueLoaderPlugin = require("vue-loader/lib/plugin"); // 两者等同
          
          //...
          plugins: [
            new VueLoaderPlugin()
          ]
          

            После обновления до webpack4 плагин mini-css-extract-plugin заменяет extract-text-webpack-plugin и становится первым выбором для упаковки css.По сравнению с предыдущим он имеет следующие преимущества:

            1. Асинхронная загрузка
            2. Нет повторной компиляции, лучшая производительность
            3. проще в использовании

            Дефект, не поддерживает горячее обновление css. Поэтому необходимо внедрить css-hot-loader в среду разработки для поддержки горячего обновления CSS, как показано ниже:

            {
                test: /\.scss$/,
                use: [
                    ...(isDev ? ["css-hot-loader", "style-loader"] : [MiniCssExtractPlugin.loader]),
                    "css-loader",
                    postcss,
                    "sass-loader"
                ]
            }
            

              Перед публикацией в производственной среде необходимо оптимизировать и сжать CSS.

              const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
              
              //...
              plugins: [
                  new OptimizeCssAssetsPlugin({
                      cssProcessor: cssnano,
                      cssProcessorOptions: {
                          discardComments: {
                              removeAll: true
                          }
                      }
                  })
              ]
              

                продолжай ускоряться

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

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

                1. Уменьшите объем компиляции и сократите ненужную работу по компиляции, то есть используются модули, mainFields, noParse, include, exclude, alias.

                  const resolve = dir => path.join(__dirname, '..', dir);
                  
                  // ...
                  resolve: {
                      modules: [ // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索
                          resolve('src'),
                          resolve('node_modules'),
                          resolve(config.common.layoutPath)
                      ],
                      mainFields: ['main'], // 只采用main字段作为入口文件描述字段,减少搜索步骤
                      alias: {
                          vue$: "vue/dist/vue.common",
                          "@": resolve("src") // 缓存src目录为@符号,避免重复寻址
                      }
                  },
                  module: {
                      noParse: /jquery|lodash/, // 忽略未采用模块化的文件,因此jquery或lodash将不会被下面的loaders解析
                      // noParse: function(content) {
                      //     return /jquery|lodash/.test(content)
                      // },
                      rules: [
                          {
                              test: /\.js$/,
                              include: [ // 表示只解析以下目录,减少loader处理范围
                                  resolve("src"),
                                  resolve(config.common.layoutPath)
                              ],
                              exclude: file => /test/.test(file), // 排除test目录文件
                              loader: "happypack/loader?id=happy-babel" // 后面会介绍
                          },
                      ]
                  }
                  
                  • Если вы хотите еще больше повысить скорость компиляции, вы должны знать, где находится узкое место? В ходе тестирования было обнаружено, что существует два медленных этапа: ① этап разбора babel и других загрузчиков; ② этап сжатия js. Разбор загрузчика будет рассмотрен позже, а js-сжатие является завершающим этапом публикации и компиляции.Обычно webpack нужно застопорить на некоторое время, потому что для сжатия JS нужно разобрать код в синтаксическое дерево AST, а затем проанализировать и обработать его в соответствии к сложным правилам AST и, наконец, восстановить AST к JS.Этот процесс включает в себя много вычислений, поэтому он занимает много времени. Как показано ниже, компиляция застряла.

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

                    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
                    
                    // ...
                    optimization: {
                        minimizer: [
                            new ParallelUglifyPlugin({ // 多进程压缩
                                cacheDir: '.cache/',
                                uglifyJS: {
                                    output: {
                                        comments: false,
                                        beautify: false
                                    },
                                    compress: {
                                        warnings: false,
                                        drop_console: true,
                                        collapse_vars: true,
                                        reduce_vars: true
                                    }
                                }
                            }),
                        ]
                    }
                    

                      Конечно, я протестировал пять наборов данных по отдельности, а именно:

                      Анализ данных выглядит следующим образом (единицы мс):

                    1-й 2-й 3-й раз 4-й 5-й средний повышение скорости
                    webpack3 58293 60971 57263 58993 60459 59195.8 -
                    webpack3 оснащен плагином ParallelUglifyPlugin 44380 39969 39694 39344 39295 40536.4 46%
                    webpack4 42346 40386 40138 40330 40323 40704.6 -
                    webpack4 оснащен плагином ParallelUglifyPlugin 31134 29554 31883 29198 29072 30168.2 35%

                    После установки плагина webpack-parallel-uglify-plugin скорость сборки webpack3 может быть увеличена на 46%; даже после обновления до webpack4 скорость сборки все еще может быть увеличена на 35%.

                    1. Теперь давайте посмотрим, как можно улучшить скорость парсинга загрузчика. Как и плагин webpack-parallel-uglify-plugin, HappyPack также может выполнять параллельную компиляцию, что может значительно повысить скорость синтаксического анализа загрузчика.Ниже приведены некоторые конфигурации.

                      const HappyPack = require('happypack');
                      const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
                      const createHappyPlugin = (id, loaders) => new HappyPack({
                          id: id,
                          loaders: loaders,
                          threadPool: happyThreadPool,
                          verbose: process.env.HAPPY_VERBOSE === '1' // make happy more verbose with HAPPY_VERBOSE=1
                      });
                      

                        Затем для предыдущегоloader: "happypack/loader?id=happy-babel"Это предложение, вам нужно создать плагин в pluginshappy-babelэкземпляр плагина.

                        plugins: [
                            createHappyPlugin('happy-babel', [{
                                loader: 'babel-loader',
                                options: {
                                    babelrc: true,
                                    cacheDirectory: true // 启用缓存
                                }
                            }])
                        ]
                        

                          Таким образом, happyPack открывает 3 процесса (по умолчанию количество ЦП - 1), и вы можете почувствовать запущенный процесс.

                          Кроме того, как и vue-loader, css-loader поддерживает ускорение happyPack, как показано ниже.

                          plugins: [
                              createHappyPlugin('happy-css', ['css-loader', 'vue-style-loader']),
                              new HappyPack({
                                  loaders: [{
                                      path: 'vue-loader',
                                      query: {
                                          loaders: {
                                              scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax'
                                          }
                                      }
                                  }]
                              })
                          ]
                          

                            На основе webpack4, оснащенного плагинами webpack-parallel-uglify-plugin и happyPack, тестовые скриншоты выглядят следующим образом:

                            Анализ данных выглядит следующим образом (единицы мс):

                          1-й 2-й 3-й раз 4-й 5-й средний повышение скорости
                          Поставляется только с ParallelUglifyPlugin 31134 29554 31883 29198 29072 30168.2 35%
                          Работает на ParallelUglifyPlugin и happyPack 26036 25884 25645 25627 25794 25797.2 17%

                          Видно, что на основе плагина webpack-parallel-uglify-plugin плагин happyPack по-прежнему может улучшить скорость компиляции на 17%. Фактически, поскольку загрузчики, такие как sass, не поддерживают happyPack, производительность happyPack по-прежнему есть возможности для улучшения. Для получения дополнительной информации см.Анализ принципа happypack.

                          1. Все мы знаем, что когда webpack упакован, некоторые коды фреймворка практически не изменяются, например, babel-polyfill, vue, vue-router, vuex, axios, element-ui, fastclick и т. д. Эти модули также имеют большой размер. приходится заново загружать, что занимает много времени и сил. Эти модули могут быть предварительно упакованы с помощью подключаемых модулей DLLPlugin и DLLReferencePlugin.

                            Чтобы завершить процесс dll, нам нужно подготовить новую конфигурацию веб-пакета, webpack.dll.config.js.

                            const webpack = require("webpack");
                            const path = require('path');
                            const CleanWebpackPlugin = require("clean-webpack-plugin");
                            const dllPath = path.resolve(__dirname, "../src/assets/dll"); // dll文件存放的目录
                            
                            module.exports = {
                                entry: {
                                    // 把 vue 相关模块的放到一个单独的动态链接库
                                    vue: ["babel-polyfill", "fastclick", "vue", "vue-router", "vuex", "axios", "element-ui"]
                                },
                                output: {
                                    filename: "[name]-[hash].dll.js", // 生成vue.dll.js
                                    path: dllPath,
                                    library: "_dll_[name]"
                                },
                                plugins: [
                                    new CleanWebpackPlugin(["*.js"], { // 清除之前的dll文件
                                        root: dllPath,
                                    }),
                                    new webpack.DllPlugin({
                                        name: "_dll_[name]",
                                        // manifest.json 描述动态链接库包含了哪些内容
                                        path: path.join(__dirname, "./", "[name].dll.manifest.json")
                                    }),
                                ],
                            };
                            

                              Далее вам нужно добавить команду dll в package.json.

                              "scripts": {
                                  "dll": "webpack --mode production --config build/webpack.dll.config.js"
                              }
                              
                              • 1
                              • 2
                              • 3

                              бегатьnpm run dll, будет генерировать./src/assets/dll/vue.dll-[hash].jsпубличный js и./build/vue.dll.manifest.jsonФайл описания ресурсов, на этом подготовка dll завершена, а затем на него можно ссылаться в wepack.

                              externals: {
                                  'vue': 'Vue',
                                  'vue-router': 'VueRouter',
                                  'vuex': 'vuex',
                                  'elemenct-ui': 'ELEMENT',
                                  'axios': 'axios',
                                  'fastclick': 'FastClick'
                              },
                              plugins: [
                                  ...(config.common.needDll ? [
                                      new webpack.DllReferencePlugin({
                                          manifest: require("./vue.dll.manifest.json")
                                      })
                                  ] : [])
                              ]
                              

                                Общедоступная dll js не будет легко меняться.Если в будущем будет обновление, то новое имя файла dll необходимо добавить с новым хэшем, чтобы браузер не кэшировал старый файл и не вызывал ошибок выполнения. Из-за неопределенности хэша мы не можем указать сценарий фиксированной ссылки в html-файле записи, просто плагин add-asset-html-webpack-plugin может помочь нам автоматически импортировать dll-файлы.

                                const autoAddDllRes = () => {
                                    const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
                                    return new AddAssetHtmlPlugin([{ // 往html中注入dll js
                                        publicPath: config.common.publicPath + "dll/",  // 注入到html中的路径
                                        outputPath: "dll", // 最终输出的目录
                                        filepath: resolve("src/assets/dll/*.js"),
                                        includeSourcemap: false,
                                        typeOfAsset: "js" // options js、css; default js
                                    }]);
                                };
                                
                                // ...
                                plugins: [
                                    ...(config.common.needDll ? [autoAddDllRes()] : [])
                                ]
                                

                                  После установки плагина dll скорость компиляции webpack4 еще больше повышается, как показано на следующем снимке экрана:

                                  Анализ данных выглядит следующим образом (единицы мс):

                                1-й 2-й 3-й раз 4-й 5-й средний повышение скорости
                                Работает на ParallelUglifyPlugin и happyPack 26036 25884 25645 25627 25794 25797.2 17%
                                Оснащен ParallelUglifyPlugin, happyPack и dll 20792 20963 20845 21675 21023 21059.6 22%

                                Видно, что после установки dll скорость компиляции webpack4 еще можно улучшить на 22%.

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

                                1-й 2-й 3-й раз 4-й 5-й средний повышение скорости
                                webpack3 58293 60971 57263 58993 60459 59195.8 -
                                webpack4 42346 40386 40138 40330 40323 40704.6 45%
                                Оснащен ParallelUglifyPlugin, happyPack и dll 20792 20963 20845 21675 21023 21059.6 181%

                                После обновления до webpack4 с помощью плагинов ParallelUglifyPlugin, happyPack и dll скорость компиляции может быть увеличена на 181%, общее время компиляции сокращается почти на 2/3, что экономит много времени компиляции для разработки! И по мере развития проекта это улучшение компиляции становится все более и более впечатляющим.

                                Фактически, чтобы получить вышеупомянутые тестовые данные, я отключил кеш Babel и ParalleluglifyPluglugin. После открытия кэша среднее время второй компиляции составляет 12,8. Так как он был кэширован ранее, скорость компиляции будет Увеличено на 362% по сравнению с WebPack3, даже если у вас после обновления до WebPack4 и устанавливая вышеуказанные три плагина, скорость компиляции все еще может быть улучшена на 218%!

                                Анализ результатов компиляции

                                Конечно, как показатель, скорость компиляции больше влияет на опыт разработчика, по сравнению со скоростью компиляции важнее размер файла после компиляции. Файлы, скомпилированные webpack4, немного меньше, чем в предыдущей версии.Чтобы лучше отслеживать изменения размера файлов, как в среде разработки, так и в производственной среде необходимо ввести подключаемый модуль webpack-bundle-analyzer, как показано на рисунке ниже.

                                Размер файла показан на следующем рисунке:

                                ориентированный на встряхивание деревьев, кодирование с ограничением

                                sideEffects

                                Начиная с webpack2, древовидная тряска использовалась для устранения бесполезных модулей, полагаясь на статическую структуру модуля ES и устанавливая его в файле .babelrc."modules": falseЧтобы включить бесполезное обнаружение модуля, это относительно грубо. webapck4 гибко расширяет метод обнаружения бесполезного кода, в основном за счетpackage.jsonустановить в файлsideEffects: falseЧтобы сообщить компилятору, что проект или модуль чистый, можно выполнить бесполезное удаление модуля, поэтому при разработке общих компонентов можно попробовать установить его.

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

                                import { Button, Input } from "element-ui"; // 只引入需要的组件
                                

                                  конец

                                  В процессе обновления webpack4 необходимо наступить на яму.Ключ что можно получить наступив на яму?

                                  Кроме того, в дополнение к некоторым методам оптимизации, представленным в статье, постепенно проверяются и другие стратегии оптимизации...