Loading...
墨滴

Phil

2021/05/11  阅读:35  主题:前端之巅同款

webpack

webpack

webpack 核心概念

  • entry

指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

bundle: 经过Webpack 流程解析编译后最终输出的⽂件

  • output

告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。

  • loader

webpack 自身只理解 JavaScript文件。

loader 让 webpack 能够去处理那些非 JavaScript 文件。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

  • plugins

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

  • mode

development 或 production。 通过选择相应的模式,启动webpack内置的优化。

webpack 默认配置

webpack默认使用webpack.config.js作为配置文件,我们可以重写这个文件进行个性化配置

执行webpack命令时, 默认使用webpack.config.js, 通过--config 【webpackconfig.js】 来指定相应的配置文件

npx webpack

npx webpack --config [webpackconfig.js]

entry

支持单入口和多入口

  • 单入口
{
    entry:"./src/index.js"
}

上面为简写, 具体为:

{
    entry: {
        main"./src/index.js"
    }
}
  • 多入口
{
    entry: {
        index:"./src/index.js",
        login:"./src/login.js"
    }
}

output

{
    output: {
        filename'main.js',
        path: path.join(__dirname, './dist')
    }
}

多入口处理

{
    output: {
        filename'[name][chunkhash:8].js',
        path: path.join(__dirname, './dist')
    }
}

[name] 为占位符,即entry对象形式中的key。

[chunkhash:8] 为hash 处理后前8位。

mode

  • development

  • production

    对于项目而言, 在开发环境和生产环境上, 我们希望优化的效果是不一致的。 比如说,在生产环境上, 我们希望项目尽可能的压缩, 而在开发环境上, 我们希望项目代码能保持一致, 这样出现错误容易定位。

    所以,webpack会使用内置函数分别来处理开发环境和生产环境优化。

不同模式webpack使用的内置函数优化
不同模式webpack使用的内置函数优化

参考官网

loader

示例

  • 使用css-loader 来解析css文件
module: {
    rules: [{
        test/\.css$/,
        use"style-loader"
    }]
}

plugin

  plugins: [
    new HtmlWebpackPlugin({template'./src/index.html'})
  ]

使用HtmlWebpackPlugin插件可以在我们打包生成dist文件时, 自动根据我们的template模板来生成index.html文件。不需要我们手动创建。

编写一个loader

官网指导编写一个loader

  • 示例: 编写替换字符串loader

/src/index.js

console.log('hello webpack')

/loaders/replaceStringLoader.js

module.exports = function(source{
    return source.replace('webpack','world!')
}

/webpack.config.js

const path = require("path")

module.exports = {
  entry"./src/index.js",
  output: {
    filename"main.js",
    path: path.join(__dirname, "./dist"),
  },
  mode'production',
  module: {
    rules: [
      {
        test/\.js$/,
        use: path.resolve(__dirname, "./loaders/replaceStringLoader.js"),
      },
    ],
  },
}

运行npx webpack 后, 观察dist下的main.js代码

结果 webpack 字符串变成来world!

console.log("hello world!");

给loader配置参数

修改/webpack.config.js, 增加一个options

...
    rules: [
      {
        test/\.js$/,
        use: [{
            loader: path.resolve(__dirname, "./loaders/replaceStringLoader.js"),
            options: {
                name"option world!"
            }
        }],
      },
    ],
...

修改/loaders/replaceStringLoader.js, 解析配置的options, 使用loader-utils工具包帮助解析

const loaderUtils = require('loader-utils')

module.exports = function(source{
    const options = loaderUtils.getOptions(this)
    return source.replace('webpack', options.name)
}

结果:

console.log("hello option world!");

通过callback来替换return

this.callback 格式

this.callback(
 err: Error | null,
 content: string | Buffer,
 sourceMap?: SourceMap,
 meta?: any
);

修改/loaders/replaceStringLoader.js

const loaderUtils = require('loader-utils')

module.exports = function(source{
    const options = loaderUtils.getOptions(this)
    const result = source.replace('webpack', options.name)
    this.callback(null, result)
}

运行, 修改和之前return形式的保持一致

通过this.async来处理异步代码

注意this.async的使用

修改/loaders/replaceStringLoader.js

const loaderUtils = require('loader-utils')

module.exports = function(source{
    const options = loaderUtils.getOptions(this)
    // this.callback(null, result)
    const callback = this.async()
    setTimeout(() => {
        const result = source.replace('webpack', options.name)
        callback(null, result)
    }, 1000)
}

如果我们直接return result 会报错, 通过this.async才能处理异步代码

const loaderUtils = require('loader-utils')

module.exports = function(source{
  const options = loaderUtils.getOptions(this)
  // this.callback(null, result)
  // const callback = this.async()
  setTimeout(() => {
      const result = source.replace('webpack', options.name)
      return result
      // callback(null, result)
  }, 1000)
}

报错信息

错误信息
错误信息
  • 使用多个loader

顺序: ⾃下⽽上,⾃右到左

示例

{
  test/\.js/,
  use: [
    'bar-loader',
    'foo-loader'
  ]
}

自右到左

执行顺序: foo-loader 被传入原始资源,bar-loader 将接收 foo-loader 的产出

  • loader 路径问题

在前面的使用中, 都是通过

path.resolve(__dirname, "./loaders/replaceStringLoader.js")

来引入自定义的loader, 这种方式比较繁琐。

可以通过webpack.config.js 来配置loader路径。

resolveLoader: {
    modules: ["node_modules""./loaders"],
},

默认使用node_modules下的loader,未找到,再去 ./loaders下查找

修改webpack.config.js

...
resolveLoader: {
    modules: ["node_modules""./loaders"],
  },
  module: {
    rules: [
      {
        test/\.js$/,
        use: [
          {
            loader"replaceStringLoader",
            // loader: path.resolve(__dirname, "./loaders/replaceStringLoader.js"),
            options: {
              name"option world!",
            },
          },
        ],
      },
    ],
  },
...

编写一个plugin

官网指导编写一个plugin 中文官网的例子没有更新,请参考英文官网。

FileListPlugin 示例

  • FileListPlugin 基本结构
class FileListPlugin {
    constructor(options) {

    }

    apply(compiler) {
        compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
                console.log('this is custom plugin')
                callback()
        })
    }
}

module.exports = FileListPlugin

插件的核心方法为apply, 接受一个compiler对象。

compiler 对象包含很多hook(钩子)函数, 在这里使用emit(即在⽣成⽂件到output⽬录之前执⾏)函数, 使用tapAsync(异步处理, 同步处理为tap)将插件执行放到webpack执行过程中。

tapAsync 第一个参数通常我们命名为插件的名字, 第二个参数会一个函数, 接受compilation对象和callback,执行完后要调用callback。

配置webpack.config.js

const path = require("path")
const FileListPlugin = require("./plugins/fileListPlugin")

module.exports = {
  entry"./src/index.js",
  output: {
    filename"main.js",
    path: path.join(__dirname, "./dist"),
  },
  mode"production",
  resolveLoader: {
    modules: ["node_modules""./loaders"],
  },
  module: {
    rules: [
      {
        test/\.js$/,
        use: [
          {
            loader"replaceStringLoader",
            // loader: path.resolve(__dirname, "./loaders/replaceStringLoader.js"),
            options: {
              name"option world!",
            },
          },
        ],
      },
    ],
  },
  plugins: [
      new FileListPlugin()
  ]
}

执行结果

执行结果
执行结果
  • 完善FileListPlugin功能 fileListPlugin.js
class FileListPlugin {
  constructor(options) {
    // 获取插件配置项
    this.filename = options && options.filename
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync("FileListPlugin", (compilation, callback) => {
      // 通过 compilation.assets 获取⽂件数量
      let len = Object.keys(compilation.assets).length
      // 添加统计信息
      let content = `# ${len} file${len > 1 ? "s" : ""} emitted \n\n`
      // 通过 compilation.assets 获取⽂件名列表
      for (let filename in compilation.assets) {
        content += `- ${filename}\n`
      }

      const filename = this.filename || "fileList.md"
      // 往 compilation.assets 中添加清单⽂件
      compilation.assets[filename] = {
        // 写⼊新⽂件的内容
        sourcefunction ({
          return content
        },
        // 新⽂件⼤⼩(给 webapck 输出展示⽤)
        sizefunction ({
          return content.length
        },
      }
      callback()
    })
  }
}

module.exports = FileListPlugin

修改webpack.config.js

...
  plugins: [
      new FileListPlugin({
          filename'FileList.md'
      })
  ]
...
结果
结果
dist 目录变化
dist 目录变化

参考文章

webpack 官网

Phil

2021/05/11  阅读:35  主题:前端之巅同款

作者介绍

Phil