【问题标题】:Is there a way to hook into Webpack's AST to make it recognize a new module format?有没有办法连接到 Webpack 的 AST 以使其识别新的模块格式?
【发布时间】:2017-02-15 18:31:59
【问题描述】:

短版:

我们如何处理最终输出包的 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,dependencies参数是导入名称到模块路径的映射而不是模块路径数组,模块主体函数接收import对象所有请求的导入,而不是每个请求的导入一个参数。

上面和下面AMD格式的类似,输出一样:

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

什么是挂钩到 Webpack 以使 Webpack 能够理解文件(能够知道文件具有哪些依赖项)以最终构建包含以 module() 格式编写的文件的包的推荐方法是什么?

我已经尝试过一种方法:我制作了一个自定义加载器,它以字符串的形式接收文件的源,解析它并创建和 AST,转换 AST,然后以 Webpack 理解的 AMD define() 格式输出代码.

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

【问题讨论】:

    标签: javascript node.js webpack


    【解决方案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

    我用 babel 在大约 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")]
              }
            },
          }
        ]
      }
    }
    

    【讨论】:

      【解决方案2】:

      我想你会发现在依赖解析的时候用到了loader。

      基本上,解析器需要源代码才能完成其工作。因此,在当前解析阶段遇到的任何 import/require 语句(依赖项)都需要:得到解决并且: b.在解析之前加载。如果您连接到增强解析包的“解析步骤”,您可以 console.log 注销解析器转换的状态转换,通常最终会触发“创建模块”插件。

      挂钩到“解决步骤”:

      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);
          }
      }
      

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2021-04-17
        • 2018-06-20
        • 1970-01-01
        • 1970-01-01
        • 2021-03-27
        • 1970-01-01
        • 2018-11-20
        • 2022-11-11
        • 1970-01-01
        相关资源
        最近更新 更多