@knighted/css 1.0.0-rc.0 → 1.0.0-rc.10

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.
Files changed (51) hide show
  1. package/bin/generate-types.js +31 -0
  2. package/dist/cjs/css.cjs +107 -25
  3. package/dist/cjs/css.d.cts +10 -6
  4. package/dist/cjs/generateTypes.cjs +636 -0
  5. package/dist/cjs/generateTypes.d.cts +104 -0
  6. package/dist/cjs/loader.cjs +128 -56
  7. package/dist/cjs/loader.d.cts +5 -0
  8. package/dist/cjs/loaderInternals.cjs +108 -0
  9. package/dist/cjs/loaderInternals.d.cts +23 -0
  10. package/dist/cjs/moduleGraph.cjs +431 -0
  11. package/dist/cjs/moduleGraph.d.cts +15 -0
  12. package/dist/cjs/moduleInfo.cjs +62 -0
  13. package/dist/cjs/moduleInfo.d.cts +10 -0
  14. package/dist/cjs/sassInternals.cjs +135 -0
  15. package/dist/cjs/sassInternals.d.cts +25 -0
  16. package/dist/cjs/stableNamespace.cjs +12 -0
  17. package/dist/cjs/stableNamespace.d.cts +3 -0
  18. package/dist/cjs/stableSelectors.cjs +44 -0
  19. package/dist/cjs/stableSelectors.d.cts +13 -0
  20. package/dist/cjs/stableSelectorsLiteral.cjs +104 -0
  21. package/dist/cjs/stableSelectorsLiteral.d.cts +19 -0
  22. package/dist/cjs/types.cjs +2 -0
  23. package/dist/cjs/types.d.cts +4 -0
  24. package/dist/css.d.ts +10 -6
  25. package/dist/css.js +107 -26
  26. package/dist/generateTypes.d.ts +104 -0
  27. package/dist/generateTypes.js +628 -0
  28. package/dist/loader.d.ts +5 -0
  29. package/dist/loader.js +127 -55
  30. package/dist/loaderInternals.d.ts +23 -0
  31. package/dist/loaderInternals.js +96 -0
  32. package/dist/moduleGraph.d.ts +15 -0
  33. package/dist/moduleGraph.js +425 -0
  34. package/dist/moduleInfo.d.ts +10 -0
  35. package/dist/moduleInfo.js +55 -0
  36. package/dist/sassInternals.d.ts +25 -0
  37. package/dist/sassInternals.js +124 -0
  38. package/dist/stableNamespace.d.ts +3 -0
  39. package/dist/stableNamespace.js +8 -0
  40. package/dist/stableSelectors.d.ts +13 -0
  41. package/dist/stableSelectors.js +36 -0
  42. package/dist/stableSelectorsLiteral.d.ts +19 -0
  43. package/dist/stableSelectorsLiteral.js +98 -0
  44. package/dist/types.d.ts +4 -0
  45. package/dist/types.js +1 -0
  46. package/loader-queries.d.ts +61 -0
  47. package/package.json +58 -8
  48. package/stable/_index.scss +57 -0
  49. package/stable/stable.css +15 -0
  50. package/types-stub/index.d.ts +5 -0
  51. package/types.d.ts +4 -0
package/dist/loader.js CHANGED
@@ -1,29 +1,103 @@
1
- import { cssWithMeta } from './css.js';
1
+ import { cssWithMeta, compileVanillaModule } from './css.js';
2
+ import { detectModuleDefaultExport } from './moduleInfo.js';
3
+ import { buildSanitizedQuery, hasCombinedQuery, hasNamedOnlyQueryFlag, hasQueryFlag, shouldEmitCombinedDefault, shouldForwardDefaultExport, TYPES_QUERY_FLAG, } from './loaderInternals.js';
4
+ import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js';
5
+ import { resolveStableNamespace } from './stableNamespace.js';
2
6
  const DEFAULT_EXPORT_NAME = 'knightedCss';
3
- const COMBINED_QUERY_FLAG = 'combined';
4
7
  const loader = async function loader(source) {
5
- const cssOptions = resolveCssOptions(this);
8
+ const { cssOptions, vanillaOptions, stableNamespace: optionNamespace, } = resolveLoaderOptions(this);
9
+ const resolvedNamespace = resolveStableNamespace(optionNamespace);
10
+ const typesRequested = hasQueryFlag(this.resourceQuery, TYPES_QUERY_FLAG);
6
11
  const css = await extractCss(this, cssOptions);
7
- const injection = buildInjection(css);
8
- const input = toSourceString(source);
12
+ const stableSelectorsLiteral = typesRequested
13
+ ? buildStableSelectorsLiteral({
14
+ css,
15
+ namespace: resolvedNamespace,
16
+ resourcePath: this.resourcePath,
17
+ emitWarning: message => emitKnightedWarning(this, message),
18
+ })
19
+ : undefined;
20
+ const injection = buildInjection(css, {
21
+ stableSelectorsLiteral: stableSelectorsLiteral?.literal,
22
+ });
9
23
  const isStyleModule = this.resourcePath.endsWith('.css.ts');
10
- return isStyleModule ? `${injection}export default {};\n` : `${input}${injection}`;
24
+ if (isStyleModule) {
25
+ const { source: compiledSource } = await compileVanillaModule(this.resourcePath, cssOptions.cwd ?? this.rootContext ?? process.cwd(), cssOptions.peerResolver);
26
+ const vanillaSource = maybeTransformVanillaModule(compiledSource, vanillaOptions);
27
+ return `${vanillaSource}${injection}`;
28
+ }
29
+ const input = toSourceString(source);
30
+ return `${input}${injection}`;
11
31
  };
32
+ function transformVanillaModuleToEsm(source) {
33
+ const exportBlock = /__export\([^,]+,\s*{([\s\S]*?)}\);/m.exec(source);
34
+ if (!exportBlock) {
35
+ return source;
36
+ }
37
+ const names = exportBlock[1]
38
+ .split(',')
39
+ .map(part => part.trim())
40
+ .filter(Boolean)
41
+ .map(entry => entry.split(':')[0]?.trim())
42
+ .filter(Boolean);
43
+ let transformed = source.replace(/module\.exports\s*=\s*__toCommonJS\([^;]+;\n?/m, '');
44
+ transformed = transformed.replace(/0 && \(module\.exports = {[^}]+}\);?\n?/m, '');
45
+ if (names.length > 0) {
46
+ transformed = `${transformed}\nexport { ${names.join(', ')} };\n`;
47
+ }
48
+ return transformed;
49
+ }
50
+ function maybeTransformVanillaModule(source, options) {
51
+ if (!options?.transformToEsm) {
52
+ return source;
53
+ }
54
+ return transformVanillaModuleToEsm(source);
55
+ }
12
56
  export const pitch = function pitch() {
13
57
  if (!hasCombinedQuery(this.resourceQuery)) {
14
58
  return;
15
59
  }
16
60
  const request = buildProxyRequest(this);
17
- const cssOptions = resolveCssOptions(this);
18
- return extractCss(this, cssOptions).then(css => createCombinedModule(request, css));
61
+ const { cssOptions, stableNamespace: optionNamespace } = resolveLoaderOptions(this);
62
+ const typesRequested = hasQueryFlag(this.resourceQuery, TYPES_QUERY_FLAG);
63
+ const resolvedNamespace = resolveStableNamespace(optionNamespace);
64
+ const skipSyntheticDefault = hasNamedOnlyQueryFlag(this.resourceQuery);
65
+ const defaultSignalPromise = skipSyntheticDefault
66
+ ? Promise.resolve('unknown')
67
+ : detectModuleDefaultExport(this.resourcePath);
68
+ return Promise.all([extractCss(this, cssOptions), defaultSignalPromise]).then(([css, defaultSignal]) => {
69
+ const emitDefault = shouldEmitCombinedDefault({
70
+ request,
71
+ skipSyntheticDefault,
72
+ detection: defaultSignal,
73
+ });
74
+ const stableSelectorsLiteral = typesRequested
75
+ ? buildStableSelectorsLiteral({
76
+ css,
77
+ namespace: resolvedNamespace,
78
+ resourcePath: this.resourcePath,
79
+ emitWarning: message => emitKnightedWarning(this, message),
80
+ })
81
+ : undefined;
82
+ return createCombinedModule(request, css, {
83
+ emitDefault,
84
+ stableSelectorsLiteral: stableSelectorsLiteral?.literal,
85
+ });
86
+ });
19
87
  };
20
88
  loader.pitch = pitch;
21
89
  export default loader;
22
- function resolveCssOptions(ctx) {
90
+ function resolveLoaderOptions(ctx) {
23
91
  const rawOptions = (typeof ctx.getOptions === 'function' ? ctx.getOptions() : {});
92
+ const { vanilla, stableNamespace, ...rest } = rawOptions;
93
+ const cssOptions = {
94
+ ...rest,
95
+ cwd: rest.cwd ?? ctx.rootContext ?? process.cwd(),
96
+ };
24
97
  return {
25
- ...rawOptions,
26
- cwd: rawOptions.cwd ?? ctx.rootContext ?? process.cwd(),
98
+ cssOptions,
99
+ vanillaOptions: vanilla,
100
+ stableNamespace,
27
101
  };
28
102
  }
29
103
  async function extractCss(ctx, options) {
@@ -37,22 +111,20 @@ async function extractCss(ctx, options) {
37
111
  function toSourceString(source) {
38
112
  return typeof source === 'string' ? source : source.toString('utf8');
39
113
  }
40
- function buildInjection(css) {
41
- return `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
42
- }
43
- function hasCombinedQuery(query) {
44
- if (!query)
45
- return false;
46
- const trimmed = query.startsWith('?') ? query.slice(1) : query;
47
- if (!trimmed)
48
- return false;
49
- return trimmed
50
- .split('&')
51
- .filter(Boolean)
52
- .some(part => isQueryFlag(part, COMBINED_QUERY_FLAG));
114
+ function buildInjection(css, extras) {
115
+ const lines = [`\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`];
116
+ if (extras?.stableSelectorsLiteral) {
117
+ lines.push(extras.stableSelectorsLiteral);
118
+ }
119
+ return lines.join('');
53
120
  }
54
121
  function buildProxyRequest(ctx) {
55
122
  const sanitizedQuery = buildSanitizedQuery(ctx.resourceQuery);
123
+ const rawRequest = getRawRequest(ctx);
124
+ if (rawRequest) {
125
+ const stripped = stripResourceQuery(rawRequest);
126
+ return `${stripped}${sanitizedQuery}`;
127
+ }
56
128
  const request = `${ctx.resourcePath}${sanitizedQuery}`;
57
129
  const context = ctx.context ?? ctx.rootContext ?? process.cwd();
58
130
  if (ctx.utils && typeof ctx.utils.contextify === 'function') {
@@ -60,40 +132,40 @@ function buildProxyRequest(ctx) {
60
132
  }
61
133
  return request;
62
134
  }
63
- function buildSanitizedQuery(query) {
64
- if (!query)
65
- return '';
66
- const entries = splitQuery(query).filter(part => {
67
- return !isQueryFlag(part, COMBINED_QUERY_FLAG) && !isQueryFlag(part, 'knighted-css');
68
- });
69
- return entries.length > 0 ? `?${entries.join('&')}` : '';
70
- }
71
- function splitQuery(query) {
72
- const trimmed = query.startsWith('?') ? query.slice(1) : query;
73
- if (!trimmed)
74
- return [];
75
- return trimmed.split('&').filter(Boolean);
76
- }
77
- function isQueryFlag(entry, flag) {
78
- const [rawKey] = entry.split('=');
79
- try {
80
- return decodeURIComponent(rawKey) === flag;
81
- }
82
- catch {
83
- return rawKey === flag;
135
+ function getRawRequest(ctx) {
136
+ const mod = ctx._module;
137
+ const request = mod?.rawRequest;
138
+ if (typeof request === 'string' && request.length > 0) {
139
+ return request;
84
140
  }
141
+ return undefined;
142
+ }
143
+ function stripResourceQuery(request) {
144
+ const idx = request.indexOf('?');
145
+ return idx >= 0 ? request.slice(0, idx) : request;
85
146
  }
86
- function createCombinedModule(request, css) {
147
+ function createCombinedModule(request, css, options) {
148
+ const shouldEmitDefault = options?.emitDefault ?? shouldForwardDefaultExport(request);
87
149
  const requestLiteral = JSON.stringify(request);
88
- const defaultExport = `const __knightedDefault =
89
- typeof __knightedModule.default !== 'undefined'
90
- ? __knightedModule.default
91
- : __knightedModule;`;
92
- return [
150
+ const lines = [
93
151
  `import * as __knightedModule from ${requestLiteral};`,
94
152
  `export * from ${requestLiteral};`,
95
- defaultExport,
96
- 'export default __knightedDefault;',
97
- buildInjection(css),
98
- ].join('\n');
153
+ ];
154
+ if (shouldEmitDefault) {
155
+ lines.push(`const __knightedDefault =
156
+ typeof __knightedModule.default !== 'undefined'
157
+ ? __knightedModule.default
158
+ : __knightedModule;`, 'export default __knightedDefault;');
159
+ }
160
+ lines.push(buildInjection(css, { stableSelectorsLiteral: options?.stableSelectorsLiteral }));
161
+ return lines.join('\n');
162
+ }
163
+ function emitKnightedWarning(ctx, message) {
164
+ const formatted = `\x1b[33m@knighted/css warning\x1b[0m ${message}`;
165
+ if (typeof ctx.emitWarning === 'function') {
166
+ ctx.emitWarning(new Error(formatted));
167
+ return;
168
+ }
169
+ // eslint-disable-next-line no-console
170
+ console.warn(formatted);
99
171
  }
@@ -0,0 +1,23 @@
1
+ import type { ModuleDefaultSignal } from './moduleInfo.js';
2
+ export declare const COMBINED_QUERY_FLAG = "combined";
3
+ export declare const TYPES_QUERY_FLAG = "types";
4
+ export declare const NAMED_ONLY_QUERY_FLAGS: readonly ["named-only", "no-default"];
5
+ export type SelectorTypeVariant = 'types' | 'combined' | 'combinedWithoutDefault';
6
+ export declare function splitQuery(query: string): string[];
7
+ export declare function isQueryFlag(entry: string, flag: string): boolean;
8
+ export declare function buildSanitizedQuery(query?: string | null): string;
9
+ export declare function hasQueryFlag(query: string | null | undefined, flag: string): boolean;
10
+ export declare function shouldForwardDefaultExport(request: string): boolean;
11
+ export declare function hasCombinedQuery(query?: string | null): boolean;
12
+ export declare function hasNamedOnlyQueryFlag(query?: string | null): boolean;
13
+ export declare function determineSelectorVariant(query?: string | null): SelectorTypeVariant;
14
+ export declare function shouldEmitCombinedDefault(options: {
15
+ detection: ModuleDefaultSignal;
16
+ request: string;
17
+ skipSyntheticDefault: boolean;
18
+ }): boolean;
19
+ export declare const __loaderInternals: {
20
+ buildSanitizedQuery: typeof buildSanitizedQuery;
21
+ shouldEmitCombinedDefault: typeof shouldEmitCombinedDefault;
22
+ determineSelectorVariant: typeof determineSelectorVariant;
23
+ };
@@ -0,0 +1,96 @@
1
+ export const COMBINED_QUERY_FLAG = 'combined';
2
+ export const TYPES_QUERY_FLAG = 'types';
3
+ export const NAMED_ONLY_QUERY_FLAGS = ['named-only', 'no-default'];
4
+ export function splitQuery(query) {
5
+ const trimmed = query.startsWith('?') ? query.slice(1) : query;
6
+ if (!trimmed)
7
+ return [];
8
+ return trimmed.split('&').filter(Boolean);
9
+ }
10
+ export function isQueryFlag(entry, flag) {
11
+ const [rawKey] = entry.split('=');
12
+ try {
13
+ return decodeURIComponent(rawKey) === flag;
14
+ }
15
+ catch {
16
+ return rawKey === flag;
17
+ }
18
+ }
19
+ export function buildSanitizedQuery(query) {
20
+ if (!query)
21
+ return '';
22
+ const entries = splitQuery(query).filter(part => {
23
+ if (isQueryFlag(part, COMBINED_QUERY_FLAG)) {
24
+ return false;
25
+ }
26
+ if (isQueryFlag(part, 'knighted-css')) {
27
+ return false;
28
+ }
29
+ if (isQueryFlag(part, TYPES_QUERY_FLAG)) {
30
+ return false;
31
+ }
32
+ if (NAMED_ONLY_QUERY_FLAGS.some(flag => isQueryFlag(part, flag))) {
33
+ return false;
34
+ }
35
+ return true;
36
+ });
37
+ return entries.length > 0 ? `?${entries.join('&')}` : '';
38
+ }
39
+ export function hasQueryFlag(query, flag) {
40
+ if (!query)
41
+ return false;
42
+ const entries = splitQuery(query);
43
+ if (entries.length === 0)
44
+ return false;
45
+ return entries.some(part => isQueryFlag(part, flag));
46
+ }
47
+ function safeDecode(value) {
48
+ try {
49
+ return decodeURIComponent(value);
50
+ }
51
+ catch {
52
+ return value;
53
+ }
54
+ }
55
+ export function shouldForwardDefaultExport(request) {
56
+ const [pathPart] = request.split('?');
57
+ if (!pathPart)
58
+ return true;
59
+ const lower = pathPart.toLowerCase();
60
+ if (lower.endsWith('.css.ts') || lower.endsWith('.css.js')) {
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+ export function hasCombinedQuery(query) {
66
+ return hasQueryFlag(query, COMBINED_QUERY_FLAG);
67
+ }
68
+ export function hasNamedOnlyQueryFlag(query) {
69
+ return NAMED_ONLY_QUERY_FLAGS.some(flag => hasQueryFlag(query, flag));
70
+ }
71
+ export function determineSelectorVariant(query) {
72
+ if (hasCombinedQuery(query)) {
73
+ return hasNamedOnlyQueryFlag(query) ? 'combinedWithoutDefault' : 'combined';
74
+ }
75
+ return 'types';
76
+ }
77
+ export function shouldEmitCombinedDefault(options) {
78
+ if (options.skipSyntheticDefault) {
79
+ return false;
80
+ }
81
+ if (!shouldForwardDefaultExport(options.request)) {
82
+ return false;
83
+ }
84
+ if (options.detection === 'has-default') {
85
+ return true;
86
+ }
87
+ if (options.detection === 'no-default') {
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+ export const __loaderInternals = {
93
+ buildSanitizedQuery,
94
+ shouldEmitCombinedDefault,
95
+ determineSelectorVariant,
96
+ };
@@ -0,0 +1,15 @@
1
+ import type { CssResolver } from './types.js';
2
+ export interface ModuleGraphOptions {
3
+ tsConfig?: string | Record<string, unknown>;
4
+ extensions?: string[];
5
+ conditions?: string[];
6
+ }
7
+ interface CollectOptions {
8
+ cwd: string;
9
+ styleExtensions: string[];
10
+ filter: (filePath: string) => boolean;
11
+ resolver?: CssResolver;
12
+ graphOptions?: ModuleGraphOptions;
13
+ }
14
+ export declare function collectStyleImports(entryPath: string, options: CollectOptions): Promise<string[]>;
15
+ export {};