@knighted/css 1.0.0-alpha.4 → 1.0.0-rc.1

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/cjs/css.cjs CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_EXTENSIONS = void 0;
7
7
  exports.css = css;
8
8
  exports.cssWithMeta = cssWithMeta;
9
+ exports.compileVanillaModule = compileVanillaModule;
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const node_fs_1 = require("node:fs");
11
12
  const dependency_tree_1 = __importDefault(require("dependency-tree"));
@@ -114,7 +115,7 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
114
115
  case '.less':
115
116
  return compileLess(file.path, peerResolver);
116
117
  case '.css.ts':
117
- return compileVanillaExtract(file.path, cwd, peerResolver);
118
+ return (await compileVanillaModule(file.path, cwd, peerResolver)).css;
118
119
  default:
119
120
  return '';
120
121
  }
@@ -134,7 +135,7 @@ async function compileLess(filePath, peerResolver) {
134
135
  const result = await less.render(source, { filename: filePath });
135
136
  return result.css;
136
137
  }
137
- async function compileVanillaExtract(filePath, cwd, peerResolver) {
138
+ async function compileVanillaModule(filePath, cwd, peerResolver) {
138
139
  const mod = await optionalPeer('@vanilla-extract/integration', 'Vanilla Extract', peerResolver);
139
140
  const namespace = unwrapModuleNamespace(mod);
140
141
  const compileFn = namespace.compile;
@@ -174,7 +175,10 @@ async function compileVanillaExtract(filePath, cwd, peerResolver) {
174
175
  imports.push(virtualFile.source);
175
176
  }
176
177
  }
177
- return imports.join('\n');
178
+ return {
179
+ source,
180
+ css: imports.join('\n'),
181
+ };
178
182
  }
179
183
  const defaultPeerLoader = name => import(name);
180
184
  async function optionalPeer(name, label, loader) {
@@ -21,6 +21,10 @@ export interface CssOptions {
21
21
  resolver?: CssResolver;
22
22
  peerResolver?: PeerLoader;
23
23
  }
24
+ export interface VanillaCompileResult {
25
+ source: string;
26
+ css: string;
27
+ }
24
28
  /**
25
29
  * Extract and compile all CSS-like dependencies for a given module.
26
30
  */
@@ -30,4 +34,5 @@ export interface CssResult {
30
34
  }
31
35
  export declare function css(entry: string, options?: CssOptions): Promise<string>;
32
36
  export declare function cssWithMeta(entry: string, options?: CssOptions): Promise<CssResult>;
37
+ export declare function compileVanillaModule(filePath: string, cwd: string, peerResolver?: PeerLoader): Promise<VanillaCompileResult>;
33
38
  export {};
@@ -1,25 +1,165 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pitch = void 0;
3
4
  const css_js_1 = require("./css.cjs");
4
5
  const DEFAULT_EXPORT_NAME = 'knightedCss';
6
+ const COMBINED_QUERY_FLAG = 'combined';
5
7
  const loader = async function loader(source) {
6
- const rawOptions = (typeof this.getOptions === 'function' ? this.getOptions() : {});
7
- const cssOptions = rawOptions;
8
- const normalizedOptions = {
9
- ...cssOptions,
10
- cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
11
- };
12
- const { css, files } = await (0, css_js_1.cssWithMeta)(this.resourcePath, normalizedOptions);
13
- const uniqueFiles = new Set([this.resourcePath, ...files]);
14
- for (const file of uniqueFiles) {
15
- this.addDependency(file);
16
- }
17
- const input = typeof source === 'string' ? source : source.toString('utf8');
18
- const injection = `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
8
+ const { cssOptions, vanillaOptions } = resolveLoaderOptions(this);
9
+ const css = await extractCss(this, cssOptions);
10
+ const injection = buildInjection(css);
19
11
  const isStyleModule = this.resourcePath.endsWith('.css.ts');
20
- const output = isStyleModule
21
- ? `${injection}export default {};\n`
22
- : `${input}${injection}`;
23
- return output;
12
+ if (isStyleModule) {
13
+ const { source: compiledSource } = await (0, css_js_1.compileVanillaModule)(this.resourcePath, cssOptions.cwd ?? this.rootContext ?? process.cwd(), cssOptions.peerResolver);
14
+ const vanillaSource = maybeTransformVanillaModule(compiledSource, vanillaOptions);
15
+ return `${vanillaSource}${injection}`;
16
+ }
17
+ const input = toSourceString(source);
18
+ return `${input}${injection}`;
19
+ };
20
+ function transformVanillaModuleToEsm(source) {
21
+ const exportBlock = /__export\([^,]+,\s*{([\s\S]*?)}\);/m.exec(source);
22
+ if (!exportBlock) {
23
+ return source;
24
+ }
25
+ const names = exportBlock[1]
26
+ .split(',')
27
+ .map(part => part.trim())
28
+ .filter(Boolean)
29
+ .map(entry => entry.split(':')[0]?.trim())
30
+ .filter(Boolean);
31
+ let transformed = source.replace(/module\.exports\s*=\s*__toCommonJS\([^;]+;\n?/m, '');
32
+ transformed = transformed.replace(/0 && \(module\.exports = {[^}]+}\);?\n?/m, '');
33
+ if (names.length > 0) {
34
+ transformed = `${transformed}\nexport { ${names.join(', ')} };\n`;
35
+ }
36
+ return transformed;
37
+ }
38
+ function maybeTransformVanillaModule(source, options) {
39
+ if (!options?.transformToEsm) {
40
+ return source;
41
+ }
42
+ return transformVanillaModuleToEsm(source);
43
+ }
44
+ const pitch = function pitch() {
45
+ if (!hasCombinedQuery(this.resourceQuery)) {
46
+ return;
47
+ }
48
+ const request = buildProxyRequest(this);
49
+ const { cssOptions } = resolveLoaderOptions(this);
50
+ return extractCss(this, cssOptions).then(css => createCombinedModule(request, css));
24
51
  };
52
+ exports.pitch = pitch;
53
+ loader.pitch = exports.pitch;
25
54
  exports.default = loader;
55
+ function resolveLoaderOptions(ctx) {
56
+ const rawOptions = (typeof ctx.getOptions === 'function' ? ctx.getOptions() : {});
57
+ const { vanilla, ...rest } = rawOptions;
58
+ const cssOptions = {
59
+ ...rest,
60
+ cwd: rest.cwd ?? ctx.rootContext ?? process.cwd(),
61
+ };
62
+ return {
63
+ cssOptions,
64
+ vanillaOptions: vanilla,
65
+ };
66
+ }
67
+ async function extractCss(ctx, options) {
68
+ const { css, files } = await (0, css_js_1.cssWithMeta)(ctx.resourcePath, options);
69
+ const uniqueFiles = new Set([ctx.resourcePath, ...files]);
70
+ for (const file of uniqueFiles) {
71
+ ctx.addDependency(file);
72
+ }
73
+ return css;
74
+ }
75
+ function toSourceString(source) {
76
+ return typeof source === 'string' ? source : source.toString('utf8');
77
+ }
78
+ function buildInjection(css) {
79
+ return `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
80
+ }
81
+ function hasCombinedQuery(query) {
82
+ if (!query)
83
+ return false;
84
+ const trimmed = query.startsWith('?') ? query.slice(1) : query;
85
+ if (!trimmed)
86
+ return false;
87
+ return trimmed
88
+ .split('&')
89
+ .filter(Boolean)
90
+ .some(part => isQueryFlag(part, COMBINED_QUERY_FLAG));
91
+ }
92
+ function buildProxyRequest(ctx) {
93
+ const sanitizedQuery = buildSanitizedQuery(ctx.resourceQuery);
94
+ const rawRequest = getRawRequest(ctx);
95
+ if (rawRequest) {
96
+ const stripped = stripResourceQuery(rawRequest);
97
+ return `${stripped}${sanitizedQuery}`;
98
+ }
99
+ const request = `${ctx.resourcePath}${sanitizedQuery}`;
100
+ const context = ctx.context ?? ctx.rootContext ?? process.cwd();
101
+ if (ctx.utils && typeof ctx.utils.contextify === 'function') {
102
+ return ctx.utils.contextify(context, request);
103
+ }
104
+ return request;
105
+ }
106
+ function getRawRequest(ctx) {
107
+ const mod = ctx._module;
108
+ const request = mod?.rawRequest;
109
+ if (typeof request === 'string' && request.length > 0) {
110
+ return request;
111
+ }
112
+ return undefined;
113
+ }
114
+ function stripResourceQuery(request) {
115
+ const idx = request.indexOf('?');
116
+ return idx >= 0 ? request.slice(0, idx) : request;
117
+ }
118
+ function buildSanitizedQuery(query) {
119
+ if (!query)
120
+ return '';
121
+ const entries = splitQuery(query).filter(part => {
122
+ return !isQueryFlag(part, COMBINED_QUERY_FLAG) && !isQueryFlag(part, 'knighted-css');
123
+ });
124
+ return entries.length > 0 ? `?${entries.join('&')}` : '';
125
+ }
126
+ function splitQuery(query) {
127
+ const trimmed = query.startsWith('?') ? query.slice(1) : query;
128
+ if (!trimmed)
129
+ return [];
130
+ return trimmed.split('&').filter(Boolean);
131
+ }
132
+ function isQueryFlag(entry, flag) {
133
+ const [rawKey] = entry.split('=');
134
+ try {
135
+ return decodeURIComponent(rawKey) === flag;
136
+ }
137
+ catch {
138
+ return rawKey === flag;
139
+ }
140
+ }
141
+ function createCombinedModule(request, css) {
142
+ const requestLiteral = JSON.stringify(request);
143
+ const lines = [
144
+ `import * as __knightedModule from ${requestLiteral};`,
145
+ `export * from ${requestLiteral};`,
146
+ ];
147
+ if (shouldForwardDefaultExport(request)) {
148
+ lines.push(`const __knightedDefault =
149
+ typeof __knightedModule.default !== 'undefined'
150
+ ? __knightedModule.default
151
+ : __knightedModule;`, 'export default __knightedDefault;');
152
+ }
153
+ lines.push(buildInjection(css));
154
+ return lines.join('\n');
155
+ }
156
+ function shouldForwardDefaultExport(request) {
157
+ const [pathPart] = request.split('?');
158
+ if (!pathPart)
159
+ return true;
160
+ const lower = pathPart.toLowerCase();
161
+ if (lower.endsWith('.css.ts') || lower.endsWith('.css.js')) {
162
+ return false;
163
+ }
164
+ return true;
165
+ }
@@ -1,6 +1,14 @@
1
- import type { LoaderDefinitionFunction } from 'webpack';
1
+ import type { LoaderDefinitionFunction, PitchLoaderDefinitionFunction } from 'webpack';
2
2
  import { type CssOptions } from './css.cjs';
3
+ export type KnightedCssCombinedModule<TModule> = TModule & {
4
+ knightedCss: string;
5
+ };
6
+ export interface KnightedCssVanillaOptions {
7
+ transformToEsm?: boolean;
8
+ }
3
9
  export interface KnightedCssLoaderOptions extends CssOptions {
10
+ vanilla?: KnightedCssVanillaOptions;
4
11
  }
5
12
  declare const loader: LoaderDefinitionFunction<KnightedCssLoaderOptions>;
13
+ export declare const pitch: PitchLoaderDefinitionFunction<KnightedCssLoaderOptions>;
6
14
  export default loader;
package/dist/css.d.ts CHANGED
@@ -21,6 +21,10 @@ export interface CssOptions {
21
21
  resolver?: CssResolver;
22
22
  peerResolver?: PeerLoader;
23
23
  }
24
+ export interface VanillaCompileResult {
25
+ source: string;
26
+ css: string;
27
+ }
24
28
  /**
25
29
  * Extract and compile all CSS-like dependencies for a given module.
26
30
  */
@@ -30,4 +34,5 @@ export interface CssResult {
30
34
  }
31
35
  export declare function css(entry: string, options?: CssOptions): Promise<string>;
32
36
  export declare function cssWithMeta(entry: string, options?: CssOptions): Promise<CssResult>;
37
+ export declare function compileVanillaModule(filePath: string, cwd: string, peerResolver?: PeerLoader): Promise<VanillaCompileResult>;
33
38
  export {};
package/dist/css.js CHANGED
@@ -106,7 +106,7 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
106
106
  case '.less':
107
107
  return compileLess(file.path, peerResolver);
108
108
  case '.css.ts':
109
- return compileVanillaExtract(file.path, cwd, peerResolver);
109
+ return (await compileVanillaModule(file.path, cwd, peerResolver)).css;
110
110
  default:
111
111
  return '';
112
112
  }
@@ -126,7 +126,7 @@ async function compileLess(filePath, peerResolver) {
126
126
  const result = await less.render(source, { filename: filePath });
127
127
  return result.css;
128
128
  }
129
- async function compileVanillaExtract(filePath, cwd, peerResolver) {
129
+ export async function compileVanillaModule(filePath, cwd, peerResolver) {
130
130
  const mod = await optionalPeer('@vanilla-extract/integration', 'Vanilla Extract', peerResolver);
131
131
  const namespace = unwrapModuleNamespace(mod);
132
132
  const compileFn = namespace.compile;
@@ -166,7 +166,10 @@ async function compileVanillaExtract(filePath, cwd, peerResolver) {
166
166
  imports.push(virtualFile.source);
167
167
  }
168
168
  }
169
- return imports.join('\n');
169
+ return {
170
+ source,
171
+ css: imports.join('\n'),
172
+ };
170
173
  }
171
174
  const defaultPeerLoader = name => import(name);
172
175
  async function optionalPeer(name, label, loader) {
package/dist/loader.d.ts CHANGED
@@ -1,6 +1,14 @@
1
- import type { LoaderDefinitionFunction } from 'webpack';
1
+ import type { LoaderDefinitionFunction, PitchLoaderDefinitionFunction } from 'webpack';
2
2
  import { type CssOptions } from './css.js';
3
+ export type KnightedCssCombinedModule<TModule> = TModule & {
4
+ knightedCss: string;
5
+ };
6
+ export interface KnightedCssVanillaOptions {
7
+ transformToEsm?: boolean;
8
+ }
3
9
  export interface KnightedCssLoaderOptions extends CssOptions {
10
+ vanilla?: KnightedCssVanillaOptions;
4
11
  }
5
12
  declare const loader: LoaderDefinitionFunction<KnightedCssLoaderOptions>;
13
+ export declare const pitch: PitchLoaderDefinitionFunction<KnightedCssLoaderOptions>;
6
14
  export default loader;
package/dist/loader.js CHANGED
@@ -1,23 +1,161 @@
1
- import { cssWithMeta } from './css.js';
1
+ import { cssWithMeta, compileVanillaModule } from './css.js';
2
2
  const DEFAULT_EXPORT_NAME = 'knightedCss';
3
+ const COMBINED_QUERY_FLAG = 'combined';
3
4
  const loader = async function loader(source) {
4
- const rawOptions = (typeof this.getOptions === 'function' ? this.getOptions() : {});
5
- const cssOptions = rawOptions;
6
- const normalizedOptions = {
7
- ...cssOptions,
8
- cwd: cssOptions.cwd ?? this.rootContext ?? process.cwd(),
9
- };
10
- const { css, files } = await cssWithMeta(this.resourcePath, normalizedOptions);
11
- const uniqueFiles = new Set([this.resourcePath, ...files]);
12
- for (const file of uniqueFiles) {
13
- this.addDependency(file);
14
- }
15
- const input = typeof source === 'string' ? source : source.toString('utf8');
16
- const injection = `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
5
+ const { cssOptions, vanillaOptions } = resolveLoaderOptions(this);
6
+ const css = await extractCss(this, cssOptions);
7
+ const injection = buildInjection(css);
17
8
  const isStyleModule = this.resourcePath.endsWith('.css.ts');
18
- const output = isStyleModule
19
- ? `${injection}export default {};\n`
20
- : `${input}${injection}`;
21
- return output;
9
+ if (isStyleModule) {
10
+ const { source: compiledSource } = await compileVanillaModule(this.resourcePath, cssOptions.cwd ?? this.rootContext ?? process.cwd(), cssOptions.peerResolver);
11
+ const vanillaSource = maybeTransformVanillaModule(compiledSource, vanillaOptions);
12
+ return `${vanillaSource}${injection}`;
13
+ }
14
+ const input = toSourceString(source);
15
+ return `${input}${injection}`;
16
+ };
17
+ function transformVanillaModuleToEsm(source) {
18
+ const exportBlock = /__export\([^,]+,\s*{([\s\S]*?)}\);/m.exec(source);
19
+ if (!exportBlock) {
20
+ return source;
21
+ }
22
+ const names = exportBlock[1]
23
+ .split(',')
24
+ .map(part => part.trim())
25
+ .filter(Boolean)
26
+ .map(entry => entry.split(':')[0]?.trim())
27
+ .filter(Boolean);
28
+ let transformed = source.replace(/module\.exports\s*=\s*__toCommonJS\([^;]+;\n?/m, '');
29
+ transformed = transformed.replace(/0 && \(module\.exports = {[^}]+}\);?\n?/m, '');
30
+ if (names.length > 0) {
31
+ transformed = `${transformed}\nexport { ${names.join(', ')} };\n`;
32
+ }
33
+ return transformed;
34
+ }
35
+ function maybeTransformVanillaModule(source, options) {
36
+ if (!options?.transformToEsm) {
37
+ return source;
38
+ }
39
+ return transformVanillaModuleToEsm(source);
40
+ }
41
+ export const pitch = function pitch() {
42
+ if (!hasCombinedQuery(this.resourceQuery)) {
43
+ return;
44
+ }
45
+ const request = buildProxyRequest(this);
46
+ const { cssOptions } = resolveLoaderOptions(this);
47
+ return extractCss(this, cssOptions).then(css => createCombinedModule(request, css));
22
48
  };
49
+ loader.pitch = pitch;
23
50
  export default loader;
51
+ function resolveLoaderOptions(ctx) {
52
+ const rawOptions = (typeof ctx.getOptions === 'function' ? ctx.getOptions() : {});
53
+ const { vanilla, ...rest } = rawOptions;
54
+ const cssOptions = {
55
+ ...rest,
56
+ cwd: rest.cwd ?? ctx.rootContext ?? process.cwd(),
57
+ };
58
+ return {
59
+ cssOptions,
60
+ vanillaOptions: vanilla,
61
+ };
62
+ }
63
+ async function extractCss(ctx, options) {
64
+ const { css, files } = await cssWithMeta(ctx.resourcePath, options);
65
+ const uniqueFiles = new Set([ctx.resourcePath, ...files]);
66
+ for (const file of uniqueFiles) {
67
+ ctx.addDependency(file);
68
+ }
69
+ return css;
70
+ }
71
+ function toSourceString(source) {
72
+ return typeof source === 'string' ? source : source.toString('utf8');
73
+ }
74
+ function buildInjection(css) {
75
+ return `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`;
76
+ }
77
+ function hasCombinedQuery(query) {
78
+ if (!query)
79
+ return false;
80
+ const trimmed = query.startsWith('?') ? query.slice(1) : query;
81
+ if (!trimmed)
82
+ return false;
83
+ return trimmed
84
+ .split('&')
85
+ .filter(Boolean)
86
+ .some(part => isQueryFlag(part, COMBINED_QUERY_FLAG));
87
+ }
88
+ function buildProxyRequest(ctx) {
89
+ const sanitizedQuery = buildSanitizedQuery(ctx.resourceQuery);
90
+ const rawRequest = getRawRequest(ctx);
91
+ if (rawRequest) {
92
+ const stripped = stripResourceQuery(rawRequest);
93
+ return `${stripped}${sanitizedQuery}`;
94
+ }
95
+ const request = `${ctx.resourcePath}${sanitizedQuery}`;
96
+ const context = ctx.context ?? ctx.rootContext ?? process.cwd();
97
+ if (ctx.utils && typeof ctx.utils.contextify === 'function') {
98
+ return ctx.utils.contextify(context, request);
99
+ }
100
+ return request;
101
+ }
102
+ function getRawRequest(ctx) {
103
+ const mod = ctx._module;
104
+ const request = mod?.rawRequest;
105
+ if (typeof request === 'string' && request.length > 0) {
106
+ return request;
107
+ }
108
+ return undefined;
109
+ }
110
+ function stripResourceQuery(request) {
111
+ const idx = request.indexOf('?');
112
+ return idx >= 0 ? request.slice(0, idx) : request;
113
+ }
114
+ function buildSanitizedQuery(query) {
115
+ if (!query)
116
+ return '';
117
+ const entries = splitQuery(query).filter(part => {
118
+ return !isQueryFlag(part, COMBINED_QUERY_FLAG) && !isQueryFlag(part, 'knighted-css');
119
+ });
120
+ return entries.length > 0 ? `?${entries.join('&')}` : '';
121
+ }
122
+ function splitQuery(query) {
123
+ const trimmed = query.startsWith('?') ? query.slice(1) : query;
124
+ if (!trimmed)
125
+ return [];
126
+ return trimmed.split('&').filter(Boolean);
127
+ }
128
+ function isQueryFlag(entry, flag) {
129
+ const [rawKey] = entry.split('=');
130
+ try {
131
+ return decodeURIComponent(rawKey) === flag;
132
+ }
133
+ catch {
134
+ return rawKey === flag;
135
+ }
136
+ }
137
+ function createCombinedModule(request, css) {
138
+ const requestLiteral = JSON.stringify(request);
139
+ const lines = [
140
+ `import * as __knightedModule from ${requestLiteral};`,
141
+ `export * from ${requestLiteral};`,
142
+ ];
143
+ if (shouldForwardDefaultExport(request)) {
144
+ lines.push(`const __knightedDefault =
145
+ typeof __knightedModule.default !== 'undefined'
146
+ ? __knightedModule.default
147
+ : __knightedModule;`, 'export default __knightedDefault;');
148
+ }
149
+ lines.push(buildInjection(css));
150
+ return lines.join('\n');
151
+ }
152
+ function shouldForwardDefaultExport(request) {
153
+ const [pathPart] = request.split('?');
154
+ if (!pathPart)
155
+ return true;
156
+ const lower = pathPart.toLowerCase();
157
+ if (lower.endsWith('.css.ts') || lower.endsWith('.css.js')) {
158
+ return false;
159
+ }
160
+ return true;
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/css",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
5
5
  "type": "module",
6
6
  "main": "./dist/css.js",