webpack已經成爲編譯構建前端項目的必備工具。今天就來了解一下webpack的打包機制,先從最簡單的js編譯打包開始。
先來看下webpack打包以後的文件是什麼樣,而後再反推它的打包機制。補充說明:本文使用的是webpack 4.x的版本。前端
代碼示例:node
入口文件index.js,依賴a.js和b.js,而a.js和b.js都依賴c.jswebpack
代碼以下:es6
// index.js import {getDate} from "./a"; import {getDay} from "./b"; console.log(getDate() + ' ' + getDay()); // a.js import now from './c'; export function getDate() { var date = now(); var year = date.getFullYear(); var month = date.getMonth() + 1; month = month > 9 ? month : `0${month}`; var day = date.getDate(); day = day > 9 ? day : `0${day}`; return `${year}-${month}-${day}`; } // b.js import now from './c'; export function getDay() { var date = now(); var arr = ['日', '一', '二', '三', '四', '五', '六']; return `周${arr[date.getDay()]}`; } // c.js export default function now() { var date = new Date(); return date; }
webpack打包後的bundle.js以下:web
/******/ (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, { 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 }); /******/ }; /******/ /******/ // 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; /******/ }; /******/ /******/ // 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 = "./example/src/index.js"); /******/ }) /************************************************************************/ /******/ ({ /***/ "./example/src/a.js": /*!**************************!*\ !*** ./example/src/a.js ***! \**************************/ /*! exports provided: getDate */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getDate\", function() { return getDate; });\n/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./c */ \"./example/src/c.js\");\n\nfunction getDate() {\n var date = Object(_c__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n var year = date.getFullYear();\n var month = date.getMonth() + 1;\n month = month > 9 ? month : `0${month}`;\n var day = date.getDate();\n day = day > 9 ? day : `0${day}`;\n return `${year}-${month}-${day}`;\n}\n\n\n//# sourceURL=webpack:///./example/src/a.js?"); /***/ }), /***/ "./example/src/b.js": /*!**************************!*\ !*** ./example/src/b.js ***! \**************************/ /*! exports provided: getDay */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getDay\", function() { return getDay; });\n/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./c */ \"./example/src/c.js\");\n\nfunction getDay() {\n var date = Object(_c__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n var arr = ['日', '一', '二', '三', '四', '五', '六'];\n return `周${arr[date.getDay()]}`;\n}\n\n\n//# sourceURL=webpack:///./example/src/b.js?"); /***/ }), /***/ "./example/src/c.js": /*!**************************!*\ !*** ./example/src/c.js ***! \**************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return now; });\nfunction now() {\n var date = new Date();\n return date;\n}\n\n\n//# sourceURL=webpack:///./example/src/c.js?"); /***/ }), /***/ "./example/src/index.js": /*!******************************!*\ !*** ./example/src/index.js ***! \******************************/ /*! no exports provided */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./example/src/a.js\");\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./b */ \"./example/src/b.js\");\n\n\nconsole.log(Object(_a__WEBPACK_IMPORTED_MODULE_0__[\"getDate\"])() + ' ' + Object(_b__WEBPACK_IMPORTED_MODULE_1__[\"getDay\"])());\n\n\n//# sourceURL=webpack:///./example/src/index.js?"); /***/ }) /******/ });
簡單點,打包後的形式是這樣的:babel
(function(modules) { // ... })({ // ... })
其實就是自執行函數,傳入的moduels對象是下面的形式:ide
{ "./example/src/a.js": (function(module, __webpack_exports__, __webpack_require__) { // 模塊代碼 }), "./example/src/b.js": (function(module, __webpack_exports__, __webpack_require__) { // 模塊代碼 }), "./example/src/c.js": (function(module, __webpack_exports__, __webpack_require__) { // 模塊代碼 }), "./example/src/index.js": (function(module, __webpack_exports__, __webpack_require__) { // 模塊代碼 }) }
模塊代碼被編譯成es5格式的代碼,在執行時,自執行函數從入口文件開始執行函數
__webpack_require__(__webpack_require__.s = "./example/src/index.js");
再來看看__webpack_require__
函數的定義:工具
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; /******/ }
函數返回的是module.exports
,對於已經存在於installedModules
中的模塊,再次require
時,直接返回module.exports
,而不會執行。ui
根據webpack打包後的文件,能夠得出打包的思路:
這一步經過babel來轉換
babylon
生成ASTbabel-core
將AST從新生成源碼/** * 獲取文件,生成AST * @param filename */ function getAst(filename) { const content = fs.readFileSync(filename, 'utf-8'); return babylon.parse(content, { sourceType: 'module' }); } /** * 編譯 * @param ast */ function getTranslateCode(ast) { const { code } = babel.transformFromAst(ast, null, { presets: ['env'] }); return code; }
經過babel-traverse遍歷AST,找到模塊的依賴
function getDependencies(ast) { let dependencies = []; traverse(ast, { ImportDeclaration: ({node}) => { dependencies.push(node.source.value); } }); return dependencies; }
function parse(filename, entry) { let absolutePath = path.join(entry, filename + '.js'); const ast = getAst(absolutePath); return { filename, dependence: getDependencies(ast), // 解析依賴 code: getTranslateCode(ast) // 編譯成es5 }; }
此時的getDependencies還只是解析一個模塊的依賴,但依賴的依賴沒有解析,因此須要深度遍歷
const modules = {}; /** * 深度隊列依賴 * @param main */ function getQueue(main) { if (modules[main.filename]) { return; } modules[main.filename] = main; main.dependence.forEach(dep => { let child = parse(dep, 'example/src'); getQueue(child); }); }
function bundle(queue) { let modules = ''; queue.forEach(mod => { modules += `'${mod.filename}': function(require, module, exports) {${mod.code}},` }); const result = ` (function(modules){ var installedModules = {}; function require(moduleId){ if (installedModules[moduleId]) { return installedModules[moduleId].exports; } var fn = modules[moduleId]; var module = installedModules[moduleId] = {exports: {}}; fn(require, module, module.exports); return module.exports; } require('index'); })({${modules}}) `; return result; }
(function (modules) { var installedModules = {}; function require(moduleId) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } var fn = modules[moduleId]; var module = installedModules[moduleId] = {exports: {}}; fn(require, module, module.exports); return module.exports; } require('index'); })({ 'index': function (require, module, exports) { "use strict"; var _a = require("./a"); var _b = require("./b"); console.log((0, _a.getDate)() + ' ' + (0, _b.getDay)()); }, './a': function (require, module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDate = getDate; var _c = require("./c"); var _c2 = _interopRequireDefault(_c); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } function getDate() { var date = (0, _c2.default)(); var year = date.getFullYear(); var month = date.getMonth() + 1; month = month > 9 ? month : "0" + month; var day = date.getDate(); day = day > 9 ? day : "0" + day; return year + "-" + month + "-" + day; } }, './c': function (require, module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = now; function now() { var date = new Date(); return date; } }, './b': function (require, module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDay = getDay; var _c = require("./c"); var _c2 = _interopRequireDefault(_c); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } function getDay() { var date = (0, _c2.default)(); var arr = ['日', '一', '二', '三', '四', '五', '六']; return "\u5468" + arr[date.getDay()]; } } })
這樣,基本上處理js依賴和編譯的工做算是完成了