@knighted/css 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -0
- package/dist/cjs/generateTypes.cjs +0 -1
- package/dist/cjs/loader.cjs +51 -6
- package/dist/cjs/loaderInternals.cjs +0 -8
- package/dist/generateTypes.js +0 -1
- package/dist/loader.js +48 -6
- package/dist/loaderInternals.js +0 -8
- package/package.json +2 -3
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# [`@knighted/css`](../../README.md)
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://codecov.io/gh/knightedcodemonkey/css)
|
|
5
|
+
[](https://www.npmjs.com/package/@knighted/css)
|
|
6
|
+
|
|
7
|
+
`@knighted/css` walks your JavaScript/TypeScript module graph, compiles every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), and ships both the concatenated stylesheet string and optional `.knighted-css.*` imports that keep selectors typed. Use it when you need fully materialized styles ahead of runtime—Shadow DOM surfaces, server-rendered routes, static site builds, or any entry point that should inline CSS without spinning up a full bundler.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
I needed a single source of truth for UI components that could drop into both light DOM pages and Shadow DOM hosts, without losing encapsulated styling in the latter.
|
|
12
|
+
|
|
13
|
+
## Quick Links
|
|
14
|
+
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Requirements](#requirements)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Quick Start](#quick-start)
|
|
19
|
+
- [API](#api)
|
|
20
|
+
- [Entry points (`import`)](#entry-points-at-a-glance)
|
|
21
|
+
- [Examples](#examples)
|
|
22
|
+
- [Demo](#demo)
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- Traverses module graphs with a built-in walker to find transitive style imports (no bundler required).
|
|
27
|
+
- Resolution parity via [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver): tsconfig `paths`, package `exports` + `imports`, and extension aliasing (e.g., `.css.js` → `.css.ts`) are honored without wiring up a bundler.
|
|
28
|
+
- Compiles `*.css`, `*.scss`, `*.sass`, `*.less`, and `*.css.ts` (vanilla-extract) files out of the box.
|
|
29
|
+
- Optional post-processing via [`lightningcss`](https://github.com/parcel-bundler/lightningcss) for minification, prefixing, media query optimizations, or specificity boosts.
|
|
30
|
+
- Pluggable resolver/filter hooks for custom module resolution (e.g., Rspack/Vite/webpack aliases) or selective inclusion.
|
|
31
|
+
- First-class loader (`@knighted/css/loader`) so bundlers can import compiled CSS alongside their modules via `?knighted-css`.
|
|
32
|
+
- Built-in type generation CLI (`knighted-css-generate-types`) that emits `.knighted-css.*` selector manifests so TypeScript gets literal tokens in lockstep with the loader exports.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Node.js `>= 22.17.0`
|
|
37
|
+
- npm `>= 10.9.0`
|
|
38
|
+
- Install peer toolchains you intend to use (`sass`, `less`, `@vanilla-extract/integration`, etc.).
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @knighted/css
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Install the peers your project is using, for example `less`, or `sass`, etc.
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// scripts/extract-styles.ts
|
|
52
|
+
import { css } from '@knighted/css'
|
|
53
|
+
|
|
54
|
+
const styles = await css('./src/components/app.ts', {
|
|
55
|
+
cwd: process.cwd(),
|
|
56
|
+
lightningcss: { minify: true },
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
console.log(styles)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run it with `tsx`/`node` and you will see a fully inlined stylesheet for `app.ts` and every style import it references, regardless of depth.
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
type CssOptions = {
|
|
68
|
+
extensions?: string[] // customize file extensions to scan
|
|
69
|
+
cwd?: string // working directory (defaults to process.cwd())
|
|
70
|
+
filter?: (filePath: string) => boolean
|
|
71
|
+
lightningcss?: boolean | LightningTransformOptions
|
|
72
|
+
specificityBoost?: {
|
|
73
|
+
visitor?: LightningTransformOptions<never>['visitor']
|
|
74
|
+
strategy?: SpecificityStrategy
|
|
75
|
+
match?: SpecificitySelector[]
|
|
76
|
+
}
|
|
77
|
+
moduleGraph?: ModuleGraphOptions
|
|
78
|
+
resolver?: (
|
|
79
|
+
specifier: string,
|
|
80
|
+
ctx: { cwd: string; from?: string },
|
|
81
|
+
) => string | Promise<string | undefined>
|
|
82
|
+
peerResolver?: (name: string) => Promise<unknown> // for custom module loading
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function css(entry: string, options?: CssOptions): Promise<string>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Entry points at a glance
|
|
89
|
+
|
|
90
|
+
### Runtime loader hook (`?knighted-css`)
|
|
91
|
+
|
|
92
|
+
Import any module with the `?knighted-css` query to receive the compiled stylesheet string:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { knightedCss } from './button.js?knighted-css'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [docs/loader.md](../../docs/loader.md) for the full configuration, combined imports, and `&types` runtime selector map guidance.
|
|
99
|
+
|
|
100
|
+
### Type generation hook (`*.knighted-css*`)
|
|
101
|
+
|
|
102
|
+
Run `knighted-css-generate-types` so every specifier that ends with `.knighted-css` produces a sibling manifest containing literal selector tokens:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import stableSelectors from './button.module.scss.knighted-css.js'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Refer to [docs/type-generation.md](../../docs/type-generation.md) for CLI options and workflow tips.
|
|
109
|
+
|
|
110
|
+
### Combined + runtime selectors
|
|
111
|
+
|
|
112
|
+
Need the module exports, `knightedCss`, and a runtime `stableSelectors` map from one import? Use `?knighted-css&combined&types` (plus optional `&named-only`). Example:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { asKnightedCssCombinedModule } from '@knighted/css/loader-helpers'
|
|
116
|
+
import type { KnightedCssStableSelectors as ButtonStableSelectors } from './button.css.knighted-css.js'
|
|
117
|
+
import * as buttonModule from './button.js?knighted-css&combined&types'
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
default: Button,
|
|
121
|
+
knightedCss,
|
|
122
|
+
stableSelectors,
|
|
123
|
+
} = asKnightedCssCombinedModule<
|
|
124
|
+
typeof import('./button.js'),
|
|
125
|
+
{ stableSelectors: Readonly<Record<keyof ButtonStableSelectors, string>> }
|
|
126
|
+
>(buttonModule)
|
|
127
|
+
|
|
128
|
+
stableSelectors.shell
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
> [!NOTE]
|
|
132
|
+
> `stableSelectors` here is for runtime use; TypeScript still reads literal tokens from the generated `.knighted-css.*` modules. For a full decision matrix, see [docs/combined-queries.md](../../docs/combined-queries.md).
|
|
133
|
+
> Prefer importing `asKnightedCssCombinedModule` from `@knighted/css/loader-helpers` instead of grabbing it from `@knighted/css/loader`—the helper lives in a Node-free chunk so both browser and server bundles stay happy.
|
|
134
|
+
|
|
135
|
+
## Examples
|
|
136
|
+
|
|
137
|
+
- [Generate standalone stylesheets](#generate-standalone-stylesheets)
|
|
138
|
+
- [Inline CSS during SSR](#inline-css-during-ssr)
|
|
139
|
+
- [Custom resolver](#custom-resolver-enhanced-resolve-example)
|
|
140
|
+
- [Specificity boost](#specificity-boost)
|
|
141
|
+
- [Bundler loader](../../docs/loader.md#loader-example)
|
|
142
|
+
|
|
143
|
+
### Generate standalone stylesheets
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { writeFile } from 'node:fs/promises'
|
|
147
|
+
import { css } from '@knighted/css'
|
|
148
|
+
|
|
149
|
+
// Build-time script that gathers all CSS imported by a React route
|
|
150
|
+
const sheet = await css('./src/routes/marketing-page.tsx', {
|
|
151
|
+
lightningcss: { minify: true, targets: { chrome: 120, safari: 17 } },
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await writeFile('./dist/marketing-page.css', sheet)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Inline CSS during SSR
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { renderToString } from 'react-dom/server'
|
|
161
|
+
import { css } from '@knighted/css'
|
|
162
|
+
|
|
163
|
+
export async function render(url: string) {
|
|
164
|
+
const styles = await css('./src/routes/root.tsx')
|
|
165
|
+
const html = renderToString(<App url={url} />)
|
|
166
|
+
return `<!doctype html><style>${styles}</style>${html}`
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Custom resolver (enhanced-resolve example)
|
|
171
|
+
|
|
172
|
+
The built-in walker already leans on [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver), so tsconfig `paths`, package `exports` conditions, and common extension aliases work out of the box. If you still need to mirror bespoke behavior (virtual modules, framework-specific loaders, etc.), plug in a custom resolver. Here’s how to use [`enhanced-resolve`](https://github.com/webpack/enhanced-resolve):
|
|
173
|
+
|
|
174
|
+
> [!TIP]
|
|
175
|
+
> Hash-prefixed specifiers defined in `package.json#imports` resolve automatically—no extra loader or `css()` options required. Reach for a custom resolver only when you need behavior beyond what `oxc-resolver` already mirrors.
|
|
176
|
+
|
|
177
|
+
> [!NOTE]
|
|
178
|
+
> Sass-specific prefixes such as `pkg:#button` live outside Node’s resolver and still need a shim. See [docs/sass-import-aliases.md](../../docs/sass-import-aliases.md) for a drop-in helper that strips those markers before `@knighted/css` walks the graph.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { ResolverFactory } from 'enhanced-resolve'
|
|
182
|
+
import { css } from '@knighted/css'
|
|
183
|
+
|
|
184
|
+
const resolver = ResolverFactory.createResolver({
|
|
185
|
+
extensions: ['.ts', '.tsx', '.js'],
|
|
186
|
+
mainFiles: ['index'],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
async function resolveWithEnhanced(id: string, cwd: string): Promise<string | undefined> {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
resolver.resolve({}, cwd, id, {}, (err, result) => {
|
|
192
|
+
if (err) return reject(err)
|
|
193
|
+
resolve(result ?? undefined)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const styles = await css('./src/routes/page.tsx', {
|
|
199
|
+
resolver: (specifier, { cwd }) => resolveWithEnhanced(specifier, cwd),
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This keeps `@knighted/css` resolution in sync with your bundler’s alias/extension rules.
|
|
204
|
+
|
|
205
|
+
### Specificity boost
|
|
206
|
+
|
|
207
|
+
Use `specificityBoost` to tweak selector behavior:
|
|
208
|
+
|
|
209
|
+
- **Strategies (built-in)**:
|
|
210
|
+
- `repeat-class` duplicates the last class in matching selectors to raise specificity (useful when you need a real specificity bump).
|
|
211
|
+
- `append-where` appends `:where(.token)` (zero specificity) for a harmless, order-based tie-breaker without changing matching.
|
|
212
|
+
- **Custom visitor**: Supply your own Lightning CSS visitor via `specificityBoost.visitor` for full control.
|
|
213
|
+
- **match filtering**: Provide `match: (string | RegExp)[]` to target selectors. Matches are OR’d; if any entry matches, the strategy applies. If omitted/empty, all selectors are eligible.
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { css } from '@knighted/css'
|
|
219
|
+
|
|
220
|
+
const styles = await css('./src/entry.ts', {
|
|
221
|
+
lightningcss: { minify: true },
|
|
222
|
+
specificityBoost: {
|
|
223
|
+
match: ['.card', /^\.btn/], // OR match
|
|
224
|
+
strategy: { type: 'repeat-class', times: 1 },
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
If you omit `match`, the strategy applies to all selectors. Use `append-where` when you don’t want to change specificity; use `repeat-class` when you do.
|
|
230
|
+
|
|
231
|
+
> [!NOTE]
|
|
232
|
+
> For the built-in strategies, the last class in a matching selector is the one that gets duplicated/appended. If you have multiple similar classes, tighten your `match` (string or RegExp) to target exactly the selector you want boosted.
|
|
233
|
+
|
|
234
|
+
> [!TIP]
|
|
235
|
+
> See [docs/specificity-boost-visitor.md](../../docs/specificity-boost-visitor.md) for a concrete visitor example.
|
|
236
|
+
|
|
237
|
+
## Demo
|
|
238
|
+
|
|
239
|
+
Want to see everything wired together? Check the full demo app at [css-jsx-app](https://github.com/morganney/css-jsx-app).
|
|
240
|
+
|
|
241
|
+
> [!TIP]
|
|
242
|
+
> This repo also includes a [playwright workspace](../playwright/src/lit-react/lit-host.ts) which serves as an end-to-end demo.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT © Knighted Code Monkey
|
package/dist/cjs/loader.cjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.pitch = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
4
8
|
const css_js_1 = require("./css.cjs");
|
|
5
9
|
const moduleInfo_js_1 = require("./moduleInfo.cjs");
|
|
6
10
|
const loaderInternals_js_1 = require("./loaderInternals.cjs");
|
|
@@ -128,15 +132,20 @@ function buildProxyRequest(ctx) {
|
|
|
128
132
|
const sanitizedQuery = (0, loaderInternals_js_1.buildSanitizedQuery)(ctx.resourceQuery);
|
|
129
133
|
const rawRequest = getRawRequest(ctx);
|
|
130
134
|
if (rawRequest) {
|
|
131
|
-
|
|
132
|
-
return `${stripped}${sanitizedQuery}`;
|
|
135
|
+
return rebuildProxyRequestFromRaw(ctx, rawRequest, sanitizedQuery);
|
|
133
136
|
}
|
|
134
137
|
const request = `${ctx.resourcePath}${sanitizedQuery}`;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
return contextifyRequest(ctx, request);
|
|
139
|
+
}
|
|
140
|
+
function rebuildProxyRequestFromRaw(ctx, rawRequest, sanitizedQuery) {
|
|
141
|
+
const stripped = stripResourceQuery(rawRequest);
|
|
142
|
+
const loaderDelimiter = stripped.lastIndexOf('!');
|
|
143
|
+
const loaderPrefix = loaderDelimiter >= 0 ? stripped.slice(0, loaderDelimiter + 1) : '';
|
|
144
|
+
let resource = loaderDelimiter >= 0 ? stripped.slice(loaderDelimiter + 1) : stripped;
|
|
145
|
+
if (isRelativeSpecifier(resource)) {
|
|
146
|
+
resource = makeResourceRelativeToContext(ctx, ctx.resourcePath);
|
|
138
147
|
}
|
|
139
|
-
return
|
|
148
|
+
return `${loaderPrefix}${resource}${sanitizedQuery}`;
|
|
140
149
|
}
|
|
141
150
|
function getRawRequest(ctx) {
|
|
142
151
|
const mod = ctx._module;
|
|
@@ -150,6 +159,42 @@ function stripResourceQuery(request) {
|
|
|
150
159
|
const idx = request.indexOf('?');
|
|
151
160
|
return idx >= 0 ? request.slice(0, idx) : request;
|
|
152
161
|
}
|
|
162
|
+
function contextifyRequest(ctx, request) {
|
|
163
|
+
const context = ctx.context ?? ctx.rootContext ?? process.cwd();
|
|
164
|
+
if (ctx.utils && typeof ctx.utils.contextify === 'function') {
|
|
165
|
+
return ctx.utils.contextify(context, request);
|
|
166
|
+
}
|
|
167
|
+
return rebuildRelativeRequest(context, request);
|
|
168
|
+
}
|
|
169
|
+
function rebuildRelativeRequest(context, request) {
|
|
170
|
+
const queryIndex = request.indexOf('?');
|
|
171
|
+
const resourcePath = queryIndex >= 0 ? request.slice(0, queryIndex) : request;
|
|
172
|
+
const query = queryIndex >= 0 ? request.slice(queryIndex) : '';
|
|
173
|
+
const relative = ensureDotPrefixedRelative(node_path_1.default.relative(context, resourcePath), resourcePath);
|
|
174
|
+
return `${relative}${query}`;
|
|
175
|
+
}
|
|
176
|
+
function makeResourceRelativeToContext(ctx, resourcePath) {
|
|
177
|
+
const context = ctx.context ?? node_path_1.default.dirname(resourcePath);
|
|
178
|
+
if (ctx.utils && typeof ctx.utils.contextify === 'function') {
|
|
179
|
+
const result = ctx.utils.contextify(context, resourcePath);
|
|
180
|
+
return stripResourceQuery(result);
|
|
181
|
+
}
|
|
182
|
+
return ensureDotPrefixedRelative(node_path_1.default.relative(context, resourcePath), resourcePath);
|
|
183
|
+
}
|
|
184
|
+
function ensureDotPrefixedRelative(relativePath, resourcePath) {
|
|
185
|
+
const fallback = relativePath.length > 0 ? relativePath : node_path_1.default.basename(resourcePath);
|
|
186
|
+
const normalized = normalizeToPosix(fallback);
|
|
187
|
+
if (normalized.startsWith('./') || normalized.startsWith('../')) {
|
|
188
|
+
return normalized;
|
|
189
|
+
}
|
|
190
|
+
return `./${normalized}`;
|
|
191
|
+
}
|
|
192
|
+
function normalizeToPosix(filePath) {
|
|
193
|
+
return filePath.split(node_path_1.default.sep).join('/');
|
|
194
|
+
}
|
|
195
|
+
function isRelativeSpecifier(specifier) {
|
|
196
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
197
|
+
}
|
|
153
198
|
function createCombinedModule(request, css, options) {
|
|
154
199
|
const shouldEmitDefault = options?.emitDefault ?? (0, loaderInternals_js_1.shouldForwardDefaultExport)(request);
|
|
155
200
|
const requestLiteral = JSON.stringify(request);
|
|
@@ -56,14 +56,6 @@ function hasQueryFlag(query, flag) {
|
|
|
56
56
|
return false;
|
|
57
57
|
return entries.some(part => isQueryFlag(part, flag));
|
|
58
58
|
}
|
|
59
|
-
function safeDecode(value) {
|
|
60
|
-
try {
|
|
61
|
-
return decodeURIComponent(value);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
59
|
function shouldForwardDefaultExport(request) {
|
|
68
60
|
const [pathPart] = request.split('?');
|
|
69
61
|
if (!pathPart)
|
package/dist/generateTypes.js
CHANGED
|
@@ -55,7 +55,6 @@ function getImportMetaUrl() {
|
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
const PACKAGE_ROOT = resolvePackageRoot();
|
|
59
58
|
const SELECTOR_REFERENCE = '.knighted-css';
|
|
60
59
|
const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
|
|
61
60
|
export async function generateTypes(options = {}) {
|
package/dist/loader.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { cssWithMeta, compileVanillaModule } from './css.js';
|
|
2
3
|
import { detectModuleDefaultExport } from './moduleInfo.js';
|
|
3
4
|
import { buildSanitizedQuery, hasCombinedQuery, hasNamedOnlyQueryFlag, hasQueryFlag, shouldEmitCombinedDefault, shouldForwardDefaultExport, TYPES_QUERY_FLAG, } from './loaderInternals.js';
|
|
@@ -124,15 +125,20 @@ function buildProxyRequest(ctx) {
|
|
|
124
125
|
const sanitizedQuery = buildSanitizedQuery(ctx.resourceQuery);
|
|
125
126
|
const rawRequest = getRawRequest(ctx);
|
|
126
127
|
if (rawRequest) {
|
|
127
|
-
|
|
128
|
-
return `${stripped}${sanitizedQuery}`;
|
|
128
|
+
return rebuildProxyRequestFromRaw(ctx, rawRequest, sanitizedQuery);
|
|
129
129
|
}
|
|
130
130
|
const request = `${ctx.resourcePath}${sanitizedQuery}`;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
return contextifyRequest(ctx, request);
|
|
132
|
+
}
|
|
133
|
+
function rebuildProxyRequestFromRaw(ctx, rawRequest, sanitizedQuery) {
|
|
134
|
+
const stripped = stripResourceQuery(rawRequest);
|
|
135
|
+
const loaderDelimiter = stripped.lastIndexOf('!');
|
|
136
|
+
const loaderPrefix = loaderDelimiter >= 0 ? stripped.slice(0, loaderDelimiter + 1) : '';
|
|
137
|
+
let resource = loaderDelimiter >= 0 ? stripped.slice(loaderDelimiter + 1) : stripped;
|
|
138
|
+
if (isRelativeSpecifier(resource)) {
|
|
139
|
+
resource = makeResourceRelativeToContext(ctx, ctx.resourcePath);
|
|
134
140
|
}
|
|
135
|
-
return
|
|
141
|
+
return `${loaderPrefix}${resource}${sanitizedQuery}`;
|
|
136
142
|
}
|
|
137
143
|
function getRawRequest(ctx) {
|
|
138
144
|
const mod = ctx._module;
|
|
@@ -146,6 +152,42 @@ function stripResourceQuery(request) {
|
|
|
146
152
|
const idx = request.indexOf('?');
|
|
147
153
|
return idx >= 0 ? request.slice(0, idx) : request;
|
|
148
154
|
}
|
|
155
|
+
function contextifyRequest(ctx, request) {
|
|
156
|
+
const context = ctx.context ?? ctx.rootContext ?? process.cwd();
|
|
157
|
+
if (ctx.utils && typeof ctx.utils.contextify === 'function') {
|
|
158
|
+
return ctx.utils.contextify(context, request);
|
|
159
|
+
}
|
|
160
|
+
return rebuildRelativeRequest(context, request);
|
|
161
|
+
}
|
|
162
|
+
function rebuildRelativeRequest(context, request) {
|
|
163
|
+
const queryIndex = request.indexOf('?');
|
|
164
|
+
const resourcePath = queryIndex >= 0 ? request.slice(0, queryIndex) : request;
|
|
165
|
+
const query = queryIndex >= 0 ? request.slice(queryIndex) : '';
|
|
166
|
+
const relative = ensureDotPrefixedRelative(path.relative(context, resourcePath), resourcePath);
|
|
167
|
+
return `${relative}${query}`;
|
|
168
|
+
}
|
|
169
|
+
function makeResourceRelativeToContext(ctx, resourcePath) {
|
|
170
|
+
const context = ctx.context ?? path.dirname(resourcePath);
|
|
171
|
+
if (ctx.utils && typeof ctx.utils.contextify === 'function') {
|
|
172
|
+
const result = ctx.utils.contextify(context, resourcePath);
|
|
173
|
+
return stripResourceQuery(result);
|
|
174
|
+
}
|
|
175
|
+
return ensureDotPrefixedRelative(path.relative(context, resourcePath), resourcePath);
|
|
176
|
+
}
|
|
177
|
+
function ensureDotPrefixedRelative(relativePath, resourcePath) {
|
|
178
|
+
const fallback = relativePath.length > 0 ? relativePath : path.basename(resourcePath);
|
|
179
|
+
const normalized = normalizeToPosix(fallback);
|
|
180
|
+
if (normalized.startsWith('./') || normalized.startsWith('../')) {
|
|
181
|
+
return normalized;
|
|
182
|
+
}
|
|
183
|
+
return `./${normalized}`;
|
|
184
|
+
}
|
|
185
|
+
function normalizeToPosix(filePath) {
|
|
186
|
+
return filePath.split(path.sep).join('/');
|
|
187
|
+
}
|
|
188
|
+
function isRelativeSpecifier(specifier) {
|
|
189
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
190
|
+
}
|
|
149
191
|
function createCombinedModule(request, css, options) {
|
|
150
192
|
const shouldEmitDefault = options?.emitDefault ?? shouldForwardDefaultExport(request);
|
|
151
193
|
const requestLiteral = JSON.stringify(request);
|
package/dist/loaderInternals.js
CHANGED
|
@@ -44,14 +44,6 @@ export function hasQueryFlag(query, flag) {
|
|
|
44
44
|
return false;
|
|
45
45
|
return entries.some(part => isQueryFlag(part, flag));
|
|
46
46
|
}
|
|
47
|
-
function safeDecode(value) {
|
|
48
|
-
try {
|
|
49
|
-
return decodeURIComponent(value);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
47
|
export function shouldForwardDefaultExport(request) {
|
|
56
48
|
const [pathPart] = request.split('?');
|
|
57
49
|
if (!pathPart)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/css",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|
|
@@ -114,8 +114,7 @@
|
|
|
114
114
|
"types.d.ts",
|
|
115
115
|
"stable",
|
|
116
116
|
"bin",
|
|
117
|
-
"types-stub"
|
|
118
|
-
"README.md"
|
|
117
|
+
"types-stub"
|
|
119
118
|
],
|
|
120
119
|
"author": "KCM <knightedcodemonkey@gmail.com>",
|
|
121
120
|
"license": "MIT",
|