17.【青训营】Webpack知识体系

87 阅读4分钟

Webpack

Webpack本质上是一种前端资源编译、打包工具

webapck的核心流程

  1. 入口处理: 从entry入口文件开始,启动编译流程
  2. 依赖解析: 从entry入口文件开始,根据requireimport等语句找到依赖资源。
  3. 资源解析: 根据module配置,调用资源转译器,将png、css等非标准JS资源转译为JS资源。
  4. 资源合并打包: 将转译后的资源内容合并打包为可直接在浏览器中运行的JS文件。

安装及简单使用

npm i webpack webpack-cli -D

安装过程如上述命令,然后在根目录下手动创建一个webpack.config.js然后只需要三个属性的配置即可完成wepack的运行。

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist')
  },
  mode: 'production'
}

entry定义了打包入口,即指定了入口文件;output定义了输出文件的路径和文件名;mode定义了模式,production为生产模式,development为开发模式,这两个模式还是会有一些区别的:主要是两种模式会自动使用一些插件继续配置和优化。

  • development打包后,一些没有依赖的方法、变量、文件会保留,production则会移除。

  • production打包后,代码会进行压缩,比development的文件小;会启用UglifyJsPlugin插件(移除未使用的内容和文件压缩)。

npx webpack

执行上述命令,即可执行webpack进行打包。在此使用npx的目的主要是其可以执行依赖包中的命令,安装完成自动运行,让npm包中的命令行工具和其他可执行文件在使用上变得更加简单。

处理CSS

安装依赖

npm i css-loader style-loader -D

添加module处理css文件

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 匹配css结尾的文件
        use: ['style-loader', 'css-loader'] // 使用这两个loader解析css
      }
    ]
  },
  mode: 'production'
}

对于入口文件index.js来说,我们只需要引入相关css文件即可,以下两种方式均可。

import 'index.css'
import styles from 'index.css'

Loader

Loader的作用

Webpack Loader最核心的功能是实现了内容转译器,将各式各样的网络资源转为标准的JS内容。

Loader 的结构

代码层面,Loader是一个函数,结构如下:

module.exports = function(source, sourceMap?, data?) {
  // source 为 loader 的输入,可能是文件内容,也可能是上一个 loader 处理结果
  return source;
};

Loader函数接收三个参数,分别是:

  • source:资源输入,对于第一个执行的loader,source为资源文件的内容,对于非第一个执行的loader,为前一个loader的执行结果。
  • sourceMap:可选参数,代码中的sourcemap结构
  • data: 可选参数,为其他需要在Loader链中传递的信息,比如posthtml、posthtml-loader就会通过这个参数传递参数的AST对象。

Loader实例

假设有个需求,我们需要对js文件中的变量或常量a进行相乘操作,并打印出来。与之相乘的乘数由用户在配置这个Loader的时候自定义。

webpack.config.js代码如下:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, './dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [{
          // 需要使用path,为loader属性指定我们写的loader的路径
          loader: path.resolve(__dirname, './src/loader.js'),
          // 用户自定义的乘数,此处设置为2
          options: {times: 2}
        }],
      }
    ]
  },
  mode: 'development'
}

其中,mode建议设置为development模式,否则,若为production模式,会自动进行代码简化,产生的结果不明显。本人一开始使用的就是production,结果产生的index.js文件为空,是因为我实现功能的时候,字符串拼接有问题,导致Loader函数中return后的结果不能被JS正常解析,优化后,所以为空了。

Loader函数如下:

module.exports = function (source, sourceMap, data) {
  source = source.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\2029")
  // 获取用户设置的options对象
  const options = this.query
  // 获取用户设置的options对象中的times,也就是乘数
  const times = options.times
  // 返回实现功能的字符串
  return `${source} console.log(a*${times})`
}

函数内容部分的第一行中,\u2028的字符为行分隔符,会被浏览器理解为换行,而在Javascript的字符串表达式中是不允许换行的,从而导致错误。同理,\u2029的字符为段分隔符,不处理也会报错。

src目录下的index.js代码如下:

const a = 1;

使用npx weboack命令运行后,打开dist目录下的index.js,即可查看结果。

image.png 当然,Loader可以实现的功能远不止如此,还可以支持链式执行异步执行,以及normalpitch两种模式。在此只是简单的介绍其使用方法。

将打包后的JS文件插入到指定的html文件中

安装依赖

npm i html-webpack-plugin -D

把html-webpack-plugin插件添加plguins中

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist')
  },
  // 核心代码
  plugins: [
    new HTMLWebpackPlugin({
      // 指定html的路径
      template: './src/index.html'
    })
  ],
  mode: 'production'
}

Plugin

Plugin的作用

Webpack 通过 Plugin 机制让其更加灵活。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

插件架构的精髓,就是对拓展开放,对修改封闭。

Plugin的结构

代码层面,Plugin是一个类,结构如下:

class BasicPlugin{
  // 在构造函数中获取用户给该插件传入的配置
  constructor(options){
  }
  
  // Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler){
    compiler.plugin('compilation',function(compilation) {
    })
  }
}

// 导出 Plugin
module.exports = BasicPlugin;

使用方法

const BasicPlugin = require('./BasicPlugin.js');

module.export = {
  ...(此处省略很多代码)
  plugins:[
    new BasicPlugin(options),
  ]
  ...(此处省略很多代码)
}

Webpack 启动后,在读取配置的过程中会先执行 new BasicPlugin(options) 初始化一个 BasicPlugin 获得其实例。
在初始化 compiler 对象后,再调用 basicPlugin.apply(compiler) 给插件实例传入 compiler 对象。
插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。

可以看得出来,Plugin的核心就是在合适的钩子中执行一些代码,以产生期望的结果。用户可以在webpack指定的钩子中(即用户自己指定一些函数的调用时机),利用webpack提供的上下文环境(一些可能用到的变量或函数等),满足用户的编程需求。

webpack提供的主要钩子及其发生顺序如下图所示。

image.png

需要说明的是,使用钩子时是可以进行异步编程和Promise编程的。

参考链接