@massimo-cassandro/minimo 1.36.1 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dev-tools/design-tokens-builder/README.md +22 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/config.mjs +22 -3
- package/dev-tools/design-tokens-builder/build-tokens-src/platforms.mjs +4 -2
- package/dev-tools/design-tokens-builder/build-tokens-src/source-prefixes.mjs +129 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/transforms.mjs +20 -0
- package/dev-tools/design-tokens-builder/build-tokens.mjs +9 -0
- package/dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs +13 -8
- package/dev-tools/design-tokens-builder/tokens-config-sample.mjs +10 -1
- package/dev-tools/starter-kit/source_files/app/email/_mail-properties.scss +18 -0
- package/dev-tools/starter-kit/source_files/app/email/mail-embed.scss +68 -0
- package/dev-tools/starter-kit/source_files/app/email/mail-inline.scss +203 -0
- package/dev-tools/starter-kit/source_files/app/email/readme.md +26 -0
- package/dev-tools/starter-kit/source_files/app/email/{_mail-properties.css → versione postcss/_mail-properties.css } +2 -5
- package/dev-tools/starter-kit/source_files/app/email/{mail-embed.css → versione postcss/mail-embed.css } +14 -15
- package/dev-tools/starter-kit/source_files/app/email/{mail-inline.css → versione postcss/mail-inline.css } +50 -61
- package/dev-tools/starter-kit/source_files/app/email/{postcss.config.mjs → versione postcss/postcss.config.mjs } +1 -1
- package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root/_sf/templates}/_main-tpl.html.twig +3 -0
- package/dev-tools/starter-kit/source_files/root/{sf_templates → _sf/templates}/email/_email-tpl.html.twig +8 -9
- package/dev-tools/starter-kit/source_files/root/_sf/templates/email/css/.gitkeep +0 -0
- package/dev-tools/starter-kit/source_files/root/_sf/templates/email/imgs/.gitkeep +0 -0
- package/dev-tools/starter-kit/source_files/root/jsconfig.json +1 -1
- package/dev-tools/starter-kit/source_files/root/package-tpl.json +5 -2
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/css-rules.mjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/custom-props-purgecss-plugin.mjs +328 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/get-jsConfig-aliases.mjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/inline-critical-css.mjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/mini-svg-data-uri-loader.cjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/postcss.config.mjs +9 -5
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/purgecss-setup.mjs +23 -27
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/svg-rules.mjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/svgo.config.mjs +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack.config.mjs +137 -65
- package/dev-tools/starter-kit/starter-install.sh +2 -1
- package/dev-tools/starter-kit/webpack-config-upg.md +147 -0
- package/package.json +1 -1
- package/src/css/forms/form-group.css +0 -1
- package/src/minimo.css +42 -42
- package/dev-tools/starter-kit/source_files/root/sf_templates/email/css/mail-embed.css +0 -1
- package/dev-tools/starter-kit/source_files/root/sf_templates/email/css/mail-inline.css +0 -1
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/purgecss-variables-safelist.mjs +0 -92
- /package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root}/_env.local +0 -0
- /package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root/_sf}/symfony-webpack-setup.md +0 -0
- /package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root/_sf/templates}/bundles/TwigBundle/Exception/error.html.twig +0 -0
- /package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root/_sf/templates/incl}/flash-messages.html.twig +0 -0
- /package/{snippets/twig-symfony → dev-tools/starter-kit/source_files/root/_sf/templates/incl}/minimo-form-layout.html.twig +0 -0
- /package/snippets/{twig-symfony → symfony}/ImgViewerController.php +0 -0
- /package/snippets/{twig-symfony → symfony}/UnsplashController.php +0 -0
- /package/snippets/{twig-symfony → symfony}/ViewerController.php +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// webpack-config-modules/custom-props-purgecss-plugin.mjs
|
|
2
|
+
// v.3
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import postcss from 'postcss';
|
|
5
|
+
import { globSync } from 'glob';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Plugin webpack per l'estrazione ottimizzata delle CSS custom properties.
|
|
9
|
+
|
|
10
|
+
Contesto: il file "master" (definitionsFile) contiene TUTTE le definizioni di
|
|
11
|
+
custom properties disponibili. I css compilati da webpack contengono invece
|
|
12
|
+
solo *usi* (`var(--nome)`), mai definizioni. Dopo la minificazione degli
|
|
13
|
+
asset, il plugin:
|
|
14
|
+
1. scansiona i css di ogni "set" per trovare le custom properties usate;
|
|
15
|
+
2. risolve le dipendenze transitive (`--a: calc(var(--b) * 2)` => anche
|
|
16
|
+
`--b`, fino al fixed point, fallback `var(--x, var(--y))` inclusi);
|
|
17
|
+
3. estrae dal master le sole definizioni necessarie, PRESERVANDO l'ordine
|
|
18
|
+
originale di dichiarazione;
|
|
19
|
+
4. le inietta all'inizio del file target del set (dopo l'eventuale banner
|
|
20
|
+
webpack, cioè un commento che inizia con `/*!`, o `@charset`), oppure le
|
|
21
|
+
emette come nuovo asset.
|
|
22
|
+
|
|
23
|
+
Rispetto a postcss-jit-props (che opera sul singolo file, prima del bundling)
|
|
24
|
+
lavora sugli asset finali e permette di decidere il raggruppamento
|
|
25
|
+
sources -> target.
|
|
26
|
+
|
|
27
|
+
Regole di scope del master:
|
|
28
|
+
- una prop è identificata dal nome: se `--x` è usata, TUTTE le sue
|
|
29
|
+
dichiarazioni nel master vengono portate con sé, anche quelle dentro
|
|
30
|
+
blocchi di override (`@media (prefers-color-scheme: dark)`,
|
|
31
|
+
`[data-theme="dark"]`, ecc.), altrimenti il tema alternativo si rompe
|
|
32
|
+
in silenzio; le var() presenti nei valori degli override entrano nella
|
|
33
|
+
risoluzione transitiva;
|
|
34
|
+
- le dichiarazioni non custom (es. `color-scheme`) sono mantenute solo nei
|
|
35
|
+
blocchi che conservano almeno una custom property;
|
|
36
|
+
- le props "locali" con prefisso `--_` non fanno parte del pool globale:
|
|
37
|
+
sono escluse dalla scansione degli usi e dalla risoluzione transitiva
|
|
38
|
+
(e ignorate nel master).
|
|
39
|
+
|
|
40
|
+
Stage: PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE, quindi dopo la minificazione
|
|
41
|
+
(CssMinimizerPlugin, OPTIMIZE_SIZE) e dopo PurgeCSSPlugin (ADDITIONAL), ma
|
|
42
|
+
prima del calcolo del contenthash reale (OPTIMIZE_HASH, `realContentHash`).
|
|
43
|
+
Il match degli usi avviene quindi sul css realmente servito e l'hash
|
|
44
|
+
riflette il contenuto finale.
|
|
45
|
+
|
|
46
|
+
Watch mode: il master è parsato una sola volta e ricaricato solo se cambia
|
|
47
|
+
(mtime); è comunque registrato come fileDependency. Il set di props usate
|
|
48
|
+
viene ricalcolato a ogni build.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const PLUGIN_NAME = 'CustomPropsPurgeCssPlugin';
|
|
52
|
+
|
|
53
|
+
const VAR_REFS_REGEX = /var\(\s*(--[A-Za-z0-9_-]+)/g;
|
|
54
|
+
// banner webpack (commento `/*!`) e/o `@charset` iniziali, che devono restare in testa
|
|
55
|
+
const LEADING_REGEX = /^(?:\s*(?:@charset\s+["'][^"']*["']\s*;|\/\*![\s\S]*?\*\/))*\s*/;
|
|
56
|
+
|
|
57
|
+
// le props con prefisso `--_` sono locali: fuori dal pool globale
|
|
58
|
+
const isGlobalCustomProp = (name) => name.startsWith('--') && !name.startsWith('--_');
|
|
59
|
+
|
|
60
|
+
const varRefs = (text) => [...text.matchAll(VAR_REFS_REGEX)]
|
|
61
|
+
.map(m => m[1])
|
|
62
|
+
.filter(isGlobalCustomProp);
|
|
63
|
+
|
|
64
|
+
// pattern asset: stringa con `*` come wildcard (es. 'index.*.css') o RegExp,
|
|
65
|
+
// confrontati con il nome completo dell'asset (path relativo a output.path)
|
|
66
|
+
const toMatcher = (pattern) => {
|
|
67
|
+
if (pattern instanceof RegExp) {
|
|
68
|
+
return (name) => pattern.test(name);
|
|
69
|
+
}
|
|
70
|
+
const regex = new RegExp(
|
|
71
|
+
'^' + pattern.split('*').map(s => s.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*') + '$'
|
|
72
|
+
);
|
|
73
|
+
return (name) => regex.test(name);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const hasCustomDecl = (node) => {
|
|
77
|
+
let found = false;
|
|
78
|
+
node.walkDecls(decl => {
|
|
79
|
+
if (isGlobalCustomProp(decl.prop)) {
|
|
80
|
+
found = true;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return found;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// rimuove commenti, custom props non incluse e blocchi rimasti senza custom props
|
|
87
|
+
const prune = (container, keep) => {
|
|
88
|
+
container.each(node => {
|
|
89
|
+
if (node.type === 'comment') {
|
|
90
|
+
node.remove();
|
|
91
|
+
} else if (node.type === 'decl') {
|
|
92
|
+
if (isGlobalCustomProp(node.prop) ? !keep.has(node.prop) : node.prop.startsWith('--')) {
|
|
93
|
+
node.remove();
|
|
94
|
+
}
|
|
95
|
+
} else if (node.type === 'rule' || (node.type === 'atrule' && node.nodes)) {
|
|
96
|
+
prune(node, keep);
|
|
97
|
+
if (!hasCustomDecl(node)) {
|
|
98
|
+
node.remove();
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// at-rule senza blocco (@import, @charset, @layer a, b;): non pertinenti
|
|
102
|
+
node.remove();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// serializzazione compatta (senza indentazioni) per gli asset minificati
|
|
108
|
+
const compact = (root) => {
|
|
109
|
+
root.walk(node => {
|
|
110
|
+
node.raws.before = '';
|
|
111
|
+
if (node.type === 'decl') {
|
|
112
|
+
node.raws.between = ':';
|
|
113
|
+
} else {
|
|
114
|
+
node.raws.between = '';
|
|
115
|
+
node.raws.after = '';
|
|
116
|
+
node.raws.semicolon = false;
|
|
117
|
+
if (node.type === 'atrule') {
|
|
118
|
+
node.raws.afterName = ' ';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
root.raws.after = '';
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export class CustomPropsPurgeCssPlugin {
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {object} options
|
|
129
|
+
* @param {string} options.definitionsFile - Percorso assoluto del file master con tutte
|
|
130
|
+
* le definizioni di custom properties
|
|
131
|
+
* @param {{ sources: (string|RegExp)[], target?: (string|RegExp) }[]} options.sets - Set
|
|
132
|
+
* di asset css da elaborare in modo indipendente. `sources`: nomi degli asset css
|
|
133
|
+
* compilati (con l'hash nel nome), stringhe con `*` come wildcard (es. `'index.*.css'`)
|
|
134
|
+
* o RegExp. `target`: asset in cui iniettare le definizioni (stesso formato; se il
|
|
135
|
+
* pattern non corrisponde ad alcun asset e non contiene `*`, viene emesso come nuovo
|
|
136
|
+
* asset con quel nome). Se omesso, è il primo asset che corrisponde a `sources`
|
|
137
|
+
* @param {string} [options.selector=':root'] - Selettore con cui sostituire i blocchi
|
|
138
|
+
* `:root` del master (es. `':where(html)'`); gli altri selettori (temi, ecc.) restano invariati (default: ':root')
|
|
139
|
+
* @param {string[]} [options.extraUsageGlobs=[]] - Glob di file css i cui usi di var()
|
|
140
|
+
* contano come usati in TUTTI i set: css caricati fuori dagli asset (shadow DOM dei
|
|
141
|
+
* web components, import `?raw`) (default: [])
|
|
142
|
+
* @param {(string|RegExp)[]} [options.seeds=[]] - Props da includere comunque in tutti i
|
|
143
|
+
* set (nomi esatti o pattern risolti sui nomi dichiarati nel master), es. override
|
|
144
|
+
* consumati da un altro asset (default: [])
|
|
145
|
+
* @param {boolean} [options.minify=false] - Serializza le definizioni in forma compatta
|
|
146
|
+
* (default: false)
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* new CustomPropsPurgeCssPlugin({
|
|
150
|
+
* definitionsFile: path.resolve(__dirname, './app/css/custom-properties.css'),
|
|
151
|
+
* sets: [
|
|
152
|
+
* // le definizioni vengono iniettate in layout.critical.*.css (inline nel template)
|
|
153
|
+
* // e valgono anche per index.*.css
|
|
154
|
+
* { sources: ['index.*.css', 'layout.critical.*.css'], target: 'layout.critical.*.css' },
|
|
155
|
+
* // target omesso: definizioni iniettate nel primo asset del set
|
|
156
|
+
* { sources: ['admin.*.css'] }
|
|
157
|
+
* ],
|
|
158
|
+
* selector: ':root', // default
|
|
159
|
+
* extraUsageGlobs: [], // default
|
|
160
|
+
* seeds: [], // default
|
|
161
|
+
* minify: !isDevelopment // default: false
|
|
162
|
+
* })
|
|
163
|
+
*/
|
|
164
|
+
constructor({
|
|
165
|
+
definitionsFile,
|
|
166
|
+
sets,
|
|
167
|
+
selector = ':root',
|
|
168
|
+
extraUsageGlobs = [],
|
|
169
|
+
seeds = [],
|
|
170
|
+
minify = false
|
|
171
|
+
}) {
|
|
172
|
+
if (!definitionsFile || !Array.isArray(sets)) {
|
|
173
|
+
throw new Error(`${PLUGIN_NAME}: 'definitionsFile' e 'sets' sono obbligatori`);
|
|
174
|
+
}
|
|
175
|
+
this.definitionsFile = definitionsFile;
|
|
176
|
+
this.sets = sets.map(set => ({
|
|
177
|
+
sources: set.sources.map(toMatcher),
|
|
178
|
+
target: set.target == null ? null : {
|
|
179
|
+
matcher: toMatcher(set.target),
|
|
180
|
+
// nome letterale, usato per emettere un nuovo asset se nessuno corrisponde
|
|
181
|
+
literal: typeof set.target === 'string' && !set.target.includes('*') ? set.target : null
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
this.selector = selector;
|
|
185
|
+
this.extraUsageGlobs = extraUsageGlobs;
|
|
186
|
+
this.seeds = seeds;
|
|
187
|
+
this.minify = minify;
|
|
188
|
+
|
|
189
|
+
this.cache = { mtimeMs: null, root: null, declaredRefs: null };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// parsing (cachato) del master: root PostCSS + grafo nome -> Set(var() nei valori)
|
|
193
|
+
loadDefinitions() {
|
|
194
|
+
const { mtimeMs } = fs.statSync(this.definitionsFile);
|
|
195
|
+
|
|
196
|
+
if (this.cache.mtimeMs !== mtimeMs) {
|
|
197
|
+
const root = postcss.parse(fs.readFileSync(this.definitionsFile, 'utf8'), { from: this.definitionsFile });
|
|
198
|
+
const declaredRefs = new Map();
|
|
199
|
+
|
|
200
|
+
root.walkDecls(decl => {
|
|
201
|
+
if (!isGlobalCustomProp(decl.prop)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!declaredRefs.has(decl.prop)) {
|
|
205
|
+
declaredRefs.set(decl.prop, new Set());
|
|
206
|
+
}
|
|
207
|
+
varRefs(decl.value).forEach(ref => declaredRefs.get(decl.prop).add(ref));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.cache = { mtimeMs, root, declaredRefs };
|
|
211
|
+
}
|
|
212
|
+
return this.cache;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
apply(compiler) {
|
|
216
|
+
const { Compilation, sources } = compiler.webpack;
|
|
217
|
+
const { RawSource, ReplaceSource } = sources;
|
|
218
|
+
|
|
219
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
220
|
+
|
|
221
|
+
compilation.hooks.processAssets.tap(
|
|
222
|
+
{ name: PLUGIN_NAME, stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE },
|
|
223
|
+
() => {
|
|
224
|
+
compilation.fileDependencies.add(this.definitionsFile);
|
|
225
|
+
|
|
226
|
+
const { root: masterRoot, declaredRefs } = this.loadDefinitions();
|
|
227
|
+
const declaredNames = [...declaredRefs.keys()];
|
|
228
|
+
|
|
229
|
+
// usi comuni a tutti i set: file css fuori dagli asset + seeds
|
|
230
|
+
const commonUsages = new Set();
|
|
231
|
+
for (const file of globSync(this.extraUsageGlobs, { nodir: true, absolute: true })) {
|
|
232
|
+
compilation.fileDependencies.add(file);
|
|
233
|
+
varRefs(fs.readFileSync(file, 'utf8')).forEach(ref => commonUsages.add(ref));
|
|
234
|
+
}
|
|
235
|
+
for (const seed of this.seeds) {
|
|
236
|
+
(typeof seed === 'string' ? [seed] : declaredNames.filter(name => seed.test(name)))
|
|
237
|
+
.forEach(name => commonUsages.add(name));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const cssAssetNames = () => compilation.getAssets()
|
|
241
|
+
.map(asset => asset.name)
|
|
242
|
+
.filter(name => /\.css$/i.test(name));
|
|
243
|
+
|
|
244
|
+
for (const set of this.sets) {
|
|
245
|
+
const sourceNames = cssAssetNames().filter(name => set.sources.some(match => match(name)));
|
|
246
|
+
|
|
247
|
+
if (sourceNames.length === 0) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// props usate nei css del set
|
|
252
|
+
const used = new Set(commonUsages);
|
|
253
|
+
for (const name of sourceNames) {
|
|
254
|
+
varRefs(compilation.getAsset(name).source.source().toString()).forEach(ref => used.add(ref));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// chiusura transitiva (solo props dichiarate nel master)
|
|
258
|
+
const keep = new Set();
|
|
259
|
+
const queue = [...used];
|
|
260
|
+
while (queue.length > 0) {
|
|
261
|
+
const name = queue.pop();
|
|
262
|
+
if (!keep.has(name) && declaredRefs.has(name)) {
|
|
263
|
+
keep.add(name);
|
|
264
|
+
queue.push(...declaredRefs.get(name));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (keep.size === 0) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// definizioni: si parte da una copia del master (l'ordine originale è preservato)
|
|
273
|
+
const definitionsRoot = masterRoot.clone();
|
|
274
|
+
prune(definitionsRoot, keep);
|
|
275
|
+
if (this.selector !== ':root') {
|
|
276
|
+
definitionsRoot.walkRules(rule => {
|
|
277
|
+
if (rule.selector.trim() === ':root') {
|
|
278
|
+
rule.selector = this.selector;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (this.minify) {
|
|
283
|
+
compact(definitionsRoot);
|
|
284
|
+
} else {
|
|
285
|
+
// primo nodo senza riga vuota iniziale
|
|
286
|
+
if (definitionsRoot.first) {
|
|
287
|
+
definitionsRoot.first.raws.before = '';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const definitions = definitionsRoot.toString().trim() + '\n';
|
|
291
|
+
|
|
292
|
+
// target
|
|
293
|
+
let targetName;
|
|
294
|
+
if (set.target == null) {
|
|
295
|
+
targetName = sourceNames[0];
|
|
296
|
+
} else {
|
|
297
|
+
const matching = cssAssetNames().filter(name => set.target.matcher(name));
|
|
298
|
+
if (matching.length > 0) {
|
|
299
|
+
targetName = matching[0];
|
|
300
|
+
if (matching.length > 1) {
|
|
301
|
+
compilation.warnings.push(new compiler.webpack.WebpackError(
|
|
302
|
+
`${PLUGIN_NAME}: il target del set corrisponde a più asset (${matching.join(', ')}), usato: ${targetName}`
|
|
303
|
+
));
|
|
304
|
+
}
|
|
305
|
+
} else if (set.target.literal) {
|
|
306
|
+
compilation.emitAsset(set.target.literal, new RawSource(definitions));
|
|
307
|
+
continue;
|
|
308
|
+
} else {
|
|
309
|
+
compilation.warnings.push(new compiler.webpack.WebpackError(
|
|
310
|
+
`${PLUGIN_NAME}: nessun asset corrisponde al target del set (sources: ${sourceNames.join(', ')})`
|
|
311
|
+
));
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const original = compilation.getAsset(targetName).source;
|
|
317
|
+
const originalText = original.source().toString();
|
|
318
|
+
const position = originalText.match(LEADING_REGEX)[0].length;
|
|
319
|
+
const replaced = new ReplaceSource(original, PLUGIN_NAME);
|
|
320
|
+
// insert(pos) inserisce PRIMA del carattere in posizione `pos`
|
|
321
|
+
replaced.insert(position, definitions);
|
|
322
|
+
compilation.updateAsset(targetName, replaced);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
// webpack-modules/postcss.config.mjs
|
|
2
|
+
// v.3
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import globalData from '@csstools/postcss-global-data';
|
|
5
6
|
import customMedia from 'postcss-custom-media';
|
|
6
7
|
import autoprefixer from 'autoprefixer';
|
|
7
8
|
// import jitProps from 'postcss-jit-props';
|
|
9
|
+
// import OpenProps from 'open-props';
|
|
8
10
|
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = path.dirname(__filename);
|
|
11
13
|
|
|
12
14
|
// NB: le custom properties NON sono gestite qui (postcss-jit-props rimosso):
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
15
|
+
// i css del progetto contengono solo usi `var(--nome)` e le definizioni
|
|
16
|
+
// (solo quelle usate) sono iniettate a fine build dal plugin
|
|
17
|
+
// custom-props-purgecss-plugin.mjs, che parte dal file master
|
|
18
|
+
// custom-properties.css (vedi `useCustomPropsPlugin` in webpack.config.mjs)
|
|
16
19
|
|
|
17
20
|
// NB: la minificazione è gestita da CssMinimizerPlugin (webpack.config.mjs):
|
|
18
21
|
// cssnano NON va aggiunto qui, oltre a duplicare la minificazione
|
|
@@ -25,8 +28,8 @@ export default (loaderContext) => {
|
|
|
25
28
|
const plugins = [
|
|
26
29
|
// rende disponibili le @custom-media a tutti i file css
|
|
27
30
|
// (il contenuto iniettato viene rimosso a fine elaborazione;
|
|
28
|
-
// NON aggiungere qui custom-properties.css:
|
|
29
|
-
//
|
|
31
|
+
// NON aggiungere qui custom-properties.css: le definizioni sono
|
|
32
|
+
// gestite dal plugin delle custom properties, vedi sopra)
|
|
30
33
|
globalData({
|
|
31
34
|
files: [
|
|
32
35
|
path.resolve(__dirname, '../app/css/custom-media.css'),
|
|
@@ -34,6 +37,7 @@ export default (loaderContext) => {
|
|
|
34
37
|
}),
|
|
35
38
|
|
|
36
39
|
// jitProps({
|
|
40
|
+
// ...OpenProps,
|
|
37
41
|
// files: [
|
|
38
42
|
// path.resolve(__dirname, '../app/css/custom-properties.css'),
|
|
39
43
|
// ]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// webpack-modules/purgecss-setup.mjs
|
|
2
|
+
// v.3
|
|
2
3
|
import { PurgeCSSPlugin } from 'purgecss-webpack-plugin';
|
|
3
4
|
import { globSync } from 'glob';
|
|
4
|
-
import { purgecssVariablesSafelist } from './purgecss-variables-safelist.mjs';
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
7
|
Istanzia i plugin PurgeCSS del progetto: un passaggio stretto sui css
|
|
@@ -9,37 +9,39 @@ import { purgecssVariablesSafelist } from './purgecss-variables-safelist.mjs';
|
|
|
9
9
|
passaggio generale su tutti gli altri asset (il purge è idempotente: il
|
|
10
10
|
secondo passaggio sui critical non rimuove altro).
|
|
11
11
|
|
|
12
|
-
NB:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
(vedi purgecss-variables-safelist.mjs)
|
|
12
|
+
NB: PurgeCSS gestisce SOLO classi/id/tag e @keyframes. Le custom properties
|
|
13
|
+
NON sono gestite qui (`variables` è sempre false): le definizioni vengono
|
|
14
|
+
iniettate, solo per quelle usate, da custom-props-purgecss-plugin.mjs, che
|
|
15
|
+
opera sugli asset css già finalizzati (dopo questo purge e dopo la
|
|
16
|
+
minificazione), quindi vede anche gli usi di var() nei blocchi
|
|
17
|
+
`purgecss start/end ignore`, che PurgeCSS mantiene.
|
|
18
|
+
Il purge lavora sul singolo asset css: i @keyframes definiti in un asset
|
|
19
|
+
(es. `shared.css`) ma usati solo in un altro vengono rimossi (vedi
|
|
20
|
+
`safelist.keyframes` / `keyframes: false`).
|
|
22
21
|
*/
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* @param {object} params
|
|
26
25
|
* @param {string[]} params.contentGlobs file da scansionare per le classi/id usati
|
|
27
26
|
* (twig, php, js...); i file di stile vengono comunque scartati dal plugin
|
|
28
|
-
* @param {object} params.safelist { standard, deep, greedy } — selettori sempre
|
|
27
|
+
* @param {object} params.safelist { standard, deep, greedy, keyframes } — selettori sempre
|
|
29
28
|
* mantenuti, indipendentemente dal contenuto scansionato
|
|
30
|
-
* @param {
|
|
31
|
-
*
|
|
32
|
-
* @param {boolean} [params.variables=true] rimuove le custom properties non usate
|
|
33
|
-
* @param {boolean} [params.keyframes=true] rimuove i @keyframes non referenziati
|
|
34
|
-
* @param {boolean} [params.debug=false] logga i selettori rimossi a fine build
|
|
29
|
+
* @param {boolean} [params.keyframes=true] rimuove i @keyframes non referenziati (default: true)
|
|
30
|
+
* @param {boolean} [params.debug=false] logga i selettori rimossi a fine build (default: false)
|
|
35
31
|
* @returns {object[]} plugin webpack da inserire in `plugins`
|
|
36
32
|
* (critical + generale, più il logger di debug se `debug: true`)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* createPurgeCSSPlugins({
|
|
36
|
+
* contentGlobs: [path.resolve(__dirname, './app/src/**\/*.{js,mjs}')],
|
|
37
|
+
* safelist: { standard: [/^alert-/], deep: [/^m_/], greedy: [/^&$/] },
|
|
38
|
+
* keyframes: true, // default
|
|
39
|
+
* debug: false // default
|
|
40
|
+
* })
|
|
37
41
|
*/
|
|
38
42
|
export function createPurgeCSSPlugins({
|
|
39
43
|
contentGlobs,
|
|
40
44
|
safelist,
|
|
41
|
-
variablesSafelist,
|
|
42
|
-
variables = true,
|
|
43
45
|
keyframes = true,
|
|
44
46
|
debug = false
|
|
45
47
|
}) {
|
|
@@ -49,7 +51,7 @@ export function createPurgeCSSPlugins({
|
|
|
49
51
|
// extractor di default (/[A-Za-z0-9_-]+/g): adeguato alle classi/tag/id del progetto,
|
|
50
52
|
// non servono extractors custom
|
|
51
53
|
|
|
52
|
-
variables,
|
|
54
|
+
variables: false, // vedi sopra: custom properties gestite da custom-props-purgecss-plugin.mjs
|
|
53
55
|
keyframes,
|
|
54
56
|
// fontFace resta false (default): i font sono referenziati solo tramite
|
|
55
57
|
// var(--font-family) e purgecss non risolve le custom properties nei valori
|
|
@@ -65,13 +67,7 @@ export function createPurgeCSSPlugins({
|
|
|
65
67
|
|
|
66
68
|
const pluginMain = new PurgeCSSPlugin({
|
|
67
69
|
...commonOptions,
|
|
68
|
-
safelist
|
|
69
|
-
...safelist,
|
|
70
|
-
// elenco generato: usi di var() nei blocchi `purgecss ignore` e nei css
|
|
71
|
-
// shadow-DOM/`?raw`, override cross-asset (seeds), con chiusura transitiva
|
|
72
|
-
// delle dipendenze (vedi purgecss-variables-safelist.mjs)
|
|
73
|
-
variables: purgecssVariablesSafelist(variablesSafelist)
|
|
74
|
-
}
|
|
70
|
+
safelist
|
|
75
71
|
});
|
|
76
72
|
|
|
77
73
|
return [
|