🌟给webpack一个认识吧!

1,816 阅读7分钟

1. webpack是什么

webpack是一个现代Javascript应用程序的静态模块打包器,指的是webpack在不进行特殊配置时,就只能处理JavaScript这一种语言,打包指的就是多个js文件不需要人为的去理清其的依赖关系,而且还能将其合成为一个文件。

为什么要打包呢?因为前端项目中的逻辑多了,文件多了,复杂度提高了(比如一个项目依赖多个模块),于是提出多种模块化的标准,webpack就是这么一种优秀的模块化方案。
webpack不仅强大,而且灵活(每个功能可插拔)。

webpack除了打包还能干什么?

  • 翻译!加入loader,把代码翻译成浏览器看的懂的代码。
  • 做点别的操作,能力扩展类,在webpack的plugin里。

下面会解释loader和plugin

2. webpack中的关键人物

  • loader:指文件加载器,执行一个文件的编译转译功能,例如有,es6转es5,就有babel-loader,文件中引入css文件就需要css-loaderstyle-loader
  • plugin:用于增强一个项目里面webpack的能力,其机制就是强调一个事件监听的能力,在项目里监听一些事件,并且改变一些文件打包输出的结果。比如使用一个叫UglifyJSPlugin的插件来对js文件进行压缩,从而减小js文件的大小,加速load速度。

Loader负责文件转换,那么Plugin便是负责功能扩展。Loader和Plugin作为Webpack的两个重要组成部分,承担着两部分不同的职责。

3. 前端模块化

什么是模块呢? 举个例子:一个公司需要正常运转,就有市场部,技术部,人事部等等,这每个部门就相当于一个模块,在前端项目中也就有比如专门网络请求的模块,错误处理的模块,专门渲染的模块。

了解模块化之前了解下作用域的概念先。

传统做法会引入多个js脚本,它们共处于全局作用域下,就容易导致全局作用域变量冲突(例如同名变量冲突),而发生一些不可预测的事情。 例如:

/*moduleA.js里*/
var a=10;

/*moduleB.js里*/
var a=11;

/*index.html里*/
<body>
    <script src="./moduleA.js"></script>
    <script src="./moduleB.js"></script>
    <script src="./moduleC.js"></script>
</body>

当出现上面得冲突后,a的值还能确定吗?——不能!

然后就有人想出,每个js脚本里都使用一个对象包裹,形成一个局部作用域。

// 定义模块内的局部作用域,以moduleA为例
    var Susan = {
    	name: "susan",
        sex: "female",
        tell: function(){
        	console.log("im susan")
        }
    }

但是这样又有各严重的问题,就是对象里值我们能更改,无法保证模块属性内部安全性,对于比如说用户名密码等数据的情景就很严重了。

于是又改进到了立即执行函数和闭包的形式。

// 定义模块内的闭包作用域(模块作用域),以moduleA为例
    var SusanModule = (function(){
    	var name = "susan"
        var sex = "female"
        functioon tell(){//这样就不能更改其中的数据了
        	console.log("I'm ", this.name)
        }
    })()

我们再改进下写法,为立即执行函数写入参数为window

// 定义模块内的闭包作用域(模块作用域),以moduleA为例
    (function(window){
    	var name = "susan"
        var sex = "female"
        functioon tell(){
        	console.log("I'm ", this.name)
        }
        window.susanModule = {tell}
    })(window)// window作为参数传给
//////////////////////
//测试
window.susanModule.tell(); //im susan

这样大概就是早期的模块化的形式了。

现在的模块化方案有

  • AMD(Asynchronous Module Definition 异步模块定义)
//大概形式如下
//定义
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
});

// 加载
require(["module", "../file"], function(module, file) {
});
  • CommonJs:Node.js 专用, 该方案的核心思想就是允许模块通过require方案同步加载依赖的其他模块,通过exports或module.exports来暴露出需要的接口。
// 通过require函数来引用
const math = require("./math");

// 通过exports将其导出
exports.getSum = function(a,b){
    return a + b;
}
  • ES6 Module:该方案最大的特点就是静态化,静态化的优势在于可以在编译的时候确定模块的依赖关系以及输入输出的变量。上面提到的CommonJs和AMD都只能在运行时确定这些东西。
// 通过import函数来引用
import math from "./math";

// 通过export将其导出
export function sum(a, b){
    return a + b;
}

此外说说ES6模块化和CommonJs的模块化的区别:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

    注意:CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

    ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    原因:CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

前端模块化主要解决了两个问题:“命名空间冲突”,“文件依赖管理”

和介绍webpack又有什么关系呢?
在webpack中,一切皆模块。我们在模块化开发的时候,通常会使用ES Module或者CommonJS规范导出或引入依赖模块,webpack打包编译的时候,会统一替换成自己的__webpack_require__来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。

4. webpack打包的核心思路

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
  • 编译:从 Entry入口文件出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
  • 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

5. 一个简单的webpack配置示例

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: path.resolve(__dirname, '../src/index.js'), //指定入口文件,程序从这里开始编译,__dirname当前所在目录, ../表示上一级目录, ./同级目录
    output: {
        path: path.resolve(__dirname, '../dist'), // 输出的路径
        filename: 'app/[name]_[hash:8].js'  // 打包后文件
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015', 'react'],
                    }
                },
                exclude: /node_modules/
            }
        ]
    },
   plugins: [//plugin下的插件
     new HtmlWebpackPlugin({
       template: path.resolve(__dirname, '../src/index.template.html'),
       inject: true
     })
   ]

loader下

  • babel-loader: babel加载器

  • babel-preset-es2015: 支持es2015

  • babel-preset-react: jsx 转换成js

loader是支持以数组的形式配置多个的,当Webpack在转换该文件类型的时候,会按顺序链式调用每一个loader,前一个loader返回的内容会作为下一个loader的入参。

plugin下

html-webapck-plugin插件的两个主要作用:

  • html文件中引入的外部资源如scriptlink动态添加每次compile后的hash,防止引用缓存的外部文件问题

  • 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口

webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,plugin插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务,从而实现自己想要的功能。

一些常见的loader和plugin可以看看这里,最好对这些有个大概了解和记忆。。

6.总结

到这里就为理解webpack下一个简单定义:webpack作用于模块打包,将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。并且通过其的loader机制,让webpack能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。且再通过webpack的Plugin机制,我们还可以进一步实现诸如按需加载,代码压缩等一系列扩展功能。

参考文章:

当面试官问Webpack的时候他想知道什么

再来一打webpack面试考题