ngx-theme-stack 1.0.1 → 2.1.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/README.md +117 -33
- package/fesm2022/ngx-theme-stack.mjs +228 -50
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/collection.json +6 -1
- package/schematics/ng-add/anti-flash.d.ts +26 -0
- package/schematics/ng-add/anti-flash.js +135 -0
- package/schematics/ng-add/anti-flash.js.map +1 -0
- package/schematics/ng-add/app-config.d.ts +3 -3
- package/schematics/ng-add/app-config.js +9 -7
- package/schematics/ng-add/app-config.js.map +1 -1
- package/schematics/ng-add/constants.d.ts +5 -5
- package/schematics/ng-add/constants.js +5 -5
- package/schematics/ng-add/constants.js.map +1 -1
- package/schematics/ng-add/index.js +115 -15
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +2 -0
- package/schematics/ng-add/schema.json +7 -2
- package/schematics/ng-add/utils.d.ts +1 -6
- package/schematics/ng-add/utils.js +10 -27
- package/schematics/ng-add/utils.js.map +1 -1
- package/schematics/sync/index.d.ts +3 -0
- package/schematics/sync/index.js +314 -0
- package/schematics/sync/index.js.map +1 -0
- package/schematics/sync/schema.d.ts +6 -0
- package/schematics/sync/schema.js +3 -0
- package/schematics/sync/schema.js.map +1 -0
- package/schematics/sync/schema.json +21 -0
- package/types/ngx-theme-stack.d.ts +217 -43
|
@@ -11,9 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ngAdd = ngAdd;
|
|
13
13
|
const schematics_1 = require("@angular-devkit/schematics");
|
|
14
|
+
const anti_flash_1 = require("./anti-flash");
|
|
15
|
+
const app_config_1 = require("./app-config");
|
|
14
16
|
const constants_1 = require("./constants");
|
|
15
17
|
const utils_1 = require("./utils");
|
|
16
|
-
const app_config_1 = require("./app-config");
|
|
17
18
|
/**
|
|
18
19
|
* Interactively prompts the user for custom configuration options using readline.
|
|
19
20
|
* It allows defining additional themes, selecting a default theme from the expanded list,
|
|
@@ -34,13 +35,19 @@ function collectCustomOptions() {
|
|
|
34
35
|
.filter(Boolean)
|
|
35
36
|
: [];
|
|
36
37
|
const allThemes = [...constants_1.DEFAULT_THEMES, ...customThemes];
|
|
37
|
-
const
|
|
38
|
+
const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:', allThemes, 0);
|
|
38
39
|
const rawKey = yield (0, utils_1.ask)(rl, ` localStorage key [${constants_1.DEFAULTS.storageKey}]: `);
|
|
39
40
|
const storageKey = rawKey || constants_1.DEFAULTS.storageKey;
|
|
40
41
|
const MODES = ['class', 'attribute', 'both'];
|
|
41
42
|
const mode = yield (0, utils_1.askList)(rl, 'How to apply theme on <html>:', MODES, 0);
|
|
43
|
+
const STRATEGIES = ['critters', 'blocking'];
|
|
42
44
|
process.stdout.write('\n');
|
|
43
|
-
|
|
45
|
+
process.stdout.write(' Anti-flash strategy:\n');
|
|
46
|
+
process.stdout.write(' - critters: Zero network requests (inlines CSS in <head>)\n');
|
|
47
|
+
process.stdout.write(' - blocking: Standard CSS loading (themes.css)\n');
|
|
48
|
+
const strategy = (yield (0, utils_1.askList)(rl, 'Choose strategy:', STRATEGIES, 0));
|
|
49
|
+
process.stdout.write('\n');
|
|
50
|
+
return { defaultTheme, storageKey, mode, themes: allThemes, strategy };
|
|
44
51
|
}
|
|
45
52
|
finally {
|
|
46
53
|
rl.close();
|
|
@@ -56,31 +63,124 @@ function collectCustomOptions() {
|
|
|
56
63
|
* @returns A rule that modifies the project's setup.
|
|
57
64
|
*/
|
|
58
65
|
function ngAdd(options) {
|
|
59
|
-
return (
|
|
66
|
+
return (tree, context) => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
// ── Workspace Resolution ──────────────────────────────────────────────────
|
|
68
|
+
// In a monorepo, we must find the project's actual root and sourceRoot.
|
|
69
|
+
const workspaceConfig = tree.read('/angular.json');
|
|
70
|
+
if (!workspaceConfig) {
|
|
71
|
+
throw new Error('Could not find angular.json. Are you in an Angular workspace?');
|
|
72
|
+
}
|
|
73
|
+
const workspace = JSON.parse(workspaceConfig.toString());
|
|
74
|
+
const projectName = options.project || workspace.defaultProject;
|
|
75
|
+
const project = workspace.projects[projectName];
|
|
76
|
+
if (!project) {
|
|
77
|
+
throw new Error(`Project "${projectName}" not found in angular.json.`);
|
|
78
|
+
}
|
|
79
|
+
const projectRoot = project.root || '';
|
|
80
|
+
const projectSourceRoot = project.sourceRoot || `${projectRoot}/src`;
|
|
60
81
|
context.logger.info('');
|
|
61
|
-
context.logger.info(
|
|
82
|
+
context.logger.info(`🎨 ngx-theme-stack — setup [project: ${projectName}]`);
|
|
62
83
|
context.logger.info('');
|
|
63
84
|
let provideCall;
|
|
85
|
+
let scriptOptions;
|
|
86
|
+
let finalThemes;
|
|
87
|
+
let themesToScaffold;
|
|
88
|
+
let finalStrategy;
|
|
64
89
|
if (options.mode === 'quick') {
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
const themes = [...constants_1.DEFAULT_THEMES];
|
|
91
|
+
const { defaultTheme, storageKey, mode } = constants_1.DEFAULTS;
|
|
92
|
+
const strategy = 'critters'; // default for quick
|
|
93
|
+
provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes);
|
|
94
|
+
scriptOptions = { storageKey, defaultTheme, mode };
|
|
95
|
+
finalThemes = themes;
|
|
96
|
+
themesToScaffold = themes.filter((t) => t !== 'system');
|
|
97
|
+
finalStrategy = strategy;
|
|
98
|
+
context.logger.info('⚡ Quick setup — providing explicit defaults (strategy: critters).');
|
|
67
99
|
}
|
|
68
100
|
else {
|
|
69
101
|
context.logger.info('🛠 Custom setup — answer the prompts below:');
|
|
70
102
|
const opts = yield collectCustomOptions();
|
|
71
|
-
const {
|
|
103
|
+
const { defaultTheme, storageKey, mode, themes, strategy } = opts;
|
|
72
104
|
context.logger.info(' Applying your configuration:');
|
|
73
|
-
context.logger.info(`
|
|
74
|
-
context.logger.info(` themes
|
|
75
|
-
context.logger.info(` storageKey
|
|
76
|
-
context.logger.info(` mode
|
|
77
|
-
|
|
105
|
+
context.logger.info(` defaultTheme : ${defaultTheme}`);
|
|
106
|
+
context.logger.info(` themes : [${themes.join(', ')}]`);
|
|
107
|
+
context.logger.info(` storageKey : ${storageKey}`);
|
|
108
|
+
context.logger.info(` mode : ${mode}`);
|
|
109
|
+
context.logger.info(` strategy : ${strategy}`);
|
|
110
|
+
provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes);
|
|
111
|
+
scriptOptions = { storageKey, defaultTheme, mode };
|
|
112
|
+
finalThemes = themes;
|
|
113
|
+
themesToScaffold = themes.filter((t) => t !== 'system');
|
|
114
|
+
finalStrategy = strategy;
|
|
78
115
|
}
|
|
79
116
|
return (0, schematics_1.chain)([
|
|
117
|
+
(t, context) => {
|
|
118
|
+
const themesPath = `${projectSourceRoot}/themes.css`;
|
|
119
|
+
if (!t.exists(themesPath)) {
|
|
120
|
+
let content = '/* ngx-theme-stack tokens */\n\n';
|
|
121
|
+
themesToScaffold.forEach((theme) => {
|
|
122
|
+
const selector = scriptOptions.mode === 'attribute' ? `[data-theme="${theme}"]` : `.${theme}`;
|
|
123
|
+
if (theme === 'light') {
|
|
124
|
+
content += `:root, ${selector} {\n /* Add your light theme variables here */\n}\n\n`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
content += `${selector} {\n /* Add your ${theme} theme variables here */\n}\n\n`;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
t.create(themesPath, content);
|
|
131
|
+
context.logger.info(`\n \u001b[36mResume :\u001b[0m \n`);
|
|
132
|
+
context.logger.info(`\u001b[32m✔ Created ${themesPath} with your theme selectors.\u001b[0m`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
context.logger.info(`\n \u001b[36mResume :\u001b[0m \n`);
|
|
136
|
+
context.logger.info(`\u001b[33mℹ ${themesPath} already exists. Skipping creation to preserve your styles.\u001b[0m`);
|
|
137
|
+
context.logger.info(` Tip: Make sure to manually add selectors (class or attribute) for any new themes.`);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
(t) => {
|
|
141
|
+
const pkgPath = '/package.json';
|
|
142
|
+
const buffer = t.read(pkgPath);
|
|
143
|
+
if (buffer) {
|
|
144
|
+
const pkg = JSON.parse(buffer.toString());
|
|
145
|
+
pkg.scripts = pkg.scripts || {};
|
|
146
|
+
pkg.scripts.prebuild = `ng generate ngx-theme-stack:sync --project ${projectName}`;
|
|
147
|
+
t.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
(t, context) => {
|
|
151
|
+
var _a;
|
|
152
|
+
const workspaceConfig = t.read('/angular.json');
|
|
153
|
+
if (workspaceConfig) {
|
|
154
|
+
const workspace = JSON.parse(workspaceConfig.toString());
|
|
155
|
+
const project = workspace.projects[projectName];
|
|
156
|
+
const target = project.architect.build.options;
|
|
157
|
+
const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
|
|
158
|
+
// Add themes.css to styles if not already there
|
|
159
|
+
if (target.styles && !target.styles.includes(themesPath)) {
|
|
160
|
+
target.styles.unshift(themesPath);
|
|
161
|
+
}
|
|
162
|
+
// Handle inlineCritical optimization for the blocking strategy
|
|
163
|
+
const prodConfig = (_a = project.architect.build.configurations) === null || _a === void 0 ? void 0 : _a.production;
|
|
164
|
+
if (prodConfig && finalStrategy === 'blocking') {
|
|
165
|
+
if (typeof prodConfig.optimization === 'object') {
|
|
166
|
+
prodConfig.optimization.styles = prodConfig.optimization.styles || {};
|
|
167
|
+
prodConfig.optimization.styles.inlineCritical = false;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
prodConfig.optimization = {
|
|
171
|
+
styles: { inlineCritical: false },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
context.logger.info(`✔ Disabled inlineCritical in angular.json for blocking strategy.`);
|
|
175
|
+
}
|
|
176
|
+
t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
|
|
177
|
+
}
|
|
178
|
+
},
|
|
80
179
|
(t, ctx) => {
|
|
81
|
-
(0, app_config_1.patchAppConfig)(t, ctx, provideCall);
|
|
180
|
+
(0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, provideCall);
|
|
181
|
+
(0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, Object.assign(Object.assign({}, scriptOptions), { themes: finalThemes, strategy: options.strategy || finalStrategy }));
|
|
82
182
|
ctx.logger.info('');
|
|
83
|
-
ctx.logger.info('✅ Done!
|
|
183
|
+
ctx.logger.info('✅ Done! ngx-theme-stack is ready with automatic sync on build.');
|
|
84
184
|
ctx.logger.info('');
|
|
85
185
|
},
|
|
86
186
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":";;;;;;;;;;;AAiEA,sBA8IC;AA/MD,2DAAiF;AACjF,6CAA8C;AAC9C,6CAA8C;AAC9C,2CAAuD;AAEvD,mCAAmE;AAEnE;;;;;;GAMG;AACH,SAAe,oBAAoB;;QAOjC,MAAM,EAAE,GAAG,IAAA,gBAAQ,GAAE,CAAC;QAEtB,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAE3B,MAAM,SAAS,GAAG,MAAM,IAAA,WAAG,EAAC,EAAE,EAAE,oDAAoD,CAAC,CAAC;YACtF,MAAM,YAAY,GAAG,SAAS;gBAC5B,CAAC,CAAC,SAAS;qBACN,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,OAAO,CAAC;gBACpB,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,SAAS,GAAG,CAAC,GAAG,0BAAc,EAAE,GAAG,YAAY,CAAC,CAAC;YAEvD,MAAM,YAAY,GAAG,MAAM,IAAA,eAAO,EAAC,EAAE,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAEvE,MAAM,MAAM,GAAG,MAAM,IAAA,WAAG,EAAC,EAAE,EAAE,uBAAuB,oBAAQ,CAAC,UAAU,KAAK,CAAC,CAAC;YAC9E,MAAM,UAAU,GAAG,MAAM,IAAI,oBAAQ,CAAC,UAAU,CAAC;YAEjD,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAU,CAAC;YACtD,MAAM,IAAI,GAAG,MAAM,IAAA,eAAO,EAAC,EAAE,EAAE,+BAA+B,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YAE1E,MAAM,UAAU,GAAG,CAAC,UAAU,EAAE,UAAU,CAAU,CAAC;YACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACtF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;YAC1E,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAA,eAAO,EAAC,EAAE,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC,CAAC,CAA4B,CAAC;YAEnG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QACzE,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;IACH,CAAC;CAAA;AAED;;;;;;;GAOG;AACH,SAAgB,KAAK,CAAC,OAAe;IACnC,OAAO,CAAO,IAAU,EAAE,OAAyB,EAAE,EAAE;QACrD,6EAA6E;QAC7E,wEAAwE;QACxE,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,CAAC;QACzD,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,IAAI,SAAS,CAAC,cAAc,CAAC;QAChE,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAEhD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,8BAA8B,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QACvC,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,WAAW,MAAM,CAAC;QAErE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,yCAAyC,WAAW,GAAG,CAAC,CAAC;QAC7E,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAExB,IAAI,WAAmB,CAAC;QACxB,IAAI,aAAyE,CAAC;QAC9E,IAAI,WAAqB,CAAC;QAC1B,IAAI,gBAA0B,CAAC;QAC/B,IAAI,aAAsC,CAAC;QAE3C,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,CAAC,GAAG,0BAAc,CAAC,CAAC;YACnC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,oBAAQ,CAAC;YACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,oBAAoB;YACjD,WAAW,GAAG,IAAA,wBAAgB,EAAC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YACvE,aAAa,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YACnD,WAAW,GAAG,MAAM,CAAC;YACrB,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;YACxD,aAAa,GAAG,QAAQ,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;QAC3F,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAC;YAC1C,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;YAElE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YACvD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,YAAY,EAAE,CAAC,CAAC;YACzD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;YACjD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;YAErD,WAAW,GAAG,IAAA,wBAAgB,EAAC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YACvE,aAAa,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YACnD,WAAW,GAAG,MAAM,CAAC;YACrB,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;YACxD,aAAa,GAAG,QAAQ,CAAC;QAC3B,CAAC;QAED,OAAO,IAAA,kBAAK,EAAC;YACX,CAAC,CAAO,EAAE,OAAyB,EAAE,EAAE;gBACrC,MAAM,UAAU,GAAG,GAAG,iBAAiB,aAAa,CAAC;gBACrD,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC1B,IAAI,OAAO,GAAG,kCAAkC,CAAC;oBAEjD,gBAAgB,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;wBACjC,MAAM,QAAQ,GACZ,aAAa,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;wBAE/E,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;4BACtB,OAAO,IAAI,UAAU,QAAQ,wDAAwD,CAAC;wBACxF,CAAC;6BAAM,CAAC;4BACN,OAAO,IAAI,GAAG,QAAQ,qBAAqB,KAAK,iCAAiC,CAAC;wBACpF,CAAC;oBACH,CAAC,CAAC,CAAC;oBAEH,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;oBAC9B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;oBACzD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,UAAU,sCAAsC,CAAC,CAAC;gBAC/F,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;oBACzD,OAAO,CAAC,MAAM,CAAC,IAAI,CACjB,eAAe,UAAU,sEAAsE,CAChG,CAAC;oBACF,OAAO,CAAC,MAAM,CAAC,IAAI,CACjB,qFAAqF,CACtF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,CAAC,CAAO,EAAE,EAAE;gBACV,MAAM,OAAO,GAAG,eAAe,CAAC;gBAChC,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC/B,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;oBAC1C,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;oBAChC,GAAG,CAAC,OAAO,CAAC,QAAQ,GAAG,8CAA8C,WAAW,EAAE,CAAC;oBACnF,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;YACD,CAAC,CAAO,EAAE,OAAyB,EAAE,EAAE;;gBACrC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAChD,IAAI,eAAe,EAAE,CAAC;oBACpB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACzD,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;oBAChD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;oBAC/C,MAAM,UAAU,GAAG,GAAG,iBAAiB,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBAExE,gDAAgD;oBAChD,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;wBACzD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;oBACpC,CAAC;oBAED,+DAA+D;oBAC/D,MAAM,UAAU,GAAG,MAAA,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,0CAAE,UAAU,CAAC;oBACtE,IAAI,UAAU,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC;wBAC/C,IAAI,OAAO,UAAU,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;4BAChD,UAAU,CAAC,YAAY,CAAC,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAC;4BACtE,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC;wBACxD,CAAC;6BAAM,CAAC;4BACN,UAAU,CAAC,YAAY,GAAG;gCACxB,MAAM,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE;6BAClC,CAAC;wBACJ,CAAC;wBACD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;oBAC1F,CAAC;oBAED,CAAC,CAAC,SAAS,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;YACD,CAAC,CAAO,EAAE,GAAqB,EAAE,EAAE;gBACjC,IAAA,2BAAc,EAAC,CAAC,EAAE,GAAG,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC;gBACvD,IAAA,2BAAc,EAAC,CAAC,EAAE,GAAG,EAAE,iBAAiB,kCACnC,aAAa,KAChB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAG,OAAO,CAAC,QAAoC,IAAI,aAAa,IACxE,CAAC;gBACH,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACpB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;gBACnF,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtB,CAAC;SACF,CAAC,CAAC;IACL,CAAC,CAAA,CAAC;AACJ,CAAC"}
|
|
@@ -22,14 +22,19 @@
|
|
|
22
22
|
"items": [
|
|
23
23
|
{
|
|
24
24
|
"value": "quick",
|
|
25
|
-
"label": "Quick – apply defaults instantly (theme: system, mode: class)"
|
|
25
|
+
"label": "Quick – apply defaults instantly (theme: system, mode: class, strategy: critters)"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"value": "custom",
|
|
29
|
-
"label": "Custom – choose themes, default theme, storage key and
|
|
29
|
+
"label": "Custom – choose themes, default theme, storage key and strategy"
|
|
30
30
|
}
|
|
31
31
|
]
|
|
32
32
|
}
|
|
33
|
+
},
|
|
34
|
+
"strategy": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "The strategy to use for flash prevention (critters or blocking).",
|
|
37
|
+
"enum": ["blocking", "critters"]
|
|
33
38
|
}
|
|
34
39
|
},
|
|
35
40
|
"required": ["project"]
|
|
@@ -12,9 +12,4 @@ export declare function ask(rl: readline.Interface, question: string): Promise<s
|
|
|
12
12
|
* If the input is invalid or ignored, the default index item is returned.
|
|
13
13
|
*/
|
|
14
14
|
export declare function askList(rl: readline.Interface, label: string, items: readonly string[], defaultIndex?: number): Promise<string>;
|
|
15
|
-
|
|
16
|
-
* Builds the 'provideThemeStack({...})' string representation.
|
|
17
|
-
* It compares the selected values with library defaults to generate a
|
|
18
|
-
* minimal call string (omitting properties that match defaults).
|
|
19
|
-
*/
|
|
20
|
-
export declare function buildProvideCall(theme: string, storageKey: string, mode: string, themes: string[]): string;
|
|
15
|
+
export declare function buildProvideCall(defaultTheme: string, storageKey: string, mode: string, themes: string[]): string;
|
|
@@ -14,7 +14,6 @@ exports.ask = ask;
|
|
|
14
14
|
exports.askList = askList;
|
|
15
15
|
exports.buildProvideCall = buildProvideCall;
|
|
16
16
|
const readline = require("readline");
|
|
17
|
-
const constants_1 = require("./constants");
|
|
18
17
|
/**
|
|
19
18
|
* Creates a readline interface using the system's standard input and output.
|
|
20
19
|
*/
|
|
@@ -40,31 +39,15 @@ function askList(rl_1, label_1, items_1) {
|
|
|
40
39
|
return isNaN(n) || n < 1 || n > items.length ? items[defaultIndex] : items[n - 1];
|
|
41
40
|
});
|
|
42
41
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
mode === constants_1.DEFAULTS.mode &&
|
|
54
|
-
isDefaultThemes;
|
|
55
|
-
if (isAllDefault)
|
|
56
|
-
return `provideThemeStack()`;
|
|
57
|
-
const parts = [];
|
|
58
|
-
if (theme !== constants_1.DEFAULTS.theme)
|
|
59
|
-
parts.push(`theme: '${theme}'`);
|
|
60
|
-
if (storageKey !== constants_1.DEFAULTS.storageKey)
|
|
61
|
-
parts.push(`storageKey: '${storageKey}'`);
|
|
62
|
-
if (mode !== constants_1.DEFAULTS.mode)
|
|
63
|
-
parts.push(`mode: '${mode}'`);
|
|
64
|
-
if (!isDefaultThemes) {
|
|
65
|
-
const arr = themes.map((t) => `'${t}'`).join(', ');
|
|
66
|
-
parts.push(`themes: [${arr}]`);
|
|
67
|
-
}
|
|
68
|
-
return `provideThemeStack({ ${parts.join(', ')} })`;
|
|
42
|
+
function buildProvideCall(defaultTheme, storageKey, mode, themes) {
|
|
43
|
+
const themesArr = themes.map((t) => `'${t}'`).join(', ');
|
|
44
|
+
return [
|
|
45
|
+
'provideThemeStack({',
|
|
46
|
+
` themes: [${themesArr}],`,
|
|
47
|
+
` defaultTheme: '${defaultTheme}',`,
|
|
48
|
+
` storageKey: '${storageKey}',`,
|
|
49
|
+
` mode: '${mode}',`,
|
|
50
|
+
' })',
|
|
51
|
+
].join('\n');
|
|
69
52
|
}
|
|
70
53
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/utils.ts"],"names":[],"mappings":";;;;;;;;;;;AAMA,4BAEC;AAKD,kBAEC;AAMD,0BAWC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/utils.ts"],"names":[],"mappings":";;;;;;;;;;;AAMA,4BAEC;AAKD,kBAEC;AAMD,0BAWC;AAED,4CAeC;AAjDD,qCAAqC;AAGrC;;GAEG;AACH,SAAgB,QAAQ;IACtB,OAAO,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACpF,CAAC;AAED;;GAEG;AACH,SAAgB,GAAG,CAAC,EAAsB,EAAE,QAAgB;IAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;GAGG;AACH,SAAsB,OAAO;yDAC3B,EAAsB,EACtB,KAAa,EACb,KAAwB,EACxB,YAAY,GAAG,CAAC;QAEhB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,EAAE,EAAE,aAAa,YAAY,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpF,CAAC;CAAA;AAED,SAAgB,gBAAgB,CAC9B,YAAoB,EACpB,UAAkB,EAClB,IAAY,EACZ,MAAgB;IAEhB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO;QACL,qBAAqB;QACrB,kBAAkB,SAAS,IAAI;QAC/B,wBAAwB,YAAY,IAAI;QACxC,sBAAsB,UAAU,IAAI;QACpC,gBAAgB,IAAI,IAAI;QACxB,QAAQ;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sync = sync;
|
|
4
|
+
const constants_1 = require("../ng-add/constants");
|
|
5
|
+
// ── Regex patterns ────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Matches the provideThemeStack() call in app.config.ts.
|
|
8
|
+
* Captures the full options object string (may be empty or span multiple lines).
|
|
9
|
+
*
|
|
10
|
+
* Examples matched:
|
|
11
|
+
* provideThemeStack()
|
|
12
|
+
* provideThemeStack({ mode: 'attribute', themes: ['dark', 'light'] })
|
|
13
|
+
* provideThemeStack({ ← multi-line call (new explicit format)
|
|
14
|
+
* themes: ['light', 'dark'],
|
|
15
|
+
* defaultTheme: 'system',
|
|
16
|
+
* })
|
|
17
|
+
*/
|
|
18
|
+
const PROVIDE_CALL_RE = /provideThemeStack\s*\(([\s\S]*?)\)/;
|
|
19
|
+
/** Extracts "mode" value from the captured options string. */
|
|
20
|
+
const OPTION_MODE_RE = /mode\s*:\s*['"]([^'"]+)['"]/;
|
|
21
|
+
/** Extracts "storageKey" value from the captured options string. */
|
|
22
|
+
const OPTION_KEY_RE = /storageKey\s*:\s*['"]([^'"]+)['"]/;
|
|
23
|
+
/** Extracts "defaultTheme" value from the captured options string. */
|
|
24
|
+
const OPTION_DEFAULT_THEME_RE = /defaultTheme\s*:\s*['"]([^'"]+)['"]/;
|
|
25
|
+
const OPTION_STRATEGY_RE = /strategy\s*:\s*['"]([^'"]+)['"]/;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the themes array from the options string.
|
|
28
|
+
* Matches: themes: ['light', 'dark', 'sunset']
|
|
29
|
+
*/
|
|
30
|
+
const OPTION_THEMES_RE = /themes\s*:\s*\[([^\]]*)\]/;
|
|
31
|
+
/** Matches the complete <script> anti-flash block (identified by its marker comment). */
|
|
32
|
+
const SCRIPT_BLOCK_RE = /<!-- ngx-theme-stack anti-flash -->\s*<script>[\s\S]*?<\/script>/;
|
|
33
|
+
/** Marker injected by ng-add that delimits the Critters Trick zone in <body>. */
|
|
34
|
+
const CRITTERS_MARKER = '<!-- ngx-theme-stack critters-trick -->';
|
|
35
|
+
/** Regex that matches the entire Critters Trick block (marker + divs). */
|
|
36
|
+
const CRITTERS_BLOCK_RE = /<!-- ngx-theme-stack critters-trick -->[\s\S]*?<!-- \/ngx-theme-stack critters-trick -->/;
|
|
37
|
+
// ── Config extraction ─────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Reads `app.config.ts` (or `main.ts`) and extracts the current
|
|
40
|
+
* `provideThemeStack()` configuration using regex.
|
|
41
|
+
*
|
|
42
|
+
* Falls back to library defaults for any option that is not found.
|
|
43
|
+
*/
|
|
44
|
+
function extractConfig(tree, sourceRoot, context) {
|
|
45
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
46
|
+
const candidates = [
|
|
47
|
+
`${sourceRoot}/app/app.config.ts`,
|
|
48
|
+
`${sourceRoot}/main.ts`,
|
|
49
|
+
].map((p) => (p.startsWith('/') ? p.slice(1) : p));
|
|
50
|
+
for (const filePath of candidates) {
|
|
51
|
+
if (!tree.exists(filePath))
|
|
52
|
+
continue;
|
|
53
|
+
const content = tree.readText(filePath);
|
|
54
|
+
const provideMatch = PROVIDE_CALL_RE.exec(content);
|
|
55
|
+
if (!provideMatch) {
|
|
56
|
+
context.logger.warn(`⚠ provideThemeStack() not found in ${filePath}. Using library defaults.\n` +
|
|
57
|
+
` Tip: Add provideThemeStack({...}) to your providers for explicit control.`);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const opts = provideMatch[1]; // everything inside provideThemeStack(...)
|
|
61
|
+
const mode = (_b = (_a = OPTION_MODE_RE.exec(opts)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : constants_1.DEFAULTS.mode;
|
|
62
|
+
const storageKey = (_d = (_c = OPTION_KEY_RE.exec(opts)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : constants_1.DEFAULTS.storageKey;
|
|
63
|
+
const defaultTheme = (_f = (_e = OPTION_DEFAULT_THEME_RE.exec(opts)) === null || _e === void 0 ? void 0 : _e[1]) !== null && _f !== void 0 ? _f : constants_1.DEFAULTS.defaultTheme;
|
|
64
|
+
const strategy = (_h = (_g = OPTION_STRATEGY_RE.exec(opts)) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : undefined;
|
|
65
|
+
// Extract themes array: ['light', 'dark', 'sunset'] → ['light', 'dark', 'sunset']
|
|
66
|
+
const themesRaw = (_k = (_j = OPTION_THEMES_RE.exec(opts)) === null || _j === void 0 ? void 0 : _j[1]) !== null && _k !== void 0 ? _k : '';
|
|
67
|
+
const themes = themesRaw
|
|
68
|
+
? themesRaw
|
|
69
|
+
.split(',')
|
|
70
|
+
.map((t) => t.trim().replace(/^['"]|['"]$/g, ''))
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
: [...constants_1.DEFAULTS.themes];
|
|
73
|
+
context.logger.info(` Detected mode : ${mode}`);
|
|
74
|
+
context.logger.info(` Detected strategy : ${strategy !== null && strategy !== void 0 ? strategy : '(not in code, using auto-detect)'}`);
|
|
75
|
+
context.logger.info(` Detected storageKey : ${storageKey}`);
|
|
76
|
+
context.logger.info(` Detected defaultTheme : ${defaultTheme}`);
|
|
77
|
+
context.logger.info(` Detected themes : [${themes.join(', ')}]`);
|
|
78
|
+
return { mode, strategy, storageKey, defaultTheme, themes };
|
|
79
|
+
}
|
|
80
|
+
// Fallback to defaults if no config file found
|
|
81
|
+
return {
|
|
82
|
+
mode: constants_1.DEFAULTS.mode,
|
|
83
|
+
storageKey: constants_1.DEFAULTS.storageKey,
|
|
84
|
+
defaultTheme: constants_1.DEFAULTS.defaultTheme,
|
|
85
|
+
themes: [...constants_1.DEFAULTS.themes],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// ── Anti-flash script generation ──────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* Builds the minimal blocking inline script — identical logic to anti-flash.ts
|
|
91
|
+
* but kept here to avoid cross-directory dependencies in the schematic build.
|
|
92
|
+
*/
|
|
93
|
+
function buildScript(config) {
|
|
94
|
+
const { storageKey, defaultTheme, mode } = config;
|
|
95
|
+
return (`(function(){try{` +
|
|
96
|
+
`var k=${JSON.stringify(storageKey)},` +
|
|
97
|
+
`d=${JSON.stringify(defaultTheme)},` +
|
|
98
|
+
`m=${JSON.stringify(mode)},` +
|
|
99
|
+
`t=localStorage.getItem(k)||d,` +
|
|
100
|
+
`e=document.documentElement;` +
|
|
101
|
+
`if(!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t))t=d;` +
|
|
102
|
+
`if(t==='system')t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';` +
|
|
103
|
+
`if(m==='class'||m==='both')e.classList.add(t);` +
|
|
104
|
+
`if(m==='attribute'||m==='both')e.setAttribute('data-theme',t);` +
|
|
105
|
+
`if(t==='dark'||t==='light')e.style.setProperty('color-scheme',t);` +
|
|
106
|
+
`}catch(x){}})();`);
|
|
107
|
+
}
|
|
108
|
+
// ── Critters Trick div generation ─────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Generates the hidden divs for the Critters Trick based on the mode.
|
|
111
|
+
*
|
|
112
|
+
* When Angular builds for production, Critters inlines "critical" CSS.
|
|
113
|
+
* It determines "critical" by checking which selectors match elements in the HTML.
|
|
114
|
+
* By placing hidden divs with the theme class/attribute, we trick Critters into
|
|
115
|
+
* inlining ALL theme token blocks — achieving zero-network-request CSS for themes.
|
|
116
|
+
*
|
|
117
|
+
* @param themes - The list of themes to generate divs for (excludes 'system').
|
|
118
|
+
* @param mode - 'class' | 'attribute' | 'both'
|
|
119
|
+
*/
|
|
120
|
+
function buildCrittersDivs(themes, mode) {
|
|
121
|
+
// 'system' is a meta-theme that resolves to 'light' or 'dark'; no CSS selector needed.
|
|
122
|
+
const renderableThemes = themes.filter((t) => t !== 'system');
|
|
123
|
+
const divs = renderableThemes
|
|
124
|
+
.map((theme) => {
|
|
125
|
+
if (mode === 'class') {
|
|
126
|
+
return ` <div class="${theme}"></div>`;
|
|
127
|
+
}
|
|
128
|
+
else if (mode === 'attribute') {
|
|
129
|
+
return ` <div data-theme="${theme}"></div>`;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// 'both'
|
|
133
|
+
return ` <div class="${theme}" data-theme="${theme}"></div>`;
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.join('\n');
|
|
137
|
+
return (`<!-- ngx-theme-stack critters-trick -->\n` +
|
|
138
|
+
` <div id="ngx-theme-stack-critters-trick" hidden>\n${divs}\n </div>\n` +
|
|
139
|
+
` <!-- /ngx-theme-stack critters-trick -->`);
|
|
140
|
+
}
|
|
141
|
+
// ── index.html patching ───────────────────────────────────────────────────────
|
|
142
|
+
function syncIndexHtml(tree, context, sourceRoot, config, strategy) {
|
|
143
|
+
const candidates = [`${sourceRoot}/index.html`, 'public/index.html'].map((p) => p.startsWith('/') ? p.slice(1) : p);
|
|
144
|
+
for (const path of candidates) {
|
|
145
|
+
if (!tree.exists(path))
|
|
146
|
+
continue;
|
|
147
|
+
let content = tree.readText(path);
|
|
148
|
+
if (!content.includes('ngx-theme-stack anti-flash')) {
|
|
149
|
+
context.logger.warn(`⚠ Anti-flash script marker not found in ${path}.\n` +
|
|
150
|
+
` Run 'ng add ngx-theme-stack' first, or add the script manually.`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// ── 1. Sync the anti-flash JS script ───────────────────────────────────
|
|
154
|
+
const newScriptBlock = `<!-- ngx-theme-stack anti-flash -->\n <script>${buildScript(config)}</script>`;
|
|
155
|
+
const updatedScript = content.replace(SCRIPT_BLOCK_RE, newScriptBlock);
|
|
156
|
+
if (updatedScript === content) {
|
|
157
|
+
context.logger.warn(`⚠ Could not replace script block in ${path}. The format may have changed.`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
content = updatedScript;
|
|
161
|
+
context.logger.info(`✔ Anti-flash script synced in ${path}`);
|
|
162
|
+
// ── 2. Sync the Critters Trick divs (only for 'critters' strategy) ─────
|
|
163
|
+
if (strategy === 'critters') {
|
|
164
|
+
const crittersBlock = buildCrittersDivs(config.themes, config.mode);
|
|
165
|
+
const CRITTERS_ID_RE = /<div id="ngx-theme-stack-critters-trick"[\s\S]*?<\/div>/;
|
|
166
|
+
if (CRITTERS_BLOCK_RE.test(content)) {
|
|
167
|
+
content = content.replace(CRITTERS_BLOCK_RE, crittersBlock);
|
|
168
|
+
context.logger.info(`✔ Critters Trick block updated in ${path}`);
|
|
169
|
+
}
|
|
170
|
+
else if (CRITTERS_ID_RE.test(content)) {
|
|
171
|
+
content = content.replace(CRITTERS_ID_RE, crittersBlock);
|
|
172
|
+
context.logger.info(`✔ Critters Trick div wrapped in ${path}`);
|
|
173
|
+
}
|
|
174
|
+
else if (content.includes(CRITTERS_MARKER)) {
|
|
175
|
+
content = content.replace(CRITTERS_MARKER, crittersBlock);
|
|
176
|
+
context.logger.info(`✔ Critters Trick divs injected in ${path}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
content = content.replace('</body>', ` ${crittersBlock}\n </body>`);
|
|
180
|
+
context.logger.info(`✔ Critters Trick block added before </body> in ${path}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// blocking strategy: remove any existing Critters divs if present
|
|
185
|
+
if (CRITTERS_BLOCK_RE.test(content)) {
|
|
186
|
+
content = content.replace(CRITTERS_BLOCK_RE, '');
|
|
187
|
+
context.logger.info(`✔ Critters Trick divs removed (blocking strategy) in ${path}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
tree.overwrite(path, content);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
context.logger.warn(`⚠ Could not find index.html (tried: ${candidates.join(', ')}).`);
|
|
194
|
+
}
|
|
195
|
+
// ── angular.json patching ───────────────────────────────────────────────────
|
|
196
|
+
function syncAngularJson(tree, context, projectName, strategy) {
|
|
197
|
+
var _a, _b;
|
|
198
|
+
const content = tree.read('/angular.json');
|
|
199
|
+
if (!content)
|
|
200
|
+
return;
|
|
201
|
+
const workspace = JSON.parse(content.toString());
|
|
202
|
+
const project = workspace.projects[projectName];
|
|
203
|
+
if (!project)
|
|
204
|
+
return;
|
|
205
|
+
const buildTarget = (_a = project.architect) === null || _a === void 0 ? void 0 : _a.build;
|
|
206
|
+
if (!buildTarget)
|
|
207
|
+
return;
|
|
208
|
+
const prodConfig = (_b = buildTarget.configurations) === null || _b === void 0 ? void 0 : _b.production;
|
|
209
|
+
if (!prodConfig)
|
|
210
|
+
return;
|
|
211
|
+
if (strategy === 'blocking') {
|
|
212
|
+
// Disable inlineCritical for blocking strategy
|
|
213
|
+
if (typeof prodConfig.optimization === 'object') {
|
|
214
|
+
prodConfig.optimization.styles = prodConfig.optimization.styles || {};
|
|
215
|
+
if (prodConfig.optimization.styles.inlineCritical !== false) {
|
|
216
|
+
prodConfig.optimization.styles.inlineCritical = false;
|
|
217
|
+
context.logger.info(`✔ Disabled inlineCritical in angular.json projects/${projectName} (production).`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// It's either boolean or undefined
|
|
222
|
+
prodConfig.optimization = {
|
|
223
|
+
styles: { inlineCritical: false },
|
|
224
|
+
};
|
|
225
|
+
context.logger.info(`✔ Disabled inlineCritical in angular.json projects/${projectName} (production).`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Enable inlineCritical (or revert to default) for critters strategy
|
|
230
|
+
if (typeof prodConfig.optimization === 'object' && prodConfig.optimization.styles) {
|
|
231
|
+
if (prodConfig.optimization.styles.inlineCritical === false) {
|
|
232
|
+
prodConfig.optimization.styles.inlineCritical = true;
|
|
233
|
+
context.logger.info(`✔ Re-enabled inlineCritical in angular.json projects/${projectName} (production).`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
|
|
238
|
+
}
|
|
239
|
+
// ── Schematic factory ─────────────────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* `ng generate ngx-theme-stack:sync`
|
|
242
|
+
*
|
|
243
|
+
* Reads the current `provideThemeStack()` configuration from `app.config.ts`
|
|
244
|
+
* and regenerates:
|
|
245
|
+
*
|
|
246
|
+
* 1. **The anti-flash inline script** in `index.html` — keeping `storageKey`,
|
|
247
|
+
* `defaultTheme`, and `mode` in sync with the Angular provider.
|
|
248
|
+
*
|
|
249
|
+
* 2. **The Critters Trick divs** (if `strategy: 'critters'`) — hidden `<div>`
|
|
250
|
+
* elements that trick Angular's built-in CSS inliner (Critters) into treating
|
|
251
|
+
* all theme token blocks as "critical CSS", inlining them in the `<head>`
|
|
252
|
+
* at build time. This achieves zero-flash without any extra network requests.
|
|
253
|
+
*
|
|
254
|
+
* Run this whenever you change `mode`, `storageKey`, `defaultTheme`, or `themes`
|
|
255
|
+
* inside `provideThemeStack()`. Tip: add it as a `prebuild` script in package.json
|
|
256
|
+
* so it runs automatically before every build.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* // One-off sync
|
|
260
|
+
* ng generate ngx-theme-stack:sync
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* // Automatic sync (recommended — add to package.json)
|
|
264
|
+
* "prebuild": "ng generate ngx-theme-stack:sync"
|
|
265
|
+
*/
|
|
266
|
+
/**
|
|
267
|
+
* Auto-detects the strategy by checking if a Critters marker exists in index.html.
|
|
268
|
+
* This allows the prebuild command to run with zero extra flags.
|
|
269
|
+
*/
|
|
270
|
+
function detectStrategy(tree, sourceRoot, explicitStrategy) {
|
|
271
|
+
if (explicitStrategy)
|
|
272
|
+
return explicitStrategy;
|
|
273
|
+
const candidates = [`${sourceRoot}/index.html`, 'public/index.html'].map((p) => p.startsWith('/') ? p.slice(1) : p);
|
|
274
|
+
for (const path of candidates) {
|
|
275
|
+
if (!tree.exists(path))
|
|
276
|
+
continue;
|
|
277
|
+
const content = tree.readText(path);
|
|
278
|
+
// If either the full block OR the bare marker comment exist → critters
|
|
279
|
+
if (content.includes('ngx-theme-stack critters-trick'))
|
|
280
|
+
return 'critters';
|
|
281
|
+
return 'blocking';
|
|
282
|
+
}
|
|
283
|
+
return 'critters'; // safe default
|
|
284
|
+
}
|
|
285
|
+
function sync(options) {
|
|
286
|
+
return (tree, context) => {
|
|
287
|
+
var _a, _b;
|
|
288
|
+
const workspaceConfig = tree.read('/angular.json');
|
|
289
|
+
if (!workspaceConfig) {
|
|
290
|
+
throw new Error('Could not find angular.json. Are you in an Angular workspace?');
|
|
291
|
+
}
|
|
292
|
+
const workspace = JSON.parse(workspaceConfig.toString());
|
|
293
|
+
const projectName = (_a = options.project) !== null && _a !== void 0 ? _a : workspace.defaultProject;
|
|
294
|
+
const project = workspace.projects[projectName];
|
|
295
|
+
if (!project) {
|
|
296
|
+
throw new Error(`Project "${projectName}" not found in angular.json.`);
|
|
297
|
+
}
|
|
298
|
+
const sourceRoot = project.sourceRoot || `${(_b = project.root) !== null && _b !== void 0 ? _b : ''}/src`;
|
|
299
|
+
const config = extractConfig(tree, sourceRoot, context);
|
|
300
|
+
const strategy = (options.strategy || config.strategy || detectStrategy(tree, sourceRoot));
|
|
301
|
+
context.logger.info('');
|
|
302
|
+
context.logger.info(`🔄 ngx-theme-stack sync [project: ${projectName}, strategy: ${strategy}]`);
|
|
303
|
+
context.logger.info('');
|
|
304
|
+
syncIndexHtml(tree, context, sourceRoot, config, strategy);
|
|
305
|
+
syncAngularJson(tree, context, projectName, strategy);
|
|
306
|
+
context.logger.info('');
|
|
307
|
+
context.logger.info('✅ Done! The anti-flash setup is now in sync with your provider config.');
|
|
308
|
+
if (strategy === 'critters') {
|
|
309
|
+
context.logger.info(' Critters Trick: theme CSS will be automatically inlined at build time.');
|
|
310
|
+
}
|
|
311
|
+
context.logger.info('');
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=index.js.map
|