diff --git a/README.md b/README.md index 55fa1b3b..f7c40dc6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ module.exports = { | [**`injectType`**](#injecttype) | `{String}` | `styleTag` | Allows to setup how styles will be injected into the DOM | | [**`attributes`**](#attributes) | `{Object}` | `{}` | Adds custom attributes to tag | | [**`insert`**](#insert) | `{String\|Function}` | `head` | Inserts tag at the given position into the DOM | -| [**`styleTagTransform`**](#styleTagTransform) | `{Function}` | `undefined` | Transform tag and css when insert 'style' tag into the DOM | +| [**`styleTagTransform`**](#styleTagTransform) | `{String\|Function}` | `undefined` | Transform tag and css when insert 'style' tag into the DOM | | [**`base`**](#base) | `{Number}` | `true` | Sets module ID base (DLLPlugin) | | [**`esModule`**](#esmodule) | `{Boolean}` | `true` | Use ES modules syntax | @@ -542,9 +542,41 @@ Insert styles at top of `head` tag. ### `styleTagTransform` -Type: `Function` +Type: `String | Function` Default: `undefined` +#### `String` + +Allows to setup absolute path to custom function that allows to override default behavior styleTagTransform. + +> ⚠ Do not forget that this code will be used in the browser and not all browsers support latest ECMA features like `let`, `const`, `arrow function expression` and etc, we recommend use only ECMA 5 features, but it is depends what browsers you want to support + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: "style-loader", + options: { + injectType: "styleTag", + styleTagTransform: require.resolve("module-path"), + }, + }, + "css-loader", + ], + }, + ], + }, +}; +``` + +#### `Function` + Transform tag and css when insert 'style' tag into the DOM. > ⚠ Do not forget that this code will be used in the browser and not all browsers support latest ECMA features like `let`, `const`, `arrow function expression` and etc, we recommend use only ECMA 5 features, but it is depends what browsers you want to support diff --git a/src/index.js b/src/index.js index c6e60fda..5f3c0711 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ import { getExportLazyStyleCode, getSetAttributesCode, getInsertOptionCode, + getStyleTagTransformFnCode, } from "./utils"; import schema from "./options.json"; @@ -26,7 +27,6 @@ const loaderAPI = () => {}; loaderAPI.pitch = function loader(request) { const options = this.getOptions(schema); const injectType = options.injectType || "styleTag"; - const { styleTagTransform } = options; const esModule = typeof options.esModule !== "undefined" ? options.esModule : true; const runtimeOptions = {}; @@ -46,20 +46,12 @@ loaderAPI.pitch = function loader(request) { ? "module-path" : "selector"; - const styleTagTransformFn = - typeof styleTagTransform === "function" - ? styleTagTransform.toString() - : `function(css, style){ - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - while (style.firstChild) { - style.removeChild(style.firstChild); - } - - style.appendChild(document.createTextNode(css)); - } - }`; + const styleTagTransformType = + typeof options.styleTagTransform === "function" + ? "function" + : options.styleTagTransform && path.isAbsolute(options.styleTagTransform) + ? "module-path" + : "default"; switch (injectType) { case "linkTag": { @@ -103,6 +95,13 @@ ${esModule ? "export default {}" : ""}`; ${getImportInsertBySelectorCode(esModule, this, insertType, options)} ${getSetAttributesCode(esModule, this, options)} ${getImportInsertStyleElementCode(esModule, this)} + ${getStyleTagTransformFnCode( + esModule, + this, + options, + isSingleton, + styleTagTransformType + )} ${getImportStyleContentCode(esModule, this, request)} ${isAuto ? getImportIsOldIECode(esModule, this) : ""} ${ @@ -120,7 +119,7 @@ var refs = 0; var update; var options = ${JSON.stringify(runtimeOptions)}; -${getStyleTagTransformFn(styleTagTransformFn, isSingleton)}; +${getStyleTagTransformFn(options, isSingleton)}; options.setAttributes = setAttributes; ${getInsertOptionCode(insertType, options)} options.domAPI = ${getdomAPI(isAuto)}; @@ -162,6 +161,13 @@ ${getExportLazyStyleCode(esModule, this, request)} ${getImportInsertBySelectorCode(esModule, this, insertType, options)} ${getSetAttributesCode(esModule, this, options)} ${getImportInsertStyleElementCode(esModule, this)} + ${getStyleTagTransformFnCode( + esModule, + this, + options, + isSingleton, + styleTagTransformType + )} ${getImportStyleContentCode(esModule, this, request)} ${isAuto ? getImportIsOldIECode(esModule, this) : ""} ${ @@ -172,7 +178,7 @@ ${getExportLazyStyleCode(esModule, this, request)} var options = ${JSON.stringify(runtimeOptions)}; -${getStyleTagTransformFn(styleTagTransformFn, isSingleton)}; +${getStyleTagTransformFn(options, isSingleton)}; options.setAttributes = setAttributes; ${getInsertOptionCode(insertType, options)} options.domAPI = ${getdomAPI(isAuto)}; diff --git a/src/options.json b/src/options.json index f80f0a08..7500ddd5 100644 --- a/src/options.json +++ b/src/options.json @@ -39,7 +39,14 @@ }, "styleTagTransform": { "description": "Transform tag and css when insert 'style' tag into the DOM", - "instanceof": "Function" + "anyOf": [ + { + "type": "string" + }, + { + "instanceof": "Function" + } + ] } }, "additionalProperties": false diff --git a/src/runtime/styleTagTransform.js b/src/runtime/styleTagTransform.js new file mode 100644 index 00000000..8098d895 --- /dev/null +++ b/src/runtime/styleTagTransform.js @@ -0,0 +1,14 @@ +/* istanbul ignore next */ +function styleTagTransform(css, style) { + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + while (style.firstChild) { + style.removeChild(style.firstChild); + } + + style.appendChild(document.createTextNode(css)); + } +} + +module.exports = styleTagTransform; diff --git a/src/utils.js b/src/utils.js index a456c666..168624e2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -284,10 +284,49 @@ function getImportIsOldIECode(esModule, loaderContext) { : `var isOldIE = require(${modulePath});`; } -function getStyleTagTransformFn(styleTagTransformFn, isSingleton) { +function getStyleTagTransformFnCode( + esModule, + loaderContext, + options, + isSingleton, + styleTagTransformType +) { + if (isSingleton) { + return ""; + } + + if (styleTagTransformType === "default") { + const modulePath = stringifyRequest( + loaderContext, + `!${path.join(__dirname, "runtime/styleTagTransform.js")}` + ); + + return esModule + ? `import styleTagTransformFn from ${modulePath};` + : `var styleTagTransformFn = require(${modulePath});`; + } + + if (styleTagTransformType === "module-path") { + const modulePath = stringifyRequest( + loaderContext, + `${options.styleTagTransform}` + ); + + return esModule + ? `import styleTagTransformFn from ${modulePath};` + : `var styleTagTransformFn = require(${modulePath});`; + } + + return ""; +} + +function getStyleTagTransformFn(options, isSingleton) { + // Todo remove "function" type for styleTagTransform option in next major release, because code duplication occurs. Leave require.resolve() return isSingleton ? "" - : `options.styleTagTransform = ${styleTagTransformFn}`; + : typeof options.styleTagTransform === "function" + ? `options.styleTagTransform = ${options.styleTagTransform.toString()}` + : `options.styleTagTransform = styleTagTransformFn`; } function getExportStyleCode(esModule, loaderContext, request) { @@ -356,4 +395,5 @@ export { getExportLazyStyleCode, getSetAttributesCode, getInsertOptionCode, + getStyleTagTransformFnCode, }; diff --git a/test/__snapshots__/styleTagTransform-option.test.js.snap b/test/__snapshots__/styleTagTransform-option.test.js.snap index 59cf33eb..bdbaebd2 100644 --- a/test/__snapshots__/styleTagTransform-option.test.js.snap +++ b/test/__snapshots__/styleTagTransform-option.test.js.snap @@ -50,6 +50,32 @@ exports[`"styleTagTransform" option should work when the "styleTagTransform" opt exports[`"styleTagTransform" option should work when the "styleTagTransform" option is not specify: warnings 1`] = `Array []`; +exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: errors 1`] = `Array []`; + +exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: warnings 1`] = `Array []`; + exports[`"styleTagTransform" option should work when the "styleTagTransform" option is specify and injectType lazyStyleTag: DOM 1`] = ` " style-loader test diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index f8b48aee..6bd14287 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -32,20 +32,22 @@ exports[`validate options should throw an error on the "insert" option with "tru exports[`validate options should throw an error on the "styleTagTransform" option with "[]" value 1`] = ` "Invalid options object. Style Loader has been initialized using an options object that does not match the API schema. - - options.styleTagTransform should be an instance of function. - -> Transform tag and css when insert 'style' tag into the DOM" + - options.styleTagTransform should be one of these: + string | function + -> Transform tag and css when insert 'style' tag into the DOM + Details: + * options.styleTagTransform should be a string. + * options.styleTagTransform should be an instance of function." `; exports[`validate options should throw an error on the "styleTagTransform" option with "true" value 1`] = ` "Invalid options object. Style Loader has been initialized using an options object that does not match the API schema. - - options.styleTagTransform should be an instance of function. - -> Transform tag and css when insert 'style' tag into the DOM" -`; - -exports[`validate options should throw an error on the "styleTagTransform" option with "true" value 2`] = ` -"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema. - - options.styleTagTransform should be an instance of function. - -> Transform tag and css when insert 'style' tag into the DOM" + - options.styleTagTransform should be one of these: + string | function + -> Transform tag and css when insert 'style' tag into the DOM + Details: + * options.styleTagTransform should be a string. + * options.styleTagTransform should be an instance of function." `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` diff --git a/test/fixtures/styleTagTransform.js b/test/fixtures/styleTagTransform.js new file mode 100644 index 00000000..8ebce4e4 --- /dev/null +++ b/test/fixtures/styleTagTransform.js @@ -0,0 +1,8 @@ +function styleTagTransform(css, style) { + // eslint-disable-next-line no-param-reassign + style.innerHTML = `${css}.modify{}\n`; + + document.head.appendChild(style); +} + +module.exports = styleTagTransform; diff --git a/test/styleTagTransform-option.test.js b/test/styleTagTransform-option.test.js index 43b6b660..4938032a 100644 --- a/test/styleTagTransform-option.test.js +++ b/test/styleTagTransform-option.test.js @@ -83,4 +83,20 @@ describe('"styleTagTransform" option', () => { expect(getWarnings(stats)).toMatchSnapshot("warnings"); expect(getErrors(stats)).toMatchSnapshot("errors"); }); + + it(`should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag`, async () => { + const entry = getEntryByInjectType("simple.js", "lazyStyleTag"); + const compiler = getCompiler(entry, { + injectType: "lazyStyleTag", + styleTagTransform: require.resolve("./fixtures/styleTagTransform"), + }); + const stats = await compile(compiler); + + runInJsDom("main.bundle.js", compiler, stats, (dom) => { + expect(dom.serialize()).toMatchSnapshot("DOM"); + }); + + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index d7925347..87f9d19f 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -28,8 +28,8 @@ describe("validate options", () => { }, styleTagTransform: { // eslint-disable-next-line func-names - success: [function () {}], - failure: ["true", true, []], + success: [function () {}, require.resolve("path")], + failure: [true, []], }, unknown: { success: [],