Детали конфигурации Webpack (в том числе 4) — внимание к деталям

Webpack

предисловие

исходный код

Знаком с конфигурацией webpack и webpack4.

Основное различие между webpack 4 и 3 заключается в т.н.零配置, но для того, чтобы удовлетворить потребности нашего проекта, нам все равно придется настраивать его самим, но мы можем использовать некоторые пресеты веб-пакета. В то же время, webpack также разделен на две части, webpack и webpack-cli, обе из которых должны быть установлены локально.

У нас есть опыт реализации шаблона разработки vue (шаблон vue init webpack, который имеет мало общего с vue). В процессе настройки мы постараемся использовать контент, связанный с webpack4.

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

Главы, относящиеся к версии 4, будут добавляться с символом ④.

Следует отметить, что наш код веб-пакета работает в среде узла, и эта часть кода может использовать API узла, но наш бизнес-код (под src) не может использовать API узла.

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

Поскольку конфигурация загрузчиков в конфигурации веб-пакета, такая как контекст, запись (запись фрагмента), вывод (выход) и module.rules, в основном общие в режиме разработки и режиме производства, мы извлекаемwebpack.base.jsфайл для повторного использования. Выходная часть выглядит следующим образом:

output: {
  
  path: path.resolve(__dirname, '../dist/'), // 资源文件输出时写入的路径
  filename: 'static/js/[name].[chunkhash].js', // 使用 chunkhash 加入文件名做文件更新和缓存处理
  chunkFilename: 'static/js/[name].[chunkhash].js'
}

Примечание:

хэш имени файла

hash используется в имени выходного файла, например.[name].[hash].jsВ общем, WebPack предоставляет три хэши:

  1. [hash]: Хэш всего контента, упакованного на этот раз.
  2. [chunkhash]: каждый фрагмент рассчитывается на основе собственного содержимого.
  3. [contenthash]: Предоставляется плагином извлечения css, рассчитывается на основе его собственного содержимого.

Использование трех хэшей, о нем мы поговорим в разделе оптимизации, сначала используйте сначала[chunkhash].

приоритет загрузчика

Приоритет загрузчика должен обратить внимание на два момента,

  1. Тестовые конфигурации с одинаковым приоритетом: несколько загрузчиков в одном тесте,Приоритетный загрузчик размещается после массива конфигурации, если для меньшей обработки, то:
    {
      test: /\.less$/,
      use: [
        'style-loader', 
        'css-loader', 
        'postcss-loader', 
        'less-loader'
      ]
    }
    
  2. Приоритет в разных тестах: если для обработки файлов js требуется настроить два теста отдельно, используйтеeslint-loaderа такжеbabel-loader, но не может быть настроен в объекте конфигурации, вы можете использоватьenforce: 'pre'Подчеркнуть приоритет,eslint-loaderприоритет.
    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
    },
    {
      test: /\.js$/,
      loader: 'babel-loader'
    }
    

настройка препроцессора css

Настраиваем его с загрузчиком меньшего файла['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader'],использовать@import url(demo.less)Например:

  1. less-loader сначала обрабатывает меньше синтаксиса
  2. postcss-loader выполняет другую обработку, такую ​​как добавление префикса
  3. css-loader импортирует содержимое в файл css, где находится @import
  4. vue-style-loader будет генерировать теги стиля для вставки содержимого css в HTML.

vue-style-loader работает как style-loader

Но из-за однофайловой составляющей в vue он делится на два случая:

  • Стиль в файле .vue:
    vue-loaderБудет обработан однофайловый компонент .vue, и для различных lang="type" в однофайловом компоненте .vue мы можемvue-loaderОпцииНастроить разные загрузчики,из-заvue-loaderвстроенныйpostcssПроцесс css, поэтому здесь его настраивать не нужноpostcss-loader

    {
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        loaders: {
          less: ['// xxx-loaders'],
          scss: ['// xxx-loaders'],
        }
      }
    }
    
  • Добавьте файлы стилей в прямой импорт js:
    Как в main.jsimport 'demo.less', файл стиля, представленный таким образом, вvue-loaderОбработка выходит за рамки, поэтому настройка по-прежнему требуетсяpostcss-loader.

Из-за этой разницы мы инкапсулируем конфигурацию файла препроцессора css в виде функции, которая определяетсяusePostCssПараметры генерируют соответствующую конфигурацию и помещают файл вutils.jsфайл,vue-loaderнастроить вvue-loader.jsвнутри файла.

То есть нам нужно настроить препроцессор css вvue-loaderвнутренняя гармонияwebpackНастраивал два раза.

Во время написания этого README.md vue-loader выпустил версию v15, которую нужно использовать с плагином, поэтому нет необходимости настраивать его дважды.

postcss-loader

postcss-loaderЭто мощный инструмент обработки css.Мы разделяем конфигурацию postcss и создаем новыйpostcss.config.jsконфигурационный файл

module.exports = {
  plugins: {
    // 处理 @import
    'postcss-import': {},
    // 处理 css 中 url
    'postcss-url': {},
    // 自动前缀
    'autoprefixer': {
      "browsers": [
        "> 1%",
        "last 2 versions"
      ]
    }
  }
}

В дополнение к необходимым функциональным плагинам, перечисленным в комментариях, мы также можем использоватьnextcss(обработка нового синтаксиса css),px2rem/px-to-viewportПлагины, связанные с адаптацией мобильного терминала.

babel-loader

Мы используем babel для компиляции js и js-подобных синтаксисов, которые не распознаются браузерами, таких как экранирование ES6+, JSX и т. д. будет такжеbabel-loaderКонфигурация разделена и должна быть создана.babelrcи настроить:

{
  "presets": [
    [
      /* *
       *  babel-preset-env
       *  可以根据配置的目标运行环境自动启用需要的 babel 插件。
       */
      "env", {
        "modules": false, // 关闭 babel 对 es module 的处理
        "targets": { // 目标运行环境
          "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
        }
      }
    ]
  ],
  "plugins": [
    "syntax-dynamic-import" // 异步加载语法编译插件
  ]
}

загрузчик медиаресурсов

Также нам нужно настроить загрузчик для картинок, видео, шрифтов и др. Взяв за пример файлы шрифтов, мы в основном используемurl-loader:

{
  /**
   * 末尾 \?.* 匹配带 ? 资源路径
   * 我们引入的第三方 css 字体样式对字体的引用路径中可能带查询字符串的版本信息
   */
  test: /\.(woff2|woff|eot|ttf|otf)(\?.*)?$/,
  /**
   * url-loader
   * 会配合 webpack 对资源引入路径进行复写,如将 css 提取成独立文件,可能出现 404 错误可查看 提取 js 中的 css 部分解决
   * 会以 webpack 的输出路径为基本路径,以 name 配置进行具体输出
   * limit 单位为 byte,小于这个大小的文件会编译为 base64 写进 js 或 html
   */
  loader: 'url-loader',
  options: {
    limit: 10000,
    name: 'static/fonts/[name].[hash:7].[ext]',
  }
}

Копия статического файла

Прямая ссылка (абсолютный путь) и путь к ресурсам, определенный при выполнении кода, должны существовать в виде статических файлов.Эти файлы ресурсов не будут компилироваться и обрабатываться вебпаком, поэтому мы помещаем их в отдельную папку (например, static) и в После того, как код упакован и скопирован в наш выходной каталог, мы используемcopy-webpack-pluginЧтобы сделать это автоматически:

const CopyWebpackPlugin = require('copy-webpack-plugin')

// 在开发模式下,会将文件写入内存
new CopyWebpackPlugin([
  {
    from: path.resolve(__dirname, '../static'),
    to: 'static',
    ignore: ['.*']
  }
])

Этот плагин вылетает, когда копируется слишком много файлов. Я не знаю, решена ли проблема.

производственный режим производства

Сначала мы настраиваем производственный режим.

Добавить команду сценария сценария

Добавить в package.json

"scripts": {
  "build": "node build/build.js"`
}

затем используйтеnpm run buildкоманда может быть выполненаnode build/build.js, мы не используем напрямуюwebpack webpack.prod.config.jsкоманду для выполнения файла конфигурации, но в build.js выполните некоторую обработку удаления файла, а затем запустите webpack.

Создать логику build.js

В основном две работы, представляяrimrafМодуль удаляет указанные файлы, созданные в рамках веб-пакета, запускает веб-пакет и выдает разные подсказки на разных этапах.

// 在第一行设置当前为 生产环境
process.env.NODE_ENV = 'production'

const webpack = require('webpack')
const rm = require('rimraf')
const webpackConfig = require('./webpack.prod')
// 删除 webpack 输出目录下的内容,也可只删除子文件如 static 等
rm(webpackConfig.output.path, err => {
  // webpack 按照生产模式配置启动
  webpack(webpackConfig, (err, stats) => {
    // 输出一些状态信息
  })
}

Дополнительные сведения см. в комментариях к исходному коду..

Профиль производственного режима

новыйwebpack.prod.jsфайл, использовать

const merge = require('webpack-merge') // 专用合并 webpack 配置的包
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
  // 生产模式配置
})

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

④ предустановка режима

Это новый API для webpack4 с тремя пресетами:development,production,none, выбираем в производственном режимеmode: 'production', webpack4 под этой конфигурациейвключено по умолчаниюсейчас:

  • плагин
    • FlagDependencyUsagePlugin: должен удалять бесполезный код, другие плагины зависят от
    • FlagIncludedChunksPlugin: должен удалять бесполезный код, другие плагины зависят от
    • ModuleConcatenationPlugin: расширяет возможности хостинга webpack3.
    • NoEmitOnErrorsPlugin: не выскакивать при обнаружении кодов ошибок
    • OccurrenceOrderPlugin
    • SideEffectsFlagPlugin
    • UglifyJsPlugin: сжатие кода js
    • Значение process.env.NODE_ENV установлено на производство

Поэтому нам не нужно настраивать это содержимое, включенное по умолчанию.

Последняя часть настройкиprocess.env.NODE_ENV 的值设为 productionФактически, используйте плагин DefinePlugin:

new webpack.DefinePlugin({
  "process.env.NODE_ENV": JSON.stringify("production") 
})

Чтобы мы могли передать бизнес-кодprocess.env.NODE_ENV, например, решить, следует ли использовать интерфейс разработки или онлайн-интерфейс. Если нам нужно судить о текущей среде в webpack, нам также нужна отдельная настройкаprocess.env.NODE_ENV = 'production', где мы также находимсяbuild.jsкакая первая строка в .

Добавьте пакеты, сгенерированные webpack, в файл HTML.

  • Когда мы используем веб-пакет для настройки записи, мы можем настроить только файл js в качестве записи, а пакеты, созданные веб-пакетом, не могут быть автоматически связаны с HTML-файлом нашего проекта.
  • Нам нужно вручную добавить<script src="./bundles.js"></script>(может также включать файл css, извлеченный позже) в файл HTML.
  • мы можем использоватьhtml-webpack-pluginПлагин делает это автоматически.
  • Этот шаг не требуется, если с веб-пакетом связан только js и файлы HTML не требуются.
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
  new HtmlWebpackPlugin({
    filename: path.join(__dirname, '../dist/index.html'),// 文件写入路径
    template: path.join(__dirname, '../src/index.html'),// 模板文件路径
    inject: true // js 等 bundles 插入 html 的位置 head/body等
  })
]

Если HtmlWebpackPlugin не настроен, он создаст файл HTML сfilenameЭто более важно в режиме разработки.

④ Извлеките css-часть js в отдельный файл.

Студенты, которые использовали webpack3, должны быть правыextract-text-webpack-pluginПлагин (названный в честь старого плагина) более привычный, чтобы попробовать webpack4, я не хочу использовать этот плагин@nextверсии, поэтому был выбран новый альтернативный плагинmini-css-extract-plugin(называемый новым плагином).
Так же, как и старый плагин, его также необходимо настроить как в разделе загрузчика, так и в разделе плагинов веб-пакета, разница в том, что новый плагин предоставляет отдельный загрузчик, который не настраивается так же, как старый плагин в раздел загрузчика. Конфигурация выглядит следующим образом:

  • секция погрузчика

    ```js
    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    // ...
    [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
        /*
        * 复写 css 文件中资源路径
        * webpack3.x 配置在 extract-text-webpack-plugin 插件中
        * 因为 css 文件中的外链是相对与 css 的,
        * 我们抽离的 css 文件在可能会单独放在 css 文件夹内
        * 引用其他如 img/a.png 会寻址错误
        * 这种情况下所以单独需要配置 ../,复写其中资源的路径
        */
        publicPath: '../' 
      },
      {
        loader: 'css-loader',
        options: {}
      },
      {
        loader: 'less-loader',
        options: {}
      }
    ]
    ```
    
  • раздел плагинов

    ```js
    new MiniCssExtractPlugin({
      // 输出到单独的 css 文件夹下
      filename: "static/css/[name].[chunkhash].css"
    })
    ```
    

Вы можете видеть, что этот загрузчик также настроен в разделе препроцессора css, мы извлекли конфигурацию препроцессора css в функцию файла utils.js, поэтому здесь мы также используемextractПараметр определяет, требуется ли извлечение.

Напомним, ранее использовавшийсяstyle-loaderилиvue-style-loader, они создают теги для вставки содержимого css непосредственно в HTML. После извлечения в отдельный файл css работа по вставке в HTML выполняетсяhtml-webpack-pluginПлагин сделан, и эта часть обязанностей у двоих дублируется, поэтому нам нужно использоватьextractПараметры обрабатываются следующим образом:

if (options.extract) {
  return [MiniCssExtractPlugin.loader, ...otherLoaders]
} else {
  return ['vue-style-loader', ...otherLoaders]
}

④ Разделить код js

Это очень важная часть настройки вебпака.Она влияет на рациональность использования нами кеширования браузера и скорость загрузки ресурсов страницы.Разумное разбиение js может эффективно уменьшить диапазон файлов, затрагиваемых каждым обновлением кода. .
Студенты, которые использовали webpack3, должны знать, что мы обычно извлекаем эти файлыmanifest.js(Когда работает webpack, то есть webpack анализирует код других бандлов и т.д.),vendor.js(библиотеки в node_modules), app.js (реальный бизнес-код проекта). В webpack3 мы используемwebpack.optimize.CommonsChunkPluginПлагин извлечен, и мы можем использовать его прямо в webpack4.optimizationЭлементы конфигурации для настройки (конечно, вы все еще можете использовать конфигурацию плагина):

/**
 * 优化部分包括代码拆分
 * 且运行时(manifest)的代码拆分提取为了独立的 runtimeChunk 配置 
 */
optimization: {
  splitChunks: {
    chunks: "all",
    cacheGroups: {
      // 提取 node_modules 中代码
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: "vendors",
        chunks: "all"
      },
      commons: {
        // async 设置提取异步代码中的公用代码
        chunks: "async"
        name: 'commons-async',
        /**
         * minSize 默认为 30000
         * 想要使代码拆分真的按照我们的设置来
         * 需要减小 minSize
         */
        minSize: 0,
        // 至少为两个 chunks 的公用代码
        minChunks: 2
      }
    }
  },
  /**
   * 对应原来的 minchunks: Infinity
   * 提取 webpack 运行时代码
   * 直接置为 true 或设置 name
   */
  runtimeChunk: {
    name: 'manifest'
  }
}

Вы также можете настроить неизмененные зависимости разработки в отдельные записи, например:

entry: {
  app: 'index.js',
  vendor2: ['vue', 'vue-router', 'axios']
}

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

Разница между режимом разработки и производственным режимом заключается в том, что код часто запускается во время разработки, поэтому многие вещи не рекомендуется настраивать в режиме разработки, например извлечение файлов css, сжатие кода и т. д. Итак, для некоторых функций, которые записаны в общедоступный файл конфигурации, но не требуются в режиме разработки, нам необходимо внести аналогичные изменения:process.env.NODE_ENV === 'production' ? true : false, например, настраивать ли загрузчик извлечения в предварительной обработке cssMiniCssExtractPlugin.loader. Кроме того, некоторые из них настраиваются только в рабочем режиме, напримерMiniCssExtractPluginи оптимизация разделения кода js.

режим разработки нам нуженслужба разработки, помогите нам завершить обновление в реальном времени, прокси-сервер интерфейса и другие функции. Мы используемwebpack-dev-server. Требуется установка npm.

Добавить команду сценария сценария

Точно так же в package.json добавьте

"scripts": {
  "dev": "webpack-dev-server --config ./build/webpack.dev.js"
}

использовать--configУкажите файл конфигурации.Поскольку команда напрямую вызывает webpack-dev-server для запуска, мы можем просто написать конфигурацию напрямую, и мы можем написать логику вызова, в отличие от производственного режима.

Файл конфигурации режима разработки

новыйwebpack.dev.jsфайл, также используйте:

// 在第一行设置当前环境为开发环境
process.env.NODE_ENV = 'development'
const merge = require('webpack-merge') // 专用合并webpack配置的包
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
  // 开发模式配置
})

④ предустановка режима

Точно так же в режиме разработки мы можем поставитьmodeнастроен какdevelopment,такой жевключено по умолчаниюнекоторые функции:

  • плагин
    • NamedChunksPlugin: использовать имя записи в качестве идентификатора чанка
    • NamedModulesPlugin: используйте относительный путь модуля, чтобы не увеличивать идентификатор в качестве идентификатора модуля.
  • Значение process.env.NODE_ENV установлено на разработку

Конфигурация службы разработки devServer

Документация

devServer: {
  clientLogLevel: 'warning',
  inline: true,
  // 启动热更新
  hot: true,
  // 在页面上全屏输出报错信息
  overlay: {
    warnings: true,
    errors: true
  },
  // 显示 webpack 构建进度
  progress: true,
  // dev-server 服务路径
  contentBase: false,
  compress: true,
  host: 'localhost',
  port: '8080',
  // 自动打开浏览器
  open: true,
  // 可以进行接口代理配置
  proxy: xxx,
  // 跟 friendly-errors-webpack-plugin 插件配合
  quiet: true,
  publicPath: '/'
}

другие плагины

Плагин требуется, когда devServer использует горячее обновление:

plugins: [
  new webpack.HotModuleReplacementPlugin()
]

Чтобы оптимизировать вывод информации веб-пакета, вам необходимо настроить:

const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
plugins: [
  new FriendlyErrorsPlugin()
]

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

  • Горячее обновление: при использовании горячего обновления нельзя использовать имя нашего чанка.[hash]Для идентификации изменение имени файла не может быть обновлено в горячем режиме, поэтому вам необходимо записать конфигурацию имени файла в выходных данных, изначально настроенных в общедоступной конфигурации, в конфигурации режима производства и разработки и удалить режим разработки.[hash]
    filename: 'static/[name].js', 
    chunkFilename: 'static/[id].js'
    
  • HtmlWebpackPlugin: в производственном режиме мы записываем html-файлы в dist, но в режиме разработки нет фактического процесса записи, иdevServerСодержимое службы после запуска такое же, какcontentBaseсвязаны, они должны быть согласованы, поэтому мы будемHtmlWebpackPluginКонфигурация также разделена на режим производства и разработки, используйте в режиме разработки:
    new HtmlWebpackPlugin({
      filename: 'index.html', // 文件写入路径,前面的路径与 devServer 中 contentBase 对应
      template: path.resolve(__dirname, '../src/index.html'),// 模板文件路径
      inject: true
    })
    

оптимизация

Извлечение конфигурации

  • Некоторые функции режима разработки и производственного режима включены, например, извлекается CSS или нет.
  • Конфигурация пути, например путь к выходному файлу и имя файла, publicPath в выходных данных (настраивается только в выходных данных кодаpath, не настроеноpublicPath, напишите статическую часть этой части пути к выходному имени каждого ресурса, вы можете обратиться кПодробное объяснение publicPath в Webpack), конфигурация службы, такая как порт и т. д.

Мы можем извлечь независимый файл конфигурации (этот код не выполняется).

разделить js-код

в производственном режиме拆分 js 代码Мы уже говорили о том, как разделить, поэтому, чтобы лучше проанализировать, разумно ли наше разделение, мы можем настроить плагин для анализа состава бандла.

const BundleAnalyzer = require('webpack-bundle-analyzer')
plugins: [
  new BundleAnalyzer.BundleAnalyzerPlugin()
]

отверждение гашиша

Мы используем изменение хеша в имени файла для обновления файла ресурсов, поэтому при разумном использовании кеша мы должны разумно разбивать файл и минимально влиять на хеш в имени файла при обновлении содержимого. используется здесь[hash],[chunkhash],[contenthash]. Однако стандартная обработка хэша веб-пакетом неудовлетворительна.Для оптимизации этой части см.Схема постоянного кэширования на основе веб-пакета

несколько страниц

Код многостраничной конфигурации находится по адресумногостраничная ветка. Нам нужно всего лишь внести несколько изменений, в качестве примера возьмем страницу входа и страницу индекса.

изменения записи

Настройте запись js обеих страниц вwebpackизentryсередина:

entry: {
  /**
    * 入口,chunkname: 路径
    * 多入口可配置多个
    */
  main: './src/main.js',
  entry: './src/entry.js'
}

Вы также можете сами задать структуру проекта и использовать API узла для динамического чтения текущей многостраничной записи.

Изменения HtmlWebpackPlugin

Необходимо настроить несколько в зависимости от количества страницHtmlWebpackPlugin:

new HtmlWebpackPlugin({
  filename: path.join(__dirname, '../dist/main.html'),// 文件写入路径
  template: path.join(__dirname, '../src/index.html'),// 模板文件路径
  inject: true, // 插入位置
  chunks: ['manifest', 'vendors', 'common', 'main']
}),
new HtmlWebpackPlugin({
  filename: path.join(__dirname, '../dist/entry.html'),// 文件写入路径
  template: path.join(__dirname, '../src/index.html'),// 模板文件路径
  inject: true, // 插入位置
  chunks: ['manifest', 'vendors', 'common', 'entry']
}),

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

④ Открытое извлечение js

На одной странице, как правило, нет проблем с извлечением общего кода (non-node_modules) неасинхронных js-файлов.На нескольких страницах наши страницы могут совместно использовать API, файлы конфигурации и другие файлы.В настоящее время вы можете добавить:

'common': {
  // initial 设置提取同步代码中的公用代码
  chunks: 'initial',
  // test: 'xxxx', 也可使用 test 选择提取哪些 chunks 里的代码
  name: 'common',
  minSize: 0,
  minChunks: 2
}

Извлечение общего кода в синхронном коде

Ссылаться на

  1. Схема постоянного кэширования на основе веб-пакета
  2. webpack issues
  3. vuejs-templates/webpack/issues