前端工程化经历过很多优秀的工具,例如 Grunt、Gulp、webpack、rollup 等等,每种工具都有自己适用的场景,而现今应用最为广泛的当属 webpack 打包了,因此学习好 webpack 也成为一个优秀前端的必备技能。
由于 webpack 技术栈比较复杂,因此作者打算分两篇文章进行讲解:
高级应用篇:讲解 webpack 优化以及原理。
[注] 本文是基于 webpack 4.x 版本
webpack 是什么webpack 是模块打包工具。
webpack 可以在不进行任何配置的情况下打包如下代码:
[注] 不进行任何配置时,webpack 会使用默认配置。
// moduleA.js function ModuleA(){ this.a = "a"; this.b = "b"; } export default ModuleA // index.js import ModuleA from "./moduleA.js"; const module = new ModuleA();我们知道浏览器是不认识 import 语法的,直接在浏览器中运行这样的代码会报错。我们就可以借助 webpack 来打包这样的代码,赋予 JavaScript 模块化的能力。
最初版本的 webpack 只能打包 JavaScript 代码,随着发展 css 文件,图片文件,字体文件都可以被 webpack 打包。
本文将主要讲解 webpack 是如何打包这些资源的,属于比较基础的文章主要是为了后面讲解性能优化和原理做铺垫,如果已经对 webpack 比较熟悉的同学可以跳过本文。
初始化安装 webpack mkdir webpackDemo // 创建文件夹 cd webpackDemo // 进入文件夹 npm init -y // 初始化package.json npm install webpack webpack-cli -D // 开发环境安装 webpack 以及 webpack-cli通过这样安装之后,我们就可以在项目中使用 webpack 命令了。
打包第一个文件webpack.config.js
const path = require('path'); module.exports = { mode: 'development', // {1} entry: { // {2} main:'./src/index.js' }, output: { // {3} publicPath:"", // 所有dist文件添加统一的前缀地址,例如发布到cdn的域名就在这里统一添加 filename: 'bundle.js', path: path.resolve(__dirname,'dist') } }代码分析:
{1} mode 打包模式是开发环境还是生成环境, development | production
{2} entry 入口文件为 index.js
{3} output 输出到 path 配置的 dist 文件夹下,输出的文件名为 filename 配置的 bundle.js
创建文件进行简单打包:
src/moduleA.js const moduleA = function () { return "moduleA" } export default moduleA; -------------------------------- src/index.js import moduleA from "./moduleA"; console.log(moduleA());修改 package.json 的 scripts,增加一条命令:
"scripts": { "build": "webpack --config webpack.config.js" }执行 npm run build 命令
打包后的 bundle.js 源码分析源码经过简化,只把核心部分展示出来,方便理解:
(function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { // 缓存文件 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 初始化 moudle,并且也在缓存中存入一份 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 执行 "./src/index.js" 对应的函数体 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 标记"./src/index.js"该模块以及加载 module.l = true; // 返回已经加载成功的模块 return module.exports; } // 匿名函数开始执行的位置,并且默认路径就是入口文件 return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) // 传入匿名执行函数体的module对象,包含"./src/index.js","./src/moduleA.js" // 以及它们对应要执行的函数体 ({ "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\");\n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])());\n\n\n//# sourceURL=webpack:///./src/index.js?"); }), "./src/moduleA.js": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\nconst moduleA = function () {\n return \"moduleA\"\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (moduleA);\n\n\n//# sourceURL=webpack:///./src/moduleA.js?"); }) });再来看看"./src/index.js" 对应的执行函数
(function(module, __webpack_exports__, __webpack_require__) { "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\");\n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])());\n\n\n//# sourceURL=webpack:///./src/index.js?"); })你会发现就是一个 eval 执行方法。
我们拆开 eval 来仔细看看里面是什么内容,简化后代码如下:
var moduleA = __webpack_require__("./src/moduleA.js"); console.log(Object(moduleA["default"])());上面源码中其实已经调用了 __webpack_require__(__webpack_require__.s = "./src/index.js"); ,然后 "./src/index.js" 又递归调用了去获取 "./src/moduleA.js" 的输出对象。
我们看看 "./src/moduleA.js" 代码会输出什么:
const moduleA = function () { return "moduleA" } __webpack_exports__["default"] = (moduleA);再回头看看上面的代码就相当于:
console.log(Object(function () { return "moduleA" })());最后执行打印了 "moduleA"
通过这段源码的分析可以看出:
打包之后的模块,都是通过 eval 函数进行执行的;