Webpack4 в vue-cli заполняет яму за один шаг

Командная строка JavaScript Vue.js Webpack

Основное введение

WebpackОн также постоянно оптимизируется и дорабатывается, на данный момент он обновлен до версии 4.16.0;Webpack4В этой версии, на основе оригинала, сделано много оптимизаций, а также введено много новых функций. В новой версии будет больше типов модулей и.mjsПоддержка, лучшее значение по умолчанию, более краткие настройки режима, более интеллектуальное разделениеChunkДобавить новоеsplitChunksнастроить разделенные блоки кода и многое другое. Обновление до новой версииWebpackпроект, в пакете构建速度,代码块体积&数量,так же как运行效率, будет качественный скачок.

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

процесс обновления webpack4.0

среда узла

больше не поддерживаетсяNode4, Рекомендуемые старшие версииnode, для обновления используется следующееnode v8.11.1а такжеnpm v5.6.0

тип модуля

До веб-пакета 4 js был единственным типом модуля в веб-пакете и, следовательно, не мог эффективно упаковывать другие типы файлов. И webpack 4 предоставляет 5 типов модулей:

  • javascript/auto: (тип по умолчанию в webpack 3) поддерживает все модульные системы JS: CommonJS, AMD, ESM
  • javascript/esm: модули EcmaScript, недоступные в других модульных системах (файл .mjs по умолчанию)
  • javascript/динамический: поддерживает только CommonJS и AMD, модули EcmaScript недоступны
  • json: данные в формате JSON, которые можно импортировать с помощью require и import (по умолчанию файлы .json).
  • webassembly/experimental: модули WebAssembly (экспериментальные, по умолчанию файлы .wasm)

Кроме того, webpack 4 по умолчанию будет анализировать файлы с суффиксами .wasm, .mjs, .js и .json.

webpack-cli

После обновленияwebpack4Затем запустите команду упаковки проекта напрямуюnpm run build, вам будет предложено установитьwebpack-cli/webpack-command, вы можете выбрать установку в соответствии с вашими потребностями, я выбираюwebpack-cli.

Обновление конфигурации

режим добавить

webpack4По умолчаниюmodeустановить это生产环境еще开发环境, так и должно бытьwebpack.dev.conf.jsа такжеwebpack.prod.conf.jsувеличить соответствующийmodeЭлементы конфигурации и удалите код, устанавливающий переменную среды передprocess.env.NODE_ENV = 'production'А способ установки переменных окружения в конфигурации плагина, код фрагмента следующий:

// webpack.dev.conf.js
module.exports = merge(baseWebpackConfig, {
  mode: 'development',
  // 省略
  plugins: [
    new webpack.DefinePlugin({
        'process.env': config.dev.env
    }),
  ]
} 

// webpack.prod.conf.js
var webpackConfig = merge(baseWebpackConfig, {
  mode: 'production',
  // 省略
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env
    }),
  ]  
}  

Примечание: новый webpack.DefinePlugin гарантированно доступен в сценариях браузера.process.envпеременные для выполнения соответствующих логических операций

режим разработки:

  • 1. В основном оптимизирована инкрементная скорость сборки и опыт разработки.
  • 2. Значение process.env.NODE_ENV не нужно определять заново, по умолчанию это development
  • 3. Комментарии и подсказки поддерживаются в режиме разработки, а исходные карты под eval поддерживаются

режим производства:

  • 1. Многие оптимизации кода (минимизация, разделение и т. д.) включены по умолчанию в рабочей среде.
  • 2. Включите просмотр и проверку во время разработки и автоматически добавьте eval devtool
  • 3. Рабочая среда не поддерживает просмотр, а среда разработки оптимизирует скорость переупаковки
  • 4. Подъем области действия и встряхивание деревьев включены по умолчанию (исходный плагин ModuleConcatenation).
  • 5. Автоматически устанавливать process.env.NODE_ENV для разных окружений, то есть не нужно, чтобы DefinePlugin делал это
  • 6. Если вы установите для режима значение none, все конфигурации по умолчанию будут удалены.
  • 7. Если вы не добавите эту конфигурацию, веб-пакет напомнит вам, поэтому добавьте ее.

использование плагина mini-css-extract

потому чтоextract-text-webpack-pluginПоследняя официальная версия еще не поддерживает webpack4.x, даже используяextract-text-webpack-plugin@nextверсия все равно появитсяcontenthashошибка, поэтому рекомендуется использоватьmini-css-extract-plugin, разумеется, это тоже официально рекомендуется.

Главное надо доработатьwebpack.prod.conf.jsконфигурация плагина в иloadersЗагруженная служебная функцияutils.js, измените код фрагмента следующим образом:

// webpack.dev.conf.js
module.exports = merge(baseWebpackConfig, {
  // 省略
  plugins: [
    new MiniCssExtractPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css')
    }),
  ]
} 

// utils.js
if (options.extract) {
return [
  {
    loader: MiniCssExtractPlugin.loader,
    options: {
      publicPath: '../../'
    }
  }
  ].concat(loaders)
} else {
  return ['vue-style-loader'].concat(loaders)
}

Примечание: гдеutils.jsСредняя конфигурацияpublicPathВ основном решить проблему ошибки пути в ссылочных изображениях в css.

элемент конфигурации оптимизации

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

Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.
    at Object.get [as CommonsChunkPlugin] (/data/test/node_modules/webpack/lib/webpack.js:159:10)

В основном потому, чтоwebpack4Удаленоwebpack.optimize.CommonsChunkPluginи использоватьoptimizationсерединаsplitChunkзаменить

Главное надо доработатьwebpack.prod.conf.jsфайл и удалить всеwebpack.optimize.CommonsChunkPluginСоответствующий код, код фрагмента выглядит следующим образом:

var webpackConfig = merge(baseWebpackConfig, {
  mode: 'production',
  entry: {
    charts: ['echarts'],
    vendors: ['vue', 'vuex', 'vue-router', 'moment'],
    iconfonts: ['ga-iconfont']
  },
  // 省略
  optimization: {
    // minimizer: true, // [new UglifyJsPlugin({...})]
    providedExports: true,
    usedExports: true,
    //识别package.json中的sideEffects以剔除无用的模块,用来做tree-shake
    //依赖于optimization.providedExports和optimization.usedExports
    sideEffects: true,
    //取代 new webpack.optimize.ModuleConcatenationPlugin()
    concatenateModules: true,
    //取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
    noEmitOnErrors: true,
    splitChunks: {
      // maxAsyncRequests: 1,                     // 最大异步请求数, 默认1
      // maxInitialRequests: 1,                   // 最大初始化请求数,默认1
      cacheGroups: {
        // 抽离第三方插件
        commons: {
          // test: path.resolve(__dirname, '../node_modules'),
          chunks: 'all',
          minChunks: 2,
          maxInitialRequests: 5, // The default limit is too small to showcase the effect
          minSize: 0, // This is example is too small to create commons chunks
          name: 'common'
        }
      }
    },
}    

testВ основном за счет регулярного сопоставленияentryНастроить сторонние библиотеки в, конечно, можно и сюда написатьpath.resolve(__dirname, '../node_modules')соответствовать проектуnode_modulesИмпортированные файлы библиотеки.

chunksФорма имеет три значения (если настроеноentry, то извлекается из файла входа по умолчанию, если не настроеноentryнастроенtest, по умолчанию согласноtestРегулярное сопоставление в ) рекомендуется для личного сравненияallилиasync:

  • когда значениеallКогда эффект заключается в том, является ли он асинхронным или синхронным, запись будетentryПубличная часть настроенного пакета извлекается, преимущество в том, что другие файлы очень маленькие, а публичные файлы будут загружены только один раз.enrtyСлишком много сконфигурированных пакетов может привести к большому размеру файла. Эффект в основном следующий:

  • когда значениеasync, эффект в том, что входentryНастроенный пакет извлекается из асинхронной общедоступной части, в основном для просмотраentryЯвляется ли внедрение среднего пакета асинхронным? Эффект в основном следующий:

  • когда значениеinitialна самом деле, эффект не так хорош, какallа такжеasyncПри инициализации пакет, участвующий в каждой странице из JS на соответствующих страницах, из файлов JS на соответствующих страницах, будет упакован для JS каждой страницы в соответствии со страницей. Эффект в основном следующий:

sideEffectsПри включении бесполезные модули можно исключить и использовать для выполненияtree-shake. когда модульpackage.jsonКогда это поле добавляется в , это означает, что модуль не имеет побочных эффектов, а это означает, чтоwebpackКод, используемый для реэкспорта, можно безопасно очистить.

concatenateModulesзамененыwebpack.optimize.ModuleConcatenationPlugin()плагин

noEmitOnErrorsзамененыnew webpack.NoEmitOnErrorsPlugin()плагин.

minChunksдаsplitРаньше были общие модулиchunksМинимальное количество , по умолчанию 1, но код в примере находится вdefaultПереписывается как 2 в , исходя из здравого смысла,minChunks = 2Это должен быть разумный выбор.

Уведомление:webpack.optimize.UglifyJsPluginСейчас нет необходимости, просто используйтеoptimization.minimizeдляtrueПросто хорошо,production modeДалее автоматическиtrue, конечно, если вы хотите использовать сторонний плагин сжатия, вы также можетеoptimization.minimizerнастроен в списке массивов

Обновление плагина HTML-WebPack

Рекомендуется обновить до последней версии@4.0.0-alpha, здесь нужно поставить по умолчаниюchunksSortMode: dependencyудалены, в основном потому, чтоwebpack4Удалены связанныеCommonsChunkPluginAPI тоже.

обновление vue-загрузчика

На самом деле это не нужно обновлять, но если вы обновитесь до версии 15.x или выше, вам нужно выполнить это во время использования.VueLoaderPluginМетод плагина, другие способы использования такие же, как и раньше, код фрагмента выглядит следующим образом:

// webpack.prod.conf.js
const { VueLoaderPlugin } = require('vue-loader')
// 省略
plugins: [
    new VueLoaderPlugin(),
]    

нужно открыть исходную карту

webpack4Вам будет предложено включить по умолчаниюsourceMap, так что пока соответствующиеloaderв конфигурацииoptionsнастроитьsourceMap:trueВот и все.

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

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

  • babel-loader 7.1.5
  • css-loader 1.0.0
  • file-loader 1.1.11
  • less-loader 4.1.0
  • url-loader 1.0.1
  • vue-style-loader 4.1.0
  • vue-template-compiler 2.5.16

Полная конфигурация

webpack.dev.conf.js

const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')

// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

baseWebpackConfig.output.chunkFilename = '[name].[chunkhash].js';            // 路由js命名 这个拆分路由 模块依赖脚本文件

module.exports = merge(baseWebpackConfig, {
  mode: 'development',
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  devtool: '#cheap-module-eval-source-map',
  optimization: {
    // minimizer: true,
    providedExports: true,
    usedExports: true,
    //识别package.json中的sideEffects以剔除无用的模块,用来做tree-shake
    //依赖于optimization.providedExports和optimization.usedExports
    sideEffects: true,
    //取代 new webpack.optimize.ModuleConcatenationPlugin()
    concatenateModules: true,
    //取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
    noEmitOnErrors: true,
    splitChunks: {
      chunks: 'initial', //'all'|'async'|'initial'(全部|按需加载|初始加载)的chunks
    },
    //提取webpack运行时的代码
    runtimeChunk: {
      name: 'manifest'
    }
  },
  plugins: [
    new VueLoaderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    new FriendlyErrorsPlugin()
  ]
})

webpack.prod.conf.js

const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')

var webpackConfig = merge(baseWebpackConfig, {
  mode: 'production',
  entry: {
    charts: ['echarts'],
    vendors: ['vue', 'vuex', 'vue-router', 'moment'],
    iconfonts: ['ga-iconfont']
  },
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  devtool: config.build.productionSourceMap ? '#source-map' : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
    publicPath: './'
  },
  optimization: {
    // minimizer: true,
    providedExports: true,
    usedExports: true,
    //识别package.json中的sideEffects以剔除无用的模块,用来做tree-shake
    //依赖于optimization.providedExports和optimization.usedExports
    sideEffects: true,
    //取代 new webpack.optimize.ModuleConcatenationPlugin()
    concatenateModules: true,
    //取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
    noEmitOnErrors: true,
    splitChunks: {
      // maxAsyncRequests: 1,                     // 最大异步请求数, 默认1
      // maxInitialRequests: 1,                   // 最大初始化请求书,默认1
      cacheGroups: {
        // test: path.resolve(__dirname, '../node_modules'),
        commons: {
          chunks: 'all',
          minChunks: 2,
          maxInitialRequests: 5, // The default limit is too small to showcase the effect
          minSize: 0, // This is example is too small to create commons chunks
          name: 'common'
        }
      }
    },
    //提取webpack运行时的代码
    runtimeChunk: {
      name: 'manifest'
    }
  },
  plugins: [
    new VueLoaderPlugin(),
    // 解决moment语言包问题
    new webpack.ContextReplacementPlugin(
      /moment[\\\/]locale$/,
      /^\.\/(zh-cn)$/
    ),

    new MiniCssExtractPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css')
    }),
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true,
        discardComments: { removeAll: true }
      }
    }),

    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      hash:true,// 防止缓存
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),
    new webpack.HashedModuleIdsPlugin(),

    new CopyWebpackPlugin([{
      from: path.resolve(__dirname, '../static'),
      to: config.build.assetsSubDirectory,
      ignore: ['.*']
    }])
  ]
})

if (config.build.productionGzip) {
  var CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

Меры предосторожности

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

Если используется в проектеpreload-webpack-pluginПлагин, вы должны обновить до3.0.0-beta.1Версия, вы можете запустить следующую команду:

npm i preload-webpack-plugin@next -D

В то же время необходимоhtml-webpack-pluginВерсия плагина возвращается к3.2.0Просто сделайте это, а затем добавьте его в файл конфигурации в следующем порядке, код фрагмента выглядит следующим образом:

// 省略
plugins: [
  new HtmlWebpackPlugin({
    filename: config.build.index,
    template: 'index.html',
    inject: true,
    minify: {
      removeComments: true,
      collapseWhitespace: true,
      removeAttributeQuotes: true
    },
  }),
  new PreloadWebpackPlugin({
    rel: 'prefetch',
  }),
  new PreloadWebpackPlugin({
    rel: 'preload'
  }),
  // 省略
]

html-webpack-plugin-after-emit

Обновитьwebpack4После этого вdevсреды, вы обнаружите, что изменение любого кода приведет к обновлению всей страницы и сообщитcb is not a function, причина этогоhtml-webpack-plugin-after-emitПлагин для старшей версииwebpack4а такжеhtml-webpack-plugin3.2.0Он устарел, плагина на замену пока не найдено. Вы можете временно закомментировать этот код. Код находится вbuild/dev-server.js, код фрагмента выглядит следующим образом:

compiler.plugin('compilation', function(compilation) {
    compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) {
        hotMiddleware.publish({ action: 'reload' })
        cb()
    })
})

Суммировать

В соответствии с приведенной выше модификацией его можно в принципе дополнитьwebpack4После обновления лично мне кажется, что конфигурация стала проще, раньше удалялось много громоздкой конфигурации плагинов, много функцийwebpack4По умолчанию идет вместе с ним.После тестирования скорость упаковки была улучшена более чем на 50%.До модификации время упаковки было143894msО, после апгрейда время в основном58080msЭффект в основном следующий:

Если вы упакуете его снова, время в основном27534msЭффект в основном следующий:

21.07.2018 Дополнение

О конфигурации входа и оптимизации

Выше описано, какoptimizationПутем определения сопряженийentryУпаковка, если окончательная упаковка выполнена в соответствии с приведенной выше конфигурацией, она действительно будет сгенерирована.charts,vendors,iconfontsТри js файла, но будут сомнения по поводу размера js, ибо размер в основном в199 bytesСледующее, это кажется немного странным, прямо откройте эти три js, чтобы увидеть, код выглядит следующим образом:

// charts.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{21:function(n,o,p){n.exports=p("K8M1")}},[[21,1,0]]]);
//# sourceMappingURL=charts.2e5cbbfa2a894d2bb5aa.js.map

// iconfonts.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{19:function(n,o,p){n.exports=p("t+cQ")}},[[19,1,0]]]);
//# sourceMappingURL=iconfonts.e90fd0507d501ef81b69.js.map

// vendors.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{20:function(n,o,w){w("oCYn"),w("L2JU"),w("jE9Z"),n.exports=w("wd/R")}},[[20,1,0]]]);
//# sourceMappingURL=vendors.5c535f00ba89522ba93b.js.map

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

entry: {
  // charts: ['echarts'],
  // vendors: ['vue', 'vuex', 'vue-router', 'moment'],
  // iconfonts: ['ga-iconfont']
}

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

Итак, вопрос, как мы можем сделать три пакета js в соответствии с конфигурацией входного файла?Следующая конфигурация может быть следующей, и код фрагмента выглядит следующим образом:

// 前提是不注释entry中的代码

// 省略
optimization: {
  // 省略
  splitChunks: {
    cacheGroups: {
      charts: {
        chunks: 'async',
        minChunks: 2,
        maxInitialRequests: 5,
        minSize: 0, 
        name: 'charts'
      },
      vendors: {
        chunks: 'async',
        minChunks: 2,
        maxInitialRequests: 5,
        minSize: 0, 
        name: 'vendors'
      },
      iconfonts: {
        chunks: 'async',
        minChunks: 2,
        maxInitialRequests: 5,
        minSize: 0, 
        name: 'iconfonts'
      }
    }
  },
  // 省略
}
// 省略

Сфокусируйся наcacheGroupsКонфигурация внутри изменила исходные общие до трех, и в то же времяchunksнеобходимо изменить наasync, результаты упаковки в основном следующие:

Относительно скорости упаковки

Количество страниц проекта для следующей тестовой скорости — 26 страниц, более десяти бизнес-компонентов, набор библиотек UI-компонентов, разработанных внутри группы, и несколько сторонних библиотек. Фактическое время будет зависеть от того, сколько соответствующих файлов проекта.

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

// webpack.base.conf.js
// 在rules中的babel-loader改用happypack中的loader
// 省略
module: {
  rules: [{
    test: /\.js$/,
    loader: 'happypack/loader', // 增加新的HappyPack构建loader
    include: [resolve('src')],
    exclude: /node_modules/,
    options: {
      sourceMap: true,
    }
  }
}    
// 省略

// webpack.prod.conf.js
// 省略
plugins: [
  new HappyPack({
    loaders: [{
      loader: 'babel-loader',
      options: { 
        babelrc: true, 
        cacheDirectory: true 
      }
    }],
    threadPool: happyThreadPool
  })
]    
// 省略

Ниже приведено время, необходимое для упаковки до того, как немодифицированный26831ms:

После модификации скорость упаковки составляет20387ms, уменьшение почти6.5sо:

установивbabel-loaderсерединаcacheDirectoryСвойства также могут повысить скорость компиляции. Многие онлайн-настройки выглядят следующим образом, но будет сообщено об ошибке. Код фрагмента выглядит следующим образом:

// 这是错误用法,我实测发现报错
{
  test: /\.js$/,
  loader: 'babel-loader?cacheDirectory=true', // 或者loader: 'babel-loader?cacheDirectory'
  include: [resolve('src')],
  exclude: /node_modules/,
  options: {
    sourceMap: true,
  }
}

На самом деле, это может бытьcacheDirectoryкак свойство, настроенное вoptions, основной код выглядит следующим образом:

{
  test: /\.js$/,
  loader: 'babel-loader',
  include: [resolve('src')],
  exclude: /node_modules/,
  options: {
    sourceMap: true,
    cacheDirectory: true 
  }
}

Добавьте вышеуказанную модификацию, скорость упаковки20593ms, который, похоже, не сильно изменился по сравнению с предыдущим:

После финального теста для моего проектаHappyPackа такжеcacheDirectoryЭффекты нельзя накладывать друг на друга, с помощью любого из них можно добиться20sвсе время.

Выше приведено все содержание, если что-то не так, добро пожаловать, чтобы упомянутьissues