利用 Node.js 中的 Module 类,执行字符串形式代码的方法

mini-css-extract-plugin 中有一个直接执行字符串形式的 commonjs 代码,在编译阶段获取 css-loader 产物的方法。学习了一下这个方法所作的事情。css

在写编译工具的时候,咱们常常须要提取文件中的特定内容来完成后续的编译。举例来讲假如咱们正在写一个编译插件,须要提取 .vue 文件中 data 的初始值,若是想要用 babel 之类的工具来解析文件经过 AST 来获取,会是很麻烦的一件事。html

若是 .vue 文件写的还比较简单,例如这种:vue

或许比较好处理,咱们只须要提取 data 函数中 return 的对象就能够了。可是只要稍微复杂一点,咱们就无能为力了,例如 data 初始值中若是执行了一个函数:node

因为写法很是灵活,想要继续经过静态分析来找到内容是很是困难的了。这时候一个更好的方法,就是在编译阶段直接执行一下这段 JS 文件,同时执行一下 data 方法,那么就能够直接拿到 data 的返回值了。如今问题来了,如何能在编译阶段执行一下这段代码呢?webpack

简陋方法

以前我一直的作法是编译成 commonjs 文件:git

以后 new 一个 Function,传入一些假的 exportsmodule 等方法,例如:github

很是容易看出来,这种方法最大的问题是使用了假的 require 方法。这里直接传入 Node.js 提供的 require 方法也是不行的,由于 require 可能写相对路径或者包名称,而咱们传入的 require 方法是以当前文件来查找的,会找不到对应的依赖。web

mini-css-extract-plugin 插件所用的方法

最近看 webpack 的 mini-css-extract-plugin 插件时,发现这个插件为了提取 css-loader 输出的样式,实现了一个 evalModuleCode 函数,这个函数的位置在这里:github.com/webpack-con…,内容是这样的:api

使用起来大概是这样的:babel

若是执行一下,咱们就能够拿到代码中输出的内容了:

咱们来看下这个函数如何实现的:

  • 首先引入了 module 模块。
  • 以后建立了一个 NativeModule 实例。
  • 接下来设置了模块的 pathsfilename
  • 最后执行了 _compile 方法。

这个 module 模块是一个 Node.js 提供的模块,文档地址在这里:nodejs.org/api/modules…,注意这个跟咱们直接使用的 module.exports 并非同一个(module.exports 中的 moduleNativeModule 的一个实例)。

可是咱们若是看这个文档,会发现文档中的内容只有很是有限的几个方法,并无说起到上面的任何操做😓。

因而咱们只能看代码了。

Node.js 的 module 类

require('module') 引入的文件在 Node.js 源文件中的 lib/module.js。这个文件又 exports 出了 internal/modules/cjs/loader 文件中的内容。

咱们对照着 evalModuleCode 的实现,一步一步在 internal/modules/cjs/loader 文件中找对应的内容:

首先是构造函数,Module 的构造函数没有作什么特殊的内容,只是设置了一些属性。其中 updateChildren,是为了设置不一样 module 以前的对应关系,这里其实咱们并无用到:

以后是 _nodeModulePaths 方法,这个方法有 window 和 posix 两个版本,区别只是对路径的处理方式不一样,所作的工做其实就是从当前目录开始,向上遍历出全部的 node_modules 文件夹路径,用于在这些目录中查找对应的包 :

例如当前咱们的路径是 /home/work/code/tmp,那么解析出的 paths 路径就是这样的:

再接下来是设置了 filename 属性,以后就是调用 _compile 方法了:

其中重点关注上面这两句,第一句 wrapSafe 作的事情是将 JS 源代码,使用函数包装一下,参数增长 requires、module 这些,相似这样:

以后在 V8 中编译执行,执行结果就获得了包装后的函数。

再执行这个方法,就能够获得最终输出的结果了:

这里比较有意思的是 module 参数,能够看到传入的是 this,而 this 就是一个 Module 实例。

执行过程大概就是这样了,其实 咱们直接用的 require 方法,也是在 Module 上定义的,evalModuleCode 是将 require 的过程简化了,感兴趣的同窗能够本身看一下 require 方法是如何实现的:

相关文章
相关标签/搜索