-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
220 lines (180 loc) · 8.37 KB
/
index.js
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
const MagicString = require('magic-string');
const utils = require('@rollup/pluginutils');
const path =require('path');
const fs = require('fs');
const nodeNativeDeps = ['async_hooks', 'tls', 'crypto', 'http', 'fs', 'path', 'events', 'url', 'net', 'zlib', 'tty', 'querystring', 'util', 'buffer', 'domain', 'stream', 'os', 'https', 'string_decoder', 'assert', 'child_process', 'cluster', 'timers', 'vm', 'worker_threads', 'module', 'constants']
function escape(str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
}
function ensureFunction(functionOrValue) {
if (typeof functionOrValue === 'function') return functionOrValue;
return () => functionOrValue;
}
function longest(a, b) {
return b.length - a.length;
}
function getReplacements(options) {
if (options.values) {
return Object.assign({}, options.values);
}
const values = Object.assign({}, options);
delete values.delimiters;
delete values.include;
delete values.exclude;
delete values.sourcemap;
delete values.sourceMap;
return values;
}
function mapToFunctions(object) {
return Object.keys(object).reduce((fns, key) => {
const functions = Object.assign({}, fns);
functions[key] = ensureFunction(object[key]);
return functions;
}, {});
}
module.exports = function rollupPluginNode(options = {}) {
const filter = utils.createFilter(options.include, options.exclude);
const { delimiters, finalRenderES7 = false, additionalOptionalDeps = {} } = options;
if(typeof additionalOptionalDeps !== 'object')
throw new Error('additionalOptionaDeps optioni is not a valid object')
const additionalOptionaDepsKeys = Object.keys(additionalOptionalDeps);
const replacements = {
// 'commonjsRequire.resolve': 'require.resolve', // workaround for promise.resolve usage and commonjs plugin
}
const optionalES5RegexTemplate = `[\\w\\s=_\\$\\(]*require\\(((\\'|\\")(.*)LIBRARY_NAME(\\'|\\"))\\)(.)*\\n`
const optionalES7RegexTemplate = `import [\\w$0-9]* from (\\'|\\")((.*)LIBRARY_NAME)(\\'|\\")(.)*\\n`
let optionalRegexTemplate = (finalRenderES7) ? optionalES7RegexTemplate : optionalES5RegexTemplate;
const functionValues = mapToFunctions(getReplacements(replacements));
const keys = Object.keys(functionValues)
.sort(longest)
.map(escape);
const optionalDeps = [];
const optionalDepsInternal = [];
const pattern = delimiters
? new RegExp(`${escape(delimiters[0])}(${keys.join('|')})${escape(delimiters[1])}`, 'g')
: new RegExp(`\\b(${keys.join('|')})\\b`, 'g');
return {
name: 'nodejs',
resolveId ( source ) {
// Test additional internal optional dep
if(additionalOptionaDepsKeys?.length !== 0){
let additionalSource;
additionalOptionaDepsKeys.forEach(partialNaming => {
if(source.includes(partialNaming)){
additionalSource = partialNaming
}
});
if(additionalSource){
//console.log(`Library call detect as optional: ${source}`)
optionalDepsInternal.push({
source: source.split('?')[0],
dep: additionalSource
});
return {id: source, external: true};
}
}
if (!source.includes(path.sep) && nodeNativeDeps.includes(source.split('?')[0])){
// You enter here for native nodejs native lib -> we set external lib
return {id: source, external: true};
} else if (!source.includes(path.sep) && !nodeNativeDeps.includes(source.split('?')[0])){
// We enter here for optional or peer dep -> set to external and try to bundle as optional dep (try catch around)
optionalDeps.push(source.split('?')[0]);
return {id: source, external: true};
}
return null; // other ids should be handled as usually
},
renderChunk(code, chunk) {
const id = chunk.fileName;
if (!keys.length) return null;
if (!filter(id)) return null;
return executeReplacement(code, id);
},
transform(code, id) {
if (!keys.length) return null;
if (!filter(id)) return null;
return executeReplacement(code, id);
},
generateBundle(assetInfos, bundleInfo){
// remove same values
const finalOptionalDeps = Array.from(new Set(optionalDeps));
for(let [key, value] of Object.entries(bundleInfo)) {
// for classic option dep
finalOptionalDeps.forEach( optionalLibrary => {
console.log(getRegexFromLibraryName(optionalLibrary))
value.code = value.code.replaceAll(getRegexFromLibraryName(optionalLibrary), '')
})
// for specific internal dep
optionalDepsInternal.forEach( optionalInternalLib => {
const intermediateFinalPath = optionalInternalLib.source.split(path.sep);
const finalPath = path.join(additionalOptionalDeps[optionalInternalLib.dep] || './', intermediateFinalPath[intermediateFinalPath.length - 1])
let myExec;
const regexOptionalInternal = getRegexFromLibraryName(optionalInternalLib.dep)
while ((optsDep = regexOptionalInternal.exec(value.code)) !== null) {
value.code = value.code.replaceAll(optsDep[1], `'./${finalPath}'`)
}
const dest = path.resolve(path.dirname(assetInfos.file), finalPath)
const from = optionalInternalLib.source
if(fs.existsSync(from)){
try {
if(!fs.existsSync(path.dirname(dest))){
fs.mkdirSync(path.dirname(dest))
}
fs.copyFileSync(from, dest)
console.log(`The copy of ${from} succeed`)
} catch(e){
console.log(`The copy of ${from} failed`)
}
} else {
console.log(`Warn: Missing optional internal dep source: ${from}`)
}
})
}
}
};
function getRegexFromLibraryName(libraryName){
return new RegExp(optionalRegexTemplate.replace('LIBRARY_NAME', libraryName), 'g');
}
function executeReplacementChunk(code, id){
const magicString = new MagicString(code);
// execute specific chunk replacement for optional deps
console.log(new RegExp(optionalRegexTemplate,'g'))
// if(code.includes('bufferutil'))
codeHasReplacements(code, id, magicString, new RegExp(optionalRegexTemplate,'g'))
if (!codeHasReplacements(code, id, magicString)) {
return null;
}
const result = { code: magicString.toString() };
if (isSourceMapEnabled()) {
result.map = magicString.generateMap({ hires: true });
}
return result;
}
function executeReplacement(code, id) {
const magicString = new MagicString(code);
if (!codeHasReplacements(code, id, magicString)) {
return null;
}
const result = { code: magicString.toString() };
if (isSourceMapEnabled()) {
result.map = magicString.generateMap({ hires: true });
}
return result;
}
function codeHasReplacements(code, id, magicString, specificPattern = undefined, replacement = '') {
let result = false;
let match;
const patternToExecute = specificPattern ? specificPattern : pattern;
// eslint-disable-next-line no-cond-assign
while ((match = patternToExecute.exec(code)) !== null) {
result = true;
const start = match.index;
const end = start + match[0].length;
const finalReplacment = specificPattern ? replacement : String(functionValues[match[1]](id));
magicString.overwrite(start, end, finalReplacment);
}
return result;
}
function isSourceMapEnabled() {
return options.sourceMap !== false && options.sourcemap !== false;
}
}