Can PostCSS change the content of CSS before another plugin? - postcss

Say that you have a PostCSS plugin that parse at-rules like #remove and then removes them. Here’s an example program:
const fs = require('fs');
const postcss = require('postcss');
const plugin = func => {
func.postcss = true;
return func;
};
fs.readFile('test.css', (err, css) => {
postcss([
plugin(() => ({
postcssPlugin: 'postcss-remove',
Root(root) {
console.log('---BEFORE postcss-remove---')
console.log(root.source.input.css);
root.walkAtRules('remove', atRule => {
atRule.remove();
});
console.log('---AFTER postcss-remove---')
console.log(root.source.input.css);
}
})),
plugin(() => ({
postcssPlugin: 'postcss-another',
Root(root) {
console.log('---BEFORE postcss-another---')
console.log(root.source.input.css);
console.log('---AFTER postcss-another---')
}
}))
]).process(css).then(result => {
console.log(result.css);
})
});
This is the test.css file:
#remove 'foo.js';
.foo {
color: red;
}
And this is the output:
---BEFORE postcss-remove---
#remove 'foo.js';
.foo {
color: red;
}
---AFTER postcss-remove---
#remove 'foo.js';
.foo {
color: red;
}
---BEFORE postcss-another---
#remove 'foo.js';
.foo {
color: red;
}
---AFTER postcss-another---
.foo {
color: red;
}
As you can see from the output, postcss-another still see #remove rules. Only the CSS output has #remove removed. The next plugin in the chain should not see #remove at all. I thought it was the default behavior, but it's not.
Is there a way to completely process #remove BEFORE any other plugin?

As the name implies, the .source.input.css property of CSS nodes contains the CSS code as originally fed to PostCSS, not the state of the stylesheet as subsequently manipulated. It exists mostly for the sake of being able to make use of offsets that point to the rules’ location in the original input:
root.walkAtRules('remove', atRule => {
const src = atRule.source;
atRule.remove();
console.info('removed this rule: ', src.input.css.substr(src.start.offset, src.end.offset));
});
The stylesheet data is passed between plugins not in the form of source code, but as a syntax tree, since that is what the plugins operate on anyway.
If you want to obtain source code that reflects the current state of the stylesheet, all you need to do is to convert the syntax node that interests you to a string:
module.exports = () => {
return {
postcssPlugin: 'postcss-another',
Root(root) {
console.log('---BEFORE postcss-another---')
console.log(root.toString());
console.log('---AFTER postcss-another---')
}
}
};
module.exports.postcss = true;

Related

How to solve problem with "Global CSS cannot be imported" in nextjs app?

I try to use your library in NextJS app but I accuse following error:
./node_modules/normalize.css/normalize.css
Global CSS cannot be imported from within node_modules.
Read more: https://nextjs.org/docs/messages/css-npm
Location: node_modules/react-fullpage-accordion/dist/full-page-accordion.js
I imported all like your example is written
import { FullpageAccordion, Panel } from 'react-fullpage-accordion';
import "react-fullpage-accordion/dist/react-fullpage-accordion.css";
Update.
I renamed react-fullpage-accordion.css to react-fullpage-accordion.module.css, and then try to importem as well.
Error still occur.
./node_modules/normalize.css/normalize.css
Global CSS cannot be imported from within node_modules.
Read more: https://nextjs.org/docs/messages/css-npm
Location: node_modules/react-fullpage-accordion/dist/full-page-accordion.js
think error is made by this following code
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
require("normalize.css");
var _panelContext = require("./panel-context");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
/* eslint-disable react/forbid-prop-types, no-unused-vars, import/no-unresolved, import/extensions */
var FullpageAccordion = function FullpageAccordion(_ref) {
var children = _ref.children,
height = _ref.height;
return /*#__PURE__*/_react["default"].createElement(_panelContext.PanelContextProvider, null, /*#__PURE__*/_react["default"].createElement("div", {
className: "panels",
"data-testid": "panels",
style: {
height: height || '100vh'
}
}, children));
};
FullpageAccordion.defaultProps = {
height: undefined
};
FullpageAccordion.propTypes = {
children: _propTypes["default"].node.isRequired,
height: _propTypes["default"].string
};
var _default = FullpageAccordion;
exports["default"] = _default;
but I am afraid of changing something in third part code.

Gatsby-plugin-sitemap, custom config, need to integrate pages and markdown using custom resolvePages and Serialize, what does this line of code do?

just starting with javascript and react thanks to Gatsby so excuse me if this is a total newbie question. Also just starting with posting on stackoverflow, usually just consuming content, sorry about that and if my post is incomplete or unclear in anyway.
I am building a website using GatsbyJs, and want to setup a proper sitemap using gatsby-plugin-sitemap, however i am strugling to understand what the following line of code does so i can try and customize de code to do what I need, which is integrate the pages and blog posts on the sitemap, and adding a proper lastmod when applicable. I am breaking my head but cannot get the last part to work, that is, adding lastmod when it is a blog post.
on gatsby-config.js:
{
resolve: `gatsby-plugin-sitemap`,
options: {
// Exclude specific pages or groups of pages using glob parameters
// See: https://github.com/isaacs/minimatch
// The example below will exclude the single `path/to/page` and all routes beginning with `category`
output: '/',
excludes: [`/legal/*`,`/gracias`],
query: `
{
site {
siteMetadata {
siteUrl
}
buildTime(formatString: "YYYY-MM-DD")
}
allSitePage {
nodes {
path
}
}
allMarkdownRemark(filter: {frontmatter: {path: {regex: "/blog/"}}}) {
nodes {
frontmatter {
path
date
}
}
}
}
`,
resolvePages: ({
allSitePage: { nodes: allPages },
allMarkdownRemark: { nodes: allPosts },
}) => {
const pathToDateMap = {};
allPosts.map(post => {
pathToDateMap [post.frontmatter.path] = { date: post.frontmatter.date };
});
const pages = allPages.map(page => {
return { ...page, ...pathToDateMap [page.path] };//what does this line of code do???
});
return pages;
},
serialize: ({ path, date, buildTime }) => {
let entry = {
url: path,
changefreq: 'monthly',
priority: 0.5,
};
if (date) {
entry.priority = 0.7;
entry.lastmod = date;
} else {
entry.lastmod = buildtime;
}
return entry;
}
}
}
For your knowledge both develop and build are succesful, the sitemap is generated as sitemap-index and sitemap-0.xml, and the output is there but no page has lastmod on it.
Thank you all for your help,
With this:
return { ...page, ...pathToDateMap [page.path] };
You are merging objects using the spread operator (...). So, you are returning an object that is the result of all properties of the page and pathToDateMap(at page.path position).
For example:
let person = {
firstName: 'John',
lastName: 'Doe',
age: 25,
};
let job = {
jobTitle: 'Web developer',
location: 'USA'
};
let employee = {
...person,
...job
};
console.log(employee)
In the snippet above, employee is the result of merging person and job.
The lastmod parameter is not added by default unless your source has it (WordPress normally has) but you can follow this answer https://stackoverflow.com/a/70297982/5585371 to create your own.

vscode findFiles returns nothing but npm glob returns correct results

I'm writing and vscode extension in which I need a list of the test files inside workspace.
To find the test files I'm using the default testMatch from the jest.config.js which is:
[
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)'
]
My problem is that vscode.workspace.findFiles returns empty array and I cannot set it up to get correct results, but using Glob package the output is correct.
protected async findTestFiles(
matchTestsGlobPatterns: string[]
): Promise<vscode.Uri[]> {
const testFilesUris: vscode.Uri[] = [];
const glob_testFilesUris: vscode.Uri[] = [];
const { name: workspaceName, workspaceFolders } = vscode.workspace;
if (workspaceName === undefined || workspaceFolders === undefined) {
throw new Error(`No active workspace${!workspaceFolders ? ' folders' : ''}.`);
}
for (let folderIdx = 0; folderIdx < workspaceFolders.length; folderIdx++) {
const folder = workspaceFolders[folderIdx];
// - by vscode.workspace.findFiles
for (let patternIdx = 0; patternIdx < matchTestsGlobPatterns.length; patternIdx++) {
const currentPattern = matchTestsGlobPatterns[patternIdx];
const pattern = new vscode.RelativePattern(
folder.uri.fsPath,
currentPattern
);
const files = await vscode.workspace.findFiles(
pattern,
'**/node_modules/**'
);
testFilesUris.push(...files);
}
console.log('by [vscode.workspace.findFiles]', testFilesUris.length);
// - by npm Glob
var glob = require('glob');
for (let patternIdx = 0; patternIdx < matchTestsGlobPatterns.length; patternIdx++) {
const currentPattern = matchTestsGlobPatterns[patternIdx];
const files: any[] = await new Promise((resolve, reject) => {
glob(
currentPattern,
{
absolute: true,
cwd: folder.uri.fsPath,
ignore: ['**/node_modules/**']
},
function (err: Error, files: any[]) {
if (err) {
return reject(err);
}
resolve(files);
}
);
});
glob_testFilesUris.push(...files);
}
console.log('by [npm Glob]', glob_testFilesUris.length);
}
// #todo: remove duplicates.
return testFilesUris;
}
The example console output of this function for some project is:
by [vscode.workspace.findFiles] 0
by [npm Glob] 45
Project structure:
rootFolder
src
__tests__
files.test.ts
...
utils
array.test.ts
...
So my question is how do I call vscode.workspace.findFiles to get correct results, or is there known problem with this function?
I have found some kind of answer to the question.
The problem is ?(x) in patterns. The vscode.workspace.findFiles does not work with this pattern as other packages do. If remove it from mentioned glob patterns they work except the .jsx | .tsx files are ommited.
After deep dive into vscode github's issues I have learned (here) that vscode.workspace.findFiles does not support extended patterns like ?(patterLike)

Nested postcss at-rules are not removed

Interactive Fiddle
Here is a short reproducible example for the behavior I want to achieve:
var postcss = require('postcss');
var plugin = postcss.plugin('keepme', () => (root) => {
root.walkAtRules(/keepme|removeme/, (atRule) => {
if (atRule.name === 'keepme') {
atRule.replaceWith(atRule.nodes);
} else {
atRule.remove();
}
});
});
postcss([plugin]).process(`
#keepme {
#removeme {
.selector { color: red; }
}
}
`).then(result => console.log(result.css));
Given the input
#keepme {
#removeme {
.selector { color: red; }
}
}
I would like this to return an empty string.
Instead, I receive the output
#removeme {
.selector { color: red; }
}
The #keepme rule seems to correctly replace itself with its nodes (which is then not executed?).
I'm not sure how to go about this. Any suggestions?
replaceWith is implemented like this:
/**
* Inserts node(s) before the current node and removes the current node.
*
* #param {...Node} nodes - node(s) to replace current one
*
* #example
* if ( atrule.name == 'mixin' ) {
* atrule.replaceWith(mixinRules[atrule.params]);
* }
*
* #return {Node} current node to methods chain
*/
replaceWith(...nodes) {
if (this.parent) {
for (let node of nodes) {
this.parent.insertBefore(this, node);
}
this.remove();
}
return this;
}
Given the list of at-rules to traverse:
#keepme
#removeme
The rule-walker keeps an index of the currently-inspected rule. At index 1 it finds keepme.
keepme.replaceWith(removeme) will insert removeme before keepme, then continue walking the ast...
Since removeme was moved ahead, the walker has moved past it, and will not execute that rule.
The fix is to modify replaceWith so it will move the child nodes after the inspected rule.
root.walkAtRules(/keepme|removeme/, (atRule) => {
if (atRule.name === 'keepme') {
if (atRule.parent) {
for (let node of atRule.nodes.reverse()) {
atRule.parent.insertAfter(atRule, node);
}
}
}
atRule.remove();
});
This works as intended: Interactive fiddle

Adding glyphicon icons to form fields

I'm wanting to add icons to form fields like bootstrap has: http://getbootstrap.com/css/?#forms-control-validation
I was able to get the class to display properly on the form-group by adjusting the options:
successClass: 'has-success',
errorClass: 'has-error',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
but i'm unable to figure out the best way to add the error or checkmark glyphicon. I assume it may have something to do with the errorWrapper / errorContainer but there isn't one for successWrapper/container
I ended up coming up with something else:
var bootstrapParsleyOptions = {
successClass: 'has-success has-feedback',
errorClass: 'has-error has-feedback',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
};
$.extend(true, ParsleyUI, {
enableBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
window.Parsley.on('form:init', function () {
$(this.$element).find(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
});
window.Parsley.on('field:validated', function () {
var element = this.$element;
if (this.validationResult == true) {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-remove').addClass('glyphicon-ok');
$(element).siblings(".sr-only").text("(success)");
} else {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-ok').addClass('glyphicon-remove');
$(element).siblings(".sr-only").text("(error)");
}
});
},
clearBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
}
});
To enable it:
$("#form").parsley(bootstrapParsleyOptions);
ParsleyUI.enableBootstrap();
To reset it:
$("#form").parsley(bootstrapParsleyOptions).reset();
ParsleyUI.enableBootstrap();
I imagine that you can obtain what you want with CSS, something like
.parsley-success::before { content: '√'; }

Resources