• 技术文章 >web前端 >js教程

    Webpack是什么?详解它是如何工作的?

    青灯夜游青灯夜游2022-10-14 20:22:40转载335

    大前端零基础入门到就业:进入学习

    Webpack简介

    Webpack是一款模块打包工具。它为不同的依赖创建模块,将其整体打包成可管理的输出文件。这一点对于单页面应用(如今Web应用的事实标准)来说特别有用。

    假设我们有一个可以执行两个简单数学任务(加法和乘法)的应用程序,为了方便维护,我们决定切分这些函数到不同的文件中去。

    index.html

    <html>
    <head>
        <script src="src/sum.js"></script>
        <script src="src/multiply.js"></script>
        <script src="src/index.js"></script>
    </head>
    </html>

    index.js

    var totalMultiply = multiply(5, 3);
    var totalSum = sum(5, 3);
    console.log('Product of 5 and 3 = ' + totalMultiply);
    console.log('Sum of 5 and 3 = ' + totalSum);

    multiply.js

    var multiply = function (a, b) {
        var total = 0;
        for (var i = 0; i < b; i++) {
            total = sum(a, total);
        }
        return total;
    };

    sum.js

    var sum = function (a, b) {
        return a + b;
    };

    这个应用程序的输出应该是:

    Product of 5 and 3 = 15
    Sum of 5 and 3 = 8

    Webpack是如何帮助我们的?

    我们不能仅仅只是使用工具,而不知道这些工具能帮助我们做什么。那么,Webpack帮我们做了什么呢?

    用模块来拯救依赖

    在上面的代码中,我们可以看到,multiply.jsindex.js均依赖于sum.js。因此,如果index.html导入依赖时使用了错误的顺序,那么我们的应用就无法工作。举个例子,如果index.js最先被导入,或者sum.jsmultiply.js之后被导入,都会得到错误。

    基于上面的例子,让我们想象一下,一个真实的Web应用往往可能会包含多达几十个依赖项,这些依赖项之间还可能存在依赖关系,维护这些依赖项之间的顺序想想就让人窒息。这里还可能存在变量被其它依赖覆盖的风险,而这将会导致难以发现的BUG。

    为了解决这个痛点,Webpack会将依赖转换为作用域更小的模块,从而避免变量被覆盖。依赖转换为模块带来的额外好处是,Webpack可以为我们管理这些依赖。具体做法是,Webpack会在需要时,把依赖模块引入进来,并匹配对应的作用域。

    通过打包来减少HTTP请求次数

    我们还是回看一下index.html,这个文件中我们需要下载三个独立的文件。当然这里文件比较少还能够应付,但还是之前提到的问题,真实的Web应用中,依赖项可能会很多,而这将会导致用户不得不等待所有依赖项挨个下载完成后,主应用才能运行。

    而这就引出了Webpack的另一特性——打包。Webpack可以将所有的依赖打包成一个文件,而这就意味着,用户只需要下载一个依赖项,主应用就可以运行。

    综上所述,Webpack的主要特性就是打包模块化。通过使用插件和加载器,我们可以扩展Webpack的这两大特性。

    让依赖可用,并组合它们

    我们将使用CommonJS模块语法,作为初始设置。当然,这里也有诸如AMD,ES2015等其它选择,但这里我们将先使用CommonJS,稍后迁移到ES2015。

    CommonJS将模块导出,使得其它代码可以使用导出模块中的函数或变量。我们可以通过require将导出模块中的值读出来。

    index.html

    <html>
    <head>
        <script src="./dist/bundle.js""></script>
    </head>
    </html>

    index.js

    var multiply = require('./multiply');
    var sum = require('./sum');
    var totalMultiply = multiply(5, 3);
    var totalSum = sum(5, 3);
    console.log('Product of 5 and 3 = ' + totalMultiply);
    console.log('Sum of 5 and 3 = ' + totalSum);

    multiply.js

    var sum = require('./sum');
    var multiply = function (a, b) {
        var total = 0;
        for (var i = 0; i < b; i++) {
            total = sum(a, total);
        }
        return total;
    };
    module.exports = multiply;

    sum.js

    var sum = function (a, b) {
        return a + b;
    };
    module.exports = sum;

    观察上面的代码,不难发现,为了让函数sum与函数multiply能够被使用,我们在sum.jsmultiply.js脚本中,导出了这两个函数。这里有个细节,不知道大家是否注意到了,在index.html中我们现在仅需要导入一个bundle.js文件。

    这可帮大忙了!我们现在不再需要关注依赖的顺序,可以暴露我们想暴露的内容,并使得其它内容仍然保持私有。同时,我们现在仅需要导入一个文件,而不是三个,这有助于提高应用的加载速度。

    Webpack的初始配置

    为了实现我们上面要达到的效果,我们需要对Webpack做一些初始的配置。

    var path = require('path');
    module.exports = {
      entry: './src/index.js',    
      output: {
        path: path.resolve(__dirname, './dist/'),
        filename: 'bundle.js'
      }
    }

    这里我们实现了一个最简单的配置。我们至少需要告诉Webpack,我们应用的入口在哪,输出结果应该是什么。我们来详细看看每个属性所代表的含义。

    来看看bundle.js

    阅读生成的bundle.js代码,可以给我们带来一些启发。

    // the webpack bootstrap
    (function (modules) {
        // The module cache
        var installedModules = {};
        // The require function
        function __webpack_require__(moduleId) {
            // Check if module is in cache
            // Create a new module (and put it into the cache)
            // Execute the module function
            // Flag the module as loaded
            // Return the exports of the module
        }
    
    
        // expose the modules object (__webpack_modules__)
        // expose the module cache
        // Load entry module and return exports
        return __webpack_require__(0);
    })
    /************************************************************************/
    ([
        // index.js - our application logic
        /* 0 */
        function (module, exports, __webpack_require__) {
            var multiply = __webpack_require__(1);
            var sum = __webpack_require__(2);
            var totalMultiply = multiply(5, 3);
            var totalSum = sum(5, 3);
            console.log('Product of 5 and 3 = ' + totalMultiply);
            console.log('Sum of 5 and 3 = ' + totalSum);
        },
        // multiply.js
        /* 1 */
        function (module, exports, __webpack_require__) {
            var sum = __webpack_require__(2);
            var multiply = function (a, b) {
                var total = 0;
                for (var i = 0; i < b; i++) {
                    total = sum(a, total);
                }
                return total;
            };
            module.exports = multiply;
        },
        // sum.js
        /* 2 */
        function (module, exports) {
            var sum = function (a, b) {
                return a + b;
            };
            module.exports = sum;
        }
    ]);

    从上面的代码可以看出,Webpack把每一个js脚本都封装到一个模块中,并把这些模块放进数组中。模块数组被传入Webpack的引导程序中,引导程序会把这些模块加入Webpack并执行,使得模块可用。

    这里bundle.js返回的是__webpack_require__(0),而这刚好对应了模块数组中的index.js部分。基于此我们同样可以得到正确的运行结果,而不需要处理依赖管理,下载依赖的次数也仅需要一次。

    Loader让Webpack更好用

    Webpack仅能理解最基本的JavaScript ES5代码,它自身仅支持创建模块并打包JavaScript ES5。如果我们不仅仅局限于JavaScript ES5,例如我们想使用ES2015,这就需要告诉Webpack如何处理ES2015。这里我们的处理方式往往是,我们需要将其它语言(如TypeScript)或其它版本(如ES2015)预处理成JavaScript ES5,再让Webpack做打包。这里就需要使用Babel来做转换,把ES2015转换为ES5(当然Babel能做的事情远不止如此)。

    为了说明这个过程,我们使用ES2015重写之前的功能。

    index.js

    import multiply from './multiply';
    import sum from './sum';
    const totalMultiply = multiply(5, 3);
    const totalSum = sum(5, 3);
    console.log(`Product of 5 and 3 = ${totalMultiply}`);
    console.log(`Sum of 5 and 3 = ${totalSum}`);

    multiply.js

    import sum from './sum';
    const multiply = (a, b) => {
        let total = 0;
        for(let i=0;i<b;i++) {
            total = sum(a, total);
        }
        return total;
    };
    export default multiply;

    sum.js

    const sum = (a, b) => a + b;
    export default sum;

    这里我们使用了很多ES2015的新特性,例如箭头函数、const关键字、模板字符串和ES2015的导入导出。ES2015的代码Webpack无法处理,所以我们需要Babel来进行转换。想要让Babel配合Webpack完成工作,我们就需要用到Babel Loader。事实上,Loader就是Webpack处理JavaScript ES5以外内容的方式。有了加载器,我们就可以让Webpack处理各式各样的文件。

    想要在Webpack中使用Babel Loader,我们还需要三个Babel依赖:

    在Webpack中配置Babel Loader的代码,差不多是下面这样子:

    const path = require('path');
    module.exports = {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, './dist/'),
            filename: 'bundle.js'
        },
        module: {
            loaders: [
                {
                    test: /.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/,
                    query: {
                        presets: ['es2015']
                    }
                }
            ]
        }
    };

    这段代码你可以在webpack.config.js中找到。值得注意的是,Webpack中是支持同时存在多个Loader的,所以提供的值是一个数组。接着,还是让我们来看看每个属性代表的含义。

    配置好这些内容后,再次查看打包生成的bundle.js,其中的内容看起来就像下面这样:

    /* 2 */
    function(module, exports) {
      var sum = function sum(a, b) {
        return a + b;
        };
        
        module.exports = sum;
    }

    可以看到,Babel Loader已经把ES2015的代码变成了ES5的代码。

    CSS与样式,让Webpack看起来更出色

    接下来,让我们拓展上面的例子,输出计算结果。我们将在页面上创建一个body,然后把乘积与加和的结果添加到span中。

    import multiply from './multiply';
    import sum from './sum';
    
    const totalMultiply = multiply(5, 3);
    const totalSum = sum(5, 3);
    
    // create the body
    const body = document.createElement("body");
    document.documentElement.appendChild(body);
    
    // calculate the product and add it to a span
    const multiplyResultsSpan = document.createElement('span');
    multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
    
    // calculate the sum and add it to a span
    const sumResultSpan = document.createElement('span');
    sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
    
    // add the results to the page
    document.body.appendChild(multiplyResultsSpan);
    document.body.appendChild(sumResultSpan);

    这段代码的输出结果,应该与之前是一致的,区别仅在于显示在页面上。

    Product of 5 and 3 = 15 Sum of 5 and 3 = 8

    我们可以使用CSS来美化这个结果,比如,我们可以让每个结果都独占一行,并且给每个结果都加上边框。为了实现这一点,我们可以使用如下的CSS代码。

    span {
        border: 5px solid brown;
        display: block;
    }

    我们需要将这个CSS也导入应用中。这里最简单的解决方案是,在我们的html中添加一个link标签。但有了Webpack提供的强大功能,我们可以先导入它,再用Webpack来处理这个样式。

    在代码中导入CSS带来的另一个好处是,开发者可以清晰的看到CSS与其使用之间的关联。这里需要注意的是,CSS的作用域并不局限于它所导入的模块本身,其作用域仍然是全局的,但从开发者的角度看,这样使用更加清晰。

    import multiply from './multiply';
    import sum from './sum';
    
    // import the CSS we want to use here
    import './math_output.css';
    
    const totalMultiply = multiply(5, 3);
    const totalSum = sum(5, 3);
    
    // create the body
    const body = document.createElement("body");
    document.documentElement.appendChild(body);
    
    // calculate the product and add it to a span
    const multiplyResultsSpan = document.createElement('span');
    multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
    
    // calculate the sum and add it to a span
    const sumResultSpan = document.createElement('span');
    sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
    
    // add the results to the page
    document.body.appendChild(multiplyResultsSpan);
    document.body.appendChild(sumResultSpan);

    这段代码中,与前面代码的唯一区别在于,我们导入了CSS。我们需要两个Loader来处理我们的CSS:

    现在我们在webpack.config.js中的Webpack配置看起来像这样:

    const path = require('path');
    module.exports = {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, './dist/'),
            filename: 'bundle.js'
        },
        module: {
            loaders: [
                {
                    test: /.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/,
                    query: {
                        presets: ['es2015']
                    }
                },
                {
                    test: /.css$/,
                    loaders: ['style-loader', 'css-loader']
                }
            ]
        }
    };

    我们还是来看看新增的CSS配置属性所表示的内容。

    假如我们现在需要提取CSS,并输出到一个文件中,再导入该文件。为了实现这一点,我们就要用到Plugin。Loader的作用在于,在数据被打包输出前进行预处理。而Plugin则可以阻止预处理的内容直接出现在我们的打包结果中。

    我们的Webpack配置现在变成了这样:

    const path = require('path');
    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    module.exports = {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, './dist/'),
            filename: 'bundle.js'
        },
        module: {
            loaders: [
                {
                    test: /.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/,
                    query: {
                        presets: ['es2015']
                    }
                },
                {
                    test: /.css$/,
                    loader: ExtractTextPlugin.extract('css-loader')
                }
            ]
        },
        plugins: [
            new ExtractTextPlugin('style.css')
        ]
    };

    在这段代码的顶部,我们导入了ExtractTextPlugin,并使用这个插件改造了之前的CSS Loader。这里的作用是,css-loader的处理结果不再直接返回给Webpack,而是传递给ExtractTextPlugin。在底部我们配置了这个Plugin。

    这里配置的作用是,对于传递给ExtractTextPlugin的CSS样式数据,将会被保存在名为style.css的文件中。这样做的好处与之前处理JavaScript时一样,我们可以将多个独立的CSS文件合并为一个文件,从而减少加载样式时的下载次数。

    最终我们可以直接使用我们合并好的CSS,实现和之前一致的效果。

    <html>
      <head>
        <link rel="stylesheet" href="dist/style.css"/>
        <script src="./dist/bundle.js""></script>
      </head>
    </html>

    使用Webpack处理图片

    现在我们开始尝试向应用中添加图片,并让Webpack来协助我们处理这些图片。这里我们添加了两张图片,一个用于求和,一个用于求积。为了让Webpack有能力处理这些图片,我们使用这两个Loader:

    我们准备了两张图片,用于求积的图片(multiply.png)相对较大,用于求和的图片(sum.png)相对较小。首先,我们添加一个图片工具方法,这个方法会为我们创建图片,并将图片添加到文档中。

    image_util.js

    const addImageToPage = (imageSrc) => {
        const image = document.createElement('img');
        image.src = imageSrc;
        image.style.height = '100px';
        image.style.width = '100px';
        document.body.appendChild(image);
    };
    export default addImageToPage;

    接着,让我们导入这个图片工具方法,以及我们想要添加到index.js中的图片。

    import multiply from './multiply';
    import sum from './sum';
    
    // import our image utility
    import addImageToPage from './image_util';
    
    // import the images we want to use
    import multiplyImg from '../images/multiply.png';
    import sumImg from '../images/sum.png';
    
    // import the CSS we want to use here
    import './math_output.css';
    
    const totalMultiply = multiply(5, 3);
    const totalSum = sum(5, 3);
    
    // create the body
    const body = document.createElement("body");
    document.documentElement.appendChild(body);
    
    // calculate the product and add it to a span
    const multiplyResultsSpan = document.createElement('span');
    multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`));
    
    // calculate the sum and add it to a span
    const sumResultSpan = document.createElement('span');
    sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`));
    
    // add the results to the page
    addImageToPage(multiplyImg);
    document.body.appendChild(multiplyResultsSpan);
    addImageToPage(sumImg);
    document.body.appendChild(sumResultSpan);

    最后,我们还是来修改webpack.config.js,配置两个新的Loader来处理这些图片。

    const path = require('path');
    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    module.exports = {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, './dist/'),
            filename: 'bundle.js',
            publicPath: 'dist/'
        },
        module: {
            loaders: [
                {
                    test: /.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/,
                    query: {
                        presets: ['es2015']
                    }
                },
                {
                    test: /.css$/,
                    loader: ExtractTextPlugin.extract('css-loader')
                },
                {
                    test: /.png$/,
                    loaders: [
                        'url-loader?limit=5000',
                        'image-webpack-loader'
                    ]
                }
            ]
        },
        plugins: [
            new ExtractTextPlugin('style.css')
        ]
    };

    还是老规矩,我们来看看新增的参数都表示什么含义。

    现在我们执行Webpack打包,会得到下面三个东西。

    38ba485a2e2306d9ad96d479e36d2e7b.png
    bundle.js
    style.css

    这里的38ba485a2e2306d9ad96d479e36d2e7b.png实际上就是我们的大图片multiply.png,较小的图片sum.png会被内联到bundle.js中,就像下面这样。

    module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL...."

    这其实相当于

    img.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL..."

    更多编程相关知识,请访问:编程视频!!

    以上就是Webpack是什么?详解它是如何工作的?的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:webpack
    上一篇:一文详解JavaScript中执行上下文与执行栈(图文结合) 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 深析webpack的打包流程和原理• 深入解析webpack的五个核心概念• webpack核心概念之输出(Output)• webpack核心概念之入口配置(entry)• 聊聊webpack中怎么压缩打包html资源• JavaScript webpack5配置及使用基本介绍
    1/1

    PHP中文网