webpack 基礎配置解析

針對webpack,是你們(前端開發)在平常的開發中都會碰見的,經過書寫的方式輸出,學習到的關於前端工程化的小知識點的總結和學習,造成本身的知識體系css

概念

webpack官網定義:html

webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。

webpack 資源打包

在開始瞭解webpack配置前,首先須要理解四個核心概念前端

  1. 入口(entry):webpack 應該使用哪一個模塊,來做爲構建其內部依賴圖的開始。
  2. 輸出(output):webpack 在哪裏輸出它所建立的 bundles,以及如何命名這些文件。
  3. loader:可以去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
  4. 插件(plugins):用於執行範圍更廣的任務。

安裝及構建

// npm 安裝
npm install webpack webpack-cli -g

// yarn 安裝
yarn global add webpack webpack-cli

安裝好後,能夠在不適用配置文件的方法,直接對文件進行打包:
webpack <entry> [<entry>] -o <output>vue

新建一個項目,就一個入口文件,測試webpack打包:
項目結構node

運行打包命令:
webpack index.js
webpack構建jquery

在這裏咱們會看見一個WARNING的信息,這是由於沒有設置mode,咱們只須要加一個參數-p便可:webpack

webpack -p index

webpack構建

這樣默認會生成一個dist文件夾,裏面有個main.js文件:
webpack構建web

有了入口文件咱們還須要經過命令行定義一下輸入路徑dist/bundle.js面試

webpack -p index.js -o dist/bundle.js

webpack構建

webpack 配置文件

命令行的打包構建方式僅限於簡單的項目,若是在生產中,項目複雜,多個入口,咱們就不可能每次打包都輸入一連串的入口文件地址,也難以記住;所以通常項目中都使用配置文件來進行打包;配置文件的命令方式以下:express

webpack [--config webpack.config.js]

配置文件默認的名稱就是webpack.config.js,一個項目中常常會有多套配置文件,咱們能夠針對不一樣環境配置不一樣的額文件,經過--config來進行更換:

// 開發環境
webpack --config webpack.config.dev.js

// 生產環境
webpack --config webpack.config.prod.js

多種配置類型

config配置文件經過module.exports導出一個配置對象:

// webpack.config.js
const path = require('path')

const resolve = function (dir) {
    return path.resolve(__dirname, dir)
}

module.exports = {
    entry: {
        app: resolve('../index.js')
    },
    output: {
        filename: '[name].[hash:8].js',
        path: resolve('../dist')
    },
}

除了導出爲對象,還能夠導出爲一個函數,函數中會帶入命令行中傳入的環境變量等參數,這樣能夠更方便的對環境變量進行配置;好比咱們能夠經過ENV來區分不一樣環境:

const path = require('path')

const resolve = function (dir) {
    return path.resolve(__dirname, dir)
}

module.exports = function(ENV, argv) {
    return {
        // 其餘配置
        entry: resolve('../index.js'),
        output: {}
    }
}

還能夠導出爲一個Promise,用於異步加載配置,好比能夠動態加載入口文件:

entry: () => './demo'

或

entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))

入口

正如在上面提到的,入口是整個依賴關係的起點入口;咱們經常使用的單入口配置是一個頁面的入口:

module.exports = {
    entry: resolve('../index.js')
}

可是咱們項目中可能不止一個模塊,所以須要將多個依賴文件一塊兒注入,這時就須要用到數組了:

module.exports = {
    entry: [
        '@babel/polyfill',
        resolve('../index.js')
    ]
}

若是咱們項目中有多個入口起點,則就須要用到對象形式了:

// webpack 就會構建兩個不一樣的依賴關係
module.exports = {
    entry: {
        app: resolve('../index.js'),
        share: resolve('../share.js')
    }
}

輸出

output選項用來控制webpack如何輸入編譯後的文件模塊;雖然能夠有多個entry,可是隻能配置一個output

module.exports = {
    entry: resolve('../index.js'),
    output: {
        filename: 'index.js',
        path: resolve('../dist')
    },
}

這裏咱們配置了一個單入口,輸出也就是index.js;可是若是存在多入口的模式就行不通了,webpack會提示Conflict: Multiple chunks emit assets to the same filename,即多個文件資源有相同的文件名稱;webpack提供了佔位符來確保每個輸出的文件都有惟一的名稱:

module.exports = {
    entry: {
        app: resolve('../index.js'),
        share: resolve('../index.js'),
    },
    output: {
        filename: '[name].bundle.js',
        path: resolve('../dist')
    },
}

這樣webpack打包出來的文件就會按照入口文件的名稱來進行分別打包生成三個不一樣的bundle文件;還有如下不一樣的佔位符字符串:

佔位符 描述
[hash] 模塊標識符(module identifier)的 hash
[chunkhash] chunk 內容的 hash
[name] 模塊名稱
[id] 模塊標識符
[query] 模塊的 query,例如,文件名 ? 後面的字符串

在這裏引入modulechunkbundle的概念,上面代碼中也常常會看到有這兩個名詞的出現,那麼他們三者到底有什麼區別呢?首先咱們發現module是常常出如今咱們的代碼中,好比module.exports;而chunk常常和entry一塊兒出現,bundle老是和output一塊兒出現。

  • module:咱們寫的源碼,不管是commonjs仍是amdjs,均可以理解爲一個個的module
  • chunk:當咱們寫的module源文件傳到webpack進行打包時,webpack會根據文件引用關係生成chunk文件, webpack 會對這些chunk文件進行一些操做
  • bundle:webpack處理好chunk文件後,最後會輸出bundle文件,這個bundle文件包含了通過加載和編譯的最終源文件,因此它能夠直接在瀏覽器中運行。

咱們經過下面這張圖看能夠加深對這三個概念的理解:
enter description here

hash、chunkhash、contenthash

理解了chunk的概念,相信上面表中chunkhash和hash的區別也很容易理解了;

  • hash:是跟整個項目的構建相關,只要項目裏有文件更改,整個項目構建的hash值都會更改,而且所有文件都共用相同的hash值。
  • chunkhash:跟入口文件的構建有關,根據入口文件構建對應的chunk,生成每一個chunk對應的hash;入口文件更改,對應chunk的hash值會更改。
  • contenthash:跟文件內容自己相關,根據文件內容建立出惟一hash,也就是說文件內容更改,hash就更改。

模式

在webpack2和webpack3中咱們須要手動加入插件來進行代碼的壓縮、環境變量的定義,還須要注意環境的判斷,十分的繁瑣;在webpack4中直接提供了模式這一配置,開箱便可用;若是忽略配置,webpack還會發出警告。

module.exports = {
    mode: 'development/production'
}

開發模式是告訴webpack,我如今是開發狀態,也就是打包出來的內容要對開發友好,便於代碼調試以及實現瀏覽器實時更新。

生產模式不用對開發友好,只須要關注打包的性能和生成更小體積的bundle。看到這裏用到了不少Plugin,不用慌,下面咱們會一一解釋他們的做用。

相信不少童鞋都曾有過疑問,爲何這邊DefinePlugin定義環境變量的時候要用JSON.stringify("production"),直接用"production"不是更簡單嗎?

咱們首先來看下JSON.stringify("production")生成了什麼;運行結果是""production"",注意這裏,並非你眼睛花了或者屏幕上有小黑點,結果確實比"production"多嵌套了一層引號

咱們能夠簡單的把DefinePlugin這個插件理解爲將代碼裏的全部process.env.NODE_ENV替換爲字符串中的內容。假如咱們在代碼中有以下判斷環境的代碼:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      "process.env.NODE_ENV": "production"
    }),
  ]
}
// index.js
if (process.env.NODE_ENV === 'production') {
    console.log('production');
}

這樣生成出來的代碼就會編譯成這樣:

//dist/bundle.js
//代碼中並無定義production變量
if (production === 'production') {
    console.log('production');
}

可是咱們代碼中可能並無定義production變量,所以會致使代碼直接報錯,因此咱們須要經過JSON.stringify來包裹一層:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      //"process.env.NODE_ENV": JSON.stringify("production")
      //至關於
      "process.env.NODE_ENV": '"production"'
    }),
  ]
}
//dist/bundle.js
if ("production" === 'production') {
    console.log('production');
}

生成HTML文件(html-webpack-plugin)

在上面的代碼中咱們發現都是手動來生成index.html,而後引入打包後的bundle文件,可是這樣太過繁瑣,並且若是生成的bundle文件引入了hash值,每次生成的文件名稱不同,所以咱們須要一個自動生成html的插件;首先咱們須要安裝這個插件:
yarn add html-webpack-plugin -D 或者 npm install html-webpack-plugin -D

使用:

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

module.exports = {
    // 其餘代碼
    plugins: [
        new HtmlWebpackPlugin({
            // 模板文件
            template: resolve('../public/index.html'),
            // 生成的html名稱
            filename: 'index.html',
            // icon
            favicon: resolve('../public/logo.ico')
        }),
    ]
}

webpack loader

loader 用於對模塊的源代碼進行轉換。默認webpack只能識別commonjs代碼,可是咱們在代碼中會引入好比vue、ts、less等文件,webpack就處理不過來了;loader拓展了webpack處理多種文件類型的能力,將這些文件轉換成瀏覽器可以渲染的js、css。

module.rules容許咱們配置多個loader,可以很清晰的看出當前文件類型應用了哪些loader。

module.exports = {
      module: {
            rules: [
                  { test: /\.css$/, use: 'css-loader' },
                  { test: /\.ts$/, use: 'ts-loader' }
            ]
      }
};

loader 特性

  • loader 支持鏈式傳遞。可以對資源使用流水線(pipeline)。一組鏈式的 loader 將按照相反的順序執行。loader 鏈中的第一個 loader 返回值給下一個 loader。在最後一個 loader,返回 webpack 所預期的 JavaScript。
  • loader 能夠是同步的,也能夠是異步的。
  • loader 運行在 Node.js 中,而且可以執行任何可能的操做。
  • loader 接收查詢參數。用於對 loader 傳遞配置。
  • loader 也可以使用 options 對象進行配置。
  • 除了使用 package.json 常見的 main 屬性,還能夠將普通的 npm 模塊導出爲 loader,作法是在 package.json 裏定義一個 loader 字段。
  • 插件(plugin)能夠爲 loader 帶來更多特性。
  • loader 可以產生額外的任意文件。

loader 經過(loader)預處理函數,爲 JavaScript 生態系統提供了更多能力。 用戶如今能夠更加靈活地引入細粒度邏輯,例如壓縮、打包、語言翻譯和其餘更多。

babel-loader

兼容低版本瀏覽器的痛相信不少童鞋都經歷過,寫完代碼發現本身的js代碼不能運行在IE10或者IE11上,而後嘗試着引入各類polyfill;babel的出現給咱們提供了便利,將高版本的ES6甚至ES7轉爲ES5;咱們首先安裝babel所須要的依賴:
yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime

因爲babel-loader的轉譯速度很慢,在後面咱們加入了時間插件後能夠看到每一個loader的耗時,babel-loader是最耗時間;所以咱們要儘量少的使用babel來轉譯文件,正則上使用$來進行精確匹配,經過exclude將node_modules中的文件進行排除,include將只匹配src中的文件;能夠看出來include的範圍比exclude更縮小更精確,所以也是推薦使用include。

// 省略其餘代碼
module: {
      rules: [
            {
                  test: /\.js$/,
                  exclude: /node_modules/,
                  include: [resolve('src')]
                  use: {
                    loader: 'babel-loader',
                    options: {
                      presets: [
                        ['@babel/preset-env', { targets: "defaults" }]
                      ],
                      plugins: ['@babel/plugin-proposal-class-properties']
                    }
              }
            }
      ]
}

file-loader 和 url-loader

file-loaderurl-loader都是用來處理圖片、字體圖標等文件;url-loader工做時分兩種狀況:當文件大小小於limit參數,url-loader將文件轉爲base-64編碼,用於減小http請求;當文件大小大於limit參數時,調用file-loader進行處理;所以咱們優先使用url-loader

module: {
        rules: [
            {
                test: /\.(jpe?g|png|gif)$/i, //圖片文件
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            // 10K
                            limit: 1024,
                            //資源路徑
                            outputPath: resolve('../dist/images')
                        },
                    }
                ],
                exclude: /node_modules/
            },
        ]
    }

搭建webpack開發環境

在上面咱們都是經過命令行打包生成 dist 文件,而後直接打開html或者經過static-server來查看頁面的;可是開發中咱們寫完代碼每次都來打包會嚴重影響開發的效率,咱們指望的是寫完代碼後當即就可以看到頁面的效果;webpack-dev-server就很好的提供了一個簡單的web服務器,可以實時從新加載。

webpack-dev-server的用法和wepack同樣,只不過他會額外啓動一個express的服務器。咱們在項目中webpack.config.dev.js配置文件對開發環境進行一個配置:

module.exports = {
    mode: 'development',
    plugins: [
        new Webpack.HotModuleReplacementPlugin()
    ],
    devtool: 'cheap-module-eval-source-map',
    devServer: {
        // 端口
        port: 3300,
         // 啓用模塊熱替換
        hot: true,
        // 自動打開瀏覽器
        open: true,
        // 設置代理
         proxy:{
             "/api/**":{
                 "target":"http://127.0.0.1:8075/",
                 "changeOrigin": true
            }
        }
    }
}

經過命令行webpack-dev-server來啓動服務器,啓動後咱們發現根目錄並無生成任何文件,由於webpack打包到了內存中,不生成文件的緣由在於訪問內存中的代碼比訪問文件中的代碼更快。

咱們在public/index.html的頁面上有時候會引用一些本地的靜態文件,直接打開頁面的會發現這些靜態文件的引用失效了,咱們能夠修改server的工做目錄,同時指定多個靜態資源的目錄:

contentBase: [
  path.join(__dirname, "public"),
  path.join(__dirname, "assets")
]

熱更新(Hot Module Replacemen簡稱HMR)是在對代碼進行修改並保存以後,webpack對代碼從新打包,而且將新的模塊發送到瀏覽器端,瀏覽器經過新的模塊替換老的模塊,這樣就能在不刷新瀏覽器的前提下實現頁面的更新。

webpack plugins

上面介紹了DefinePlugin、HtmlWebpackPlugin等不少插件,咱們發現這些插件都可以不一樣程度的影響着webpack的構建過程,下面還有一些經常使用的插件:

clean-webpack-plugin
clean-webpack-plugin用於在打包前清理上一次項目生成的bundle文件,它會根據output.path自動清理文件夾;這個插件在生產環境用的頻率很是高,由於生產環境常常會經過hash生成不少bundle文件,若是不進行清理的話每次都會生成新的,致使文件夾很是龐大。

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

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
    ],
}

mini-css-extract-plugin

咱們在使用webpack構建工具的時候,經過style-loader,能夠把解析出來的css經過js插入內部樣式表的方式到頁面中,mini-css-extract-plugin插件也是用來提取css到單獨的文件的,該插件有個前提條件,只能用於webpack 4及以上的版本,因此若是使用的webpack版本低於4,,那仍是用回extract-text-webpack-plugin插件。

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
    // 省略其餘代碼
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    {
                        loader: dev ? 'style-loader': MiniCssExtractPlugin.loader
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader'
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].[hash:8].css",
        })
    ]
}

copy-webpack-plugin
咱們在public/index.html中引入了靜態資源,可是打包的時候webpack並不會幫咱們拷貝到dist目錄,所以copy-webpack-plugin就能夠很好地幫我作拷貝的工做了。

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

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin([{
            from: path.resolve(__dirname, '../static'),
            to: path.resolve(__dirname, '../dist/static')
        }])
    ],
}

ProvidePlugin

ProvidePlugin能夠很快的幫咱們加載想要引入的模塊,而不用require。通常咱們加載jQuery須要先把它import

import $ from 'jquery'

$('#layout').html('test')

可是咱們在config中配置ProvidePlugin插件後可以不用import,直接使用$

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        }),
    ]
}

在項目中引入了太多模塊而且沒有require會讓人摸不着頭腦,所以建議加載一些常見的好比jQuery、vue、lodash等。

loader和plugin的區別(面試中常碰見)

  • 對於loader,它是一個轉換器,將A文件進行編譯造成B文件,這裏操做的是文件,好比將A.scss轉換爲A.css,單純的文件轉換過程
  • plugin是一個擴展器,它豐富了webpack自己,針對是loader結束後,webpack打包的整個過程,它並不直接操做文件,而是基於事件機制工做,會監聽webpack打包過程當中的某些節點,執行普遍的任務
相關文章
相關標籤/搜索