孟婆汤煨前端webpack

webpack 4.0 完全讲解及源码解读(1)

2019-04-25  本文已影响0人  公羊无衣

webpack 4.0 完全讲解及源码解读

​ 就目前前端环境而言,使用cli自动构建工具可以快速的构建项目完成项目搭建,快速完成功能,业务开发,这样水到渠成的模式深得人心,也深得科技公司信赖,因为简单,易用且方便,但是对于使用webpack构建一个0-1的项目,可能很多人只能摊手,不得不说webpack是前端工程化之中占有相当重要地位的成员,本篇文章就来说一下webpack的使用教你从0到1构建一个企业级的依赖webpack的前端工程化环境。

image.png

webpack 是什么

​ 如果你学习过gulp那么你应该了解gulp的本质是什么,这是一个流程优化工具,像是文件的压缩,开启一个webserver ,sass的编译等等, 这是gulp的功能,gulp是一个前端构建工具,这个工具简单易用深得人心,但是随着后前端时代的到来,越来越多的构建工具逐渐进入我们的事业,那么我们以gulp为铺垫来聊一下webpack。

​ webpack是什么那 ? 简单一句话,是一个前端打包工具,一言蔽之 webpack万事万物皆JS,图片是JS,css是JS,图片也是JS,一个神奇的打包工具。这个工具在技术层面和gulp是有本质区别的。如果说gulp是一个工具库,可以让我们更容易的构建我们的项目,那么webpack更像是一个压缩机,可以让我们将代码进行高性能的转义及优化。

  其实官网上的一张图片非常能说明webpack的功效 :
webpack功能图解

简单总结一下webpack 的功能:

这些比较笼统概念性的描述可能不是很容易让人理解,那么在此请暂时记住或者复制粘贴这句话,以便于后面的学习及理解。

​ 我在学习和使用webpack的时候发现其缺点只有一个,那就是难,但是我认为这并不是webpack的缺点而是开发人员本身的缺点

    ok,闲话少叙,我们开始进入正题,来正式开启我们的webpack的旅程。

先来装一个webpack

    npm install webpack webpack-cli -g|--dev
> 在这里值得注意的是webpack4开始依赖webpack-cli,必须安装webpack-cli

为了这个练习我们需要建立一个项目结构

基础项目结构.png

Home.js源码

建立一个使用了COMMONJS规范,并输出了字符串的Home.js

module.exprots = "Hello WebPack";

index.js

在index.js之中使用COMMONJS规范引入建立好的Home.js

const  Home = require("./components/Home");

console.log(Home)

index.html

在src目录下的index.html之中引入index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script src="./main.js"></script>
</body>
</html>

ok在此我们完成了学习的第一步,接下在我们进入到目录之中启动一个webserver,让我们的项目在浏览器之中跑起来,看一下会发生什么事情。

在调试之前我们需要使用一个webserver工具,非常好用 browsersync官网

browser-sync 全局安装

npm install -g browser-sync

进入src目录下启动browser-sync

browser-sync start --server --files "index.html"

此时会弹出一个页面在 http://localhost:3000 这个路径下使用webserver打开了你的 index.html,我们打开控制台之后就会发现一个神奇的事情,报错了。

报错.png

为啥报错那? 原因非常简单,用浏览器打开,浏览器不支持require这个语法啊,你回去看一眼你的main.js 你会发现这个玩意使用了nodejs语法,浏览器不支持你能怎么办?童鞋们,回顾一下webpack的功能,回去瞅一眼,你就明白了,介玩意到了发挥它神威的时候了。

接下来让我们见证webpack的神威 。

image
webpack --mode development --entry "./index.js" --output-path "../dist" --output-filename "main.js"

这么一长串代码不要写错了哦,这个东西非常的重要,我们逐个讲解这些语句的含义

独到这里你可能会问:这个玩意这么墨迹么? 这还只是WebPack的墨迹的冰山一角,如果想要彻底学习掌握它,请耐心读完所有文字,如果真的完全记不住,请使用 --help 大法。手动斜眼笑

--help大法示例 :

webpack --help 

这时候需要将index.html 文件复制到dist文件夹 :

我们重新看一下目录结构主要看一下新创建出来的 dist 文件夹。

编译后项目结构.png

​ 编译后的 main.js ,只是展示一下编译后端 效果,后续我会继续更新相关webpack编译原理的代码,把它理解为一坨编译后的代码就可以了,至少他是兼容的,这段代码如果阅读困难,可以直接看下一节,后面有简化后的代码。

/******/ (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 = "./index.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./components/Home.js":
/*!****************************!*\
  !*** ./components/Home.js ***!
  \****************************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("module.exports = \"hello world\";\n\n//# sourceURL=webpack:///./components/Home.js?");

/***/ }),

/***/ "./index.js":
/*!******************!*\
  !*** ./index.js ***!
  \******************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

eval("const Home = __webpack_require__(/*! ./components/Home */ \"./components/Home.js\");\r\nconsole.log(Home);\n\n//# sourceURL=webpack:///./index.js?");

/***/ })

/******/ });

ok我理解,你现在的表情大概是这样的 :

黑人问号脸表情包

为啥我么就写了一行代码就编译成了这个样子那, 说好了性能提升了? 说好了打包那 ? 别慌,我对上面的编译结果代码进行一些删减,你关注的部分应该集中在这几个部分

其实只是使用两个eval语句对代码进行了输出,代码如下:

eval("module.exports = \"hello world\";\n\n//# sourceURL=webpack:///./components/Home.js?");

eval("const Home = __webpack_require__(/*! ./components/Home */ \"./components/Home.js\");\r\nconsole.log(Home);\n\n//# sourceURL=webpack:///./index.js?");

会不会有一种我是谁我在哪我在感谢啥的赶脚出现 ? 是就对了,这样的代码阅读,对于基础薄弱的童鞋可能略微有些痛苦,但是没关系,我会用极简的代码对其工作原理进行剖析,如果感觉学习起来比较吃力,那么可以收藏本篇文章待提升后再次进行阅读。

接下来我会继续对代码进行删减,让大家能看清webpack的整体框架 :

/******/ (function(modules) { // webpackBootstrap

/******/    function __webpack_require__(moduleId) {

/******/    }

            __webpack_require__(__webpack_require__.s = "./index.js");
/******/ })
/******/ ({
            "./components/Home.js":(function(module, exports) {
                eval("module.exports = \"hello world\";\n\n//# sourceURL=webpack:///./components/Home.js?");
            }),
            "./index.js":(function(module, exports, __webpack_require__) {
                eval("const Home = __webpack_require__(/*! ./components/Home */ \"./components/Home.js\");\r\nconsole.log(Home);\n\n//# sourceURL=webpack:///./index.js?");
            })
/******/ });

这个代码框架那,基本说明了webpack编译之后的两件事情 :

  1. 其实编译结果就是一个匿名函数,该匿名函数的参数是一个对象。题外话 : 这是区别于webpack3.0的因为webpack3.0是以数组的形式进行传递的。

  2. webpack编译内置了一个函数 __webpack_require(){} 这个函数的核心目的用于实现浏览器的commonJS规范。

你是不是还是看不懂 , 手动斜眼笑,没关系我又帮你简化了 :

/******/ (function(modules) { 

/******/    function 实现浏览器模块的功能(moduleId) {

/******/    }

            实现浏览器模块的功能(__webpack_require__.s = "./index.js");
/******/ })
/******/ ({
            "以路径命名的依赖":(function(module, exports) {
                eval("源码的字符串");
            }),
            "核心文件":(function(module, exports, __webpack_require__) {
                eval("源码之中的字符串");
            })
/******/ });

这是中文命名的代码,可以看出来这个代码的核心,就是用对象存储编译后的代码 , 然后用一个功能进行了实现,ok我们继续阅读源码,看一下浏览器端是如何实现COMMONJS规范的。

我们来看一下 function __webpack_require__(){} 这个方法的实现 从外部先来看一下定义的方法有哪些,我删除掉了部分代码以便可以把焦点关注在正确的地方。

var installedModules = {};

function __webpack_require__(moduleId) {

}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) {};
__webpack_require__.r = function(exports) {};
__webpack_require__.t = function(value, mode) {};
__webpack_require__.n = function(module) {};
__webpack_require__.o = function(object, property) {};
__webpack_require__.p = "";

通过这个简化之后的代码框架我们可以看出,其实这些代码都是在为核心方法 __webpack_require__提供具体的功能,期中有两个变量值得注意。

ok,各位看官请在下文的阅读中请将 modules 理解为 参数对象 ,同理 __webpack_require__.m = modules;

​ 将installedModules 理解为 缓存 , 同理 __webpack_require__.c = installedModules;

其余功能我们逐个进行分析 :

d 和 o , 在原文中有注释,这个 d 方法用于 : 为Harmony导出定义getter函数

很难理解对吧,没关系我们分析一下这段代码的功能。

功能代码剖析

功能函数d,o

​ 从参数看起,共有三个参数 exports,根据COMMONJS规范我们认为这个参数是包含了功能的一个对象。所以我们将exports理解为一个存储着各种类型数据的对象。

name则表示属性名。

getter表示为某个属性设置的监听方法。

在这里如果没办法很好理解getter的话建议先去阅读一下 defineProperty 在MDN上的文档。 MDN文档链接

__webpack_require__.d = function(exports, name, getter) {
    if(!__webpack_require__.o(exports, name)) {
        Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
};

​ 在这个功能中使用了一个功能叫做__webpack_require__.o , 这是一个非常简单的Object.hasOwnProperty反柯里化封装,至于什么是反柯里化你可以理解为 : 一个嗷嗷待哺的孩子,被后妈抢走从此没有了亲妈,以后所做的一切都是后妈指示的, 这个反柯里化是另一个课题,我们暂且只关注其实现的功能即可。

这个o功能就是实现了判定某个属性(字符串或者 Symbol) ,是否存在于对象之上返回值是布尔值。

​ 所以我们判定这个d方法就是 , 判定 exports对象上是否存在name属性,如果不存在就给这个属性定义一个取值时的监听getter.

功能函数 r

代码注释为,给exports对象定义一个__esModule属性。

__webpack_require__.r = function(exports) {
    if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
};

​ 这个代码其实是给exports定义了一个特殊的符号,查看浏览器是否支持ES2015的Symbol,如果支持,以Symbol符号给当前对象添加上一个独一无二的标志key为 Symbol.toStringTag,值为 "Module"。

​ 如果不支持会添加上一个 __esModule 的标志用于记录当前浏览器的支持情况,为功能做出判断。

功能函数t

代码注释为 , 创建一个独立的命名空间对象。

__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;
};

通过mode参数对value进行不同的处理 :

期间使用了 r : 去添加对象的标记,用d去给属性绑定getter,用于返回当前属性值。

功能函数n

__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;
};

根据不同的module类型进行区别,从而定义同的取值函数。

**最后的最后主函数的阅读终于来了 **

我们来看一下这个函数主函数做了些啥

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;
}

__webpack_require__(__webpack_require__.s = "./index.js");

其实这个函数主要的作用就是自己定义了一个COMMONJS的对象,并且放入缓存;

    var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {}
    };

这是根据COMMONJS规范定义的一个对象,没有任何特别的。

然后 modules根据传入的 moduleid进行调用 moduleid的key值就是代表的就是当前程序的入口文件路径,value值就是传入的参数函数,前文提到的带有eval()字符串的函数。

期中module中的l代表的就是当前模块是否被调用,用于判定后续模块调用的钩子函数。

源码分析到此结束。

简单总结一下:其实webpack就是利用了nodejs讲源码进行了转义之后当做字符串放到了一个eval之中,然后自己创建了一个浏览器端的对象,并对这个对象进行层层处理让这个对象符合commonjs规范,最后根据规范调用编译好的字符串。到此我们完成了webpack编译代码的阅读。

你是否有收获那? 有任何问题欢迎留言讨论。

上一篇下一篇

猜你喜欢

热点阅读