简介(V4版本)
webpack是基于模块化的打包(构建)工具,它把一切视为模块
它通过一个开发时态的入口模块为起点,分析出所有的依赖关系,然后经过一系列的过程(压缩、合并),最终生成运行时态的文件。
webpack的特点:
- 为前端工程化而生:webpack致力于解决前端工程化,特别是浏览器端工程化中遇到的问题,让开发者集中注意力编写业务代码,而把工程化过程中的问题全部交给webpack来处理
- 简单易用:支持零配置,可以不用写任何一行额外的代码就使用webpack
- 强大的生态:webpack是非常灵活、可以扩展的,webpack本身的功能并不多,但它提供了一些可以扩展其功能的机制,使得一些第三方库可以融于到webpack中
- 基于nodejs:由于webpack在构建的过程中需要读取文件,因此它是运行在node环境中的
- 基于模块化:webpack在构建过程中要分析依赖关系,方式是通过模块化导入语句进行分析的,它支持各种模块化标准,包括但不限于CommonJS、ES6 Module
webpack的安装
webpack通过npm安装,它提供了两个包:
- webpack:核心包,包含了webpack构建过程中要用到的所有api
- webpack-cli:提供一个简单的cli命令,它调用了webpack核心包的api,来完成构建过程
安装方式:
- 全局安装:可以全局使用webpack命令,但是无法为不同项目对应不同的webpack版本
- 本地安装:推荐,每个项目都使用自己的webpack版本进行构建
打包结果分析
首先我们准备以下几个文件夹:
// a.js
require("./b.js")
console.log("module a")
// b.js
require("./c.js")
console.log("module b")
// c.js
console.log("module c")
// index.js
require("./a.js")
console.log("Hello Webpack")
然后执行脚本命令:
npm run dev
打包之后代码如下(去掉一部分注释后):
(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 = "./src/index.js");
})
({
"./src/a.js":
(function (module, exports, __webpack_require__) {
eval("__webpack_require__(/*! ./b */ \"./src/b.js\")\r\nconsole.log(\"module a\")\n\n//# sourceURL=webpack:///./src/a.js?");
}),
"./src/b.js":
(function (module, exports, __webpack_require__) {
eval("__webpack_require__(/*! ./c */ \"./src/c.js\")\r\nconsole.log(\"module b\")\n\n//# sourceURL=webpack:///./src/b.js?");
}),
"./src/c.js":
(function (module, exports) {
eval("console.log(\"module c\")\n\n//# sourceURL=webpack:///./src/c.js?");
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
eval("__webpack_require__(/*! ./a */ \"./src/a.js\")\r\nconsole.log(\"hello webpack\");\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
当我们把函数体折叠时,很容易看出来这是一个立即执行函数(为了不污染全局变量)
该函数接受一个modules
的参数,以一唯一路径作为键值,函数作为值(为了各模块内容互不影响
),函数体为eval
函数,每个模块对应的内容则作为eval
函数的参数,使用**_webpack_require_** 函数中的参数全部会替换为modules
中对应的键值
我们知道webpack打包依赖入口文件,当不指定时,默认为 index.js ,所以在立即执行函数内部,肯定会有运行入口文件的代码
我们看到第68行
return __webpack_require__(__webpack_require__.s = "./src/index.js");
接着我们来分析 _webpack_require_ 这个函数
// moduleId 即立即执行函数的参数的key值 说白了就是唯一路径
function __webpack_require__(moduleId) {
// 判断缓存模块中有没有该路径
if (installedModules[moduleId]) {
// 有则返回该模块的导出结果
return installedModules[moduleId].exports;
}
// 没有则创建一个模块对象,并将其添加到缓存中
var module = installedModules[moduleId] = {
i: moduleId, // i 为模块路径
l: false, // l 表示模块是否加载完成
exports: {} // exports 为模块导出对象,默认为一个空对象
};
// 从模块对象中取出该路径对应的函数,执行 this指向moudle.exports
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
//将该模块标记为加载完成
module.l = true;
// 返回模块的导出结果
return module.exports;
}
- 因为模块有缓存,所以当多次加载相同模块时,该模块代码只会运行一次
- 模块中使用
this
与exports,module.exports
是一致的
22行到63行 主要为了做兼容,以及暴露一些属性和定义一些辅助函数,影响不大
为什么模块中的内容要放在
eval
函数里呢?主要是为了方便调试
对modules参数进行改造:
{
"./src/a.js":
(function (module, exports, __webpack_require__) {
__webpack_require__("/src/b.js")
console.log("module a")
const a = null;
a.abc();
}),
"./src/b.js":
(function (module, exports, __webpack_require__) {
__webpack_require__("/src/c.js")
console.log("module b")
}),
"./src/c.js":
(function (module, exports) {
console.log("module c")
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
__webpack_require__("/src/a.js")
console.log("Hello Webpack")
})
}
a模块运行必定报错 报错信息在main.js:98
点击跳转如下,发现我明明是a模块的报错,却显示了所有模块的信息
接下来,我们在使用eval试试, 发现直接显示了a.js 这个模块文件,当时这个跟eval关系不大 是由这个决定的 这个注释是用来给浏览器看的
当我们点击跳转时,发现报错代码直接跳到了a.js模块文件,瞬间清晰很多。