Webpack編譯結果淺析

来源:https://www.cnblogs.com/imwtr/archive/2018/10/11/9770135.html
-Advertisement-
Play Games

如今Webpack已經是一個不可或缺的前端構建工具,藉助這個構建工具,我們可以使用比較新的技術(瀏覽器不能直接支持)來開發。 你是否好奇你寫的代碼經過Webpack構建之後會生成什麼東西?是否有時調試遇到莫名其妙的問題? 本文不講如何進行配置,只是基於幾個基礎的例子,簡要分析一下 webpack@4 ...


如今Webpack已經是一個不可或缺的前端構建工具,藉助這個構建工具,我們可以使用比較新的技術(瀏覽器不能直接支持)來開發。

你是否好奇你寫的代碼經過Webpack構建之後會生成什麼東西?是否有時調試遇到莫名其妙的問題?

本文不講如何進行配置,只是基於幾個基礎的例子,簡要分析一下 [email protected] 構建後的代碼結構,當然了,並不全面,時間問題能力問題還不能理解到位。

代碼比較長,生成的代碼也比較晦澀比較繞,也可能條理不順,客官坐好咧~

 

 

 

一、Webpack的運行機制

Webpack的運行過程實際上可以歸納為這個步驟

讀取配置參數 -> 相關事件綁定(插件參與) ->  識別各入口Entry模塊 -> 編譯文件(loader參與)-> 生成文件

首先讀取我們的配置文件如 webpack.config.js,然後事件流就參與進來綁定相關的事件,Webpack中的事件使用 Tapable 來管理,在這一階段,除了綁定webpack內置的一大堆事件之外,還支持自定義的一些事件處理。

配置中的 plugins部分,實際上也可以看作是一些自定義的事件處理,因為插件將在定義的”相關時刻“插入到編譯過程中處理資源,這裡的”相關時刻“指的就是 訂閱-發佈 模式中的發佈環節

webpack支持多個入口模塊,所以還需要進行各入口模塊的分析(這裡的入口模塊只能為JS模塊),比如以下兩個入口模塊

分析完入口模塊,接下來分析該模塊的依賴,並使用相關loader進行編譯(如果需要loader的話),真正的編譯環節是在這裡。

期間會使用AST抽象語法樹來分析語法,直到編譯完成,輸出到相應的文件中

可以來看看這篇文章 Webpack運行機制

 

二、Webpack編譯結果

由最簡單的例子開始

2.1 無依賴的單個模塊

./main.js

console.log('main');

./webpack.config.js

module.exports = {
    // entry: './main',
    entry: {
        main: './main'
    },

    mode: 'none',

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
};

註意,在webpack4中預設的mode對 development和production進行了一些特殊配置,為了簡化,這裡就設置成none

編譯一個文件,將在dist目錄中生成

./dist/main.js

 1 /******/ (function(modules) { // webpackBootstrap
 2 /******/     // The module cache
 3 /******/     var installedModules = {};
 4 /******/
 5 /******/     // The require function
 6 /******/     function __webpack_require__(moduleId) {
 7 /******/
 8 /******/         // Check if module is in cache
 9 /******/         if(installedModules[moduleId]) {
10 /******/             return installedModules[moduleId].exports;
11 /******/         }
12 /******/         // Create a new module (and put it into the cache)
13 /******/         var module = installedModules[moduleId] = {
14 /******/             i: moduleId,
15 /******/             l: false,
16 /******/             exports: {}
17 /******/         };
18 /******/
19 /******/         // Execute the module function
20 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 /******/
22 /******/         // Flag the module as loaded
23 /******/         module.l = true;
24 /******/
25 /******/         // Return the exports of the module
26 /******/         return module.exports;
27 /******/     }
28 /******/
29 /******/
30 /******/     // expose the modules object (__webpack_modules__)
31 /******/     __webpack_require__.m = modules;
32 /******/
33 /******/     // expose the module cache
34 /******/     __webpack_require__.c = installedModules;
35 /******/
36 /******/     // define getter function for harmony exports
37 /******/     __webpack_require__.d = function(exports, name, getter) {
38 /******/         if(!__webpack_require__.o(exports, name)) {
39 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
40 /******/         }
41 /******/     };
42 /******/
43 /******/     // define __esModule on exports
44 /******/     __webpack_require__.r = function(exports) {
45 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
46 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
47 /******/         }
48 /******/         Object.defineProperty(exports, '__esModule', { value: true });
49 /******/     };
50 /******/
51 /******/     // create a fake namespace object
52 /******/     // mode & 1: value is a module id, require it
53 /******/     // mode & 2: merge all properties of value into the ns
54 /******/     // mode & 4: return value when already ns object
55 /******/     // mode & 8|1: behave like require
56 /******/     __webpack_require__.t = function(value, mode) {
57 /******/         if(mode & 1) value = __webpack_require__(value);
58 /******/         if(mode & 8) return value;
59 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
60 /******/         var ns = Object.create(null);
61 /******/         __webpack_require__.r(ns);
62 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
63 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
64 /******/         return ns;
65 /******/     };
66 /******/
67 /******/     // getDefaultExport function for compatibility with non-harmony modules
68 /******/     __webpack_require__.n = function(module) {
69 /******/         var getter = module && module.__esModule ?
70 /******/             function getDefault() { return module['default']; } :
71 /******/             function getModuleExports() { return module; };
72 /******/         __webpack_require__.d(getter, 'a', getter);
73 /******/         return getter;
74 /******/     };
75 /******/
76 /******/     // Object.prototype.hasOwnProperty.call
77 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 /******/
79 /******/     // __webpack_public_path__
80 /******/     __webpack_require__.p = "";
81 /******/
82 /******/
83 /******/     // Load entry module and return exports
84 /******/     return __webpack_require__(__webpack_require__.s = 0);
85 /******/ })
86 /************************************************************************/
87 /******/ ([
88 /* 0 */
89 /***/ (function(module, exports) {
90 
91 
92 console.log('main');
93 
94 
95 /***/ })
96 /******/ ]);

可以看到首先是一個匿名函數,在87行時自執行傳入

[
/* 0 */
/***/ (function(module, exports) {


console.log('main');


/***/ })
/******/ ]

這個是modules,表示有一個模塊需要載入

第3行使用 installedModules 來緩存已經載入的模塊

webpack由最初支持 commonjs模塊規範,到後來要支持es6的模塊等,為了相容不同的模塊機制,定義了一個 __webpack_require__ 函數作為webpack內部的require

/******/     // The require function
/******/     function __webpack_require__(moduleId) {
/******/
/******/         // Check if module is in cache
                // 如果模塊已經載入則直接使用
/******/         if(installedModules[moduleId]) {
/******/             return installedModules[moduleId].exports;
/******/         }
/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId, // 模塊ID
/******/             l: false, // 模塊是否已載入
/******/             exports: {} // 模塊的導出項
/******/         };
/******/
/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/         // Flag the module as loaded
/******/         module.l = true; // 標記已經載入
/******/
/******/         // Return the exports of the module
/******/         return module.exports; // 返回模塊的導出項目
/******/     }

其中,這個調用非常重要

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

結合匿名函數傳入的參數來看,modules[moduleId] 其實就是這個

(function(module, exports) {


console.log('main');


/***/ })

第一個參數 module.exports 實際上就是上面模塊的導出項,是為了保證this能正確地指向module,第二第三個參數按著順序來,第四個參數一般用於依賴

因為這裡 main.js沒有依賴其他模塊,所以沒有傳進來

最後 return module.exports; 實際上就是返回了模塊的導出項,在上面的84行中,入口模塊被引入 。從而自動地載入第一個模塊並執行

return __webpack_require__(__webpack_require__.s = 0); // __webpack_require__.s為入口文件,此處引用模塊ID

另外再看其它代碼,

/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules; // 將模塊存起來
/******/
/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules; // 將已經載入的模塊存起來

/******/     // __webpack_public_path__
/******/     __webpack_require__.p = ""; // 設置的 publicPath

這裡沒什麼可說的,這裡的publicPath對應於 output中的配置,如

output: {
        publicPath: './dist/',
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },

另外

/******/     // define getter function for harmony exports
/******/     __webpack_require__.d = function(exports, name, getter) {
/******/         if(!__webpack_require__.o(exports, name)) {
/******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/         }
/******/     };
/******/
/******/     // define __esModule on exports
/******/     __webpack_require__.r = function(exports) {
/******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/         }
/******/         Object.defineProperty(exports, '__esModule', { value: true });
/******/     };

/******/     // Object.prototype.hasOwnProperty.call
/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

這裡 __webpack_require__.o 這裡只是hasOwnProperty的包裝

__webpack_require__.d 這裡是對exports定義一個屬性(當前模塊未用到,暫且如此,理解不到位)

__webpack_require__.r 這裡是對es6模塊中的export的支持(當前模塊未用到,暫且如此,理解不到位)

還有這個,這個就更難理解了

/******/     // create a fake namespace object
/******/     // mode & 1: value is a module id, require it
/******/     // mode & 2: merge all properties of value into the ns
/******/     // mode & 4: return value when already ns object
/******/     // mode & 8|1: behave like require
/******/     __webpack_require__.t = function(value, mode) {
/******/         if(mode & 1) value = __webpack_require__(value);
/******/         if(mode & 8) return value;
/******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/         var ns = Object.create(null);
/******/         __webpack_require__.r(ns);
/******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/         return ns;
/******/     };
/******/
/******/     // getDefaultExport function for compatibility with non-harmony modules
/******/     __webpack_require__.n = function(module) {
/******/         var getter = module && module.__esModule ?
/******/             function getDefault() { return module['default']; } :
/******/             function getModuleExports() { return module; };
/******/         __webpack_require__.d(getter, 'a', getter);
/******/         return getter;
/******/     };

__webpack_require__.t 暫時不說明瞭,還看不懂怎麼調用的..

__webpack_require__.n 這個主要也是為 es6模塊服務的,也沒能理解好,知道的可以在評論區留言哈~

 

2. 有依賴的單個模塊

先使用最基礎的commonjs模塊規範  require, exports ,module.exports 有助於理解上面那個模塊的導出項目

./main.js

let number = require('./number');

console.log('main', number);

 

./number.js

let n = 10;

exports.n = n;

編譯後,生成的文件變化的只是匿名函數傳入的部分

./dist/main.js

// 省略

/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {


let number = __webpack_require__(1);

console.log('main', number);


/***/ }),
/* 1 */
/***/ (function(module, exports) {


let n = 10;

exports.n = n;


/***/ })
/******/ ]);

註意到前面的數字即是模塊的ID,也可圖中的一致

這裡__webpack_require__參數被傳進來,main.js中引入number這個模塊 __webpack_require__(1);

number模塊中 exports.n = n,註意這裡的 exports即是調用時的第二個參數

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

所以此時 n屬性被存入module的export導出項中,從而__webpack_require__(1) 就能獲取這個導出項

 

換種方式,使用es6的模塊導出

更改 ./number.js

let n = 10;

export {
    n
};

編譯後 ./dist/main.js

/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {


let number = __webpack_require__(1);

console.log('main', number);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });

let n = 10;




/***/ })
/******/ ]);

可以看到模塊1變了,為了相容 export ,使用 __webpack_require__.r 定義了它為es6模塊,再使用__webpack_require__.d 將 n保存到模塊的導出項中

__webpack_require__.d 函數中的 getter即為 這裡的 function() { return n; },通過設置為對象的get屬性,可以獲取到 n這個返回值

var o = {};

Object.defineProperty(o, 'abc', {
    get: function() {
        return 123;
    }
});

console.log(o.abc); // 123

所以將 let n = 10 定義在後面也是沒問題的,因為getter是在number模塊被調用返回之後才使用的

 

接著,我們把引入依賴文件改為import

./main.js

import {n} from './number';

console.log('main', n);

編譯後 ./dist/main.js

/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["n"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });

let n = 10;




/***/ })
/******/ ]);

同樣的,這時main模塊用到了es6的模塊引入方式,所以 __webpack_require__.r(__webpack_exports__);

var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

這個 __webpack_require__(1) 實際上就是 number模塊的模塊導出項,自然就能取到屬性 n 了

 

接下來,著眼那個 default字眼,繼續更換模塊的導入導出方式

./main.js

import n from './number';

console.log('main', n);

./number.js

let n = 10;

export default n;

./dist/main.js

/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);

let n = 10;

/* harmony default export */ __webpack_exports__["default"] = (n);


/***/ })
/******/ ]);

可以看到,變化只是屬性變成了default

 

再來一種 es6的方式

./main.js

import n from './number';

console.log('main', n);

 

./number.js

import {str as n} from './str';

export default n;

 

./str.js

export var str = 10;

 

編譯後

./dist/main.js

/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);



console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _str__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);



/* harmony default export 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多數應用場景下,我們需要對重要數據進行備份、並放置到一個安全的地方,以備不時之需。 常見的 MySQL 數據備份方式有,直接打包複製對應的資料庫或表文件(物理備份)、mysqldump 全量邏輯備份、xtrabackup 增量邏輯備份等。 常見的數據存儲方式有,本機存儲、FTP 上傳到遠程伺服器... ...
  • Databricks的工程師,Apache Spark Committer介紹了Databricks和Spark的歷史,包括了Spark 1.4中的重要特性和進展,涵蓋了Spark早期版本的主要功能和使用方法,講了大數據領域近些年的發展,也介紹了Spark從這些年其它理論或者技術中吸取的靈感,當然, ...
  • 本文介紹了無法通過fastboot擦除misc分區時,利用QFIL工具退出FFBM的詳細步驟。 ...
  • 一、屏蔽系統簡訊功能 1、屏蔽所有簡訊 android 4.2 簡訊發送流程分析可參考這篇 [戳這][1] 源碼位置 vendor\mediatek\proprietary\packages\apps\Mms\src\com\android\mms\transaction\SmsReceiverSe ...
  • 歡迎大家前往 "騰訊雲+社區" ,獲取更多騰訊海量技術實踐乾貨哦~ 本文由 "QQ音樂技術團隊" 發表於 "雲+社區專欄" 一、問題背景與分析 不久前,團隊發現其Android平臺App在播放MV視頻《鳳凰花開的路口》時,會帶有如電流聲一般的雜音,這影響了用戶體驗。 研發同學在初步定位時,發現有如下 ...
  • 歡迎大家前往 "騰訊雲+社區" ,獲取更多騰訊海量技術實踐乾貨哦~ 本文由 "QQ音樂技術團隊" 發表於 "雲+社區專欄" 上篇:Android P 行為變更適配 Android P 這次有很多行為變更,其中不乏一些需要亟需適配的變更。 一、全面屏檢測 在 Android 8.0 時代各個手機廠商就 ...
  • 測試內容 ...
  • 1、清除浮動的相容性(低版本的瀏覽器不相容問題) .clearfix:after{ content:""; clear:both; display:block; visibility:hidden; height:0; } .clear{ *zoom:1; } 2、透明度的相容性 opacity:0 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...