淺入淺出webpack

準備了挺久,一直想要好好深刻了解一下Webpack,以前一直嫌棄Webpack麻煩,偏向於Parcel這種零配置的模塊打包工具一些,可是實際上仍是Webpack比較靠譜,而且Webpack功能更增強大。因爲上一次學習Webpack的時候並無瞭解過Node.js,因此不少時候真的感受無能爲力,連個__dirname都以爲好複雜,學習過Node.js以後再來學習Webpack,就會好理解不少,這一次算是比較深刻的瞭解一下Webpack,爭取之後可以脫離create-react-app或者Vue-Cli這種腳手架工具,或者本身也可以寫一套腳本自動配置開發環境。css

因爲寫這篇筆記的時候,Webpack已經發行了最新的Webpack 4.0,因此這篇筆記就算是學習Webpack 4.0的筆記吧,筆者所用版本是webpack 4.8.3,另外使用Webpack 4.x的命令行須要安裝單獨的命令行工具,筆者所使用的Webpack命令行工具是webpack-cli 2.1.3,學習的時候能夠按照這個要求部署開發環境。html

此外,在學習webpack以前,你最好對ES六、Node.js有必定的瞭解,最好使用過一個腳手架。react

1、核心概念

Webpack具備四個核心的概念,想要入門Webpack就得先好好了解這四個核心概念。它們分別是Entry(入口)Output(輸出)loaderPlugins(插件)。接下來詳細介紹這四個核心概念。webpack

1.Entry

Entry是Webpack的入口起點指示,它指示webpack應該從哪一個模塊開始着手,來做爲其構建內部依賴圖的開始。能夠在配置文件(webpack.config.js)中配置entry屬性來指定一個或多個入口點,默認爲./src(webpack 4開始引入默認值)。git

具體配置方法:github

entry: string | Array<string>
複製代碼

前者一個單獨的string是配置單獨的入口文件,配置爲後者(一個數組)時,是多文件入口。web

另外還能夠經過對象語法進行配置正則表達式

entry: {
    [entryChunkName]: string | Array<string>
}
複製代碼

好比:express

//webpack.config.js
module.exports = {
    entry: {
        app: './app.js',
        vendors: './vendors.js'
    }
};
複製代碼

以上配置表示從app和vendors屬性開始打包構建依賴樹,這樣作的好處在於分離本身開發的業務邏輯代碼和第三方庫的源碼,由於第三方庫安裝後,源碼基本就再也不變化,這樣分開打包有利於提高打包速度,減小了打包文件的個數,Vue-Cli採起的就是這種分開打包的模式。可是爲了支持拆分代碼更好的DllPlugin插件,以上語法可能會被拋棄。npm

2.Output

Output屬性告訴webpack在哪裏輸出它所建立的bundles,也可指定bundles的名稱,默認位置爲./dist。整個應用結構都會被編譯到指定的輸出文件夾中去,最基本的屬性包括filename(文件名)和path(輸出路徑)。

值得注意的是,便是你配置了多個入口文件,你也只能有一個輸出點。

具體配置方法:

output: {
    filename: 'bundle.js',
    path: '/home/proj/public/dist'
}
複製代碼

值得注意的是,output.filename必須是絕對路徑,若是是一個相對路徑,打包時webpack會拋出異常。

多個入口時,使用下面的語法輸出多個bundle:

// webpack.config.js
module.exports = {
    entry: {
        app: './src/app.js',
        vendors: './src/vendors.js'
    },
    output: {
        filename: '[name].js',
        path: __dirname + '/dist'
    }
}
複製代碼

以上配置將會輸出打包後文件app.js和vendors.js到__dirname + '/dist'下。

3.Loaders

loader能夠理解爲webpack的編譯器,它使得webpack能夠處理一些非JavaScript文件,好比png、csv、xml、css、json等各類類型的文件,使用合適的loader可讓JavaScript的import導入非JavaScript模塊。JavaScript只認爲JavaScript文件是模塊,而webpack的設計思想即萬物皆模塊,爲了使得webpack可以認識其餘「模塊」,因此須要loader這個「編譯器」。

webpack中配置loader有兩個目標:

  • (1)test屬性:標誌有哪些後綴的文件應該被處理,是一個正則表達式。
  • (2)use屬性:指定test類型的文件應該使用哪一個loader進行預處理。

好比webpack.config.js:

module.exports = {
    entry: '...',
    output: '...',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: 'css-loader'
            }
        ]
    }
};
複製代碼

該配置文件指示了全部的css文件在import時都應該通過css-loader處理,通過css-loader處理後,能夠在JavaScript模塊中直接使用import語句導入css模塊。可是使用css-loader的前提是先使用npm安裝css-loader

此處須要注意的是定義loaders規則時,不是定義在對象的rules屬性上,而是定義在module屬性的rules屬性中。

配置多個loader

有時候,導入一個模塊可能要先使用多個loader進行預處理,這時就要對指定類型的文件配置多個loader進行預處理,配置多個loader,把use屬性賦值爲數組便可,webpack會按照數組中loader的前後順序,使用對應的loader依次對模塊文件進行預處理。

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

此外,還可使用內聯方式進行loader配置:

import Styles from 'style-loader!css-loader?modules!./style.css'
複製代碼

可是這不是推薦的方法,請儘可能使用module.rules進行配置。

4.Plugins

loader用於轉換非JavaScript類型的文件,而插件能夠用於執行範圍更廣的任務,包括打包、優化、壓縮、搭建服務器等等,功能十分強大。要是用一個插件,通常是先使用npm包管理器進行安裝,而後在配置文件中引入,最後將其實例化後傳遞給plugins數組屬性。

插件是webpack的支柱功能,目前主要是解決loader沒法實現的其餘許多複雜功能,經過plugins屬性使用插件:

// webpack.config.js
const webpack = require('webpack');
module.exports = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ]
}
複製代碼

向plugins屬性傳遞實例數組便可。

5.Mode

模式(Mode)能夠經過配置對象的mode屬性進行配置,主要值爲production或者development。兩種模式的區別在於一個是爲生產環境編譯打包,一個是爲了開發環境編譯打包。生產環境模式下,webpack會自動對代碼進行壓縮等優化,省去了配置的麻煩。

學習完以上基本概念以後,基本也就入門webpack了,由於webpack的強大就是創建在這些基本概念之上,利用webpack多樣的loaders和plugins,能夠實現強大的打包功能。

2、基本配置

按照如下步驟實現webpack簡單的打包功能:

  • (1)創建工程文件夾,位置和名稱隨意,並將cmd或者git bash的當前路徑切換到工程文件夾。

  • (2)安裝webpack和webpack-cli到開發環境:

    npm install webpack webpack-cli --save-dev
    複製代碼
  • (3)在工程文件夾下創建如下文件和目錄:

    • /src
      • index.js
      • index.css
    • /dist
      • index.html
    • webpack.config.js
  • (4)安裝css-loader

    npm install css-loader --save-dev
    複製代碼
  • (5)配置webpack.config.js

    module.exports = {
          mode: 'development',
          entry: './src/index.js',
          output: {
              path: __dirname + '/dist',
              filename: 'bundle.js'
          },
          module: {
              rules: [
                  {
                      test: /\.css$/,
                      use: 'css-loader'
                  }
              ]
          }
      };
    複製代碼
  • (6)在index.html中引入bundle.js

    <!--index.html-->
      <html>
          <head>
              <title>Test</title>
              <meta charset='utf-8'/>
          </head>
          <body>
              <h1>Hello World!</h1>
          </body>
          <script src='./bundle.js'></script>
      </html>
    複製代碼
  • (7)在index.js中添加:

    import './index.css';
      console.log('Success!');
    複製代碼
  • (8)在工程目錄下,使用如下命令打包:

    webpack
    複製代碼

    查看輸出結果,能夠雙擊/dist/index.html查看有沒有報錯以及控制檯的輸出內容。

3、如何經過Node腳本使用webpack?

webpack提供Node API,方便咱們在Node腳本中使用webpack。

基本代碼以下:

// 引入webpack模塊。
const webpack = require('webpack');
// 引入配置信息。
const config = require('./webpack.config');
// 經過webpack函數直接傳入config配置信息。
const compiler = webpack(config);
// 經過compiler對象的apply方法應用插件,也可在配置信息中配置插件。
compiler.apply(new webpack.ProgressPlugin());
// 使用compiler對象的run方法運行webpack,開始打包。
compiler.run((err, stats) => {
    if(err) {
        // 回調中接收錯誤信息。
        console.error(err);
    }
    else {
        // 回調中接收打包成功的具體反饋信息。
        console.log(stats);
    }
});
複製代碼

4、動態生成index.html和bundle.js

動態生成是啥?動態生成就是指在打包後的模塊名稱內插入hash值,使得每一次生成的模塊具備不一樣的名稱,而index.html之因此要動態生成是由於每次打包生成的模塊名稱不一樣,因此在HTML文件內引用時也要更改script標籤,這樣才能保證每次都能引用到正確的JavaScript文件。

爲何要添加hash值?

之因此要動態生態生成bundle文件,是爲了防止瀏覽器緩存機制阻礙文件的更新,在每次修改代碼以後,文件名中的hash都會發生改變,強制瀏覽器進行刷新,獲取當前最新的文件。

如何添加hash到bundle文件中?

只須要在設置output時,在output.filename中添加[hash]到文件名中便可,好比:

// webpack.config.js
module.exports = {
    output: {
        path: __dirname + '/dist',
        filename: '[name].[hash].js'
    }
};
複製代碼

如今能夠動態生成bundle文件了,那麼如何動態添加bundle到HTML文件呢?

每次打包bundle文件以後,其名稱都會發生更改,每次人爲地修改對應的HTML文件以添加JavaScript文件引用實在是使人煩躁,這時須要使用到強大的webpack插件了,有一個叫html-webpack-plugin的插件,能夠自動生成HTML文件。安裝到開發環境:

npm install html-webpack-plugin --save-dev
複製代碼

安裝以後,在webpack.config.js中引入,並添加其實例到插件屬性(plugins)中去:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    // other configs ...
    plugins: [
        new HtmlWebpackPlugin({
            // options配置
        })
    ]
};
複製代碼

這時就能夠看到每次生成bundle文件以後,都會被動態生成對應的html文件。

在上面的代碼中還能夠看到HtmlWebpackPlugin插件的構造函數還能夠傳遞一個配置對象做爲參數。比較有用的配置屬性有title(指定HTML中title標籤的內容,及網頁標題)、template(指定模板HTML文件)等等,其餘更多具體參考信息請訪問:Html-Webpack-Plugin

5、清理/dist文件夾

因爲每次生成的JavaScript文件都不一樣名,因此新的文件不會覆蓋舊的文件,而舊的文件一隻會存在於/dist文件夾中,隨着編譯次數的增長,這個文件夾會愈來愈膨脹,因此應該想辦法每次生成新的bundle文件以前清理/dist文件夾,以確保文件夾的乾淨整潔,有如下兩個較好的處理辦法:

若是你是Node腳本調用webpack打包:

若是經過Node API調用webpack進行打包,能夠在打包以前直接使用Node的fs模塊刪除/dist文件夾中的全部文件:

const webpack = require('webpack');
const config = require('./webpack.config');
const fs = require('fs');
const compiler = webpack(config);

var deleteFolderRecursive = function(path) {
    if (fs.existsSync(path)) {
        fs.readdirSync(path).forEach(function(file, index){
            var curPath = path + "/" + file;
            if (fs.lstatSync(curPath).isDirectory()) { // recurse
                deleteFolderRecursive(curPath);
            } else { // delete file
                fs.unlinkSync(curPath);
            }
        });
        fs.rmdirSync(path);
    }
};

deleteFolderRecursive(__dirname + '/dist');
compiler.run((err, stats) => {
    if(err) {
        console.error(err);
    }
    else {
        console.log(stats.hash);
    }
});
複製代碼

能夠看到在調用compiler.run打包以前,先使用自定義的deleteFolderRecursive方法刪除了/dist目錄下的全部文件。

若是你使用webpack-cli進行打包

這時候就得經過webpack的插件完成這個任務了,用到的插件是clean-webpack-plugin

安裝:

npm install clean-webpack-plugin --save-dev
複製代碼

而後在webpack.config.js文件中添加插件:

// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ]
};
複製代碼

以後再次打包,你會發現以前的打包文件所有被刪除了。

6、搭建開發環境

開發環境與生產環境存在許多的差別,生產環境更講究生產效率,所以代碼必須壓縮、精簡,必須去除一些生產環境並不須要用到的調試工具,只須要提升應用的效率和性能便可。開發環境更講究調試、測試,爲了方便開發,咱們須要搭建一個合適的開發環境。

(一)使用source maps進行調試

爲什麼要使用source maps?

由於webpack對源代碼進行打包後,會對源代碼進行壓縮、精簡、甚至變量名替換,在瀏覽器中,沒法對代碼逐行打斷點進行調試,全部須要使用source maps進行調試,它使得咱們在瀏覽器中能夠看到源代碼,進而逐行打斷點調試。

如何使用source maps?

在配置中添加devtool屬性,賦值爲source-map或者inline-source-map便可,後者報錯信息更加具體,會指示源代碼中的具體錯誤位置,而source-map選項沒法指示到源代碼中的具體位置。

(二)使用開發工具

每次寫完代碼保存以後還須要手動輸入命令或啓動Node腳本進行編譯是一件使人不勝其煩的事情,選擇一下工具能夠簡化開發過程當中的工做:

  • 啓用watch模式
  • 使用webpack-dev-server
  • 使用webpack-dev-middleware

(1)使用watch模式

在使用webpack-cli進行打包時,經過命令webpack --watch便可開啓watch模式,進入watch模式以後,一旦依賴樹中的某一個模塊發生了變化,webpack就會從新進行編譯。

(2)使用webpack-dev-server

使用過create-react-app或者Vue-Cli這種腳手架的童鞋都知道,經過命令npm run start便可創建一個本地服務器,而且webpack會自動打開瀏覽器打開你正在開發的頁面,而且一旦你修改了文件,瀏覽器會自動進行刷新,基本作到了所見即所得的效果,比webpack的watch模式更加方便給力。

使用方法:

  • ① 安裝webpack-dev-server:

    npm install --save-dev webpack-dev-server
    複製代碼
  • ② 修改配置文件,添加devServer屬性:

    // webpack.config.js
      module.exports = {
          devServer: {
              contentBase: './dist'
          }
      };
    複製代碼
  • ③ 添加命令屬性到package.json

    // package.json
      {
          "scripts": {
              "start": "webpack-dev-server --open"
          }
      }
    複製代碼
  • ④ 運行命令

    npm run start
    複製代碼

    能夠看到瀏覽器打開後的實際效果,嘗試修改文件,查看瀏覽器是否實時更新。

此外還能夠再devServer屬性下指定更多的配置信息,好比開發服務器的端口、熱更新模式、是否壓縮等等,具體查詢:Webpack

經過Node API使用webpack-dev-server

'use strict';

const Webpack = require('webpack');
const WebpackDevServer = require('../../../lib/Server');
const webpackConfig = require('./webpack.config');

const compiler = Webpack(webpackConfig);
const devServerOptions = Object.assign({}, webpackConfig.devServer, {
    stats: {
        colors: true
    }
});
const server = new WebpackDevServer(compiler, devServerOptions);

server.listen(8080, '127.0.0.1', () => {
    console.log('Starting server on http://localhost:8080');
});
複製代碼

(3)使用webpack-dev-middleware

webpack-dev-middleware是一個比webpack-dev-server更加基礎的插件,webpack-dev-server也使用了這個插件,因此能夠理解爲webpack-dev-middleware的封裝層次更低,使用起來更加複雜,可是低封裝性意味着較高的自定義性,使用webpack-dev-middleware能夠定義更多的設置來知足更多的開發需求,它基於express模塊。

這一塊不作過多介紹,由於webpack-dev-server已經可以應付大多數開發場景,不用再設置更多的express屬性了,想要詳細瞭解的童鞋能夠了解:使用 webpack-dev-middleware

(4)設置IDE

某些IDE具備安全寫入功能,致使開發服務器運行時IDE沒法保存文件,此時須要進行對應的設置。

具體參考:調整文本編輯器

(三)熱模塊替換

熱模塊替換(Hot Module Replacement,HMR),表明在應用程序運行過程當中替換、添加、刪除模塊,瀏覽器無需刷新頁面便可呈現出相應的變化。

使用方法:

  • (1)在devServer屬性中添加hot屬性並賦值爲true:

    // webpack.config.js
      module.exports = {
          devServer: {
              hot: true
          }
      }
    複製代碼
  • (2)引入兩個插件到webpack配置文件:

    // webpack.config.js
      const webpack = require('webpack');
      module.exports = {
          devServer: {
              hot: true
          },
          plugins: [
              new webpack.NamedModulesPlugin(),
              new webpack.HotModuleReplacementPlugin()
          ]
      };
    複製代碼
  • (3)在入口文件底部添加代碼,使得在全部代碼發生變化時,都可以通知webpack:

    if (module.hot) {
          module.hot.accept('./print.js', function() {
              console.log('Accepting the updated intMe module!');
              printMe();
          })
      }
    複製代碼

熱模塊替換比較難以掌控,容易報錯,推薦在不一樣的開發配置下使用不一樣的loader簡化HMR過程。具體參考:其餘代碼和框架

7、搭建生產環境

生產環境要求代碼精簡、性能優異,而開發要求開發快速、測試方便,代碼不要求簡潔,因此兩種環境下webpack打包的目的也不相同,因此最好將兩種環境下的配置文件分開來。對於分開的配置文件,在使用webpack時仍是要對其中的配置信息進行整合,webpack-merge是一個不錯的整合工具(Vue-Cli也有使用到)。

使用方法:

  • (1)安裝webpack-merge:

    npm install webpack-merge --save-dev
    複製代碼
  • (2)創建三個配置文件:

    • webpack.base.conf.js
    • webpack.dev.conf.js
    • webpack.prod.conf.js

    其中,webpack.base.conf.js表示最基礎的配置信息,開發環境和生產環境都須要設置的信息,好比entryoutputmodule等。在另外兩個文件中配置一些對應環境下特有的信息,而後經過webpack-merge模塊與webpack.base.conf.js整合。

  • (3)添加npm scripts:

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

此外,建議設置mode屬性,由於生產環境下會自動開啓代碼壓縮,免去了配置的麻煩。

8、性能優化

TreeShaking

TreeShaking表示移除JavaScript文件中的未使用到的代碼,webpack 4加強了這一部分的功能。經過配置package.json的sideEffects屬性,能夠指定哪些文件能夠移除多餘代碼。若是sideEffects設置爲false,那麼表示文件中的未使用代碼能夠放心移除,沒有反作用。若是有些文件中的冗餘代碼不能被移除,那麼能夠設置sideEffects屬性爲一個數組,數組內容爲文件的路徑字符串。

指定無反作用的文件以後,設置mode爲"production",再次構建代碼,能夠發現未使用到的代碼已經被移除。

Tips

  • module.rules屬性中,設置include屬性以指定哪些文件須要被loader處理。
  • 只使用必要的loader。
  • 保持最新版本。
  • 減小項目文件數。

9、經過webpack構建PWA應用

漸進式網絡應用程序(Progressive Web Application - PWA),是一種能夠提供相似於原生應用程序(native app)體驗的網絡應用程序(web app),在離線(offline)時應用程序可以繼續運行功能,這是經過 Service Workers 技術來實現的。PWA是最近幾年比較火的概念,它的核心是由service worker技術實現的在客戶瀏覽器與服務器之間搭建的一個代理服務器,在網絡暢通時,客戶瀏覽器會經過service worker訪問服務器,而且緩存註冊的文件;在網絡斷開時,瀏覽器會訪問service worker這個代理服務器,使得在網絡斷開的狀況下,頁面仍是可以訪問,實現了相似原生應用的網站開發。create-react-app已經實現了PWA開發的配置。

下面介紹如何經過webpack快速開發PWA。

  • (1)安裝插件workbox-webpack-plugin

    npm install workbox-webpack-plugin --save-dev
    複製代碼
  • (2)在配置文件中引入該插件:

    // webpack.config.js
      const WorkboxPlugin = require('workbox-webpack-plugin');
      module.exports = {
          plugins: [
              new WorkboxPlugin.GenerateSW({
                  clientsClaim: true,
                  skipWaiting: true
              })
          ]
      };
    複製代碼
  • (3)使用webpack進行編譯,打包出service-worker.js

  • (4)在入口文件底部註冊service worker:

    if ('serviceWorker' in navigator) {
          window.addEventListener('load', () => {
              navigator.serviceWorker.register('/service-worker.js').then(registration => {
                  console.log('SW registered: ', registration);
              }).catch(registrationError => {
                  console.log('SW registration failed: ', registrationError);
              });
          });
      }
    複製代碼
  • (5)打開頁面,進行調試:

    npm run start
    複製代碼
  • (6)打開瀏覽器調試工具,查看控制檯的輸出,若是輸出「SW registered: ... ...」,表示註冊service worker成功,接下來能夠斷開網絡,或者關閉服務器,再次刷新,能夠看到頁面仍然能夠顯示。

10、參考文章

11、總結

webpack確實是一個功能強大的模塊打包工具,豐富的loader和plugin使得其功能多而強。學習webpack使得咱們能夠自定義本身的開發環境,無需依賴create-react-appVue-Cli這類腳手架,也能夠針對不一樣的需求對代碼進行不一樣方案的處理。這篇筆記還只是一篇入門的筆記,若是要真正的構建較爲複雜的開發環境和生產環境,還須要瞭解許多的loader和plugin,好在webpack官網提供了全部的說明,能夠給用戶提供使用指南:

閱讀腳手架的源碼也有助於學習webpack,從此應該還有進行這方面的學習,可是答辯即將到來,不知道畢業以前還有沒有機會^_^。

相關文章
相關標籤/搜索