-
-
Notifications
You must be signed in to change notification settings - Fork 35
/
AureliaDependenciesPlugin.ts
138 lines (119 loc) · 4.98 KB
/
AureliaDependenciesPlugin.ts
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
import { IncludeDependency } from "./IncludeDependency";
import BasicEvaluatedExpression = require("webpack/lib/BasicEvaluatedExpression");
const TAP_NAME = "Aurelia:Dependencies";
class AureliaDependency extends IncludeDependency {
constructor(request: string,
public range: [number, number],
options?: DependencyOptions) {
super(request, options);
}
}
class Template {
apply(dep: AureliaDependency, source: Webpack.Source) {
source.replace(dep.range[0], dep.range[1] - 1, "'" + dep.request.replace(/^async(?:\?[^!]*)?!/, "") + "'");
};
}
class ParserPlugin {
constructor(private methods: string[]) {
}
apply(parser: Webpack.Parser) {
function addDependency(module: string, range: [number, number], options?: DependencyOptions) {
let dep = new AureliaDependency(module, range, options);
parser.state.current.addDependency(dep);
return true;
}
// The parser will only apply "call PLATFORM.moduleName" on free variables.
// So we must first trick it into thinking PLATFORM.moduleName is an unbound identifier
// in the various situations where it is not.
const hooks = parser.hooks;
// This covers native ES module, for example:
// import { PLATFORM } from "aurelia-pal";
// PLATFORM.moduleName("id");
hooks.evaluateIdentifier.tap("imported var.moduleName", TAP_NAME, (expr: Webpack.MemberExpression) => {
if (expr.property.name === "moduleName" &&
expr.object.name === "PLATFORM" &&
expr.object.type === "Identifier") {
return new BasicEvaluatedExpression().setIdentifier("PLATFORM.moduleName").setRange(expr.range);
}
return undefined;
});
// This covers commonjs modules, for example:
// const _aureliaPal = require("aurelia-pal");
// _aureliaPal.PLATFORM.moduleName("id");
// Or (note: no renaming supported):
// const PLATFORM = require("aurelia-pal").PLATFORM;
// PLATFORM.moduleName("id");
hooks.evaluate.tap("MemberExpression", TAP_NAME, expr => {
if (expr.property.name === "moduleName" &&
(expr.object.type === "MemberExpression" && expr.object.property.name === "PLATFORM" ||
expr.object.type === "Identifier" && expr.object.name === "PLATFORM")) {
return new BasicEvaluatedExpression().setIdentifier("PLATFORM.moduleName").setRange(expr.range);
}
return undefined;
});
for (let method of this.methods) {
hooks.call.tap(method, TAP_NAME, (expr: Webpack.CallExpression) => {
if (expr.arguments.length === 0 || expr.arguments.length > 2)
return;
let [arg1, arg2] = expr.arguments;
let param1 = parser.evaluateExpression(arg1);
if (!param1.isString()) return;
if (expr.arguments.length === 1) {
// Normal module dependency
// PLATFORM.moduleName('some-module')
return addDependency(param1.string!, expr.range);
}
let options: DependencyOptions | undefined;
let param2 = parser.evaluateExpression(arg2);
if (param2.isString()) {
// Async module dependency
// PLATFORM.moduleName('some-module', 'chunk name');
options = { chunk: param2.string };
}
else if (arg2.type === "ObjectExpression") {
// Module dependency with extended options
// PLATFORM.moduleName('some-module', { option: value });
options = {};
for (let prop of arg2.properties) {
if (prop.key.type !== "Identifier") continue;
let value = parser.evaluateExpression(prop.value);
switch (prop.key.name) {
case "chunk":
if (value.isString())
options.chunk = value.string;
break;
case "exports":
if (value.isArray() && value.items!.every(v => v.isString()))
options.exports = value.items!.map(v => v.string!);
break;
}
}
}
else {
// Unknown PLATFORM.moduleName() signature
return;
}
return addDependency(param1.string!, expr.range, options);
});
}
}
}
export class AureliaDependenciesPlugin {
private parserPlugin: ParserPlugin;
constructor(...methods: string[]) {
// Always include PLATFORM.moduleName as it's what used in libs.
if (!methods.includes("PLATFORM.moduleName"))
methods.push("PLATFORM.moduleName");
this.parserPlugin = new ParserPlugin(methods);
}
apply(compiler: Webpack.Compiler) {
compiler.hooks.compilation.tap(TAP_NAME, (compilation, params) => {
const normalModuleFactory = params.normalModuleFactory;
compilation.dependencyFactories.set(AureliaDependency, normalModuleFactory);
compilation.dependencyTemplates.set(AureliaDependency, new Template());
normalModuleFactory.hooks.parser.for("javascript/auto").tap(TAP_NAME, parser => {
this.parserPlugin.apply(parser);
});
});
}
};