Webpack基础(一):安装与启动篇

1,151 阅读10分钟

前言

模块化思想

何为模块化思想?

模块化思想其实就是将一块代码按照特定的功能进行拆分,每个代码段有各自的目的,可以对它进行独立的设计,开发,测试,最终通过接口将它们组合在一起。

在传统的使用script标签的方式引入到页面中,这种做法的弊端是:

  • 需要手动维护JavaScript的加载顺序。当这些script存在依赖关系的时候,需要手动的调整顺序,防止因为依赖问题导致出现问题。
  • 多个script,请求也多,意味着每一个script标签都请求一次静态资源,过多的请求会拖慢网页的性能。
  • 每个script的顶层作用域都是全局作用域,如果没有做处理,将会导致全局作用域污染问题。

模块化将解决这些问题:

  • 通过导入导出语句,我们就知道模块间的依赖关系
  • 借助打包工具,我们可以将一些资源进行合并,压缩,减少网络开销
  • 多个模块的作用域是隔离的,不会出现命名冲突问题

模块化思想有:AMD、CommonJS、CMD,ES Module等

模块化的变化过程

使用函数进行封装

我们可以将不同逻辑和功能封装到不同的全局函数中,这样逻辑可以解耦

写法:

// 基数
const base = 10// 加法
function add(a, b) {
    return a + b + base
}

// 减法
function subtract(a, b) {
    return a - b - base
}

// 乘法
function multiply(a, b) {
    return a*b*base
}

上面就是最简单的例子,分别实现了加减乘方法,并且加入了一个基数,以后扩展和复用就可以自己进行组合了。

缺点: 会造成全局变量污染,容易引发命名冲突,并且关系不明确

使用命名空间的形式

命名空间的写法我们一般使用a.b.c.d,一层一层的找对应的值,使用我们可以使用简单的对象进行封装我们的逻辑,这样就可以减少全局变量,从而减少变量污染,命名冲突的问题。

写法:

let convertFun = {
    base: 10,
    add: function(a, b) {
        return a + b + this.base
    },
    subtract: function(a, b) {
        return a - b - this.base
    },
    multiply: function(a, b) {
        return a*b* this.base
    }
}

上面我们可以看出,我们将之前的四个换算方法用一个对象包裹起来了,相当于多了一层命名空间,访问的话就需要使用convertFun.add(1,2)来进行访问,这样就可以与全局变量进行隔离开来。

缺点: 内部的方法和状态会被外部随意改动,比如外部随意就修改了base

自执行函数

从上面来看,我们只需要解决私有化变量的问题就能算一个好的模块的了。

为了不影响到全局作用域,那么我们可以使用自执行函数来做隔离,因为它在编译的时候就执行了,执行完就释放,不影响其他全局变量, 它就相当于一个window一样,那么我们如何实现像定义全局变量一样,在里面定义变量呢?答案就是利用闭包,函数内部能访问到外部的变量,那么闭包则不会立即回收。

用法:

(function(window) {
    let base = 10
    function add(a, b) {
        return a + b + base
    }

    // 减法
    function subtract(a, b) {
        return a - b - base
    }

    // 乘法
    function multiply(a, b) {
        return a*b*base
    }

    // 挂载
    window.convertFun = {
        add,
        subtract,
        multiply
    }

})(window)

这样外界就不能修改到base了,相当于base变成了一个私有属性。 看到上面的写法,不禁想到了jQuery,也是使用这种方式实现的,源码如下:

(function(window) {
    var jQuery = (function() {    
        // 逻辑
        return jQuery
    })();
    // .....
    window.jQuery = window.$ = jQuery
})(window)

但是这种写法还有一个问题,那就是依赖问题,当这个模块依赖另一个模块的时候,那么就需要新增传参解决,那么就可以使用下面的形式将依赖传进入。

(function(window, jQuery) {
    let base = 10
    function add(a, b) {
        // jQuery.....
        return a + b + base
    }

    // 减法
    function subtract(a, b) {
        // jQuery.....
        return a - b - base
    }

    // 乘法
    function multiply(a, b) {
        // jQuery.....
        return a*b*base
    }

    // 挂载
    window.convertFun = {
        add,
        subtract,
        multiply
    }

})(window, jQuery)

这些就是模块化的进化过程,但是当我们有多个这样的script文件被引入到一个里面的时候,就会导致请求过多,依赖不清晰,加载顺序问题。 为了解决这些问题,我们还需要借助一些模块化思想,也就是模块化规范。

为什么选择Webpack

  • 默认支持多种模块标准,包括AMD,CommonJS,以及ES6模块。
  • Webpack有完备的代码分割解决方案。首屏只加载必要的部分,不太重要的功能放到最后动态加载,有效提高首屏加载速度。
  • Webpack处理各种类型的资源。
  • 拥有庞大的社区支持。除了核心库以外,还有很多周边的插件和工具。

安装和启动

安装

首先想用webpack打包的话,必须要安装webpack。

我们先可以使用npm init -y 快速初始化一个项目

然后使用npm install webpack@4.29.4 --save-dev,这里我们安装的是4.29.4版本的,每个版本都可能不一样,建议安装相同版本,除了用npm外,还可以用yarn add来进行安装,这里就不多说了。那么安装分为全局安装和本地安装,我们使用的是本地安装save-dev,不影响你的全局版本的。

除了安装webpack外,还需安装webpack命令行工具webpack-cl,所以我们使用npm install webpack-cli@3.2.3,同样也是要注意版本一致。

如果安装成功,我们可以使用npx webpack -vnpx webpack-cli -v看是否安装成功。

如下是全部过程:

// 初始化项目
npm init -y
// 安装webpack核心模块
npm install webpack@4.29.4 --save-dev
// 安装webpack命令行工具
npm install webpack-cli@3.2.3 --save-dev
// 验证是否安装成功
npx webpack -v
npx webpack-cli -v

启动

下面我们来打包第一个应用。

  • 先初始化文件目录:

    首先我们先新建一个test1的文件夹,表示测试1,后续都是单独新建文件夹来做测试的。

    然后再test1中新建一个index.html文件和一个src文件夹,在src中新增一个index.js,然后再新增一个utils文件夹,里面新增一个add.js的文件

  • 写入代码:

    我们在test1/src/utils/add.js中写一个add方法

    export default function(a, b) {
        return a + b
    }
    

    然后在外面的test1/src/index.js中写上

    import add from "./utils/add";
    
    document.write('问题: 1+2等于几? 1+2='+ add(1, 2))
    

    最后我们在test1/index.html模板中引入一个假设我们打包好的bundle.js,它默认会打包进根目录的dist,所以我们会将bundle.js放到dist中的test1下

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <script src="../dist/test1/bundle.js"></script>
    </body>
    </html>
    
  • 执行打包命令:

    打包命令以webpack开头,有很多配置,我们暂时只需要关心最基础的输入输出配置即可,也就是--entry和--output

    npx webpack --entry=./test1/src/index.js --output-filename=./test1/bundle.js --mode=development

执行命令会得到如下命令输出:

1.png

按照上面的步骤来,最后能在根目录中看见多出了个dist,并且在dist中有个test1,里面就有bundle.js,也就是我们打包后的文件。

我们一般工作中,会使用npm run build代替这个很长的命令,那么我们可以在package.json中的scripts对象中加上这个

"scripts": {
    "build": "webpack --entry=./test1/src/index.js --output-filename=./test1/bundle.js --mode=development"
}

最后打包出来也是一样的

2.png

我们还可以简化,只执行webpack命令,当执行webpack命令的时候,将会去找webpack.config.js配置文件,我们一般将这个文件放在根目录下,将命令行的配置方式改成了key-value的形式。

// packages.json
"scripts": {
    "build": "webpack" // 简化后
}
// webpack.config.js
var path = require('path')

module.exports = {
    entry: './test1/src/index.js',
    output: {
        path: path.join(__dirname , '/dist'),// 要求使用绝对路径
        filename: './test1/bundle.js',
    },
    mode: 'development'
}

这样我们就算是启动了webpack打包的能力了,这些是最最最基础的入门了,后续将介绍更多打包方法。

如果我们想在本地开发方便一点的话,我们可以使用便捷的本地开发工具webpack-dev-server,也就是本地起一个服务,当保存的时候,重新打包,然后生效(模块热替更)。它仅仅在开发环境下使用,所以可以使用以下命令进行安装,

npm install webpack-dev-server --save-dev

在生产环境中,我们不需要它,因此放在了devDependencies中比较恰当。如果我们在工程上线的时候进行安装依赖,那么我们可以使用npm install --production 进行过滤掉devDependencies中的冗余模块,从而快速安装和发布。

下面我们来使用一下这个工具:

我们先在package.json中添加dev命令,让我们在运行npm run dev的时候就启动。

// package.json
{
  "scripts": {
    "build": "webpack"
    "dev": "webpack-dev-server"  
  }
}

然后我们要在webpack.config.js中进行配置devServer:

// webpack.config.js
var path = require('path')
const htmlPlugin = require('html-webpack-plugin')

module.exports = {
    entry: './test1/src/index.js',
    output: {
        path: path.join(__dirname , '/dist'),// 要求使用绝对路径
        filename: './test1/bundle.js',
    },
    plugins: [new htmlPlugin({ title: '测试'})],
    mode: 'development',
    devServer: {
        publicPath: '/',
        port: 3000
    }
}

除此之外,我们还需要npm install html-webpack-plugin --save-dev , 它的作用自动在dist文件夹下生成index.html,并自动引入打包后的js。这样访问localhost:3000,就可以访问到打包后的内容。

webpack-dev-server可以看作一个服务者,也就是服务器,它的主要工作就是接收浏览器的请求,然后将资源返回。当执行npm run dev启动服务的时候,然后进行模块打包并将资源打包好也就是bundle.js,当它接收到请求时,它会对URL地址校验。如果地址是配置的资源服务地址publicPath,就会将webpack的打包结果中将资源返回给浏览器。如果URL的地址不属于资源服务地址,则直接读取硬盘中的源文件返回。

所以webpack-dev-server有两个作用:

  • 进行模块打包,并处理打包后结果的资源请求
  • web服务,处理静态资源请求并返回给客户端

所以有些教程publicPath设置为/dist, 如果我们访问localhost:3000, 就会出现如下情况:

3.png

因为URL和publicPath的资源地址不匹配,所以返回硬盘的资源,如果我们想看到打包的结果,我们就可以访问localhost:3000/dist即可,同时我们可以设置publicPath为/,这样可以直接访问localhost:3000,最后的访问结果如下:

4.png

需要注意的是,用Webpack开发和使用webpack-dev-server开发有区别,Webpack开发打包每次都生成新的bundle.js,而webpack-dev-server只是将打包结果放在内存中,并不会生成实际的bundle.js,每次接收到请求时将内存中的打包结果返回给浏览器,所以我们删除dist目录,如果不存在,仍然可以正常访问。这也是因为在开发的过程中,我们会经常改动目录和文件名,如果每次都将这些改动打包进去dist,就会产生很多垃圾文件。并且它能够自动刷新live-reloading,能提升本地开发效率。

到此为止,我们就已经熟悉了webpack的安装和打包,后续我们将学更多的打包配置和原理。