1 | 'use strict';
|
2 | const parser = require('@babel/parser');
|
3 | const defaultOptions = {
|
4 | allowAwaitOutsideFunction: true,
|
5 | sourceType: 'module',
|
6 | plugins: [
|
7 |
|
8 | 'jsx',
|
9 | 'typescript',
|
10 | 'exportExtensions',
|
11 | 'exportDefaultFrom',
|
12 | 'exportNamespaceFrom',
|
13 | 'dynamicImport',
|
14 | 'importMeta',
|
15 | 'asyncGenerators',
|
16 | 'bigInt',
|
17 | 'classProperties',
|
18 | 'classPrivateProperties',
|
19 | 'classPrivateMethods',
|
20 | ['decorators', {decoratorsBeforeExport: true}],
|
21 | 'doExpressions',
|
22 | 'functionBind',
|
23 | 'functionSent',
|
24 | 'logicalAssignment',
|
25 | 'nullishCoalescingOperator',
|
26 | 'numericSeparator',
|
27 | 'objectRestSpread',
|
28 | 'optionalCatchBinding',
|
29 | 'optionalChaining',
|
30 | 'partialApplication',
|
31 | ['pipelineOperator', {proposal: 'minimal'}],
|
32 | 'throwExpressions',
|
33 | 'topLevelAwait'
|
34 | ]
|
35 | };
|
36 |
|
37 | const EXPORT = `Object.defineProperty(exports, '__esModule', {value: true})`;
|
38 | const IMPORT = `(m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)`;
|
39 | const asDefault = name => name === 'default' ? `${parse.info.EXPORT}.default` : `exports.${name}`;
|
40 | const fromDefault = defaultImport => `${parse.info.IMPORT}(${defaultImport})`;
|
41 |
|
42 | const slice = (code, info) => code.slice(info.start, info.end);
|
43 | const chunk = (info, esm, cjs) => ({
|
44 | start: info.start,
|
45 | end: info.end,
|
46 | esm, cjs
|
47 | });
|
48 |
|
49 | const replace = {
|
50 |
|
51 | ImportDeclaration(code, item) {
|
52 | const source = item.source;
|
53 | const name = withoutCDN(slice(code, source));
|
54 | const esm = slice(code, item);
|
55 | const SEPS = /\{(\s+)/.test(esm) ? RegExp.$1 : '';
|
56 | const SEPE = /(\s+)\}/.test(esm) ? RegExp.$1 : '';
|
57 | const SEP = /(,\s+)[^{]/.test(esm) ? RegExp.$1 : ', ';
|
58 | const EOL = /;$/.test(esm) ? ';' : '';
|
59 | const imported = [];
|
60 | const specifiers = [];
|
61 | let defaultImport = `require(${name})`;
|
62 | if (item.specifiers.length) {
|
63 | item.specifiers.forEach(specifier => {
|
64 | switch(specifier.type) {
|
65 | case 'ImportDefaultSpecifier':
|
66 | imported.push(
|
67 | `const ${specifier.local.name} = ${fromDefault(defaultImport)}${EOL}`
|
68 | );
|
69 | break;
|
70 | case 'ImportNamespaceSpecifier':
|
71 | imported.push(
|
72 | `const ${specifier.local.name} = ${defaultImport}${EOL}`
|
73 | );
|
74 | break;
|
75 | case 'ImportSpecifier':
|
76 | specifiers.push(
|
77 | specifier.local.name === specifier.imported.name ?
|
78 | specifier.local.name :
|
79 | `${specifier.imported.name}: ${specifier.local.name}`
|
80 | );
|
81 | break;
|
82 | }
|
83 | });
|
84 | if (specifiers.length) {
|
85 | imported.push(
|
86 | `const {${SEPS}${specifiers.join(SEP)}${SEPE}} = ${defaultImport}${EOL}`
|
87 | );
|
88 | }
|
89 | } else {
|
90 | imported.push(`${defaultImport}${EOL}`);
|
91 | }
|
92 | return chunk(item, esm, imported.join('\n'));
|
93 | },
|
94 |
|
95 | ExportAllDeclaration(code, item) {
|
96 | const source = item.source;
|
97 | const esm = slice(code, item);
|
98 | const cjs = `(m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))\n(require(${
|
99 | withoutCDN(slice(code, source))
|
100 | }));`;
|
101 | return chunk(item, esm, cjs);
|
102 | },
|
103 |
|
104 | ExportDefaultDeclaration(code, item) {
|
105 | const declaration = item.declaration;
|
106 | const esm = slice(code, item);
|
107 | let cjs;
|
108 | switch (declaration.type) {
|
109 | case 'AssignmentExpression':
|
110 | case 'FunctionDeclaration':
|
111 | if (declaration.id) {
|
112 | cjs = `${esm.replace(/^export\s+default\s+/, '')}\n${parse.info.EXPORT}.default = ${declaration.id.name}`;
|
113 | } else {
|
114 | cjs = esm.replace(/^export\s+default\s+/, `${parse.info.EXPORT}.default = `);
|
115 | }
|
116 | break;
|
117 | case 'Identifier':
|
118 | case 'ObjectExpression':
|
119 | default:
|
120 | cjs = esm.replace(/^export\s+default\s+/, `${parse.info.EXPORT}.default = `);
|
121 | break;
|
122 | }
|
123 | return chunk(item, esm, cjs);
|
124 | },
|
125 |
|
126 | ExportNamedDeclaration(code, item) {
|
127 | const declaration = item.declaration;
|
128 | const source = item.source;
|
129 | const esm = slice(code, item);
|
130 | const EOL = /;$/.test(esm) ? ';\n' : '\n';
|
131 | let cjs = source ? '(m => {\n' : '';
|
132 | item.specifiers.forEach(specifier => {
|
133 | cjs += `${
|
134 | source ? ' ' : ''
|
135 | }${asDefault(specifier.exported.name)} = ${
|
136 | source ? 'm.' : ''
|
137 | }${specifier.local.name}${EOL}`;
|
138 | });
|
139 | if (declaration) {
|
140 | cjs += esm.replace(/^export\s+/, '') + '\n';
|
141 | (declaration.declarations || [declaration]).forEach(specifier => {
|
142 | cjs += `${asDefault(specifier.id.name)} = ${specifier.id.name}${EOL}`;
|
143 | });
|
144 | }
|
145 | if (source) cjs += `})(require(${
|
146 | withoutCDN(slice(code, source))
|
147 | }));\n`;
|
148 | return chunk(item, esm, cjs.trim());
|
149 | }
|
150 | };
|
151 |
|
152 | const parse = (code, options) => {
|
153 | if (!options) options = {};
|
154 | const parserOptions = Object.assign(
|
155 | {},
|
156 | defaultOptions,
|
157 | options
|
158 | );
|
159 | parse.info = {
|
160 | EXPORT: options.EXPORT || EXPORT,
|
161 | IMPORT: options.IMPORT || IMPORT
|
162 | };
|
163 | delete options.EXPORT;
|
164 | delete options.IMPORT;
|
165 | code = code.toString();
|
166 | const out = [];
|
167 | const chunks = [];
|
168 | const parsed = parser.parse(code, parserOptions);
|
169 | parsed.program.body.forEach(item => {
|
170 | if (replace.hasOwnProperty(item.type)) {
|
171 | chunks.push(replace[item.type](code, item));
|
172 | }
|
173 | });
|
174 | const length = chunks.length;
|
175 | let c = 0;
|
176 | for (let i = 0; i < length; i++) {
|
177 | out.push(
|
178 | code.slice(c, chunks[i].start),
|
179 | chunks[i].cjs
|
180 | );
|
181 | c = chunks[i].end;
|
182 | }
|
183 | out.push(length ? code.slice(c) : code);
|
184 | let result = out.join('').replace(
|
185 | /\bimport\.meta\b/g,
|
186 | "({url: require('url').pathToFileURL(__filename).href})"
|
187 | );
|
188 | return /^(?:#!|['"]use strict['"])/.test(result.trim()) ?
|
189 | result :
|
190 | ("'use strict';\n" + result);
|
191 | };
|
192 |
|
193 | const withoutCDN = name =>
|
194 | /^(['"`])https?:\/\/(?:unpkg\.com)\/([^@/]+)\S*?\1$/.test(name) ?
|
195 | `${RegExp.$1}${RegExp.$2}${RegExp.$1}` : name;
|
196 |
|
197 | parse.EXPORT = EXPORT;
|
198 | parse.IMPORT = IMPORT;
|
199 | module.exports = parse;
|