Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat export stylesheet #1368

Merged
merged 20 commits into from
Sep 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 180 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading