1 | import { walk } from 'estree-walker';
|
2 | import MagicString from 'magic-string';
|
3 | import { createFilter } from 'rollup-pluginutils';
|
4 |
|
5 |
|
6 |
|
7 | const whitespace = /\s/;
|
8 |
|
9 | function 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 |
|
17 | function 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 |
|
35 | function 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 |
|
142 | export default strip;
|