Skip to content

Commit

Permalink
feat: added the exportType option
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Sep 18, 2021
1 parent bce2c17 commit c6d2066
Show file tree
Hide file tree
Showing 22 changed files with 3,441 additions and 129 deletions.
192 changes: 180 additions & 12 deletions README.md
Expand Up @@ -87,14 +87,15 @@ If, for one reason or another, you need to extract CSS as a file (i.e. do not st

## Options

| Name | Type | Default | Description |
| :-----------------------------------: | :-------------------------: | :----------------: | :----------------------------------------------------------------------------------------------------------------------- |
| **[`url`](#url)** | `{Boolean\|Object}` | `true` | Allows to enables/disables `url()`/`image-set()` functions handling |
| **[`import`](#import)** | `{Boolean\|Object}` | `true` | Allows to enables/disables `@import` at-rules handling |
| **[`modules`](#modules)** | `{Boolean\|String\|Object}` | `{auto: true}` | Allows to enables/disables or setup CSS Modules options |
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps |
| **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Allows enables/disables or setups number of loaders applied before CSS loader for `@import`/CSS Modules and ICSS imports |
| **[`esModule`](#esmodule)** | `{Boolean}` | `true` | Use ES modules syntax |
| Name | Type | Default | Description |
| :-----------------------------------: | :------------------------------: | :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **[`url`](#url)** | `{Boolean\|Object}` | `true` | Allows to enables/disables `url()`/`image-set()` functions handling |
| **[`import`](#import)** | `{Boolean\|Object}` | `true` | Allows to enables/disables `@import` at-rules handling |
| **[`modules`](#modules)** | `{Boolean\|String\|Object}` | `{auto: true}` | Allows to enables/disables or setup CSS Modules options |
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps |
| **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Allows enables/disables or setups number of loaders applied before CSS loader for `@import`/CSS Modules and ICSS imports |
| **[`esModule`](#esmodule)** | `{Boolean}` | `true` | Use ES modules syntax |
| **[`exportType`](#exporttype)** | `{'array' \| 'css-style-sheet'}` | `array` | Allows exporting styles as array with modules or [constructable stylesheet](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) (i.e. [`CSSStyleSheet`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet)) |

### `url`

Expand Down Expand Up @@ -1269,6 +1270,173 @@ module.exports = {
};
```

### `exportType`

Type: `'array' | 'css-style-sheet'`
Default: `'array'`

Allows exporting styles as array with modules or [constructable stylesheet](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) (i.e. [`CSSStyleSheet`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet)).
Default value is `'array'`, i.e. loader exports array of modules with specific API which is used in `style-loader` or other.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
assert: { type: "css" },
loader: "css-loader",
options: {
exportType: "css-style-sheet",
},
},
],
},
};
```

**src/index.js**

```js
import sheet from "./styles.css" assert { type: "css" };

document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];
```

#### `'array'`

The default export is array of modules with specific API which is used in `style-loader` or other.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/i,
use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"],
},
],
},
};
```

**src/index.js**

```js
// `style-loader` applies styles to DOM
import "./styles.css";
```

#### `'css-style-sheet'`

> `@import` rules not yet allowed, more [information](https://web.dev/css-module-scripts/#@import-rules-not-yet-allowed)
> ⚠ You don't need [`style-loader`](https://github.com/webpack-contrib/style-loader) anymore, please remove it.
> ⚠ The `esModules` option should be enabled if you want to use it with [`CSS modules`](https://github.com/webpack-contrib/css-loader#modules), by default for locals will be used [named export](https://github.com/webpack-contrib/css-loader#namedexport).
> ⚠ Source maps are not currently supported in `Chrome` due [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1174094&q=CSSStyleSheet%20source%20maps&can=2)
The default export is a [constructable stylesheet](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) (i.e. [`CSSStyleSheet`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet)).

Useful for [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) and shadow DOM.

More information:

- [Using CSS Module Scripts to import stylesheets](https://web.dev/css-module-scripts/)
- [Constructable Stylesheets: seamless reusable styles](https://developers.google.com/web/updates/2019/02/constructable-stylesheets)

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
assert: { type: "css" },
loader: "css-loader",
options: {
exportType: "css-style-sheet",
},
},

// For Sass/SCSS:
//
// {
// assert: { type: "css" },
// rules: [
// {
// loader: "css-loader",
// options: {
// exportType: "css-style-sheet",
// // Other options
// },
// },
// {
// loader: "sass-loader",
// options: {
// // Other options
// },
// },
// ],
// },
],
},
};
```

**src/index.js**

```js
// Example for Sass/SCSS:
// import sheet from "./styles.scss" assert { type: "css" };

// Example for CSS modules:
// import sheet, { myClass } from "./styles.scss" assert { type: "css" };

// Example for CSS:
import sheet from "./styles.css" assert { type: "css" };

document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];
```

For migration purposes, you can use the following configuration:

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
oneOf: [
{
assert: { type: "css" },
loader: "css-loader",
options: {
exportType: "css-style-sheet",
// Other options
},
},
{
use: [
"style-loader",
{
loader: "css-loader",
options: {
// Other options
},
},
],
},
],
},
],
},
};
```

## Examples

### Recommend
Expand All @@ -1289,7 +1457,7 @@ module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
test: /\.(sa|sc|c)ss$/i,
use: [
devMode ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader",
Expand Down Expand Up @@ -1512,8 +1680,8 @@ module.exports = {
// --------
// SCSS ALL EXCEPT MODULES
{
test: /\.scss$/,
exclude: /\.module\.scss$/,
test: /\.scss$/i,
exclude: /\.module\.scss$/i,
use: [
{
loader: "style-loader",
Expand All @@ -1535,7 +1703,7 @@ module.exports = {
// --------
// SCSS MODULES
{
test: /\.module\.scss$/,
test: /\.module\.scss$/i,
use: [
{
loader: "style-loader",
Expand Down
2 changes: 2 additions & 0 deletions src/Warning.js
@@ -1,7 +1,9 @@
export default class Warning extends Error {
constructor(warning) {
super(warning);

const { text, line, column } = warning;

this.name = "Warning";

// Based on https://github.com/postcss/postcss/blob/master/lib/warning.es6#L74
Expand Down
28 changes: 3 additions & 25 deletions src/index.js
Expand Up @@ -55,24 +55,13 @@ export default async function loader(content, map, meta) {
const importPluginApi = [];

if (shouldUseImportPlugin(options)) {
const resolver = this.getResolve({
dependencyType: "css",
conditionNames: ["style"],
mainFields: ["css", "style", "main", "..."],
mainFiles: ["index", "..."],
extensions: [".css", "..."],
preferRelative: true,
});

plugins.push(
importParser({
isCSSStyleSheet: options.exportType === "css-style-sheet",
loaderContext: this,
imports: importPluginImports,
api: importPluginApi,
context: this.context,
rootContext: this.rootContext,
resourcePath: this.resourcePath,
filter: options.import.filter,
resolver,
urlHandler: (url) =>
stringifyRequest(
this,
Expand Down Expand Up @@ -114,24 +103,13 @@ export default async function loader(content, map, meta) {
const needToUseIcssPlugin = shouldUseIcssPlugin(options);

if (needToUseIcssPlugin) {
const icssResolver = this.getResolve({
dependencyType: "icss",
conditionNames: ["style"],
extensions: ["..."],
mainFields: ["css", "style", "main", "..."],
mainFiles: ["index", "..."],
preferRelative: true,
});

plugins.push(
icssParser({
loaderContext: this,
imports: icssPluginImports,
api: icssPluginApi,
replacements,
exports,
context: this.context,
rootContext: this.rootContext,
resolver: icssResolver,
urlHandler: (url) =>
stringifyRequest(
this,
Expand Down
5 changes: 5 additions & 0 deletions src/options.json
Expand Up @@ -193,6 +193,11 @@
"description": "Use the ES modules syntax.",
"link": "https://github.com/webpack-contrib/css-loader#esmodule",
"type": "boolean"
},
"exportType": {
"description": "Allows exporting styles as array with modules or constructable stylesheet (i.e. `CSSStyleSheet`).",
"link": "https://github.com/webpack-contrib/css-loader#exporttype",
"enum": ["array", "css-style-sheet"]
}
},
"type": "object"
Expand Down
21 changes: 16 additions & 5 deletions src/plugins/postcss-icss-parser.js
Expand Up @@ -11,6 +11,16 @@ const plugin = (options = {}) => {
const imports = new Map();
const tasks = [];

const { loaderContext } = options;
const resolver = loaderContext.getResolve({
dependencyType: "icss",
conditionNames: ["style"],
extensions: ["..."],
mainFields: ["css", "style", "main", "..."],
mainFiles: ["index", "..."],
preferRelative: true,
});

// eslint-disable-next-line guard-for-in
for (const url in icssImports) {
const tokens = icssImports[url];
Expand All @@ -32,13 +42,14 @@ const plugin = (options = {}) => {

const request = requestify(
normalizeUrl(normalizedUrl, true),
options.rootContext
loaderContext.rootContext
);
const doResolve = async () => {
const { resolver, context } = options;
const resolvedUrl = await resolveRequests(resolver, context, [
...new Set([normalizedUrl, request]),
]);
const resolvedUrl = await resolveRequests(
resolver,
loaderContext.context,
[...new Set([normalizedUrl, request])]
);

if (!resolvedUrl) {
return;
Expand Down

0 comments on commit c6d2066

Please sign in to comment.