1- import { readFileSync } from 'fs' ;
21import * as vm from 'vm' ;
32import * as path from 'path' ;
43
54const NodeTemplatePlugin = require ( 'webpack/lib/node/NodeTemplatePlugin' ) ;
65const NodeTargetPlugin = require ( 'webpack/lib/node/NodeTargetPlugin' ) ;
76const LoaderTargetPlugin = require ( 'webpack/lib/LoaderTargetPlugin' ) ;
87const SingleEntryPlugin = require ( 'webpack/lib/SingleEntryPlugin' ) ;
8+ const loaderUtils = require ( 'loader-utils' ) ;
99
1010
11+ interface CompilationOutput {
12+ hash : string ;
13+ outputName : string ;
14+ source : string ;
15+ newSource ?: string ;
16+ }
1117
1218export class WebpackResourceLoader {
19+ private _parentCompilation : any ;
1320 private _context : string ;
1421 private _uniqueId = 0 ;
22+ private _cache = new Map < string , CompilationOutput > ( ) ;
23+
24+ constructor ( ) { }
1525
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 ;
1830 }
1931
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+
2138 const compilerName = `compiler(${ this . _uniqueId ++ } )` ;
2239 const outputOptions = { filename : filePath } ;
2340 const relativePath = path . relative ( this . _context || '' , filePath ) ;
@@ -63,9 +80,9 @@ export class WebpackResourceLoader {
6380 // Replace [hash] placeholders in filename
6481 const outputName = this . _parentCompilation . mainTemplate . applyPluginsWaterfall (
6582 'asset-path' , outputOptions . filename , {
66- hash : childCompilation . hash ,
67- chunk : entries [ 0 ]
68- } ) ;
83+ hash : childCompilation . hash ,
84+ chunk : entries [ 0 ]
85+ } ) ;
6986
7087 // Restore the parent compilation to the state like it was before the child compilation.
7188 Object . keys ( childCompilation . assets ) . forEach ( ( fileName ) => {
@@ -78,40 +95,58 @@ export class WebpackResourceLoader {
7895 }
7996 } ) ;
8097
98+ const source = childCompilation . assets [ outputName ] . source ( ) ;
99+
81100 resolve ( {
82- // Hash of the template entry point .
83- hash : entries [ 0 ] . hash ,
101+ // Hash of the source .
102+ hash : loaderUtils . getHashDigest ( source ) ,
84103 // Output name.
85- outputName : outputName ,
104+ outputName,
86105 // Compiled code.
87- content : childCompilation . assets [ outputName ] . source ( )
106+ source
88107 } ) ;
89108 }
90109 } ) ;
91110 } ) ;
92111 }
93112
94- private _evaluate ( fileName : string , source : string ) : Promise < string > {
113+ private _evaluate ( output : CompilationOutput ) : Promise < string > {
95114 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 } ) ;
98117
99118 // Evaluate code and cast to string
100119 let newSource : string ;
101120 newSource = vmScript . runInContext ( vmContext ) ;
102121
103122 if ( typeof newSource == 'string' ) {
123+ this . _cache . set ( output . outputName , { ...output , newSource } ) ;
104124 return Promise . resolve ( newSource ) ;
105125 }
106126
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.' ) ;
108128 } catch ( e ) {
109129 return Promise . reject ( e ) ;
110130 }
111131 }
112132
113133 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+ } ) ;
116151 }
117152}
0 commit comments