Webpack 設置詳解(含 4)——關注細節

媒介

源代碼

熟習 webpack 與 webpack4 設置。

webpack4 相對於 3 的最主要的區別是所謂的零設置,然則為了滿足我們的項目需求照樣要本身舉行設置,不過我們能夠運用一些 webpack 的預設值。同時 webpack 也拆成了兩部份,webpack 和 webpack-cli,都須要當地裝置。

我們經由歷程完成一個 vue 的開闢模板(vue init webpack 模板,實在跟 vue 關聯不太大)來舉行一次體驗。在設置歷程當中會只管運用 webpack4 的相干內容。

本文不做 webpack 設置的完全引見,偏重引見設置歷程當中須要注重的處所。檢察代碼解釋瀏覽結果更佳,完全設置與細緻解釋可見源代碼。設置位於 build 文件夾下。

與版本 4 相干的章節會增添標記 ④

須要注重的一點是,我們的 webpack 代碼是運轉在node環境下的,這部份代碼能夠運用 node api,然則我們的營業代碼(src下)是沒法運用 node api 的。

基礎公用設置

由於 webpack 設置中的如 context,entry(chunk進口),output(輸出)和 module.rules 中 loaders 的設置在開闢情勢和臨盆情勢基礎都是公用的,所以我們提取到 webpack.base.js 文件內,供復用。个中 output 部份以下:

output: {
  
  path: path.resolve(__dirname, '../dist/'), // 資本文件輸出時寫入的途徑
  filename: 'static/js/[name].[chunkhash].js', // 運用 chunkhash 到場文件名做文件更新和緩存處置懲罰
  chunkFilename: 'static/js/[name].[chunkhash].js'
}

須要注重的有:

文件名 hash

hash 是用在文件輸出的名字中的,如 [name].[hash].js,總的來說,webpack 供應了三種 hash:

  1. [hash]:此次打包的一切內容的 hash。
  2. [chunkhash]:每一個 chunk 都依據本身的內容盤算而來。
  3. [contenthash]:由 css 提取插件供應,依據本身內容盤算得來。

三種 hash 的運用,我們在優化部份再講,先優先運用 [chunkhash]

loader 優先級

loader 優先級須要注重兩點,

  1. 同 test 設置內優先級:在同一個 test 下設置多個 loader ,優先處置懲罰的 loader 放在設置數組的背面,如對 less 處置懲罰,則:

    {
      test: /\.less$/,
      use: [
        'style-loader', 
        'css-loader', 
        'postcss-loader', 
        'less-loader'
      ]
    }
  2. 差別 test 內優先級:如對 js 文件的處置懲罰須要兩個 test 離別設置,運用 eslint-loaderbabel-loader ,然則又不能設置在一個設置對象內,可運用 enforce: ‘pre’ 強調優先級,由 eslint-loader 優先處置懲罰。

    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
    },
    {
      test: /\.js$/,
      loader: 'babel-loader'
    }

css 預處置懲罰器設置

我們以 less 文件的 loader 設置 ['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader'],運用 @import url(demo.less)為例:

  1. less-loader 先處置懲罰 less 語法
  2. postcss-loader 舉行前綴增添等其他處置懲罰
  3. css-loader 將內容引入 @import 地點的 css 文件內
  4. vue-style-loader 將天生 style 標籤,將 css 內容插進去 HTML

vue-style-loader 功用相似 style-loader

然則由於 vue 中的單文件組件,又分為兩種狀況:

  • .vue 文件內的 style:
    vue-loader 會對 .vue 單文件組件舉行處置懲罰,對 .vue 單文件組件內的種種 lang=”type” 我們能夠在 vue-loader 的 options 設置差別的 loader,由於 vue-loader 內置了 postcss 對 css 舉行處置懲罰,所以此處我們不須要再設置 postcss-loader

    {
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        loaders: {
          less: ['// xxx-loaders'],
          scss: ['// xxx-loaders'],
        }
      }
    }
  • js 直接引入中引入款式文件:
    如 main.js 中 import '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" // 異步加載語法編譯插件
  ]
}

媒體資本 loader

我們還須要對圖片、視頻、字體等文件舉行 loader 設置,以字體文件為例子,主要用到的是 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]',
  }
}

靜態文件拷貝

直接援用(絕對途徑)和代碼實行時肯定的資本途徑應當是以靜態文件存在的,這些資本文件不會經由 webpack 編譯處置懲罰,所以我們將它們放在自力的文件夾(如 static)中,並在代碼打包后拷貝到我們的輸出目次,我們運用 copy-webpack-plugin 自動完成這個事變:

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

// 在開闢情勢下,會將文件寫入內存
new CopyWebpackPlugin([
  {
    from: path.resolve(__dirname, '../static'),
    to: 'static',
    ignore: ['.*']
  }
])

此插件在拷貝文件過量時會崩潰,不知道處理了沒有。

臨盆情勢 production

我們先舉行臨盆情勢的設置。

增添 script 劇本敕令

在 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 模塊刪除 webpack 下之前發作的指定文件,啟動 webpack,並在差別階段給出差別的提醒信息。

// 在第一行設置當前為 臨盆環境
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, {
  // 臨盆情勢設置
})

兼并基礎設置和臨盆情勢獨佔設置,然後我們最先舉行臨盆情勢下的 webpack 的設置信息的填寫。

④ mode 預設

這是 webpack4 的新 api ,有三個預設值:developmentproductionnone,我們在臨盆情勢選用mode: 'production',webpack4在此設置下默許啟用了:

  • 插件

    • FlagDependencyUsagePlugin:應當是刪除無用代碼的,其他插件依靠
    • FlagIncludedChunksPlugin:應當是刪除無用代碼的,其他插件依靠
    • ModuleConcatenationPlugin:作用域提拔 webpack3的scope hosting
    • NoEmitOnErrorsPlugin:碰到毛病代碼不跳出
    • OccurrenceOrderPlugin
    • SideEffectsFlagPlugin
    • UglifyJsPlugin:js代碼緊縮
    • process.env.NODE_ENV 的值設為 production

所以這些默許啟用的內容我們不須要再設置。

末了一點設置 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 打出的 bundles 到 HTML 文件

  • 我們運用 webpack 設置進口時只能設置 js 文件作為進口,webpack 打出的 bundles 並不能自動與我們項目的 HTML 文件發作關聯。
  • 須要我們手動增添<script src="./bundles.js"></script>(還能夠包含背面提掏出來的 css 文件)到 HTML 文件。
  • 我們能夠運用 html-webpack-plugin 插件自動完成這個事變。
  • 當僅運用 webpack 對 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 在開闢情勢下照樣比較主要的。

④ 提取 js 中的 css 部份到零丁的文件

運用過 webpack3 的同硯應當對 extract-text-webpack-plugin 插件(以舊插件代稱)比較熟習,為了嘗試webpack4,我並不想運用這個插件的 @next 版本,所以挑選了新的替換插件 mini-css-extract-plugin(以新插件代稱)。
與舊插件雷同,一樣須要在 webpack 的 loader 部份和 plugin 部份都舉行設置,差別的是新插件供應了零丁的 loader,在 loader 部份與舊插件的設置體式格局不太雷同。設置以下:

  • loader 部份

    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    // 
    [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          // (segmentfault 這兒的多行解釋襯着有點題目 😰,改成單行解釋情勢)
          // 複寫 css 文件中資本途徑
          // webpack3.x 設置在 extract-text-webpack-plugin 插件中
          // 由於 css 文件中的外鏈是相對與 css 的,
          // 我們抽離的 css 文件在能夠會零丁放在 css 文件夾內
          // 援用其他如 img/a.png 會尋址毛病
          // 這類狀況下所以零丁須要設置 publicPath,複寫个中資本的途徑
          //
          publicPath: '../' 
        }
      },
      {
        loader: 'css-loader',
        options: {}
      },
      {
        loader: 'less-loader',
        options: {}
      }
    ]
  • plugin 部份

    new MiniCssExtractPlugin({
      // 輸出到零丁的 css 文件夾下
      filename: "static/css/[name].[chunkhash].css"
    })

能夠看到這個 loader 也設置在了 css 預處置懲罰器部份,在前面我們已把 css 預處置懲罰器的設置提取到了 utils.js 文件的函數內,所以這裏也是,我們運用 extract 參數決議是不是須要提取。

回想一下,之前運用的 style-loadervue-style-loader 的作用,它們會建立標籤將 css 的內容直接插進去到 HTML中。而提取成自力的 css 文件以後,插進去到 HTML 的事變由 html-webpack-plugin 插件完成,二者職責的這部份職責是反覆的,所以我們須要運用 extract 參數做相似以下處置懲罰:

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

④ 拆分 js 代碼

這是 webpack 設置中很主要的一個環節,影響到我們運用瀏覽器緩存的合理性,影響頁面資本的加載速率,將 js 舉行合理拆分,能夠有用減小我們每次更新代碼影響到的文件局限。
運用過 webpack3 的同硯肯定清晰,我們平常會提掏出這麼幾個文件 manifest.js(webpack 運轉時,即webpack剖析其他bundle的代碼等)、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中,如:

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

開闢情勢 development

開闢情勢與臨盆情勢的差別是,在開闢時會頻仍運轉代碼,所以許多東西在開闢情勢是不引薦設置的,如css文件提取,代碼緊縮等。所以針對一些寫入大眾設置文件,然則開闢情勢不須要的功用,我們須要做相似修正:process.env.NODE_ENV === 'production' ? true : false,如 css 預處置懲罰中是不是須要設置提取 loader MiniCssExtractPlugin.loader。另外另有一些是只設置在臨盆情勢下的,如 MiniCssExtractPlugin 和 js 代碼拆分優化。

開闢情勢我們須要一個開闢效勞,幫我們完成及時更新、接口代辦等功用。我們運用 webpack-dev-server。須要 npm 裝置。

增添 script 劇本敕令

一樣,在 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 預設

一樣,在開闢情勢下我們能夠將 mode 設置為 development,一樣默許啟用了一些功用:

  • 插件

    • NamedChunksPlugin:運用 entry 名做 chunk 標識
    • NamedModulesPlugin:運用模塊的相對途徑非自增 id 做模塊標識
  • process.env.NODE_ENV 的值設為 development

開闢效勞設置 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 運用熱更新 hot 時須要運用插件:

plugins: [
  new webpack.HotModuleReplacementPlugin()
]

優化 webpack 輸出信息,須要設置:

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

注重事項

  • 熱更新:在運用熱更新時,我們的 chunk 名中不能運用 [hash] 做標識,文件名變化沒法熱更新,所以須要將本來設置在大眾設置中的 output 中的文件名設置離別寫入臨盆和開闢情勢設置中,開闢情勢去掉 [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 是不是提取。
  • 途徑設置,如文件輸出途徑和文件名、output 中的 publicPath(代碼 output 中只設置了 path,沒設置 publicPath,將這部份途徑的 static 寫到了各個資本的輸出name中,可參考Webpack中publicPath詳解)、效勞設置如端口等。

我們能夠提取到自力的 config 文件中(本代碼沒做)。

拆分 js 代碼

在臨盆情勢的 拆分 js 代碼 部份我們已講了怎樣拆分,那末為了更好的剖析我們的拆分是不是合理,我們能夠設置一個 bundle 組成剖析的插件。

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

hash 固化

我們運用文件名中的 hash 變化來舉行資本文件的更新,那末合理應用緩存時,就要求我們合理的拆分文件,在內容更新時最小限度的影響文件名中的 hash。這裏就用到了[hash][chunkhash][contenthash]。但是 webpack 對 hash 的默許處置懲罰並不盡善盡美,這一部份的優化能夠參考基於 webpack 的耐久化緩存計劃

多頁面

多頁面設置代碼位於 muilt-pages 分支。我們只需做少許修正,以現在有 entry 頁和 index 頁為例。

entry 修改

將兩個頁面的 js 進口都設置在 webpackentry中:

entry: {
  /**
    * 進口,chunkname: 途徑
    * 多進口可設置多個
    */
  main: './src/main.js',
  entry: './src/entry.js'
}

也能夠本身設置項目構造,運用 node 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']
}),

个中需手動指定每一個頁面的插進去的 chunks(同步的),不然會將其他頁面的文件也一同插進去當前頁面。

④ 大眾js提取

在單頁面下,平常不存在提取非異步 js 文件的大眾代碼(非 node_modules)的題目,在多頁面下我們的頁面間能夠會公用 api、設置等文件,此時能夠增添:

'common': {
  // initial 設置提取同步代碼中的公用代碼
  chunks: 'initial',
  // test: 'xxxx', 也可運用 test 挑選提取哪些 chunks 里的代碼
  name: 'common',
  minSize: 0,
  minChunks: 2
}

提取同步代碼中的公用代碼

參考

  1. 基於 webpack 的耐久化緩存計劃
  2. webpack issues
  3. vuejs-templates/webpack/issues
    原文作者:toBeTheLight
    原文地址: https://segmentfault.com/a/1190000014685887
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞