詳細的 webpack4 多入口配置

本文主要是多入口配置,但願能在無框架開發網頁時提升開發效率,對代碼進行打包優化。本文有什麼須要改善的地方,還望各位多多指教。css

本文關鍵詞:html

  1. babel7
  2. 多入口
  3. sass
  4. 圖片處理
  5. 音視頻處理
  6. 字體處理
  7. gzip

github 源碼node

模塊總覽

目錄結構大概以下:jquery

|-build
  |-create.js
  |-utils.js
  |-webpack.base.js
  |-webpack.dev.js
  |-webpack.prod.js
|-dist
|-src
|-.babelrc
|-.eslintrc.js
|-package.json
複製代碼
// webpack.base.js
const webpack = require('webpack')
const PurgecssPlugin = require('purgecss-webpack-plugin')

const rules = require('./webpack.rules.js')
const utils = require('./utils.js')

module.exports = {
  entry: {},
  resolve: {},
  module: {},
  externals: {},
  plugins: []
}

// webpack.prod.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CompressionPlugin = require('compression-webpack-plugin')

const configBase = require('./webpack.base.js')
const utils = require('./utils.js')

const configProd = {
  mode: 'production',
  devtool: 'none',
  output: {},
  optimization: {},
  plugins: []
}
module.exports = merge(configBase, configProd)

// webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')

const utils = require('./utils.js')
const configBase = require('./webpack.base.js')

const configDev = {
  mode: 'development'
  output: {},
  devServer: {},
  plugins: [],
  module: {}
}
module.exports = merge(configBase, configDev)

// webpack.rules.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const devMode = process.env.NODE_ENV !== 'production'
const rules = []
module.exports = rules
複製代碼

後文省略 module.exports 等代碼,再也不贅述。webpack

配置入口 entry

入口告訴 webapck 從哪一個模塊開始,根據依賴關係打包git

  1. 單入口
entry: './src/index.js'
複製代碼
  1. 多入口
entry: {
  index: './src/index/index.js'
}
複製代碼

對於多入口配置,能夠用 glob 庫來動態獲取入口文件,以下:github

// utils.js
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const glob = require('glob') // 遍歷目錄
const devMode = process.env.NODE_ENV !== 'production'

/** * 返回文件的絕對路徑 * @param {string} dir 文件路徑 * __dirname 得到當前執行文件所在目錄的完整目錄名(這裏指的是 build 目錄) */
function resolve(dir) {
  return path.resolve(__dirname, dir)
}

//動態添加入口
function getEntry(globPath) {
  var dirname, name
  return glob.sync(globPath).reduce((acc, entry) => {
    // name ./src/pages/index/index.js
    // dirname ./src/pages/index
    // basename index.js
    dirname = path.dirname(entry)
    name = dirname.slice(dirname.lastIndexOf('/') + 1)
    acc[name] = entry
    return acc
  }, {})
}

function htmlPlugins() {}

module.exports = {
  resolve,
  getEntry,
  htmlPlugins
}

// webpack.base.js
entry: utils.getEntry('./src/pages/*/index.js'),
複製代碼

配置 clean-webpack-plugin

在配置 output 以前配置這個插件是爲,每次打包前能夠刪除 dist 目錄,保證沒有冗餘文件。web

// webpack.prod.js
const cleanWebpackPlugin = require('clean-webpack-plugin')

plugins: [
  // 刪除 dist 目錄
  new CleanWebpackPlugin({
    // verbose Write logs to console.
    verbose: false, //開啓在控制檯輸出信息
    // dry Use boolean "true" to test/emulate delete. (will not remove files).
    // Default: false - remove files
    dry: false
  }),
]
複製代碼

配置出口 output

自定義輸出文件的位置和名稱json

// webpack.dev.js
output: {
  path: utils.resolve('../dist'),
  // 包名稱
  filename: 'js/[name].js'
},

// webpack.prod.js
output: {
  path: utils.resolve('../dist'),
  // 包名稱
  filename: 'js/[name].[chunkhash:8].js',
  // 塊名,公共塊名(非入口)
  chunkFilename: 'js/[name].[chunkhash:8].js',
  // 打包生成的 index.html 文件裏面引用資源的前綴
  // 也爲發佈到線上資源的 URL 前綴
  // 使用的是相對路徑,默認爲 ''
  publicPath: '.'
},
複製代碼

hash

文件名加入 hash,是爲了更好的利用瀏覽器對靜態文件的緩存。api

  1. hash

即便文件內容沒有改變,每次構建都產生一個新的哈希值,這顯然不是咱們想看到的。能夠用在開發環境,但不建議用於生產環境。

  1. chunkhash

每一個入口都有對應的哈希值,當入口依賴關係中有文件內容發生變化,該入口的哈希值纔會發生變化。適用於生產環境。

  1. contenthash

根據包內容計算出哈希值,只要包內容不變,哈希值不變。適用於生產環境。

關於這三者的區別,網上也有相關文章,例如我查到的一篇 《webpack 中的 hash、chunkhash、contenthash 區別》 能夠參考。

配置模式 mode

none、development、production,默認爲 production

// webpack.prod.js
mode: 'production'

// webpack.dev.js
mode: 'development'
複製代碼

webpack4 針對不一樣模式,調用內置的優化策略,能夠減小不少配置。參考 webpack 模式

配置解析策略 resolve

// webpack.base.js
resolve: {
  // import 導入時別名,減小耗時的遞歸解析操做
  alias: {
    '@': resolve('../src'),
    'assets': utils.resolve('../src/assets')
  },
  extensions: [
    '.js',
    '.json'
  ]
}
複製代碼

配置解析和轉換文件的規則 module

給項目中不一樣的文件類型,配置相應的規則

// webpack.base.js
module: {
  // 忽略大型的 library 能夠提升構建性能
  noParse: /jquery|lodash/,
  rules: []
}
複製代碼
  1. js 解析規則
// webpack.rules.js
rules: [
  {
    test: /\.js$/,
    use: ['babel-loader'],
    // 不檢查 node_modules 下的 js 文件
    exclude: '/node_modules/'
  }
]

// .babelrc
{
	"presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3,
        "modules": false
      }
    ]
  ]
}

// pages/index/index.js
import 'core-js/stable'
import 'regenerator-runtime/runtime'
複製代碼

根據官網 Usage Guide 配置如上,這裏採用的是 core-js@3 來實現 polyfill。由於 babel7 已經廢棄 @babel/polyfill 和 core-js@2,再也不更新。新的特性只會添加到 core-js@3,爲了不後續再改動,直接用 3。只是打出來的包大了點,這個本身平衡,若是以爲不爽,就仍是用 @babel/polyfill。

關於這個 core-js@3 有篇文章 講的挺清晰,能夠參考。

  1. sass 解析規則
// webpack.rules.js
rules: [
  {
    test: /\.s[ac]ss$/i,
    use: [
      devMode
        ? 'style-loader'
        : {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // you can specify a publicPath here
              // by default it use publicPath in webpackOptions.output
              publicPath: '../'
            }
          },
      'css-loader',
      'postcss-loader',
      'sass-loader'
    ]
  }
]

// webpack.prod.js
plugins: [
  new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:8].css',
    chunkFilename: 'css/[name].[contenthash:8].css'
  }),
]
複製代碼
  1. html
// webpack.base.js
plugins: [...utils.htmlPlugins('./src/pages/*/index.html')]

// utils.js
function htmlPlugins(globPath) {
  var dirname, name
  return glob.sync(globPath).reduce((acc, entry) => {
    dirname = path.dirname(entry)
    name = dirname.slice(dirname.lastIndexOf('/') + 1)
    acc.push(new htmlWebpackPlugin(htmlConfig(name, name)))
    return acc
  }, [])
}

function htmlConfig(name, chunks) {
  return {
    template: `./src/pages/${name}/index.html`,
    filename: `${name}.html`,
    // favicon: './favicon.ico',
    // title: title,
    inject: true,
    chunks: [chunks],
    minify: devMode
      ? false
      : {
          removeComments: true,
          collapseWhitespace: true
        }
  }
}
複製代碼
  1. 圖片
// webpack.rules.js
rules: [
  {
    test: /\.(png|jpe?g|gif)(\?.*)?$/,
    use: [
      {
        loader: 'url-loader',
        options: {
          esModule: false,
          limit: 4 * 1024,
          name: 'img/[name].[hash:8].[ext]'
        }
      },
      {
        loader: 'img-loader',
        options: {
          plugins: [
            require('imagemin-pngquant')({
              speed: 2 // 1-11
            }),
            require('imagemin-mozjpeg')({
              quality: 80 // 1-100
            }),
            require('imagemin-gifsicle')({
              optimizationLevel: 1 // 1,2,3
            })
          ]
        }
      }
    ]
  },
  {
    test: /\.(svg)(\?.*)?$/,
    use: [
      {
        loader: 'url-loader',
        options: {
          name: 'img/[name].[hash:8].[ext]'
        }
      }
    ]
  },
]
複製代碼

用法:

background: url(~assets/index/icons/ic-star-16px.png);
複製代碼
import wukong from 'assets/index/wukong.jpg'
複製代碼
<img src="~assets/index/wukong.jpg" alt="wukong" />
複製代碼

這裏有有幾個點要注意:

  • url-loader 和 file-loader 若是配置了 limit,那麼小於這個 limit 值的圖片會被 url-loader 轉換成 base64,超過的圖片直接用 file-loader 處理。因此雖然規則裏沒出現 file-loader,但仍是要安裝。
  • html-loader 用於處理 html 文件,這裏主要是處理 html 文件裏的圖片。圖片會經過 url-loader 處理,處理完再給圖片 src 設置正確的路徑或 base64。而 html 中要使用 webpack 的 alias 配置,須要在前面加上 ~,而後 url-loader 要配置 esModule: false 纔不會出錯。
  1. 音視頻和字體
// webpack.rules.js
rules: [
  {
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
    loader: 'url-loader',
    options: {
      limit: 4 * 1024,
      name: '[name].[hash:8].[ext]',
      outputPath: 'media'
    }
  },
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    options: {
      limit: 4 * 1024,
      name: '[name].[hash:8].[ext]',
      outputPath: 'font'
    }
  }
]
複製代碼

devserver

// webpack.dev.js
devServer: {
  contentBase: utils.resolve('../src'), // 告訴服務器從哪一個目錄中提供內容
  publicPath: '/', // 此路徑下的打包文件可在瀏覽器中訪問
  port: '8090',
  overlay: true, // 瀏覽器頁面上顯示錯誤
  open: true, // 自動打開瀏覽器
  // stats: "errors-only", //stats: "errors-only"表示只打印錯誤:
  historyApiFallback: false, // 404 會被替代爲 index.html
  inline: true, // 內聯模式,實時刷新
  hot: true, // 開啓熱更新
  proxy: {
    '/api': {
      target: 'https://example.com/',
      changeOrigin: true,
      pathRewrite: {}
    }
  }
},
plugins: [
  //熱更新
  new webpack.HotModuleReplacementPlugin()
],
複製代碼
  1. 打包後文件的內存路徑 = devServer.contentBase + output.publicPath + output.filename,只能經過瀏覽器來訪問這個路由來訪問內存中的 bundle
  2. 對於 publicPath,有兩個用處:
  • 像以上的被 webpack-dev-server 做爲在內存中的輸出目錄。
  • 被其餘的 loader 插件所讀取,修改 url 地址等。

devtool

// webpack.dev.js
devtool: 'cheap-eval-source-map',

// webpack.prod.js
devtool: 'none',
複製代碼

此選項控制是否生成,以及如何生成 source map。不一樣選項之間,官網 有更詳細解釋和對比。

optimization

// webpack.prod.js
optimization: {
  runtimeChunk: {
    name: 'manifest'
  },
  splitChunks: {
    cacheGroups: {
      vendors: {
        name: 'vendors',
        test: /[\\\/]node_modules[\\\/]/,
        priority: -10,
        chunks: 'initial' // 只對入口文件處理
      },
      vendors: {
        name: 'chunk-common',
        minChunks: 2,
        priority: -20,
        chunks: 'initial',
        reuseExistingChunk: true
      }
    }
  }
},
複製代碼

runtimeChunk 和 splitChunks 主要優化的點在於瀏覽器緩存,若是不考慮,也能夠不加這個配置。

externals

// webpack.base.js
externals: {
  'jquery': 'window.jquery'
},
複製代碼

做用:防止將某些 import 的包 (package) 打包到 bundle 中,而是在運行時 (runtime) 再去從外部獲取這些擴展依賴。 沒加 externals 配置,jq 經過 cdn 加載,直接在本地使用 $('#id') 打包沒什麼問題。可是,若是你在本地使用了模塊化的 jq 插件,就加上面這個 externals 配置了。緣由以下:

;(function(window, factory) {
  if (typeof exports === 'object') {
    module.exports = factory(require('jQuery'))
  } else if (typeof define === 'function' && define.amd) {
    define(['jQuery'], factory)
  } else {
    factory()
  }
})(window, function($) {
  $.fn.green = function() {
    $(this).each(function() {
      $(this).css('color', 'green')
    })
  }
})
複製代碼

上面的代碼是一個簡單 jq 插件,採用了 UMD 模塊化方案。if (typeof exports === 'object') 這行代碼會被 webpack 解析爲 if (true),也就是說,webpack 編譯後的代碼,會執行 require('jquery'),而本地並無安裝 jq,因此會報錯,沒法打包成功。

ProvidePlugin

plugins: [
  // 自動加載模塊,無需 import 或 require
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    'window.jQuery': 'jquery'
  }),
]
複製代碼
相關文章
相關標籤/搜索