A workspace with an example tech you how to create babel plugin

how-to-implement-babel-optional-chain

This is an example about how to implement babel-optional-chain plugin.Also,you can use it as simple babel plugin workspace.

Quick start

  • step 1
1
yarn or npm install
  • step2 entry config
    config your entry in babel.config.js like:
1
2
3
module.exports = {
plugins: [["./plugin-with-option-loose.js", { loose: true }]],
};
  • step3
1
npm run watch
  • step4

modify code in input.js and save,you will see genrated code in output.js

If you wanna konw about option-chain implement progress,keep page moving up

The simplest implementation

c1.png

c2.png

c3.png
Although simple, it achieves the simplest effect,But add another attribute and kneel immediately
c4.png

c5.png

Repair normal ‘.’ symbol also triggers “?.” Conversion problem

Obviously, b.c does not need to judge b, so we continue to improve it

c9.png
Try again, it ‘s not bad

c10.png

But if you add an optional value
c11.png
There will be problems
c12.png

Store calculation results

a==null?undefined:a.bIt was repeated twice,So let’s optimize it again, as shown below
c13.png
First,we create a unique variable in the current scope and injects it into the scope. Then, while performing the ternary operation, we assign the result and store the operation result

c14.png

You think it’s over, but there’s another question
c15.png

avoid ‘undefined’ being declared,like ‘let undefined = 2’

In order to prevent a big brother from assigning an undefined value, we need to use Babel’s API to build the undefined value in the scope
c17.png
here is the result below
c18.png

add ‘&&’ form

Before we finish, let’s play again. Babel’s afferent option constructs another form
c20.png

Parameter incoming
c21.png
result outing
c22.png

c23.png
Just a simple plug-in.

Complete code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
module.exports = function plugin(
{ types: t, template },
{ loose = false } = {}
) {
const buildLoose = template.expression(`
(%%tmp%% = %%obj%%) && %%expr%%
`);

return {
name: "optional-chaining",
visitor: {
OptionalMemberExpression(path) {
let { object, property } = path.node;
if (path.node.optional) {
let tmp = path.scope.generateUidIdentifier("obj");
path.scope.push({ id: tmp });
let undef = path.scope.buildUndefinedNode();
if (loose) {
path.replaceWith(
buildLoose({
tmp,
obj: object,
expr: template.expression.ast`${tmp}.${property}`,
})
);
return;
}
path.replaceWith(
template.expression
.ast`(${tmp} =${object})== null?${undef}:${tmp}.${property}`
);
} else {
path.replaceWith(template.expression.ast`${object}.${property}`);
}
},
},
};
};

Another implementation

This is the original author’s implementation, but it is more complex to understand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
module.exports = function plugin(
{ types: t, template },
{ loose = false } = {}
) {
const buildLoose = template.expression(`
(%%tmp%% = %%obj%%) && %%expr%%
`);

return {
name: "optional-chaining",
visitor: {
OptionalMemberExpression(path) {
//#region 另一种实现方式
let originalPath = path;
let tmp = path.scope.generateUidIdentifier("obj");
path.scope.push({ id: tmp });

while (!path.node.optional) {
path = path.get("object");
}

let { object } = path.node;

let memberExpr = tmp;
do {
memberExpr = t.memberExpression(
memberExpr,
path.node.property,
path.node.computed
);

if (path === originalPath) {
break;
}

path = path.parentPath;
} while (true);
if (loose) {
path.replaceWith(
buildLoose({
tmp,
obj: object,
expr: memberExpr,
})
);
} else {
let undef = path.scope.buildUndefinedNode();

originalPath.replaceWith(
template.expression.ast`
(${tmp} = ${object}) == null
? ${undef}
: ${memberExpr}
`
);
}
},
//#endregion
},
};
};

Github repo address

Welcome to star.

reference resources

video address

origin author’s github

查看评论