webpack 自定义模块-webpack原理-如何实现代码打包

2023-08-21 0 3,671 百度已收录

这是第 122 篇纯粹的原创文章。 如果想获取更多原创好文章,请搜索公众号并关注我们~本文首发于正彩云后端博客:Webpack原理-如何实现代码打包

前言

作为后端“攻城狮”,Webpack 是再熟悉不过了。 Webpack 可以做的事情太多了。 它可以将所有资源(包括JS、TS、JSX、图像、字体和CSS等)封装在一个关系中,您可以根据需求引用依赖关系来使用资源。 Webpack 在翻译多个后端文件资源和分析复杂的模块依赖关系方面做得很好,但我们也可以自定义加载器并自由加载我们自己的资源。 Webpack是如何实现打包的? 我们昨天过来看看。

如果我们想要了解Webpack打包的原理,我们需要提前了解两个知识点

1. 需要什么?

说到require,首先想到的可能是import,import是es6的一句标准,

–require是运行时调用,因此理论上require可以在代码中的任何地方使用;

--import是在编译时调用的,所以必须放在文件的开头;

webpack 自定义模块-webpack原理-如何实现代码打包

当我们使用Webpack编译时,我们会使用babel将import翻译为require。 在 CommonJS 中webpack 自定义模块,有一个全局方法 require(),用于加载模块。 AMD和CMD也是使用require方法来引用。

例如:

var add = require('./a.js');
add(1,2)

简单来说,虽然require是一个函数,但是引用的./a.js只是该函数的一个参数。

2.什么是出口?

这里我们可以认为exports是一个对象,可以看具体用法。

了解了require和exports之后,我们就可以开始打包了

下面我们看一下我们打包后的代码结构,可以发现打包后会出现require和exports。

并不是所有的浏览器都能执行requireexports,你必须自己实现require并exports才能保证代码的正常运行。 打包后的代码是一个自执行函数,参数有依赖信息,以及文件的代码,执行的函数体通过eval执行代码。

整体设计图如下:

第1步:编译我们的配置文件

配置文件配置了我们打包的入口条目和打包的导出输出,为之前生成的文件做准备。

const path = require("path");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "./dist"),//打包后输出的文件地址,需要绝对路径因此需要path
    filename:"main.js"
  },
  mode:"development"
  

第二步:模块分析

webpack 自定义模块-webpack原理-如何实现代码打包

总体思路:可以概括为使用fs文件读取入口文件,通过AST获取导入依赖的文件的路径。 如果依赖文件仍然存在依赖关系,那么它将继续递归,直到依赖关系被清楚地分析并维护在地图上。

详细拆解:有些人可能会有疑问为什么要使用AST。 由于 AST 本身就有这个功能,它的 ImportDeclaration 可以帮助我们快速过滤掉 import 句型。 其实正则匹配也是可以的。 虽然文件读取后只是一个字符串,但通过 Compiled 强大的正则表达式来获取文件依赖路径,还不够优雅。

Step1:新建index.js,a.js,b.js依赖如下

索引.js 文件

import { str } from "./a.js";
console.log(`${str} Webpack`)

一个.js文件

import { b} from "./b.js"
export const str = "hello"

b.js 文件

export const b="bbb"

步骤2:编译Webpack

模块分析:使用AST的@babel/parser将从文件中读取的字符串转换成AST树,@babel/traverse分析句型,使用ImportDeclaration过滤导入来查找文件依赖关系。

    const content = fs.readFileSync(entryFile, "utf-8");
    const ast = parser.parse(content, { sourceType: "module" });
  
    const dirname = path.dirname(entryFile);
    const dependents = {};
    traverse(ast, {
      ImportDeclaration({ node }) {
        // 过滤出import
        const newPathName = "./" + path.join(dirname, node.source.value);
        dependents[node.source.value] = newPathName;
      }
    })
    const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"]
    })
    return {
      entryFile,
      dependents,
      code
    }
    

结果如下:

使用递归或循环来一一分析导入文件的依赖关系。 请注意,我们使用 for 循环来分析所有依赖关系。 循环之所以能够分析所有依赖关系webpack 自定义模块,是为了注意到当存在依赖关系时,模块的宽度会发生变化。 module.push新的依赖项,modules.length会改变。

 for (let i = 0; i < this.modules.length; i++) {
      const item = this.modules[i];
      const { dependents } = item;
      if (dependents) {
        for (let j in dependents) {
​
          this.modules.push(this.parse(dependents[j]));
        }
​
      }
    }

webpack 自定义模块-webpack原理-如何实现代码打包

步骤3:编写WebpackBootstrap函数+生成输出文件

编译WebpackBootstrap函数:这里我们需要做的第一件事是WebpackBootstrap函数。 编译完成后,我们源码的导入会被解析为require。 由于浏览器不知道 require,所以我们先声明它。 虽然 require 是一门技巧,但是在函数的编译时,我们也需要注意作用域隔离,避免变量污染。 我们代码中的导出也需要我们声明它,以确保代码执行时导出已经存在。

生成输出文件:我们已经在配置文件中写入了生成文件的地址,然后使用 fs.writeFileSync 将其写入到输出文件夹中。

  file(code) {
    const filePath = path.join(this.output.path, this.output.filename)
    const newCode = JSON.stringify(code);
    // 生成bundle文件内容
    const bundle = `(function(modules){
      function require(module){
        function pathRequire(relativePath){
          return require(modules[module].dependents[relativePath])
        }
        const exports={};
        (function(require,exports,code){
          eval(code)
        })(pathRequire,exports,modules[module].code);
        return exports
      }
      require('${this.entry}')
    })(${newCode})`;
      //  WebpackBoostrap
    // 生成文件。放入dist 目录
    fs.writeFileSync(filePath,bundle,'utf-8')
  }
  

第四步:分析执行顺序

我们可以在浏览器的控制台中运行打包的结果。 如果正常工作,应该复制helloWebpack。

总结

通过上面的分析,我们应该对Webpack的大致流程有了基本的了解。 使用 AST 解析代码只是本次演示的一种方法,并不是 Webpack 的真正实现。 Webpack有自己的AST解析方法,这离不开接收模块依赖。 Webpack生态系统非常完整。 有兴趣的童鞋可以考虑以下三个问题:

一、webpack背景知识html

后端项目开发的完美工具。或者大口大口地喝。前端

主要功能有:提供前端平滑的前端分离开发环境,可以配置和解析不同的资源文件webpack 插件实现,统一打包和包交付,资源文件按需加载,网站优化等。 webpack

webpack 的主要组件有:入口/退出、编译包括加载器和插件、模型、规则等开发环境配置,webpack 本身提供了许多插件,如分析、压缩、html 等。

webpack 自定义模块-webpack原理-如何实现代码打包

联系后端一段时间开发同学应该基本了解怎么写一个加载器,一般流程比较简单,需要一些配置才能完成自定义加载器webpack 插件实现,咕噜咕噜地吃点

自定义插件有点合乎逻辑,了解一些 webpack 内部实现的源代码,后端

实现的逻辑是相关的,比如引入形式、加载时间、新编译等。

webpack 自定义模块-webpack原理-如何实现代码打包

2、webpack自定义插件开发链表

2.1 构建插件,5步应用

在函数上构建函数 扩展 Apply 方法以指定绑定到 WebPack 自己的风钩 处理 WebPack 内部实例的特定数据 函数完成后,调用 WebPack 提供的反弹

webpack 自定义模块-webpack原理-如何实现代码打包

// 自定义事件的插件函数,也能够写成class的形式,可是内部apply方法不能用 箭头函数。
function myPlugin () { } // 对 插件函数扩展 apply 方法 myPlugin.prototype.apply = function(compiler) {
// webpack提供的编译函数模板,监听webpack的钩子事件,而后会触发 compilation,和插件执行完成的回调。 compiler.plugin('webpacksEventHook', function(compilation,callback) { console.log('this is customer plugins'); callback(); }); }

2.2 webpack 插件的钩子功能与前端和后端分离

Webpack 在打包过程中也有自己的生命周期功能,webpack bar 流程也是分类的,所以有很多不同类型的插件。

webpack 自定义模块-webpack原理-如何实现代码打包

有两个重要对象:编译和编译器

编译器包含 webpack (webpack.config.js 的所有配置信息,并在启动时初始化为 webpack 的实例。编译包含当前模块,编译文件,

例如在开发环境中,当文件更改时,新的编译将得到改进。

webpack 自定义模块-webpack原理-如何实现代码打包

以下内容是 webpack 的自定义插件。

function MyPlugin(options) {
    this.options = options;
}
MyPlugin.prototype.apply = function(compiler) {
    
    console.log('开始执行插件')
    
    compiler.plugin('compile', function () {
        console.log('webpack 编译器开始编译...-----')
    })
    
    compiler.plugin('compilation', function (compilation) {
        console.log('编译器开始一个新的编译任务...-----')
        compilation.plugin('optimize', function () {
             console.log('编译器开始优化文件...')
        })
    })
    compiler.plugin('done', function () {
        console.log('打包完成......')
    })
    
};
module.exports = MyPlugin;

例如,编译的块文件中包含的路径是通过 emit 挂钩获取的。不支持使用 webpack 2 进行更高版本的 API 更改

compiler.plugin('emit', function (compilation, callback) {
  // compilation.chunks 存放全部代码块,是一个数组
  compilation.chunks.forEach(function (chunk) {
    // chunk 代码块
    // 代码块由模块组成,读取模块
    chunk.forEachModule(function (module) {
      // module 模块
      // module.fileDependencies 访问当前模块须要的依赖,便是文件路径。
      module.fileDependencies.forEach(function (filepath) {
        console.log(filepath)
      })
    })
  })
  // emit 是异步事件,AsyncSeriesHook,异步事件都须要调用callback。有点像英语里的及物动词和不及物动词
  callback();
})

2.3 多页网页包插件

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 webpack webpack 自定义模块-webpack原理-如何实现代码打包 https://www.wkzy.net/game/136789.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务