@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
|
@@ -3,550 +3,599 @@ import postcss from 'postcss';
|
|
|
3
3
|
import autoprefixer from 'autoprefixer';
|
|
4
4
|
import customMedia from 'postcss-custom-media';
|
|
5
5
|
import * as cssoModule from 'csso';
|
|
6
|
-
import { glob } from 'glob';
|
|
7
6
|
import { FOLDERS, FILES, EXTENSIONS } from '../core/constants.js';
|
|
8
7
|
import { ensureDir, pathExists, readFile, writeFile, remove, copy } from '../utils/fs.js';
|
|
8
|
+
import { scanGlob } from '../utils/glob.js';
|
|
9
9
|
import type { Builder, BuilderContext } from './types.js';
|
|
10
10
|
import { getPages } from '../core/pages.js';
|
|
11
11
|
import { hashContent } from '../utils/hash.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
updatePageManifest,
|
|
14
|
+
updateSharedAssets,
|
|
15
|
+
readSharedAssets,
|
|
16
|
+
} from '../assets/assetManifest.js';
|
|
13
17
|
import { createCompressedVariants } from '../assets/precompression.js';
|
|
14
18
|
import { shouldProcess } from '../utils/changedFile.js';
|
|
15
19
|
import { findPageFromChangedFile } from '../utils/pathMatch.js';
|
|
16
20
|
|
|
17
21
|
const MODULE_SUFFIX = '.module';
|
|
18
22
|
const APP_CSS_BASENAME = 'app';
|
|
19
|
-
const csso = ((cssoModule as unknown as { default?: typeof cssoModule }).default ??
|
|
20
|
-
|
|
23
|
+
const csso = ((cssoModule as unknown as { default?: typeof cssoModule }).default ??
|
|
24
|
+
cssoModule) as typeof cssoModule;
|
|
25
|
+
const PAGE_IMPORT_PATTERN = /@import\s+(?:url\()?[\s]*['"]([^'"]+)['"][\s]*\)?\s*;?/g;
|
|
21
26
|
|
|
22
27
|
interface SharedCssArtifacts {
|
|
23
|
-
|
|
28
|
+
appCss?: string;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export function createCssBuilder(context: BuilderContext): Builder {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
return {
|
|
33
|
+
name: 'css',
|
|
34
|
+
async build(): Promise<void> {
|
|
35
|
+
await processCss(context, false);
|
|
36
|
+
},
|
|
37
|
+
async publish(): Promise<void> {
|
|
38
|
+
await processCss(context, true);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
async function processCss(context: BuilderContext, isProduction: boolean): Promise<void> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const { config } = context;
|
|
45
|
+
if (
|
|
46
|
+
!shouldProcess(context, [
|
|
47
|
+
{ directory: config.paths.src.pages, extensions: [EXTENSIONS.css] },
|
|
48
|
+
{ directory: config.paths.src.frontend, extensions: [EXTENSIONS.css] },
|
|
49
|
+
])
|
|
50
|
+
) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const processor = createPostcssProcessor();
|
|
55
|
+
const customMediaPrelude = await loadCustomMediaPrelude(config);
|
|
56
|
+
const sharedArtifacts = await processAppCss(config, isProduction, processor, customMediaPrelude);
|
|
57
|
+
const targetPage = findPageFromChangedFile(context.changedFile, config.paths.src.pages);
|
|
58
|
+
const pages = await getPages(config.paths.src.pages);
|
|
59
|
+
|
|
60
|
+
for (const page of pages) {
|
|
61
|
+
if (targetPage && page.name !== targetPage) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const entryPath = await resolveCssEntry(page.directory);
|
|
65
|
+
if (!entryPath) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const css = await readFile(entryPath);
|
|
70
|
+
const inlinedCss = await inlinePageImports(css, page.directory);
|
|
71
|
+
const prepared = applyCustomMediaPrelude(inlinedCss, customMediaPrelude);
|
|
72
|
+
const processed = await processor.process(prepared, {
|
|
73
|
+
from: entryPath,
|
|
74
|
+
map: !isProduction ? { inline: true } : false,
|
|
75
|
+
});
|
|
76
|
+
const normalized = resolveAppImports(
|
|
77
|
+
processed.css,
|
|
78
|
+
isProduction ? sharedArtifacts.appCss : undefined,
|
|
79
|
+
);
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const css = await readFile(entryPath);
|
|
63
|
-
const inlinedCss = await inlinePageImports(css, page.directory);
|
|
64
|
-
const prepared = applyCustomMediaPrelude(inlinedCss, customMediaPrelude);
|
|
65
|
-
const processed = await processor.process(prepared, { from: entryPath, map: !isProduction ? { inline: true } : false });
|
|
66
|
-
const normalized = resolveAppImports(processed.css, isProduction ? sharedArtifacts.appCss : undefined);
|
|
67
|
-
|
|
68
|
-
if (isProduction) {
|
|
69
|
-
const inlined = await inlineAppImports(normalized, config.paths.dist.frontend);
|
|
70
|
-
await emitProductionCss(config, page.name, inlined);
|
|
71
|
-
} else {
|
|
72
|
-
await emitDevelopmentCss(config, page.name, normalized);
|
|
73
|
-
await syncPageCssAssetsForDevelopment(
|
|
74
|
-
page.directory,
|
|
75
|
-
path.join(config.paths.build.pages, page.name),
|
|
76
|
-
entryPath
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
81
|
+
if (isProduction) {
|
|
82
|
+
const inlined = await inlineAppImports(normalized, config.paths.dist.frontend);
|
|
83
|
+
await emitProductionCss(config, page.name, inlined);
|
|
84
|
+
} else {
|
|
85
|
+
await emitDevelopmentCss(config, page.name, normalized);
|
|
86
|
+
await syncPageCssAssetsForDevelopment(
|
|
87
|
+
page.directory,
|
|
88
|
+
path.join(config.paths.build.pages, page.name),
|
|
89
|
+
entryPath,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
async function emitDevelopmentCss(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
async function emitDevelopmentCss(
|
|
96
|
+
config: BuilderContext['config'],
|
|
97
|
+
pageName: string,
|
|
98
|
+
css: string,
|
|
99
|
+
): Promise<void> {
|
|
100
|
+
const outputDir = path.join(config.paths.build.pages, pageName);
|
|
101
|
+
await ensureDir(outputDir);
|
|
102
|
+
const outputPath = path.join(outputDir, `${FILES.index}${EXTENSIONS.css}`);
|
|
103
|
+
await writeFile(outputPath, css);
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
async function emitProductionCss(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
async function emitProductionCss(
|
|
107
|
+
config: BuilderContext['config'],
|
|
108
|
+
pageName: string,
|
|
109
|
+
css: string,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const minified = csso.minify(css).css;
|
|
112
|
+
const hash = hashContent(minified);
|
|
113
|
+
const fileName = `${FILES.index}-${hash}${EXTENSIONS.css}`;
|
|
114
|
+
const outputDir = path.join(config.paths.dist.pages, pageName);
|
|
115
|
+
await ensureDir(outputDir);
|
|
116
|
+
const outputPath = path.join(outputDir, fileName);
|
|
117
|
+
await writeFile(outputPath, minified);
|
|
118
|
+
if (config.features.precompression) {
|
|
119
|
+
await createCompressedVariants(outputPath);
|
|
120
|
+
} else {
|
|
121
|
+
await Promise.all([
|
|
122
|
+
remove(`${outputPath}${EXTENSIONS.br}`).catch(() => undefined),
|
|
123
|
+
remove(`${outputPath}${EXTENSIONS.gz}`).catch(() => undefined),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
await updatePageManifest(outputDir, pageName, (manifest) => {
|
|
127
|
+
manifest.css = fileName;
|
|
128
|
+
});
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
async function syncPageCssAssetsForDevelopment(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
pageDirectory: string,
|
|
133
|
+
outputDir: string,
|
|
134
|
+
entryPath: string,
|
|
114
135
|
): Promise<void> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
copySet.add(normalized);
|
|
126
|
-
const sourcePath = path.join(pageDirectory, relative);
|
|
127
|
-
const destinationPath = path.join(outputDir, relative);
|
|
128
|
-
await ensureDir(path.dirname(destinationPath));
|
|
129
|
-
await copy(sourcePath, destinationPath);
|
|
136
|
+
const sourceFiles = await scanGlob('**/*.css', { cwd: pageDirectory });
|
|
137
|
+
const entryRelative = normalizeForwardSlashes(path.relative(pageDirectory, entryPath));
|
|
138
|
+
|
|
139
|
+
const copySet = new Set<string>();
|
|
140
|
+
for (const relative of sourceFiles) {
|
|
141
|
+
const normalized = normalizeForwardSlashes(relative);
|
|
142
|
+
if (normalized === entryRelative) {
|
|
143
|
+
continue;
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
copySet.add(normalized);
|
|
147
|
+
const sourcePath = path.join(pageDirectory, relative);
|
|
148
|
+
const destinationPath = path.join(outputDir, relative);
|
|
149
|
+
await ensureDir(path.dirname(destinationPath));
|
|
150
|
+
await copy(sourcePath, destinationPath);
|
|
151
|
+
}
|
|
138
152
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
const existingFiles = await scanGlob('**/*.css', { cwd: outputDir });
|
|
154
|
+
for (const relative of existingFiles) {
|
|
155
|
+
const normalized = normalizeForwardSlashes(relative);
|
|
156
|
+
if (normalized === `${FILES.index}${EXTENSIONS.css}`) {
|
|
157
|
+
continue;
|
|
142
158
|
}
|
|
159
|
+
|
|
160
|
+
if (!copySet.has(normalized)) {
|
|
161
|
+
await remove(path.join(outputDir, relative)).catch(() => undefined);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
async function processAppCss(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
config: BuilderContext['config'],
|
|
168
|
+
isProduction: boolean,
|
|
169
|
+
processor: postcss.Processor,
|
|
170
|
+
customMediaPrelude: string,
|
|
150
171
|
): Promise<SharedCssArtifacts> {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return {};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const source = applyCustomMediaPrelude(await readFile(appCssPath), customMediaPrelude);
|
|
157
|
-
|
|
158
|
-
if (isProduction) {
|
|
159
|
-
const stylesMap = await emitAppStylesProduction(config, processor, customMediaPrelude);
|
|
160
|
-
const processed = await processor.process(source, { from: appCssPath, map: false });
|
|
161
|
-
const rewritten = rewriteAppStyleImports(processed.css, stylesMap);
|
|
162
|
-
const inlined = await inlineAppImports(rewritten, config.paths.dist.frontend);
|
|
163
|
-
const fileName = await emitAppProductionCss(config, inlined);
|
|
164
|
-
await updateSharedAssets(config.paths.dist.frontend, shared => {
|
|
165
|
-
shared.css = fileName;
|
|
166
|
-
});
|
|
167
|
-
return { appCss: fileName };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const processed = await processor.process(source, { from: appCssPath, map: { inline: true } });
|
|
171
|
-
const stylesVersion = await computeAppStylesVersion(config.paths.src.app);
|
|
172
|
-
const rewritten = rewriteAppStyleImportsForDevelopment(processed.css, stylesVersion);
|
|
173
|
-
await emitAppDevelopmentCss(config, rewritten);
|
|
174
|
-
await syncAppStyles(config.paths.src.app, path.join(config.paths.build.frontend, FOLDERS.app), processor, customMediaPrelude);
|
|
172
|
+
const appCssPath = path.join(config.paths.src.app, 'app.css');
|
|
173
|
+
if (!(await pathExists(appCssPath))) {
|
|
175
174
|
return {};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const source = applyCustomMediaPrelude(await readFile(appCssPath), customMediaPrelude);
|
|
178
|
+
|
|
179
|
+
if (isProduction) {
|
|
180
|
+
const stylesMap = await emitAppStylesProduction(config, processor, customMediaPrelude);
|
|
181
|
+
const processed = await processor.process(source, { from: appCssPath, map: false });
|
|
182
|
+
const rewritten = rewriteAppStyleImports(processed.css, stylesMap);
|
|
183
|
+
const inlined = await inlineAppImports(rewritten, config.paths.dist.frontend);
|
|
184
|
+
const fileName = await emitAppProductionCss(config, inlined);
|
|
185
|
+
await updateSharedAssets(config.paths.dist.frontend, (shared) => {
|
|
186
|
+
shared.css = fileName;
|
|
187
|
+
});
|
|
188
|
+
return { appCss: fileName };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const processed = await processor.process(source, { from: appCssPath, map: { inline: true } });
|
|
192
|
+
const stylesVersion = await computeAppStylesVersion(config.paths.src.app);
|
|
193
|
+
const rewritten = rewriteAppStyleImportsForDevelopment(processed.css, stylesVersion);
|
|
194
|
+
await emitAppDevelopmentCss(config, rewritten);
|
|
195
|
+
await syncAppStyles(
|
|
196
|
+
config.paths.src.app,
|
|
197
|
+
path.join(config.paths.build.frontend, FOLDERS.app),
|
|
198
|
+
processor,
|
|
199
|
+
customMediaPrelude,
|
|
200
|
+
);
|
|
201
|
+
return {};
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
function createPostcssProcessor(): postcss.Processor {
|
|
179
|
-
|
|
205
|
+
return postcss([customMedia(), autoprefixer]);
|
|
180
206
|
}
|
|
181
207
|
|
|
182
208
|
async function loadCustomMediaPrelude(config: BuilderContext['config']): Promise<string> {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
const tokensPath = path.join(config.paths.src.app, 'styles', 'tokens.css');
|
|
210
|
+
if (!(await pathExists(tokensPath))) {
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const contents = await readFile(tokensPath);
|
|
215
|
+
const matches = contents.match(/^[\t ]*@custom-media[^\n]*;[\t ]*$/gm) ?? [];
|
|
216
|
+
if (matches.length === 0) {
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${matches.join('\n')}\n`;
|
|
195
221
|
}
|
|
196
222
|
|
|
197
223
|
function applyCustomMediaPrelude(css: string, prelude: string): string {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
224
|
+
if (!prelude) {
|
|
225
|
+
return css;
|
|
226
|
+
}
|
|
201
227
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
228
|
+
if (!css.includes('@media (--')) {
|
|
229
|
+
return css;
|
|
230
|
+
}
|
|
205
231
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
232
|
+
if (css.includes('@custom-media')) {
|
|
233
|
+
return css;
|
|
234
|
+
}
|
|
209
235
|
|
|
210
|
-
|
|
236
|
+
return `${prelude}${css}`;
|
|
211
237
|
}
|
|
212
238
|
|
|
213
239
|
async function emitAppDevelopmentCss(config: BuilderContext['config'], css: string): Promise<void> {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
240
|
+
const outputDir = path.join(config.paths.build.frontend, FOLDERS.app);
|
|
241
|
+
await ensureDir(outputDir);
|
|
242
|
+
await writeFile(path.join(outputDir, 'app.css'), css);
|
|
217
243
|
}
|
|
218
244
|
|
|
219
|
-
async function emitAppProductionCss(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
async function emitAppProductionCss(
|
|
246
|
+
config: BuilderContext['config'],
|
|
247
|
+
css: string,
|
|
248
|
+
): Promise<string> {
|
|
249
|
+
const { css: stripped, layerOrder } = stripAppLayerOrderStatement(css);
|
|
250
|
+
const minified = restoreAppLayerOrderStatement(csso.minify(stripped).css, layerOrder);
|
|
251
|
+
const hash = hashContent(minified);
|
|
252
|
+
const fileName = `${APP_CSS_BASENAME}-${hash}${EXTENSIONS.css}`;
|
|
253
|
+
const outputDir = path.join(config.paths.dist.frontend, FOLDERS.app);
|
|
254
|
+
await ensureDir(outputDir);
|
|
255
|
+
const outputPath = path.join(outputDir, fileName);
|
|
256
|
+
await writeFile(outputPath, minified);
|
|
257
|
+
|
|
258
|
+
if (config.features.precompression) {
|
|
259
|
+
await createCompressedVariants(outputPath);
|
|
260
|
+
} else {
|
|
261
|
+
await Promise.all([
|
|
262
|
+
remove(`${outputPath}${EXTENSIONS.br}`).catch(() => undefined),
|
|
263
|
+
remove(`${outputPath}${EXTENSIONS.gz}`).catch(() => undefined),
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Remove previously hashed variants to avoid stale files.
|
|
268
|
+
const existing = await readSharedAssets(config.paths.dist.frontend);
|
|
269
|
+
const previousFile = existing?.css;
|
|
270
|
+
if (previousFile && previousFile !== fileName) {
|
|
271
|
+
const previousPath = path.join(outputDir, previousFile);
|
|
272
|
+
await remove(previousPath).catch(() => undefined);
|
|
273
|
+
await remove(`${previousPath}${EXTENSIONS.br}`).catch(() => undefined);
|
|
274
|
+
await remove(`${previousPath}${EXTENSIONS.gz}`).catch(() => undefined);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return fileName;
|
|
249
278
|
}
|
|
250
279
|
|
|
251
280
|
async function syncAppStyles(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
281
|
+
sourceAppDir: string,
|
|
282
|
+
destinationAppDir: string,
|
|
283
|
+
processor: postcss.Processor,
|
|
284
|
+
customMediaPrelude: string,
|
|
256
285
|
): Promise<void> {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
const stylesSource = path.join(sourceAppDir, 'styles');
|
|
287
|
+
if (!(await pathExists(stylesSource))) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
261
290
|
|
|
262
|
-
|
|
263
|
-
|
|
291
|
+
const stylesDestination = path.join(destinationAppDir, 'styles');
|
|
292
|
+
await ensureDir(stylesDestination);
|
|
264
293
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
294
|
+
const files = await scanGlob('**/*', { cwd: stylesSource });
|
|
295
|
+
for (const relative of files) {
|
|
296
|
+
const sourcePath = path.join(stylesSource, relative);
|
|
297
|
+
const destinationPath = path.join(stylesDestination, relative);
|
|
298
|
+
await ensureDir(path.dirname(destinationPath));
|
|
270
299
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const source = applyCustomMediaPrelude(await readFile(sourcePath), customMediaPrelude);
|
|
277
|
-
const processed = await processor.process(source, { from: sourcePath, map: { inline: true } });
|
|
278
|
-
await writeFile(destinationPath, processed.css);
|
|
300
|
+
if (!relative.endsWith(EXTENSIONS.css)) {
|
|
301
|
+
await copy(sourcePath, destinationPath);
|
|
302
|
+
continue;
|
|
279
303
|
}
|
|
304
|
+
|
|
305
|
+
const source = applyCustomMediaPrelude(await readFile(sourcePath), customMediaPrelude);
|
|
306
|
+
const processed = await processor.process(source, { from: sourcePath, map: { inline: true } });
|
|
307
|
+
await writeFile(destinationPath, processed.css);
|
|
308
|
+
}
|
|
280
309
|
}
|
|
281
310
|
|
|
282
311
|
async function computeAppStylesVersion(sourceAppDir: string): Promise<string> {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
312
|
+
const stylesDir = path.join(sourceAppDir, 'styles');
|
|
313
|
+
if (!(await pathExists(stylesDir))) {
|
|
314
|
+
return 'no-styles';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const files = await scanGlob('**/*.css', { cwd: stylesDir });
|
|
318
|
+
if (files.length === 0) {
|
|
319
|
+
return 'no-styles';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let fingerprint = '';
|
|
323
|
+
for (const relative of files) {
|
|
324
|
+
const contents = await readFile(path.join(stylesDir, relative));
|
|
325
|
+
fingerprint += `${normalizeForwardSlashes(relative)}\0${contents}\0`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return hashContent(fingerprint, 10);
|
|
300
329
|
}
|
|
301
330
|
|
|
302
331
|
function rewriteAppStyleImportsForDevelopment(css: string, stylesVersion: string): string {
|
|
303
|
-
|
|
304
|
-
|
|
332
|
+
const importPattern = /(@import\s+['"])(?:\.\/)?(styles\/[^'"]+?\.css)(\?v=[^'"]+)?(['"];?)/g;
|
|
333
|
+
return css.replace(importPattern, `$1./$2?v=${stylesVersion}$4`);
|
|
305
334
|
}
|
|
306
335
|
|
|
307
336
|
function resolveAppImports(css: string, appCssFile?: string): string {
|
|
308
|
-
|
|
337
|
+
let result = css;
|
|
309
338
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
339
|
+
if (appCssFile) {
|
|
340
|
+
result = result.replace(/@import\s+['"]@app\/app\.css['"];?/g, `@import "/app/${appCssFile}";`);
|
|
341
|
+
}
|
|
313
342
|
|
|
314
|
-
|
|
343
|
+
return result.replace(/@app\//g, '/app/');
|
|
315
344
|
}
|
|
316
345
|
|
|
317
|
-
async function inlinePageImports(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const importPath = String(match[1] ?? '').trim();
|
|
326
|
-
if (!shouldInlinePageImport(importPath)) {
|
|
327
|
-
segments.push(match[0]);
|
|
328
|
-
lastIndex = index + match[0].length;
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const resolved = path.resolve(pageDirectory, importPath);
|
|
333
|
-
if (!isWithin(resolved, pageDirectory)) {
|
|
334
|
-
segments.push(match[0]);
|
|
335
|
-
lastIndex = index + match[0].length;
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const key = resolved;
|
|
340
|
-
if (seen.has(key)) {
|
|
341
|
-
lastIndex = index + match[0].length;
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (!(await pathExists(resolved))) {
|
|
346
|
-
segments.push(match[0]);
|
|
347
|
-
lastIndex = index + match[0].length;
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
seen.add(key);
|
|
352
|
-
const imported = await readFile(resolved);
|
|
353
|
-
const inlined = await inlinePageImports(imported, pageDirectory, seen);
|
|
354
|
-
seen.delete(key);
|
|
355
|
-
segments.push(inlined);
|
|
356
|
-
|
|
357
|
-
lastIndex = index + match[0].length;
|
|
358
|
-
}
|
|
346
|
+
async function inlinePageImports(
|
|
347
|
+
css: string,
|
|
348
|
+
pageDirectory: string,
|
|
349
|
+
seen: Set<string> = new Set(),
|
|
350
|
+
): Promise<string> {
|
|
351
|
+
const segments: string[] = [];
|
|
352
|
+
let lastIndex = 0;
|
|
359
353
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
354
|
+
for (const match of css.matchAll(PAGE_IMPORT_PATTERN)) {
|
|
355
|
+
const index = match.index ?? 0;
|
|
356
|
+
segments.push(css.slice(lastIndex, index));
|
|
363
357
|
|
|
364
|
-
|
|
365
|
-
if (importPath
|
|
366
|
-
|
|
358
|
+
const importPath = String(match[1] ?? '').trim();
|
|
359
|
+
if (!shouldInlinePageImport(importPath)) {
|
|
360
|
+
segments.push(match[0]);
|
|
361
|
+
lastIndex = index + match[0].length;
|
|
362
|
+
continue;
|
|
367
363
|
}
|
|
368
364
|
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
const resolved = path.resolve(pageDirectory, importPath);
|
|
366
|
+
if (!isWithin(resolved, pageDirectory)) {
|
|
367
|
+
segments.push(match[0]);
|
|
368
|
+
lastIndex = index + match[0].length;
|
|
369
|
+
continue;
|
|
371
370
|
}
|
|
372
371
|
|
|
373
|
-
|
|
374
|
-
|
|
372
|
+
const key = resolved;
|
|
373
|
+
if (seen.has(key)) {
|
|
374
|
+
lastIndex = index + match[0].length;
|
|
375
|
+
continue;
|
|
375
376
|
}
|
|
376
377
|
|
|
377
|
-
if (
|
|
378
|
-
|
|
378
|
+
if (!(await pathExists(resolved))) {
|
|
379
|
+
segments.push(match[0]);
|
|
380
|
+
lastIndex = index + match[0].length;
|
|
381
|
+
continue;
|
|
379
382
|
}
|
|
380
383
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
seen.add(key);
|
|
385
|
+
const imported = await readFile(resolved);
|
|
386
|
+
const inlined = await inlinePageImports(imported, pageDirectory, seen);
|
|
387
|
+
seen.delete(key);
|
|
388
|
+
segments.push(inlined);
|
|
384
389
|
|
|
385
|
-
|
|
386
|
-
}
|
|
390
|
+
lastIndex = index + match[0].length;
|
|
391
|
+
}
|
|
387
392
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
393
|
+
segments.push(css.slice(lastIndex));
|
|
394
|
+
return segments.join('');
|
|
391
395
|
}
|
|
392
396
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
397
|
+
function shouldInlinePageImport(importPath: string): boolean {
|
|
398
|
+
if (importPath.length === 0) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!importPath.endsWith(EXTENSIONS.css)) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (
|
|
407
|
+
importPath.startsWith('/') ||
|
|
408
|
+
importPath.startsWith('http:') ||
|
|
409
|
+
importPath.startsWith('https:')
|
|
410
|
+
) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (importPath.startsWith('@') || importPath.includes('?') || importPath.includes('#')) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (importPath.includes('..')) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
401
424
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
} else {
|
|
407
|
-
segments.push(match[0]);
|
|
408
|
-
}
|
|
425
|
+
function isWithin(candidate: string, root: string): boolean {
|
|
426
|
+
const relative = path.relative(root, candidate);
|
|
427
|
+
return relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
428
|
+
}
|
|
409
429
|
|
|
410
|
-
|
|
430
|
+
async function inlineAppImports(
|
|
431
|
+
css: string,
|
|
432
|
+
distRoot: string,
|
|
433
|
+
seen: Set<string> = new Set(),
|
|
434
|
+
): Promise<string> {
|
|
435
|
+
const importPattern = /@import\s+(?:url\()?[\s]*['"]\/app\/([^'"]+)['"][\s]*\)?;?/g;
|
|
436
|
+
const segments: string[] = [];
|
|
437
|
+
let lastIndex = 0;
|
|
438
|
+
|
|
439
|
+
for (const match of css.matchAll(importPattern)) {
|
|
440
|
+
const index = match.index ?? 0;
|
|
441
|
+
segments.push(css.slice(lastIndex, index));
|
|
442
|
+
|
|
443
|
+
const relative = normalizeForwardSlashes(match[1] ?? '');
|
|
444
|
+
const inlined = await inlineAppImport(relative, distRoot, seen);
|
|
445
|
+
if (inlined !== null) {
|
|
446
|
+
segments.push(inlined);
|
|
447
|
+
} else {
|
|
448
|
+
segments.push(match[0]);
|
|
411
449
|
}
|
|
412
450
|
|
|
413
|
-
|
|
414
|
-
|
|
451
|
+
lastIndex = index + match[0].length;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
segments.push(css.slice(lastIndex));
|
|
455
|
+
return segments.join('');
|
|
415
456
|
}
|
|
416
457
|
|
|
417
|
-
async function inlineAppImport(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
458
|
+
async function inlineAppImport(
|
|
459
|
+
relativePath: string,
|
|
460
|
+
distRoot: string,
|
|
461
|
+
seen: Set<string>,
|
|
462
|
+
): Promise<string | null> {
|
|
463
|
+
if (relativePath.length === 0 || relativePath.includes('..')) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
421
466
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
467
|
+
const resolved = path.join(distRoot, FOLDERS.app, relativePath);
|
|
468
|
+
if (!(await pathExists(resolved))) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
426
471
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
472
|
+
const key = resolved;
|
|
473
|
+
if (seen.has(key)) {
|
|
474
|
+
return '';
|
|
475
|
+
}
|
|
431
476
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
477
|
+
seen.add(key);
|
|
478
|
+
const content = await readFile(resolved);
|
|
479
|
+
const inlined = await inlineAppImports(content, distRoot, seen);
|
|
480
|
+
seen.delete(key);
|
|
436
481
|
|
|
437
|
-
|
|
482
|
+
return inlined;
|
|
438
483
|
}
|
|
439
484
|
|
|
440
485
|
async function emitAppStylesProduction(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
486
|
+
config: BuilderContext['config'],
|
|
487
|
+
processor: postcss.Processor,
|
|
488
|
+
customMediaPrelude: string,
|
|
444
489
|
): Promise<Map<string, string>> {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (!(await pathExists(sourceDir))) {
|
|
449
|
-
const destinationDir = path.join(config.paths.dist.frontend, FOLDERS.app, 'styles');
|
|
450
|
-
await remove(destinationDir).catch(() => undefined);
|
|
451
|
-
return mapping;
|
|
452
|
-
}
|
|
490
|
+
const sourceDir = path.join(config.paths.src.app, 'styles');
|
|
491
|
+
const mapping = new Map<string, string>();
|
|
453
492
|
|
|
493
|
+
if (!(await pathExists(sourceDir))) {
|
|
454
494
|
const destinationDir = path.join(config.paths.dist.frontend, FOLDERS.app, 'styles');
|
|
455
495
|
await remove(destinationDir).catch(() => undefined);
|
|
496
|
+
return mapping;
|
|
497
|
+
}
|
|
456
498
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const sourcePath = path.join(sourceDir, relative);
|
|
460
|
-
const source = applyCustomMediaPrelude(await readFile(sourcePath), customMediaPrelude);
|
|
461
|
-
const processed = await processor.process(source, { from: sourcePath, map: false });
|
|
462
|
-
const minified = csso.minify(processed.css).css;
|
|
463
|
-
const hash = hashContent(minified);
|
|
464
|
-
const parsed = path.parse(relative);
|
|
465
|
-
const hashedName = `${parsed.name}-${hash}${EXTENSIONS.css}`;
|
|
466
|
-
const relativeHashedPath = parsed.dir ? path.join(parsed.dir, hashedName) : hashedName;
|
|
467
|
-
const destinationPath = path.join(destinationDir, relativeHashedPath);
|
|
468
|
-
await ensureDir(path.dirname(destinationPath));
|
|
469
|
-
await writeFile(destinationPath, minified);
|
|
470
|
-
|
|
471
|
-
if (config.features.precompression) {
|
|
472
|
-
await createCompressedVariants(destinationPath);
|
|
473
|
-
} else {
|
|
474
|
-
await Promise.all([
|
|
475
|
-
remove(`${destinationPath}${EXTENSIONS.br}`).catch(() => undefined),
|
|
476
|
-
remove(`${destinationPath}${EXTENSIONS.gz}`).catch(() => undefined)
|
|
477
|
-
]);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
mapping.set(normalizeForwardSlashes(relative), normalizeForwardSlashes(path.join('styles', relativeHashedPath)));
|
|
481
|
-
}
|
|
499
|
+
const destinationDir = path.join(config.paths.dist.frontend, FOLDERS.app, 'styles');
|
|
500
|
+
await remove(destinationDir).catch(() => undefined);
|
|
482
501
|
|
|
483
|
-
|
|
484
|
-
|
|
502
|
+
const files = await scanGlob('**/*.css', { cwd: sourceDir });
|
|
503
|
+
for (const relative of files) {
|
|
504
|
+
const sourcePath = path.join(sourceDir, relative);
|
|
505
|
+
const source = applyCustomMediaPrelude(await readFile(sourcePath), customMediaPrelude);
|
|
506
|
+
const processed = await processor.process(source, { from: sourcePath, map: false });
|
|
507
|
+
const minified = csso.minify(processed.css).css;
|
|
508
|
+
const hash = hashContent(minified);
|
|
509
|
+
const parsed = path.parse(relative);
|
|
510
|
+
const hashedName = `${parsed.name}-${hash}${EXTENSIONS.css}`;
|
|
511
|
+
const relativeHashedPath = parsed.dir ? path.join(parsed.dir, hashedName) : hashedName;
|
|
512
|
+
const destinationPath = path.join(destinationDir, relativeHashedPath);
|
|
513
|
+
await ensureDir(path.dirname(destinationPath));
|
|
514
|
+
await writeFile(destinationPath, minified);
|
|
485
515
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
516
|
+
if (config.features.precompression) {
|
|
517
|
+
await createCompressedVariants(destinationPath);
|
|
518
|
+
} else {
|
|
519
|
+
await Promise.all([
|
|
520
|
+
remove(`${destinationPath}${EXTENSIONS.br}`).catch(() => undefined),
|
|
521
|
+
remove(`${destinationPath}${EXTENSIONS.gz}`).catch(() => undefined),
|
|
522
|
+
]);
|
|
489
523
|
}
|
|
490
524
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
result = result.replace(pattern, `$1/app/${hashed}$2`);
|
|
497
|
-
}
|
|
525
|
+
mapping.set(
|
|
526
|
+
normalizeForwardSlashes(relative),
|
|
527
|
+
normalizeForwardSlashes(path.join('styles', relativeHashedPath)),
|
|
528
|
+
);
|
|
529
|
+
}
|
|
498
530
|
|
|
499
|
-
|
|
531
|
+
return mapping;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function rewriteAppStyleImports(css: string, stylesMap: Map<string, string>): string {
|
|
535
|
+
if (stylesMap.size === 0) {
|
|
536
|
+
return css;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
let result = css;
|
|
540
|
+
for (const [original, hashed] of stylesMap.entries()) {
|
|
541
|
+
const normalizedOriginal = original.startsWith('styles/') ? original : `styles/${original}`;
|
|
542
|
+
const escaped = escapeRegExp(normalizedOriginal);
|
|
543
|
+
const pattern = new RegExp(`(@import\\s+['"])(?:\\./)?${escaped}(['"];?)`, 'g');
|
|
544
|
+
result = result.replace(pattern, `$1/app/${hashed}$2`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return result;
|
|
500
548
|
}
|
|
501
549
|
|
|
502
550
|
function normalizeForwardSlashes(value: string): string {
|
|
503
|
-
|
|
551
|
+
return value.replace(/\\/g, '/');
|
|
504
552
|
}
|
|
505
553
|
|
|
506
554
|
function escapeRegExp(value: string): string {
|
|
507
|
-
|
|
555
|
+
return value.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
|
|
508
556
|
}
|
|
509
557
|
|
|
510
558
|
function stripAppLayerOrderStatement(css: string): { css: string; layerOrder?: string } {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
559
|
+
const layerMatch = css.match(/@layer[^;]*;/);
|
|
560
|
+
if (!layerMatch || layerMatch.index === undefined) {
|
|
561
|
+
return { css };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const layerText = layerMatch[0];
|
|
565
|
+
if (layerText.includes('{')) {
|
|
566
|
+
return { css };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const withoutLayer =
|
|
570
|
+
css.slice(0, layerMatch.index) + css.slice(layerMatch.index + layerText.length);
|
|
571
|
+
return { css: withoutLayer, layerOrder: layerText.trim() };
|
|
523
572
|
}
|
|
524
573
|
|
|
525
574
|
function restoreAppLayerOrderStatement(css: string, layerOrder?: string): string {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
575
|
+
if (!layerOrder) {
|
|
576
|
+
return css;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const charsetMatch = css.match(/^@charset[^;]*;/);
|
|
580
|
+
if (charsetMatch && charsetMatch.index === 0) {
|
|
581
|
+
const charsetText = charsetMatch[0];
|
|
582
|
+
const rest = css.slice(charsetText.length);
|
|
583
|
+
return `${charsetText}${layerOrder}${rest}`;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return `${layerOrder}${css}`;
|
|
538
587
|
}
|
|
539
588
|
|
|
540
589
|
async function resolveCssEntry(pageDirectory: string): Promise<string | null> {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
590
|
+
const modulePath = path.join(pageDirectory, `${FILES.index}${MODULE_SUFFIX}${EXTENSIONS.css}`);
|
|
591
|
+
if (await pathExists(modulePath)) {
|
|
592
|
+
return modulePath;
|
|
593
|
+
}
|
|
545
594
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
595
|
+
const plainPath = path.join(pageDirectory, `${FILES.index}${EXTENSIONS.css}`);
|
|
596
|
+
if (await pathExists(plainPath)) {
|
|
597
|
+
return plainPath;
|
|
598
|
+
}
|
|
550
599
|
|
|
551
|
-
|
|
600
|
+
return null;
|
|
552
601
|
}
|