Skip to content

Commit 6ee472d

Browse files
feat: import/module/export message api
1 parent 430ede3 commit 6ee472d

17 files changed

+271
-187
lines changed

README.md

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -301,73 +301,6 @@ module.exports = {
301301
};
302302
```
303303

304-
### CSS Modules
305-
306-
To begin, you'll need to install `postcss-loader` and `postcss-modules`:
307-
308-
```console
309-
$ npm install postcss-loader postcss-moduless --save-dev
310-
```
311-
312-
**webpack.config.js**
313-
314-
```js
315-
module.exports = {
316-
module: {
317-
rules: [
318-
{
319-
test: /\.modules.css$/,
320-
use: [
321-
{
322-
loader: 'css-loader',
323-
options: {
324-
importLoaders: 1,
325-
},
326-
},
327-
{
328-
loader: 'postcss-loader',
329-
options: {
330-
plugins: () => [
331-
// List of options https://github.com/css-modules/postcss-modules#usage
332-
require('postcss-modules')({
333-
// Can be 'global' or 'local',
334-
scopeBehaviour: 'local',
335-
getJSON() {},
336-
}),
337-
],
338-
},
339-
},
340-
],
341-
},
342-
],
343-
},
344-
};
345-
```
346-
347-
### Custom Export
348-
349-
Due to the [ICSS](https://github.com/css-modules/icss) specification, you can export any values from your styles.
350-
351-
Just add `:export {}` with exported values. You can use `postcss-loader` with own `postcss` plugin to automate these actions. [Writing a PostCSS Plugin](Writing a PostCSS Plugin).
352-
353-
Example:
354-
355-
**style.css**
356-
357-
```css
358-
:export {
359-
color: black;
360-
}
361-
```
362-
363-
**file.js**
364-
365-
```js
366-
import styles from 'style.css'
367-
368-
console.log(styles.color); // Output `black`
369-
```
370-
371304
## Contributing
372305

373306
Please take a moment to read our contributing guidelines if you haven't yet done so.

src/index.js

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import urlPlugin from './plugins/url';
1818
import importPlugin from './plugins/import';
1919
import Warning from './Warning';
2020
import SyntaxError from './SyntaxError';
21+
import { messageReducer } from './utils';
2122

2223
export default function loader(content, map, meta) {
2324
const options = getOptions(this) || {};
@@ -116,45 +117,32 @@ export default function loader(content, map, meta) {
116117
newMap = JSON.stringify(newMap);
117118
}
118119

119-
let moduleObj = {
120-
runtime: `module.exports = exports = require(${stringifyRequest(
121-
this,
122-
require.resolve('./runtime/api')
123-
)})(${!!sourceMap});`,
124-
imports: [],
125-
module: `exports.push([module.id, ${JSON.stringify(result.css)}, ""${
120+
const runtimeCode = `module.exports = exports = require(${stringifyRequest(
121+
this,
122+
require.resolve('./runtime/api')
123+
)})(${!!sourceMap});\n`;
124+
const importCode = messageReducer(result.messages, 'import', '', this);
125+
const moduleCode = messageReducer(
126+
result.messages,
127+
'module',
128+
`exports.push([module.id, ${JSON.stringify(result.css)}, ""${
126129
newMap ? `,${newMap}` : ''
127-
}]);`,
128-
exports: [],
129-
};
130-
131-
if (result.messages && result.messages.length > 0) {
132-
result.messages
133-
.filter(
134-
(message) => (message.type === 'css-loader' ? message : false)
135-
)
136-
.forEach((message) => {
137-
try {
138-
moduleObj = message.modify(moduleObj, this);
139-
} catch (err) {
140-
this.emitError(err);
141-
}
142-
});
143-
}
144-
145-
const { runtime, imports, module, exports } = moduleObj;
130+
}]);\n`,
131+
this
132+
);
133+
const exportCode = messageReducer(result.messages, 'export', '', this);
146134

147135
return cb(
148136
null,
149137
[
150-
runtime ? `// CSS runtime\n${runtime}` : '',
151-
imports.length > 0 ? `// CSS imports\n${imports.join('\n')}` : '',
152-
module ? `// CSS module\n${module}` : '',
153-
exports.length > 0 ? `// CSS exports\n${exports.join('\n')}` : '',
154-
].join('\n')
138+
`// CSS runtime\n${runtimeCode}\n`,
139+
importCode ? `// CSS imports\n${importCode}\n` : '',
140+
moduleCode ? `// CSS module\n${moduleCode}\n` : '',
141+
exportCode ? `// CSS exports\n${exportCode}\n` : '',
142+
].join('')
155143
);
156144
})
157-
.catch((err) => {
158-
cb(err.name === 'CssSyntaxError' ? new SyntaxError(err) : err);
145+
.catch((error) => {
146+
cb(error.name === 'CssSyntaxError' ? new SyntaxError(error) : error);
159147
});
160148
}

src/plugins/import.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,32 +101,26 @@ export default postcss.plugin(
101101

102102
result.messages.push({
103103
pluginName,
104-
type: 'css-loader',
105-
modify(moduleObj, loaderContext) {
104+
type: 'import',
105+
import(accumulator, currentValue, index, array, loaderContext) {
106106
const { url, media } = importee;
107107

108108
if (isUrlRequest(url)) {
109109
// Remove `#` from `require`
110110
const [normalizedUrl] = normalizeUrl(url);
111111

112112
// Requestable url in `@import` at-rule (`@import './style.css`)
113-
moduleObj.imports.push(
114-
`exports.i(require(${stringifyRequest(
115-
loaderContext,
116-
getImportPrefix(loaderContext, importLoaders) +
117-
urlToRequest(normalizedUrl)
118-
)}), ${JSON.stringify(media)});`
119-
);
120-
} else {
121-
// Absolute url in `@import` at-rule (`@import 'https://example.com/style.css`)
122-
moduleObj.imports.push(
123-
`exports.push([module.id, ${JSON.stringify(
124-
`@import url(${url});`
125-
)}, ${JSON.stringify(media)}]);`
126-
);
113+
return `${accumulator}exports.i(require(${stringifyRequest(
114+
loaderContext,
115+
getImportPrefix(loaderContext, importLoaders) +
116+
urlToRequest(normalizedUrl)
117+
)}), ${JSON.stringify(media)});\n`;
127118
}
128119

129-
return moduleObj;
120+
// Absolute url in `@import` at-rule (`@import 'https://example.com/style.css`)
121+
return `${accumulator}exports.push([module.id, ${JSON.stringify(
122+
`@import url(${url});`
123+
)}, ${JSON.stringify(media)}]);\n`;
130124
},
131125
});
132126
});

src/plugins/url.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,40 +112,43 @@ export default postcss.plugin(
112112
item.decl.value = item.parsed.toString();
113113
});
114114

115-
let hasURLEscapeRuntime = false;
115+
let URLEscapeRuntime = false;
116116

117117
Object.keys(urls).forEach((url) => {
118118
result.messages.push({
119119
pluginName,
120-
type: 'css-loader',
121-
modify(moduleObj, loaderContext) {
122-
if (!hasURLEscapeRuntime) {
123-
moduleObj.imports.push(
124-
`var escape = require(${stringifyRequest(
125-
loaderContext,
126-
require.resolve('../runtime/escape')
127-
)});`
128-
);
129-
130-
hasURLEscapeRuntime = true;
131-
}
132-
120+
type: 'import',
121+
import(accumulator, currentValue, index, array, loaderContext) {
133122
const placeholder = urls[url];
134123
const [normalizedUrl] = normalizeUrl(url);
124+
let URLEscapeRuntimeCode = '';
135125

136-
moduleObj.imports.push(
137-
`var ${placeholder} = escape(require(${stringifyRequest(
126+
if (!URLEscapeRuntime) {
127+
URLEscapeRuntimeCode = `var escape = require(${stringifyRequest(
138128
loaderContext,
139-
urlToRequest(normalizedUrl)
140-
)}));`
141-
);
142-
// eslint-disable-next-line no-param-reassign
143-
moduleObj.module = moduleObj.module.replace(
129+
require.resolve('../runtime/escape')
130+
)});\n`;
131+
132+
URLEscapeRuntime = true;
133+
}
134+
135+
return `${URLEscapeRuntimeCode}${accumulator}var ${placeholder} = escape(require(${stringifyRequest(
136+
loaderContext,
137+
urlToRequest(normalizedUrl)
138+
)}));\n`;
139+
},
140+
});
141+
142+
result.messages.push({
143+
pluginName,
144+
type: 'module',
145+
module(accumulator) {
146+
const placeholder = urls[url];
147+
148+
return accumulator.replace(
144149
new RegExp(placeholder, 'g'),
145150
`" + ${placeholder} + "`
146151
);
147-
148-
return moduleObj;
149152
},
150153
});
151154
});

src/utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
function messageReducer(messages, type, initialValue, loaderContext) {
2+
if (messages.length === 0) {
3+
return initialValue;
4+
}
5+
6+
return messages
7+
.filter((message) => (message.type === type ? message : false))
8+
.filter((message) => message[type] && typeof message[type] === 'function')
9+
.reduce((accumulator, currentValue, index, array) => {
10+
try {
11+
// eslint-disable-next-line no-param-reassign
12+
accumulator = currentValue[type](
13+
accumulator,
14+
currentValue,
15+
index,
16+
array,
17+
loaderContext
18+
);
19+
} catch (error) {
20+
loaderContext.emitError(error);
21+
}
22+
23+
return accumulator;
24+
}, initialValue);
25+
}
26+
27+
// eslint-disable-next-line import/prefer-default-export
28+
export { messageReducer };

test/__snapshots__/import-option.test.js.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ module.exports = exports = require(\\"../../../src/runtime/api.js\\")(false);
5757
5858
// CSS module
5959
exports.push([module.id, \\"@import url(test.css);\\\\n@import url('test.css');\\\\n@import url(\\\\\\"test.css\\\\\\");\\\\n@IMPORT url(test.css);\\\\n@import URL(test.css);\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import \\\\\\"test.css\\\\\\";\\\\n@import '';\\\\n@import \\\\\\"\\\\\\";\\\\n@import \\\\\\" \\\\\\";\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import url(test.css) screen and print;\\\\n@import url(test.css) SCREEN AND PRINT;\\\\n@import url(test.css)screen and print;\\\\n@import url(test-media.css) screen and print;\\\\n@import url(test-other.css) (min-width: 100px);\\\\n@import url(http://example.com/style.css);\\\\n@import url(http://example.com/style.css#hash);\\\\n@import url(http://example.com/other-style.css) screen and print;\\\\n@import url(\\\\\\"//example.com/style.css\\\\\\");\\\\n@import url(~package/test.css);\\\\n@import ;\\\\n@import foo-bar;\\\\n@import-normalize;\\\\n@import url('http://') :root {}\\\\n@import url('query.css?foo=1&bar=1');\\\\n@import url('other-query.css?foo=1&bar=1#hash');\\\\n\\\\n.class {\\\\n a: b c d;\\\\n}\\\\n\\\\n.foo {\\\\n @import 'path.css';\\\\n}\\\\n\\", \\"\\"]);
60+
6061
"
6162
`;
6263

@@ -174,6 +175,7 @@ Array [
174175
exports[`import true: module 1`] = `
175176
"// CSS runtime
176177
module.exports = exports = require(\\"../../../src/runtime/api.js\\")(false);
178+
177179
// CSS imports
178180
exports.i(require(\\"-!../../../src/index.js??ref--4!./test.css\\"), \\"\\");
179181
exports.i(require(\\"-!../../../src/index.js??ref--4!./test.css\\"), \\"screen and print\\");
@@ -186,8 +188,10 @@ exports.push([module.id, \\"@import url(//example.com/style.css);\\", \\"\\"]);
186188
exports.i(require(\\"-!../../../src/index.js??ref--4!package/test.css\\"), \\"\\");
187189
exports.i(require(\\"-!../../../src/index.js??ref--4!./query.css?foo=1&bar=1\\"), \\"\\");
188190
exports.i(require(\\"-!../../../src/index.js??ref--4!./other-query.css?foo=1&bar=1\\"), \\"\\");
191+
189192
// CSS module
190193
exports.push([module.id, \\"@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import '';\\\\n@import \\\\\\"\\\\\\";\\\\n@import \\\\\\" \\\\\\";\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import ;\\\\n@import foo-bar;\\\\n@import-normalize;\\\\n@import url('http://') :root {}\\\\n\\\\n.class {\\\\n a: b c d;\\\\n}\\\\n\\\\n.foo {\\\\n @import 'path.css';\\\\n}\\\\n\\", \\"\\"]);
194+
191195
"
192196
`;
193197

0 commit comments

Comments
 (0)