Webpack入門 - 從0開始搭建項目配置

前言

webpack 做爲前端最知名的打包工具,可以把散落的模塊打包成一個完整的應用,大多數的知名框架 cli 都是基於 webpack 來編寫。這些 cli 爲使用者預設好各類處理配置,使用多了就會以爲理所固然,也就不在乎是內部是如何配置。若是脫離 cli 開發,可能就無從下手了。css

最近在開發一些單頁項目時,出於需求便開始從頭搭建項目配置,本文主要分享搭建時用到的配置。html

準備工做

快速生成 package.json:前端

npm init -y
複製代碼

必不可少的 webpack 和 webpack-cli:node

npm i webpack webpack-cli -D
複製代碼

入口、出口

webpack 的配置會統一放到配置文件中去管理,在根目錄下新建一個名爲 webpack.config.js 的文件:webpack

const path = require('path')

module.exports = {
  entry: {
    main: './src/js/main.js'
  },
  output: {
    // filename 定義打包的文件名稱
    // [name] 對應entry配置中的入口文件名稱(如上面的main)
    // [hash] 根據文件內容生成的一段隨機字符串
    filename: '[name].[hash].js',
    // path 定義整個打包文件夾的路徑,文件夾名爲 dist
    path: path.join(__dirname, 'dist')
  }
}
複製代碼

entry 配置入口,可配置多個入口。webpack 會從入口文件開始尋找相關依賴,進行解析和打包。es6

output 配置出口,多入口對應多出口,即入口配置多少個文件,打包出來也是對應的文件。web

修改 package.json 的 script 配置:npm

"scripts": {
  "build": "webpack --config webpack.config.js"
}
複製代碼

這樣一個最簡單的配置就完成了,經過命令行輸入 npm run build 就能夠實現打包。json

配置項智能提示

webpack 的配置項比較繁雜,對於不熟悉的同窗來講,若是在輸入配置項可以提供智能提示,那開發的效率和準確性會大大提升。瀏覽器

默認 VSCode 並不知道 webpack 配置對象的類型,經過 import 的方式導入 webpack 模塊中的 Configuration 類型後,在書寫配置項就會有智能提示了。

/** @type {import('webpack').Configuration} */
module.exports = {

}
複製代碼

環境變量

通常開發中會分開發和生產兩種環境,而 webpack 的一些配置也會隨環境的不一樣而變化。所以環境變量是很重要的一項功能,使用 cross-env 模塊能夠爲配置文件注入環境變量。

安裝模塊 cross-env

npm i cross-env -D
複製代碼

修改 package.json 的命令傳入變量:

"scripts": {
  "build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
複製代碼

在配置文件裏,就能夠這樣使用傳入的變量:

module.exports = {
  devtool: process.env.NODE_ENV === 'production' ? 'none' : 'source-map'
}
複製代碼

同理,在項目的 js 內也可使用該變量。

設置 source-map

該選項能設置不一樣類型的 source-map 。代碼通過壓縮後,一旦報錯不能準肯定位到具體位置,而 source-map 就像一個地圖, 可以對應源代碼的位置。這個選項可以幫助開發者加強調試過程,準肯定位錯誤。

爲了體驗它的做用,我在源代碼中故意輸出一個不存在的變量,模擬線上錯誤:

在預覽時,觸發錯誤:

很明顯錯誤的行數是不對應的,下面設置 devtool 讓 webpack 在打包後輸出 source-map 文件,用於定位錯誤。

module.exports = {
  devtool: 'source-map'
}
複製代碼

再次觸發錯誤,source-map 文件起做用準肯定位到代碼錯誤的行數。

source-map 通常只在開發環境用於調試,上線時絕對不能帶有 source-map 文件,這樣會暴露源代碼。下面經過環境變量來正確設置 devtool 選項。

module.exports = {
  devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'
}
複製代碼

devtool 的可選項不少,它的品質和生成速度有關聯,好比只定位到某個文件,或者定位到某行某列,相應的生成速度會快或更慢。能夠根據你的需求進行選擇,更多可選值請查看 webpack 文檔

loader 與 plugin

loader 與 plugin 是 webpack 的靈魂。若是把 webpack 比做成一個食品加工廠,那麼 loader 就像不少條流水線,對食品原料進行加工處理。plugins 則是在原有的功能上,添加其餘功能。

loader 基本用法

loader 配置在 module.rules 屬性上。

module.exports = {
  module: {
    rules: []
  }
}
複製代碼

下面來簡單瞭解下 loader 的規則。通常經常使用的就是 testuse 兩個屬性:

  1. test 屬性,用於標識出應該被對應的 loader 進行轉換的某個或某些文件。
  2. use 屬性,表示進行轉換時,應該使用哪一個 loader。
rules: [
  {
    test: /\.css$/,
    use: ['css-loader']
  }
]
// 也能夠寫爲
rules: [
  {
    test: /\.css$/,
    loader: 'css-loader'
  }
]
複製代碼

上面例子是匹配以 .css 結尾的文件,使用 css-loader 解析。

當有些 loader 可傳入配置時,能夠改成對象的形式:

rules: [
  {
    test: /\.css$/,
    user: [
      {
        loader: 'css-loader',
        options: {}
      }
    ]
  }
]
複製代碼

最後須要記住多個 loader 的執行是嚴格區分前後順序的,從右到左,從下到上。

plugin 基本用法

plugin 配置在 plugins 屬性上。

module.exports = {
  plugins: []
}
複製代碼

通常 plugin 會暴露一個構造函數,經過 new 的方式使用。plugin 的參數則在調用函數時傳入。plugin 命名通常是將按照原名轉爲大駝峯。

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  plugin: [
    new CleanWebpackPlugin()
  ]
}
複製代碼

生成 html 文件

沒有通過任何配置的 webpack 打包出來只有 js 文件,使用插件 html-webpack-plugin 能夠自定義一個 html 文件做爲模版,最終 html 會被打包到 dist 中,而 js 也會被引入其中。

安裝 html-webpack-plugin:

npm i html-webpack-plugin -D
複製代碼

配置 plugins:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    // 使用html模版
    new HtmlWebpackPlugin({
      // 配置html標題
      title: 'home',
      // 模版路徑
      template: './src/index.html',
      // 壓縮
      minify: true
    })
  ]
}
複製代碼

此時想要配置的標題生效還須要在 html 中爲 title 標籤插值:

<title><%= htmlWebpackPlugin.options.title %></title>
複製代碼

除了 title 外,還能夠配置諸如 meta 標籤等。

多頁面

上面的使用方法,在打包後只會有一個 html。對於多頁面的需求其實也很簡單,有多少個頁面就 new 幾回 htmlWebpackPlugin

可是須要注意一點,入口配置的 js 是會被所有引入到 html 的。若是想某個 js 對應某個 html,能夠配置插件的 chunks 選項。並且須要配置 filename 屬性,由於 filename 屬性默認是 index.html,同名會被覆蓋。

module.exports = {
  entry: {
    main: './src/js/main.js',
    news: './src/js/news.js'
  },
  output: {
    filename: '[name].[hash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      minify: true,
      chunks: ['main']
    }),
    new HtmlWebpackPlugin({
      template: './src/news.html',
      filename: 'news.html',
      minify: true,
      chunks: ['news']
    }),
  ]
}
複製代碼

es6轉es5

安裝 babel 的相關模塊:

npm i babel-loader @babel/core @babel/preset-env -D
複製代碼

@babel/core 是 babel 的核心模塊,@babel/preset-env 內預設不一樣環境的語法轉換插件,默認將 es6 轉 es5。

根目錄下建立 .babelrc 文件:

{
  "presets": ["@babel/preset-env"]
}
複製代碼

配置 loader:

rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/, 
    loader: 'babel-loader'
  }
]
複製代碼

解析 css

安裝 loader:

npm i css-loader style-loader -D
複製代碼

css-loader 只負責解析 css 文件,一般須要配合 style-loader 將解析的內容插入到頁面,讓樣式生效。順序是先解析後插入,因此 css-loader 放在最右邊,第一個先執行。

rules: [
  {
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
  }
]
複製代碼

但要注意最終打包出來的並非css文件,而是js。它是經過建立 style 標籤去插入樣式。

分離css

通過上面的 css 解析,打包出來的樣式會混在 js 中。某些場景下,咱們但願把 css 單獨打包出來,這時可使用 mini-css-extract-plugin 分離 css。

安裝 mini-css-extract-plugin:

npm i mini-css-extract-plugin -D
複製代碼

配置 plugins:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css',
      chunkFilename: 'css/[id].[hash].css'
    })
  ]
}
複製代碼

配置 loader:

rules: [
  {
    test: /\.css$/,
    use: [
      // 插入到頁面中
      'style-loader',
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          // 爲外部資源(如圖像,文件等)指定自定義公共路徑
          publicPath: '../',
        }
      },
      'css-loader',
    ]
  },
]
複製代碼

通過上面配置後,打包後 css 就會被分離出來。

但要注意若是 css 文件不是很大的話,分離出來效果可能會拔苗助長,由於這樣會多一次文件請求,通常來講單個 css 文件超過 200kb 再考慮分離。

css瀏覽器兼容前綴

安裝相關依賴:

npm i postcss postcss-loader autoprefixer -D
複製代碼

項目根目錄下新建 postcss.config.js:

module.exports = {
  plugins: [
    require('autoprefixer')()
  ]
}
複製代碼

package.json 新增 browserslist 配置:

{
  "browserslist": [
    "defaults",
    "not ie < 11",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]
}
複製代碼

最後配置 postcss-loader

rules: [
  {
    test: /\.css$/,
    use: [
      'style-loader', 
      'css-loader',
      'postcss-loader'
     ]
  }
]
複製代碼

處理先後對比:

壓縮 css

webpack 內部默認只對 js 文件進行壓縮。css 的壓縮可使用 optimize-css-assets-webpack-plugin 來完成。

安裝 optimize-css-assets-webpack-plugin:

npm i optimize-css-assets-webpack-plugin -D
複製代碼

配置 plugins:

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  plugins: [
    new OptimizeCssAssetsWebpackPlugin()
  ]
}
複製代碼

壓縮結果:

解析圖片

項目中確定少不了圖片,對於圖片資源,webpack 也有相應的 url-loader 來解析。url-loader 除了解析圖片外,還能夠將比較小的圖片能夠轉爲base64,減小線上對圖片的請求。

安裝 url-loader:

npm i url-loader -D
複製代碼

配置 loader:

rules: [
  {
    test: /\.(jpg|png|jpeg|gif)$/,
      use: [{
        loader: 'url-loader',
        // 小於50k的圖片轉爲base64,超出的打包到images文件夾
        options: {
          limit: 1024 * 50,
          outputPath: './images/',
          pulbicPath: './images/'
        }
    }]
  }
]
複製代碼

配置完成後,只須要在入口文件內引入圖片使用,webpack 就能夠幫助咱們把圖片打包出來了。

但有時候,圖片連接是直接寫到 html 中,這種狀況 url-loader 沒法解析。不慌,使用 html-loader 能完成這項需求。

rules: [
  {
    test: /\.(html)$/,
    use: [{
      loader: 'html-loader',
      options: {
        // 壓縮html模板空格
        minimize: true,
        attributes: {
          // 配置須要解析的屬性和標籤
          list: [{
            tag: 'img',
            attribute: 'src',
            type: 'src',
          }]
        },
      }
    }]
  },
]
複製代碼

注意這裏 html-loader 只是起到解析的做用,須要配合 url-loader 或者 file-loader 去使用。也就是說解析模板的圖片連接後,仍是會走上面所配置的 url-loader 的流程。

還有一點,使用 html-loader 後, html-webpack-plugin 在 html 中的插值會失效。

其餘類型資源解析

解析其餘資源和上面差很少,不過這裏用到的是 file-loaderfile-loaderurl-loader 主要是將文件上的 import / require() 引入的資源解析爲url,並將該資源發送到輸出目錄,區別在於 url-loader 能將資源轉爲 base64。

安裝 file-loader:

npm i file-loader -D
複製代碼

配置 loader:

rules: [
  {
    test:/\.(mp3)$/,
    use: [{
      loader: 'file-loader',
      options: {
        outputPath: './music/',
        pulbicPath: './music/'
      }
    }]
  },
  {
    test:/\.(mp4)$/,
    use: [{
      loader: 'file-loader',
      options: {
        outputPath: './video/',
        pulbicPath: './video/'
      }
    }]
  }
]
複製代碼

上面只是列舉了部分,須要解析其餘類型資源,參照上面的格式添加配置。

解析 html 中的其餘類型資源也和上面同理,使用 html-loader 配置對象的標籤和屬性便可。

devServer 提升開發效率

每次想運行項目時,都須要 build 完再去預覽,這樣的開發效率很低。

官方爲此提供了插件 webpack-dev-server,它能夠本地開啓一個服務器,經過訪問本地服務器來預覽項目,當項目文件發生變化時會熱更新,無需再去手動刷新,以此提升開發效率。

安裝 webpack-dev-server

npm i webpack-dev-server -D
複製代碼

配置 webpack.config.js

const path = require('path')

module.exports = {
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    // 默認 8080
    port: 8000,
    compress: true,
    open: true,
  }
}
複製代碼

webpack-dev-server 用法和其餘插件不一樣,它能夠不添加到 plugins,只需將配置添加到 devServer 屬性下便可。

添加啓動命令 package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve"
  },
}
複製代碼

命令行運行 npm run dev,就能夠感覺到飛通常的體驗。

複製文件到 dist

對於一些不須要通過解析的文件,在打包後也想將它放到 dist 中,可使用 copy-webpack-plugin

安裝 copy-webpack-plugin:

npm i copy-webpack-plugin -D
複製代碼

配置 plugins:

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

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          // 資源路徑
          from: 'src/json',
          // 目標文件夾
          to: 'json',
        }
      ]
    }),
  ]
}
複製代碼

打包前清除舊 dist

打包後文件通常都會帶有哈希值,它是根據文件的內容來生成的。因爲名稱不一樣,可能會致使 dist 殘留有上一次打包的文件,若是每次都手動去清除顯得不那麼智能。利用 clean-webpack-plugin 能夠幫助咱們將上一次的 dist 清除,保證無冗餘文件。

安裝 clean-webpack-plugin

npm i clean-webpack-plugin -D
複製代碼

配置 plugins:

const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  plugins: [
    new CleanWebpackPlugin()
  ]
}
複製代碼

插件會默認清除 output.path 的文件夾。

自定義壓縮選項

webpack 從 v4.26.0 開始內置的壓縮插件變爲 terser-webpack-plugin。若是沒有其餘需求,自定義壓縮插件也儘可能保持與官方的一致。

安裝 terser-webpack-plugin:

npm i terser-webpack-plugin -D
複製代碼

配置 plugins:

const TerserPlugin = reuqire('terser-webpack-plugin')

module.exports = {
  optimization: {
    // 默認爲 true
    minimize: true,
    minimizer: [
        new TerserPlugin()
    ]
  },
}
複製代碼

插件壓縮配置能夠查閱 terser-webpack-plugin 文檔,在調用時自定義選項。

添加到 minimizer 和 plugins 的區別

像上面的 css 壓縮插件也能夠添加到 optimization.minimizer。與配置到 plugins 的區別是,配置到 plugins 的插件在任何狀況都會去執行,而配置到 minimizer 中,只有在 minimize 屬性開啓時纔會工做。

這樣作的目的便於經過 minimize 屬性來統一控制壓縮。

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = reuqire('terser-webpack-plugin')

module.exports = {
  optimization: {
    // 默認爲 true
    minimize: true,
    minimizer: [
        new TerserPlugin(),
        new OptimizeCssAssetsWebpackPlugin()
    ]
  },
}
複製代碼

注意若是你提供 minimizer 選項而沒有使用 js 壓縮插件,即便 webpack 內置 js 壓縮,打包出來的 js 也不會被壓縮。由於 webpack 壓縮配置會被 minimizer 覆蓋。

排查錯誤的建議

在使用 webpack 的過程當中,這玩意偶爾會有些奇奇怪怪的報錯。

下面是我遇到的一些錯誤以及解決方法(僅供參考並非萬能法則):

  1. 一些 loader 和 plugin 在使用時,會依賴 webpack 的版本。若是使用過程發生錯誤,檢查是否有版本不兼容的問題,能夠嘗試降一個版本。
  2. 從新安裝依賴,有可能下載過程當中,一些依賴會沒裝上。
  3. 查看使用文檔,不一樣版本所傳入的選項屬性可能會不同(被坑過) 。

還有注意控制檯的提示,通常根據錯誤提示都能猜出大概是什麼問題。

依賴版本和完整配置

項目結構:

依賴版本:

{
    "@babel/core": "^7.12.3",
    "@babel/preset-env": "^7.12.1",
    "autoprefixer": "^10.0.1",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^6.3.0",
    "cross-env": "^7.0.2",
    "css-loader": "^5.0.0",
    "file-loader": "^6.2.0",
    "html-loader": "^1.3.2",
    "html-webpack-plugin": "^4.5.0",
    "mini-css-extract-plugin": "^0.9.0",
    "postcss": "^8.1.6",
    "postcss-loader": "^4.0.4",
    "style-loader": "^2.0.0",
    "url-loader": "^4.1.1",
    "webpack": "^4.44.2",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.0"
}
複製代碼

webpack.config.js:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

/** @type {import('webpack').Configuration} */
module.exports = {
  devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none',
  entry: {
    main: './src/js/main.js'
  },
  output: {
    filename: '[name].[hash].js',
    path: path.join(__dirname, 'dist')
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 8000,
    compress: true,
    open: true,
  },
  plugins: [
    // 清除上一次的打包內容
    new CleanWebpackPlugin(),
    // 複製文件到dist
    new CopyPlugin({
      patterns: [
        {
          from: 'src/music',
          to: 'music',
        }
      ]
    }),
    // 使用html模版
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: true
    }),
    // 分離css
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css',
      chunkFilename: 'css/[id].[hash].css'
    }),
    // 壓縮css
    new OptimizeCssAssetsWebpackPlugin()
  ],
  module: {
    rules: [
      // 解析js(es6轉es5)
      {
        test: /\.js$/,
        exclude: /node_modules/, 
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../',
            }
          },
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(html)$/,
        use: [{
          // 主要爲了解析html中的img圖片路徑 須要配合url-loader或file-loader使用
          loader: 'html-loader',
          options: {
            attributes: {
              list: [{
                tag: 'img',
                attribute: 'src',
                type: 'src',
              },{
                tag: 'source',
                attribute: 'src',
                type: 'src',
              }]
            },
            minimize: true
          }
        }]
      },
      // 解析圖片
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: [{
          loader: 'url-loader',
          // 小於50k的圖片轉爲base64,超出的打包到images文件夾
          options: {
            limit: 1024 * 50,
            outputPath: './images/',
            pulbicPath: './images/'
          }
        }]
      },
      // 解析其餘類型文件(如字體)
      {
        test: /\.(eot|ttf)$/,
        use: ['file-loader']
      },
      {
        test: /\.(mp3)$/,
        use: [{
          loader: 'file-loader',
          options: {
            outputPath: './music/',
            pulbicPath: './music/'
          }
        }]
      }
    ]
  },
}
複製代碼

最後

因爲單頁項目簡單,配置項比較樸實無華,本文主要是些基礎配置。不過套路都差很少,根據項目的需求去選擇 loader 和 plugin。更多的仍是要了解這些插件的做用和使用方法,以及其餘經常使用的插件。

相關文章
相關標籤/搜索