diff --git a/README.md b/README.md index 30d8c900..f03b21e2 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,42 @@ import style from './file.css'; style.className === 'z849f98ca812'; ``` -### `Useable` +## Options + +| Name | Type | Default | Description | +| :--------------: | :------------------: | :--------: | :------------------------------------------------- | +| **`injectType`** | `{String}` | `styleTag` | Allows to setup how styles will be injected in DOM | +| **`attributes`** | `{Object}` | `{}` | Add custom attributes to tag | +| **`insertAt`** | `{String\|Object}` | `bottom` | Inserts tag at the given position | +| **`insertInto`** | `{String\|Function}` | `
` | Inserts tag into the given position | +| **`base`** | `{Number}` | `true` | Set module ID base (DLLPlugin) | + +### `injectType` + +Type: `String` +Default: `styleTag` + +Allows to setup how styles will be injected in DOM. -The `style-loader` injects the styles lazily making them useable on-demand via `style.use()` / `style.unuse()` +Possible values: + +- `styleTag` +- `singletonStyleTag` +- `lazyStyleTag` +- `lazySingletonStyleTag` +- `linkTag` + +When you `lazyStyleTag` or `lazySingletonStyleTag` value the `style-loader` injects the styles lazily making them useable on-demand via `style.use()` / `style.unuse()`. +It is named `Reference Counter API`. + +**component.js** + +```js +import style from './file.css'; + +style.use(); // = style.ref(); +style.unuse(); // = style.unref(); +``` By convention the `Reference Counter API` should be bound to `.useable.css` and the `.css` should be loaded with basic `style-loader` usage.(similar to other file types, i.e. `.useable.less` and `.less`). @@ -84,15 +117,19 @@ module.exports = { module: { rules: [ { - test: /\.css$/, - exclude: /\.useable\.css$/, + test: /\.css$/i, + exclude: /\.useable\.css$/i, use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], }, { - test: /\.useable\.css$/, + test: /\.useable\.css$/i, use: [ { - loader: 'style-loader/useable', + loader: 'style-loader', + options: { + // Can be `'lazyStyleTag'` or `'lazySingletonStyleTag'` + injectType: 'lazyStyleTag', + }, }, { loader: 'css-loader' }, ], @@ -102,27 +139,57 @@ module.exports = { }; ``` -#### `Reference Counter API` +Styles are not added on `import/require()`, but instead on call to `use`/`ref`. Styles are removed from page if `unuse`/`unref` is called exactly as often as `use`/`ref`. -**component.js** +> ⚠️ Behavior is undefined when `unuse`/`unref` is called more often than `use`/`ref`. Don't do that. + +#### `styleTag` + +Injects styles in multiple ``. It is **default** behaviour. ```js -import style from './file.css'; +import './styles.css'; +``` -style.use(); // = style.ref(); -style.unuse(); // = style.unref(); +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { loader: 'style-loader', options: { injectType: 'styleTag' } }, + 'css-loader', + ], + }, + ], + }, +}; ``` -Styles are not added on `import/require()`, but instead on call to `use`/`ref`. Styles are removed from page if `unuse`/`unref` is called exactly as often as `use`/`ref`. +The loader inject styles like: -> ⚠️ Behavior is undefined when `unuse`/`unref` is called more often than `use`/`ref`. Don't do that. +```html + + +``` -### `Url` +#### `singletonStyleTag` -It's also possible to add a URL `` instead of inlining the CSS `{String}` with `` tag. +Injects styles in one ``. ```js -import url from 'file.css'; +import './styles.css'; ``` **webpack.config.js** @@ -132,45 +199,54 @@ module.exports = { module: { rules: [ { - test: /\.css$/, - use: [{ loader: 'style-loader/url' }, { loader: 'file-loader' }], + test: /\.css$/i, + use: [ + { + loader: 'style-loader', + options: { injectType: 'singletonStyleTag' }, + }, + 'css-loader', + ], }, ], }, }; ``` +The loader inject styles like: + ```html - + ``` -## Options +#### `lazyStyleTag` -| Name | Type | Default | Description | -| :--------------: | :------------------: | :---------: | :------------------------------------------------------------------------------------------------------------------ | -| **`base`** | `{Number}` | `true` | Set module ID base (DLLPlugin) | -| **`attributes`** | `{Object}` | `{}` | Add custom attributes to `` | -| **`insertAt`** | `{String\|Object}` | `bottom` | Inserts `` at the given position | -| **`insertInto`** | `{String\|Function}` | `` | Inserts `` into the given position | -| **`singleton`** | `{Boolean}` | `undefined` | Reuses a single `` element, instead of adding/removing individual elements for each required module. | +Injects styles in multiple `` on demand (documentation above). -### `base` +```js +import styles from './styles.css'; -This setting is primarily used as a workaround for [css clashes](https://github.com/webpack-contrib/style-loader/issues/163) when using one or more [DllPlugin](https://robertknight.github.io/posts/webpack-dll-plugins/)'s. `base` allows you to prevent either the _app_'s css (or _DllPlugin2_'s css) from overwriting _DllPlugin1_'s css by specifying a css module id base which is greater than the range used by _DllPlugin1_ e.g.: +styles.use(); +``` -**webpack.dll1.config.js** +**webpack.config.js** ```js module.exports = { module: { rules: [ { - test: /\.css$/i, + test: /\.useable\.css$/i, use: [ - { - loader: 'style-loader', - }, - { loader: 'css-loader' }, + { loader: 'style-loader', options: { injectType: 'lazyStyleTag' } }, + 'css-loader', ], }, ], @@ -178,17 +254,45 @@ module.exports = { }; ``` -**webpack.dll2.config.js** +The loader inject styles like: + +```html + + +``` + +#### `lazySingletonStyleTag` + +Injects styles in one `` on demand (documentation above). + +```js +import styles from './styles.css'; + +styles.use(); +``` + +**webpack.config.js** ```js module.exports = { module: { rules: [ { - test: /\.css$/, + test: /\.useable\.css$/i, use: [ - { loader: 'style-loader', options: { base: 1000 } }, - { loader: 'css-loader' }, + { + loader: 'style-loader', + options: { injectType: 'lazySingletonStyleTag' }, + }, + 'css-loader', ], }, ], @@ -196,17 +300,39 @@ module.exports = { }; ``` -**webpack.app.config.js** +The loader generate this: + +```html + +``` + +#### `linkTag` + +Injects styles in multiple `` . + +```js +import './styles.css'; +import './other-styles.css'; +``` + +**webpack.config.js** ```js module.exports = { module: { rules: [ { - test: /\.css$/, + test: /\.css$/i, use: [ - { loader: 'style-loader', options: { base: 2000 } }, - { loader: 'css-loader' }, + { loader: 'style-loader', options: { injectType: 'linkTag' } }, + { loader: 'file-loader' }, ], }, ], @@ -214,8 +340,18 @@ module.exports = { }; ``` +The loader generate this: + +```html + + +``` + ### `attributes` +Type: `Object` +Default: `{}` + If defined, style-loader will attach given attributes with their values on `` element, instead of adding/removing individual elements for each required module. +This setting is primarily used as a workaround for [css clashes](https://github.com/webpack-contrib/style-loader/issues/163) when using one or more [DllPlugin](https://robertknight.github.io/posts/webpack-dll-plugins/)'s. `base` allows you to prevent either the _app_'s css (or _DllPlugin2_'s css) from overwriting _DllPlugin1_'s css by specifying a css module id base which is greater than the range used by _DllPlugin1_ e.g.: -> ℹ️ This option is on by default in IE9, which has strict limitations on the number of style tags allowed on a page. You can enable or disable it with the singleton option. +**webpack.dll1.config.js** -**webpack.config.js** +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: 'style-loader', + }, + { loader: 'css-loader' }, + ], + }, + ], + }, +}; +``` + +**webpack.dll2.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { loader: 'style-loader', options: { base: 1000 } }, + { loader: 'css-loader' }, + ], + }, + ], + }, +}; +``` + +**webpack.app.config.js** ```js module.exports = { @@ -369,7 +541,7 @@ module.exports = { { test: /\.css$/i, use: [ - { loader: 'style-loader', options: { singleton: true } }, + { loader: 'style-loader', options: { base: 2000 } }, { loader: 'css-loader' }, ], }, diff --git a/package.json b/package.json index b20d8898..86911bbe 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,7 @@ "defaults": "webpack-defaults" }, "files": [ - "dist", - "url.js", - "useable.js" + "dist" ], "peerDependencies": { "webpack": "^4.0.0" diff --git a/src/index.js b/src/index.js index 0199f4d4..c04b50ff 100644 --- a/src/index.js +++ b/src/index.js @@ -30,7 +30,105 @@ module.exports.pitch = function loader(request) { insertInto = `"${options.insertInto}"`; } - const hmrCode = ` + const injectType = options.injectType || 'styleTag'; + + switch (injectType) { + case 'linkTag': { + const hmrCode = this.hot + ? ` +if (module.hot) { + module.hot.accept( + ${loaderUtils.stringifyRequest(this, `!!${request}`)}, + function() { + update(require(${loaderUtils.stringifyRequest(this, `!!${request}`)})); + } + ); + + module.hot.dispose(function() { + update(); + }); +}` + : ''; + + return `var update = require(${loaderUtils.stringifyRequest( + this, + `!${path.join(__dirname, 'runtime/addStyleUrl.js')}` + )})(require(${loaderUtils.stringifyRequest( + this, + `!!${request}` + )}), ${JSON.stringify(options)}); + ${hmrCode}`; + } + + case 'lazyStyleTag': + case 'lazySingletonStyleTag': { + const isSingleton = injectType === 'lazySingletonStyleTag'; + + const hmrCode = this.hot + ? ` +if (module.hot) { + var lastRefs = module.hot.data && module.hot.data.refs || 0; + + if (lastRefs) { + exports.ref(); + if (!content.locals) { + refs = lastRefs; + } + } + + if (!content.locals) { + module.hot.accept(); + } + + module.hot.dispose(function(data) { + data.refs = content.locals ? 0 : refs; + + if (dispose) { + dispose(); + } + }); +}` + : ''; + + return `var refs = 0; +var dispose; +var content = require(${loaderUtils.stringifyRequest(this, `!!${request}`)}); +var options = ${JSON.stringify(options)}; + +options.insertInto = ${insertInto}; +options.singleton = ${isSingleton}; + +if (typeof content === 'string') content = [[module.id, content, '']]; +if (content.locals) exports.locals = content.locals; + +exports.use = exports.ref = function() { + if (!(refs++)) { + dispose = require(${loaderUtils.stringifyRequest( + this, + `!${path.join(__dirname, 'runtime/addStyles.js')}` + )})(content, options); + } + + return exports; +}; + +exports.unuse = exports.unref = function() { + if (refs > 0 && !--refs) { + dispose(); + dispose = null; + } +}; +${hmrCode} +`; + } + + case 'styleTag': + case 'singletonStyleTag': + default: { + const isSingleton = injectType === 'singletonStyleTag'; + + const hmrCode = this.hot + ? ` if (module.hot) { module.hot.accept( ${loaderUtils.stringifyRequest(this, `!!${request}`)}, @@ -67,10 +165,13 @@ if (module.hot) { module.hot.dispose(function() { update(); }); -}`; +}` + : ''; - return ` -var content = require(${loaderUtils.stringifyRequest(this, `!!${request}`)}); + return `var content = require(${loaderUtils.stringifyRequest( + this, + `!!${request}` + )}); if (typeof content === 'string') content = [[module.id, content, '']]; @@ -79,13 +180,15 @@ var insertInto; var options = ${JSON.stringify(options)} options.insertInto = ${insertInto}; +options.singleton = ${isSingleton}; var update = require(${loaderUtils.stringifyRequest( - this, - `!${path.join(__dirname, 'runtime/addStyles.js')}` - )})(content, options); + this, + `!${path.join(__dirname, 'runtime/addStyles.js')}` + )})(content, options); if (content.locals) module.exports = content.locals; -${this.hot ? hmrCode : ''} -`; +${hmrCode}`; + } + } }; diff --git a/src/options.json b/src/options.json index 05ebb503..77d6ce74 100644 --- a/src/options.json +++ b/src/options.json @@ -5,6 +5,16 @@ "description": "Set module ID base for DLLPlugin (https://github.com/webpack-contrib/style-loader#base).", "type": "number" }, + "injectType": { + "description": "Allows to setup how styles will be injected in DOM.", + "enum": [ + "styleTag", + "singletonStyleTag", + "lazyStyleTag", + "lazySingletonStyleTag", + "linkTag" + ] + }, "attributes": { "description": "Add custom attributes to tag (https://github.com/webpack-contrib/style-loader#attributes).", "type": "object" @@ -16,10 +26,6 @@ "insertInto": { "description": "Inserts into the given position (https://github.com/webpack-contrib/style-loader#insertinto).", "anyOf": [{ "type": "string" }, { "instanceof": "Function" }] - }, - "singleton": { - "description": "Reuses a single element, instead of adding/removing individual elements for each required module (https://github.com/webpack-contrib/style-loader#singleton).", - "type": "boolean" } }, "additionalProperties": false diff --git a/src/runtime/addStyleUrl.js b/src/runtime/addStyleUrl.js index 7ff4cee7..87166f3d 100644 --- a/src/runtime/addStyleUrl.js +++ b/src/runtime/addStyleUrl.js @@ -15,6 +15,15 @@ module.exports = function addStyleUrl(url, options) { options.attributes = typeof options.attributes === 'object' ? options.attributes : {}; + if (options.attributes.nonce === undefined) { + var nonce = + typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null; + + if (nonce) { + options.attributes.nonce = nonce; + } + } + const link = document.createElement('link'); link.rel = 'stylesheet'; diff --git a/src/url-loader.js b/src/url-loader.js deleted file mode 100644 index ecebd3d7..00000000 --- a/src/url-loader.js +++ /dev/null @@ -1,40 +0,0 @@ -import path from 'path'; - -import loaderUtils from 'loader-utils'; -import validateOptions from 'schema-utils'; - -import schema from './options.json'; - -module.exports = () => {}; - -module.exports.pitch = function loader(request) { - const options = loaderUtils.getOptions(this) || {}; - - validateOptions(schema, options, { - name: 'Style Loader (URL)', - baseDataPath: 'options', - }); - - const hmrCode = ` -if (module.hot) { - module.hot.accept( - ${loaderUtils.stringifyRequest(this, `!!${request}`)}, - function() { - update(require(${loaderUtils.stringifyRequest(this, `!!${request}`)})); - } - ); - - module.hot.dispose(function() { - update(); - }); -}`; - - return `var update = require(${loaderUtils.stringifyRequest( - this, - `!${path.join(__dirname, 'runtime/addStyleUrl.js')}` - )})(require(${loaderUtils.stringifyRequest( - this, - `!!${request}` - )}), ${JSON.stringify(options)}); - ${this.hot ? hmrCode : ''}`; -}; diff --git a/src/useable-loader.js b/src/useable-loader.js deleted file mode 100644 index 0204dab1..00000000 --- a/src/useable-loader.js +++ /dev/null @@ -1,87 +0,0 @@ -import path from 'path'; - -import loaderUtils from 'loader-utils'; -import validateOptions from 'schema-utils'; - -import schema from './options.json'; - -module.exports = () => {}; - -module.exports.pitch = function loader(request) { - const options = loaderUtils.getOptions(this) || {}; - - validateOptions(schema, options, { - name: 'Style Loader (Useable)', - baseDataPath: 'options', - }); - - // The variable is needed, because the function should be inlined. - // If is just stored it in options, JSON.stringify will quote - // the function and it would be just a string at runtime - let insertInto; - - if (typeof options.insertInto === 'function') { - insertInto = options.insertInto.toString(); - } - - // We need to check if it a string, or variable will be "undefined" - // and the loader crashes - if (typeof options.insertInto === 'string') { - insertInto = `"${options.insertInto}"`; - } - - const hmrCode = ` -if (module.hot) { - var lastRefs = module.hot.data && module.hot.data.refs || 0; - - if (lastRefs) { - exports.ref(); - if (!content.locals) { - refs = lastRefs; - } - } - - if (!content.locals) { - module.hot.accept(); - } - - module.hot.dispose(function(data) { - data.refs = content.locals ? 0 : refs; - - if (dispose) { - dispose(); - } - }); -}`; - - return ` -var refs = 0; -var dispose; -var content = require(${loaderUtils.stringifyRequest(this, `!!${request}`)}); -var options = ${JSON.stringify(options)}; - -options.insertInto = ${insertInto}; - -if (typeof content === 'string') content = [[module.id, content, '']]; -if (content.locals) exports.locals = content.locals; - -exports.use = exports.ref = function() { - if (!(refs++)) { - dispose = require(${loaderUtils.stringifyRequest( - this, - `!${path.join(__dirname, 'runtime/addStyles.js')}` - )})(content, options); - } - - return exports; -}; - -exports.unuse = exports.unref = function() { - if (refs > 0 && !--refs) { - dispose(); - dispose = null; - } -}; -${this.hot ? hmrCode : ''} -`; -}; diff --git a/test/__snapshots__/attributes-option.test.js.snap b/test/__snapshots__/attributes-option.test.js.snap index 903f757f..6893144a 100644 --- a/test/__snapshots__/attributes-option.test.js.snap +++ b/test/__snapshots__/attributes-option.test.js.snap @@ -1,10 +1,58 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`attributes option should add attributes to tag ("injectType" option is "linkTag"): DOM 1`] = ` +exports[`attributes option should add attributes to tag when the "injectType" option is "lazySingletonStyleTag": DOM 1`] = ` "