forked from facebook/metro
-
Notifications
You must be signed in to change notification settings - Fork 0
/
JsFileWrapping.js
145 lines (122 loc) Β· 3.94 KB
/
JsFileWrapping.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
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
import type {FunctionExpression, Identifier, Program} from '@babel/types';
import template from '@babel/template';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import invariant from 'invariant';
const WRAP_NAME = '$$_REQUIRE'; // note: babel will prefix this with _
// Check first the `global` variable as the global object. This way serializers
// can create a local variable called global to fake it as a global object
// without having to pollute the window object on web.
const IIFE_PARAM = template.expression(
"typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this",
);
function wrapModule(
fileAst: BabelNodeFile,
importDefaultName: string,
importAllName: string,
dependencyMapName: string,
globalPrefix: string,
skipRequireRename: boolean,
): {
ast: BabelNodeFile,
requireName: string,
} {
const params = buildParameters(
importDefaultName,
importAllName,
dependencyMapName,
);
const factory = functionFromProgram(fileAst.program, params);
const def = t.callExpression(t.identifier(`${globalPrefix}__d`), [factory]);
const ast = t.file(t.program([t.expressionStatement(def)]));
// `require` doesn't need to be scoped when Metro serializes to iife because the local function
// `require` will be used instead of the global one.
const requireName = skipRequireRename ? 'require' : renameRequires(ast);
return {ast, requireName};
}
function wrapPolyfill(fileAst: BabelNodeFile): BabelNodeFile {
const factory = functionFromProgram(fileAst.program, ['global']);
const iife = t.callExpression(factory, [IIFE_PARAM()]);
return t.file(t.program([t.expressionStatement(iife)]));
}
function jsonToCommonJS(source: string): string {
return `module.exports = ${source};`;
}
function wrapJson(source: string, globalPrefix: string): string {
// Unused parameters; remember that's wrapping JSON.
const moduleFactoryParameters = buildParameters(
'_importDefaultUnused',
'_importAllUnused',
'_dependencyMapUnused',
);
return [
`${globalPrefix}__d(function(${moduleFactoryParameters.join(', ')}) {`,
` ${jsonToCommonJS(source)}`,
'});',
].join('\n');
}
function functionFromProgram(
program: Program,
parameters: $ReadOnlyArray<string>,
): FunctionExpression {
return t.functionExpression(
undefined,
parameters.map(makeIdentifier),
t.blockStatement(program.body, program.directives),
);
}
function makeIdentifier(name: string): Identifier {
return t.identifier(name);
}
function buildParameters(
importDefaultName: string,
importAllName: string,
dependencyMapName: string,
): $ReadOnlyArray<string> {
return [
'global',
'require',
importDefaultName,
importAllName,
'module',
'exports',
dependencyMapName,
];
}
// Renaming requires should ideally only be done when generating for the target
// that expects the custom require name in the optimize step.
// This visitor currently renames all `require` references even if the module
// contains a custom `require` declaration. This should be fixed by only renaming
// if the `require` symbol hasn't been redeclared.
function renameRequires(ast: BabelNodeFile): string {
let newRequireName = WRAP_NAME;
traverse(ast, {
Program(path) {
const body = path.get('body.0.expression.arguments.0.body');
invariant(
!Array.isArray(body),
'metro: Expected `body` to be a single path.',
);
newRequireName = body.scope.generateUid(WRAP_NAME);
body.scope.rename('require', newRequireName);
},
});
return newRequireName;
}
module.exports = {
WRAP_NAME,
wrapJson,
jsonToCommonJS,
wrapModule,
wrapPolyfill,
};