23
23
24
24
const path = require ( 'path' ) ;
25
25
const util = require ( 'util' ) ;
26
+ const commondir = require ( 'commondir' ) ;
26
27
const Template = require ( "webpack/lib/Template" ) ;
27
28
const stringify = require ( "node-stringify" ) ;
28
29
const { plugin} = require ( "./pluginHelper" ) ;
@@ -43,12 +44,12 @@ module.exports = class DojoAMDMainTemplatePlugin {
43
44
compilation :{ value : compilation } ,
44
45
params :{ value : params }
45
46
} ) ;
47
+ compilation . plugin ( "before-chunk-assets" , this . relativizeAbsoluteAbsMids . bind ( context ) ) ;
46
48
plugin ( compilation . mainTemplate , {
47
49
"bootstrap" : this . bootstrap ,
48
50
"require-extensions" : this . requireExtensions ,
49
51
"dojo-require-extensions" : this . dojoRequireExtensions , // plugin specific event
50
52
"render-dojo-config-vars" : this . renderDojoConfigVars , // plugin specific event
51
- "set-emit-build-context" : this . setEmitBuildContext , // plugin specific event
52
53
"hash" : this . hash
53
54
} , context ) ;
54
55
} ) ;
@@ -131,13 +132,8 @@ but the loader specified at ${this.embeddedLoaderFilename} was built without the
131
132
}
132
133
}
133
134
buf . push ( mainTemplate . applyPluginsWaterfall ( "render-dojo-config-vars" , loaderScope , "" , ...rest ) ) ;
134
- if ( this . emitBuildContext ) {
135
- var context = path . resolve ( this . compiler . context , this . options . globalContext || '.' ) . replace ( / \\ / g, '/' ) ;
136
- /* istanbul ignore else */
137
- if ( ! context . endsWith ( '/' ) ) {
138
- context += '/' ;
139
- }
140
- buf . push ( `loaderScope.buildContext = "${ context } "` ) ;
135
+ if ( this . buildContext ) {
136
+ buf . push ( `loaderScope.buildContext = "${ this . buildContext } "` ) ;
141
137
}
142
138
buf . push ( "var dojoLoader = " + mainTemplate . requireFn + "(" + JSON . stringify ( dojoLoaderModule . id ) + ");" ) ;
143
139
buf . push ( "dojoLoader.call(loaderScope, userConfig, defaultConfig, loaderScope, loaderScope);" ) ;
@@ -205,14 +201,10 @@ but the loader specified at ${this.embeddedLoaderFilename} was built without the
205
201
return mainTemplate . asString ( buf ) ;
206
202
}
207
203
208
- setEmitBuildContext ( ) {
209
- this . emitBuildContext = true ;
210
- }
211
-
212
204
hash ( hash ) {
213
205
const { options} = this ;
214
206
hash . update ( "DojoAMDMainTemplate" ) ;
215
- hash . update ( "9 " ) ; // Increment this whenever the template code above changes
207
+ hash . update ( "10 " ) ; // Increment this whenever the template code above changes
216
208
if ( util . isString ( options . loaderConfig ) ) {
217
209
hash . update ( options . loaderConfig ) ; // loading the config as a module, so any any changes to the
218
210
// content will be detected at the module level
@@ -222,7 +214,83 @@ but the loader specified at ${this.embeddedLoaderFilename} was built without the
222
214
hash . update ( stringify ( options . loaderConfig ) ) ;
223
215
}
224
216
}
217
+
225
218
getDefaultFeatures ( ) {
226
219
return require ( "./defaultFeatures" ) ;
227
220
}
221
+
222
+ /**
223
+ * Relativize absolute absMids. Absolute absMids are created when global require is used with relative module ids.
224
+ * They have the form '$$root$$/<absolute-path>' where '$$root$$' can be changed using the globalContext.varName
225
+ * option. So as not to expose absolute paths on the client for security reasons, we determine the closest common
226
+ * directory for all the absolute absMids and rewrite the absMids in the form '$$root$$/<relative-path>', where the
227
+ * path is relative to the buildContext path, and the buildContext path is calculated as the globalContext
228
+ * directory relative to the closest common directory. For example, if the globalContext path is '/a/b/c/d/e'
229
+ * and the closest common directory is '/a/b/c', then the buildContext path would be'$$root$$/d/e', and the
230
+ * modified absMid for the module with absolute absMid '$$root$$/a/b/c/d/f/foo' would be '$$root$$/d/f/foo'. The
231
+ * buildContext path is emitted to the client so that runtime global require calls specifying relative module ids
232
+ * can be resolved on the client. If at runtime, the client calls global require with the module id '../f/foo',
233
+ * then the absMid for the module will be computed by resolving the specified module id against the buildContext
234
+ * path ('$$root$$/d/e'), resulting in '$$root$$/d/f/foo' and the client will locate the module by looking
235
+ * it up in the table of registered absMids.
236
+ *
237
+ * In the event that runtime global require calls attempt to traverse the buildContext parent hierarchy
238
+ * beyond the level of the closest common directory, the globalContext.numParents option can be specified
239
+ * to indicate the number of parent directories beyond the closest common directory to include in
240
+ * the buildContext path. In the example above, a numParents value of 1 would result in the buildContext
241
+ * path changing from '$$root$$/d/e' to '$$root$$/c/d/e' and the absMid for the module with absolute path
242
+ * '/a/b/c/d/f/foo' changing from '$$root$$/d/f/foo' to '$$root$$/c/d/f/foo'.
243
+ */
244
+ relativizeAbsoluteAbsMids ( ) {
245
+ const relAbsMids = [ ] ;
246
+ const relMods = [ ] ;
247
+ var rootVarName = this . options . getGlobalContextVarName ( ) ;
248
+ // Gather the absMids that need to be rewritten, as well as the modules that contain them
249
+ this . compilation . modules . forEach ( module => {
250
+ if ( module . filterAbsMids ) {
251
+ var foundSome = false ;
252
+ module . filterAbsMids ( absMid => {
253
+ if ( absMid . startsWith ( rootVarName + "/" ) ) {
254
+ relAbsMids . push ( path . dirname ( absMid . substring ( rootVarName . length + 1 ) ) ) ;
255
+ foundSome = true ;
256
+ }
257
+ return true ;
258
+ } ) ;
259
+ if ( foundSome ) {
260
+ relMods . push ( module ) ;
261
+ }
262
+ }
263
+ } ) ;
264
+ if ( relAbsMids . length ) {
265
+ // Determine the closest common directory for all the root relative modules
266
+ var commonRoot = commondir ( relAbsMids ) ;
267
+ // Get the global context
268
+ var context = this . options . getGlobalContext ( this . compiler ) ;
269
+ // Adjust the closest common directory as specified by config
270
+ for ( var i = 0 ; i < this . options . getGlobalContextNumParents ( ) ; i ++ ) {
271
+ commonRoot = path . resolve ( commonRoot , '..' ) ;
272
+ }
273
+ // Determine the relative path from the adjusted common root to the global context and set it
274
+ // as the build context that gets adorned and emitted to the client.
275
+ var relative = path . relative ( commonRoot , context ) ;
276
+ this . buildContext = path . join ( rootVarName , relative ) . replace ( / \\ / g, '/' ) ;
277
+ if ( ! this . buildContext . endsWith ( '/' ) ) {
278
+ this . buildContext += '/' ;
279
+ }
280
+ // Now rewrite the absMids to be relative to the computed build context
281
+ relMods . forEach ( module => {
282
+ const toFix = [ ] ;
283
+ module . filterAbsMids ( absMid => {
284
+ if ( absMid . startsWith ( rootVarName + "/" ) ) {
285
+ toFix . push ( absMid . substring ( rootVarName . length + 1 ) ) ;
286
+ return false ;
287
+ }
288
+ return true ;
289
+ } ) ;
290
+ toFix . forEach ( absMid => {
291
+ module . addAbsMid ( path . normalize ( path . join ( rootVarName , relative , path . relative ( context , absMid ) ) ) . replace ( / \\ / g, '/' ) ) ;
292
+ } ) ;
293
+ } ) ;
294
+ }
295
+ }
228
296
} ;
0 commit comments