初识 webpack ,先看看它打包出来的代码是怎么样的。
环境
webpack@4、 wepback-cli@3
备注:在 webpack@3 中 webpack 和 webpack-cli 是集成在一起的,后来开发者为了方便管理和维护,对它们组成的整体进行了拆分,wepback 注重打包编译,wepback-cli 提供命令行界面接口,CLI 即 Command line interface。
配置
var path = require('path');
module.exports = {
// webapck3 是没有 mode 这个选项的
// webapck4+ 的 mode 默认是 'production'
mode: 'development',
entry: './src/single.js',
output: {
// 执行 webpack 命令后,打包编译生成 demo 的目录
path: path.resolve(__dirname, 'dist'),
// 出口文件名,和入口配置的名称对应,默认是 “main”
filename: '[name].js'
}
}
被打包的JS文件
// ./src/other.js
export const a = 2;
// ./src/single.js
import {a} from './other';
console.log(a);
console.log('single.js');
开发模式下生成的文件
// webpack@4 打包的代码
/**
* 最外层是一个自执行函数,闭包了全局函数的 modules
* 进来,这里的 modules 就是一个个的模块函数,我们
* 编写的模块化文件最终都被映射到了 modules 上。
*/
(function (modules) { // webpackBootstrap
// The module cache
/**
* 全局安装的模块,如果模块 id 对应的模块不在全局安装
* 的模块里,则执行这个模块函数,然后缓存到全局安装的
* 模块里,下次需要用的时侯再从全局安装的模块里取。
*/
var installedModules = {};
// The require function
// 相当于模块化规范里的 require 方法
// 返回 module.exports 对象,即模块的导出对象。
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
// 执行模块 id 对应的模块函数,模块函数的执行结果都放到 module.exports 上
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
// 标记该模块已经被加载过了,即已经执行过了模块函数
module.l = true;
// Return the exports of the module
// 返回这个模块的导出对象,即 module.exports
return module.exports;
}
// expose the modules object (__webpack_modules__)
/** 将所有的模块 modules 暴露给 require 方法的 m 属性 */
__webpack_require__.m = modules;
// expose the module cache
/** 将全局模块缓存 installedModules 暴露给 reqire 方法的 c 属性 */
__webpack_require__.c = installedModules;
// define getter function for harmony exports
/**
* 如果某个模块导出了相同名称的"东西",它不会被覆盖。
* 导出对象 exports 如果没有 name 对应的属性,
* 则为 exprots 对象 定义 name 对应的 getter,
* 访问 exports 的 name 对应的属性时,会执行
* getter。
* @param {*} exports 导出的那个对象
* @param {*} name 属性
* @param {*} getter getter 函数
*/
__webpack_require__.d = function (exports, name, getter) {
// 如果导出对象 exports 没有 name 对应的属性,则为其定义 getter
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
/**
* 如果你的模块采用了 ES6+ 的模块化规范,
* 就将 exports 导出对象标记为 ES6+ Module
* @param {*} exports 导出的那个对象
*/
__webpack_require__.r = function (exports) {
// 如果支持 Symbol (ES6 的数据类型),并且 Symbol 有 toStringTag 方法
// 则将这个 exports 对象的 toString 方法重写,exports.toString() 会输出
// [object Module]
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
// 并且将 exports 的 __esModule 设为 true
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
/**
* 创建一个假的命名空间的对象,说实话真不知道用来干嘛的..
* @param {*} value exports 或者模块 id
* @param {*} mode 模式
*/
__webpack_require__.t = function (value, mode) {
// value 是模块的 id,执行 require
if (mode & 1) value = __webpack_require__(value);
// 直接返回 value
if (mode & 8) return value;
// 如果 value 已经是 ES6+ Module 的实例,直接返回 value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
// ns 是一个没有原型对象的空对象
var ns = Object.create(null);
// 将 ns 这个空对象标记为 ES6+ Module 的实例
__webpack_require__.r(ns);
// 为 ns 添加默认导出值 value
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
// 如果 value 是一个对象,将它的属性浅拷贝到 ns 上,然后返回 ns
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
/**
* 返回一个函数,获取默认导出
* @param {*} module 整个模块
*/
__webpack_require__.n = function (module) {
// 如果是 ES6+ Module,则获取 module['default']
// 不是 ES6+ Module,则获取整个的 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
/** 判断对象 object 是否有自己的 property 属性,排除继承过来的 property。 */
__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
/**
* 应用程序中所有的资源的基本路径,和 webpack 配置里的 output.publicPath 对应,
* 模块访问静态资源的时候会用到这个路径,它是打包编译项目后放置静态资源的目录路径
*/
__webpack_require__.p = "";
// Load entry module and return exports
// 执行入口模块函数,并将导出对象返回出去
return __webpack_require__(__webpack_require__.s = "./src/single.js");
})
/************************************************************************/
({
"./src/other.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return a; });\nconst a = 2;\n\n//# sourceURL=webpack:///./src/other.js?");
/***/
}),
"./src/single.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _other__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./other */ \"./src/other.js\");\n\r\nconsole.log(_other__WEBPACK_IMPORTED_MODULE_0__[\"a\"]);\r\nconsole.log('single.js');\n\n//# sourceURL=webpack:///./src/single.js?");
})
});
总结
webpack 打包出来的 JS 文件,整体就是一个自执行函数,同时它传入了一个闭包参数——通过编译一个个的 JS 文件后生成的模块集合。
主要流程:先定义了一个“模块缓存池”(installedModules)和 webapck 自己的 require 函数(__webpack_require__),然后从入口模块函数开始执行,模块之间通过 module.exports(导出模块)和 require (导入模块)解决依赖关系,如果入口模块依赖了其他的模块,则执行该模块函数,以此类推,直到所有的依赖模块函数都被执行完。在导入模块的时候,如果这个模块不在“模块缓存池”中,则执行该模块函数,并把该函数执行后得到的模块缓存到“模块缓存池”中,下次获取该模块的时候,直接从“模块缓存池”中取,这里和 nodejs 的模块依赖机制类似。