@webstir-io/webstir-frontend 0.1.40 → 0.1.41
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 +124 -60
- package/dist/assets/imageOptimizer.js +10 -15
- package/dist/assets/precompression.js +1 -1
- package/dist/builders/contentBuilder.js +102 -90
- package/dist/builders/cssBuilder.js +25 -19
- package/dist/builders/htmlBuilder.js +57 -42
- package/dist/builders/index.js +1 -1
- package/dist/builders/jsBuilder.js +219 -76
- package/dist/builders/staticAssetsBuilder.js +27 -9
- package/dist/builders/types.d.ts +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -30
- package/dist/config/manifest.js +7 -6
- package/dist/config/paths.js +2 -2
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +7 -6
- package/dist/config/setup.js +1 -1
- package/dist/config/workspace.js +11 -9
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.js +5 -5
- package/dist/core/diagnostics.js +1 -1
- package/dist/core/pages.js +4 -4
- package/dist/hooks.js +3 -3
- package/dist/html/criticalCss.js +6 -3
- package/dist/html/htmlSecurity.d.ts +6 -1
- package/dist/html/htmlSecurity.js +28 -14
- package/dist/html/lazyLoad.js +1 -1
- package/dist/html/pageScaffold.js +1 -1
- package/dist/html/resourceHints.js +5 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +110 -0
- package/dist/modes/ssg/metadata.js +4 -4
- package/dist/modes/ssg/routing.js +2 -5
- package/dist/modes/ssg/seo.js +5 -5
- package/dist/modes/ssg/views.js +17 -11
- package/dist/operations.js +18 -10
- package/dist/pipeline.d.ts +1 -0
- package/dist/pipeline.js +6 -1
- package/dist/provider.js +28 -24
- package/dist/runtime/boundary.d.ts +28 -0
- package/dist/runtime/boundary.js +247 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/utils/fs.d.ts +11 -10
- package/dist/utils/fs.js +48 -20
- package/dist/utils/glob.d.ts +8 -0
- package/dist/utils/glob.js +21 -0
- package/dist/utils/hash.js +1 -2
- package/dist/utils/pagePaths.js +2 -2
- package/package.json +19 -14
- package/scripts/publish.sh +2 -94
- package/scripts/update-contract.sh +12 -10
- package/src/assets/assetManifest.ts +39 -29
- package/src/assets/imageOptimizer.ts +91 -82
- package/src/assets/precompression.ts +22 -16
- package/src/builders/contentBuilder.ts +1224 -1149
- package/src/builders/cssBuilder.ts +466 -417
- package/src/builders/htmlBuilder.ts +511 -448
- package/src/builders/index.ts +7 -7
- package/src/builders/jsBuilder.ts +538 -280
- package/src/builders/staticAssetsBuilder.ts +166 -135
- package/src/builders/types.ts +7 -6
- package/src/cli.ts +66 -90
- package/src/config/manifest.ts +16 -14
- package/src/config/paths.ts +5 -5
- package/src/config/schema.ts +38 -37
- package/src/config/setup.ts +7 -7
- package/src/config/workspace.ts +118 -116
- package/src/config/workspaceManifest.ts +14 -14
- package/src/core/constants.ts +62 -62
- package/src/core/diagnostics.ts +26 -26
- package/src/core/pages.ts +19 -19
- package/src/hooks.ts +128 -118
- package/src/html/criticalCss.ts +84 -77
- package/src/html/htmlSecurity.ts +107 -66
- package/src/html/lazyLoad.ts +22 -19
- package/src/html/pageScaffold.ts +37 -28
- package/src/html/resourceHints.ts +83 -74
- package/src/index.ts +2 -0
- package/src/inspect.ts +158 -0
- package/src/modes/ssg/metadata.ts +53 -51
- package/src/modes/ssg/routing.ts +177 -177
- package/src/modes/ssg/seo.ts +208 -200
- package/src/modes/ssg/validation.ts +31 -25
- package/src/modes/ssg/views.ts +257 -238
- package/src/operations.ts +105 -95
- package/src/pipeline.ts +81 -69
- package/src/provider.ts +184 -176
- package/src/runtime/boundary.ts +325 -0
- package/src/runtime/index.ts +1 -0
- package/src/types.ts +107 -48
- package/src/utils/changedFile.ts +22 -22
- package/src/utils/fs.ts +73 -26
- package/src/utils/glob.ts +38 -0
- package/src/utils/hash.ts +2 -4
- package/src/utils/pagePaths.ts +35 -23
- package/src/utils/pathMatch.ts +26 -23
- package/tests/add-page-defaults.test.js +44 -39
- package/tests/bundlerParity.test.js +252 -0
- package/tests/cli.contract.test.js +13 -0
- package/tests/content-pages.test.js +108 -13
- package/tests/css-app-imports.test.js +22 -11
- package/tests/css-page-imports.test.js +26 -13
- package/tests/diagnostics.test.js +39 -36
- package/tests/features.test.js +48 -43
- package/tests/hooks.test.js +58 -42
- package/tests/htmlSecurity.test.js +66 -0
- package/tests/inspect.test.js +148 -0
- package/tests/provider.integration.test.js +71 -20
- package/tests/runtime.test.js +493 -0
- package/tests/ssg-defaults.test.js +284 -177
- package/tests/ssg-guardrails.test.js +51 -51
- package/tsconfig.json +3 -10
- package/dist/watch/frontendFiles.d.ts +0 -3
- package/dist/watch/frontendFiles.js +0 -25
- package/dist/watch/hotUpdateTracker.d.ts +0 -51
- package/dist/watch/hotUpdateTracker.js +0 -205
- package/dist/watch/pipelineHelpers.d.ts +0 -26
- package/dist/watch/pipelineHelpers.js +0 -177
- package/dist/watch/types.d.ts +0 -27
- package/dist/watch/types.js +0 -1
- package/dist/watch/watchCoordinator.d.ts +0 -36
- package/dist/watch/watchCoordinator.js +0 -551
- package/dist/watch/watchDaemon.d.ts +0 -17
- package/dist/watch/watchDaemon.js +0 -127
- package/dist/watch/watchReporter.d.ts +0 -21
- package/dist/watch/watchReporter.js +0 -64
- package/scripts/smoke.mjs +0 -35
- package/src/watch/frontendFiles.ts +0 -32
- package/src/watch/hotUpdateTracker.ts +0 -285
- package/src/watch/pipelineHelpers.ts +0 -242
- package/src/watch/types.ts +0 -23
- package/src/watch/watchCoordinator.ts +0 -666
- package/src/watch/watchDaemon.ts +0 -144
- package/src/watch/watchReporter.ts +0 -98
package/src/core/pages.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { glob } from 'glob';
|
|
3
2
|
import { pathExists } from '../utils/fs.js';
|
|
3
|
+
import { scanDirectories } from '../utils/glob.js';
|
|
4
4
|
|
|
5
5
|
export interface PageInfo {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly directory: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export async function getPages(root: string): Promise<PageInfo[]> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const directories = await getPageDirectories(root);
|
|
12
|
+
return directories.map((entry) => ({
|
|
13
|
+
name: entry.name,
|
|
14
|
+
directory: entry.directory,
|
|
15
|
+
}));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export async function getPageDirectories(root: string): Promise<PageInfo[]> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (!(await pathExists(root))) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const entries = await scanDirectories('*/', { cwd: root, absolute: false });
|
|
24
|
+
return entries.map((entry) => {
|
|
25
|
+
const name = entry.endsWith('/') ? entry.slice(0, -1) : entry;
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
directory: path.join(root, name),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
31
|
}
|
package/src/hooks.ts
CHANGED
|
@@ -7,169 +7,179 @@ import type { PipelineMode } from './pipeline.js';
|
|
|
7
7
|
const CONFIG_CANDIDATES = ['webstir.config.mjs', 'webstir.config.js', 'webstir.config.cjs'];
|
|
8
8
|
|
|
9
9
|
export interface HookContext {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
readonly config: FrontendConfig;
|
|
11
|
+
readonly mode: PipelineMode;
|
|
12
|
+
readonly workspaceRoot: string;
|
|
13
|
+
readonly builderName?: string;
|
|
14
|
+
readonly changedFile?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export type HookHandler = (context: HookContext) => unknown | Promise<unknown>;
|
|
18
18
|
|
|
19
19
|
export interface ResolvedHooks {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
readonly pipelineBefore: HookHandler[];
|
|
21
|
+
readonly pipelineAfter: HookHandler[];
|
|
22
|
+
readonly builderBefore: Map<string, HookHandler[]>;
|
|
23
|
+
readonly builderAfter: Map<string, HookHandler[]>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
interface RawHooks {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
readonly pipeline?: RawPipelineHooks;
|
|
28
|
+
readonly builders?: Record<string, RawBuilderHooks | HookHandler | HookHandler[]>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
interface RawPipelineHooks {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
readonly beforeAll?: HookHandler | HookHandler[];
|
|
33
|
+
readonly afterAll?: HookHandler | HookHandler[];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface RawBuilderHooks {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
readonly before?: HookHandler | HookHandler[];
|
|
38
|
+
readonly after?: HookHandler | HookHandler[];
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const EMPTY_HOOKS: ResolvedHooks = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
pipelineBefore: [],
|
|
43
|
+
pipelineAfter: [],
|
|
44
|
+
builderBefore: new Map(),
|
|
45
|
+
builderAfter: new Map(),
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export async function loadHooks(workspaceRoot: string, cacheBust: boolean): Promise<ResolvedHooks> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
49
|
+
const configPath = findConfigPath(workspaceRoot);
|
|
50
|
+
if (!configPath) {
|
|
51
|
+
return EMPTY_HOOKS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let moduleConfig: unknown;
|
|
55
|
+
try {
|
|
56
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
57
|
+
const url = cacheBust ? `${fileUrl}?update=${Date.now()}` : fileUrl;
|
|
58
|
+
moduleConfig = await import(url);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
throw new Error(`Failed to load hooks from ${configPath}: ${message}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const exported = (moduleConfig as Record<string, unknown>)?.default ?? moduleConfig;
|
|
65
|
+
if (!exported || typeof exported !== 'object') {
|
|
66
|
+
return EMPTY_HOOKS;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rawHooks = (exported as Record<string, unknown>).hooks ?? exported;
|
|
70
|
+
if (!rawHooks || typeof rawHooks !== 'object') {
|
|
71
|
+
return EMPTY_HOOKS;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return normalizeHooks(rawHooks as RawHooks, configPath);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export function createHookContext(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
config: FrontendConfig,
|
|
79
|
+
mode: PipelineMode,
|
|
80
|
+
changedFile: string | undefined,
|
|
81
|
+
builderName?: string,
|
|
82
82
|
): HookContext {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
return {
|
|
84
|
+
config,
|
|
85
|
+
mode,
|
|
86
|
+
workspaceRoot: config.paths.workspace,
|
|
87
|
+
builderName,
|
|
88
|
+
changedFile,
|
|
89
|
+
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export async function executeHooks(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
export async function executeHooks(
|
|
93
|
+
label: string,
|
|
94
|
+
handlers: HookHandler[],
|
|
95
|
+
context: HookContext,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
for (const handler of handlers) {
|
|
98
|
+
try {
|
|
99
|
+
await handler(context);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw wrapHookError(label, error);
|
|
99
102
|
}
|
|
103
|
+
}
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
function wrapHookError(label: string, error: unknown): Error {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
error.message = `[hook:${label}] ${error.message}`;
|
|
109
|
+
return error;
|
|
110
|
+
}
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
return new Error(`[hook:${label}] ${String(error)}`);
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
function normalizeHooks(raw: RawHooks, configPath: string): ResolvedHooks {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const before = normalizeHandlerSet(value.before, `${configPath} builders.${name}.before`);
|
|
131
|
-
const after = normalizeHandlerSet(value.after, `${configPath} builders.${name}.after`);
|
|
132
|
-
if (before.length > 0) {
|
|
133
|
-
builderBefore.set(name, before);
|
|
134
|
-
}
|
|
135
|
-
if (after.length > 0) {
|
|
136
|
-
builderAfter.set(name, after);
|
|
137
|
-
}
|
|
116
|
+
const pipeline = raw.pipeline ?? {};
|
|
117
|
+
const pipelineBefore = normalizeHandlerSet(
|
|
118
|
+
pipeline.beforeAll,
|
|
119
|
+
`${configPath} pipeline.beforeAll`,
|
|
120
|
+
);
|
|
121
|
+
const pipelineAfter = normalizeHandlerSet(pipeline.afterAll, `${configPath} pipeline.afterAll`);
|
|
122
|
+
|
|
123
|
+
const builderBefore = new Map<string, HookHandler[]>();
|
|
124
|
+
const builderAfter = new Map<string, HookHandler[]>();
|
|
125
|
+
|
|
126
|
+
const builders = raw.builders ?? {};
|
|
127
|
+
for (const [name, value] of Object.entries(builders)) {
|
|
128
|
+
if (typeof value === 'function' || Array.isArray(value)) {
|
|
129
|
+
builderBefore.set(name, normalizeHandlerSet(value, `${configPath} builders.${name}`));
|
|
130
|
+
continue;
|
|
138
131
|
}
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
builderBefore,
|
|
144
|
-
builderAfter
|
|
145
|
-
};
|
|
146
|
-
}
|
|
133
|
+
if (!value || typeof value !== 'object') {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
147
136
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
const before = normalizeHandlerSet(value.before, `${configPath} builders.${name}.before`);
|
|
138
|
+
const after = normalizeHandlerSet(value.after, `${configPath} builders.${name}.after`);
|
|
139
|
+
if (before.length > 0) {
|
|
140
|
+
builderBefore.set(name, before);
|
|
141
|
+
}
|
|
142
|
+
if (after.length > 0) {
|
|
143
|
+
builderAfter.set(name, after);
|
|
151
144
|
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
pipelineBefore,
|
|
149
|
+
pipelineAfter,
|
|
150
|
+
builderBefore,
|
|
151
|
+
builderAfter,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function normalizeHandlerSet(
|
|
156
|
+
value: HookHandler | HookHandler[] | undefined,
|
|
157
|
+
label: string,
|
|
158
|
+
): HookHandler[] {
|
|
159
|
+
if (value === undefined) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
const handlers = Array.isArray(value) ? value : [value];
|
|
164
|
+
const normalized: HookHandler[] = [];
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
normalized.push(handler);
|
|
166
|
+
for (const handler of handlers) {
|
|
167
|
+
if (typeof handler !== 'function') {
|
|
168
|
+
throw new Error(`Invalid hook handler in ${label}; expected function`);
|
|
161
169
|
}
|
|
170
|
+
normalized.push(handler);
|
|
171
|
+
}
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
return normalized;
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
function findConfigPath(workspaceRoot: string): string | undefined {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
177
|
+
for (const candidate of CONFIG_CANDIDATES) {
|
|
178
|
+
const fullPath = path.join(workspaceRoot, candidate);
|
|
179
|
+
if (fs.existsSync(fullPath)) {
|
|
180
|
+
return fullPath;
|
|
172
181
|
}
|
|
182
|
+
}
|
|
173
183
|
|
|
174
|
-
|
|
184
|
+
return undefined;
|
|
175
185
|
}
|
package/src/html/criticalCss.ts
CHANGED
|
@@ -6,13 +6,15 @@ import { pathExists, readFile, stat } from '../utils/fs.js';
|
|
|
6
6
|
import { resolvePageAssetUrl } from '../utils/pagePaths.js';
|
|
7
7
|
|
|
8
8
|
const INLINE_THRESHOLD_BYTES = 6 * 1024;
|
|
9
|
-
const csso = ((cssoModule as unknown as { default?: typeof cssoModule }).default ??
|
|
9
|
+
const csso = ((cssoModule as unknown as { default?: typeof cssoModule }).default ??
|
|
10
|
+
cssoModule) as typeof cssoModule;
|
|
10
11
|
|
|
11
12
|
function minifyCriticalCss(css: string): string {
|
|
12
|
-
|
|
13
|
+
return csso.minify(css).css;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const APP_SHELL_CRITICAL_CSS = minifyCriticalCss(
|
|
16
|
+
const APP_SHELL_CRITICAL_CSS = minifyCriticalCss(
|
|
17
|
+
`
|
|
16
18
|
@layer tokens {
|
|
17
19
|
:root {
|
|
18
20
|
--ws-header-control-size: 2.6rem;
|
|
@@ -83,8 +85,10 @@ const APP_SHELL_CRITICAL_CSS = minifyCriticalCss(`
|
|
|
83
85
|
height: calc(var(--ws-header-sticky-offset) - 1px);
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
|
-
`.trim()
|
|
87
|
-
|
|
88
|
+
`.trim(),
|
|
89
|
+
);
|
|
90
|
+
const DOCS_SHELL_CRITICAL_CSS = minifyCriticalCss(
|
|
91
|
+
`
|
|
88
92
|
@layer overrides {
|
|
89
93
|
.docs-layout {
|
|
90
94
|
--ws-docs-sidebar-width: clamp(14rem, 20vw, 18rem);
|
|
@@ -130,85 +134,88 @@ const DOCS_SHELL_CRITICAL_CSS = minifyCriticalCss(`
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
|
-
`.trim()
|
|
137
|
+
`.trim(),
|
|
138
|
+
);
|
|
134
139
|
|
|
135
140
|
export async function inlineCriticalCss(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
document: CheerioAPI,
|
|
142
|
+
pageName: string,
|
|
143
|
+
pagesRoot: string,
|
|
144
|
+
pagesUrlPrefix: string,
|
|
145
|
+
cssFile?: string,
|
|
141
146
|
): Promise<void> {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
147
|
+
if (!cssFile) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cssPath = path.join(pagesRoot, pageName, cssFile);
|
|
152
|
+
if (!(await pathExists(cssPath))) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const info = await stat(cssPath).catch(() => null);
|
|
157
|
+
if (!info || !info.isFile() || info.size > INLINE_THRESHOLD_BYTES) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const cssContent = minifyCriticalCss(await readFile(cssPath));
|
|
162
|
+
const head = document('head').first();
|
|
163
|
+
if (head.length === 0) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const href = resolvePageAssetUrl(pagesUrlPrefix, pageName, cssFile);
|
|
168
|
+
document(`link[href="${href}"]`).remove();
|
|
169
|
+
|
|
170
|
+
if (cssFile.endsWith(EXTENSIONS.css)) {
|
|
171
|
+
document(`link[rel="preload"][href="${href}"]`).remove();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
head.append(`\n<style data-critical>\n${cssContent}\n</style>\n`);
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
export function ensureAppShellCriticalCss(document: CheerioAPI, appCssHref: string): void {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
const head = document('head').first();
|
|
179
|
+
if (head.length === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const existing = head.find('style[data-critical="app"]').first();
|
|
184
|
+
if (existing.length > 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const stylesheet = document(`link[rel="stylesheet"][href="${appCssHref}"]`).first();
|
|
189
|
+
const styleTag = `<style data-critical="app">\n${APP_SHELL_CRITICAL_CSS}\n</style>`;
|
|
190
|
+
if (stylesheet.length > 0) {
|
|
191
|
+
stylesheet.before(styleTag);
|
|
192
|
+
} else {
|
|
193
|
+
head.append(styleTag);
|
|
194
|
+
}
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
export function ensureDocsShellCriticalCss(document: CheerioAPI): void {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
const head = document('head').first();
|
|
199
|
+
if (head.length === 0) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const existing = head.find('style[data-critical="docs"]').first();
|
|
204
|
+
if (existing.length > 0) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const docsStylesheet = document('link[rel="stylesheet"]')
|
|
209
|
+
.filter((_, element) => {
|
|
210
|
+
const href = document(element).attr('href');
|
|
211
|
+
return typeof href === 'string' && href.includes('/docs/');
|
|
212
|
+
})
|
|
213
|
+
.first();
|
|
214
|
+
|
|
215
|
+
const styleTag = `<style data-critical="docs">\n${DOCS_SHELL_CRITICAL_CSS}\n</style>`;
|
|
216
|
+
if (docsStylesheet.length > 0) {
|
|
217
|
+
docsStylesheet.before(styleTag);
|
|
218
|
+
} else {
|
|
219
|
+
head.append(styleTag);
|
|
220
|
+
}
|
|
214
221
|
}
|