1 | // Copyright 2015 Esri
|
2 | // Licensed under The MIT License(MIT);
|
3 | // you may not use this file except in compliance with the License.
|
4 | // You may obtain a copy of the License at
|
5 | // http://opensource.org/licenses/MIT
|
6 | // Unless required by applicable law or agreed to in writing, software
|
7 | // distributed under the License is distributed on an "AS IS" BASIS,
|
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
9 | // See the License for the specific language governing permissions and
|
10 | // limitations under the License.
|
11 |
|
12 | /* eslint-env node */
|
13 | ;
|
14 |
|
15 | const {
|
16 | parse
|
17 | } = require('@babel/parser');
|
18 | const traverse = require('@babel/traverse').default;
|
19 | const t = require('@babel/types');
|
20 | const generator = require('@babel/generator').default;
|
21 |
|
22 | // Replace indentifier
|
23 | const IdentifierMap = {
|
24 | 'require': 'eriuqer',
|
25 | 'define': 'enifed'
|
26 | };
|
27 |
|
28 | const Identifiers = Object.keys(IdentifierMap);
|
29 |
|
30 |
|
31 | // Use babel to parse/traverse/generate code.
|
32 | // - replace define and require with enifed and eriuqer
|
33 | // - if required, collect the external AMD modules referenced
|
34 |
|
35 | module.exports = function replaceRequireAndDefine(code, amdPackages, externalAmdModules) {
|
36 |
|
37 | const ast = parse(code);
|
38 |
|
39 | traverse(ast, {
|
40 | Program: {
|
41 | exit(path) {
|
42 | // This will take care of the loader.js code where define and require are define globally
|
43 | // The cool thing is that babel will rename all references as well
|
44 | // Rename at the end so we don't overlap with the CallExpression visitor
|
45 | Identifiers.forEach(identifier => path.scope.rename(identifier, IdentifierMap[identifier]));
|
46 | }
|
47 | },
|
48 | CallExpression(path) {
|
49 |
|
50 | // Looking for:
|
51 | // - call for the global define function. If it matches then rename it and collect the external AMD references
|
52 | // - call for the global require function. It it matches then rename it
|
53 | // - eval calls: We need to process the eval code itself (used by auto-import)
|
54 | const {
|
55 | node
|
56 | } = path;
|
57 |
|
58 | // Collect external AMD references
|
59 | // Looking for define('foo', ['a', 'b'], function(a, b) {})
|
60 | if (amdPackages &&
|
61 | externalAmdModules &&
|
62 | t.isIdentifier(node.callee, {
|
63 | name: 'define'
|
64 | }) &&
|
65 | node.arguments.length >= 2 &&
|
66 | t.isArrayExpression(node.arguments[1])) {
|
67 |
|
68 | node.arguments[1].elements.forEach(function (element) {
|
69 | if (!t.isStringLiteral(element)) {
|
70 | return;
|
71 | }
|
72 |
|
73 | const isExternalAmd = amdPackages.some(function (amdPackage) {
|
74 | return element.value.indexOf(amdPackage + '/') === 0 || element.value === amdPackage;
|
75 | });
|
76 |
|
77 | if (!isExternalAmd) {
|
78 | return;
|
79 | }
|
80 |
|
81 | externalAmdModules.add(element.value);
|
82 |
|
83 | });
|
84 | }
|
85 |
|
86 | // auto-import injects eval expression. We need to process them as individual code
|
87 | if (t.isIdentifier(node.callee, {
|
88 | name: 'eval'
|
89 | }) && t.isStringLiteral(node.arguments[0])) {
|
90 | node.arguments[0].value = replaceRequireAndDefine(node.arguments[0].value, amdPackages, externalAmdModules);
|
91 | }
|
92 | },
|
93 | Identifier(path) {
|
94 | // Only interested by our identifiers that have no bindings in the path scope
|
95 | if (!Identifiers.includes(path.node.name) || path.scope.hasBinding(path.node.name)) {
|
96 | return;
|
97 | }
|
98 |
|
99 | // Avoid: foo.define/require
|
100 | if (t.isMemberExpression(path.container) && path.container.property === path.node) {
|
101 | return;
|
102 | }
|
103 |
|
104 | // Avoid class properties/methods
|
105 | if ((t.isClassMethod(path.container) || t.isClassProperty(path.container)) && path.container.key === path.node) {
|
106 | return;
|
107 | }
|
108 |
|
109 | // Rename
|
110 | path.node.name = IdentifierMap[path.node.name];
|
111 | }
|
112 |
|
113 | });
|
114 |
|
115 | return generator(ast, {
|
116 | retainLines: true,
|
117 | retainFunctionParens: true
|
118 | }, code).code;
|
119 | }
|