天天看點

webpack 基本配置

大綱:

1、webpack 是什麼,為什麼需要webpack

2、子產品化

3、webpack的基本配置

webpack是什麼

我們先引入一段官網的解釋

本質上,webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler)。當 webpack 處理應用程式時,它會遞歸地建構一個依賴關系圖(dependency graph),其中包含應用程式需要的每個子產品,然後将所有這些子產品打包成一個或多個 bundle。

了解一下,那就是 webpack 是一個打包子產品化 JavaScript 程式的工具。webpack 處理應用程式時,會從配置的入口檔案開始,識别出子產品化導入語句,然後遞歸找出入口檔案的所有子產品依賴,将這些依賴統統打包進一個或多個獨立檔案中(即bundle檔案)。

為什麼需要子產品化打包工具

那麼我們為什麼要使用它,子產品化究竟有什麼好處呢?試想一下,在沒有子產品化之前,代碼是怎麼樣的呢?

  • 變量和方法容易造成全局污染,不容易維護
  • 通過script标簽從上自下加載資源
  • 大型項目資源難以維護,多人協作容易造成混亂

我們都知道,在HTML中,可以通過script标簽引入js資源。script标簽自上而下的加載執行,當然,如果設定了defer 和 async 屬性的話,那就另當别論了。但不難看出,js的執行順序至關重要,資源加載的先後順序決定了你的項目是否能夠成功運作下去。當項目越來越大,邏輯越來越複雜時,就會讓維護變得困難和混亂起來,全局污染的可能性就大的多了。

形成獨立作用域

在早期,我們通過命名空間的方法,盡量減少全局變量的産生:

// 使用對象字面量的形式形成命名空間
var Lily = {
        name: 'Lily',
        age: 20,
        hello: function() {
            console.log('My name is ' + this.name);
        }
    };

var Tom = {
        name: 'Tom',
        age: 20,
        hello: function() {
            console.log('My name is ' + this.name);
        }
    };
           

雖然命名空間能夠在一定程度上減少全局變量的産生,但它存在另外一個問題。我們很容易就能夠對 Lily 和 Tom 的姓名和年齡進行更改,但實際我們隻希望暴露 hello 方法,而不希望基礎屬性被修改。那麼我們怎麼進行屬性私有化呢?

那就是IIFE 立即執行函數,它會形成自己的獨立作用域。我們可以在立即執行函數中建立屬性和方法,return出希望暴露的方法。

var helloLily = (function() {
    var _Lily = {
        name: 'Lily',
        age: 20,
    };
    var hello = function() {
        console.log('My name is ' + _Lily.name);
    }
    return hello;
})();

var helloTom = (function() {
    var _Tom = {
        name: 'Tom',
        age: 20,
    };
    var hello = function() {
        console.log('My name is ' + _Tom.name);
    }
    return hello;
})();
           

實際上立即執行函數就是子產品化的基石。我們使用 webpack 打包生成的 bundle 檔案就是一個立即執行函數。

webpack 基本配置

随之,社群制定了 COMMONJS/AMD/CMD 子產品化管理方案,讓大程式拆分成互相依賴的小檔案成為可能。

ES6 子產品化

ES6 在語言标準的層面上,實作了子產品功能。

ES6 子產品的設計思想是盡量的靜态化,使得編譯時就能确定子產品的依賴關系,以及輸入和輸出的變量。CommonJS 和 AMD 子產品,都隻能在運作時确定這些東西。比如,CommonJS 子產品就是對象,輸入時必須查找對象屬性。

子產品化的優點:

  • 編譯時加載的特性,讓靜态分析成為可能
  • 團隊協作、代碼組織及管理起來更加友善
  • 減少代碼的耦合性,一個子產品處理一件事情
  • 将來浏覽器的新 API 就能用子產品格式提供,不再必須做成全局變量或者navigator對象的屬性。
  • 不再需要對象作為命名空間(比如Math對象),未來這些功能可以通過子產品提供。

現在我們大緻了解了子產品化的一個演變過程,接下來我們來談談webpack的子產品。

webpack 子產品

在webpack子產品中,我們可以使用:

  • ES2015 import 語句 CommonJS require() 語句
  • AMD define 和 require 語句
  • css/sass/less 檔案中的 @import 語句。
  • 樣式(url(…))
  • HTML 檔案(<img src=…>)中的圖檔連結(image url)

webpack本身隻能處理 json 和 JavaScript 語言。但是它提供的 loader,讓其他語言的編寫子產品變成了可能。loader 描述了 webpack 如何處理非 JavaScript 子產品。

webpack 基礎概念

入口 entry

在webpack進行建構的時候,首先會找到入口檔案,通過入口檔案的依賴遞歸查找到所有依賴。

單頁應用入口配置:

// 執行建構的入口檔案
    entry: './src/index.js'
           

多頁應用入口配置:

// 多頁應用入口
    entry: {
        index: './src/index.js',
        list: './src/list.js',
        about: './src/about.js'
    }
           

出口 output

output 配置項,用于告訴 webpack 将打包生成的 bundle 檔案放在哪個目錄下。

多個入口時必須配置多出口,否則會造成建構出錯。

output: {
        path: path.resolve(__dirname, "./dist"),
        // filename: 'index.js', // 可指定導出檔案命名
        /* 
            可自動根據name生成導出檔案名,多個時會與入口配置的屬性一一對應
         */
        filename: "[name].js", 
    }
           

loader

webpack 自身隻了解 JavaScript 檔案,而 loader 為 webpack 提供了處理其他語言的能力,它可以将所有類型的檔案轉換為 webpack 能夠處理的有效子產品。

  • loader 支援鍊式傳遞,loader将按照從右往左的順序,鍊式的進行調用。

    在第一個 loader,傳入的參數是資源檔案(resource file)的内容。

    在最終一個 loader 中,處理結果應該是 String 或者 Buffer(被轉換為一個 string),代表了子產品的 JavaScript 源碼。

  • loader 可以同步、可以異步
  • loader 子產品需要傳回一個函數

    配置loader:

module: {
        rules: [
            {
                // 正規表達式,辨別應該被處理的檔案
                test: /\.css$/,
                // 使用什麼 loader 去處理比對到的檔案
                use: ['style-loader', 'css-loader'],
            }
        ]
    },
           

插件 plugins

Loader 被用于轉換某些類型的子產品,而插件則可以用于執行範圍更廣的任務。插件的範圍包括,從打包優化和壓縮,一直到重新定義環境中的變量。插件接口功能極其強大,可以用來處理各種各樣的任務。

在使用插件時,我們隻需要通過 require 将插件引入,new 一個執行個體出來就可以。

const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    plugins: [
        new htmlWebpackPlugin({
            template: "./src/index.html",
            filename: "index.html",
        }),
    ]
}
           

模式 mode

用于告訴 webpack 啟用哪種模式的内置優化

module.exports = {
    mode: "development"
}
           
選項 描述
development 會将 process.env.NODE_ENV 的值設為 development。啟用 NamedChunksPlugin 和 NamedModulesPlugin。
production 會将 process.env.NODE_ENV 的值設為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.

bundle

bundle 即是 webpack 打包建構之後生成的檔案。通常情況下,一個入口會對應一個 bundle 檔案。

bundle 檔案是由 chunk 片段組成的,每一個子產品,都會生成一個 chunk 片段。例如入口檔案和入口檔案引用a.js打包後的chunk檔案:

webpack 基本配置
webpack 基本配置

defer 和 async 拓展

當浏覽器解析到js腳本時:

情況一:立即加載執行

<script src="index.js"></script>

情況二:異步解析,解析完成之後立即執行。

可能在DOMContentLoaded事件執行之前或之後執行,一定會在loaded事件前執行。

多個async腳本,誰先解析完成誰先執行

<script src="index.js" async></script>

情況三:異步解析,解析完成之後,需要等待文檔解析完成之後,DOMContentLoaded 事件觸發之前執行。

有defer的腳本會阻止DOMContentLoaded事件,直到腳本解析完成。

defer腳本會按照js順序執行。

<script src="index.js" defer></script>

情況四:既設定了 async,又設定了 defer 的腳本,如果浏覽器兩者都支援,則會按照 async 來處理腳本的解析和執行

<script src="index.js" async defer></script>

webpack 基本配置

情況五:浏覽器在加載 es6 子產品時,需要加入type = ‘module’ 屬性,浏覽器在識别到該屬性時,會對腳本進行異步加載,表現形式上等同于 defer。

如果此時設定了 async,則會解析完成後立即執行,遵循 async 的執行規則。

<script src="index.js" type="module"></script>

參考

webpack官方文檔

阮一峰-es6入門子產品化章節

async、defer與DOMContentLoaded的執行先後關系

代碼位址

webpack基礎

寫在後面:初次釋出于個人部落格網站 http://agangxuezhang.cn/single-minded