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,传入一些假的 exports
、module
等方法,例如:github
很是容易看出来,这种方法最大的问题是使用了假的 require
方法。这里直接传入 Node.js 提供的 require
方法也是不行的,由于 require
可能写相对路径或者包名称,而咱们传入的 require
方法是以当前文件来查找的,会找不到对应的依赖。web
最近看 webpack 的 mini-css-extract-plugin 插件时,发现这个插件为了提取 css-loader 输出的样式,实现了一个 evalModuleCode
函数,这个函数的位置在这里:github.com/webpack-con…,内容是这样的:api
使用起来大概是这样的:babel
若是执行一下,咱们就能够拿到代码中输出的内容了:
咱们来看下这个函数如何实现的:
module
模块。NativeModule
实例。paths
和 filename
。这个 module
模块是一个 Node.js 提供的模块,文档地址在这里:nodejs.org/api/modules…,注意这个跟咱们直接使用的 module.exports
并非同一个(module.exports
中的 module
是 NativeModule
的一个实例)。
可是咱们若是看这个文档,会发现文档中的内容只有很是有限的几个方法,并无说起到上面的任何操做😓。
因而咱们只能看代码了。
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
方法是如何实现的: