webpack:提高开发者效率的工具

923 阅读4分钟

webpack的定义


webpack从本质上说,就是一个打包器,主要的作用就是将前端的文件进行模块化的工具。

image.png

从图上可以看出,开发者将开发中的各种后缀的文件,经过webpack之后,最后变成了js,png,css结尾的文件。现在的前端工程里,你要是说没有用webpack,你都不好意思出门。

那webpack的主要功能有哪些呢:

  • Webpack 作为一个模块打包工具,本身就可以解决模块化代码打包的问题,将零散的 JavaScript 代码打包到一个 JS 文件中。

  • 对于有环境兼容问题的代码,Webpack 可以在打包过程中通过 Loader 机制对其实现编译转换,然后再进行打包。

  • 对于不同类型的前端模块类型,Webpack 支持在 JavaScript 中以模块化的方式载入任意类型的资源文件,例如,我们可以通过 Webpack 实现在 JavaScript 中加载 CSS 文件,被加载的 CSS 文件将会通过 style 标签的方式工作。

    除此之外,Webpack 还具备代码拆分的能力,它能够将应用中所有的模块按照我们的需要分块打包。这样一来,就不用担心全部代码打包到一起,产生单个文件过大,导致加载慢的问题。我们可以把应用初次加载所必需的模块打包到一起,其他的模块再单独打包,等到应用工作过程中实际需要用到某个模块,再异步加载该模块,实现增量加载,或者叫作渐进式加载,非常适合现代化的大型 Web 应用。

webpack的核心元素(entry,output,loader和plugin)


那接下来,我们通过一个小demo看下,webpack最简单的运行方式是怎么样的。

//目录结构
webpack
├── index.html
├── package.json
├── public
├── src
│   ├── heading.js
│   ├── main.css
│   └── main.js
└── webpack.config.js

// ./webpack.config.js
// 所有的配置都在这个对象中
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const webpack = require('webpack');
module.export = {
 entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 根据打包过程中所遇到文件路径匹配是否使用这个 loader
        use: [
          'style-loader',
          'css-loader'
        ] // 指定具体的 loader
      }
    ]
  },
    plugins:[
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      title: 'Webpack Plugin Sample',
      template: './index.html'
    })
  ],
}

在这个其中,我们可以看到webpack打包时的基本配置。

  • entry:webpack的入口文件
  • output:webpack打包后的输出文件
  • loader(就是 moduls包裹的内容)
  • plugins(webpack的插件)

在这里重点解释先loader和plugins

loader:

loader是webpack作为打包器的灵魂了。没有loader之前,webpack是一个只能识别js文件的青年,一但碰到css,png等等特殊资源的时候,就只能两眼抓瞎,然后在命令行里告诉开发者:"这东西我不认识啊!"。所以,webpack就有了loader这个帮手,loader可以帮助webpack在遇到这些webpack不认识的文件类型时,将这些文件类型转换成js类型文件。

那么loader是怎么工作的呢,以css为例:

  1. 首先,要用npm去按照对应的loader文件,css-loader;

  2. 在配置对象的 module 属性中添加一个 rules 数组。这个数组就是我们针对资源模块的加载规则配置,其中的每个规则对象都需要设置两个属性:

    ​ 首先是 test 属性,它是一个正则表达式,用来匹配打包过程中所遇到文件路径,这里我们是以 .css 结尾;

    ​ 然后是 use 属性,它用来指定匹配到的文件需要使用的 loader,这里用到的是 css-loader。

  3. 配置完成过后,我们回到命令行终端重新运行打包命令,打包过程就不会再出现错误了,因为这时 CSS 文件会交给 css-loader 处理过后再由 Webpack 打包。

image.png

还有一个需要注意的点,css-loader只会把 CSS 模块加载到 JS 代码中,而并不会使用这个模块。所以这里我们还需要在 css-loader 的基础上再使用一个 style-loader,把 css-loader 转换后的结果通过 style 标签追加到页面上。在这个过程上大家也可以看出来,为什么loader的执行顺序是从右到左,从下到上了。

plugins:

如果说loader是webpack的灵魂,那plugins等于给webpack插上了想象的翅膀,横向的扩展了webpack的构建能力,为开发者的效率添砖加瓦了。

我在这里先介绍几个插件最常见的应用场景:

  • 实现自动在打包之前清除 dist 目录(上次的打包结果);
  • 自动生成应用所需要的 HTML 文件;
  • 根据不同环境为代码注入类似 API 地址这种可能变化的部分;
  • 拷贝不需要参与打包的资源文件到输出目录;
  • 压缩 Webpack 打包完成后输出的文件;
  • 自动发布打包结果到服务器实现自动部署。

总之,有了 Plugin 的 Webpack 几乎“无所不能”。借助插件,我们就可以轻松实现前端工程化中绝大多数经常用到的功能,这也正是很多初学webpack的人会认为 “Webpack 就是前端工程化,或者前端工程化就是 Webpack” 的原因。

那么,webpack是如何实现这些插件的呢。其实说起来也非常简单,Webpack 的插件机制就是我们在软件开发中最常见的钩子机制。

钩子机制也特别容易理解,它有点类似于 Web 中的事件。在 Webpack 整个工作过程会有很多环节,为了便于插件的扩展,Webpack 几乎在每一个环节都埋下了一个钩子。这样我们在开发插件的时候,通过往这些不同节点上挂载不同的任务,就可以轻松扩展 Webpack 的能力。

img

具体有哪些预先定义好的钩子,我们可以参考官方文档的 API:

webpack帮助提高生产力


Webpack Dev Server

现在大家在前端开发时,应该都可以感受到现在开发时的便利。只要启动了项目的虚拟服务器,在开发者修改了代码后,相应的页面就会修改对应的页面,开发者可以实时的检查自己的需求是不是已经在代码中实现了。其实这些功能,在使用webpack的工程里,都是webpack的devServer 的属性实现了。

# 安装 webpack-dev-server
$ npm install webpack-dev-server --save-dev
# 运行 webpack-dev-server
$ npx webpack-dev-server
// ./webpack.config.js

const path = require('path')
module.exports = {
  // ...
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000
    // ...
    // 详细配置文档:https://webpack.js.org/configuration/dev-server/
  }
}

运行 webpack-dev-server 这个命令时,它内部会启动一个 HTTP Server,为打包的结果提供静态文件服务,并且自动使用 Webpack 打包我们的应用,然后监听源代码的变化,一旦文件发生变化,它会立即重新打包,流程如下图:

image.png

值得注意的是,devServer是运行在内存中的,内部的 HTTP Server 也是从内存中读取这些文件的。这样一来,这就减少了很多不必要的磁盘读写操作,大大提高了整体的构建效率。

image.png

另外,devServer还给我们提供了一个proxy的代理。当服务器太多的时候,根据浏览器的同源策略,大家难免会遇到跨域请求的问题。那这个时候,我们可能会要求后端帮我们在服务器上增加下代理。要是这个需求比较简单的时候,后端也就顺手帮你做掉了。但是,有时候,可能由于后端比较忙,或者服务器上的配置不好修改的时候,那就可以前端试试这个proxy代理来解决一些问题了。

  1. proxy托管的是一个相对地址,而不能是绝对地址。如下图所示:

image.png

// ./webpack.config.js
module.exports = {
  // ...
  devServer: {
    proxy: {
      '/test': {
        target: 'https://abc.com/',//指向的要请求的服务器地址,如‘https://’
        changeOrigin: true, //需要修改指向地址,设为true
        onProxyReq: function(proxyReq) {
          proxyReq.removeHeader('origin')
        },//根据函数运行,将header的origin去掉
        secure: false, // HTTPS的证书忽略
        pathRewrite: {'^/test' : '/'} //这步也很重要,因为是通过识别'/test'来指向目标服务器的,所以转发时需要将‘/test’改为/来取消test的影响
      },
    }
  }
}

完成以后,打开命令行终端,运行 webpack-dev-server。然后打开浏览器,这里我们直接尝试请求 ,就能得到想要的数据了。 因为这个地址已经被代理到了ABC的数据接口。此时,我们就可以回到代码中使用代理后的本地同源地址去请求后端接口,而不必担心出现跨域问题了。

感谢

谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。

我是南瓜小神(✿◡‿◡),如果觉得写得可以的话,请点个赞吧❤。