首页 > 解决方案 > Webpack 插件静态分析导出函数的使用情况?

问题描述

首先,请注意我是故意设置这个假设问题,因为这是我面临解决的现实世界问题,甚至不确定它是否可能。

假设我有一个名为 的 JavaScript 包,road-fetcher其中有一个名为 的导出函数find,我可以将 lat/lng 坐标传递到该函数中,然后返回到该点最近的道路。

所以:

import { find } from 'road-fetcher'

find(36.585940, -95.304241) // output: 'US Route 66'

假设使用我的road-fetcher应用程序的用户最终在他们的网络应用程序中使用此find功能 200-300 倍(所有这些都是可预测的,非Math.random方式)。

在内部,我的road-fetcher包正在向我的外部 API 发出网络请求。这很好,但是如果我们在运行时继续这样做,我们会在每个客户端上产生带宽和延迟成本(无论是在浏览器还是服务器中),我们不一定需要。此外,也许所有 200 个电话都在同一页面上。相反,最好在构建时生成初始值,并可能设置一些较长的 TTL 以便稍后重新验证值。

使用 Webpack 处理 JavaScript 是司空见惯的,我想知道是否可以静态分析用户对该find函数的使用以找到所有排列。

理想情况下,我想用它们的 args 编译函数调用的总列表,然后在构建步骤期间基于此发出网络请求,但甚至能够编译所有函数调用 args 的列表并将其存储在某处文件系统(不确定,withinnode_modules或 cwd),以促进单个缓存热步骤也足够了。

初步了解一下 Webpack 文档,这似乎一个起点,但我在这里的深度不够。evaluateCallExpressionMember

我很欣赏这是一个人为的例子,但它确实代表了一个非常现实的问题,我在这里试图简化它以清楚地隔离手头的问题。

标签: javascriptnode.jswebpackoptimizationstatic-analysis

解决方案


我设法编写了一个接受函数名称的 webpack 插件,以及一个将使用传递给原始函数的参数调用的回调。

如果您只想跳入代码,这里是repo

它可以工作,但有一些限制:

  1. 它仅适用于 ES 模块
  2. 如果命名导入被重命名(例如:import { log as logg } from './something'
  3. 如果传递给函数的参数是变量,则它不起作用

结果

这是我的源代码:

import { log } from './helpers/log';

// these will be logged
log(1);
log(2, 3);
log(2, "foo");
log(2, "foo", 4, "bar");
log(2, "foo", 4, "bar", [1, 2, "asd"]);
log(2, "foo", 4, "bar", [1, 2, "asd"], { foo: "bar" }, [
  { a: "asd", b: 123, c: [] },
]);

// this one will not be logged because it's using a variable
const a = [1,2,3];
log(a);

// this one will also not be logged because it's not using `log` exactly
console.log('asd');

这是 webpack 配置:

const FunctionCallPlugin = require('./webpack-plugins/FunctionCall');

module.exports = {
  plugins: [ new FunctionCallPlugin({ functionName: 'log', callback: ({ arguments: args }) => {
    console.log('`log` function was found and called with the arguments:', args);
    // you can do whatever here, make a http request, write to db, etc
  }})],
}

这是运行 webpack 时的终端输出:

➜  webpack-plugin-function-invoke git:(master) ✗ yarn webpack
yarn run v1.16.0
warning ..\package.json: No license field
$ D:\Projects\webpack-plugin-function-invoke\node_modules\.bin\webpack
`log` function was found and called with the arguments: [ 1 ]
`log` function was found and called with the arguments: [ 2, 3 ]
`log` function was found and called with the arguments: [ 2, 'foo' ]
`log` function was found and called with the arguments: [ 2, 'foo', 4, 'bar' ]
`log` function was found and called with the arguments: [ 2, 'foo', 4, 'bar', [ 1, 2, 'asd' ] ]
`log` function was found and called with the arguments: [
  2,
  'foo',
  4,
  { foo: 'bar' },
  [ { a: 'asd', b: 123, c: [] } ]
]

插件的工作原理

  1. 它遍历所有 webpack 块,以及每个块中的所有模块,以查看它们是否具有具有指定名称的任何依赖项。例如,如果您正在寻找log,它将找到任何import { log } from './somewhere'模块import log from './somewhere'
  2. 对于每个具有指定依赖关系的模块,它通过查看其收集原始源代码的所有文件路径fileDependencies
  3. fs使用模块将每个文件的源代码读取为字符串
  4. 使用 babel 解析器将源代码解析为 AST
  5. 遍历 AST,我们查看所有CallExpression使用相同指定函数名称的函数
  6. 我们收集函数调用中使用的所有参数
  7. 我们运行指定的回调,同时传递收集的参数

而已!它可能不是那么高效,但是它可以工作:)

如果您对代码感兴趣,我已将其放在GitHub 存储库中。我知道这不是您问题的完整答案,但希望对您有所帮助!


推荐阅读