@fluid-app/fluid-cli-portal 0.1.33 → 0.1.35
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/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1253 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -610,7 +610,15 @@ const CONFIG_CANDIDATES = [
|
|
|
610
610
|
const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
|
|
611
611
|
const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
|
|
612
612
|
const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
|
|
613
|
+
const FLUID_WIDGET_AUTHORING_NODE_CONDITIONS = ["--conditions=fluid-widget-authoring", "--conditions=production"];
|
|
613
614
|
const TSX_CLI_PATH$1 = createRequire(import.meta.url).resolve("tsx/cli");
|
|
615
|
+
function withFluidWidgetAuthoringNodeCondition(nodeOptions) {
|
|
616
|
+
const existingOptions = nodeOptions?.trim();
|
|
617
|
+
const options = existingOptions ? existingOptions.split(/\s+/) : [];
|
|
618
|
+
const optionSet = new Set(options);
|
|
619
|
+
for (const condition of FLUID_WIDGET_AUTHORING_NODE_CONDITIONS) if (!optionSet.has(condition)) options.push(condition);
|
|
620
|
+
return options.join(" ");
|
|
621
|
+
}
|
|
614
622
|
async function resolvePortalWidgetSourceConfig(projectDir) {
|
|
615
623
|
for (const relativePath of CONFIG_CANDIDATES) {
|
|
616
624
|
const candidate = path.join(projectDir, relativePath);
|
|
@@ -644,7 +652,8 @@ async function loadSourceWidgetPackages(projectDir) {
|
|
|
644
652
|
stdio: "pipe",
|
|
645
653
|
env: {
|
|
646
654
|
...process.env,
|
|
647
|
-
NODE_ENV: "production"
|
|
655
|
+
NODE_ENV: "production",
|
|
656
|
+
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
648
657
|
}
|
|
649
658
|
});
|
|
650
659
|
const outputResult = await readSourcePackageExtractorOutput(outputFile);
|
|
@@ -734,12 +743,12 @@ const server = await createServer({
|
|
|
734
743
|
logLevel: "error",
|
|
735
744
|
clearScreen: false,
|
|
736
745
|
resolve: {
|
|
737
|
-
conditions: ["fluid-widget-authoring"],
|
|
746
|
+
conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
|
|
738
747
|
},
|
|
739
748
|
ssr: {
|
|
740
749
|
noExternal: ["@fluid-app/portal-sdk"],
|
|
741
750
|
resolve: {
|
|
742
|
-
conditions: ["fluid-widget-authoring"],
|
|
751
|
+
conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
|
|
743
752
|
},
|
|
744
753
|
},
|
|
745
754
|
});
|
|
@@ -871,7 +880,8 @@ async function extractManifests(projectDir) {
|
|
|
871
880
|
stdio: "pipe",
|
|
872
881
|
env: {
|
|
873
882
|
...process.env,
|
|
874
|
-
NODE_ENV: "production"
|
|
883
|
+
NODE_ENV: "production",
|
|
884
|
+
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
875
885
|
}
|
|
876
886
|
});
|
|
877
887
|
const outputResult = await readManifestExtractorOutput(outputFile);
|
|
@@ -977,6 +987,15 @@ const server = await createServer({
|
|
|
977
987
|
appType: "custom",
|
|
978
988
|
logLevel: "error",
|
|
979
989
|
clearScreen: false,
|
|
990
|
+
resolve: {
|
|
991
|
+
conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
|
|
992
|
+
},
|
|
993
|
+
ssr: {
|
|
994
|
+
noExternal: ["@fluid-app/portal-sdk"],
|
|
995
|
+
resolve: {
|
|
996
|
+
conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
|
|
997
|
+
},
|
|
998
|
+
},
|
|
980
999
|
});
|
|
981
1000
|
|
|
982
1001
|
try {
|
|
@@ -2527,18 +2546,10 @@ function validateCssUrl(value, cssUrlPath, errors) {
|
|
|
2527
2546
|
});
|
|
2528
2547
|
return;
|
|
2529
2548
|
}
|
|
2530
|
-
if (!
|
|
2531
|
-
errors.push({
|
|
2532
|
-
code: "INVALID_SOURCE_PACKAGE",
|
|
2533
|
-
path: cssUrlPath,
|
|
2534
|
-
message: "Widget package cssUrls entries must be https URLs, trusted local http URLs, or relative URLs."
|
|
2535
|
-
});
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
if (!isAbsoluteRuntimeUrl(value) && !isWidgetPackageCssArtifactPath(value)) errors.push({
|
|
2549
|
+
if (!isWidgetPackageCssArtifactPath(value)) errors.push({
|
|
2539
2550
|
code: "INVALID_SOURCE_PACKAGE",
|
|
2540
2551
|
path: cssUrlPath,
|
|
2541
|
-
message: "Widget package
|
|
2552
|
+
message: "Widget package cssUrls entries must be top-level generated CSS artifact filenames matching [A-Za-z0-9._~-]+.css. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them."
|
|
2542
2553
|
});
|
|
2543
2554
|
}
|
|
2544
2555
|
function validateRuntimeDescriptorUrl(value, urlPath) {
|
|
@@ -2622,21 +2633,19 @@ function validateOptionalRecordField(record, key, path, errors) {
|
|
|
2622
2633
|
}
|
|
2623
2634
|
function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
|
|
2624
2635
|
const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
|
|
2636
|
+
generatedCssUrls?.forEach((cssUrl, index) => {
|
|
2637
|
+
validateGeneratedCssArtifactUrl(cssUrl, `generated cssUrls[${index}]`);
|
|
2638
|
+
});
|
|
2625
2639
|
sourceCssUrls?.forEach((cssUrl, index) => {
|
|
2626
|
-
|
|
2627
|
-
if (isAbsoluteRuntimeUrl(cssUrl)) return;
|
|
2628
|
-
if (!isWidgetPackageCssArtifactPath(cssUrl)) throw new Error(`cssUrls[${index}] relative URL ${JSON.stringify(cssUrl)} must be a top-level CSS artifact filename matching [A-Za-z0-9._~-]+.css.`);
|
|
2640
|
+
validateGeneratedCssArtifactUrl(cssUrl, `cssUrls[${index}]`);
|
|
2629
2641
|
if (generatedCssUrlSet.has(cssUrl)) return;
|
|
2630
|
-
throw new Error(`cssUrls[${index}]
|
|
2642
|
+
throw new Error(`cssUrls[${index}] URL ${JSON.stringify(cssUrl)} must match a generated top-level CSS artifact. Import the CSS from the widget package bundle or remove it from cssUrls.`);
|
|
2631
2643
|
});
|
|
2632
|
-
|
|
2633
|
-
cssUrls.forEach((cssUrl, index) => {
|
|
2634
|
-
validateRuntimeDescriptorUrl(cssUrl, `cssUrls[${index}]`);
|
|
2635
|
-
});
|
|
2636
|
-
return cssUrls;
|
|
2644
|
+
return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []]));
|
|
2637
2645
|
}
|
|
2638
|
-
function
|
|
2639
|
-
|
|
2646
|
+
function validateGeneratedCssArtifactUrl(value, cssUrlPath) {
|
|
2647
|
+
if (value.trim().length === 0) throw new Error(`${cssUrlPath} must be a non-empty string.`);
|
|
2648
|
+
if (!isWidgetPackageCssArtifactPath(value)) throw new Error(`${cssUrlPath} must be a top-level generated CSS artifact filename matching [A-Za-z0-9._~-]+.css. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them.`);
|
|
2640
2649
|
}
|
|
2641
2650
|
function normalizePropertySchema(value, widgetType) {
|
|
2642
2651
|
return {
|
|
@@ -2806,9 +2815,13 @@ async function buildSharedWidgetPackage(options) {
|
|
|
2806
2815
|
}
|
|
2807
2816
|
}
|
|
2808
2817
|
function createWidgetPackageEntrySource(options) {
|
|
2818
|
+
const runtimePackageId = getWidgetPackageDescriptorPackageId(options.validated);
|
|
2809
2819
|
return `import * as widgetConfig from ${JSON.stringify(options.configImportPath)};
|
|
2820
|
+
import { createElement } from "react";
|
|
2810
2821
|
|
|
2811
2822
|
const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
|
|
2823
|
+
const WIDGET_PACKAGE_ID = ${JSON.stringify(runtimePackageId)};
|
|
2824
|
+
const WIDGET_PACKAGE_VERSION = ${JSON.stringify(options.validated.version)};
|
|
2812
2825
|
const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
|
|
2813
2826
|
|
|
2814
2827
|
function isSourceWidgetPackage(value) {
|
|
@@ -2832,12 +2845,31 @@ const componentsByName = new Map(
|
|
|
2832
2845
|
sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
|
|
2833
2846
|
);
|
|
2834
2847
|
|
|
2848
|
+
function createScopedWidgetComponent(component, descriptor) {
|
|
2849
|
+
const wrapperTagName = descriptor.container === "inline" ? "span" : "div";
|
|
2850
|
+
const WrappedFluidWidgetComponent = (props) => createElement(
|
|
2851
|
+
wrapperTagName,
|
|
2852
|
+
{
|
|
2853
|
+
"data-fluid-widget-package": WIDGET_PACKAGE_ID,
|
|
2854
|
+
"data-fluid-widget-version": WIDGET_PACKAGE_VERSION,
|
|
2855
|
+
"data-fluid-widget-name": descriptor.name,
|
|
2856
|
+
},
|
|
2857
|
+
// PortalContainerProvider is not imported here because standalone widget packages
|
|
2858
|
+
// only depend on @fluid-app/portal-sdk, which does not currently expose a
|
|
2859
|
+
// public portal-container provider. Radix/ui-primitives portals remain a
|
|
2860
|
+
// residual isolation risk until a public widget-safe provider is available.
|
|
2861
|
+
createElement(component, props),
|
|
2862
|
+
);
|
|
2863
|
+
WrappedFluidWidgetComponent.displayName = "FluidWidgetPackage(" + WIDGET_PACKAGE_ID + "/" + descriptor.name + ")";
|
|
2864
|
+
return WrappedFluidWidgetComponent;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2835
2867
|
const widgets = descriptors.map((descriptor) => {
|
|
2836
2868
|
const component = componentsByName.get(descriptor.name);
|
|
2837
2869
|
if (typeof component !== "function" && typeof component !== "object") {
|
|
2838
2870
|
throw new Error("Widget package is missing component for " + descriptor.name + ".");
|
|
2839
2871
|
}
|
|
2840
|
-
return { ...descriptor, component };
|
|
2872
|
+
return { ...descriptor, component: createScopedWidgetComponent(component, descriptor) };
|
|
2841
2873
|
});
|
|
2842
2874
|
|
|
2843
2875
|
var _fluidWidgets = globalObject.FluidWidgets;
|
|
@@ -2846,7 +2878,7 @@ if (!_fluidWidgets || typeof _fluidWidgets.registerPackage !== "function") {
|
|
|
2846
2878
|
}
|
|
2847
2879
|
|
|
2848
2880
|
_fluidWidgets.registerPackage({
|
|
2849
|
-
packageId: ${JSON.stringify(
|
|
2881
|
+
packageId: ${JSON.stringify(runtimePackageId)},
|
|
2850
2882
|
version: ${JSON.stringify(options.validated.version)},
|
|
2851
2883
|
widgets,
|
|
2852
2884
|
});
|
|
@@ -2863,6 +2895,8 @@ async function buildWidgetScriptBundle(options) {
|
|
|
2863
2895
|
}), "utf-8");
|
|
2864
2896
|
await fs.writeFile(viteConfigPath, createViteConfigSource({
|
|
2865
2897
|
entryPath,
|
|
2898
|
+
packageId: getWidgetPackageDescriptorPackageId(options.validated),
|
|
2899
|
+
packageVersion: options.validated.version,
|
|
2866
2900
|
publishDir: options.publishDir,
|
|
2867
2901
|
projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
|
|
2868
2902
|
}), "utf-8");
|
|
@@ -2877,7 +2911,8 @@ async function buildWidgetScriptBundle(options) {
|
|
|
2877
2911
|
stdio: "pipe",
|
|
2878
2912
|
env: {
|
|
2879
2913
|
...process.env,
|
|
2880
|
-
NODE_ENV: "production"
|
|
2914
|
+
NODE_ENV: "production",
|
|
2915
|
+
NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
|
|
2881
2916
|
}
|
|
2882
2917
|
});
|
|
2883
2918
|
const widgetScriptPath = path.join(options.publishDir, "widget.js");
|
|
@@ -2890,13 +2925,14 @@ function wrapWidgetPackageScript(widgetScriptContent) {
|
|
|
2890
2925
|
var runWidgetPackage = function fluidWidgetPackageFactory() {
|
|
2891
2926
|
// Intentionally shadow Rollup's expected FluidShared global with the wrapper-scoped host globals.
|
|
2892
2927
|
var FluidShared = globalObject.FluidShared;
|
|
2893
|
-
if (!FluidShared) {
|
|
2894
|
-
throw new Error("window.FluidShared is not installed.");
|
|
2928
|
+
if (!FluidShared || !FluidShared.PortalSdk) {
|
|
2929
|
+
throw new Error("window.FluidShared.PortalSdk is not installed.");
|
|
2895
2930
|
}
|
|
2896
2931
|
${indentScript(widgetScriptContent, " ")}
|
|
2897
2932
|
};
|
|
2933
|
+
runWidgetPackage.__fluidRequiresPortalSdk = true;
|
|
2898
2934
|
|
|
2899
|
-
if (globalObject.FluidShared) {
|
|
2935
|
+
if (globalObject.FluidShared && globalObject.FluidShared.PortalSdk) {
|
|
2900
2936
|
runWidgetPackage();
|
|
2901
2937
|
return;
|
|
2902
2938
|
}
|
|
@@ -2989,6 +3025,21 @@ export default defineConfig(async (configEnv) => {
|
|
|
2989
3025
|
function createWidgetViteConfigObjectSource(options) {
|
|
2990
3026
|
const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
|
|
2991
3027
|
return `(() => {
|
|
3028
|
+
const widgetPackageId = ${JSON.stringify(options.packageId)};
|
|
3029
|
+
const widgetPackageVersion = ${JSON.stringify(options.packageVersion)};
|
|
3030
|
+
const widgetPackageScopeSelector = '[data-fluid-widget-package="' + widgetPackageId + '"][data-fluid-widget-version="' + widgetPackageVersion + '"]';
|
|
3031
|
+
const widgetPackageGeneratedCssFileName = "widget.css";
|
|
3032
|
+
const widgetPackageKeyframePrefix = "fluid-widget-" + encodeCssIdentifier(widgetPackageId) + "-" + encodeCssIdentifier(widgetPackageVersion) + "-";
|
|
3033
|
+
const widgetPackageLayerPrefix = widgetPackageKeyframePrefix + "layer-";
|
|
3034
|
+
const widgetPackageHostDefaultLayerName = widgetPackageLayerPrefix + "generated-host-defaults";
|
|
3035
|
+
const widgetPackageHostDefaultRule = "@layer " + widgetPackageHostDefaultLayerName + " { " + widgetPackageScopeSelector + " { display: contents; } }";
|
|
3036
|
+
const widgetPackageContainerPrefix = widgetPackageKeyframePrefix + "container-";
|
|
3037
|
+
const widgetPackageCssGlobalNamePrefix = widgetPackageKeyframePrefix + "css-name-";
|
|
3038
|
+
const groupingAtRules = new Set(["media", "supports", "container", "document", "starting-style"]);
|
|
3039
|
+
const unsafeSelectorPattern = /(^|[\\s>+~,])(:root|html|body)(?=$|[\\s.#:[>+~,)])/i;
|
|
3040
|
+
const universalSelectorPattern = /(^|[\\s>+~,])\\*(?=$|[\\s.#:[>+~,)])/g;
|
|
3041
|
+
const pseudoElementOnlySelectorPattern = /^::[A-Za-z-]+(?:\\([^)]*\\))?$/;
|
|
3042
|
+
|
|
2992
3043
|
function createFluidWidgetBuildInvariants() {
|
|
2993
3044
|
return {
|
|
2994
3045
|
copyPublicDir: false,
|
|
@@ -3002,12 +3053,18 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
3002
3053
|
fileName: () => "widget.js",
|
|
3003
3054
|
},
|
|
3004
3055
|
rollupOptions: {
|
|
3005
|
-
external: [
|
|
3056
|
+
external: [
|
|
3057
|
+
"react",
|
|
3058
|
+
"react-dom",
|
|
3059
|
+
"react/jsx-runtime",
|
|
3060
|
+
"@fluid-app/portal-sdk",
|
|
3061
|
+
],
|
|
3006
3062
|
output: {
|
|
3007
3063
|
globals: {
|
|
3008
3064
|
react: "FluidShared.React",
|
|
3009
3065
|
"react-dom": "FluidShared.ReactDOM",
|
|
3010
3066
|
"react/jsx-runtime": "FluidShared.ReactJsxRuntime",
|
|
3067
|
+
"@fluid-app/portal-sdk": "FluidShared.PortalSdk",
|
|
3011
3068
|
},
|
|
3012
3069
|
},
|
|
3013
3070
|
},
|
|
@@ -3027,12 +3084,1174 @@ function createWidgetViteConfigObjectSource(options) {
|
|
|
3027
3084
|
};
|
|
3028
3085
|
}
|
|
3029
3086
|
|
|
3087
|
+
function fluidWidgetCssIsolationPlugin() {
|
|
3088
|
+
return {
|
|
3089
|
+
name: "fluid-widget-css-isolation",
|
|
3090
|
+
enforce: "post",
|
|
3091
|
+
generateBundle(_outputOptions, bundle) {
|
|
3092
|
+
let hasCssAsset = false;
|
|
3093
|
+
for (const asset of Object.values(bundle)) {
|
|
3094
|
+
if (!asset || asset.type !== "asset" || !asset.fileName.endsWith(".css")) continue;
|
|
3095
|
+
hasCssAsset = true;
|
|
3096
|
+
const source = typeof asset.source === "string" ? asset.source : new TextDecoder().decode(asset.source);
|
|
3097
|
+
asset.source = isolateWidgetCssAsset(source, asset.fileName);
|
|
3098
|
+
}
|
|
3099
|
+
if (!hasCssAsset) {
|
|
3100
|
+
this.emitFile({
|
|
3101
|
+
type: "asset",
|
|
3102
|
+
fileName: widgetPackageGeneratedCssFileName,
|
|
3103
|
+
source: widgetPackageHostDefaultRule + "\\n",
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
},
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
function isolateWidgetCssAsset(css, assetName) {
|
|
3111
|
+
const keyframeNames = collectKeyframeNames(css);
|
|
3112
|
+
const fontFamilyNames = collectFontFamilyNames(css);
|
|
3113
|
+
const customPropertyNames = collectRegisteredCustomPropertyNames(css);
|
|
3114
|
+
const placementSensitivePrefix = readPlacementSensitiveCssPrefix(css, assetName);
|
|
3115
|
+
const isolatedCss = isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, placementSensitivePrefix.endIndex, css.length).css;
|
|
3116
|
+
return placementSensitivePrefix.css + widgetPackageHostDefaultRule + "\\n" + isolatedCss;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
function collectKeyframeNames(css) {
|
|
3120
|
+
const names = new Map();
|
|
3121
|
+
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
3122
|
+
if (!isKeyframesAtRule(atRuleName)) return false;
|
|
3123
|
+
const match = /^@(?:-[A-Za-z]+-)?keyframes\\s+([^\\s{]+)/i.exec(atRule.prelude);
|
|
3124
|
+
const name = match?.[1];
|
|
3125
|
+
if (name && !names.has(name)) names.set(name, widgetPackageKeyframePrefix + name);
|
|
3126
|
+
return atRule.blockStart !== -1;
|
|
3127
|
+
});
|
|
3128
|
+
return names;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
function collectFontFamilyNames(css) {
|
|
3132
|
+
const names = new Map();
|
|
3133
|
+
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
3134
|
+
if (atRuleName !== "font-face" || atRule.blockStart === -1 || atRule.blockEnd === -1) return false;
|
|
3135
|
+
let familyName = "";
|
|
3136
|
+
rewriteDeclarationBlock(css.slice(atRule.blockStart + 1, atRule.blockEnd), (propertyName, rawProperty, value) => {
|
|
3137
|
+
if (propertyName === "font-family" && !familyName) {
|
|
3138
|
+
familyName = normalizeFontFamilyName(value);
|
|
3139
|
+
}
|
|
3140
|
+
return { property: rawProperty, value };
|
|
3141
|
+
});
|
|
3142
|
+
if (familyName && !names.has(familyName)) names.set(familyName, widgetPackageKeyframePrefix + familyName);
|
|
3143
|
+
return true;
|
|
3144
|
+
});
|
|
3145
|
+
return names;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
function collectRegisteredCustomPropertyNames(css) {
|
|
3149
|
+
const names = new Map();
|
|
3150
|
+
visitCssAtRules(css, (atRuleName, atRule) => {
|
|
3151
|
+
if (atRuleName !== "property") return false;
|
|
3152
|
+
const match = /^@property\\s+(--[A-Za-z0-9_-]+)/i.exec(atRule.prelude);
|
|
3153
|
+
const name = match?.[1];
|
|
3154
|
+
if (name && !names.has(name)) names.set(name, "--" + widgetPackageKeyframePrefix + name.slice(2));
|
|
3155
|
+
return atRule.blockStart !== -1;
|
|
3156
|
+
});
|
|
3157
|
+
return names;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
function visitCssAtRules(css, visitAtRule) {
|
|
3161
|
+
let index = 0;
|
|
3162
|
+
while (index < css.length) {
|
|
3163
|
+
if (cssStartsWithComment(css, index)) {
|
|
3164
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
3165
|
+
index = commentEnd === -1 ? css.length : commentEnd + 2;
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
const char = css[index];
|
|
3169
|
+
if (char === '"' || char === "'") {
|
|
3170
|
+
index = findCssStringEnd(css, index);
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
if (char !== "@") {
|
|
3174
|
+
index += 1;
|
|
3175
|
+
continue;
|
|
3176
|
+
}
|
|
3177
|
+
const atRule = readRulePrelude(css, index, css.length);
|
|
3178
|
+
const blockEnd = atRule.blockStart === -1 ? -1 : findMatchingBrace(css, atRule.blockStart, css.length);
|
|
3179
|
+
const shouldSkipBlock = visitAtRule(readAtRuleName(atRule.prelude), { ...atRule, blockEnd });
|
|
3180
|
+
index = shouldSkipBlock && blockEnd !== -1 ? blockEnd + 1 : index + 1;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
function isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, startIndex, endIndex) {
|
|
3185
|
+
let output = "";
|
|
3186
|
+
let index = startIndex;
|
|
3187
|
+
while (index < endIndex) {
|
|
3188
|
+
if (css.startsWith("/*", index)) {
|
|
3189
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
3190
|
+
const nextIndex = commentEnd === -1 ? endIndex : commentEnd + 2;
|
|
3191
|
+
output += css.slice(index, nextIndex);
|
|
3192
|
+
index = nextIndex;
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
if (/\\s/.test(css[index] ?? "")) {
|
|
3197
|
+
output += css[index];
|
|
3198
|
+
index += 1;
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
if (css[index] === "@") {
|
|
3203
|
+
const atRule = readRulePrelude(css, index, endIndex);
|
|
3204
|
+
const atRuleName = readAtRuleName(atRule.prelude);
|
|
3205
|
+
if (atRule.blockStart === -1) {
|
|
3206
|
+
if (atRuleName === "import") {
|
|
3207
|
+
throw new Error('Unsupported @import rule in CSS asset "' + assetName + '" cannot be isolated safely. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them.');
|
|
3208
|
+
}
|
|
3209
|
+
if (atRuleName === "charset" || atRuleName === "namespace") {
|
|
3210
|
+
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely after generated host defaults. Move @' + atRuleName + ' before all non-@charset/@namespace rules.');
|
|
3211
|
+
}
|
|
3212
|
+
if (atRuleName === "layer") {
|
|
3213
|
+
output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.end);
|
|
3214
|
+
index = atRule.end;
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
output += css.slice(index, atRule.end);
|
|
3218
|
+
index = atRule.end;
|
|
3219
|
+
continue;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
const blockEnd = findMatchingBrace(css, atRule.blockStart, endIndex);
|
|
3223
|
+
if (blockEnd === -1) {
|
|
3224
|
+
output += css.slice(index, endIndex);
|
|
3225
|
+
index = endIndex;
|
|
3226
|
+
continue;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
const ruleHeader = css.slice(index, atRule.blockStart + 1);
|
|
3230
|
+
if (isKeyframesAtRule(atRuleName)) {
|
|
3231
|
+
output += rewriteKeyframesHeader(ruleHeader, keyframeNames) + namespaceKeyframesBlock(css.slice(atRule.blockStart + 1, blockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
3232
|
+
} else if (atRuleName === "font-face") {
|
|
3233
|
+
output += ruleHeader + namespaceFontFaceBlock(css.slice(atRule.blockStart + 1, blockEnd), fontFamilyNames, customPropertyNames) + "}";
|
|
3234
|
+
} else if (atRuleName === "property") {
|
|
3235
|
+
output += rewritePropertyHeader(ruleHeader, customPropertyNames) + namespaceCustomPropertyReferences(css.slice(atRule.blockStart + 1, blockEnd), customPropertyNames) + "}";
|
|
3236
|
+
} else if (atRuleName === "layer") {
|
|
3237
|
+
output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.blockStart + 1) + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
|
|
3238
|
+
} else if (groupingAtRules.has(atRuleName)) {
|
|
3239
|
+
const groupingRuleHeader = atRuleName === "container" ? rewriteContainerAtRuleHeader(ruleHeader) : ruleHeader;
|
|
3240
|
+
output += groupingRuleHeader + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
|
|
3241
|
+
} else {
|
|
3242
|
+
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3243
|
+
}
|
|
3244
|
+
index = blockEnd + 1;
|
|
3245
|
+
continue;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
const styleRule = readRulePrelude(css, index, endIndex);
|
|
3249
|
+
if (styleRule.blockStart === -1) {
|
|
3250
|
+
output += css.slice(index, styleRule.end);
|
|
3251
|
+
index = styleRule.end;
|
|
3252
|
+
continue;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
const styleBlockEnd = findMatchingBrace(css, styleRule.blockStart, endIndex);
|
|
3256
|
+
if (styleBlockEnd === -1) {
|
|
3257
|
+
output += css.slice(index, endIndex);
|
|
3258
|
+
index = endIndex;
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
output += isolateSelectorList(styleRule.prelude, assetName) + "{" + namespaceStyleBlock(css.slice(styleRule.blockStart + 1, styleBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
3263
|
+
index = styleBlockEnd + 1;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
return { css: output, index };
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
function readPlacementSensitiveCssPrefix(css, assetName) {
|
|
3270
|
+
let output = "";
|
|
3271
|
+
let index = 0;
|
|
3272
|
+
let hasNamespace = false;
|
|
3273
|
+
while (index < css.length) {
|
|
3274
|
+
const leadingStart = index;
|
|
3275
|
+
index = skipCssWhitespaceAndComments(css, index);
|
|
3276
|
+
const leading = css.slice(leadingStart, index);
|
|
3277
|
+
if (css[index] !== "@") {
|
|
3278
|
+
return { css: output + leading, endIndex: index };
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
const atRule = readRulePrelude(css, index, css.length);
|
|
3282
|
+
const atRuleName = readAtRuleName(atRule.prelude);
|
|
3283
|
+
if (atRuleName !== "charset" && atRuleName !== "namespace") {
|
|
3284
|
+
return { css: output + leading, endIndex: index };
|
|
3285
|
+
}
|
|
3286
|
+
if (atRule.blockStart !== -1) {
|
|
3287
|
+
throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3288
|
+
}
|
|
3289
|
+
if (atRuleName === "charset" && hasNamespace) {
|
|
3290
|
+
throw new Error('Unsupported @charset rule in CSS asset "' + assetName + '" cannot be isolated safely after @namespace. Move @charset before @namespace.');
|
|
3291
|
+
}
|
|
3292
|
+
if (atRuleName === "namespace") hasNamespace = true;
|
|
3293
|
+
output += leading + css.slice(index, atRule.end);
|
|
3294
|
+
index = atRule.end;
|
|
3295
|
+
}
|
|
3296
|
+
return { css: output, endIndex: index };
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
function readRulePrelude(css, startIndex, endIndex) {
|
|
3300
|
+
let index = startIndex;
|
|
3301
|
+
let quote = "";
|
|
3302
|
+
let bracketDepth = 0;
|
|
3303
|
+
let parenDepth = 0;
|
|
3304
|
+
while (index < endIndex) {
|
|
3305
|
+
const char = css[index];
|
|
3306
|
+
if (quote) {
|
|
3307
|
+
if (char === "\\\\") index += 2;
|
|
3308
|
+
else {
|
|
3309
|
+
if (char === quote) quote = "";
|
|
3310
|
+
index += 1;
|
|
3311
|
+
}
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
if (cssStartsWithComment(css, index)) {
|
|
3315
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
3316
|
+
index = commentEnd === -1 ? endIndex : Math.min(commentEnd + 2, endIndex);
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
if (char === '"' || char === "'") {
|
|
3320
|
+
quote = char;
|
|
3321
|
+
index += 1;
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
if (char === "[") bracketDepth += 1;
|
|
3325
|
+
else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
3326
|
+
else if (char === "(") parenDepth += 1;
|
|
3327
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
3328
|
+
else if (bracketDepth === 0 && parenDepth === 0 && char === "{") {
|
|
3329
|
+
return { prelude: css.slice(startIndex, index), blockStart: index, end: index + 1 };
|
|
3330
|
+
} else if (bracketDepth === 0 && parenDepth === 0 && char === ";") {
|
|
3331
|
+
return { prelude: css.slice(startIndex, index), blockStart: -1, end: index + 1 };
|
|
3332
|
+
}
|
|
3333
|
+
index += 1;
|
|
3334
|
+
}
|
|
3335
|
+
return { prelude: css.slice(startIndex, endIndex), blockStart: -1, end: endIndex };
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
function findMatchingBrace(css, openBraceIndex, endIndex) {
|
|
3339
|
+
let depth = 1;
|
|
3340
|
+
let index = openBraceIndex + 1;
|
|
3341
|
+
let quote = "";
|
|
3342
|
+
while (index < endIndex) {
|
|
3343
|
+
const char = css[index];
|
|
3344
|
+
if (quote) {
|
|
3345
|
+
if (char === "\\\\") index += 2;
|
|
3346
|
+
else {
|
|
3347
|
+
if (char === quote) quote = "";
|
|
3348
|
+
index += 1;
|
|
3349
|
+
}
|
|
3350
|
+
continue;
|
|
3351
|
+
}
|
|
3352
|
+
if (char === '"' || char === "'") {
|
|
3353
|
+
quote = char;
|
|
3354
|
+
index += 1;
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
if (css.startsWith("/*", index)) {
|
|
3358
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
3359
|
+
index = commentEnd === -1 ? endIndex : commentEnd + 2;
|
|
3360
|
+
continue;
|
|
3361
|
+
}
|
|
3362
|
+
if (char === "{") depth += 1;
|
|
3363
|
+
else if (char === "}") {
|
|
3364
|
+
depth -= 1;
|
|
3365
|
+
if (depth === 0) return index;
|
|
3366
|
+
}
|
|
3367
|
+
index += 1;
|
|
3368
|
+
}
|
|
3369
|
+
return -1;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
function readAtRuleName(prelude) {
|
|
3373
|
+
const match = /^@([A-Za-z-]+)/.exec(prelude.trim());
|
|
3374
|
+
return match?.[1]?.toLowerCase() ?? "";
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
function isKeyframesAtRule(atRuleName) {
|
|
3378
|
+
return atRuleName === "keyframes" || atRuleName.endsWith("-keyframes");
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
function rewriteKeyframesHeader(header, keyframeNames) {
|
|
3382
|
+
return header.replace(/(@(?:-[A-Za-z]+-)?keyframes\\s+)([^\\s{]+)/, (_match, prefix, name) => prefix + (keyframeNames.get(name) ?? name));
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
function splitSelectorList(selectorList) {
|
|
3386
|
+
const selectors = [];
|
|
3387
|
+
let current = "";
|
|
3388
|
+
let quote = "";
|
|
3389
|
+
let bracketDepth = 0;
|
|
3390
|
+
let parenDepth = 0;
|
|
3391
|
+
for (let index = 0; index < selectorList.length; index += 1) {
|
|
3392
|
+
const char = selectorList[index];
|
|
3393
|
+
if (quote) {
|
|
3394
|
+
current += char;
|
|
3395
|
+
if (char === "\\\\") {
|
|
3396
|
+
index += 1;
|
|
3397
|
+
current += selectorList[index] ?? "";
|
|
3398
|
+
} else if (char === quote) quote = "";
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
if (char === '"' || char === "'") {
|
|
3402
|
+
quote = char;
|
|
3403
|
+
current += char;
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
if (cssStartsWithComment(selectorList, index)) {
|
|
3407
|
+
const commentEnd = selectorList.indexOf("*/", index + 2);
|
|
3408
|
+
const nextIndex = commentEnd === -1 ? selectorList.length : commentEnd + 2;
|
|
3409
|
+
current += selectorList.slice(index, nextIndex);
|
|
3410
|
+
index = nextIndex - 1;
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3413
|
+
if (char === "[") bracketDepth += 1;
|
|
3414
|
+
else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
3415
|
+
else if (char === "(") parenDepth += 1;
|
|
3416
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
3417
|
+
if (char === "," && bracketDepth === 0 && parenDepth === 0) {
|
|
3418
|
+
selectors.push(current);
|
|
3419
|
+
current = "";
|
|
3420
|
+
} else current += char;
|
|
3421
|
+
}
|
|
3422
|
+
selectors.push(current);
|
|
3423
|
+
return selectors;
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
function isolateSelectorList(selectorList, assetName) {
|
|
3427
|
+
return splitSelectorList(selectorList).map((selector) => isolateSelector(selector, assetName)).join(",");
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
function isolateSelector(selector, assetName) {
|
|
3431
|
+
const leadingWhitespace = selector.match(/^\\s*/)?.[0] ?? "";
|
|
3432
|
+
const trailingWhitespace = selector.match(/\\s*$/)?.[0] ?? "";
|
|
3433
|
+
const trimmed = selector.trim();
|
|
3434
|
+
assertSafeSelector(trimmed, assetName);
|
|
3435
|
+
if (trimmed.includes(":host")) {
|
|
3436
|
+
return leadingWhitespace + isolateHostSelector(trimmed, assetName) + trailingWhitespace;
|
|
3437
|
+
}
|
|
3438
|
+
return leadingWhitespace + widgetPackageScopeSelector + " " + trimmed + trailingWhitespace;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
function isolateHostSelector(selector, assetName) {
|
|
3442
|
+
if (!selector.startsWith(":host")) {
|
|
3443
|
+
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3444
|
+
}
|
|
3445
|
+
const match = /^:host(?:\\(([^()]*)\\))?($|[\\s>+~].*)/.exec(selector);
|
|
3446
|
+
if (!match) {
|
|
3447
|
+
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3448
|
+
}
|
|
3449
|
+
const hostSelector = (match[1] ?? "").trim();
|
|
3450
|
+
const selectorAfterHost = match[2] ?? "";
|
|
3451
|
+
const firstSelectorAfterHostIndex = skipCssWhitespaceAndComments(selectorAfterHost, 0);
|
|
3452
|
+
const firstSelectorAfterHostChar = selectorAfterHost[firstSelectorAfterHostIndex] ?? "";
|
|
3453
|
+
if (hostSelector.includes(",") || hasTopLevelHostArgumentCombinator(hostSelector) || firstSelectorAfterHostChar === "+" || firstSelectorAfterHostChar === "~" || (hostSelector && (unsafeSelectorPattern.test(hostSelector) || hasUnsafeUniversalSelector(hostSelector)))) {
|
|
3454
|
+
throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3455
|
+
}
|
|
3456
|
+
return widgetPackageScopeSelector + hostSelector + selectorAfterHost;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
function hasTopLevelHostArgumentCombinator(value) {
|
|
3460
|
+
let quote = "";
|
|
3461
|
+
let bracketDepth = 0;
|
|
3462
|
+
let hasSelectorBeforeWhitespace = false;
|
|
3463
|
+
let index = 0;
|
|
3464
|
+
while (index < value.length) {
|
|
3465
|
+
const char = value[index];
|
|
3466
|
+
if (quote) {
|
|
3467
|
+
if (char === "\\\\") index += 2;
|
|
3468
|
+
else {
|
|
3469
|
+
if (char === quote) quote = "";
|
|
3470
|
+
index += 1;
|
|
3471
|
+
}
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
if (char === '"' || char === "'") {
|
|
3475
|
+
quote = char;
|
|
3476
|
+
index += 1;
|
|
3477
|
+
continue;
|
|
3478
|
+
}
|
|
3479
|
+
if (char === "[") {
|
|
3480
|
+
bracketDepth += 1;
|
|
3481
|
+
hasSelectorBeforeWhitespace = true;
|
|
3482
|
+
} else if (char === "]") {
|
|
3483
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
3484
|
+
} else if (bracketDepth === 0 && (char === "+" || char === "~" || char === ">")) {
|
|
3485
|
+
return true;
|
|
3486
|
+
} else if (bracketDepth === 0 && /\\s/.test(char)) {
|
|
3487
|
+
const nextIndex = skipCssWhitespaceAndComments(value, index);
|
|
3488
|
+
if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
|
|
3489
|
+
index = nextIndex;
|
|
3490
|
+
continue;
|
|
3491
|
+
} else if (bracketDepth === 0 && cssStartsWithComment(value, index)) {
|
|
3492
|
+
const nextIndex = skipCssWhitespaceAndComments(value, index);
|
|
3493
|
+
if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
|
|
3494
|
+
index = nextIndex;
|
|
3495
|
+
continue;
|
|
3496
|
+
} else if (bracketDepth === 0 && !/\\s/.test(char)) {
|
|
3497
|
+
hasSelectorBeforeWhitespace = true;
|
|
3498
|
+
}
|
|
3499
|
+
index += 1;
|
|
3500
|
+
}
|
|
3501
|
+
return false;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
function assertSafeSelector(selector, assetName) {
|
|
3505
|
+
if (!selector || unsafeSelectorPattern.test(selector) || hasUnsafeUniversalSelector(selector) || pseudoElementOnlySelectorPattern.test(selector)) {
|
|
3506
|
+
throw new Error('Unsafe selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
function hasUnsafeUniversalSelector(selector) {
|
|
3511
|
+
universalSelectorPattern.lastIndex = 0;
|
|
3512
|
+
let match;
|
|
3513
|
+
while ((match = universalSelectorPattern.exec(selector)) !== null) {
|
|
3514
|
+
const universalStart = match.index + match[1].length;
|
|
3515
|
+
if (!hasAnchoringSelectorBefore(selector.slice(0, universalStart))) return true;
|
|
3516
|
+
}
|
|
3517
|
+
return false;
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
function hasAnchoringSelectorBefore(value) {
|
|
3521
|
+
return stripCssComments(value).replace(/[\\s>+~]+/g, "").length > 0;
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
function namespaceKeyframesBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
|
|
3525
|
+
let output = "";
|
|
3526
|
+
let index = 0;
|
|
3527
|
+
while (index < block.length) {
|
|
3528
|
+
if (cssStartsWithComment(block, index)) {
|
|
3529
|
+
const commentEnd = block.indexOf("*/", index + 2);
|
|
3530
|
+
const nextIndex = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
3531
|
+
output += block.slice(index, nextIndex);
|
|
3532
|
+
index = nextIndex;
|
|
3533
|
+
continue;
|
|
3534
|
+
}
|
|
3535
|
+
if (/\\s/.test(block[index] ?? "")) {
|
|
3536
|
+
output += block[index];
|
|
3537
|
+
index += 1;
|
|
3538
|
+
continue;
|
|
3539
|
+
}
|
|
3540
|
+
const keyframeRule = readRulePrelude(block, index, block.length);
|
|
3541
|
+
if (keyframeRule.blockStart === -1) {
|
|
3542
|
+
output += block.slice(index, keyframeRule.end);
|
|
3543
|
+
index = keyframeRule.end;
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
const keyframeBlockEnd = findMatchingBrace(block, keyframeRule.blockStart, block.length);
|
|
3547
|
+
if (keyframeBlockEnd === -1) {
|
|
3548
|
+
output += block.slice(index);
|
|
3549
|
+
index = block.length;
|
|
3550
|
+
continue;
|
|
3551
|
+
}
|
|
3552
|
+
output += keyframeRule.prelude + "{" + namespaceStyleBlock(block.slice(keyframeRule.blockStart + 1, keyframeBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
|
|
3553
|
+
index = keyframeBlockEnd + 1;
|
|
3554
|
+
}
|
|
3555
|
+
return output;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
function namespaceStyleBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
|
|
3559
|
+
assertNoNestedCssBlock(block, assetName);
|
|
3560
|
+
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
|
|
3561
|
+
const nextProperty = namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames);
|
|
3562
|
+
let nextValue = value;
|
|
3563
|
+
if (propertyName === "animation" || propertyName === "-webkit-animation") {
|
|
3564
|
+
nextValue = namespaceAnimationShorthandValue(nextValue, keyframeNames);
|
|
3565
|
+
} else if (propertyName === "animation-name" || propertyName === "-webkit-animation-name") {
|
|
3566
|
+
nextValue = namespaceAnimationNameValue(nextValue, keyframeNames);
|
|
3567
|
+
} else if (propertyName === "font-family") {
|
|
3568
|
+
nextValue = namespaceFontFamilyValue(nextValue, fontFamilyNames);
|
|
3569
|
+
} else if (propertyName === "font") {
|
|
3570
|
+
nextValue = namespaceFontShorthandValue(nextValue, fontFamilyNames, assetName);
|
|
3571
|
+
}
|
|
3572
|
+
if (propertyName === "container-name") {
|
|
3573
|
+
nextValue = namespaceContainerNameValue(nextValue);
|
|
3574
|
+
} else if (propertyName === "container") {
|
|
3575
|
+
nextValue = namespaceContainerShorthandValue(nextValue);
|
|
3576
|
+
}
|
|
3577
|
+
if (propertyName === "transition" || propertyName === "transition-property" || propertyName === "will-change") {
|
|
3578
|
+
nextValue = namespaceCustomPropertyIdentifierValue(nextValue, customPropertyNames);
|
|
3579
|
+
}
|
|
3580
|
+
if (propertyName === "view-transition-name" || propertyName === "scroll-timeline-name" || propertyName === "animation-timeline") {
|
|
3581
|
+
nextValue = namespaceCssGlobalNameValue(nextValue);
|
|
3582
|
+
}
|
|
3583
|
+
nextValue = namespaceCustomPropertyReferencesValue(nextValue, customPropertyNames);
|
|
3584
|
+
return { property: nextProperty, value: nextValue };
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
function namespaceFontFaceBlock(block, fontFamilyNames, customPropertyNames) {
|
|
3589
|
+
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
|
|
3590
|
+
const nextValue = propertyName === "font-family"
|
|
3591
|
+
? namespaceFontFamilyValue(value, fontFamilyNames)
|
|
3592
|
+
: namespaceCustomPropertyReferencesValue(value, customPropertyNames);
|
|
3593
|
+
return { property: rawProperty, value: nextValue };
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
function namespaceAnimationNameValue(value, keyframeNames) {
|
|
3598
|
+
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
3599
|
+
let nextChunk = chunk;
|
|
3600
|
+
for (const [name, scopedName] of keyframeNames) {
|
|
3601
|
+
nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
|
|
3602
|
+
}
|
|
3603
|
+
return nextChunk;
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
function namespaceAnimationShorthandValue(value, keyframeNames) {
|
|
3608
|
+
if (keyframeNames.size === 0) return value;
|
|
3609
|
+
return splitCssCommaList(value).map((item) => rewriteAnimationShorthandItem(item, keyframeNames)).join(",");
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
function rewriteAnimationShorthandItem(item, keyframeNames) {
|
|
3613
|
+
const tokens = readCssValueTokens(item);
|
|
3614
|
+
const reservedState = createAnimationShorthandReservedState();
|
|
3615
|
+
let output = "";
|
|
3616
|
+
let lastIndex = 0;
|
|
3617
|
+
for (const token of tokens) {
|
|
3618
|
+
const scopedName = keyframeNames.get(token.text);
|
|
3619
|
+
const isReservedToken = consumeAnimationShorthandReservedToken(token.text, reservedState);
|
|
3620
|
+
if (!scopedName || isReservedToken) continue;
|
|
3621
|
+
output += item.slice(lastIndex, token.start) + scopedName;
|
|
3622
|
+
lastIndex = token.end;
|
|
3623
|
+
}
|
|
3624
|
+
return output + item.slice(lastIndex);
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
function createAnimationShorthandReservedState() {
|
|
3628
|
+
return {
|
|
3629
|
+
duration: false,
|
|
3630
|
+
delay: false,
|
|
3631
|
+
timingFunction: false,
|
|
3632
|
+
iterationCount: false,
|
|
3633
|
+
direction: false,
|
|
3634
|
+
fillMode: false,
|
|
3635
|
+
playState: false,
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
function consumeAnimationShorthandReservedToken(token, state) {
|
|
3640
|
+
const lower = token.toLowerCase();
|
|
3641
|
+
if (isAnimationTimeToken(token)) {
|
|
3642
|
+
if (!state.duration) {
|
|
3643
|
+
state.duration = true;
|
|
3644
|
+
return true;
|
|
3645
|
+
}
|
|
3646
|
+
if (!state.delay) {
|
|
3647
|
+
state.delay = true;
|
|
3648
|
+
return true;
|
|
3649
|
+
}
|
|
3650
|
+
return false;
|
|
3651
|
+
}
|
|
3652
|
+
if (!state.timingFunction && isAnimationTimingFunctionToken(lower)) {
|
|
3653
|
+
state.timingFunction = true;
|
|
3654
|
+
return true;
|
|
3655
|
+
}
|
|
3656
|
+
if (!state.iterationCount && isAnimationIterationCountToken(token, lower)) {
|
|
3657
|
+
state.iterationCount = true;
|
|
3658
|
+
return true;
|
|
3659
|
+
}
|
|
3660
|
+
if (!state.direction && animationDirectionKeywords.has(lower)) {
|
|
3661
|
+
state.direction = true;
|
|
3662
|
+
return true;
|
|
3663
|
+
}
|
|
3664
|
+
if (!state.fillMode && animationFillModeKeywords.has(lower)) {
|
|
3665
|
+
state.fillMode = true;
|
|
3666
|
+
return true;
|
|
3667
|
+
}
|
|
3668
|
+
if (!state.playState && animationPlayStateKeywords.has(lower)) {
|
|
3669
|
+
state.playState = true;
|
|
3670
|
+
return true;
|
|
3671
|
+
}
|
|
3672
|
+
if (animationGlobalKeywords.has(lower)) return true;
|
|
3673
|
+
return false;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
function isAnimationTimeToken(token) {
|
|
3677
|
+
return /^-?(?:\\d+|\\d*\\.\\d+)(?:ms|s)$/i.test(token);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
function isAnimationTimingFunctionToken(lowerToken) {
|
|
3681
|
+
return animationTimingFunctionKeywords.has(lowerToken) || lowerToken.includes("(");
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
function isAnimationIterationCountToken(token, lowerToken) {
|
|
3685
|
+
return lowerToken === "infinite" || /^-?(?:\\d+|\\d*\\.\\d+)$/.test(token);
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
const animationTimingFunctionKeywords = new Set([
|
|
3689
|
+
"ease",
|
|
3690
|
+
"ease-in",
|
|
3691
|
+
"ease-in-out",
|
|
3692
|
+
"ease-out",
|
|
3693
|
+
"linear",
|
|
3694
|
+
"step-end",
|
|
3695
|
+
"step-start",
|
|
3696
|
+
]);
|
|
3697
|
+
const animationDirectionKeywords = new Set(["normal", "reverse", "alternate", "alternate-reverse"]);
|
|
3698
|
+
const animationFillModeKeywords = new Set(["none", "forwards", "backwards", "both"]);
|
|
3699
|
+
const animationPlayStateKeywords = new Set(["running", "paused"]);
|
|
3700
|
+
const animationGlobalKeywords = new Set(["inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
3701
|
+
|
|
3702
|
+
function namespaceFontFamilyValue(value, fontFamilyNames) {
|
|
3703
|
+
if (fontFamilyNames.size === 0) return value;
|
|
3704
|
+
const important = splitCssImportantSuffix(value);
|
|
3705
|
+
return splitCssCommaList(important.value).map((family) => namespaceSingleFontFamily(family, fontFamilyNames)).join(",") + important.suffix;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
function namespaceFontShorthandValue(value, fontFamilyNames, assetName) {
|
|
3709
|
+
if (fontFamilyNames.size === 0) return value;
|
|
3710
|
+
const familyStart = findFontShorthandFamilyStart(value);
|
|
3711
|
+
if (familyStart === -1) {
|
|
3712
|
+
if (fontShorthandReferencesScopedFontFamily(value, fontFamilyNames)) {
|
|
3713
|
+
throw new Error('Unsupported font shorthand in CSS asset "' + assetName + '" cannot be isolated safely. Use font-family with font-style/font-weight/font-size longhands, or use a supported font shorthand with an explicit font-size before the family list.');
|
|
3714
|
+
}
|
|
3715
|
+
return value;
|
|
3716
|
+
}
|
|
3717
|
+
return value.slice(0, familyStart) + namespaceFontFamilyValue(value.slice(familyStart), fontFamilyNames);
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
function fontShorthandReferencesScopedFontFamily(value, fontFamilyNames) {
|
|
3721
|
+
const tokens = readCssValueTokens(value);
|
|
3722
|
+
return tokens.some((token) => fontFamilyNames.has(normalizeFontFamilyName(token.text)));
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
function splitCssImportantSuffix(value) {
|
|
3726
|
+
const match = /!\\s*important\\s*$/i.exec(value);
|
|
3727
|
+
if (!match) return { value, suffix: "" };
|
|
3728
|
+
return {
|
|
3729
|
+
value: value.slice(0, match.index),
|
|
3730
|
+
suffix: value.slice(match.index),
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function namespaceSingleFontFamily(family, fontFamilyNames) {
|
|
3735
|
+
const leadingWhitespace = family.match(/^\\s*/)?.[0] ?? "";
|
|
3736
|
+
const trailingWhitespace = family.match(/\\s*$/)?.[0] ?? "";
|
|
3737
|
+
const trimmed = family.trim();
|
|
3738
|
+
const normalized = normalizeFontFamilyName(trimmed);
|
|
3739
|
+
const scopedName = fontFamilyNames.get(normalized);
|
|
3740
|
+
if (!scopedName) return family;
|
|
3741
|
+
const quote = trimmed[0] === '"' || trimmed[0] === "'" ? trimmed[0] : "";
|
|
3742
|
+
return leadingWhitespace + (quote ? quote + scopedName + quote : scopedName) + trailingWhitespace;
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
function normalizeFontFamilyName(value) {
|
|
3746
|
+
const trimmed = value.trim();
|
|
3747
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
3748
|
+
return trimmed.slice(1, -1);
|
|
3749
|
+
}
|
|
3750
|
+
return trimmed;
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
function rewritePropertyHeader(header, customPropertyNames) {
|
|
3754
|
+
let nextHeader = header;
|
|
3755
|
+
for (const [name, scopedName] of customPropertyNames) {
|
|
3756
|
+
nextHeader = nextHeader.replace(new RegExp("(@property\\\\s+)" + escapeRegExp(name) + "(?=$|[\\\\s{])", "g"), (_match, prefix) => prefix + scopedName);
|
|
3757
|
+
}
|
|
3758
|
+
return nextHeader;
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
function rewriteLayerPrelude(prelude) {
|
|
3762
|
+
const match = /^(@layer\\b)([\\s\\S]*)$/i.exec(prelude);
|
|
3763
|
+
if (!match) return prelude;
|
|
3764
|
+
const layerList = match[2] ?? "";
|
|
3765
|
+
if (!layerList.trim()) return prelude;
|
|
3766
|
+
return match[1] + splitCssCommaList(layerList).map(namespaceLayerName).join(",");
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
function namespaceLayerName(layerName) {
|
|
3770
|
+
const leadingWhitespace = layerName.match(/^\\s*/)?.[0] ?? "";
|
|
3771
|
+
const trailingWhitespace = layerName.match(/\\s*$/)?.[0] ?? "";
|
|
3772
|
+
const trimmed = layerName.trim();
|
|
3773
|
+
return trimmed ? leadingWhitespace + widgetPackageLayerPrefix + trimmed + trailingWhitespace : layerName;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
function rewriteContainerAtRuleHeader(header) {
|
|
3777
|
+
if (!header.endsWith("{")) return header;
|
|
3778
|
+
return rewriteContainerAtRulePrelude(header.slice(0, -1)) + "{";
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
function rewriteContainerAtRulePrelude(prelude) {
|
|
3782
|
+
const match = /^(@container\\b)([\\s\\S]*)$/i.exec(prelude);
|
|
3783
|
+
if (!match) return prelude;
|
|
3784
|
+
const condition = match[2] ?? "";
|
|
3785
|
+
const leadingWhitespace = condition.match(/^\\s*/)?.[0] ?? "";
|
|
3786
|
+
const conditionBody = condition.slice(leadingWhitespace.length);
|
|
3787
|
+
if (!conditionBody || conditionBody.startsWith("(") || /^(?:style|scroll-state)\\s*\\(/.test(conditionBody)) {
|
|
3788
|
+
return prelude;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
const tokens = readCssValueTokens(conditionBody);
|
|
3792
|
+
const nameToken = tokens[0];
|
|
3793
|
+
if (!nameToken || nameToken.start !== 0 || isContainerQueryConditionKeyword(nameToken.text) || isReservedContainerName(nameToken.text) || nameToken.text.includes("(")) {
|
|
3794
|
+
return prelude;
|
|
3795
|
+
}
|
|
3796
|
+
return match[1] + leadingWhitespace + namespaceContainerName(nameToken.text) + conditionBody.slice(nameToken.end);
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
function namespaceContainerNameValue(value) {
|
|
3800
|
+
return replaceCssValueOutsideProtectedRanges(value, (chunk) => namespaceContainerNamesInChunk(chunk));
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
function namespaceContainerShorthandValue(value) {
|
|
3804
|
+
const slashIndex = findTopLevelSlash(value);
|
|
3805
|
+
if (slashIndex !== -1) {
|
|
3806
|
+
return namespaceContainerNameValue(value.slice(0, slashIndex)) + value.slice(slashIndex);
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
const trimmed = stripCssComments(value).trim().toLowerCase();
|
|
3810
|
+
if (isReservedContainerName(trimmed) || containerTypeKeywords.has(trimmed)) return value;
|
|
3811
|
+
return namespaceContainerNameValue(value);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
function namespaceContainerNamesInChunk(chunk) {
|
|
3815
|
+
return chunk.replace(/(^|[^A-Za-z0-9_-])([A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name) => {
|
|
3816
|
+
if (isReservedContainerName(name)) return prefix + name;
|
|
3817
|
+
return prefix + namespaceContainerName(name);
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
function namespaceContainerName(name) {
|
|
3822
|
+
return widgetPackageContainerPrefix + name;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
function isReservedContainerName(name) {
|
|
3826
|
+
return containerNameReservedKeywords.has(name.toLowerCase());
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
function isContainerQueryConditionKeyword(name) {
|
|
3830
|
+
return containerQueryConditionKeywords.has(name.toLowerCase());
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
const containerTypeKeywords = new Set(["normal", "size", "inline-size", "scroll-state"]);
|
|
3834
|
+
const containerQueryConditionKeywords = new Set(["not", "and", "or"]);
|
|
3835
|
+
const containerNameReservedKeywords = new Set(["none", "inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
3836
|
+
|
|
3837
|
+
function namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames) {
|
|
3838
|
+
const scopedName = customPropertyNames.get(propertyName);
|
|
3839
|
+
if (!scopedName) return rawProperty;
|
|
3840
|
+
return rawProperty.replace(new RegExp(escapeRegExp(propertyName) + "(?=\\\\s*$)"), scopedName);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
function namespaceCustomPropertyReferences(block, customPropertyNames) {
|
|
3844
|
+
return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => ({
|
|
3845
|
+
property: rawProperty,
|
|
3846
|
+
value: namespaceCustomPropertyReferencesValue(value, customPropertyNames),
|
|
3847
|
+
}));
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
function namespaceCustomPropertyReferencesValue(value, customPropertyNames) {
|
|
3851
|
+
if (customPropertyNames.size === 0) return value;
|
|
3852
|
+
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
3853
|
+
let nextChunk = chunk;
|
|
3854
|
+
for (const [name, scopedName] of customPropertyNames) {
|
|
3855
|
+
nextChunk = nextChunk.replace(new RegExp("(\\\\bvar\\\\(\\\\s*)" + escapeRegExp(name) + "(?=\\\\s*[,)])", "g"), (_match, prefix) => prefix + scopedName);
|
|
3856
|
+
}
|
|
3857
|
+
return nextChunk;
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
function namespaceCustomPropertyIdentifierValue(value, customPropertyNames) {
|
|
3862
|
+
if (customPropertyNames.size === 0) return value;
|
|
3863
|
+
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
3864
|
+
let nextChunk = chunk;
|
|
3865
|
+
for (const [name, scopedName] of customPropertyNames) {
|
|
3866
|
+
nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
|
|
3867
|
+
}
|
|
3868
|
+
return nextChunk;
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
function namespaceCssGlobalNameValue(value) {
|
|
3873
|
+
return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
|
|
3874
|
+
return chunk.replace(/(^|[^A-Za-z0-9_-])(--[A-Za-z0-9_-]+|[A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name, offset, source) => {
|
|
3875
|
+
const nextChar = source[offset + prefix.length + name.length] ?? "";
|
|
3876
|
+
const beforeName = source.slice(0, offset + prefix.length);
|
|
3877
|
+
if (nextChar === "(" || /(?:^|[^A-Za-z0-9_-])var\\s*\\($/i.test(beforeName) || cssGlobalNameReservedKeywords.has(name.toLowerCase())) return prefix + name;
|
|
3878
|
+
return prefix + widgetPackageCssGlobalNamePrefix + name;
|
|
3879
|
+
});
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
const cssGlobalNameReservedKeywords = new Set(["auto", "none", "inherit", "initial", "revert", "revert-layer", "unset"]);
|
|
3884
|
+
|
|
3885
|
+
function assertNoNestedCssBlock(block, assetName) {
|
|
3886
|
+
let index = 0;
|
|
3887
|
+
let quote = "";
|
|
3888
|
+
let parenDepth = 0;
|
|
3889
|
+
while (index < block.length) {
|
|
3890
|
+
const char = block[index];
|
|
3891
|
+
if (quote) {
|
|
3892
|
+
if (char === "\\\\") index += 2;
|
|
3893
|
+
else {
|
|
3894
|
+
if (char === quote) quote = "";
|
|
3895
|
+
index += 1;
|
|
3896
|
+
}
|
|
3897
|
+
continue;
|
|
3898
|
+
}
|
|
3899
|
+
if (cssStartsWithComment(block, index)) {
|
|
3900
|
+
const commentEnd = block.indexOf("*/", index + 2);
|
|
3901
|
+
index = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
3904
|
+
if (char === '"' || char === "'") {
|
|
3905
|
+
quote = char;
|
|
3906
|
+
index += 1;
|
|
3907
|
+
continue;
|
|
3908
|
+
}
|
|
3909
|
+
if (char === "(") parenDepth += 1;
|
|
3910
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
3911
|
+
else if (char === "{" && parenDepth === 0) {
|
|
3912
|
+
throw new Error('Unsupported nested CSS rule in CSS asset "' + assetName + '" cannot be isolated safely. Compile nested CSS before building the widget package.');
|
|
3913
|
+
}
|
|
3914
|
+
index += 1;
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
function rewriteDeclarationBlock(block, rewriteDeclaration) {
|
|
3919
|
+
let output = "";
|
|
3920
|
+
let declarationStart = 0;
|
|
3921
|
+
let index = 0;
|
|
3922
|
+
let quote = "";
|
|
3923
|
+
let parenDepth = 0;
|
|
3924
|
+
while (index < block.length) {
|
|
3925
|
+
const char = block[index];
|
|
3926
|
+
if (quote) {
|
|
3927
|
+
if (char === "\\\\") index += 2;
|
|
3928
|
+
else {
|
|
3929
|
+
if (char === quote) quote = "";
|
|
3930
|
+
index += 1;
|
|
3931
|
+
}
|
|
3932
|
+
continue;
|
|
3933
|
+
}
|
|
3934
|
+
if (cssStartsWithComment(block, index)) {
|
|
3935
|
+
const commentEnd = block.indexOf("*/", index + 2);
|
|
3936
|
+
index = commentEnd === -1 ? block.length : commentEnd + 2;
|
|
3937
|
+
continue;
|
|
3938
|
+
}
|
|
3939
|
+
if (char === '"' || char === "'") {
|
|
3940
|
+
quote = char;
|
|
3941
|
+
index += 1;
|
|
3942
|
+
continue;
|
|
3943
|
+
}
|
|
3944
|
+
if (char === "(") parenDepth += 1;
|
|
3945
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
3946
|
+
else if (char === ";" && parenDepth === 0) {
|
|
3947
|
+
output += rewriteDeclarationSegment(block.slice(declarationStart, index), rewriteDeclaration) + ";";
|
|
3948
|
+
declarationStart = index + 1;
|
|
3949
|
+
}
|
|
3950
|
+
index += 1;
|
|
3951
|
+
}
|
|
3952
|
+
return output + rewriteDeclarationSegment(block.slice(declarationStart), rewriteDeclaration);
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
function rewriteDeclarationSegment(segment, rewriteDeclaration) {
|
|
3956
|
+
const colonIndex = findDeclarationColon(segment);
|
|
3957
|
+
if (colonIndex === -1) return segment;
|
|
3958
|
+
const rawProperty = segment.slice(0, colonIndex);
|
|
3959
|
+
const rawPropertyName = stripCssComments(rawProperty).trim();
|
|
3960
|
+
const propertyName = rawPropertyName.startsWith("--") ? rawPropertyName : rawPropertyName.toLowerCase();
|
|
3961
|
+
if (!propertyName) return segment;
|
|
3962
|
+
const rewritten = rewriteDeclaration(propertyName, rawProperty, segment.slice(colonIndex + 1));
|
|
3963
|
+
return rewritten.property + ":" + rewritten.value;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
function findDeclarationColon(segment) {
|
|
3967
|
+
let index = 0;
|
|
3968
|
+
let quote = "";
|
|
3969
|
+
let parenDepth = 0;
|
|
3970
|
+
while (index < segment.length) {
|
|
3971
|
+
const char = segment[index];
|
|
3972
|
+
if (quote) {
|
|
3973
|
+
if (char === "\\\\") index += 2;
|
|
3974
|
+
else {
|
|
3975
|
+
if (char === quote) quote = "";
|
|
3976
|
+
index += 1;
|
|
3977
|
+
}
|
|
3978
|
+
continue;
|
|
3979
|
+
}
|
|
3980
|
+
if (cssStartsWithComment(segment, index)) {
|
|
3981
|
+
const commentEnd = segment.indexOf("*/", index + 2);
|
|
3982
|
+
index = commentEnd === -1 ? segment.length : commentEnd + 2;
|
|
3983
|
+
continue;
|
|
3984
|
+
}
|
|
3985
|
+
if (char === '"' || char === "'") {
|
|
3986
|
+
quote = char;
|
|
3987
|
+
index += 1;
|
|
3988
|
+
continue;
|
|
3989
|
+
}
|
|
3990
|
+
if (char === "(") parenDepth += 1;
|
|
3991
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
3992
|
+
else if (char === ":" && parenDepth === 0) return index;
|
|
3993
|
+
index += 1;
|
|
3994
|
+
}
|
|
3995
|
+
return -1;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
function replaceCssValueOutsideProtectedRanges(value, replaceChunk) {
|
|
3999
|
+
let output = "";
|
|
4000
|
+
let chunkStart = 0;
|
|
4001
|
+
let index = 0;
|
|
4002
|
+
while (index < value.length) {
|
|
4003
|
+
if (cssStartsWithComment(value, index)) {
|
|
4004
|
+
output += replaceChunk(value.slice(chunkStart, index));
|
|
4005
|
+
const commentEnd = value.indexOf("*/", index + 2);
|
|
4006
|
+
const nextIndex = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4007
|
+
output += value.slice(index, nextIndex);
|
|
4008
|
+
index = nextIndex;
|
|
4009
|
+
chunkStart = index;
|
|
4010
|
+
continue;
|
|
4011
|
+
}
|
|
4012
|
+
const char = value[index];
|
|
4013
|
+
if (char === '"' || char === "'") {
|
|
4014
|
+
output += replaceChunk(value.slice(chunkStart, index));
|
|
4015
|
+
const stringEnd = findCssStringEnd(value, index);
|
|
4016
|
+
output += value.slice(index, stringEnd);
|
|
4017
|
+
index = stringEnd;
|
|
4018
|
+
chunkStart = index;
|
|
4019
|
+
continue;
|
|
4020
|
+
}
|
|
4021
|
+
if (isCssUrlFunctionStart(value, index)) {
|
|
4022
|
+
output += replaceChunk(value.slice(chunkStart, index));
|
|
4023
|
+
const urlEnd = findCssFunctionEnd(value, value.indexOf("(", index));
|
|
4024
|
+
output += value.slice(index, urlEnd);
|
|
4025
|
+
index = urlEnd;
|
|
4026
|
+
chunkStart = index;
|
|
4027
|
+
continue;
|
|
4028
|
+
}
|
|
4029
|
+
index += 1;
|
|
4030
|
+
}
|
|
4031
|
+
return output + replaceChunk(value.slice(chunkStart));
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
function findCssStringEnd(value, quoteIndex) {
|
|
4035
|
+
const quote = value[quoteIndex];
|
|
4036
|
+
let index = quoteIndex + 1;
|
|
4037
|
+
while (index < value.length) {
|
|
4038
|
+
const char = value[index];
|
|
4039
|
+
if (char === "\\\\") index += 2;
|
|
4040
|
+
else {
|
|
4041
|
+
index += 1;
|
|
4042
|
+
if (char === quote) return index;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return value.length;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
function findCssFunctionEnd(value, openParenIndex) {
|
|
4049
|
+
let index = openParenIndex + 1;
|
|
4050
|
+
let depth = 1;
|
|
4051
|
+
let quote = "";
|
|
4052
|
+
while (index < value.length) {
|
|
4053
|
+
const char = value[index];
|
|
4054
|
+
if (quote) {
|
|
4055
|
+
if (char === "\\\\") index += 2;
|
|
4056
|
+
else {
|
|
4057
|
+
if (char === quote) quote = "";
|
|
4058
|
+
index += 1;
|
|
4059
|
+
}
|
|
4060
|
+
continue;
|
|
4061
|
+
}
|
|
4062
|
+
if (char === '"' || char === "'") {
|
|
4063
|
+
quote = char;
|
|
4064
|
+
index += 1;
|
|
4065
|
+
continue;
|
|
4066
|
+
}
|
|
4067
|
+
if (cssStartsWithComment(value, index)) {
|
|
4068
|
+
const commentEnd = value.indexOf("*/", index + 2);
|
|
4069
|
+
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4070
|
+
continue;
|
|
4071
|
+
}
|
|
4072
|
+
if (char === "(") depth += 1;
|
|
4073
|
+
else if (char === ")") {
|
|
4074
|
+
depth -= 1;
|
|
4075
|
+
if (depth === 0) return index + 1;
|
|
4076
|
+
}
|
|
4077
|
+
index += 1;
|
|
4078
|
+
}
|
|
4079
|
+
return value.length;
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
function findFontShorthandFamilyStart(value) {
|
|
4083
|
+
const tokens = readCssValueTokens(value);
|
|
4084
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
4085
|
+
const token = tokens[index];
|
|
4086
|
+
if (!token || !isFontSizeToken(token.text)) continue;
|
|
4087
|
+
if (findTopLevelSlash(token.text) !== -1) return token.end;
|
|
4088
|
+
const slashIndex = skipCssWhitespaceAndComments(value, token.end);
|
|
4089
|
+
if (value[slashIndex] !== "/") return token.end;
|
|
4090
|
+
const lineHeightStart = skipCssWhitespaceAndComments(value, slashIndex + 1);
|
|
4091
|
+
const lineHeightToken = tokens.find((candidate) => candidate.start >= lineHeightStart);
|
|
4092
|
+
return lineHeightToken ? lineHeightToken.end : token.end;
|
|
4093
|
+
}
|
|
4094
|
+
return -1;
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
function readCssValueTokens(value) {
|
|
4098
|
+
const tokens = [];
|
|
4099
|
+
let index = 0;
|
|
4100
|
+
while (index < value.length) {
|
|
4101
|
+
if (/\\s/.test(value[index] ?? "")) {
|
|
4102
|
+
index += 1;
|
|
4103
|
+
continue;
|
|
4104
|
+
}
|
|
4105
|
+
if (cssStartsWithComment(value, index)) {
|
|
4106
|
+
const commentEnd = value.indexOf("*/", index + 2);
|
|
4107
|
+
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4108
|
+
continue;
|
|
4109
|
+
}
|
|
4110
|
+
const start = index;
|
|
4111
|
+
const char = value[index];
|
|
4112
|
+
if (char === '"' || char === "'") {
|
|
4113
|
+
index = findCssStringEnd(value, index);
|
|
4114
|
+
} else {
|
|
4115
|
+
let parenDepth = 0;
|
|
4116
|
+
while (index < value.length) {
|
|
4117
|
+
if (cssStartsWithComment(value, index)) {
|
|
4118
|
+
if (parenDepth === 0) break;
|
|
4119
|
+
const commentEnd = value.indexOf("*/", index + 2);
|
|
4120
|
+
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4121
|
+
continue;
|
|
4122
|
+
}
|
|
4123
|
+
const tokenChar = value[index];
|
|
4124
|
+
if ((tokenChar === '"' || tokenChar === "'") && parenDepth > 0) {
|
|
4125
|
+
index = findCssStringEnd(value, index);
|
|
4126
|
+
continue;
|
|
4127
|
+
}
|
|
4128
|
+
if (tokenChar === "(") parenDepth += 1;
|
|
4129
|
+
else if (tokenChar === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4130
|
+
if (parenDepth === 0 && /\\s/.test(tokenChar ?? "")) break;
|
|
4131
|
+
index += 1;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
tokens.push({ start, end: index, text: value.slice(start, index) });
|
|
4135
|
+
}
|
|
4136
|
+
return tokens;
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
function isFontSizeToken(token) {
|
|
4140
|
+
const slashIndex = findTopLevelSlash(token);
|
|
4141
|
+
const sizeToken = slashIndex === -1 ? token : token.slice(0, slashIndex);
|
|
4142
|
+
return /^(?:xx-small|x-small|small|medium|large|x-large|xx-large|xxx-large|smaller|larger|math|(?:\\d+|\\d*\\.\\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|ch|ex|cap|ic|lh|rlh|cm|mm|q|in|pc|pt))$/i.test(sizeToken) || isFontSizeFunctionToken(sizeToken);
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
function isFontSizeFunctionToken(token) {
|
|
4146
|
+
return /^(?:calc|clamp|min|max|var|env|abs|sign|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp)\\(/i.test(token);
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
function findTopLevelSlash(value) {
|
|
4150
|
+
let index = 0;
|
|
4151
|
+
let quote = "";
|
|
4152
|
+
let parenDepth = 0;
|
|
4153
|
+
while (index < value.length) {
|
|
4154
|
+
const char = value[index];
|
|
4155
|
+
if (quote) {
|
|
4156
|
+
if (char === "\\\\") index += 2;
|
|
4157
|
+
else {
|
|
4158
|
+
if (char === quote) quote = "";
|
|
4159
|
+
index += 1;
|
|
4160
|
+
}
|
|
4161
|
+
continue;
|
|
4162
|
+
}
|
|
4163
|
+
if (char === '"' || char === "'") {
|
|
4164
|
+
quote = char;
|
|
4165
|
+
index += 1;
|
|
4166
|
+
continue;
|
|
4167
|
+
}
|
|
4168
|
+
if (char === "(") parenDepth += 1;
|
|
4169
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4170
|
+
else if (char === "/" && parenDepth === 0) return index;
|
|
4171
|
+
index += 1;
|
|
4172
|
+
}
|
|
4173
|
+
return -1;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
function skipCssWhitespaceAndComments(value, startIndex) {
|
|
4177
|
+
let index = startIndex;
|
|
4178
|
+
while (index < value.length) {
|
|
4179
|
+
if (/\\s/.test(value[index] ?? "")) {
|
|
4180
|
+
index += 1;
|
|
4181
|
+
continue;
|
|
4182
|
+
}
|
|
4183
|
+
if (cssStartsWithComment(value, index)) {
|
|
4184
|
+
const commentEnd = value.indexOf("*/", index + 2);
|
|
4185
|
+
index = commentEnd === -1 ? value.length : commentEnd + 2;
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
return index;
|
|
4189
|
+
}
|
|
4190
|
+
return index;
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
function cssStartsWithComment(value, index) {
|
|
4194
|
+
return value[index] === "/" && value[index + 1] === "*";
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
function isCssUrlFunctionStart(value, index) {
|
|
4198
|
+
return /^url\\s*\\(/i.test(value.slice(index)) && !/[A-Za-z0-9_-]/.test(value[index - 1] ?? "");
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
function stripCssComments(value) {
|
|
4202
|
+
return value.replace(/\\/\\*[\\s\\S]*?\\*\\//g, "");
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
function splitCssCommaList(value) {
|
|
4206
|
+
const parts = [];
|
|
4207
|
+
let current = "";
|
|
4208
|
+
let quote = "";
|
|
4209
|
+
let parenDepth = 0;
|
|
4210
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
4211
|
+
const char = value[index];
|
|
4212
|
+
if (quote) {
|
|
4213
|
+
current += char;
|
|
4214
|
+
if (char === "\\\\") {
|
|
4215
|
+
index += 1;
|
|
4216
|
+
current += value[index] ?? "";
|
|
4217
|
+
} else if (char === quote) quote = "";
|
|
4218
|
+
continue;
|
|
4219
|
+
}
|
|
4220
|
+
if (char === '"' || char === "'") {
|
|
4221
|
+
quote = char;
|
|
4222
|
+
current += char;
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
if (char === "(") parenDepth += 1;
|
|
4226
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
4227
|
+
if (char === "," && parenDepth === 0) {
|
|
4228
|
+
parts.push(current);
|
|
4229
|
+
current = "";
|
|
4230
|
+
} else current += char;
|
|
4231
|
+
}
|
|
4232
|
+
parts.push(current);
|
|
4233
|
+
return parts;
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
function escapeRegExp(value) {
|
|
4237
|
+
return value.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
function encodeCssIdentifier(value) {
|
|
4241
|
+
return Array.from(value, (char) => char.codePointAt(0)?.toString(16).padStart(2, "0") ?? "00").join("_");
|
|
4242
|
+
}
|
|
4243
|
+
|
|
3030
4244
|
return {
|
|
3031
4245
|
${configFileSource} publicDir: false,
|
|
3032
4246
|
resolve: {
|
|
3033
|
-
conditions: [
|
|
4247
|
+
conditions: [
|
|
4248
|
+
"fluid-widget-authoring",
|
|
4249
|
+
"module",
|
|
4250
|
+
"browser",
|
|
4251
|
+
"development|production",
|
|
4252
|
+
],
|
|
3034
4253
|
},
|
|
3035
|
-
plugins: [fluidWidgetBuildInvariantsPlugin()],
|
|
4254
|
+
plugins: [fluidWidgetBuildInvariantsPlugin(), fluidWidgetCssIsolationPlugin()],
|
|
3036
4255
|
build: createFluidWidgetBuildInvariants(),
|
|
3037
4256
|
};
|
|
3038
4257
|
})()`;
|