Webpack是一个打包模块化Javascript的工具,在Webpack里一切文件皆模块,通过Loader转换文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。Webpack专注于构建模块化项目。 ——《深入浅出Webpack》
其官网的首页图很形象地展示了Webpack的定义 如下图所示
配置文件: webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'none',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
入口文件: ./src/index.js
let title = require('./title');
console.log(title);
被入口文件导入的文件: ./src/title.js
module.exports = '标题';
打包后的文件: main.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;
}
// 把modules对象放在__webpack_require__.m属性上
__webpack_require__.m = modules;
// 把模块的缓存对象放在__webpack_require__.c属性上
__webpack_require__.c = installedModules;
// 导出定义getter函数
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// 声明es6模块
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// 包装es6模块
__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;
};
// 获取默认导出的函数,为了兼容非harmony模块
__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.prototye.hasOwnProperty
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// 把公开访问路径放在__webpack_require__.p属性上
__webpack_require__.p = '';
// 加载入口模块并且返回exports (__webpack_require__.s: 指定入口模块ID)
return __webpack_require__((__webpack_require__.s = './src/index.js'));
})({
'./src/index.js': function (module, exports, __webpack_require__) {
let title = __webpack_require__('./src/title.js');
console.log(title);
},
'./src/title.js': function (module, exports) {
module.exports = '标题';
},
});
__webpack_require__.r (声明es6模块)
__webpack_require__.r = function (exports) {
/**
* Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用
* 对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签
* 通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里
*/
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
-------------------------------------------------------------------------------------------
实际可以理解成如下代码:
__webpack_require__.r = function (exports) {
exports[Symbol.toStringTag] = 'Module';
exports.__esModule = true;
}
let exports = {};
__webpack_require__.r(exports);
console.log(Object.prototype.toString.call(exports));
// 控制台打印结果:"[object Module]"
__webpack_require__.o (Object.prototye.hasOwnProperty)
// 实际就是对Object.prototype.hasOwnProperty.call做了一个包装
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.d (导出定义getter函数)
__webpack_require__.d = function (exports, name, getter) {
// 检查形参name是否是形参exports的私有属性
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
__webpack_require__.n (获取默认导出的函数,为了兼容非harmony模块)
__webpack_require__.n = function (module) {
var getter =
module && module.__esModule
? function getDefault() { // ES6 modules
return module['default'];
}
: function getModuleExports() { // common.js
return module;
};
__webpack_require__.d(getter, 'a', getter);
return getter;
};
__webpack_require__.t (包装es6模块)
// 懒加载的时候用的
// require.t 一般来说核心用法是用来把一个任意模块都变成一个es模块
// import('./xxx').then(result => console.log(result))..不管懒加载的是一个common.js还是es6模块,都会变成es6模块的格式
// create a fake namespace object 创建一个模拟的命名空间对象,不管什么模块都转成es6模块
// mode & 1: value is a module id, require it value是一个模块ID,需要用require加载
// mode & 2: merge all properties of value into the ns // 将值的所有属性合并到ns中
// mode & 4: return value when already ns object 如果已经是ns对象了,则直接返回
// mode & 8 | 1: behave like require 等同于require方法
__webpack_require__.t = function (value, mode) {
// 1转为二进制 0b0001
if (mode & 1) value = __webpack_require__(value);
// 8转为二进制 0b1000
if (mode & 8) return value;
// 4转为二进制 0b0100 value已经是es模块了,可以直接返回
if (mode & 4 && typeof value === 'object' && value && value.__esModule)
return value;
var ns = Object.create(null);
__webpack_require__.r(ns); // ns.__esModule=true
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string')
// 把值拷贝到ns上
for (var key in value)
__webpack_require__.d(
ns,
key,
function (key) {
return value[key];
}.bind(null, key)
);
return ns;
};
顾名思义,harmony中文是和谐的意思。webpack_require.n方法就是为了兼容非harmony的模块。
入口文件: ./src/index.js
let title = require('./title');
console.log(title.name);
console.log(title.age);
被入口文件导入的文件: ./src/title.js
exports.name = 'title_name';
exports.age = 'title_age';
打包后的文件: main.js (自执行函数实参部分)
{
'./src/index.js': function (module, exports, __webpack_require__) {
let title = __webpack_require__('./src/title.js');
console.log(title.name);
console.log(title.age);
},
'./src/title.js': function (module, exports) {
exports.name = 'title_name';
exports.age = 'title_age';
}
}
入口文件: ./src/index.js
let title = require('./title');
console.log(title);
被入口文件导入的文件: ./src/title.js
export default 'title_name'; // 默认导出
export const age = 'title_age'; // 单个导出
打包后的文件: main.js (自执行函数实参部分)
{
'./src/index.js': function (module, exports, __webpack_require__) {
let title = __webpack_require__('./src/title.js');
console.log(title);
},
'./src/title.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict';
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, 'age', function () {
return age;
});
__webpack_exports__['default'] = 'title_name'; // 默认导出
const age = 'title_age'; // 单个导出
}
}
入口文件: ./src/index.js
import name, { age } from './title';
console.log(name);
console.log(age);
被入口文件导入的文件: ./src/title.js
export default 'title_name'; // 默认导出
export const age = 'title_age'; // 单个导出
打包后的文件: main.js (自执行函数实参部分)
{
'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict';
__webpack_require__.r(__webpack_exports__);
var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
'./src/title.js'
);
console.log(_title__WEBPACK_IMPORTED_MODULE_0__['default']);
console.log(_title__WEBPACK_IMPORTED_MODULE_0__['age']);
},
'./src/title.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict';
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, 'age', function () {
return age;
});
__webpack_exports__['default'] = 'title_name'; // 默认导出
const age = 'title_age'; // 单个导出
}
}
入口文件: ./src/index.js
import name, { age } from './title';
console.log(name);
console.log(age);
被入口文件导入的文件: ./src/title.js
exports.name = 'title_name';
exports.age = 'title_age';
打包后的文件: main.js (自执行函数实参部分)
{
'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict';
__webpack_require__.r(__webpack_exports__);
var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
'./src/title.js'
);
var _title__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(
_title__WEBPACK_IMPORTED_MODULE_0__
);
console.log(_title__WEBPACK_IMPORTED_MODULE_0___default.a);
console.log(_title__WEBPACK_IMPORTED_MODULE_0__['age']);
},
'./src/title.js': function (module, exports) {
exports.name = 'title_name';
exports.age = 'title_age';
}
}
入口文件: ./src/index.js
let importBtn = document.getElementById('import');
importBtn.addEventListener('click', () => {
import(/* webpackChunkName: 'title' */'./title').then(result => {
console.log(result);
})
})
被入口文件导入的文件: ./src/title.js
exports.name = 'title_name';
exports.age = 'title_age';
打包后的文件: main.js (自执行函数实参部分)
{
'./src/index.js': function (module, exports, __webpack_require__) {
let importBtn = document.getElementById('import');
importBtn.addEventListener('click', () => {
// 下面代码就是 import('./title')
__webpack_require__
.e('title')
.then(
__webpack_require__.t.bind(null, /*! ./title */ './src/title.js', 7)
)
.then((result) => {
// result就是这个title的导出对象
console.log(result);
});
});
},
}
打包后的文件(chunk):title.js
(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
['title'],
{
'./src/title.js': function (module, exports) {
exports.name = 'title_name';
exports.age = 'title_age';
},
},
]);
基本概念
理解事件流机制 Tapable
理解Compiler
Compiler 对象包含了当前运行Webpack的配置,包括entry、output、loaders等配置,这个对象在启动Webpack时被实例化,而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。
理解Compilation
Compilation对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 Compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,简单来讲就是把本次打包编译的内容存到内存里。Compilation 对象也提供了插件需要自定义功能的回调,以供插件做自定义处理时选择使用拓展。
简单来说,Compilation的职责就是构建模块和Chunk,并利用插件优化构建过程。
Webpack的运行是个串行的过程,从启动到结束会依次执行以下流程: