Skip to content

Add opts.preserveInjectedVariables #74

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

Merged
merged 5 commits into from
Jun 26, 2018

Conversation

akdetrick
Copy link
Contributor

@akdetrick akdetrick commented Jun 12, 2018

What is this new option?

This PR adds an option, preserveInjectedVariables, which when set to false, removes custom property
declarations added via opts.variables.

Why is this option useful?

The current behavior of opts.variables is to write a :root rule block containing custom property declarations passed from opts.variables when preserve is set to true.

When working with a single stylesheet, this behavior is exactly what you would want when using
the preserve option. Custom property declarations are added to output CSS so that preserved var() declarations can be resolved by a modern browser.

When working with CSS Modules, injected custom properties are repeated in every style module.

This cleanInjectedVariables option gives users control over where global custom property definitions live. Users can pass opts.variables so postcss-css-variables can resolve fallbacks for older browsers, and provide custom property declarations in a global stylesheet so that modern browsers can resolve var() at runtime without the need to repeat custom property declarations in every module/stylesheet transformed by this plugin.

Example

Usage in plugin options

require('postcss-css-variables')({
	preserve: true,
	preserveInjectedVariables: false,
	variables: {
		'--c-black': '#000',
		'--c-white': '#fff',
		'--font-size-big': '32px',
		...50 more variables...
	}
})

someModule.module.css transformed with preserveInjectedVariables: true

// injected declarations in every module.
:root {
	--c-black: #000;
	--c-white: #fff;
	--font-size-big: 32px;
	...50 more declarations...
}

.someElement {
	background: #000;
	background: var(--c-black);
	color: #fff;
	color: var(--c-white);
	font-size: 32px;
	font-size: var(--font-size-big);
}

someModule.module.css transformed with cleanInjectedVariables: false

// injected declarations from `opts.variables` are removed, but fallbacks
// can still be calculated, allowing the developer to include global custom
// property declarations outside of individual modules.

.someElement {
	background: #000;
	background: var(--c-black);
	color: #fff;
	color: var(--c-white);
	font-size: 32px;
	font-size: var(--font-size-big);
}

add option to clean from output variables injected via js

add option to clean from output variables injected via js
@MadLittleMods
Copy link
Owner

Was this inspired by the conversation here https://gitter.im/postcss/postcss?at=5b1eae1582b1b355c94e9efa ? cc @killtheliterate

@akdetrick
Copy link
Contributor Author

It was not inspired by that conversation, but probably covers the issue. If I understand the discussion, it looks like there's a use case for "global vars I want everywhere" without duplicating the rules or importing them.

This new option would provide a solution in this form:

  1. you define global custom properties in a base stylesheet or inline style
  2. you pass the same custom properties via JS to this postcss plugin
  3. fallbacks can be generated for files run though the plugin, but custom property definitions are not added by the plugin (they're already available in the browser global scope)

@akdetrick
Copy link
Contributor Author

@MadLittleMods Does this option make sense to merge into the mainline project, or should we plan on using a fork to meet our need of stripping js-injected -- custom property definitions from output?

As a maintainer, you might have a better idea how common this use case would be.

Copy link
Owner

@MadLittleMods MadLittleMods left a comment

Choose a reason for hiding this comment

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

.

index.js Outdated
variables: {},
// Remove variables injected via JS with the `variables` option above
// before serializing to CSS
cleanInjectedVariables: false
Copy link
Owner

Choose a reason for hiding this comment

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

I think we should call this preserveInjectedVariables: true

index.js Outdated
@@ -106,6 +113,12 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
});
variableRootRule.append(varDecl);

// mark JS-injected variables for removal if `cleanInjectedVariables`
Copy link
Owner

Choose a reason for hiding this comment

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

Just above lines 106-114, we insert a root note with the variables. We can probably just add an if-statement if(!opts.preserveInjectedVariables) instead of having to remove the decls we just added.

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'm waiting until the end to remove injected variable declarations so that they're available in the AST to calculate fallbacks for var() declarations as expected.

Copy link
Owner

Choose a reason for hiding this comment

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

@akdetrick I think the map should be sufficient though? We don't look in the AST for variables after finding them all in the beginning

test/test.js Outdated
);
test(
'should preserve var() values and clean injected declarations with `options.variables` AND `options.preserve` AND `options.cleanInjectedVariables`',
'js-defined-preserve-cleanInjected',
Copy link
Owner

Choose a reason for hiding this comment

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

js-defined-preserve-injected-false

@MadLittleMods
Copy link
Owner

@akdetrick Thanks for the contribution. I added some review comments on a alternative approach.

It seems like this use-case could be covered by css-nano discardDuplicates optimisation,

README.md Outdated
@@ -368,6 +368,12 @@ Can be a simple key-value pair or an object with a `value` property and an optio

The object keys are automatically prefixed with `--` (according to CSS custom property syntax) if you do not provide it.

### `cleanInjectedVariables` (default: `false`)
Copy link
Owner

Choose a reason for hiding this comment

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

preserveInjectedVariables (default: true)

README.md Outdated
@@ -368,6 +368,12 @@ Can be a simple key-value pair or an object with a `value` property and an optio

The object keys are automatically prefixed with `--` (according to CSS custom property syntax) if you do not provide it.

### `cleanInjectedVariables` (default: `false`)

Removes custom property declarations inserted via the `variables` option from final output.
Copy link
Owner

Choose a reason for hiding this comment

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

Whether to preserve the ....

@MadLittleMods
Copy link
Owner

@akdetrick This comment is still outstanding, #74 (comment)

@akdetrick
Copy link
Contributor Author

@MadLittleMods Thanks for your attention on this.

Regarding #74 - I agree it's preferable to avoid adding js-injected vars to the AST in the first place, but it may involve a non-trivial refactor of the tools in lib...

Consider the parent property in the expected map value for each js-injected variable:

	// Add the entry to the map
	prevVariableMap[variableName] = (prevVariableMap[variableName] || []).concat({
		decl: varDecl,
		prop: variableName,
		calculatedInPlaceValue: variableValue,
		isImportant: isImportant,
		variablesUsed: [],
		parent: variableRootRule, // Expects an AST node
		isUnderAtRule: false
	});

As the parent property is expecting an AST node, so a best case scenario would be to always write the parent node, but only conditionally append the declaration for each js-injected variable:

	// Add a root node to the AST
	var variableRootRule = postcss.rule({ selector: ':root' });
	css.root().prepend(variableRootRule);

	// Add the variable decl to the root node
	var varDecl = postcss.decl({
		prop: variableName,
		value: variableValue
	});

	// only append the declaration for the var if `preserveInjectedVariables` is `true`
	if (opts.preserveInjectedVariables) {
		variableRootRule.append(varDecl);
	}

This means that parent would be defined as an AST node, but the node will be empty. However, the plugin is expecting this to be a non-empty AST node. The map object doesn't exactly work separately from the AST; it holds references to AST nodes. Testing the code above will result in this error (likely one of many to fix):

     TypeError: Cannot read property 'parent' of undefined
      at findNodeAncestorWithSelector (lib/find-node-ancestor-with-selector.js:11:20)

tl;dr
The map object holds references to AST and relies on the js-injected variables to be present nodes in the AST. I'd vote for following the convention of nodesToRemoveAtEnd, which allows nodes to be written to the AST and cleans them up after transforms have completed.

@MadLittleMods MadLittleMods changed the title add cleanInjectedVariables option Add opts.preserveInjectedVariables Jun 26, 2018
Copy link
Owner

@MadLittleMods MadLittleMods left a comment

Choose a reason for hiding this comment

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

Small comment touch-ups

index.js Outdated
@@ -82,6 +85,10 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
// We use this because we don't want to modify the AST when we still need to reference these later on
var nodesToRemoveAtEnd = [];

// List of declarations injected from `opts.variables` to remove at the end
// if user passes `opts.preserveInjectedVariables`
Copy link
Owner

Choose a reason for hiding this comment

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

// if user passes opts.preserveInjectedVariables = false

index.js Outdated
@@ -106,6 +113,12 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
});
variableRootRule.append(varDecl);

// mark JS-injected variables for removal if `preserveInjectedVariables`
// option is passed.
Copy link
Owner

@MadLittleMods MadLittleMods Jun 26, 2018

Choose a reason for hiding this comment

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

// Colect JS-injected variables for removal if opts.preserveInjectedVariables = false

@MadLittleMods
Copy link
Owner

MadLittleMods commented Jun 26, 2018

@akdetrick Thanks for trying the alternative. We can go with it for now 🙇

`opts.preserveInjectedVariables = false` means remove JS injected variables
@MadLittleMods MadLittleMods merged commit 46a3b8e into MadLittleMods:master Jun 26, 2018
@MadLittleMods
Copy link
Owner

MadLittleMods commented Jun 26, 2018

@akdetrick Thanks for the contribution ❤️

Shipped in postcss-css-variables@0.9.0 and is available on npm 🚀

@akdetrick
Copy link
Contributor Author

🙌 thank you!

calebeby referenced this pull request in Pigmice2733/scouting-frontend Jun 30, 2018
This Pull Request updates dependency [postcss-css-variables](https://github.com/MadLittleMods/postcss-css-variables) from `v0.8.1` to `v0.9.0`



<details>
<summary>Release Notes</summary>

### [`v0.9.0`](https://github.com/MadLittleMods/postcss-css-variables/blob/master/CHANGELOG.md#v090---2018-6-26)
[Compare Source](MadLittleMods/postcss-css-variables@v0.8.1...v0.9.0)
### v0.9.0 - 2018-6-26
 - Adds `opts.preserveInjectedVariables`, which when set to `false`, removes the `:root { ... }` custom property declarations added via `opts.variables`
    - Thank you to [@&#8203;akdetrick] for the [contribution](`https://github.com/MadLittleMods/postcss-css-variables/pull/74`)

---

</details>




---

This PR has been generated by [Renovate Bot](https://renovatebot.com).
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.

2 participants