-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
index.js
125 lines (115 loc) · 3.77 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
/**
* @fileoverview
* Transforms ESM import statements into an async loader meant for `nomodule`
* builds.
*/
const {
buildNamespaceInitStatements,
ensureStatementsHoisted,
hasExports,
isModule,
rewriteModuleStatementsAndPrepareHeader,
} = require('@babel/helper-module-transforms');
const {readFileSync} = require('fs');
const {join: pathJoin, posix, relative} = require('path');
let wrapperTemplate;
module.exports = function (babel) {
const {template, types: t} = babel;
const pathToModuleName = (filename) =>
filename.replace(/^(\.\/)?dist\//, '').replace(/(\.max)?\.m?js$/, '');
const resolveModuleName = (filename, source) =>
pathToModuleName(posix.join(posix.dirname(filename), source));
/**
* @param {Object} replacements
* @return {babel.Node}
*/
function buildWrapper(replacements) {
if (!wrapperTemplate) {
const templateSource = readFileSync(
pathJoin(__dirname, 'define-template.js'),
'utf8'
);
wrapperTemplate = template(templateSource, {
placeholderPattern: /^__[A-Z0-9_]+__$/,
});
}
return wrapperTemplate(replacements);
}
/**
* @param {babel.NodePath<import('@babel/types').Program>} path
* @param {babel.Node} wrapper
*/
function injectWrapper(path, wrapper) {
const {body, directives} = path.node;
path.node.directives = [];
path.node.body = [];
const wrapperPath = path.pushContainer('body', wrapper)[0];
const callback = wrapperPath
.get('expression.arguments')
// @ts-ignore
.filter((arg) => arg.isFunctionExpression())[0]
.get('body');
callback.pushContainer('directives', directives);
callback.pushContainer('body', body);
}
return {
name: 'nomodule-loader',
visitor: {
Program: {
enter(path, state) {
// We can stop since this should be the last transform step.
// See nomodule-loader-config.js
path.stop();
if (!isModule(path)) {
throw new Error();
}
const loose = true;
const noInterop = true;
const {headers, meta} = rewriteModuleStatementsAndPrepareHeader(
path,
{loose, noInterop}
);
const filename = relative(process.cwd(), state.filename);
const importNames = [];
const callbackArgs = [];
const metaHasExports = hasExports(meta);
if (metaHasExports) {
// exports is identified as the number 0
importNames.push(t.numericLiteral(0));
callbackArgs.push(t.identifier(meta.exportName));
}
for (const [source, metadata] of meta.source) {
importNames.push(
t.stringLiteral(resolveModuleName(filename, source))
);
callbackArgs.push(t.identifier(metadata.name));
headers.push(
...buildNamespaceInitStatements(meta, metadata, loose)
);
}
if (importNames.length < 1) {
return;
}
ensureStatementsHoisted(headers);
path.unshiftContainer('body', headers);
injectWrapper(
path,
buildWrapper({
__MODULE_NAME__: t.stringLiteral(pathToModuleName(filename)),
__HAS_EXPORTS__: t.booleanLiteral(metaHasExports),
__ONLY_EXPORTS__: t.booleanLiteral(
metaHasExports && importNames.length === 1
),
__IMPORT_NAMES__: t.arrayExpression(importNames),
__SINGLE_IMPORT_NO_EXPORTS__:
importNames.length === 1 && !metaHasExports
? t.cloneNode(importNames[0])
: t.nullLiteral(),
__CALLBACK_ARGS__: callbackArgs,
})
);
},
},
},
};
};