-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathextractLoader.js
More file actions
156 lines (135 loc) · 4.84 KB
/
extractLoader.js
File metadata and controls
156 lines (135 loc) · 4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import vm from "vm";
import path from "path";
import { getOptions } from "loader-utils";
/**
* @name LoaderContext
* @property {function} cacheable
* @property {function} async
* @property {function} addDependency
* @property {function} loadModule
* @property {string} resourcePath
* @property {object} options
*/
/**
* Random placeholder. Marks the location in the source code where the result of other modules should be inserted.
* @type {string}
*/
const rndPlaceholder = "__EXTRACT_LOADER_PLACEHOLDER__" + rndNumber() + rndNumber();
/**
* Executes the given module's src in a fake context in order to get the resulting string.
*
* @this LoaderContext
* @throws Error
* @param {string} content - the module's src
*/
function extractLoader(content) {
const callback = this.async();
const options = getOptions(this) || {};
const publicPath = getPublicPath(options, this);
const dependencies = [];
const script = new vm.Script(content, {
filename: this.resourcePath,
displayErrors: true,
});
const sandbox = {
require: resourcePath => {
const absPath = path.resolve(path.dirname(this.resourcePath), resourcePath).split("?")[0];
// If the required file is a css-loader helper, we just require it with node's require.
// If the required file should be processed by a loader we do not touch it (even if it is a .js file).
if (/^[^!]*node_modules[/\\](_css-loader@[.\d]+@)*css-loader[/\\].*\.js$/i.test(absPath)) {
// Mark the file as dependency so webpack's watcher is working for the css-loader helper.
// Other dependencies are automatically added by loadModule() below
this.addDependency(absPath);
return require(absPath); // eslint-disable-line import/no-dynamic-require
}
dependencies.push(resourcePath);
return rndPlaceholder;
},
module: {},
exports: {},
};
this.cacheable();
sandbox.module.exports = sandbox.exports;
script.runInNewContext(sandbox);
Promise.all(dependencies.map(loadModule, this))
.then(sources =>
sources.map(
// runModule may throw an error, so it's important that our promise is rejected in this case
(src, i) => runModule(src, dependencies[i], publicPath)
)
)
.then(results =>
sandbox.module.exports.toString().replace(new RegExp(rndPlaceholder, "g"), () => results.shift())
)
.then(content => callback(null, content))
.catch(callback);
}
/**
* Loads the given module with webpack's internal module loader and returns the source code.
*
* @this LoaderContext
* @param {string} request
* @returns {Promise<string>}
*/
function loadModule(request) {
return new Promise((resolve, reject) => {
// LoaderContext.loadModule automatically calls LoaderContext.addDependency for all requested modules
this.loadModule(request, (err, src) => (err ? reject(err) : resolve(src)));
});
}
/**
* Executes the given CommonJS module in a fake context to get the exported string. The given module is expected to
* just return a string without requiring further modules.
*
* @throws Error
* @param {string} src
* @param {string} filename
* @param {string} [publicPath]
* @returns {string}
*/
function runModule(src, filename, publicPath = "") {
const script = new vm.Script(src, {
filename,
displayErrors: true,
});
const sandbox = {
module: {},
__webpack_public_path__: publicPath, // eslint-disable-line camelcase
};
script.runInNewContext(sandbox);
return sandbox.module.exports.toString();
}
/**
* @returns {string}
*/
function rndNumber() {
return Math.random()
.toString()
.slice(2);
}
/**
* Retrieves the public path from the loader options, context.options (webpack <4) or context._compilation (webpack 4+).
* context._compilation is likely to get removed in a future release, so this whole function should be removed then.
* See: https://github.com/peerigon/extract-loader/issues/35
*
* @deprecated
* @param {Object} options - Extract-loader options
* @param {Object} context - Webpack loader context
* @returns {string}
*/
function getPublicPath(options, context) {
const property = "publicPath";
if (property in options) {
return options[property];
}
if (context.options && context.options.output && property in context.options.output) {
return context.options.output[property];
}
if (context._compilation && context._compilation.outputOptions && property in context._compilation.outputOptions) {
return context._compilation.outputOptions[property];
}
return "";
}
// For CommonJS interoperability
module.exports = extractLoader;
export default extractLoader;