【前端工程化】篇三 席捲八荒-Webpack(基礎)

字數:8960, 閱讀時間:28分鐘,點擊閱讀原文 javascript

盡前行者地步窄,向後看者眼界寬。 ——《格言聯璧·持躬類》

【前端工程化】系列文章連接:css

示例代碼倉庫:https://github.com/BWrong/dev-toolshtml

聲明:本篇文章基於webpack v4.43.0,如按照文中代碼執行報錯,請先檢查依賴版本是否和示例代碼倉庫中一致。前端

前言

自Web2.0以來,前端技術日益蓬勃發展,前端仔們再也不知足於切切頁面、寫寫動畫,而是可以作更多"高大上"的事情了。但隨着項目規模和複雜度的提高,代碼的依賴維護、代碼壓縮、代碼風格審查等與業務無關但又不得不作的事情佔據了開發人員愈來愈多的時間。那時,這些操做均只能依靠開發人員手動來進行處理,耗時耗力,徹底是一個刀耕火種的時代(從前車馬很慢,一輩子只夠愛一我的?)。vue

後來,NodeJS出現了,做爲一門服務端語言,它擁有更增強大的能力,尤爲是處理文件的能力,運行也再也不受限於瀏覽器沙盒,能夠直接在終端命令行運行。這些特性正是開發前端工程化的核心需求,因此有人開始藉助NodeJS來作那些耗時耗力的工做,屬於前端本身的工程化時代初見端倪。java

固然,這裏咱們的重點是Webpack,因此不會花大量篇幅去講述前端工程化的發展史,僅僅列出一些比較有表明性的工具,以至敬這些前浪們node

  • Grunt:基於任務的命令行構建工具,構建工具的先驅。
  • Gulp:管道,流式處理,構建性能比grunt高,配置也比較簡單。
  • Browserify:把Commonjs打包成瀏覽器支持的包。
  • Webpack:模塊打包器,經過loader支持衆多文件類型,支持插件,支持按需加載,提取公用代碼等,生態完善,目前最流行的打包工具。
  • Rollup:側重於打包庫、SDK,輸出成果體積較小。
  • Parcel:打包速度快,入口支持html,打包時會自動安裝須要的插件,人家的口號是技術零配置。
  • snowpack:打包速度快,無需打包工具。

在Webpack剛剛出來的時候,那個時候Gulp和Grunt還風華正茂,在網上常常有人拿它們來作對比,其實他們是不一樣類型的構建工具,不是太具備可比性。react

如上圖所示,雖然說它們都是構建工具,可是Gulp、Grunt更加偏向任務式,全部的操做均需以任務的方式來構建;而Webpack則是模塊化的編譯方案,它經過依賴分析,進行模塊打包,與它對比的應該是Browserify,Rollup之流。jquery

目前來講,grunt和gulp,已經功成身退了,當下webpack無疑是最熱門、最強大的打包工具。這都得益於維護團隊海納百川有容乃大的態度,Rollup的tree shaking、Parcel的零配置這些亮點都被webpack吸取了。也許有的人以爲是抄襲,可是我們讀書人的世界何來抄襲呢。更況且不是這樣的話,不是得學更多的工具了,又何來我這一頭烏黑靚麗的秀髮呢???webpack

好了,囉嗦了半天,該進入主題了,接下來,看看webpack官方的定義:

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.

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

這裏屢次提到了模塊,模塊在前端開發中通常指JavaScript的模塊化產物,相關的介紹網上也有不少,實在找不到也能夠看看鄙人以前的文章再談JavaScript模塊化,這裏咱們再也不贅述。可是這裏Webpack所指的模塊不只僅是JavaScript中的模塊,經過Loader,它能夠處理任意類型的資源。廣義上來講,在Webpack看來,任意資源都是模塊。

安裝與配置

如今Webpack最新的版本是4.43.0(注意如今5.0已發佈,默認安裝會是5.0),能夠安裝在全局,也能夠安裝到項目,這裏推薦在項目本地安裝。

npm init -y  # 初始化npm配置文件
npm install --save-dev webpack # 安裝核心庫
npm install --save-dev webpack-cli # 安裝命令行工具

備註: webpack4.0後,將核心和cli部分拆分開了,因此兩個都須要安裝,拆分開的好處就是核心部分能夠在nodejs的項目中使用,再也不受限於命令行環境,更加符合職責單一原則。

受Parcel的「刺激」,Webpack從4.0開始支持零配置,開箱即用,默認會使用/src/main.js做爲entry,/dist/main.js做爲輸出成果。

創建以下文件:

- src
  |- index.js
// index.js
console.log('hello webpack');

而後執行npx webpack,就能夠看到打包結果:

image-20200526231221659

在輸出信息中,顯示了本次打包的hash值、webpack版本、耗時等,還列出了打包的每一個模塊信息,包含資源名稱、大小、chunk(後面會詳解)等信息,另外在src同級目錄會生成一個dist目錄,下面會有一個main.js,便是打包成果,在index.html中直接引入該文件就能夠了。

上面,其實咱們已經成功完成了一個文件的打包,是否是很簡單。不過仔細看看命令行輸出的信息中,後面是有一大段警告的,做爲一個有追求的程序猿,怎麼可能讓這種事情發生呢!究其緣由,其實在webpack4.0中,建議在打包的時候傳入mode,來告知其打包的目標環境是開發環境仍是生產環境(不設置默認爲production),以便它內部來作相應的優化,能夠經過--mode參數來指定模式,如npx webpack --mode=production,這樣就不會有警告了。

咱們能夠試試將mode設置爲development後再打包一次,看當作果有什麼不一樣(答案:development下代碼未進行壓縮)。

固然,也能夠在命令行中配置參數來改變Webpack的打包設置,具體的用法能夠查看Command Line Interface,經常使用的配置均可以經過命令行來配置,例如默認webpack會查找項目根目錄下的webpack.config.js,咱們能夠經過webpack --config ./build/webpack.config.js來指定配置文件,在平常的開發中,通常都是經過配置文件來使用的,能夠實現更加複雜的設置,並且更加方便。

核心概念

在開始前,有必要了解幾個核心概念:

  • chunk:指代碼塊,一個 chunk 可能由多個模塊組合而成,也用於代碼合併與分割。
  • bundle:資源通過Webpack 流程解析編譯後最終結輸出的成果文件。
  • entry:顧名思義,就是入口起點,用來告訴webpack用哪一個文件做爲構建依賴圖的起點。webpack會根據entry遞歸的去尋找依賴,每一個依賴都將被它處理,最後輸出到打包成果中。
  • output:output配置描述了webpack打包的輸出配置,包含輸出文件的命名、位置等信息。
  • loader:默認狀況下,webpack僅支持.js文件,經過loader,可讓它解析其餘類型的文件,充當翻譯官的角色。理論上只要有相應的loader,就能夠處理任何類型的文件。
  • plugin:loader主要的職責是讓webpack認識更多的文件類型,而plugin的職責則是讓其能夠控制構建流程,從而執行一些特殊的任務。插件的功能很是強大,能夠完成各類各樣的任務。
  • mode:4.0開始,webpack支持零配置,旨在爲開發人員減小上手難度,同時加入了mode的概念,用於指定打包的目標環境,以便在打包的過程當中啓用webpack針對不一樣的環境下內置的優化。

webpack的配置較多,接下來,僅對經常使用配置作一些瞭解,完整的配置能夠查閱webpack-options

說明:接下來的內容,使用的命令均使用前面一篇介紹的npm script來定義,而沒必要再每次都輸入npx。

核心配置

mode

指定打包的模式和環境,取值爲development, productionnone 之中的一個,能夠啓用 webpack 內置在相應環境下的優化。其默認值爲 production

module.exports = {
  mode: 'production'
};

或者經過命令行配置:

webpack --mode=production
選項 描述
development 會將 DefinePluginprocess.env.NODE_ENV 的值設置爲 development。啓用 NamedChunksPluginNamedModulesPlugin
production 會將 DefinePluginprocess.env.NODE_ENV 的值設置爲 production。啓用 FlagDependencyUsagePlugin(添加依賴標識), FlagIncludedChunksPlugin(給chunk添加id), ModuleConcatenationPlugin(處理模塊做用域), NoEmitOnErrorsPlugin(避免生成異常的代碼), OccurrenceOrderPlugin(按次數進行模塊排序), SideEffectsFlagPlugin(處理Side Effects模塊標識) 和 TerserPlugin(js壓縮)。
none 不使用任何默認優化選項

提示:關於各選項webpack內部默認的具體配置,能夠查看該文檔

context

用於指定基礎目錄,用於從配置中解析入口起點和loader,須爲絕對路徑,默認爲啓動webpack的工做目錄。

module.exports = {
  context: path.resolve(__dirname, 'app')
};

entry

打包的入口文件,通常爲應用的入口,方便webpack查找並構建依賴圖。

module.exports = {
    entry: "./app/entry", // 若是僅有一個入口,能夠簡寫爲此方式,爲entry:{main:"./app/entry"}的簡寫
    entry: ["./app/entry1", "./app/entry2"], // 爲entry:{main:["./app/entry1", "./app/entry2"]}的簡寫
    entry: { // 多入口的方式,每項即爲一個chunk,key爲chunkName
        a: "./app/entry-a",
        b: ["./app/entry-b1", "./app/entry-b2"]
    }
}

output

描述了webpack如何輸出,值爲一組選項,包含了輸出文件名字、位置等信息。

module.exports = {
  output: {
    filename: '[name]_[chunkhash:8].bundle.js',
       path: path.resolve(__dirname, 'dist')
  }
};
  • output.filename:配置輸出文件的名字,對於單入口的狀況,須要設定爲一個指定的名稱,對於多入口的狀況,可使用佔位符模板來指定。
模板 描述
[hash] 模塊標識符(module identifier)的 hash
[chunkhash] chunk 內容的 hash
[name] 模塊名稱
[id] 模塊標識符(module identifier)
[query] 模塊的 query,例如,文件名 ? 後面的字符串
[function] 可使用函數動態返回filename

[hash][chunkhash] 的長度可使用 [hash:16](默認爲20)來指定。或者,經過指定output.hashDigestLength 在全局配置長度。

  • output.path:配置輸出目錄,值爲絕對路徑。
  • output.publicPath:若是須要引用外部資源,能夠經過此配置設置資源的地址,例如部署要將資源上傳到CDN服務器,就須要填上CDN的地址。

若是打包一個類庫或者sdk,可能還須要設置librarylibraryExportlibraryTargetumdNamedDefine等選項來配置類庫暴露的名字及兼容的模塊規範等,可查看官方指南

loader

loader 用於對模塊的源代碼進行轉換,在 import 或"加載"模塊時解析文件,經過添加loader可讓webpack處理多種類型的文件。

module.exports = {
  module: {
    rules: [
      { 
        test: /\.css$/, 
           use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            include: './src/assets', // 指定查找的目錄,resource.include的簡寫
            // exclude:'', // 指定排除的目錄,resource.exclude的簡寫
            options: {
              modules: true
            }
          }
        ]
      },
      { test: /\.ts$/, use: 'ts-loader' }
    ]
  }
};

loader能夠在配置文件的module.rules屬性中設置,能夠配置多個規則,每一個規則經過test屬性設置匹配的文件類型,在use屬性中指定對應的loader及其對應的配置(options)。

對於匹配條件,webpack提供了多種配置形式:

  • { test: ... } 匹配特定條件
  • { include: ... } 匹配特定路徑
  • { exclude: ... } 排除特定路徑
  • { and: [...] }必須匹配數組中全部條件
  • { or: [...] } 匹配數組中任意一個條件
  • { not: [...] } 排除匹配數組中全部條件

注意:同一個規則能夠指定多個loader,從右到左鏈式傳遞,依次對文件進行處理,固然能夠經過enforce(可取值爲pre | post,分別爲前置和後置)強制改變執行順序。

經過loader咱們能夠在js文件中導入css、img等文件,理論上能夠實現解析各種文件,經常使用的loader能夠在官網-Loaders中查詢。

plugin

插件的目的在於解決 loader 沒法實現的其餘事。插件的本質實際上是一個具備apply 方法的 JavaScript 對象,該方法會被webpack compiler 調用,而且 compiler 對象可在整個編譯生命週期訪問,用於自定義構建過程。

在配置文件的plugins屬性中傳入插件實例來使用插件:

plugins: [
    new webpack.DefinePlugin({ // 內置的插件(DefinePlugin 容許建立可在編譯時配置的全局常量)
      // Definitions...
    })
    new HtmlWebpackPlugin({template: './src/index.html'}) // 第三方插件,須要安裝
]

使用 Plugin 的難點在於掌握 Plugin 自己提供的配置項,而不是在 Webpack 中使用 Plugin。webpack擁有至關多的插件,經常使用的插件均可以在官方文檔-plugins上查找到,文檔中也有相關配置的說明和案例,也算比較友好了。

其餘經常使用配置

resolve

resolve用於配置模塊解析規則,能夠經過此配置更改webpack默認的解析規則。

webpack的模塊路徑解析規則和Node.js 的模塊機制同樣。

  • 若是是相對路徑

    1. 查找當前模塊的目錄下是否有對應文件/夾
    2. 若是是文件則直接加載
    3. 若是是文件夾,則繼續查找文件夾下的 package.json 文件
    4. 若是有 package.json 文件,則按照其中 main 屬性聲明獲得的文件名來查找文件
    5. 若是無 package.json 或者無 main 屬性,則查找 index.js 文件
  • 若是直接是模塊名
    會依次查找當前目錄下、父目錄、父父目錄、... 一直到根目錄,直到找到目錄下的 node_modules 文件夾,查找是否有對應的模塊
  • 若是是絕對路徑
    直接查找此路徑對應的文件
  • resolve.alias:用於定義一些路徑簡寫佔位符,也有人稱之爲路徑別名映射,目的是簡化模塊導入時的路徑。
module.exports = {
  resolve: {
    alias: {
      @: path.resolve(__dirname, 'src/') //這裏就將@映射到了/src/目錄了,在使用時用@就行
    }
  }
};
  • resolve.extensions:解析文件時,缺省文件擴展名時,嘗試自動補全的擴展名集,嘗試的順序從前到後,通常將高頻使用的擴展名放在前面,優先匹配。
module.exports = {
  resolve: {
    extensions: ['.wasm', '.mjs', '.js', '.json']
  }
};
  • resolve.enforceExtension:若是是 true,將不容許加載無擴展名(extension-less)文件,也便是不會自動進行擴展名補全。
  • resolve.modules:webpack 解析模塊時應該搜索的目錄,默認爲node_modules,若是項目中某個文件夾頻繁使用,就能夠添加進此配置,引入文件時路徑就能夠省略該目錄。
module.exports = {
  resolve: {
    modules: ['node_modules']
  }
};

optimization

webpack4.0將一些優化的配置都放在了該屬性下,根據mode來進行不一樣的優化,也能夠進行手動配置和重寫。

  • optimization.minimize:是否使用 TerserPlugin 壓縮代碼,production 模式下,默認是 true
module.exports = {
  optimization: {
    minimize: false
  }
};
  • optimization.minimizer:容許經過提供一個或多個定製過的 TerserPlugin 實例,覆蓋默認壓縮工具(minimizer)。
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        cache: true,
        parallel: true,
        sourceMap: true, // Must be set to true if using source-maps in production
        terserOptions: {
          // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
          compress:{
            drop_console: true, // 去除consle
            drop_debugger: true  // 去除debugger
          }
        }
      }),
    ],
  }
};

devServer

在項目開發時,若是不能實時看到開發的預覽效果,是否是內心沒底?因此咱們須要一個工具,來啓動一個server,讓咱們在開發的時候能夠實時預覽,webpack-dev-server就是這樣一個工具,它會基於express啓動一個server,提供一些好用的功能:

  • 自動打開瀏覽器
  • 文件監聽
  • 自動刷新與模塊熱替換
  • 跨域代理

這是一個很是實用的工具,用了就會上癮系列。webpack-dev-server並無被webpack內置,須要咱們自行安裝(npm i -D webpack-dev-server),它全部的配置都在配置文件的devServer屬性中。

  • devServer.before,devServer.after:其實至關於devServer的中間件,提供執行自定義處理程序的功能,本質是一個函數,接收devServer實例做爲參數。

    例如,能夠在before中咱們能夠來啓動一個server用來作數據mock。

// webpack.config.js
module.exports = {
  devServer: {
    before: function(app, server) {
      app.get('/some/path', function(req, res) {
        res.json({ custom: 'response' });
      });
    }
  }
};
  • devServer.hostdevServer.port:分別用來配置啓動server的主機地址和端口。
module.exports = {
  //...
  devServer: {
    host: '0.0.0.0',
    port: 8080
  }
};
  • devServer.hot:開啓模塊熱替換HMR(Hot Module Replacement)功能,開啓後它會盡可能採起不刷新整個頁面的方式來局部熱更新頁面。

注意:必須有 webpack.HotModuleReplacementPlugin 才能徹底啓用 HMR。若是webpack-dev-server 是經過 webpack-dev-server --hot選項啓動的,那麼這個插件會被自動添加,不然須要把它手動添加到 webpack.config.js的plugins 中。

module.exports = {
    //...
    devServer: {
        hot: true, // 開啓模塊熱替換
        // ...
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin() // 須要添加模塊熱替換插件,若是啓動帶上了--hot參數則不須要手動添加此插件
    ]
}

開啓熱替換後,須要編寫控制代碼來響應更新時的操做:

if (module.hot) { // 先判斷是否開啓熱替換
  module.hot.accept('./library.js', function() { // library.js更新將會觸發此處的回調函數
    // 使用更新過的 library 模塊執行某些操做...
  });
}

看起來,本身來寫這些控制代碼仍是比較麻煩,不過幸運的是,不少loader(如style-loader、vue-loader)內部都實現了熱替換,而不用咱們本身編寫。

  • devServer.inline:推薦設置爲true,實時預覽重載的腳本將之內聯模式插入到包中,設置爲false將使用iframe模式,採用輪詢的方式執行實時重載。
  • devServer.open:配置是否自動打開瀏覽器。
  • devServer.overlay:當出現編譯器錯誤或警告時,在瀏覽器中顯示覆蓋層提示,默認false。

    image-20200529105657275

  • devServer.proxy:在先後端接口聯調的過程當中,跨域是一個很是常見的問題,要是後端大哥裝大爺的話,那工做就很難作下去了。跨域究其緣由是受瀏覽器的同源策略限制,而服務端則不會有此限制。因此咱們能夠經過nodeServer將後端的接口服務代理到本地,在請求的時候直接訪問本地nodeServer地址,而後nodeServer再將請求轉發到目標服務端,拿到結果後返回給本地的請求。

    proxy就是用來作這個事情的,它是基於強大的http-proxy-middleware來實現的,配置也是相同的。

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://your-host.com', // 代理的目標地址,/api的請求都會被代理到http://your-host.com/api
        secure: false, // 若是使用了HTTPS,須要關閉此配置
        pathRewrite: {
          '^/api': '' // 重寫,目標地址中是否包含/api, 如此設置/api的請求都會被代理到http://your-host.com
        },
        bypass: function(req, res, proxyOptions) { // 若是想本身控制代理,可使用此配置來繞過代理
          if (req.headers.accept.indexOf('html') !== -1) {
            console.log('Skipping proxy for browser request.');
            return '/index.html';
          }
        }
      }
    }
  }
};
  • devServer.publicPath:資源的訪問路徑。
module.exports = {
  //...
  devServer: {
    publicPath: '/assets/' // 能夠經過http://localhost:8080/assets/*訪問到assets目錄下的資源
  }
};

devtool

此選項控制是否生成以及如何生成 source map,不一樣的值會明顯影響到構建(build)和從新構建(rebuild)的速度。

devtool 構建速度 從新構建速度 生產環境 品質(quality)
(none) +++ +++ yes 打包後的代碼
eval +++ +++ no 生成後的代碼
cheap-eval-source-map + ++ no 轉換過的代碼(僅限行)
cheap-module-eval-source-map o ++ no 原始源代碼(僅限行)
eval-source-map -- + no 原始源代碼
cheap-source-map + o yes 轉換過的代碼(僅限行)
cheap-module-source-map o - yes 原始源代碼(僅限行)
inline-cheap-source-map + o no 轉換過的代碼(僅限行)
inline-cheap-module-source-map o - no 原始源代碼(僅限行)
source-map -- -- yes 原始源代碼
inline-source-map -- -- no 原始源代碼
hidden-source-map -- -- yes 原始源代碼
nosources-source-map -- -- yes 無源代碼內容
+++ 很是快速, ++ 快速, + 比較快, o 中等, - 比較慢, --

通常在生產環境推薦使用none(不生成)或者source-map(生成單獨的一個文件)選項,而在開發環境能夠從evaleval-source-mapcheap-eval-source-mapcheap-module-eval-source-map中選擇一個。

webpack 倉庫中包含一個 顯示全部 devtool 變體效果的示例。這些例子或許會有助於你理解這些差別之處。看似值比較多,只要在對應的環境中根據需求(平衡構建速度和打包成果品質)配置合適的值就好。

externals

用來排除特定的依賴,排除的依賴將不會打包進成果中,而是在程序運行時再去外部獲取。

例如,在開發一個library 的時候,若是有依賴其餘的庫,在打包庫的時候就須要排除依賴的庫,而不該該把依賴的庫打包到咱們的library裏面。

另外,在平常的開發中,也能夠利用此配置來實現資源以CDN方式引入:

<!-- index.html -->
<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous">
</script>
// webpack.config.js
module.exports = {
  //...
  externals: {
    jquery: 'jQuery'
  }
};
// src/index.js
import $ from 'jquery'; // 此處的導入能夠正常運行,可是打包的時候不會包含,運行的時候會去檢索jquery全局變量
$('.my-element').animate(/* ... */);

cache

配置是否緩存生成的 webpack 模塊和 chunk,能夠用來改善構建速度。

上面就是一些經常使用的配置,掌握了這些,就能夠在項目中本身來配置一套打包流程了。

實踐一下

1. 目錄規劃

整個項目的目錄結構,咱們作以下規劃:

- build                           // webpack配置文件目錄
   |- webpack.base.conf.js
   |- webpack.dev.conf.js
   |- webpack.prod.conf.js
- dist                            // 打包輸出目錄
- public                        // 放置不須要處理的靜態文件和HTML模板
   |- js
   |- index.html
- src                            // 項目核心代碼,與業務相關的均可以放在此處
   |- assets                    
   |- index.js                    // 入口entry
   |- ...

用過vuecli2.0的同窗應該會很熟悉這個結構。

2. 基礎配置

雖然,webpack默認提供了兩種Mode及內置了相應的優化,可以知足一些簡單項目,可是一些複雜的項目遠遠不止這兩套環境(還有測試、預發佈等環境),每一個環境中的配置也有着巨大差別,遵循邏輯分離原則,此時能夠根據環境將配置文件拆分爲獨立的文件。

下面以只考慮兩種環境爲例:

實現功能 開發環境(速度優先) 生成環境(性能優先)
代碼壓縮
圖片壓縮
css文件抽離
模塊熱替換
devserver、proxy
source-map eval-cheap-source-map none或者source-map

除了上述羅列內容,實際狀況可能還有更多的差別性,總的來看,通常就是開發環境更加側重構建速度,生產環境更加側重代碼的執行性能。

咱們能夠將配置拆分紅三個文件:

  • webpack.base.conf.js:開發環境和生產環境公用的配置,最終使用 webpack-merge合併到dev和prod兩個配置中。
// build/webpack.base.conf.js
const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
   entry: '../src/index.js',
   output: {
     filename: '[name].[hash:8].js',
     path: path.resolve(__dirname, '../dist')
   },
   plugins: [
     new CleanWebpackPlugin(), //打包前清空輸出目錄
     new HtmlWebpackPlugin({
           title: 'webpack',
          template: './public/index.html',
          filename: 'index.html'
     })
   ]
};
  • webpack.dev.conf.js:開發環境特有的配置,一些輔助開發的配置都放到此文件。
// build/webpack.dev.conf.js
const {merge} = require('webpack-merge');
const baseConfig = require('./webpack.base.conf.js');

module.exports = merge(baseConfig, {
   mode: 'development',
   devtool: 'inline-source-map',
   devServer: {
    contentBase: '../dist',
    hot: true,
    // ...
   },
   // ..
});
  • webpack.prod.conf.js:生產環境特有的配置,一些針對輸出文件體積質量優化的都放到此文件。
// build/webpack.prod.conf.js 
const {merge} = require('webpack-merge');
const baseConfig = require('./webpack.base.conf.js');

 module.exports = merge(baseConfig, {
   mode: 'production',
   // ...
 });

而後在npm script中配置對應的命令,來指定不一樣的配置文件:

// package.json
"scripts": {
    "start": "webpack-dev-server --config ./build/webpack.dev.conf.js",
    "build": "webpack --config ./build/webpack.prod.conf.js"
 }

3. 功能完善

使用HTML模板

前端項目通常都會有一個入口(index.html),須要在此文件中引入打包後的資源,可是每次打包後手動將資源引入太費事,特別是輸出文件的名字使用了佔位符時(每次打包輸出文件的名稱都會不同),簡直就是搞事情嘛。此時,咱們就能夠經過 html-webpack-plugin來自動引入打包後的資源。

npm install html-webpack-plugin -D
// build/webpack.base.conf.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
        title:'應用名稱',
            template: './public/index.html', // 配置使用的文件模板,若是不配置,將會使用默認的內置模板
        ... // 其餘配置按需食用
    }),
  ],
}

注意:若是須要打包多頁應用,僅需實例化多個html-webpack-plugin,在每一個實例中配置相應的chunk便可。

// build/webpack.base.conf.js
module.exports = {
    entry:{
      app1:'../src/app1.js',  
      app2:'../src/app2.js' 
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/app1.html', // 模板
            filename: 'app1.html', // 打包輸出文件名
            chunks: ['app1'] // 對應的chunk,和entry中定義的chunk對應
        }),
        new HtmlWebpackPlugin({
            template: './public/app2.html', // 模板
            filename: 'app2.html', // 打包輸出文件名
            chunks: ['app2'] // 對應的chunk,和entry中定義的chunk對應
        }),
    ]
}

自動清理輸出文件

因爲使用了佔位符,每次輸出的文件可能不同,那麼就須要在每次打包前清除一下上次輸出的文件,clean-webpack-plugin就能夠幫咱們自動完成這件事。

npm install clean-webpack-plugin -D
// build/webpack.base.conf.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    plugins: [
        new CleanWebpackPlugin() // 會自動清除輸出文件夾
    ]
}

靜態資源拷貝

有些資源是不須要webpack來進行編譯的,如VueCli4.0中public中的資源文件,只須要將其拷貝到目標文件就能夠了。CopyWebpackPlugin能夠把指定文件或目錄拷貝到構建的輸出目錄中。

npm install copy-webpack-plugin -D
// build/webpack.base.conf.js
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    plugins: [
        new CopyWebpackPlugin({
          patterns: [
            // 將public/js/下的全部js文件拷貝到dist/js目錄中
            { 
              from: './public/js/*.js', 
              to: 'js', 
              flatten: true  // 拷貝是否不帶路徑,爲true只會拷貝文件,而不會攜帶文件路徑
            }, 
            // ... 多個須要拷貝的文件/夾在此繼續添加
          ]
        })
    ]
}

使用ES Next語法

ES規範愈來愈完善,ES6給咱們帶來了不少實用的新特性,可以大大提高開發體驗,但瀏覽器的支持老是那麼不盡人意。而Babel的出現,讓咱們能夠在開發環境使用更時髦的語法(jsx也是能夠的),而後生產環境將代碼轉換成瀏覽器支持的語法。關於Babel的具體介紹會放在後續內容中,這裏再也不贅述。

npm install babel-loader -D
npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm install @babel/runtime @babel/runtime-corejs3
// build/webpack.base.conf.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                use: ['babel-loader'],
                exclude: /node_modules/ //排除 node_modules 目錄
            }
        ]
    }
}

在根目錄建立一個babel配置文件babel.config.js

// babel.config.js 
module.exports = {
    "presets": ["@babel/preset-env"],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3
            }
        ]
    ]
}

注入全局變量

在使用一些框架的時候,須要在不少文件都引入一下,好比在使用React的時候,須要在每一個文件都引入一下,不然會報錯。這時,若是想偷個懶,利用ProvidePlugin來自動注入也是能夠的。

// build/webpack.base.conf.js
const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            React: 'react',
            Component: ['react', 'Component']
        })
    ]
}

如此,在寫React組件時,就再也不須要import reactcomponent了。

注意:

  • 這玩意雖好,可千萬不要貪杯喲,過多的全局變量會出事的。
  • 在開啓ESLint時,還須要在global中作對應配置,不然會報錯提示對應模塊未定義。

樣式類文件處理

樣式類文件處理主要包含樣式文件引入、瀏覽器兼容性語法的自動補全及預處理器編譯三部份內容。

css解析和引入

若是僅使用css來作爲樣式文件的話,配置相對比較簡單,只須要藉助css-loader讓webpack能夠解析css文件便可。

npm install css-loader -D
// build/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: 'css-loader',
                exclude: /node_modules/
            }
        ]
    }
}

如今,webpack能夠打包css文件了,可是樣式並不會生效,由於它們根本沒有插入到頁面中,須要藉助style-loader來作樣式引入,它會在head中動態建立 style 標籤,並將 css 插入到其中。

npm install style-loader -D
// build/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'], // 注意順序,從後到前
                exclude: /node_modules/
            }
        ]
    }
}

自動補全兼容性前綴

因爲如今瀏覽器對某些css特性支持還不夠完善,在使用這些新特性的時候,每每須要加上一些瀏覽器特定的前綴,也是一個比較麻煩的事情,這時候就輪到postcss上場了。

npm install postcss-loader postcss autoprefixer -D
// build/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        plugins: function () {
                            return [
                                require('autoprefixer')({
                                    "overrideBrowserslist": [
                                        ">0.25%",
                                        "not dead"
                                    ]
                                })
                            ]
                        }
                    }
                }],
                exclude: /node_modules/
            }
        ]
    }
}

看到這個醜陋冗長的配置,總感受怪怪的,通常會將它們抽離到postcssbrowserslist的配置文件中。

在項目根目錄建立postcss配置文件postcss.config.js

// postcss.config.js
module.exports = {
  plugins: {
    'autoprefixer': {}
  }
};

另外再建立一個browserslist配置文件.browserslistrc,用來指定要兼容的瀏覽器。

> 1%
last 2 versions
not ie <= 8

如今配置文件看起就要舒服多了。

// build/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader'],
                exclude: /node_modules/
            }
        ]
    }
}

使用預處理器

目前前端比較流行的三種css預處理器都有相應的工具進行處理,使用方法也是相似的,安裝相應的loader和核心處理程序就能夠了。

預處理器 loader 核心處理程序
less less-loader less
sass sass-loader node-sass或dart-sass
stylus stylus-loader stylus

以less爲例:

npm install less-loader less -D
// build/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader','postcss-loader','less-loader'],
                exclude: /node_modules/
            }
        ]
    }
}

樣式文件分離

通過如上幾個loader處理,css最終是打包在js中的,運行時會動態插入head中,可是咱們通常在生產環境會把css文件分離出來(有利於用戶端緩存、並行加載及減少js包的大小),這時候就用到 mini-css-extract-plugin 插件。

npm i -D mini-css-extract-plugin
// build/webpack.base.conf.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          // 插件須要參與模塊解析,須在此設置此項,再也不須要style-loader          
          {
             loader: MiniCssExtractPlugin.loader, 
             options: {
                hmr: true, // 模塊熱替換,僅需在開發環境開啓
                // reloadAll: true,
                // ... 其餘配置
             }
          },
           'css-loader',
           'postcss-loader',
           'less-loader'
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css', // 輸出文件的名字
      // ... 其餘配置
    }), 
  ]
};

圖片/字體文件處理

url-loaderfile-loader 均可以用來處理本地的資源文件,如圖片、字體、音視頻等。功能也是相似的, 不過url-loader 能夠指定在文件大小小於指定的限制時,返回 DataURL,不會輸出真實的文件,能夠減小昂貴的網絡請求。

npm install url-loader file-loader -D
// build/webpack.base.conf.js
module.exports = {
    modules: {
        rules: [
            {
                test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
                use: [
                    {
                        loader: 'url-loader', // 僅配置url-loader便可,內部會自動調用file-loader
                        options: {
                            limit: 10240, //小於此值的文件會被轉換成DataURL
                            name: '[name]_[hash:6].[ext]', // 設置輸出文件的名字
                            outputPath: 'assets', // 設置資源輸出的目錄
                            esModule: false 
                        }
                    }
                ],
                exclude: /node_modules/
            }
        ]
    }
}
注意:

limit的設置要設置合理,太大會致使JS文件加載變慢,須要兼顧加載速度和網絡請求次數。

若是須要使用圖片壓縮功能,可使用 image-webpack-loader

實時預覽及模塊熱替換

在開發環境,能夠藉助webpack-dev-server啓動一個server來實時預覽應用程序。因爲webpack-dev-server並不包含在覈心庫中,因此須要額外安裝。

npm install webpack-dev-server -D
// build/webpack.dev.conf.js
module.exports = {
    //...
    devServer: {
        port: '8080', //默認是8080
        hot: true, // 開啓模塊熱替換
        publicPath:'/', // 構建好的靜態文件訪問路徑,能夠和output.publicPath保持一致
        inline: true, //默認開啓 inline 模式,若是設置爲false,開啓 iframe 模式
        stats: "errors-only", //終端僅打印 error
        overlay: true, //啓用浮層提示
        clientLogLevel: "silent", //日誌等級
        compress: false, //是否啓用 gzip 壓縮
        contentBase: path.join(__dirname, "../public") , // 配置額外的靜態文件內容的訪問路徑
        proxy: { // 請求代理,解決開發環境跨域問題
            // 根據狀況配置
        }
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin() //須要添加模塊熱替換插件
    ]
}

優化調試功能

爲代碼生成source-map有助於調試排錯,通常在開發環境,因爲是本地加載,咱們優先考慮map文件的生成速度,能夠不用額外單獨生成map文件,而在生產環境,則須要不生成或者單獨生成文件,優先考慮加載速度。

// build/webpack.dev.conf.js
module.exports = {
    devtool: 'cheap-module-eval-source-map' // 開發環境下使用內聯方式,忽略列信息
}
// build/webpack.prod.conf.js
module.exports = {
    devtool: 'none' // 也可使用'source-map'
}

除了上述配置項,咱們通常在開發環境還會開啓ESLint,因爲後面有一篇專門的內容來敘述,因此此處不贅述。

到此,咱們就可以本身進行打包配置了,平常開發應該是能夠知足了。

結語

其實webpack配置不少,相信沒有一我的可以記住如此之多的api,這也是困擾初學者的一個問題。這裏和你們分享一下個人學習方法:

  1. 首先,瞭解webpack是解決什麼問題及有哪些能力,而不是一開始就去記憶那些枯燥的配置,成爲所謂的webpack配置工程師。
  2. 其次,按照功能和用途將api分類,方便記憶和後續的查閱。特別是loader和plugin,數量衆多,每一個的配置也不同,咱們只須要了解處理特定類型的文件和操做該用哪一個就行,沒必要記住每一個的配置。
  3. 最後,在須要用到某個功能的時候,根據上一步的分類,再去查閱對應的文檔。官網也提供了完整的配置,能夠很方便的查找對應的配置文檔。

    <center>早期的文檔</center>

相比早期的文檔,目前官方網站上的文檔質量已比較完善和友好了,自行修煉徹底沒有問題了(找個妹子雙修效果更好哦)。

好了,關於webpack的基礎部分就告一段落了,在接下來咱們將會着重介紹一些性能優化及原理方面的東西,下篇再見吧!

參考文檔:webpack官網-中文深刻淺出webpack

相關文章
相關標籤/搜索