forked from angular/angular-cli
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathresource_loader.ts
114 lines (98 loc) · 4.27 KB
/
resource_loader.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
import {ResourceLoader} from '@angular/compiler';
import {readFileSync} from 'fs';
import * as vm from 'vm';
import * as path from 'path';
const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
export class WebpackResourceLoader implements ResourceLoader {
private _context: string;
private _uniqueId = 0;
constructor(private _parentCompilation: any) {
this._context = _parentCompilation.context;
}
private _compile(filePath: string, content: string): Promise<any> {
const compilerName = `compiler(${this._uniqueId++})`;
const outputOptions = { filename: filePath };
const relativePath = path.relative(this._context || '', filePath);
const childCompiler = this._parentCompilation.createChildCompiler(relativePath, outputOptions);
childCompiler.context = this._context;
childCompiler.apply(
new NodeTemplatePlugin(outputOptions),
new NodeTargetPlugin(),
new SingleEntryPlugin(this._context, filePath),
new LoaderTargetPlugin('node')
);
// Store the result of the parent compilation before we start the child compilation
let assetsBeforeCompilation = Object.assign(
{},
this._parentCompilation.assets[outputOptions.filename]
);
// Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
// Hot module replacement requires that every child compiler has its own
// cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179
childCompiler.plugin('compilation', function (compilation: any) {
if (compilation.cache) {
if (!compilation.cache[compilerName]) {
compilation.cache[compilerName] = {};
}
compilation.cache = compilation.cache[compilerName];
}
});
// Compile and return a promise
return new Promise((resolve, reject) => {
childCompiler.runAsChild((err: Error, entries: any[], childCompilation: any) => {
// Resolve / reject the promise
if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
const errorDetails = childCompilation.errors.map(function (error: any) {
return error.message + (error.error ? ':\n' + error.error : '');
}).join('\n');
reject(new Error('Child compilation failed:\n' + errorDetails));
} else if (err) {
reject(err);
} else {
// Replace [hash] placeholders in filename
const outputName = this._parentCompilation.mainTemplate.applyPluginsWaterfall(
'asset-path', outputOptions.filename, {
hash: childCompilation.hash,
chunk: entries[0]
});
// Restore the parent compilation to the state like it was before the child compilation.
this._parentCompilation.assets[outputName] = assetsBeforeCompilation[outputName];
if (assetsBeforeCompilation[outputName] === undefined) {
// If it wasn't there - delete it.
delete this._parentCompilation.assets[outputName];
}
resolve({
// Hash of the template entry point.
hash: entries[0].hash,
// Output name.
outputName: outputName,
// Compiled code.
content: childCompilation.assets[outputName].source()
});
}
});
});
}
private _evaluate(fileName: string, source: string): Promise<string> {
try {
const vmContext = vm.createContext(Object.assign({require: require}, global));
const vmScript = new vm.Script(source, {filename: fileName});
// Evaluate code and cast to string
let newSource: string;
newSource = vmScript.runInContext(vmContext);
if (typeof newSource == 'string') {
return Promise.resolve(newSource);
}
return Promise.reject('The loader "' + fileName + '" didn\'t return a string.');
} catch (e) {
return Promise.reject(e);
}
}
get(filePath: string): Promise<string> {
return this._compile(filePath, readFileSync(filePath, 'utf8'))
.then((result: any) => this._evaluate(result.outputName, result.content));
}
}