UNPKG

3.62 kBJavaScriptView Raw
1import { walk } from 'estree-walker';
2import MagicString from 'magic-string';
3import { createFilter } from 'rollup-pluginutils';
4
5/* eslint-disable no-param-reassign */
6
7const whitespace = /\s/;
8
9function getName(node) {
10 if (node.type === 'Identifier') return node.name;
11 if (node.type === 'ThisExpression') return 'this';
12 if (node.type === 'Super') return 'super';
13
14 return null;
15}
16
17function flatten(node) {
18 const parts = [];
19
20 while (node.type === 'MemberExpression') {
21 if (node.computed) return null;
22
23 parts.unshift(node.property.name);
24 node = node.object;
25 }
26
27 const name = getName(node);
28
29 if (!name) return null;
30
31 parts.unshift(name);
32 return parts.join('.');
33}
34
35function strip(options = {}) {
36 const include = options.include || '**/*.js';
37 const { exclude } = options;
38 const filter = createFilter(include, exclude);
39 const sourceMap = options.sourceMap !== false;
40
41 const removeDebuggerStatements = options.debugger !== false;
42 const functions = (options.functions || ['console.*', 'assert.*']).map((keypath) =>
43 keypath.replace(/\./g, '\\.').replace(/\*/g, '\\w+')
44 );
45
46 const labels = options.labels || [];
47
48 const firstpass = new RegExp(`\\b(?:${functions.join('|')}|debugger)\\b`);
49 const pattern = new RegExp(`^(?:${functions.join('|')})$`);
50
51 return {
52 name: 'strip',
53
54 transform(code, id) {
55 if (!filter(id)) return null;
56 if (functions.length > 0 && !firstpass.test(code)) return null;
57
58 let ast;
59
60 try {
61 ast = this.parse(code);
62 } catch (err) {
63 err.message += ` in ${id}`;
64 throw err;
65 }
66
67 const magicString = new MagicString(code);
68 let edited = false;
69
70 function remove(start, end) {
71 while (whitespace.test(code[start - 1])) start -= 1;
72 magicString.remove(start, end);
73 }
74
75 function isBlock(node) {
76 return node && (node.type === 'BlockStatement' || node.type === 'Program');
77 }
78
79 function removeExpression(node) {
80 const { parent } = node;
81
82 if (parent.type === 'ExpressionStatement') {
83 removeStatement(parent);
84 } else {
85 magicString.overwrite(node.start, node.end, 'void 0');
86 }
87
88 edited = true;
89 }
90
91 function removeStatement(node) {
92 const { parent } = node;
93
94 if (isBlock(parent)) {
95 remove(node.start, node.end);
96 } else {
97 magicString.overwrite(node.start, node.end, 'void 0;');
98 }
99
100 edited = true;
101 }
102
103 walk(ast, {
104 enter(node, parent) {
105 Object.defineProperty(node, 'parent', {
106 value: parent,
107 enumerable: false,
108 configurable: true
109 });
110
111 if (sourceMap) {
112 magicString.addSourcemapLocation(node.start);
113 magicString.addSourcemapLocation(node.end);
114 }
115
116 if (removeDebuggerStatements && node.type === 'DebuggerStatement') {
117 removeStatement(node);
118 } else if (node.type === 'LabeledStatement') {
119 if (node.label && labels.includes(node.label.name)) {
120 removeStatement(node);
121 }
122 } else if (node.type === 'CallExpression') {
123 const keypath = flatten(node.callee);
124 if (keypath && pattern.test(keypath)) {
125 removeExpression(node);
126 this.skip();
127 }
128 }
129 }
130 });
131
132 if (!edited) return null;
133
134 code = magicString.toString();
135 const map = sourceMap ? magicString.generateMap() : null;
136
137 return { code, map };
138 }
139 };
140}
141
142export default strip;