Webpack打包文件
TIP
本节主要分析一下webpack的打包文件,包含同步模块加载和异步模块加载
一 配置环境
1 安装文件
npm init -y
npm i webpack webpack-cli html-webpack-plugin clean-webpack-plugin -D
mkdir webpack.config.js
2 编写文件
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
devtool: "source-map",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
chunkFilename:'[name].main.js',
//publicPath:'/'
},
module: {},
plugins: [
new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ["**/*"] }),
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
}),
],
devServer: {},
};
// src\title.js
module.exports = {
name:'title_name',
age:'title_age'
}
// src\index.js
let title = require('./title')
console.log(title);
// src\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack</title>
</head>
<body></body>
</html>
// package.json
"scripts": {
"build": "webpack"
}
3 添加删除注释插件
vscode添加插件 remove-js-comments
4 打包文件
// dist/main.js
;(() => {
var __webpack_modules__ = {
"./src/title.js": (module) => {
module.exports = {
name: "title_name",
age: "title_age",
}
},
}
var __webpack_module_cache__ = {}
function __webpack_require__(moduleId) {
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
})
__webpack_modules__[moduleId](module, module.exports, __webpack_require__)
return module.exports
}
;(() => {
let title = __webpack_require__("./src/title.js")
console.log(title)
})()
})()
二 同步模块加载
- webpack内部自己实现了一套CommonJS规范,所以天然能识别require、exports、module.exports
- webpack在CommonJS与ES6 modules模块的加载有四种情况,如下:
1 CommonJS 加载 CommonJS
- 代码编写
// src/index.js - CommonJS导入
let title = require('./title')
console.log(title );
// src/title.js - CommonJS导出
module.exports = {
name:'title_name',
age:'title_age'
}
- 加载原理
(() => {
// 1. 定义模块对象
var modules = {
"./src/title.js": (module) => {
module.exports = {
name: "title_name",
age: "title_age",
}
},
}
// 2. 定义缓存对象
var cache = {}
// 3. 定义require方法
function require(moduleId) {
// 3.1 先查看缓存是否已经存在,如果已经存在就返回
if (cache[moduleId]) {
return cache[moduleId].exports
}
// 3.2 缓存没有就定义空对象,两个变量指向同一个引用对象
// var module = {exports:{}};
// cache[moduleId]= module;
var module = cache[moduleId] = {
exports: {},
};
// 3.3 执行模块对象的函数,然后给module.exports赋值
modules[moduleId].call(module.exports, module, module.exports, require);
// 3.4 返回结果
return module.exports
}
// 4. 自执行函数定义模块执行入口
(() => {
let title = require("./src/title.js")
console.log(title)
})()
})()
2 CommonJS 加载 ES6 modules
- 如果原来是ES6 modules会统一变成CommonJS
- export default会变成exports.default
- export xx 保持不变,还是为 exports.xx
- 代码编写
// src/index.js - CommonJS导入
let title = require('./title')
console.log(title );
// src/title.js - ES6 modules导出
export default 'title_name';
export const age = 'title_age';
- 加载原理
(() => {
// 1. 定义模块对象
var modules = {
"./src/title.js": (module, exports, require) => {
// 如果是es6的模块,就调用r方法加入es6的模块标识
require.r(exports);
// 将es6模块中的default默认导出加入一个default的key,value全部变为函数
require.d(exports, {
default: () => DEFAULT_EXPORT,
age: () => age,
})
const DEFAULT_EXPORT = "title_name"
const age = "title_age"
},
}
// 2. 定义缓存对象
var cache = {}
// 3. 定义require方法
function require(moduleId) {
// 3.1 先查看缓存是否已经存在,如果已经存在就返回
if (cache[moduleId]) {
return cache[moduleId].exports
}
// 3.2 缓存没有就定义空对象,两个变量指向同一个引用对象
// var module = {exports:{}};
// cache[moduleId]= module;
var module = cache[moduleId] = {
exports: {},
};
// 3.3 执行模块对象的函数,然后给module.exports赋值
modules[moduleId].call(module.exports, module, module.exports, require);
// 3.4 返回结果
return module.exports
}
// 浅拷贝,将需要加载的模块执行完成之后全部放到module.exports对象上
require.d = (exports, definition) => {
for (var key in definition) {
// exports[key]=definition[key]();
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
}
}
// 给module.exports对象上加入ES6的模块标识
require.r = (exports) => {
//console.log(Object.prototype.toString.call(exports));//[object Module]
//exports.__esModule=true
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
Object.defineProperty(exports, "__esModule", { value: true })
}
// 4. 自执行函数定义模块执行入口
(() => {
let title = require("./src/title.js")
console.log(title)
console.log(title.age)
})()
})()
3 ES6 modules 加载 ES6 modules
- 代码编写
// src/index.js - ES6 modules导入
import name, { age } from "./title";
console.log(name);
console.log(age);
// src/title.js - ES6 modules导出
export default 'title_name';
export const age = 'title_age';
- 加载原理
(() => {
// 1. 定义模块对象
var modules = {
"./src/index.js": (module, exports, require) => {
require.r(exports)
var title = require("./src/title.js")
console.log(title.default)
console.log(title.age)
},
"./src/title.js": (module, exports, require) => {
// 如果是es6的模块,就调用r方法加入es6的模块标识
require.r(exports);
// 将es6模块中的default默认导出加入一个default的key,value全部变为函数
require.d(exports, {
default: () => DEFAULT_EXPORT,
age: () => age,
})
const DEFAULT_EXPORT = "title_name"
const age = "title_age"
},
}
// 2. 定义缓存对象
var cache = {}
// 3. 定义require方法
function require(moduleId) {
// 3.1 先查看缓存是否已经存在,如果已经存在就返回
if (cache[moduleId]) {
return cache[moduleId].exports
}
// 3.2 缓存没有就定义空对象,两个变量指向同一个引用对象
// var module = {exports:{}};
// cache[moduleId]= module;
var module = cache[moduleId] = {
exports: {},
};
// 3.3 执行模块对象的函数,然后给module.exports赋值
modules[moduleId].call(module.exports, module, module.exports, require);
// 3.4 返回结果
return module.exports
}
// 浅拷贝,将需要加载的模块执行完成之后全部放到module.exports对象上
require.d = (exports, definition) => {
for (var key in definition) {
// exports[key]=definition[key]();
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
}
}
// 给module.exports对象上加入ES6的模块标识
require.r = (exports) => {
//console.log(Object.prototype.toString.call(exports));//[object Module]
//exports.__esModule=true
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
Object.defineProperty(exports, "__esModule", { value: true })
}
// 4. 自执行函数定义模块执行入口
(() => {
require("./src/index.js")
})()
})()
4 ES6 modules 加载 CommonJS
- 代码编写
// src/index.js - ES6 modules导入
import name, { age } from "./title";
console.log(name);
console.log(age);
// src/title.js - CommonJS导出
module.exports = {
name:'title_name',
age:'title_age'
}
- 加载原理
(() => {
// 1. 定义模块对象
var modules = {
"./src/index.js": (module, exports, require) => {
require.r(exports)
var title = require("./src/title.js")
// 这一步还不知道是ES6调用还是CommonJS调用,需要封装一个函数
var title_default = require.n(title)
console.log(title_default())
console.log(title.age)
},
"./src/title.js": (module, exports, require) => {
module.exports = {
name:'title_name',
age:'title_age'
}
},
}
// 2. 定义缓存对象
var cache = {}
// 3. 定义require方法
function require(moduleId) {
// 3.1 先查看缓存是否已经存在,如果已经存在就返回
if (cache[moduleId]) {
return cache[moduleId].exports
}
// 3.2 缓存没有就定义空对象,两个变量指向同一个引用对象
// var module = {exports:{}};
// cache[moduleId]= module;
var module = cache[moduleId] = {
exports: {},
};
// 3.3 执行模块对象的函数,然后给module.exports赋值
modules[moduleId].call(module.exports, module, module.exports, require);
// 3.4 返回结果
return module.exports
}
// 判断是ES6的模块还是CommonJS模块,然后返回对应的函数
require.n = (exports)=>{
var getter = exports.__esModule ? () => exports.default : () => exports;
return getter;
}
// 浅拷贝,将需要加载的模块执行完成之后全部放到module.exports对象上
require.d = (exports, definition) => {
for (var key in definition) {
// exports[key]=definition[key]();
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
}
}
// 给module.exports对象上加入ES6的模块标识
require.r = (exports) => {
//console.log(Object.prototype.toString.call(exports));//[object Module]
//exports.__esModule=true
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
Object.defineProperty(exports, "__esModule", { value: true })
}
// 4. 自执行函数定义模块执行入口
(() => {
require("./src/index.js")
})()
})()
三 异步加载
module 就是src源码里面编写的每一个文件模块
chunk 就是webpack在打包过程中分包的依据
bundle 就是webpack打包之后最终输出的文件
代码编写
// webpack.config.js
module.exports = {
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
chunkFilename:'[name].main.js',
}
};
// src/index.js
// 如果不加webpackChunkName,打包生成的文件就是src-hello.main.js,加了就是hello.main.js
import(/* webpackChunkName: "hello" */ './hello').then(result => {
console.log(result.default);
});
// src/hello.js
export default 'hello';
- 原理分析
// main.js
(() => {
// 存放着所有的模块定义,包括懒加载,或者说异步加载过来的模块定义
var modules = {}
// 定义缓存对象
var cache = {}
// 定义require方法
function require(moduleId) {
// a 先查看缓存是否已经存在,如果已经存在就返回
if (cache[moduleId]) {
return cache[moduleId].exports
}
// b 缓存没有就定义空对象,两个变量指向同一个引用对象
// var module = {exports:{}};
// cache[moduleId]= module;
var module = cache[moduleId] = {
exports: {},
};
// c 执行模块对象的函数,然后给module.exports赋值
modules[moduleId].call(module.exports, module, module.exports, require);
// d 返回结果
return module.exports
}
// 浅拷贝,将需要加载的模块执行完成之后全部放到module.exports对象上
require.d = (exports, definition) => {
for (var key in definition) {
// exports[key]=definition[key]();
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
}
}
// 给module.exports对象上加入ES6的模块标识
require.r = (exports) => {
//console.log(Object.prototype.toString.call(exports));//[object Module]
//exports.__esModule=true
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
Object.defineProperty(exports, "__esModule", { value: true })
}
// 2.创建promise,发起jsonp请求
require.e = chunkId => {
let promises = [];
require.f.j(chunkId, promises);
return Promise.all(promises);
}
//已经安装的代码块 main代码块的名字:0表示已经就绪
let installedChunks = {
main: 0,
hello: 0
}
require.p = ''; // 配置文件中的publicPath 资源访问路径
require.u = chunkId => { // 拼接请求代码块名称
return chunkId + '.main.js';
}
require.f = {};
// 3.通过jsonp异步加载chunkId,也就是hello这个代码块
require.f.j = (chunkId, promises) => {
let promise = new Promise((resolve, reject) => {
installedChunks[chunkId] = [resolve, reject];
});
promises.push(promise);
// 组装异步模块的url
var url = require.p + require.u(chunkId); // /hello.main.js
require.l(url);
}
// 4.通过JSONP请求这个新的url地址 http://127.0.0.1:8080/hello.main.js
require.l = url => {
let script = document.createElement('script');
script.src = url;
document.head.appendChild(script); // 一旦添加head里,浏览器会立刻发出请求
}
// 6.开始执行回调
var webpackJsonpCallback = ([chunkIds, moreModules]) => {
// 拿到resolve数组
let resolves = chunkIds.map(chunkId => installedChunks[chunkId][0]);
// 把异步加载回来的额外代码块合并到总的模块定义对象modules上
for(let moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
// 将promises全部执行成功
resolves.forEach(resolve => resolve());
}
// 0 定义全局空数组window["webpack5"],然后重新push方法
var chunkLoadingGlobal = window["webpack5"] = [];
chunkLoadingGlobal.push = webpackJsonpCallback;
// 1.准备加载异步代码块hello
require.e("hello").then(require.bind(require, "./src/hello.js")).then(result => {
console.log(result.default);
})
})()
// hello.main.js
// 5.执行window["webpack5"]上的push方法,传递参数[chunkIds,moreModules]
;(window["webpack5"] = window["webpack5"] || []).push([
["hello"],
{
"./src/hello.js": (module, exports, require) => {
"use strict"
require.r(exports)
require.d(exports, {
default: () => DEFAULT_EXPORT,
})
const DEFAULT_EXPORT = "hello"
},
},
])