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: support passing a {Function} (options.insertInto) #279

Merged

Conversation

savelichalex
Copy link
Contributor

What kind of change does this PR introduce?
This is new feature, to use function in insertInto like options, that add more flexibility to loader. I.e. my case - I want to insert css into shadow root, but this is impossible right now (because ::shadow is deprecated)

Did you add tests for your changes?
Yes

If relevant, did you update the README?
Not sure about naming, need review first

Summary

Does this PR introduce a breaking change?
Pass all existed test cases

Other information

@jsf-clabot
Copy link

jsf-clabot commented Dec 4, 2017

CLA assistant check
All committers have signed the CLA.

requiredJS = [
"var el = document.createElement('div');",
"el.id = \"test-shadow\";",
// "var shadow = el.attachShadow({ mode: 'open' })", // sadly shadow dom not working in jsdom
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really want to test with shadow root at first, but jsdom not working with shadow dom. Maybe some day we can use smth like headless-chrome for testing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-ciniawsky michael-ciniawsky added this to the 0.20.0 milestone Dec 4, 2017
Copy link
Member

@michael-ciniawsky michael-ciniawsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx so far, +1 for the feature, but the implementation needs rework and triage

lib/addStyles.js Outdated
@@ -23,7 +23,11 @@ var isOldIE = memoize(function () {
return window && document && document.all && !window.atob;
});

var getElement = (function (fn) {
var getTargetFunction = function (target) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTargetFunction => getTarget

lib/addStyles.js Outdated
return document.querySelector(target);
};

var getElementCreator = function (fn) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getElementCreator => getElement

lib/addStyles.js Outdated

var getElement;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getElement => element

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this. Because in this place we create function nor element and then use it right there https://github.com/webpack-contrib/style-loader/blob/master/lib/addStyles.js#L154

requiredJS = [
"var el = document.createElement('div');",
"el.id = \"test-shadow\";",
// "var shadow = el.attachShadow({ mode: 'open' })", // sadly shadow dom not working in jsdom
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index.js Outdated
"var options = " + JSON.stringify(options),
"options.transform = transform",
"var insertIntoCallable;",
options.insertIntoCallable ? "insertIntoCallable = require(" + loaderUtils.stringifyRequest(this, "!" + path.resolve(options.insertIntoCallable)) + ")" : "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently introduces a new options to the loader (options.insertIntoCallable) ? I would be much in favor to resolve this via type checking of options.insertInto e.g typeof options.InsertInto === 'function' instead of adding yet another option to the loader. Passing a {Function} is a good idea I planned to reduce the options of style-loader whenever possible in a similar way for the next major release since most of current options are kind of style-loader specific 'weirdness' due to technical debt and legacy support in the code.

Copy link
Contributor Author

@savelichalex savelichalex Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, first time I'm trying to use existing options but loader have validation by schema and this is validation check for string. At second there is should be a path to file. As we can't use function directly we can't check it with typeof operator and have 100% guarantee that given string is path to file

Copy link
Contributor Author

@savelichalex savelichalex Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea: what if use .toString() for function and then eval (or inline) it. Not sure how it is safe

Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, first time I'm trying to use existing options but loader have validation by schema and this is validation check for string.

This can be fixed as the options validation is able to handle that via oneOf && typeof: 'function' now (the dependency in style-loader likely needs an update). Simply remove the the check for insertInto while developing for now and if needed I will help/take over at this point then

As we can't use function directly we can't check it with typeof operator and have 100% guarantee that given string is path to file

webpack.config.js

{
  insertInto: require('path/to/custom/targetFn') // {Object}
  insertInto () { // {Function}
    return document.querySelector('#foo')
  }
}

index.js L16+

// handler (within the loader phase)
const insertInto = typeof options.insertInto === 'function' ? ...
...
// pass the result to code block

Another idea: what if use .toString() for function and then eval (or inline) it. Not sure how it is safe

You can certainly try go in that direction since webpack will parse this code again so a function declaration as {String} should work (will be/should be 'evaluated' or parsed as {Function})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be fixed as the options validation is able to handle that via oneOf && typeof: 'function' now

Looks like oneOf in ajv not help, need to define new keyword

lib/addStyles.js Outdated
//}
// path/to/insertInto.js
// module.exports = function () { return document.querySelector("#foo").shadowRoot }
getElement = options.insertIntoCallable != null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var element = typeof options.insertInto === 'function' 
  ? getElement(options.insertInto) 
  : getElement(getTarget)

Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way maybe to pass the options to getTarget directly =>

var getTarget = function (target, options) {
    return typeof options.insertInto === 'function' 
      ? options.insertInto() 
      : function (target) { return document.querySelector(target); }
}

Something like that

@savelichalex
Copy link
Contributor Author

@michael-ciniawsky I can't reply directly to comment for puppeteer 😄 I really like this idea, but I don't think that this PR a good place for changing test runner :) what do you think?

@michael-ciniawsky
Copy link
Member

michael-ciniawsky commented Dec 4, 2017

@savelichalex This was just a suggestion not a requirement or the like :)

@michael-ciniawsky michael-ciniawsky changed the title feat: Add the opportunity to use function to insertInto element feat: support passing a {Function} (options.insertInto) Dec 5, 2017
@@ -14,9 +14,6 @@
"insertAt": {
"type": ["string", "object"]
},
"insertInto": {
"type": "string"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we need new keyword in ajv for this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, a newer version of schema-utils is needed

Copy link
Member

@michael-ciniawsky michael-ciniawsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also requires updates to the docs (README) then 😛

index.js Outdated
@@ -35,6 +35,14 @@ module.exports.pitch = function (request) {
"}"
].join("\n");

var insertInto = "void 0";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why void 0 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this place https://github.com/savelichalex/style-loader/blob/fe58dbe66444852d9133a00d2d1883eb9036ff41/index.js#L55 I need some explicit value. This can be undefined or null of course)

Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just var insertInto; please :) . Also please move your code to L21+

index.js Outdated
if (typeof options.insertInto === "function") {
insertInto = options.insertInto.toString();
}
if (typeof options.insertInto === "string") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check shouldn't be necessary as there isn't anything to handle within the pitching phase of the loader in case options.insertInto is a {String}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to make this check explicit, if you think that this is excess, I can delete this)

Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not mandatory (I think it isn't, but didn't test it) please remove it 😛

index.js Outdated
@@ -35,6 +35,14 @@ module.exports.pitch = function (request) {
"}"
].join("\n");

var insertInto = "void 0";
if (typeof options.insertInto === "function") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

lib/addStyles.js Outdated
return function(selector) {
if (typeof memo[selector] === "undefined") {
var styleTarget = fn.call(this, selector);
return function(insertInto) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insertInto => target

lib/addStyles.js Outdated
var styleTarget = fn.call(this, selector);
return function(insertInto) {
// If passing function in options, then use it for resolve "head" element.
// Usefull for Shadow Root style i.e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usefull =>Useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!)

@savelichalex
Copy link
Contributor Author

savelichalex commented Dec 11, 2017

@michael-ciniawsky Sorry for late response

This also requires updates to the docs (README) then 😛

Agree :)

@cthorner
Copy link

Would be nice with the shadow dom / iframe targeting to be able to load the styles to multiple targets. Maybe outside of the scope of this PR?

Thanks for adding this functionality.

@savelichalex
Copy link
Contributor Author

@cthorner Yeah, I think about it too! But unfortunately I don't know how to do it right. I'm also want to add styles not right after script evaluating. For me nice example of overriding runtime behavior is svg-sprite-loader

Copy link
Member

@michael-ciniawsky michael-ciniawsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-       var insertInto = "void 0";
        if (typeof options.insertInto === "function") {
-          insertInto = options.insertInto.toString();
+          options.insertInto = options.insertInto.toString();
        }
-       if (typeof options.insertInto === "string") {
-          insertInto = '"' + options.insertInto + '"';
-        }
  "// Prepare cssTransformation",
  "var transform;",
  options.transform ? "transform = require(" + loaderUtils.stringifyRequest(this, "!" + path.resolve(options.transform)) + ");" : "",
- "var insertInto = " + insertInto + ";" ,
  "var options = " + JSON.stringify(options), // using toString() should be working here (?)
  "options.transform = transform",
- "options.insertInto = insertInto;",

And then we are g2g I guess :)

index.js Outdated
@@ -35,6 +35,14 @@ module.exports.pitch = function (request) {
"}"
].join("\n");

var insertInto = "void 0";
Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just var insertInto; please :) . Also please move your code to L21+

index.js Outdated
if (typeof options.insertInto === "function") {
insertInto = options.insertInto.toString();
}
if (typeof options.insertInto === "string") {
Copy link
Member

@michael-ciniawsky michael-ciniawsky Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not mandatory (I think it isn't, but didn't test it) please remove it 😛

index.js Outdated
@@ -44,8 +52,10 @@ module.exports.pitch = function (request) {
"// Prepare cssTransformation",
"var transform;",
options.transform ? "transform = require(" + loaderUtils.stringifyRequest(this, "!" + path.resolve(options.transform)) + ");" : "",
"var options = " + JSON.stringify(options),
"options.transform = transform",
"var insertInto = " + insertInto + ";" ,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the reassignment on L57 ? 🙃

@savelichalex
Copy link
Contributor Author

@michael-ciniawsky I'll try to explain all this code :)
Why we need var insertInto and then assign to this variable instead of options? This is because if we just store function in options, then after JSON.stringify they quoted and in runtime will be just plain text nor function.
Why we need typeof options.insertInto === "string"? I found that without this check unspecified options become "undefined" and then in runtime throw an error, because of this.

@michael-ciniawsky michael-ciniawsky changed the base branch from master to feature/insertInto January 8, 2018 07:55
@michael-ciniawsky
Copy link
Member

michael-ciniawsky commented Jan 8, 2018

@savelichalex Please rebase once you have the time, so I can finally finish this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants