• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpenNTF / dojo-webpack-plugin / 331

28 May 2025 09:17AM UTC coverage: 98.132% (-0.4%) from 98.488%
331

Pull #385

travis-pro

web-flow
Merge 48d9800bc into 3940fae6b
Pull Request #385: fix: util.isString is deprecated in NodeJS 24

524 of 546 branches covered (95.97%)

Branch coverage included in aggregate %.

35 of 36 new or added lines in 2 files covered. (97.22%)

1 existing line in 1 file now uncovered.

1052 of 1060 relevant lines covered (99.25%)

1892.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.25
/lib/DojoLoaderPlugin.js
1
/*
2
 * (C) Copyright HCL Technologies Ltd. 2018, 2019
3
 * (C) Copyright IBM Corp. 2017 All Rights Reserved.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
const path = require('path');
1✔
18
const fs = require('fs');
1✔
19
const vm = require("vm");
1✔
20
const { SyncBailHook } = require('tapable');
1✔
21
const { Template } = require("webpack");
1✔
22
const { pluginName, getPluginProps } = require("./DojoAMDPlugin");
1✔
23
const loaderMainModulePatch = require("../runtime/DojoLoaderNonLocalMainPatch.runtime");
1✔
24
const CommonJsRequireDependency = require("webpack/lib/dependencies/CommonJsRequireDependency");
1✔
25
const ConstDependency = require("webpack/lib/dependencies/ConstDependency");
1✔
26
const BasicEvaluatedExpression = require("webpack/lib/javascript/BasicEvaluatedExpression");
1✔
27
const buildLoader = require("../buildDojo/buildapi");
1✔
28

29
const embeddedLoaderFilenameExpression = "__embedded_dojo_loader__";
1✔
30

31
module.exports = class DojoLoaderPlugin {
1✔
32
        constructor(options) {
33
                this.options = options;
406✔
34
        }
35

36
        apply(compiler) {
37
                this.compiler = compiler;
406✔
38
                this.pluginProps = getPluginProps(compiler);
406✔
39
                Object.defineProperties(this.pluginProps, {
406✔
40
                        'dojoRequire': {
41
                                enumerable: true,
42
                                configurable: true,
43
                                get: () => this.loaderScope.require
29,946✔
44
                        },
45
                        'dojoLoader': {
46
                                enumerable: true,
47
                                configurable: true,
48
                                get: () => this.dojoLoader
21✔
49
                        },
50
                        'dojoLoaderFilename': {
51
                                enumerable: true,
52
                                configurable: true,
53
                                get: () => this.dojoLoaderFilename
21✔
54
                        },
55
                        'embeddedLoader': {
56
                                enumerable: true,
57
                                configurable: true,
58
                                get: () => this.embeddedLoader
390✔
59
                        },
60
                        'embeddedLoaderFilename': {
61
                                enumerable: true,
62
                                configurable: true,
63
                                get: () => this.embeddedLoaderFilename
2,682✔
64
                        },
65
                        'embeddedLoaderHasConfigApi': {
66
                                enumerable: true,
67
                                configurable: true,
68
                                get: () => this.embeddedLoaderHasConfigApi
387✔
69
                        }
70
                });
71
                this.pluginProps.hooks.getDojoConfig = new SyncBailHook();
406✔
72
                this.pluginProps.hooks.createDojoLoaderScope = new SyncBailHook(["loaderConfig", "loader", "filename"]);
406✔
73
                this.pluginProps.hooks.createEmbeddedLoaderScope = new SyncBailHook(["userConfig", "embeddedLoader", "filename"]);
406✔
74
                this.pluginProps.hooks.getDojoConfig.tap(pluginName, this.getBuildLoaderConfig.bind(this));
406✔
75
                this.pluginProps.hooks.createDojoLoaderScope.tap(pluginName, this.createLoaderScope.bind(this));
406✔
76
                this.pluginProps.hooks.createEmbeddedLoaderScope.tap(pluginName, this.createEmbeddedLoaderScope.bind(this));
406✔
77
                compiler.hooks.run.tapAsync(pluginName, this.run1.bind(this));
406✔
78
                compiler.hooks.run.tapAsync(pluginName, this.run2.bind(this));
406✔
79
                compiler.hooks.watchRun.tapAsync(pluginName, this.run1.bind(this));
406✔
80
                compiler.hooks.watchRun.tapAsync(pluginName, this.run2.bind(this));
406✔
81
                compiler.hooks.compilation.tap(pluginName, (compilation, params) => {
406✔
82
                        if (!this.options.isSkipCompilation(compilation)) {
391✔
83
                                let context = Object.create(this, {
390✔
84
                                        compilation: { value: compilation },
85
                                        params: { value: params }
86
                                });
87
                                compilation.hooks.succeedModule.tap(pluginName, this.addDojoDeps.bind(context));
390✔
88
                                compilation.hooks.afterOptimizeChunks.tap(pluginName, this.afterOptimizeChunks.bind(context));
390✔
89
                                // Support for the __embedded_dojo_loader__ webpack variable.  This allows applications (and unit tests)
90
                                // to require the embedded loader module with require(__embedded_dojo_loader__);
91
                                params.normalModuleFactory.hooks.parser.for('javascript/auto').tap(pluginName, parser => {
390✔
92
                                        context = Object.create(context, { parser: { value: parser } });
390✔
93
                                        parser.hooks.expression.for(embeddedLoaderFilenameExpression).tap(pluginName, this.expressionLoader.bind(context));
390✔
94
                                        parser.hooks.evaluateTypeof.for(embeddedLoaderFilenameExpression).tap(pluginName, this.evaluateTypeofLoader.bind(context));
390✔
95
                                        parser.hooks.evaluateIdentifier.for(embeddedLoaderFilenameExpression).tap(pluginName, this.evaluateIdentifierLoader.bind(context));
390✔
96
                                });
97
                        }
98
                });
99
        }
100

101
        getDojoPath(loaderConfig) {
102
                var dojoPath;
103
                if (!loaderConfig.packages || !loaderConfig.packages.some((pkg) => {
790✔
104
                        if (pkg.name === "dojo") {
99✔
105
                                return dojoPath = path.resolve(loaderConfig.baseUrl, pkg.location);
51✔
106
                        }
107
                })) {
108
                        return path.join(require.resolve("dojo/dojo.js"), "..");
739✔
109
                }
110
                return dojoPath;
51✔
111
        }
112

113
        getOrCreateEmbeddedLoader(dojoPath, loaderConfig, options, callback) {
114
                var dojoLoaderPath;
115
                if (options.loader) {
396✔
116
                        try {
380✔
117
                                dojoLoaderPath = require.resolve(options.loader);
380✔
118
                                fs.readFile(dojoLoaderPath, "utf-8", (err, content) => {
377✔
119
                                        return callback(err, content);
377✔
120
                                });
121
                        } catch (error) {
122
                                return callback(error);
3✔
123
                        }
124
                } else {
125
                        if (!options.noConsole) {
16✔
126
                                console.log("Dojo loader not specified in options.  Building the loader...");
4✔
127
                        }
128
                        const tmp = require("tmp");
16✔
129
                        // create temporary directory to hold output
130
                        tmp.dir({ unsafeCleanup: true }, (err, tempDir) => {
16✔
131
                                if (err) {
16✔
132
                                        return callback(err);
2✔
133
                                }
134
                                const featureOverrides = {};
14✔
135
                                if (typeof options.loaderConfig !== 'string') {
14✔
136
                                        // If config is not a module, then honor the 'dojo-config-api' has feature if specified
137
                                        if (loaderConfig.has && ('dojo-config-api' in loaderConfig.has) && !loaderConfig.has['dojo-config-api']) {
13✔
138
                                                featureOverrides['dojo-config-api'] = 0;
2✔
139
                                        }
140
                                }
141
                                buildLoader({
14✔
142
                                        dojoPath: path.resolve(loaderConfig.baseUrl, dojoPath, "./dojo"),         // path to dojo.js
143
                                        releaseDir: tempDir,        // target location
144
                                        has: featureOverrides,
145
                                        noConsole: options.noConsole
146
                                }).then(() => {
147
                                        options.loader = path.join(tempDir, "dojo/dojo.js");
13✔
148
                                        dojoLoaderPath = require.resolve(path.join(options.loader));
13✔
149
                                        fs.readFile(dojoLoaderPath, "utf-8", (err, content) => { // eslint-disable-line no-shadow
13✔
150
                                                callback(err, content);
13✔
151
                                        });
152
                                }).catch(err => { // eslint-disable-line no-shadow
153
                                        callback(err);
1✔
154
                                });
155
                        });
156
                }
157
        }
158

159
        createLoaderScope(loaderConfig, loader, filename) {
160
                const loaderScope = {};
414✔
161
                loaderScope.global = loaderScope.window = loaderScope;
414✔
162
                loaderScope.dojoConfig = Object.assign({}, loaderConfig);
414✔
163
                loaderScope.dojoConfig.has = Object.assign({}, this.getDefaultFeaturesForEmbeddedLoader(), loaderScope.dojoConfig.has, { "dojo-config-api": 1, "dojo-publish-privates": 1 });
414✔
164
                var context = vm.createContext(loaderScope);
414✔
165
                const patch = "(function(loaderScope){" + Template.getFunctionContent(loaderMainModulePatch) + "})(global);";
414✔
166
                vm.runInContext('(function(global, window) {' + loader + patch + '});', context, filename).call(context, context);
414✔
167
                return loaderScope;
414✔
168
        }
169

170
        createEmbeddedLoaderScope(userConfig, embeddedLoader, filename) {
171
                const loaderScope = {};
780✔
172
                const defaultConfig = { hasCache: {}, modules: {} };
780✔
173
                loaderScope.global = loaderScope.window = loaderScope;
780✔
174
                var context = vm.createContext(loaderScope);
780✔
175
                vm.runInContext("var module = {};" + embeddedLoader, context, filename).call(context, userConfig, defaultConfig, context, context);
780✔
176
                return loaderScope;
780✔
177
        }
178

179
        getBuildLoaderConfig() {
180
                var loaderConfig = this.options.loaderConfig;
1,173✔
181
                if (typeof loaderConfig === 'string') {
1,173✔
182
                        loaderConfig = require(loaderConfig);
114✔
183
                        if (loaderConfig && loaderConfig.__esModule && loaderConfig.default) {
114!
NEW
UNCOV
184
                                loaderConfig = loaderConfig.default;
×
185
                        }
186
                }
187
                if (typeof loaderConfig === 'function') {
1,173✔
188
                        loaderConfig = loaderConfig(this.options.buildEnvironment || this.options.environment || {});
162✔
189
                }
190
                loaderConfig.baseUrl = path.resolve(this.compiler.context, loaderConfig.baseUrl || ".").replace(/\\/g, "/");
1,173✔
191
                return loaderConfig;
1,173✔
192
        }
193

194
        run1(__, callback) {
195
                // Load the Dojo loader and get the require function into loaderScope
196
                var loaderConfig = this.pluginProps.hooks.getDojoConfig.call();
397✔
197
                var dojoPath;
198
                try {
397✔
199
                        dojoPath = this.getDojoPath(loaderConfig);
397✔
200
                } catch (e) {
201
                        return callback(e);
1✔
202
                }
203
                this.dojoLoaderFilename = path.join(dojoPath, "dojo.js");
396✔
204
                fs.readFile(this.dojoLoaderFilename, 'utf-8', (err, content) => {
396✔
205
                        this.dojoLoader = content;
396✔
206
                        if (err) return callback(err);
396✔
207
                        //callSync(this.compiler, "dojo-loader", content, filename);
208
                        this.loaderScope = this.pluginProps.hooks.createDojoLoaderScope.call(loaderConfig, content, this.dojoLoaderFilename);
393✔
209
                        return callback();
393✔
210
                });
211
        }
212

213
        run2(__, callback) {
214
                // Load the Dojo loader and get the require function into loaderScope
215
                var loaderConfig = this.pluginProps.hooks.getDojoConfig.call();
395✔
216
                var dojoPath;
217
                try {
395✔
218
                        dojoPath = this.getDojoPath(loaderConfig);
395✔
219
                } catch (e) {
220
                        return callback(e);
1✔
221
                }
222
                this.getOrCreateEmbeddedLoader(dojoPath, loaderConfig, this.options, (err, content) => {
394✔
223
                        // options.loader specifies path to the embedded loader (set by createEmbeddedLoader if created)
224
                        if (!err) {
394✔
225
                                var scope = this.createEmbeddedLoaderScope({ packages: [{ name: "dojo", location: "./dojo" }] }, content, this.options.loader);
390✔
226
                                this.embeddedLoader = content;
390✔
227
                                this.embeddedLoaderFilename = this.options.loader;
390✔
228
                                this.embeddedLoaderHasConfigApi = !!scope.require.packs;
390✔
229
                        }
230
                        callback(err);
394✔
231
                });
232
        }
233

234
        addDojoDeps(module) {
235
                const { options } = this;
3,960✔
236
                if (!this.compilation.moduleGraph.getIssuer(module)) {
3,960✔
237
                        // No issuer generally means an entry module, so add a Dojo loader dependency.  It doesn't
238
                        // hurt to add extra dependencies because the Dojo loader module will be removed from chunks
239
                        // that don't need it in the 'after-optimize-chunks' handler below.
240
                        var loaderDep = new CommonJsRequireDependency(options.loader);
390✔
241
                        loaderDep.loc = {
390✔
242
                                start: { line: -1, column: 0 },
243
                                end: { line: -1, column: 0 },
244
                                index: -2
245
                        };
246
                        module.addDependency(loaderDep);
390✔
247
                        if (typeof options.loaderConfig === 'string') {
390✔
248
                                var configDep = new CommonJsRequireDependency(options.loaderConfig);
39✔
249
                                configDep.loc = {
39✔
250
                                        start: { line: -1, column: 0 },
251
                                        end: { line: -1, column: 0 },
252
                                        index: -1
253
                                };
254
                                module.addDependency(configDep);
39✔
255
                        }
256
                }
257
        }
258

259
        afterOptimizeChunks(chunks) {
260
                // Get the loader and loader config
261
                const { options, compilation } = this;
387✔
262
                const loaderModule = Array.from(compilation.modules).find((module) => { return module.rawRequest === options.loader; });
2,278✔
263
                const configModule = (typeof options.loaderConfig === 'string') &&
387✔
264
                        Array.from(compilation.modules).find((module) => { return module.rawRequest === options.loaderConfig; });
130✔
265

266
                // Ensure that the Dojo loader, and optionally the loader config, are included
267
                // only in the entry chunks that contain the webpack runtime.
268
                chunks.forEach((chunk) => {
387✔
269
                        if (chunk.hasRuntime()) {
519✔
270
                                if (!loaderModule) {
386✔
271
                                        throw Error("Can't locate " + options.loader + " in compilation");
1✔
272
                                }
273
                                if (typeof options.loaderConfig === 'string' && !configModule) {
385✔
274
                                        throw Error("Can't locate " + options.loaderConfig + " in compilation");
1✔
275
                                }
276
                                if (!this.compilation.chunkGraph.isModuleInChunk(loaderModule, chunk)) {
384✔
277
                                        this.compilation.chunkGraph.connectChunkAndModule(chunk, loaderModule);
6✔
278
                                }
279
                                if (configModule && !this.compilation.chunkGraph.isModuleInChunk(configModule, chunk)) {
384✔
280
                                        this.compilation.chunkGraph.connectChunkAndModule(chunk, configModule);
3✔
281
                                }
282
                        } else if (loaderModule) {
133✔
283
                                if (this.compilation.chunkGraph.isModuleInChunk(loaderModule, chunk)) {
132✔
284
                                        this.compilation.chunkGraph.disconnectChunkAndModule(chunk, loaderModule);
6✔
285
                                }
286
                                if (configModule && this.compilation.chunkGraph.isModuleInChunk(configModule, chunk)) {
132✔
287
                                        this.compilation.chunkGraph.disconnectChunkAndModule(chunk, configModule);
3✔
288
                                }
289
                        }
290
                });
291
        }
292

293
        expressionLoader(expr) {
294
                // change __embedded_dojo_loader__ expressions in the source to the filename value as a string.
295
                const { parser } = this;
36✔
296
                const fn = parser.hooks.evaluateIdentifier.for(embeddedLoaderFilenameExpression).call(expr).string.replace(/\\/g, "\\\\");
36✔
297
                const dep = new ConstDependency("\"" + fn + "\"", expr.range);
36✔
298
                dep.loc = expr.loc;
36✔
299
                parser.state.current.addDependency(dep);
36✔
300
                return true;
36✔
301
        }
302

303
        evaluateTypeofLoader(expr) {
304
                // implement typeof operator for the expression
305
                var result = new BasicEvaluatedExpression().setString("string");
13✔
306
                if (expr) {
13✔
307
                        result.setRange(expr.range);
12✔
308
                }
309
                return result;
13✔
310
        }
311

312
        evaluateIdentifierLoader(expr) {
313
                var result = new BasicEvaluatedExpression().setString(this.embeddedLoaderFilename);
126✔
314
                if (expr) {
126!
315
                        result.setRange(expr.range);
126✔
316
                }
317
                return result;
126✔
318
        }
319

320
        getDefaultFeaturesForEmbeddedLoader() {
321
                return require("../buildDojo/loaderDefaultFeatures");
414✔
322
        }
323
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc