有没有办法挂钩Webpack的AST以使其识别新的模块格式?

时间:2017-02-15 18:32:00

标签: javascript node.js webpack

简短版:

我们如何操作最终输出包的AST,以及加载器内文件的AST?在这两种情况下,我都想操纵现有的AST,而不是我正在做的是解析源并创建一个新的AST。我做的很慢,我知道Webpack必须已经制作了一个AST,所以我想避免重复工作。

长版:

例如,假设我有一堆文件以类似于(但不完全)AMD模块的格式编写:

module({
  Foo: '/path/to/Foo',
  Bar: '/path/to/Bar',
  Baz: '/path/to/Baz',
}, function(imports) {
  console.log(imports) // {Foo:..., Bar:... Baz:...}
})

不同之处在于它被称为module而不是define,依赖关系参数是导入名称到模块路径而不是模块路径数组的映射,模块主体函数接收到import对象包含所有请求的导入,而不是每个请求导入的一个参数。

以上类似于AMD格式的以下内容,具有相同的输出:

define([
  '/path/to/Foo',
  '/path/to/Bar',
  '/path/to/Baz',
], function(Foo, Bar, Baz) {
  console.log({Foo, Bar, Baz}) // {Foo:..., Bar:... Baz:...}
})

为了最终构建一个包含在此{{1}中编写的文件的捆绑包,建议使用Webpack来使Webpack能够理解文件(能够知道文件具有哪些依赖关系)的方法是什么?格式?

我已经尝试过一种方法:我创建了一个自定义加载器,它接收文件的源作为字符串,解析它并创建和AST,转换AST,然后输出AMD中的代码{{ 1}}格式,Webpack理解。

然而,我觉得这很慢,因为如果有很多文件并且它们很大,那么从每个文件解析并制作AST似乎是多余的,因为我打赌Webpack已经开始这样做了。有没有办法从Webpack获取AST并在Webpack想要扫描它的依赖项之前对其进行转换,这样我就可以将AST转换为AMD格式(或者任何公认的格式),这样Webpack就可以了使用该文件?还有另一种方法吗?

2 个答案:

答案 0 :(得分:2)

我认为您会发现在依赖项解析期间使用了加载器。

解析器基本上需要源代码才能完成其工作。因此,在当前解析阶段遇到的任何import / require语句(依赖项)都需要:a。得到解决并且:b。在可以解析之前加载。如果你挂钩进入enhanced-resolve软件包的“resolve-step”,你可以在console.log中找出解析器转换过来的状态转换,通常最终会触发“create-module”插件。

陷入“解决步骤”:

compiler.plugin('after-resolvers', (compiler) => {
    compiler.resolvers.normal.plugin('resolve-step', function (type, request){
        console.log("resolve-step type:["+type+"], 
            path:["+request.path+"], request:["+request.request+"]");   
        });
});

进入“创建模块”:

compiler.plugin('compilation', (compilation, params) => {
    params.normalModuleFactory.plugin("create-module", (data) => {
        console.log('create-module: raw-request: '+data.rawRequest);
    }
}

希望这有帮助。

答案 1 :(得分:1)

我正在寻找类似的东西,想要操纵ast,但没有示例或有用的文档。

谷歌搜索它只是浪费了我2小时的时间,但是用了一半完成且难以理解的文档,我确实提出了这个(虽然不起作用):

var acorn = require("acorn-dynamic-import").default;

function MyPlugin(options) {
  // Configure your plugin with options...
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin("compilation", function(compilation, data) {
    data.normalModuleFactory.plugin(
      "parser"
      , function(parser, options) {
        parser.plugin(
          [
            "statement if"
          ]
          ,(node)=>
            Object.assign(
              node
              ,{
                test:{
                  type:"Literal",
                  start: node.test.start,
                  end: node.test.start+4,
                  loc: {
                    start: {
                      line: 7,
                      column: 3
                    },
                    end: {
                      line: 7,
                      column: node.test.loc.start.column+4
                    }
                  },
                  range: [
                    node.test.range,
                    node.test.range+4
                  ],
                  value: true,
                  raw: "true"
                }
              }
            )
        ); 
      });
  });
};

module.exports = MyPlugin;

这会让我成为if语句的节点,对于Parser.js可以查看的其他类型的节点。

我尝试返回另一个节点,但创建一个节点需要做很多工作,似乎没有一种简单的方法可以做到这一点。

为什么还要在webpack中尝试这样做呢? webpack贡献者已经制作了一个很好的产品,但是没有人知道它是如何工作的,因为它的许多功能都没有文档。

作为一个babel插件,你最好这样做,这个插件编写得很好且可以理解documentation

我和巴贝尔在约20分钟内完成了这项工作:

module.exports = function ({ types: t }) {
    return {
        visitor: {
            IfStatement(path, file) {
        path.node.test = {
          "type": "BooleanLiteral",
          "start": path.node.test.start,
          "end": path.node.test.start+4,
          "loc": {
            "start": {
              "line": 7,
              "column": path.node.test.loc.start.column
            },
            "end": {
              "line": 7,
              "column": path.node.test.loc.start.column+4
            }
          },
          "value": true
        }
        // debugger;
                // path.unshiftContainer('body', t.expressionStatement(t.stringLiteral('use helloworld')));
      }
        }
  };
};

在webpack.config.js中:

const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: "./src",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].chunk.js"
  },
  module:{
    rules: [
      {
        test: /\.html$/,
        use: [{ loader: './plugin/templateLoader' }]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env'],
            plugins: [require("./plugin/myBabelPlugin")]
          }
        },
      }
    ]
  }
}