Skip to content

Commit 85963ea

Browse files
authored
postcss-global-data: late remover (#1664)
1 parent 27b9126 commit 85963ea

14 files changed

Lines changed: 381 additions & 35 deletions

plugins/postcss-global-data/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changes to PostCSS global-data
22

3+
### Unreleased (minor)
4+
5+
- Add `prepend` plugin option
6+
- Add `lateRemover` plugin option
7+
38
### 3.0.0
49

510
_August 3, 2024_

plugins/postcss-global-data/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ instructions for:
4848

4949
## Options
5050

51-
### files
51+
### `files`
5252

5353
The `files` option determines which files to inject into PostCSS.
5454

@@ -61,6 +61,48 @@ postcssGlobalData({
6161
});
6262
```
6363

64+
### Plugin order control with `lateRemover`
65+
66+
The `lateRemover` option gives you more options when ordering plugins.
67+
68+
```js
69+
// esm
70+
import postcss from 'postcss';
71+
import postcssGlobalData from '@csstools/postcss-global-data';
72+
73+
const [globalData, globalDataLateRemover] = postcssGlobalData({
74+
files: [
75+
'./src/css/variables.css',
76+
'./src/css/media-queries.css',
77+
],
78+
lateRemover: true
79+
}).plugins
80+
81+
postcss([
82+
globalData,
83+
/* other plugins */
84+
globalDataLateRemover
85+
]).process(YOUR_CSS /*, processOptions */);
86+
```
87+
88+
### `prepend`
89+
90+
The `prepend` option determines if injected CSS is appended or prepended.
91+
Defaults to `false`.
92+
93+
> [!Warning]
94+
> Prepending styles before `@import` statements will create broken stylesheets.
95+
96+
```js
97+
postcssGlobalData({
98+
files: [
99+
'./src/css/variables.css',
100+
'./src/css/media-queries.css',
101+
],
102+
prepend: true
103+
});
104+
```
105+
64106
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
65107

66108
[discord]: https://discord.gg/bUadyRwkJS
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"use strict";var e=require("node:path"),s=require("node:fs"),r=require("node:module");function parseImport(t,o,a,n){let c="";try{if(a.startsWith("node_modules://")){c=r.createRequire(process.cwd()).resolve(a.slice(15))}else if(a.startsWith("node_modules:")){c=r.createRequire(process.cwd()).resolve(a.slice(13))}else c=e.resolve(a)}catch(e){throw new Error(`Failed to read ${a} with error ${e instanceof Error?e.message:e}`)}if(n.has(c))return!1;n.add(c),o.result.messages.push({type:"dependency",plugin:"postcss-global-data",file:c,parent:t.source?.input?.file});const i=s.readFileSync(c,"utf8");return o.postcss.parse(i,{from:c})}const creator=e=>{const s=Object.assign({files:[]},e);return{postcssPlugin:"postcss-global-data",prepare(){let e=new Set,r=new Set;return{postcssPlugin:"postcss-global-data",Once(t,o){s.files.forEach(s=>{if(e.has(s))return;const a=parseImport(t,o,s,e);a&&a.each(e=>{t.append(e),r.add(e)})})},OnceExit(){r.forEach(e=>{e.remove()}),r=new Set,e=new Set}}}}};creator.postcss=!0,module.exports=creator;
1+
"use strict";var e=require("node:path"),r=require("node:fs"),s=require("node:module");function parseImport(t,o,n,c){let a="";try{if(n.startsWith("node_modules://")){a=s.createRequire(process.cwd()).resolve(n.slice(15))}else if(n.startsWith("node_modules:")){a=s.createRequire(process.cwd()).resolve(n.slice(13))}else a=e.resolve(n)}catch(e){throw new Error(`Failed to read ${n} with error ${e instanceof Error?e.message:e}`)}if(c.has(a))return!1;c.add(a),o.result.messages.push({type:"dependency",plugin:"postcss-global-data",file:a,parent:t.source?.input?.file});const i=r.readFileSync(a,"utf8");return o.postcss.parse(i,{from:a})}const t="postcss-global-data",creator=e=>{const r=Object.assign({files:[],lateRemover:!1,prepend:!1},e);function insert(e,s,t){if(!r.prepend)return void s.each(r=>{e.append(r),t.add(r)});const o=Array.from(s.nodes);o.reverse(),o.forEach(r=>{e.prepend(r),t.add(r)})}if(!r.lateRemover)return{postcssPlugin:t,prepare(){const e=new Set,s=new Set;return{postcssPlugin:t,Once(t,o){r.files.forEach(r=>{const n=parseImport(t,o,r,s);n&&insert(t,n,e)})},OnceExit(){e.forEach(e=>{e.remove()}),s.clear(),e.clear()}}}};const s=new WeakSet;return{postcssPlugin:t,plugins:[{postcssPlugin:t,prepare(){const e=new Set;return{postcssPlugin:t,Once(t,o){r.files.forEach(r=>{const n=parseImport(t,o,r,e);n&&insert(t,n,s)})},OnceExit(){e.clear()}}}},{postcssPlugin:t+"/late-remover",OnceExit(e){e.each(e=>{s.has(e)&&e.remove()})}}]}};creator.postcss=!0,module.exports=creator;

plugins/postcss-global-data/dist/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export default creator;
77
export declare type pluginOptions = {
88
/** List of files to be used as context */
99
files?: Array<string>;
10+
/** Remove nodes in a separate plugin object, this object can be added later in your list of plugins */
11+
lateRemover?: boolean;
12+
/** Add global CSS to the start of files, defaults to `false` */
13+
prepend?: boolean;
1014
};
1115

1216
export { }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import e from"node:path";import s from"node:fs";import r from"node:module";function parseImport(t,o,a,n){let c="";try{if(a.startsWith("node_modules://")){c=r.createRequire(process.cwd()).resolve(a.slice(15))}else if(a.startsWith("node_modules:")){c=r.createRequire(process.cwd()).resolve(a.slice(13))}else c=e.resolve(a)}catch(e){throw new Error(`Failed to read ${a} with error ${e instanceof Error?e.message:e}`)}if(n.has(c))return!1;n.add(c),o.result.messages.push({type:"dependency",plugin:"postcss-global-data",file:c,parent:t.source?.input?.file});const l=s.readFileSync(c,"utf8");return o.postcss.parse(l,{from:c})}const creator=e=>{const s=Object.assign({files:[]},e);return{postcssPlugin:"postcss-global-data",prepare(){let e=new Set,r=new Set;return{postcssPlugin:"postcss-global-data",Once(t,o){s.files.forEach(s=>{if(e.has(s))return;const a=parseImport(t,o,s,e);a&&a.each(e=>{t.append(e),r.add(e)})})},OnceExit(){r.forEach(e=>{e.remove()}),r=new Set,e=new Set}}}}};creator.postcss=!0;export{creator as default};
1+
import e from"node:path";import r from"node:fs";import s from"node:module";function parseImport(t,o,n,c){let a="";try{if(n.startsWith("node_modules://")){a=s.createRequire(process.cwd()).resolve(n.slice(15))}else if(n.startsWith("node_modules:")){a=s.createRequire(process.cwd()).resolve(n.slice(13))}else a=e.resolve(n)}catch(e){throw new Error(`Failed to read ${n} with error ${e instanceof Error?e.message:e}`)}if(c.has(a))return!1;c.add(a),o.result.messages.push({type:"dependency",plugin:"postcss-global-data",file:a,parent:t.source?.input?.file});const p=r.readFileSync(a,"utf8");return o.postcss.parse(p,{from:a})}const t="postcss-global-data",creator=e=>{const r=Object.assign({files:[],lateRemover:!1,prepend:!1},e);function insert(e,s,t){if(!r.prepend)return void s.each(r=>{e.append(r),t.add(r)});const o=Array.from(s.nodes);o.reverse(),o.forEach(r=>{e.prepend(r),t.add(r)})}if(!r.lateRemover)return{postcssPlugin:t,prepare(){const e=new Set,s=new Set;return{postcssPlugin:t,Once(t,o){r.files.forEach(r=>{const n=parseImport(t,o,r,s);n&&insert(t,n,e)})},OnceExit(){e.forEach(e=>{e.remove()}),s.clear(),e.clear()}}}};const s=new WeakSet;return{postcssPlugin:t,plugins:[{postcssPlugin:t,prepare(){const e=new Set;return{postcssPlugin:t,Once(t,o){r.files.forEach(r=>{const n=parseImport(t,o,r,e);n&&insert(t,n,s)})},OnceExit(){e.clear()}}}},{postcssPlugin:t+"/late-remover",OnceExit(e){e.each(e=>{s.has(e)&&e.remove()})}}]}};creator.postcss=!0;export{creator as default};

plugins/postcss-global-data/docs/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ can actually use it.
3333

3434
## Options
3535

36-
### files
36+
### `files`
3737

3838
The `files` option determines which files to inject into PostCSS.
3939

@@ -46,4 +46,46 @@ The `files` option determines which files to inject into PostCSS.
4646
});
4747
```
4848

49+
### Plugin order control with `lateRemover`
50+
51+
The `lateRemover` option gives you more options when ordering plugins.
52+
53+
```js
54+
// esm
55+
import postcss from 'postcss';
56+
import <exportName> from '<packageName>';
57+
58+
const [globalData, globalDataLateRemover] = <exportName>({
59+
files: [
60+
'./src/css/variables.css',
61+
'./src/css/media-queries.css',
62+
],
63+
lateRemover: true
64+
}).plugins
65+
66+
postcss([
67+
globalData,
68+
/* other plugins */
69+
globalDataLateRemover
70+
]).process(YOUR_CSS /*, processOptions */);
71+
```
72+
73+
### `prepend`
74+
75+
The `prepend` option determines if injected CSS is appended or prepended.
76+
Defaults to `false`.
77+
78+
> [!Warning]
79+
> Prepending styles before `@import` statements will create broken stylesheets.
80+
81+
```js
82+
<exportName>({
83+
files: [
84+
'./src/css/variables.css',
85+
'./src/css/media-queries.css',
86+
],
87+
prepend: true
88+
});
89+
```
90+
4991
<linkList>

plugins/postcss-global-data/src/index.ts

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,121 @@
1-
import type { ChildNode, Plugin, PluginCreator } from 'postcss';
1+
import type { ChildNode, Plugin, PluginCreator, Root } from 'postcss';
22
import { parseImport } from './parse-import';
33

44
/** postcss-global-data plugin options */
55
export type pluginOptions = {
66
/** List of files to be used as context */
77
files?: Array<string>,
8+
/** Remove nodes in a separate plugin object, this object can be added later in your list of plugins */
9+
lateRemover?: boolean,
10+
/** Add global CSS to the start of files, defaults to `false` */
11+
prepend?: boolean,
812
};
913

14+
const pluginName = 'postcss-global-data';
15+
1016
const creator: PluginCreator<pluginOptions> = (opts?: pluginOptions) => {
1117
const options = Object.assign(
1218
// Default options
1319
{
1420
files: [],
21+
lateRemover: false,
22+
prepend: false,
1523
},
1624
// Provided options
1725
opts,
1826
);
1927

20-
return {
21-
postcssPlugin: 'postcss-global-data',
22-
prepare(): Plugin {
23-
let importedFiles = new Set<string>();
24-
let importedCSS = new Set<ChildNode>();
25-
26-
return {
27-
postcssPlugin: 'postcss-global-data',
28-
Once(root, postcssHelpers): void {
29-
options.files.forEach((file) => {
30-
if (importedFiles.has(file)) {
31-
return;
32-
}
28+
function insert(destination: Root, source: Root, set: { add: (_: ChildNode) => void }): void {
29+
if (!options.prepend) {
30+
source.each((node) => {
31+
destination.append(node);
32+
set.add(node);
33+
});
3334

34-
const newCSS = parseImport(root, postcssHelpers, file, importedFiles);
35-
if (!newCSS) {
36-
return;
37-
}
35+
return;
36+
}
37+
38+
const nodes = Array.from(source.nodes);
39+
nodes.reverse();
3840

39-
newCSS.each((node) => {
40-
root.append(node);
41-
importedCSS.add(node);
41+
nodes.forEach((node) => {
42+
destination.prepend(node);
43+
set.add(node);
44+
});
45+
}
46+
47+
if (!options.lateRemover) {
48+
return {
49+
postcssPlugin: pluginName,
50+
prepare(): Plugin {
51+
const importedCSS = new Set<ChildNode>();
52+
const importedFiles = new Set<string>();
53+
54+
return {
55+
postcssPlugin: pluginName,
56+
Once(root, postcssHelpers): void {
57+
options.files.forEach((file) => {
58+
const newCSS = parseImport(root, postcssHelpers, file, importedFiles);
59+
if (!newCSS) {
60+
return;
61+
}
62+
63+
insert(root, newCSS, importedCSS);
4264
});
43-
});
65+
},
66+
OnceExit(): void {
67+
importedCSS.forEach((node) => {
68+
node.remove();
69+
});
70+
71+
importedFiles.clear();
72+
importedCSS.clear();
73+
},
74+
};
75+
},
76+
}
77+
}
78+
79+
const importedCSS = new WeakSet<ChildNode>();
80+
81+
return {
82+
postcssPlugin: pluginName,
83+
plugins: [
84+
{
85+
postcssPlugin: pluginName,
86+
prepare(): Plugin {
87+
const importedFiles = new Set<string>();
88+
89+
return {
90+
postcssPlugin: pluginName,
91+
Once(root, postcssHelpers): void {
92+
options.files.forEach((file) => {
93+
const newCSS = parseImport(root, postcssHelpers, file, importedFiles);
94+
if (!newCSS) {
95+
return;
96+
}
97+
98+
insert(root, newCSS, importedCSS);
99+
});
100+
},
101+
OnceExit(): void {
102+
importedFiles.clear();
103+
},
104+
};
44105
},
45-
OnceExit(): void {
46-
importedCSS.forEach((node) => {
47-
node.remove();
106+
},
107+
{
108+
postcssPlugin: pluginName + '/late-remover',
109+
OnceExit(root): void {
110+
root.each((node) => {
111+
if (importedCSS.has(node)) {
112+
node.remove();
113+
}
48114
});
49-
importedCSS = new Set<ChildNode>();
50-
importedFiles = new Set<string>();
51115
},
52-
};
53-
},
54-
};
116+
}
117+
]
118+
}
55119
};
56120

57121
creator.postcss = true;

0 commit comments

Comments
 (0)