Skip to content

Commit 38ff645

Browse files
refactor: loader (#935)
1 parent 9dabcec commit 38ff645

File tree

2 files changed

+248
-199
lines changed

2 files changed

+248
-199
lines changed

src/index.js

+32-198
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,26 @@
55
import validateOptions from 'schema-utils';
66
import postcss from 'postcss';
77
import postcssPkg from 'postcss/package.json';
8-
import localByDefault from 'postcss-modules-local-by-default';
9-
import extractImports from 'postcss-modules-extract-imports';
10-
import modulesScope from 'postcss-modules-scope';
11-
import modulesValues from 'postcss-modules-values';
8+
129
import {
1310
getOptions,
1411
isUrlRequest,
15-
urlToRequest,
1612
getRemainingRequest,
1713
getCurrentRequest,
1814
stringifyRequest,
1915
} from 'loader-utils';
20-
import normalizePath from 'normalize-path';
2116

2217
import schema from './options.json';
2318
import { importParser, icssParser, urlParser } from './plugins';
2419
import {
25-
getLocalIdent,
26-
getImportPrefix,
20+
normalizeSourceMap,
21+
getModulesPlugins,
2722
placholderRegExps,
28-
camelCase,
29-
dashesCamelCase,
23+
getImportPrefix,
24+
getImportItemReplacer,
3025
getFilter,
26+
getExports,
27+
getImports,
3128
} from './utils';
3229
import Warning from './Warning';
3330
import CssSyntaxError from './CssSyntaxError';
@@ -40,35 +37,14 @@ export default function loader(content, map, meta) {
4037
const callback = this.async();
4138
const sourceMap = options.sourceMap || false;
4239

43-
/* eslint-disable no-param-reassign */
44-
if (sourceMap) {
45-
if (map) {
46-
// Some loader emit source map as string
47-
// Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
48-
if (typeof map === 'string') {
49-
map = JSON.parse(map.replace(/^\)]}'[^\n]*\n/, ''));
50-
}
51-
52-
// Source maps should use forward slash because it is URLs (https://github.com/mozilla/source-map/issues/91)
53-
// We should normalize path because previous loaders like `sass-loader` using backslash when generate source map
54-
55-
if (map.file) {
56-
map.file = normalizePath(map.file);
57-
}
58-
59-
if (map.sourceRoot) {
60-
map.sourceRoot = normalizePath(map.sourceRoot);
61-
}
62-
63-
if (map.sources) {
64-
map.sources = map.sources.map((source) => normalizePath(source));
65-
}
66-
}
40+
if (sourceMap && map) {
41+
// eslint-disable-next-line no-param-reassign
42+
map = normalizeSourceMap(map);
6743
} else {
6844
// Some loaders (example `"postcss-loader": "1.x.x"`) always generates source map, we should remove it
45+
// eslint-disable-next-line no-param-reassign
6946
map = null;
7047
}
71-
/* eslint-enable no-param-reassign */
7248

7349
// Reuse CSS AST (PostCSS AST e.g 'postcss-loader') to avoid reparsing
7450
if (meta) {
@@ -83,32 +59,7 @@ export default function loader(content, map, meta) {
8359
const plugins = [];
8460

8561
if (options.modules) {
86-
const loaderContext = this;
87-
const mode =
88-
typeof options.modules === 'boolean' ? 'local' : options.modules;
89-
90-
plugins.push(
91-
modulesValues,
92-
localByDefault({ mode }),
93-
extractImports(),
94-
modulesScope({
95-
generateScopedName: function generateScopedName(exportName) {
96-
const localIdentName = options.localIdentName || '[hash:base64]';
97-
const customGetLocalIdent = options.getLocalIdent || getLocalIdent;
98-
99-
return customGetLocalIdent(
100-
loaderContext,
101-
localIdentName,
102-
exportName,
103-
{
104-
regExp: options.localIdentRegExp,
105-
hashPrefix: options.hashPrefix || '',
106-
context: options.context,
107-
}
108-
);
109-
},
110-
})
111-
);
62+
plugins.push(...getModulesPlugins(options, this));
11263
}
11364

11465
if (options.import !== false) {
@@ -153,93 +104,22 @@ export default function loader(content, map, meta) {
153104
.forEach((warning) => this.emitWarning(new Warning(warning)));
154105

155106
const messages = result.messages || [];
107+
const { exportOnlyLocals, importLoaders, camelCase } = options;
156108

157109
// Run other loader (`postcss-loader`, `sass-loader` and etc) for importing CSS
158-
const importUrlPrefix = getImportPrefix(this, options.importLoaders);
110+
const importPrefix = getImportPrefix(this, importLoaders);
159111

160112
// Prepare replacer to change from `___CSS_LOADER_IMPORT___INDEX___` to `require('./file.css').locals`
161-
const importItemReplacer = (placeholder) => {
162-
const match = placholderRegExps.importItem.exec(placeholder);
163-
const idx = Number(match[1]);
164-
165-
const message = messages.find(
166-
// eslint-disable-next-line no-shadow
167-
(message) =>
168-
message.type === 'icss-import' &&
169-
message.item &&
170-
message.item.index === idx
171-
);
172-
173-
if (!message) {
174-
return placeholder;
175-
}
176-
177-
const { item } = message;
178-
const importUrl = importUrlPrefix + urlToRequest(item.url);
179-
180-
if (options.exportOnlyLocals) {
181-
return `" + require(${stringifyRequest(
182-
this,
183-
importUrl
184-
)})[${JSON.stringify(item.export)}] + "`;
185-
}
186-
187-
return `" + require(${stringifyRequest(
188-
this,
189-
importUrl
190-
)}).locals[${JSON.stringify(item.export)}] + "`;
191-
};
192-
193-
const exports = messages
194-
.filter((message) => message.type === 'export')
195-
.reduce((accumulator, message) => {
196-
const { key, value } = message.item;
197-
198-
let valueAsString = JSON.stringify(value);
199-
200-
valueAsString = valueAsString.replace(
201-
placholderRegExps.importItemG,
202-
importItemReplacer
203-
);
204-
205-
function addEntry(k) {
206-
accumulator.push(`\t${JSON.stringify(k)}: ${valueAsString}`);
207-
}
208-
209-
let targetKey;
210-
211-
switch (options.camelCase) {
212-
case true:
213-
addEntry(key);
214-
targetKey = camelCase(key);
215-
216-
if (targetKey !== key) {
217-
addEntry(targetKey);
218-
}
219-
break;
220-
case 'dashes':
221-
addEntry(key);
222-
targetKey = dashesCamelCase(key);
223-
224-
if (targetKey !== key) {
225-
addEntry(targetKey);
226-
}
227-
break;
228-
case 'only':
229-
addEntry(camelCase(key));
230-
break;
231-
case 'dashesOnly':
232-
addEntry(dashesCamelCase(key));
233-
break;
234-
default:
235-
addEntry(key);
236-
break;
237-
}
113+
const importItemReplacer = getImportItemReplacer(
114+
messages,
115+
this,
116+
importPrefix,
117+
exportOnlyLocals
118+
);
238119

239-
return accumulator;
240-
}, []);
120+
const exports = getExports(messages, camelCase, importItemReplacer);
241121

242-
if (options.exportOnlyLocals) {
122+
if (exportOnlyLocals) {
243123
return callback(
244124
null,
245125
exports.length > 0
@@ -248,69 +128,23 @@ export default function loader(content, map, meta) {
248128
);
249129
}
250130

251-
const imports = messages
252-
.filter((message) => message.type === 'import')
253-
.map((message) => {
254-
const { url } = message.item;
255-
const media = message.item.media || '';
256-
257-
if (!isUrlRequest(url)) {
258-
return `exports.push([module.id, ${JSON.stringify(
259-
`@import url(${url});`
260-
)}, ${JSON.stringify(media)}]);`;
261-
}
262-
263-
const importUrl = importUrlPrefix + urlToRequest(url);
264-
265-
return `exports.i(require(${stringifyRequest(
266-
this,
267-
importUrl
268-
)}), ${JSON.stringify(media)});`;
269-
}, this);
270-
271131
let cssAsString = JSON.stringify(result.css).replace(
272132
placholderRegExps.importItemG,
273133
importItemReplacer
274134
);
275135

276-
// Helper for ensuring valid CSS strings from requires
277-
let hasUrlEscapeHelper = false;
278-
279-
messages
280-
.filter((message) => message.type === 'url')
281-
.forEach((message) => {
282-
if (!hasUrlEscapeHelper) {
283-
imports.push(
284-
`var urlEscape = require(${stringifyRequest(
285-
this,
286-
require.resolve('./runtime/url-escape.js')
287-
)});`
288-
);
289-
290-
hasUrlEscapeHelper = true;
291-
}
292-
293-
const { item } = message;
294-
const { url, placeholder, needQuotes } = item;
295-
// Remove `#hash` and `?#hash` from `require`
296-
const [normalizedUrl, singleQuery, hashValue] = url.split(/(\?)?#/);
297-
const hash =
298-
singleQuery || hashValue
299-
? `"${singleQuery ? '?' : ''}${hashValue ? `#${hashValue}` : ''}"`
300-
: '';
136+
const imports = getImports(messages, importPrefix, this, (message) => {
137+
if (message.type !== 'url') {
138+
return;
139+
}
301140

302-
imports.push(
303-
`var ${placeholder} = urlEscape(require(${stringifyRequest(
304-
this,
305-
urlToRequest(normalizedUrl)
306-
)})${hash ? ` + ${hash}` : ''}${needQuotes ? ', true' : ''});`
307-
);
141+
const { placeholder } = message.item;
308142

309-
cssAsString = cssAsString.replace(
310-
new RegExp(placeholder, 'g'),
311-
() => `" + ${placeholder} + "`
312-
);
313-
});
143+
cssAsString = cssAsString.replace(
144+
new RegExp(placeholder, 'g'),
145+
() => `" + ${placeholder} + "`
146+
);
147+
});
314148

315149
const runtimeCode = `exports = module.exports = require(${stringifyRequest(
316150
this,

0 commit comments

Comments
 (0)