Problem
Hi, I'm trying to write a plugin and use ast for parsing the file. But I can not make changes to code. For example this code does not change div to label. What is correct way to change ast?
apply(compiler) {
compiler.hooks.normalModuleFactory.tap('MyPlugin', (factory) => {
factory.hooks.parser.for('javascript/auto').tap('MyPlugin', (parser, options) => {
parser.hooks.program.tap('MyPlugin', (ast, comments) => {
if (parser.state &&
parser.state.module &&
parser.state.module.resource.indexOf('node_modules') === -1) {
if (parser.state.module.resource.endsWith('tsx')) {
var g = ast.body.filter(n=> n.type === 'ExportNamedDeclaration');
for (let a of g) {
var decl = a.declaration.declarations;
if (decl && decl[0]) {
decl[0].init.body.body[0].argument.arguments[0].raw = 'label';
decl[0].init.body.body[0].argument.arguments[0].value = 'label';
}
}
}
}
});
});
});
}`
I just need to change div to label in return block or add data-attr to div. I do not want to use regular expressions and replace all file content, I want to make it with ast. MyComponent.tsx can be as following:
import * as React from 'react';
import * as style from './MyComponent.css';
export const MyComponent = (props) => {
return (
<div className={style['test']}>bla bla</div>
);
};
May be somebody can provide small example to change something with abstract syntax tree in webpack plugin.
As mentioned in the comments and pointed to in the webpack code in this question, webpack ignores attempts to change the parser AST within a tap.
Plugins are expected to use the ast as a read-only map to build new dependencies in the dependency graph. (This is less fragile about ordering and parallel executions as multiple plugins can add to the dependency graph without invalidating each other by changing the reference AST.)
Based on the i18n-lang and Define Plugins, an example of using the ast to build a dependency for your transformation could look like:
"use strict";
const pluginName = 'MyPlugin';
const NullFactory = require('webpack/lib/NullFactory');
const ConstDependency = require("webpack/lib/dependencies/ConstDependency");
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"MyPlugin",
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(
ConstDependency,
new ConstDependency.Template()
);
});
compiler.hooks.normalModuleFactory.tap('MyPlugin', (factory) => {
factory.hooks.parser.for('javascript/auto').tap('MyPlugin', (parser, options) => {
parser.hooks.program.tap('MyPlugin', (ast, comments) => {
if (parser.state &&
parser.state.module &&
parser.state.module.resource.indexOf('node_modules') === -1) {
if (parser.state.module.resource.endsWith('tsx')) {
var g = ast.body.map(n => {
try {
let {expression:{left:{property:{name:my}}, right:{body:{body:[{argument:{arguments:[div]}}]}}}} = n
return my == 'MyComponent' && div.value == 'div' ? div: false
} catch(e) {
return false;
}
}).filter(e=>e);
for (let div of g) {
let dep = new ConstDependency(JSON.stringify('label'), div.range);
dep.loc = div.loc;
parser.state.current.addDependency(dep);
}
}
}
});
});
});
}
}
module.exports= MyPlugin;
Where the ast
provided by parser may vary quite a bit with different loaders and changes to the input.