webpack原理
模块化演进
1. 文件划分方式
<html lang="en">
<script src="module-a.js"></script>
<script src="module-b.js"></script>
<script>
// 直接使用全局成员
foo()// 可能命名冲突
data = [] // 数据可能会被修改
</script>
</html>
缺点:
- 模块直接在全局工作,大量模块成员污染全局作用域
- 没有私有空间,所有模块内的成员都可以在模块外部被访问或者修改
- 一旦模块增多,容易产生命名冲突
- 无法管理模块与模块之间的依赖关系
- 在维护过程中也很难分辨每个成员所属的模块
2. 命名空间方式
只允许暴露一个全局对象,其他模块挂载在全局对象上
// module-a.js
window.moduleA = {
method1: function() {
console.log("method1");
}
}
// module-b.js
window.moduleA = {
data: 1,
method1: function() {
console.log("method1");
}
}
只解决了命名冲突,其他问题依旧存在
3. IIFE
在2的基础上,变成立即执行函数,为模块提供私有空间
// module-a.js
(function () {
var name = "module-a";
function method1() {
}
window.moduleA = {
method1: method1
}
})()
// module-b.js
(function ($) {
// 通过参数明显表明这个模块的依赖
// 私有成员,通过闭包访问
var name = "module-b";
function method1() {
}
window.moduleB = {
method1: method1
}
})(jQuery)
解决了命名冲突和全局作用域污染以及模块之间的依赖关系
以上三种解决方式,只解决了模块的组织问题,模块加载的问题并未解决
模块加载的问题
通过在html里面script加载模块,这种方式让模块加载不受控制;
在出现模块未引入页面,或者引入的模块已经移除等情况,会影响程序
理想的方式: 在页面中引入一个JS入口文件,其余用到的模块可以通过代码控制按需加载
模块化规范
目前通过约定实现模块化,为了统一不同开发者,不同项目之间的差异,需要制定一个行业标准去规范模块化的实现方式。
- 一个统一的模块化标准规范
- 一个可以自动加载模块的基础库
- CommonJS规范:(同步模块)
Node.js所遵循的模块规范,该规范约定一个文件就是一个模块,每个模块都有单独的作用域;通过module.exports 导出成员,require函数载入模块。 - AMD: 异步模块
// 定义模块
define(['jquery', './module2.js'], function($, module2) {
// return 导出模块
return {
start: function() {
}
}
})
// 载入模块
require(['./modules/module1.js'], function(module1){
module1.start()
})
-
CMD
-
模块化标准规范
Node.js环境中,遵循CommonJS规范组织模块
浏览器环境,遵循ES Modules规范(es6)
模块打包工具
- ES Modules 模块系统本身存在环境兼容问题,尽管如今主流浏览器的最新版本都支持这一特性,但目前无法保证用户的浏览器使用情况
- 模块化的方式划分出来的模块文件过多,而前端应用又运行在浏览器中,每一个文件都需要单独从服务器请求回来,零散的模块文件必然会导致浏览器的频繁发送网络请求,影响应用的工作效率。
- 随着应用日益复杂,前端应用开发过程中,除了Js代码需要模块化,HTML和CSS这些资源文件也需要被模块化,也应该看作前端应用中的模块,只不过种类和用途跟JS不同。
- 编译:(babel 转译代码新特性) 解决环境兼容问题
- 打包:模块打包(在开发阶段必要,组织代码)
- 不同种类资源的模块打包(1,2,3webpack)(1,2 gulp)
webpack静态模块打包器(打包工具),发展成对整个前端项目的构建系统
webpack
- webpack作为一个模块打包工具,本身可以实现模块化打包,通过webpack可以将零散的JS代码打包到一个JS文件中
- 对于环境兼容,webpack可以在打包过程中通过Loader对其实现编译转换,然后再进行打包
- 对于不同类型的前端模块,webpack支持在JS中以模块化的方式载入任意类型的资源文件;例如通过webpack在js中加载css文件,被加载的css文件将会通过style标签的方式工作
- 其他
- 还具备代码拆分能力,能够将应用中的所有模块按需分块打包;不用担心全部代码打包到一起,产生单个文件过大,导致加载慢的问题。
webpack打包过程详解
webpack版本: 5.61.0
核心模块
webpack-cli 用于在命令行中调用webpack, (它所提供的命令行程序存在于node_modules/.bin文件中)
npx webpack
在执行过程中,webpack默认会自动从src/index.js文件开始打包
然后根据代码中的模块导入操作,自动将所有用到的模块打包到一起
在根目录下生成一个dist目录,打包结果就会存在dist/main.js中
在html文件中引入打包后的文件
// bundle.js
(() => {
// webpackBootstrap(打包到同一个文件,并且提供基础代码,让模块与模块之间的依赖关系还可以保持)
var __webpack_modules__ = ({
// key 模块路径
"./node_modules/babel-loader/lib/index.js??clonedRuleSet-2[0].rules[0].use[0]!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/App.vue?vue&type=script&lang=js&":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
// eval执行打包过的模块字符串代码
// __webpack_require__.r r函数的作用是为了给导出对象添加标记
/*
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {"default": () => (__WEBPACK_DEFAULT_EXPORT__) });
var _compB_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/compB.vue");
const __WEBPACK_DEFAULT_EXPORT__ = ({
name: 'App',
components: {
CompB: _compB_vue__WEBPACK_IMPORTED_MODULE_0__["default"]
},
data: function data() {
return {
message: '',
messageFromBus: '',
sender: ''
};
},
mounted: function mounted() {
var _this = this;
this.$bus.$on('sendMessage', function (obj) {
// 通过eventBus监听sendMessage事件
var sender = obj.sender,
message = obj.message;
_this.sender = sender;
_this.messageFromBus = message;
});
},
methods: {
sendMessage: function sendMessage() {
this.$bus.$emit('sendMessage', {
// 通过eventBus触发sendMessage事件
sender: this.$options.name,
message: this.message
});
}
}
});
*/
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _compB_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./compB.vue */ \"./src/compB.vue\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'App',\n components: {\n CompB: _compB_vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n data: function data() {\n return {\n message: '',\n messageFromBus: '',\n sender: ''\n };\n },\n mounted: function mounted() {\n var _this = this;\n\n this.$bus.$on('sendMessage', function (obj) {\n // 通过eventBus监听sendMessage事件 \n var sender = obj.sender,\n message = obj.message;\n _this.sender = sender;\n _this.messageFromBus = message;\n });\n },\n methods: {\n sendMessage: function sendMessage() {\n this.$bus.$emit('sendMessage', {\n // 通过eventBus触发sendMessage事件 \n sender: this.$options.name,\n message: this.message\n });\n }\n }\n});\n\n//# sourceURL=webpack:///./src/App.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-2%5B0%5D.rules%5B0%5D.use%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options");
/***/ }),
});
// The module cache
var __webpack_module_cache__ = {
};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {
}
};
// Execute the module function
// 传入创建的模块,模块导出对象和require, 为了让函数内部可以导入和导出对象
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
})()
// 用立即执行函数给__webpack_require__增加一些数据和工具函数
/* webpack/runtime/make namespace object */
(() => {
// define __esModule on exports 在导出对象上添加__esModule标记
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module' });
}
Object.defineProperty(exports, '__esModule', {
value: true });
};
})();
// startup
// Load entry module and return exports
// This entry module can't be inlined because the eval devtool is used.
var __webpack_exports__ = __webpack_require__(</