UNPKG

3.86 kBJavaScriptView Raw
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'use strict';
14
15const {
16 parse
17} = require('@babel/parser');
18const traverse = require('@babel/traverse').default;
19const t = require('@babel/types');
20const generator = require('@babel/generator').default;
21
22// Replace indentifier
23const IdentifierMap = {
24 'require': 'eriuqer',
25 'define': 'enifed'
26};
27
28const 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
35module.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}