【手把手帶你配 webpack】第二步, 面試官-談談你對模塊化的理解

上一步提到, 對於我配出來的 webpack 領導並非很承認, 那隻好...前端

2019-01-24-10-36-53
這裏用了 程序員小灰的圖, 侵刪.(ps: 我也不知道連接的是否是原創)

尷尬的面試題

問: 你知道的 js 模塊化方案有哪些?
答: js 的模塊化通常來講有 Commonjs AMD(CMD) ESModule 等, Commonjs 使用的同步加載通常用於 node, AMD(CMD) 因爲是異步加載因此廣泛用在前端. ESModule 旨在實現先後端模塊化的統一. 言簡意駭, 點到爲止. 簡直優秀... node

2019-01-24-11-10-43

問: 那麼爲何同步的 Commonjs 要用在 node, 異步的 AMD 要用在前端呢?
答: node 應用程序運行在服務器上, 程序經過文件系統能夠直接讀取到各個模塊的文件, 特色是響應快速不會由於同步而阻塞了程序的運行. 前端項目運行在瀏覽器中, 每一個模塊都要經過 http 請求加載 js 文件, 受到網絡等因素的影響若是同步的話會使得瀏覽器出現"假死"的狀況 --- 也就是卡住了. 影響用戶體驗. 整場邂逅已經徹底 cover 住了有沒有 ^_^. webpack

2019-01-24-11-41-30

問: 那你聽過 webpack 嗎? 知道 webpack 是怎麼實現模塊化的嗎?
答: webpack 看這裏, webpack 的模塊化其實就是把 ES6 模塊化代碼轉碼成 Commonjs 的形式(咦, 有點慌, 可是面試題就是這麼背的呀), 從而兼容瀏覽器的. 先後矛盾, 已經開始有點雞動啦, 可是強忍着但願矇混過關... git

88ad2618a1b1a81e05309c025

問: 好的, 既然你說 Commonjs 的同步方式前端不適用, 爲何 webpack 轉碼的 Commonjs 形式之後能夠前端運行呢?
答: emmmmm程序員

面試官: 那要不今天的面試就先到這兒吧, 你先回去等通知...
我: hmmmmm es6

2019-01-24-14-15-46

webpack 到底作了什麼

一遍又一遍的執行了 webpack 命令, 一次又一次的研究生成的 bundle.js. 嗯哼? 全部的 js 模塊都打包到了一個 bundle.js 裏了, 根本沒有分模塊加載, 那還管他丫的同步仍是異步. js 文件直接讀取到內存裏一步到位, 比 nodejs 須要讀取文件還要方便...github

那麼, webpack 究竟是怎樣把這麼多文件組合成一個 bundle.js 文件的呢? 仔細想一下, 仍是把天書和你們一塊兒學習一下吧, 要不感受這個系列的文章會變成表情包分享教程! 2019-01-25-16-16-47web

接下來我會分享 Commonjs 和 es6 Module 兩種模塊化方式 webpack 的處理, 若是有小夥伴對這兩種方式有疑問, 請 google 一下. 其實所謂的模塊化無非就是給一段代碼添加了兩把鑰匙實現和其餘代碼的通訊, 相互調用, 經過 require 獲取引入其餘模塊的能力, 經過 export 實現向其餘模塊輸出方法的功能.面試

commonjs 模塊化的處理

首先添加一個 npm script 修改 package.json 以下圖: npm

2019-01-25-16-21-54

添加完成後代碼, 就能夠拋棄 ./node_modules/.bin/webpack 這個命令了, 打包時只須要 npm run build 便可.

2019-01-25-16-24-01

搭建項目代碼, 代碼地址, 其中, index.js 內容:

const foo = require('./foo');

console.log(foo);
console.log('我是高級前端工程師~');
複製代碼

foo.js 內容:

module.exports = {
  name: 'quanquan',
  job: 'fe',
};
複製代碼

純純粹粹的 Commonjs 代碼, 執行 npm run build 後, 咱們再看看打包後的 bundle.js. (ps: 添加了部分註釋)

(function(modules) {
	// 緩存模塊對象
	var installedModules = {};
	// 模擬 commonjs 實現的 require
	function __webpack_require__(moduleId) {
		// require 模塊時先判斷是否已經緩存, 已經緩存的模塊直接返回
		if(installedModules[moduleId]) {
			return installedModules[moduleId].exports;
		}
		// (模擬)建立一個模塊, 並把新模塊的引用保存到緩存中
		var module = installedModules[moduleId] = {
            // 模塊 id
            i: moduleId,
            // 模塊是否已加載
            l: false,
            // 模塊主體內容, 會被重寫
			exports: {}
		};
		// 執行如下模塊的包裝函數, 並把模塊內部的 this 志向模塊主體
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		// 將模塊標記爲已加載
		module.l = true;
		// 返回模塊主體內容
		return module.exports;
	}
    // 向外暴露全部的模塊
	__webpack_require__.m = modules;
	// 向外暴露已緩存的模塊
    __webpack_require__.c = installedModules;

    // 下邊兩個方法暫時尚未用到
	// define getter function for harmony exports
	__webpack_require__.d = function(exports, name, getter) {
		if(!__webpack_require__.o(exports, name)) {
			Object.defineProperty(exports, name, {
				configurable: false,
				enumerable: true,
				get: getter
			});
		}
	};
	// 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;
    };

    // Object.prototype.hasOwnProperty.call 這個就沒啥好解釋的啦
    // js 權威指南上有說
    __webpack_require__.o = function(object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    // __webpack_public_path__
    // 這個暫時尚未用到
	__webpack_require__.p = "";
    // Load entry module and return exports
    // 準備工做作完了, require 一下入口模塊, 讓項目跑起來
	return __webpack_require__(__webpack_require__.s = 0);
})
/******** 華麗的分割線 上邊時 webpack 初始化代碼, 下邊是咱們寫的模塊代碼 ***************/
([
/* 模塊 0 對應 index.js */
/***/ (function(module, exports, __webpack_require__) {

const foo = __webpack_require__(1);

console.log(foo);
console.log('我是高級前端工程師~');


/***/ }),
/* 模塊 1 對應 foo.js */
/***/ (function(module, exports) {

module.exports = {
  name: 'quanquan',
  job: 'fe',
};


/***/ })
]);
複製代碼

原來 webpack 就是把咱們寫的代碼用一個一個的包裝函數包裝了起來, 再執行__webpack_require__的時候調用一下包裝函數. 經過包裝函數內部的代碼重寫了參數中參數 module 的 exports 屬性, 獲取到咱們編寫的模塊的主體代碼.

因此咱們看到了 index.js 包裝後的代碼爲:

function(module, exports, __webpack_require__) {
    const foo = __webpack_require__(1);
    console.log(foo);
    console.log('我是高級前端工程師~');
}
複製代碼

foo.js 包裝後的代碼爲:

function(module, exports) {
    module.exports = {
        name: 'quanquan',
        job: 'fe',
    };
}
複製代碼

因爲 index.js 中有 require('./foo') 因此 index.js 生成的包裝函數參數中多了__webpack_require__用於導入 foo 模塊, 從頭至尾看下來, 以前的天書彷佛再也不晦澀難懂了. 固然這一塊內容須要你們稍微動動腦筋思考一下. 也但願你們把本身的想法和問題放到評論區, 咱們一塊兒討論.

2019-01-25-19-18-47

es6 Module 模塊化的處理

改動咱們的項目代碼爲 es6 模塊化代碼, 首先修改 index.js:

import foo from './foo';

console.log(foo);
console.log('我是高級前端工程師~');
複製代碼

其次修改 foo.js

export default {
  name: 'quanquan',
  job: 'fe',
};
複製代碼

最後就是命令行執行 npm run build 進行打包啦. 打包完成後 bundle.js 內容以下(通過優化):

(function(modules) {
	var installedModules = {};
	function __webpack_require__(moduleId) {
		if(installedModules[moduleId]) {
			return installedModules[moduleId].exports;
		}
		var module = installedModules[moduleId] = {
			i: moduleId,
			l: false,
			exports: {}
		};
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		module.l = true;
		return module.exports;
	}
	__webpack_require__.m = modules;
	__webpack_require__.c = installedModules;
	__webpack_require__.d = function(exports, name, getter) {
		if(!__webpack_require__.o(exports, name)) {
			Object.defineProperty(exports, name, {
				configurable: false,
				enumerable: true,
				get: getter
			});
		}
	};
	__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__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
	__webpack_require__.p = "";
	return __webpack_require__(__webpack_require__.s = 0);
})([
  function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    var __WEBPACK_IMPORTED_MODULE_0__foo__ = __webpack_require__(1);
    console.log(__WEBPACK_IMPORTED_MODULE_0__foo__["a"]);
    console.log('我是高級前端工程師~');
  },
  function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
    __webpack_exports__["a"] = ({
      name: 'quanquan',
      job: 'fe',
    });
  }
]);
複製代碼

打包的結果仍然是天書式的代碼, 還好和 commonjs 模塊化方式大同小異. webpack 初始化代碼完成相同.

index.js 生成的包裹代碼爲:

function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
	Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
	var __WEBPACK_IMPORTED_MODULE_0__foo__ = __webpack_require__(1);
	console.log(__WEBPACK_IMPORTED_MODULE_0__foo__["a"]);
	console.log('我是高級前端工程師~');
}
複製代碼

foo.js 生成的包裹代碼爲:

function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
	__webpack_exports__["a"] = ({
		name: 'quanquan',
		job: 'fe',
	});
}
複製代碼

不難發現, 和 commonjs 不一樣的地方:

  • 首先, 包裝函數的參數以前的 module.exports 變成了__webpack_exports__
  • 其次, 在使用了 es6 模塊導入語法(import)的地方, 給__webpack_exports__添加了屬性__esModule
  • 其他的部分和 commonjs 相似

咱們發現, commonjs 中咩有用到的 __webpack_require__.n 這個方法仍是沒有用到, 這難道是廢方法? 那豈不是能夠給 webpack 提個 issue, 參與過得開源項目再也不是空白. 美滋滋...

2019-01-28-11-42-30

要是這樣都能搭上這種國際開源項目的班車, 豈不是太過容易了呢?

2019-01-28-11-52-20

es6 Module CommonJs 混合使用

修改 foo.js 內容以下:

module.exports = {
  name: 'quanquan',
  job: 'fe',
};
複製代碼

打包的結果爲:

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// 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,
/******/ 			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;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// 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;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__foo__ = __webpack_require__(1);
// 這裏用到了 __webpack_require__.n
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__foo___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__foo__);


console.log(__WEBPACK_IMPORTED_MODULE_0__foo___default.a);
console.log('我是高級前端工程師~');
/* harmony default export */ __webpack_exports__["default"] = ({});


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

module.exports = {
  name: 'quanquan',
  job: 'fe',
};


/***/ })
/******/ ]);
複製代碼

index.js 沒有變化, 也就是說當導出模塊導出的語法爲 commonjs 而導入模塊的導入語法爲 es6 時, 導入模塊就會用到了 __webpack_require__.n 這個方法.

下集預告: 到這裏一個純 js 文件的打包結果就分析完了, webpack 巧妙的把全部的模塊都打包到了一個 bundle.js 文件中從而實現同步加載模塊, 實在高明. 可是, 若是你的項目很大, 動輒成百上千的 js 模塊, 此時運用 webpack 又能怎樣實現代碼拆分呢? 咱們下一步一塊兒討論.

2019-01-28-12-14-32
相關文章
相關標籤/搜索