webpack從零開始配置大全

  • 因爲webpack5剛剛發佈,相關生態還不成熟,這裏以webpack4的版本爲例,
  • 爲了不插件版本的兼容問題,如下安裝個別npm依賴時我會指定版本號。
  • 本文僅做爲webpack學習之用,實際項目中仍是更推薦使用成熟的腳手架搭建,而後根據學習的成果再去定製改造。
  • 示例代碼已上傳github:webpack-demo

1、基礎

一、目錄文件

新建一個文件夾做爲示例項目,項目根目錄運行命令初始化package.jsoncss

npm init -y

而後按照如下目錄先建立空文件:
在這裏插入圖片描述html

二、入口entry、出口output

  • 安裝webpack依賴
npm i -D webpack@4.44.2 webpack-cli@3.3.12
  • /config/webpack.base.config.js 寫入內容:
const path = require('path')

module.exports = {
  entry: { // 入口配置
    app: './src/index.js'
  },
  output: { // 出口配置
      filename: 'js/[name].[contenthash:8].js',
      path: path.resolve(__dirname, '../dist'),
  }
}
  • package.json 裏寫入script命令:
"script": {
  "build": "webpack --config ./config/webpack.base.config.js"
}
  • 而後就能夠運行查看效果了:
npm run build
  • 正常的話,會在項目根目錄生成dist文件夾,裏面就是打包後的文件。

在這裏插入圖片描述

  • hashchunkhashcontenthash 的區別:

hash 是每次打包時從新生成,全部文件共用同一個hash;
chunkhash 根據不一樣的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值。在生產環境中將一些公共庫分離出來,只要公共庫不改變,生成的哈希值就不會變。
contenthash 和文件內容相關,內容不變,生成的哈希值就不變。node

三、html模板 html-webpack-plugin

使用 html-webpack-plugin 插件來配置html模板文件的關聯,這樣打包後的js、css等會自動引入到html中,就能夠訪問html文件查看效果了。react

  • 安裝插件:
npm i -D html-webpack-plugin@4.5.0
  • /config/webpack.base.config.js 添加plugins,配置html-webpack-plugin:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: ......,
  output: ......,
  // 在這裏添加
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      inject: 'body',
      hash: false
    }),
  ],
}
  • /public/index.html 寫入代碼:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1">
  <title>Title</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
  • /src/index.js 寫入代碼:
console.log('小王子')
  • 最後運行命令打包:npm run build,打包後會在dist下生成index.html,打開該html查看控制檯輸出效果。

四、js編譯 react、babel

用哪一個框架都行,目的都是學習webpack的配置,以react示例。webpack

  • 安裝react依賴:
npm i -S react react-dom
  • /src/index.js 替換爲如下代碼:
import React from 'react'
import ReactDOM from 'react-dom'

function App () {
  return (
    <div>
      <div className="test">小王子</div>
    </div>
  )
}

ReactDOM.render(
  <App/>, 
  document.getElementById('root')
)
  • 添加babel:

因爲react使用的jsx語法,不是js標準語言語法,因此須要藉助babel插件來轉碼,固然babel用處遠不止這些,還須要babel將es6+代碼轉爲兼容性更好的es5代碼。
安裝babel相關依賴:git

npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
  • babel.config.js 寫入代碼:
module.exports = {
  presets: [
    '@babel/preset-env', 
    '@babel/preset-react'
  ],
}
  • /config/webpack.base.config.js 添加module,配置babel的rules:
const path = require('path')

module.exports = {
  entry: ......,
  output: ......,
  plugins: ......,
  // 在這裏添加代碼
  module: {
    rules: [
      {
        test: /\.(js|jsx)?$/,
        options: {
          cacheDirectory: true
        },
        loader: 'babel-loader'
      }
    ]
  }
}
  • 運行 npm run build,而後打開打包後的index.html查看效果。

五、配置分離 webpack-merge

webpack配置裏能夠指定mode屬性來把運行環境劃分爲development和production,
使用webpack-merge插件能夠針對不一樣mode環境使用不一樣的webpack配置,插件幫咱們智能合併配置。es6

  • 安裝依賴:
npm i -D webpack-merge@4.2.2
  • /config/webpack.dev.config.js 寫入代碼:
const merge = require('webpack-merge')
const common = require('./webpack.base.config')

module.exports = merge(common, {
  mode: 'development',
  output: {
    filename: 'js/[name].js',
  },
})
  • /config/webpack.prod.config.js 寫入代碼:
const merge = require('webpack-merge')
const common = require('./webpack.base.config')

module.exports = merge(common, {
  mode: 'production',
})
  • package.json 裏修改scripts命令:
"scripts": {
  "start": "webpack --config ./config/webpack.dev.config.js",
  "build": "webpack --config ./config/webpack.prod.config.js"
},

注意build命令裏的webpack.base.config.js換成了webpack.prod.config.js。
這樣就分了開發環境和生產環境。github

六、清空目錄 clean-webpack-plugin

使用clean-webpack-plugin插件能夠在build打包以前自動刪除上次打包的dist文件夾,防止冗餘文件的產生。web

  • 安裝依賴:
npm i -D clean-webpack-plugin
  • /config/webpack.prod.config.js 裏添加plugins配置:
const merge = require('webpack-merge')
const common = require('./webpack.base.config')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = merge(common, {
  mode: 'production',
  // 在這裏添加代碼
  plugins: [
    new CleanWebpackPlugin(),
  ],
})
  • npm run build 查看dist文件夾還有沒有以前遺留的js文件。

七、熱更新 webpack-dev-server

使用webpack-dev-server插件,在webpack運行時自動啓動一個本地服務器運行打包後的html文件,配合熱更新,實現代碼改動後實時查看效果。chrome

  • 安裝依賴:
npm i -D webpack-dev-server@3.11.0
  • /config/webpack.dev.config.js 裏添加devServer和plugins配置:
const merge = require('webpack-merge')
const common = require('./webpack.base.config')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = merge(common, {
  mode: 'development',
  output: {
    filename: 'js/[name].[hash:8].js',
  },
  // 在這裏添加代碼
  devServer: {
    open: true,
    port: 9000,
    compress: true,
    hot: true,
    inline: true,
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ],
})
  • package.json 裏修改scripts的start命令:
"start": "webpack-dev-server --inline --progress --config ./config/webpack.dev.config.js",
  • npm start 查看效果。

八、源碼追蹤 devtool、source map

devtool用於配置source map選項,幫助咱們調試時追蹤原始源代碼,有多重source map格式供選擇,具體能夠參考文檔,綜合構建速度和使用效果,通常選擇 cheap-module-eval-source-map,各方面都比較均衡。

  • /config/webpack.dev.config.js 裏添加devtool配置:
module.exports = merge(common, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map', // 在這裏添加便可
  output: ......,
  devServer: ......,
  plugins: ......,
})
  • /src/index.js 裏添加一條console語句:
import React from 'react'
import ReactDOM from 'react-dom'

console.log(123) // 在這裏添加便可

function App () {
  ......
}
......
  • 在chrome控制檯找到打印結果行,點擊該行右側的文件路徑查看源代碼,經過對比添加和不添加devtool時的源碼來理解source map的做用。

九、樣式相關loader

style-loader 用於建立樣式標籤引入css代碼,不能單獨使用;
css-loader 用於解析css文件生成css代碼,給style-loader使用;
less-loader 用於將less文件轉換爲css文件,給css-loader使用;

  • 安裝依賴:
npm i -D style-loader css-loader less less-loader
  • src文件夾下建立文件index.less
@color: red;

.test {
  color: @color;
}
  • /src/index.js 引入該less使用:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.less'

function App () {
  return (
    <div>
      <div className="test">小王子</div>
    </div>
  )
}

ReactDOM.render(
  <App/>, 
  document.getElementById('root')
)
  • /config/webpack.base.config.js 裏配置loader:
module.exports = {
  module: {
    rules: [
      ......
      // 接上,追加如下代碼
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
    ]
  }
}

rules裏的use數組在解析時是按從右往左解析的,須要注意順序。

十、css工具集 postcss

postcss 是一個容許使用 JS 插件轉換樣式的工具集合;
postcss-loader 用於webpack中對css作進一步處理的loader;
autoprefixer 屬於postcss的一個插件,配合postcss-loader能夠自動給css樣式添加瀏覽器前綴,以兼容低版本瀏覽器;
browserlist 用於指定項目運行的目標瀏覽器範圍,能被autoprefixer和babel等識別,根據目標瀏覽器範圍作兼容適配。

  • 安裝依賴:
npm i -D postcss postcss-loader autoprefixer browserlist
  • /src/index.less添加樣式:
@color: red;

.test {
  color: @color;
  display: flex;
  justify-content: center;
}
  • /config/webpack.base.config.js 裏修改css和less的loader配置:
module.exports = {
  module: {
    rules: [
      ......
      // 修改css和less的loader配置
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
      },
    ]
  }
}
  • 項目根目錄新建文件 postcss.config.js
module.exports = {
  plugins: {
    autoprefixer: {}
  }
}
  • package.json 裏添加browserlist配置:
{
  "dependencies": ......,
  "devDependencies": ......,
  ---在這裏追加---
  "browserslist": [
    "> 1% in CN",
    "last 2 versions"
  ]
}
  • npm start 運行,chrome開發者工具查看文字的css樣式,看flex相關樣式是否自動加上了瀏覽器前綴。

十一、文件處理 file-loader、url-loader

file-loader用於打包靜態文件並將引入路徑和js關聯;
url-loader用於處理圖片資源的打包,低於指定大小時會將資源轉換爲base64格式使用,其餘狀況處理和file-loader同樣。

  • 安裝依賴:
npm i -D file-loader url-loader
  • /config/webpack.base.config.js 裏添加loader配置:
module.exports = {
  module: {
    rules: [
      ......
      // 接上,追加如下代碼
      {
        test: /\.(jpe?g|png|gif)$/i,
        options: {
          esModule: false,
          limit: 4096, // 配置低於4k的圖片會轉爲base64格式
        },
        loader: 'url-loader',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i, // 處理字體文件
        options: {
          esModule: false
        },
        loader: 'file-loader'
      },
    ]
  }
}

十二、css分離 mini-css-extract-plugin

mini-css-extract-plugin用於將打包後的css單獨抽離出來,webpack打包時默認是將css整合進js裏經過動態建立style標籤實現的,而這個插件將css剝離出來,能減小沒必要要的js代碼及dom操做,提高頁面加載性能。

  • 安裝依賴:
npm i -D mini-css-extract-plugin
  • /config/webpack.base.config.js 裏配置plugins和loader:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  plugins: [
    ......,
    // 接上,在這裏追加
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[id].[contenthash:8].css',
      ignoreOrder: true
    }),
  ],
  module: {
    rules: [
      // 修改css和less的loader,替換掉style-loader
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
      },
    ]
  }
}
  • npm run build 查看打包目錄是否生成單獨的css文件。

1三、快捷路徑 alias

alias是webpack內置支持的一個屬性,用來指定快捷路徑標識,配置後就能方便的書寫引入路徑。

  • /config/webpack.base.config.js裏配置:
module.exports = {
  entry: ......,
  output: ......,
  // 接上,在這裏追加
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../src'),
    }
  },
}
  • /src/index.js修改文件的引入路徑:
import './index.less' 替換爲 import '@/index.less'
  • npm start查看是否正常運行

2、進階

一、js壓縮 terser-webpack-plugin

terser-webpack-plugin 用於對js作代碼壓縮及代碼混淆等處理,對es6+支持更好,替代之前的uglifyjs-webpack-plugin。

  • 安裝依賴:
npm i -D terser-webpack-plugin@4.2.3
  • /config/webpack.prod.config.js 裏添加配置:
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = merge(common, {
  mode: 'production',
  plugins: ......,
  // 在這裏添加
  optimization: {
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({
        parallel: true, // 使用多進程提升構建速度
        extractComments: false, // 禁止生成license文件
        terserOptions: {
          compress: { // 刪除console.log代碼
            drop_console: true,
            pure_funcs: ['console.log']
          },
          output: {
            comments: false // 刪除註釋代碼
          }
        }
      }),
    ],
  },
})

二、css壓縮 optimize-css-assets-webpack-plugin

optimize-css-assets-webpack-plugin 插件用於對css文件作壓縮處理,默認使用cssnano壓縮。

  • 安裝依賴:
npm i -D optimize-css-assets-webpack-plugin
  • /config/webpack.prod.config.js 裏添加plugins:
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = merge(common, {
  plugins: [
    ......,
    // 接上,在這裏追加
    new OptimizeCssAssetsPlugin(),
  ]
})
  • npm run build 查看打包後的css文件是否已被壓縮。

三、chunk分離 splitChunks

splitChunks用於代碼分離,有利於性能優化。模塊是否分離的判斷原則:體積大、穩定不變。

  • 瀏覽器在加載文件後會將其緩存下來,下次加載該文件時直接從本地緩存裏讀取,加快訪問速度。
  • webpack打包默認會將import同步引入的代碼打包成一個文件,而使用splitChunks能夠將該文件分離成多個。
  • 分離穩定不變的代碼能保證每次打包後分離出來的文件保持不變,這樣分離後的文件就能被瀏覽器緩存且緩存不會在項目更新發布後失效,這就是splitChunks的主要做用。
  • /config/webpack.pord.config.js添加splitChunks:
module.exports = merge(common, {
  optimization: {
    ......,
    // 接上,在這裏追加
    splitChunks: {
      chunks: 'all',
      maxAsyncRequests: 8,
      maxInitialRequests: 6,
      minSize: 10000,
      cacheGroups: {
        react: { // 分離react和react-dom
          name: 'chunk-react',
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, // 匹配規則
          priority: 20 // 匹配優先級
        },
        vendors: { // 其餘npm依賴(生產環境)
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: { // 組件公共抽離
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  },
})
  • /config/webpack.dev.config.js裏添加chunkFilename:
module.exports = merge(common, {
  output: {
    filename: 'js/[name].js',
    // 在這裏添加
    chunkFilename: 'js/[name].js',
  },
})
  • /config/webpack.base.config.js裏添加chunkFilename:
module.exports = {
  output: {
    filename: 'js/[name].[contenthash:8].js',
    path: path.resolve(__dirname, '../dist'),
    // 在這裏添加
    chunkFilename: 'js/[name].[contenthash:8].js',
  },
}
  • chunkFilename就是用來配置splitChunks分離出來的文件名
  • npm run build查看打包後的文件,多打包幾回,對比分離出來的chunk文件名是否有變化。

四、自定義常量 DefinePlugin

DefinePlugin 是webpack內置的一個插件,容許建立一個在編譯時能夠配置的全局常量,配置後就能夠在代碼裏使用這個常量了。

  • /config/webpack.base.config.js 裏添加plugins:
const webpack = require('webpack')

module.exports = {
  plugins: [
    ......,
    // 接上,在這裏追加
    new webpack.DefinePlugin({
      VERSION_H5: +new Date() // 這裏添加了VERSION_H5
    }),
  ]
}

須要注意,若是給定義的常量賦值爲string類型時須要帶上原始引號,能夠經過單引號包裹雙引號的方式或經過JSON.stringify包裹,例如 '"abc"' 或 JSON.stringify('abc')

  • /src/index.js 裏添加一條打印語句:
// 找個合適的地方添加就行
console.log(VERSION_H5)
  • npm start 查看chrome控制檯輸出結果是否符合預期

五、樣式隔離 css modules

css modules是一種防止css樣式污染的模塊化解決方案。
接下來就配置.module.css或.module.less後綴的文件自動以css modules方式處理。

  • /config/webpack.base.config.js 裏配置loader:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// 爲了代碼簡潔,在這裏封裝了一下
const cssTest = /\.css$/
const lessTest = /\.less$/
const cssModuleTest = /\.module\.css$/
const lessModuleTest = /\.module\.less$/
const baseCssUse = [
  MiniCssExtractPlugin.loader, 
  'css-loader', 
  'postcss-loader'
]
const baseCssModuleUse = [
  MiniCssExtractPlugin.loader, 
  {
    loader: 'css-loader',
    options: {
      modules: {
        localIdentName: "[name]_[local]__[hash:5]"
      }
    },
  }, 
  'postcss-loader'
]

module.exports = {
  module: {
    rules: [
      ......,
      // 把以前的css和less的配置 替換成如下代碼
      {
        test: cssTest,
        exclude: cssModuleTest,
        use: baseCssUse
      },
      {
        test: lessTest,
        exclude: lessModuleTest,
        use: [...baseCssUse, 'less-loader']
      },
      {
        test: cssModuleTest,
        use: baseCssModuleUse
      },
      {
        test: lessModuleTest,
        use: [...baseCssModuleUse, 'less-loader']
      },
      ......,
    ]
  },
}
  • src目錄下新建index.module.less:
.name {
  text-decoration: line-through;
}
  • /src/index.js添加代碼:
import style from './index.module.less'

function App () {
  return (
    <div>
      <div className='test'>小王子</div>

      <div className={style.name}>Neo</div>

      <div>
        <img src={icon} alt=""/>
      </div>
    </div>
  )
}
  • 配置完後,npm start重啓項目,chrome控制檯查看元素及樣式效果。

六、兼容處理 polyfill

es6包含新的語法和新的api,新api是用更底層的語言實現的,新語法默承認以被babel降級處理,但新api默認不會處理,例如數組的find、Object.assign、promise等,須要配置polyfill來處理。
從babel v7.4版本開始,官方再也不推薦使用@babel/polyfill,更推薦直接使用core-js/stable和regenerator-runtime/runtime。

  • 安裝依賴:
npm i -S core-js regenerator-runtime
  • 在入口文件/src/index.js裏最頂部導入:
// 必須在入口文件最頂部導入
import "core-js/stable"
import "regenerator-runtime/runtime"

// 而後再導入其餘的
......
  • babel.config.js裏修改@babel/preset-env配置:
module.exports = {
  presets: [
    // 在這裏修改 @babel/preset-env 的配置
    [
      '@babel/preset-env',
      {
        modules: false,
        useBuiltIns: 'entry',
        corejs: {
          version: '3.8', // 你的core-js版本號前兩位
          proposals: true,
        },
      },
    ],
    // 其餘的保持不變
    ......
  ],
}
  • 以上是我我的推薦的配置方式,缺點是有全局命名空間的污染,但優勢是支持更全,其餘諸如 useBuiltIns: usage 以及 @babel/plugin-transform-runtime 方式的優缺點正好相反,參考文檔

七、代理 proxy

實際項目中,本地開發通常都會遇到接口跨域的問題,協議、域名、端口號 這三項任意一項不一致就會跨域,在devServer裏配置proxy代理能夠解決跨域問題。

  • 在配置代理以前,最好在你的全部api請求地址以前都加一個代理標識,用於代理的匹配攔截,告訴代理服務器哪些請求須要被代理,這裏就暫定代理標識爲/proxy
  • /config/webpack.dev.config.js裏配置devServer:
module.exports = merge(common, {
    devServer: {
      contentBase: path.resolve(__dirname, '../dist'),
      open: false,
      port: 9000,
      compress: true,
      hot: true,
      inline: true,
      proxy: {
          '/proxy': {
                target: 'https://192.111:8800',
                ws: true,
                changeOrigin: true,
                secure: false,
                pathRewrite: {
                  '^/proxy': ''
                }
           }
      }
    },
})
  • target 目標地址,有端口號的須要帶上端口號;
  • ws 配置是否支持 web socket;
  • changeOrigin 配置是否支持虛擬主機站點,我也不清楚具體啥意思;
  • secure 是否開啓安全驗證,目標地址爲https時需設置secure爲false;
  • pathRewrite 路徑重寫,上述是配置了代理後將/proxy替換爲空字符串,即實際接口地址再也不須要攜帶/proxy。
  • 更多配置參考http-proxy-middleware文檔。

八、腳本變量 cross-env

cross-env是一款運行跨平臺設置和使用環境變量的腳本。使用cross-env在scripts腳本命令裏配置自定義變量能夠實現命令行快捷切換環境配置的功能。

好比配置不一樣的測試環境使用不一樣的接口地址,傳統方式多是直接在devServer裏修改proxy代理地址的代碼,人工修改代碼容易出錯,在多人開發時也容易出現代碼衝突,若是使用cross-env配置的變量進行判斷設置對應的代理地址,經過切換scripts命令來切換代理就變的方便多了。

  • 安裝依賴:
npm i cross-env -D
  • package.json裏修改scripts:
"scripts": {
    "start": "npm run start:test1",
    "start:test1": "cross-env MY_TYPE=test1 webpack-dev-server --progress --config ./config/webpack.dev.config.js",
    "start:test2": "cross-env MY_TYPE=test2 webpack-dev-server --progress --config ./config/webpack.dev.config.js",
    "build": "webpack --config ./config/webpack.prod.config.js"
  },
  • 上述配置就是設置了MY_TYPE這個變量,兩個命令設置的值分別是test1和test2,運行npm run start:test1時在webpack配置文件裏就能夠經過process.env.MY_TYPE獲取到值。

cross-env自動把咱們設置的變量加在了process.env這個對象上,可是process.env只能在node環境裏獲取到,而在瀏覽器環境裏獲取不到。

不過還記得上面第4條介紹的DefinePlugin嗎,利用DefinePlugin咱們能夠添加個瀏覽器環境也能用的process.env對象,方式以下:

  • /config/webpack.base.config.js裏定義DefinePlugin插件配置:
new webpack.DefinePlugin({
  // VERSION_H5: +new Date(),
  'process.env': Object.keys(process.env).reduce(
    (env, key) => {
      env[key] = JSON.stringify(process.env[key]);
      return env;
    }, 
    {}
  )
}),
  • 而後在/src/index.js裏添加打印語句:
console.log(process.env.NODE_ENV)
console.log(process.env.MY_TYPE)
  • 分別運行命令npm run start:test1npm run start:test2查看瀏覽器打印結果。

查看更多

相關文章
相關標籤/搜索