1
- import { readFileSync } from 'fs' ;
2
1
import * as vm from 'vm' ;
3
2
import * as path from 'path' ;
4
3
5
4
const NodeTemplatePlugin = require ( 'webpack/lib/node/NodeTemplatePlugin' ) ;
6
5
const NodeTargetPlugin = require ( 'webpack/lib/node/NodeTargetPlugin' ) ;
7
6
const LoaderTargetPlugin = require ( 'webpack/lib/LoaderTargetPlugin' ) ;
8
7
const SingleEntryPlugin = require ( 'webpack/lib/SingleEntryPlugin' ) ;
8
+ const loaderUtils = require ( 'loader-utils' ) ;
9
9
10
10
11
+ interface CompilationOutput {
12
+ hash : string ;
13
+ outputName : string ;
14
+ source : string ;
15
+ newSource ?: string ;
16
+ }
11
17
12
18
export class WebpackResourceLoader {
19
+ private _parentCompilation : any ;
13
20
private _context : string ;
14
21
private _uniqueId = 0 ;
22
+ private _cache = new Map < string , CompilationOutput > ( ) ;
23
+
24
+ constructor ( ) { }
15
25
16
- constructor ( private _parentCompilation : any ) {
17
- this . _context = _parentCompilation . context ;
26
+ update ( parentCompilation : any ) {
27
+ this . _parentCompilation = parentCompilation ;
28
+ this . _context = parentCompilation . context ;
29
+ this . _uniqueId = 0 ;
18
30
}
19
31
20
- private _compile ( filePath : string , _content : string ) : Promise < any > {
32
+ private _compile ( filePath : string ) : Promise < CompilationOutput > {
33
+
34
+ if ( ! this . _parentCompilation ) {
35
+ throw new Error ( 'WebpackResourceLoader cannot be used without parentCompilation' ) ;
36
+ }
37
+
21
38
const compilerName = `compiler(${ this . _uniqueId ++ } )` ;
22
39
const outputOptions = { filename : filePath } ;
23
40
const relativePath = path . relative ( this . _context || '' , filePath ) ;
@@ -63,9 +80,9 @@ export class WebpackResourceLoader {
63
80
// Replace [hash] placeholders in filename
64
81
const outputName = this . _parentCompilation . mainTemplate . applyPluginsWaterfall (
65
82
'asset-path' , outputOptions . filename , {
66
- hash : childCompilation . hash ,
67
- chunk : entries [ 0 ]
68
- } ) ;
83
+ hash : childCompilation . hash ,
84
+ chunk : entries [ 0 ]
85
+ } ) ;
69
86
70
87
// Restore the parent compilation to the state like it was before the child compilation.
71
88
Object . keys ( childCompilation . assets ) . forEach ( ( fileName ) => {
@@ -78,40 +95,58 @@ export class WebpackResourceLoader {
78
95
}
79
96
} ) ;
80
97
98
+ const source = childCompilation . assets [ outputName ] . source ( ) ;
99
+
81
100
resolve ( {
82
- // Hash of the template entry point .
83
- hash : entries [ 0 ] . hash ,
101
+ // Hash of the source .
102
+ hash : loaderUtils . getHashDigest ( source ) ,
84
103
// Output name.
85
- outputName : outputName ,
104
+ outputName,
86
105
// Compiled code.
87
- content : childCompilation . assets [ outputName ] . source ( )
106
+ source
88
107
} ) ;
89
108
}
90
109
} ) ;
91
110
} ) ;
92
111
}
93
112
94
- private _evaluate ( fileName : string , source : string ) : Promise < string > {
113
+ private _evaluate ( output : CompilationOutput ) : Promise < string > {
95
114
try {
96
- const vmContext = vm . createContext ( Object . assign ( { require : require } , global ) ) ;
97
- const vmScript = new vm . Script ( source , { filename : fileName } ) ;
115
+ const vmContext = vm . createContext ( Object . assign ( { require : require } , global ) ) ;
116
+ const vmScript = new vm . Script ( output . source , { filename : output . outputName } ) ;
98
117
99
118
// Evaluate code and cast to string
100
119
let newSource : string ;
101
120
newSource = vmScript . runInContext ( vmContext ) ;
102
121
103
122
if ( typeof newSource == 'string' ) {
123
+ this . _cache . set ( output . outputName , { ...output , newSource } ) ;
104
124
return Promise . resolve ( newSource ) ;
105
125
}
106
126
107
- return Promise . reject ( 'The loader "' + fileName + '" didn\'t return a string.' ) ;
127
+ return Promise . reject ( 'The loader "' + output . outputName + '" didn\'t return a string.' ) ;
108
128
} catch ( e ) {
109
129
return Promise . reject ( e ) ;
110
130
}
111
131
}
112
132
113
133
get ( filePath : string ) : Promise < string > {
114
- return this . _compile ( filePath , readFileSync ( filePath , 'utf8' ) )
115
- . then ( ( result : any ) => this . _evaluate ( result . outputName , result . content ) ) ;
134
+ return this . _compile ( filePath )
135
+ . then ( ( result : any ) => {
136
+ // Check cache.
137
+ const outputName = result . outputName ;
138
+ const output = this . _cache . get ( outputName ) ;
139
+ if ( output ) {
140
+ if ( output . hash === result . hash && output . newSource ) {
141
+ // Return cached newSource.
142
+ return Promise . resolve ( output . newSource ) ;
143
+ } else {
144
+ // Delete cache entry.
145
+ this . _cache . delete ( outputName ) ;
146
+ }
147
+ }
148
+
149
+ return this . _evaluate ( result ) ;
150
+ } ) ;
116
151
}
117
152
}
0 commit comments