UNPKG

3.47 kBJavaScriptView Raw
1import vm from "vm";
2import path from "path";
3
4/**
5 * @name LoaderContext
6 * @property {function} cacheable
7 * @property {function} async
8 * @property {function} addDependency
9 * @property {function} loadModule
10 * @property {string} resourcePath
11 * @property {object} options
12 */
13
14/**
15 * Random placeholder. Marks the location in the source code where the result of other modules should be inserted.
16 * @type {string}
17 */
18const rndPlaceholder = "__EXTRACT_LOADER_PLACEHOLDER__" + rndNumber() + rndNumber();
19
20/**
21 * Executes the given module's src in a fake context in order to get the resulting string.
22 *
23 * @this LoaderContext
24 * @throws Error
25 * @param {string} content the module's src
26 */
27function extractLoader(content) {
28 const callback = this.async();
29 const dependencies = [];
30 const script = new vm.Script(content, {
31 filename: this.resourcePath,
32 displayErrors: true
33 });
34 const sandbox = {
35 require: (resourcePath) => {
36 const absPath = path.resolve(path.dirname(this.resourcePath), resourcePath);
37
38 // Mark the file as dependency so webpack's watcher is working
39 this.addDependency(absPath);
40
41 // If the required file is a JS-file, we just evaluate it with node's require
42 // This is necessary because of the css-loader which uses a helper module (css-base.js) to export stuff
43 if (/\.js$/i.test(resourcePath)) {
44 return require(absPath);
45 }
46
47 dependencies.push(resourcePath);
48
49 return rndPlaceholder;
50 },
51 module: {},
52 exports: {}
53 };
54
55 this.cacheable();
56
57 sandbox.module.exports = sandbox.exports;
58 script.runInNewContext(sandbox);
59
60 Promise.all(dependencies.map(loadModule, this))
61 .then(sources => sources.map(
62 // runModule may throw an error, so it's important that our promise is rejected in this case
63 (src, i) => runModule(src, dependencies[i], this.options.output.publicPath)
64 ))
65 .then(results => sandbox.module.exports.toString()
66 .replace(new RegExp(rndPlaceholder, "g"), () => results.shift())
67 )
68 .then(content => callback(null, content))
69 .catch(callback);
70}
71
72/**
73 * Loads the given module with webpack's internal module loader and returns the source code.
74 *
75 * @this LoaderContext
76 * @param {string} request
77 * @returns {Promise<string>}
78 */
79function loadModule(request) {
80 return new Promise((resolve, reject) => {
81 this.loadModule(request, (err, src) => err ? reject(err) : resolve(src));
82 });
83}
84
85/**
86 * Executes the given CommonJS module in a fake context to get the exported string. The given module is expected to
87 * just return a string without requiring further modules.
88 *
89 * @throws Error
90 * @param {string} src
91 * @param {string} filename
92 * @param {string} [publicPath]
93 * @returns {string}
94 */
95function runModule(src, filename, publicPath = "") {
96 const script = new vm.Script(src, {
97 filename,
98 displayErrors: true
99 });
100 const sandbox = {
101 module: {},
102 __webpack_public_path__: publicPath // eslint-disable-line camelcase
103 };
104
105 script.runInNewContext(sandbox);
106
107 return sandbox.module.exports.toString();
108}
109
110/**
111 * @returns {string}
112 */
113function rndNumber() {
114 return Math.random().toString().slice(2);
115}
116
117// For CommonJS interoperability
118module.exports = extractLoader;
119export default extractLoader;