@knighted/css 1.0.0-rc.4 → 1.0.0-rc.5

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
@@ -9,6 +9,7 @@ exports.cssWithMeta = cssWithMeta;
9
9
  exports.compileVanillaModule = compileVanillaModule;
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const node_fs_1 = require("node:fs");
12
+ const node_url_1 = require("node:url");
12
13
  const dependency_tree_1 = __importDefault(require("dependency-tree"));
13
14
  const lightningcss_1 = require("lightningcss");
14
15
  const helpers_js_1 = require("./helpers.cjs");
@@ -35,6 +36,7 @@ async function cssWithMeta(entry, options = {}) {
35
36
  const chunk = await compileStyleModule(file, {
36
37
  cwd,
37
38
  peerResolver: options.peerResolver,
39
+ resolver: options.resolver,
38
40
  });
39
41
  if (chunk) {
40
42
  chunks.push(chunk);
@@ -105,13 +107,17 @@ function matchExtension(filePath, extensions) {
105
107
  const lower = filePath.toLowerCase();
106
108
  return extensions.find(ext => lower.endsWith(ext));
107
109
  }
108
- async function compileStyleModule(file, { cwd, peerResolver }) {
110
+ async function compileStyleModule(file, { cwd, peerResolver, resolver, }) {
109
111
  switch (file.ext) {
110
112
  case '.css':
111
113
  return node_fs_1.promises.readFile(file.path, 'utf8');
112
114
  case '.scss':
113
115
  case '.sass':
114
- return compileSass(file.path, file.ext === '.sass', peerResolver);
116
+ return compileSass(file.path, file.ext === '.sass', {
117
+ cwd,
118
+ peerResolver,
119
+ resolver,
120
+ });
115
121
  case '.less':
116
122
  return compileLess(file.path, peerResolver);
117
123
  case '.css.ts':
@@ -120,12 +126,14 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
120
126
  return '';
121
127
  }
122
128
  }
123
- async function compileSass(filePath, indented, peerResolver) {
129
+ async function compileSass(filePath, indented, { cwd, peerResolver, resolver, }) {
124
130
  const sassModule = await optionalPeer('sass', 'Sass', peerResolver);
125
131
  const sass = sassModule;
126
- const result = sass.compile(filePath, {
132
+ const importer = createSassImporter({ cwd, resolver });
133
+ const result = await sass.compileAsync(filePath, {
127
134
  style: 'expanded',
128
135
  loadPaths: buildSassLoadPaths(filePath),
136
+ importers: importer ? [importer] : undefined,
129
137
  });
130
138
  return result.css;
131
139
  }
@@ -146,6 +154,117 @@ function buildSassLoadPaths(filePath) {
146
154
  loadPaths.add(node_path_1.default.join(cwd, 'node_modules'));
147
155
  return Array.from(loadPaths).filter(dir => dir && (0, node_fs_1.existsSync)(dir));
148
156
  }
157
+ function createSassImporter({ cwd, resolver }) {
158
+ if (!resolver)
159
+ return undefined;
160
+ const debug = process.env.KNIGHTED_CSS_DEBUG_SASS === '1';
161
+ return {
162
+ async canonicalize(url, context) {
163
+ if (debug) {
164
+ console.error('[knighted-css:sass] canonicalize request:', url);
165
+ if (context?.containingUrl) {
166
+ console.error('[knighted-css:sass] containing url:', context.containingUrl.href);
167
+ }
168
+ }
169
+ if (shouldNormalizeSpecifier(url)) {
170
+ const resolvedPath = await resolveAliasSpecifier(url, resolver, cwd);
171
+ if (!resolvedPath) {
172
+ if (debug) {
173
+ console.error('[knighted-css:sass] resolver returned no result for', url);
174
+ }
175
+ return null;
176
+ }
177
+ const fileUrl = (0, node_url_1.pathToFileURL)(resolvedPath);
178
+ if (debug) {
179
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
180
+ }
181
+ return fileUrl;
182
+ }
183
+ const relativePath = resolveRelativeSpecifier(url, context?.containingUrl);
184
+ if (relativePath) {
185
+ const fileUrl = (0, node_url_1.pathToFileURL)(relativePath);
186
+ if (debug) {
187
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
188
+ }
189
+ return fileUrl;
190
+ }
191
+ return null;
192
+ },
193
+ async load(canonicalUrl) {
194
+ if (debug) {
195
+ console.error('[knighted-css:sass] load request:', canonicalUrl.href);
196
+ }
197
+ const filePath = (0, node_url_1.fileURLToPath)(canonicalUrl);
198
+ const contents = await node_fs_1.promises.readFile(filePath, 'utf8');
199
+ return {
200
+ contents,
201
+ syntax: inferSassSyntax(filePath),
202
+ };
203
+ },
204
+ };
205
+ }
206
+ async function resolveAliasSpecifier(specifier, resolver, cwd) {
207
+ const resolved = await resolver(specifier, { cwd });
208
+ if (!resolved) {
209
+ return undefined;
210
+ }
211
+ if (resolved.startsWith('file://')) {
212
+ return ensureSassPath((0, node_url_1.fileURLToPath)(new URL(resolved)));
213
+ }
214
+ const normalized = node_path_1.default.isAbsolute(resolved) ? resolved : node_path_1.default.resolve(cwd, resolved);
215
+ return ensureSassPath(normalized);
216
+ }
217
+ function shouldNormalizeSpecifier(specifier) {
218
+ const schemeMatch = specifier.match(/^([a-z][\w+.-]*):/i);
219
+ if (!schemeMatch) {
220
+ return false;
221
+ }
222
+ const scheme = schemeMatch[1].toLowerCase();
223
+ if (scheme === 'file' ||
224
+ scheme === 'http' ||
225
+ scheme === 'https' ||
226
+ scheme === 'data' ||
227
+ scheme === 'sass') {
228
+ return false;
229
+ }
230
+ return true;
231
+ }
232
+ function inferSassSyntax(filePath) {
233
+ return filePath.endsWith('.sass') ? 'indented' : 'scss';
234
+ }
235
+ function ensureSassPath(filePath) {
236
+ if ((0, node_fs_1.existsSync)(filePath)) {
237
+ return filePath;
238
+ }
239
+ const ext = node_path_1.default.extname(filePath);
240
+ const dir = node_path_1.default.dirname(filePath);
241
+ const base = node_path_1.default.basename(filePath, ext);
242
+ const partialCandidate = node_path_1.default.join(dir, `_${base}${ext}`);
243
+ if (ext && (0, node_fs_1.existsSync)(partialCandidate)) {
244
+ return partialCandidate;
245
+ }
246
+ const indexCandidate = node_path_1.default.join(dir, base, `index${ext}`);
247
+ if (ext && (0, node_fs_1.existsSync)(indexCandidate)) {
248
+ return indexCandidate;
249
+ }
250
+ const partialIndexCandidate = node_path_1.default.join(dir, base, `_index${ext}`);
251
+ if (ext && (0, node_fs_1.existsSync)(partialIndexCandidate)) {
252
+ return partialIndexCandidate;
253
+ }
254
+ return undefined;
255
+ }
256
+ function resolveRelativeSpecifier(specifier, containingUrl) {
257
+ if (!containingUrl || containingUrl.protocol !== 'file:') {
258
+ return undefined;
259
+ }
260
+ if (/^[a-z][\w+.-]*:/i.test(specifier)) {
261
+ return undefined;
262
+ }
263
+ const containingPath = (0, node_url_1.fileURLToPath)(containingUrl);
264
+ const baseDir = node_path_1.default.dirname(containingPath);
265
+ const candidate = node_path_1.default.resolve(baseDir, specifier);
266
+ return ensureSassPath(candidate);
267
+ }
149
268
  async function compileLess(filePath, peerResolver) {
150
269
  const mod = await optionalPeer('less', 'Less', peerResolver);
151
270
  const less = unwrapModuleNamespace(mod);
package/dist/css.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { existsSync, promises as fs } from 'node:fs';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
4
  import dependencyTree from 'dependency-tree';
4
5
  import { composeVisitors, transform as lightningTransform, } from 'lightningcss';
5
6
  import { applyStringSpecificityBoost, buildSpecificityVisitor, } from './helpers.js';
@@ -26,6 +27,7 @@ export async function cssWithMeta(entry, options = {}) {
26
27
  const chunk = await compileStyleModule(file, {
27
28
  cwd,
28
29
  peerResolver: options.peerResolver,
30
+ resolver: options.resolver,
29
31
  });
30
32
  if (chunk) {
31
33
  chunks.push(chunk);
@@ -96,13 +98,17 @@ function matchExtension(filePath, extensions) {
96
98
  const lower = filePath.toLowerCase();
97
99
  return extensions.find(ext => lower.endsWith(ext));
98
100
  }
99
- async function compileStyleModule(file, { cwd, peerResolver }) {
101
+ async function compileStyleModule(file, { cwd, peerResolver, resolver, }) {
100
102
  switch (file.ext) {
101
103
  case '.css':
102
104
  return fs.readFile(file.path, 'utf8');
103
105
  case '.scss':
104
106
  case '.sass':
105
- return compileSass(file.path, file.ext === '.sass', peerResolver);
107
+ return compileSass(file.path, file.ext === '.sass', {
108
+ cwd,
109
+ peerResolver,
110
+ resolver,
111
+ });
106
112
  case '.less':
107
113
  return compileLess(file.path, peerResolver);
108
114
  case '.css.ts':
@@ -111,12 +117,14 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
111
117
  return '';
112
118
  }
113
119
  }
114
- async function compileSass(filePath, indented, peerResolver) {
120
+ async function compileSass(filePath, indented, { cwd, peerResolver, resolver, }) {
115
121
  const sassModule = await optionalPeer('sass', 'Sass', peerResolver);
116
122
  const sass = sassModule;
117
- const result = sass.compile(filePath, {
123
+ const importer = createSassImporter({ cwd, resolver });
124
+ const result = await sass.compileAsync(filePath, {
118
125
  style: 'expanded',
119
126
  loadPaths: buildSassLoadPaths(filePath),
127
+ importers: importer ? [importer] : undefined,
120
128
  });
121
129
  return result.css;
122
130
  }
@@ -137,6 +145,117 @@ function buildSassLoadPaths(filePath) {
137
145
  loadPaths.add(path.join(cwd, 'node_modules'));
138
146
  return Array.from(loadPaths).filter(dir => dir && existsSync(dir));
139
147
  }
148
+ function createSassImporter({ cwd, resolver }) {
149
+ if (!resolver)
150
+ return undefined;
151
+ const debug = process.env.KNIGHTED_CSS_DEBUG_SASS === '1';
152
+ return {
153
+ async canonicalize(url, context) {
154
+ if (debug) {
155
+ console.error('[knighted-css:sass] canonicalize request:', url);
156
+ if (context?.containingUrl) {
157
+ console.error('[knighted-css:sass] containing url:', context.containingUrl.href);
158
+ }
159
+ }
160
+ if (shouldNormalizeSpecifier(url)) {
161
+ const resolvedPath = await resolveAliasSpecifier(url, resolver, cwd);
162
+ if (!resolvedPath) {
163
+ if (debug) {
164
+ console.error('[knighted-css:sass] resolver returned no result for', url);
165
+ }
166
+ return null;
167
+ }
168
+ const fileUrl = pathToFileURL(resolvedPath);
169
+ if (debug) {
170
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
171
+ }
172
+ return fileUrl;
173
+ }
174
+ const relativePath = resolveRelativeSpecifier(url, context?.containingUrl);
175
+ if (relativePath) {
176
+ const fileUrl = pathToFileURL(relativePath);
177
+ if (debug) {
178
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
179
+ }
180
+ return fileUrl;
181
+ }
182
+ return null;
183
+ },
184
+ async load(canonicalUrl) {
185
+ if (debug) {
186
+ console.error('[knighted-css:sass] load request:', canonicalUrl.href);
187
+ }
188
+ const filePath = fileURLToPath(canonicalUrl);
189
+ const contents = await fs.readFile(filePath, 'utf8');
190
+ return {
191
+ contents,
192
+ syntax: inferSassSyntax(filePath),
193
+ };
194
+ },
195
+ };
196
+ }
197
+ async function resolveAliasSpecifier(specifier, resolver, cwd) {
198
+ const resolved = await resolver(specifier, { cwd });
199
+ if (!resolved) {
200
+ return undefined;
201
+ }
202
+ if (resolved.startsWith('file://')) {
203
+ return ensureSassPath(fileURLToPath(new URL(resolved)));
204
+ }
205
+ const normalized = path.isAbsolute(resolved) ? resolved : path.resolve(cwd, resolved);
206
+ return ensureSassPath(normalized);
207
+ }
208
+ function shouldNormalizeSpecifier(specifier) {
209
+ const schemeMatch = specifier.match(/^([a-z][\w+.-]*):/i);
210
+ if (!schemeMatch) {
211
+ return false;
212
+ }
213
+ const scheme = schemeMatch[1].toLowerCase();
214
+ if (scheme === 'file' ||
215
+ scheme === 'http' ||
216
+ scheme === 'https' ||
217
+ scheme === 'data' ||
218
+ scheme === 'sass') {
219
+ return false;
220
+ }
221
+ return true;
222
+ }
223
+ function inferSassSyntax(filePath) {
224
+ return filePath.endsWith('.sass') ? 'indented' : 'scss';
225
+ }
226
+ function ensureSassPath(filePath) {
227
+ if (existsSync(filePath)) {
228
+ return filePath;
229
+ }
230
+ const ext = path.extname(filePath);
231
+ const dir = path.dirname(filePath);
232
+ const base = path.basename(filePath, ext);
233
+ const partialCandidate = path.join(dir, `_${base}${ext}`);
234
+ if (ext && existsSync(partialCandidate)) {
235
+ return partialCandidate;
236
+ }
237
+ const indexCandidate = path.join(dir, base, `index${ext}`);
238
+ if (ext && existsSync(indexCandidate)) {
239
+ return indexCandidate;
240
+ }
241
+ const partialIndexCandidate = path.join(dir, base, `_index${ext}`);
242
+ if (ext && existsSync(partialIndexCandidate)) {
243
+ return partialIndexCandidate;
244
+ }
245
+ return undefined;
246
+ }
247
+ function resolveRelativeSpecifier(specifier, containingUrl) {
248
+ if (!containingUrl || containingUrl.protocol !== 'file:') {
249
+ return undefined;
250
+ }
251
+ if (/^[a-z][\w+.-]*:/i.test(specifier)) {
252
+ return undefined;
253
+ }
254
+ const containingPath = fileURLToPath(containingUrl);
255
+ const baseDir = path.dirname(containingPath);
256
+ const candidate = path.resolve(baseDir, specifier);
257
+ return ensureSassPath(candidate);
258
+ }
140
259
  async function compileLess(filePath, peerResolver) {
141
260
  const mod = await optionalPeer('less', 'Less', peerResolver);
142
261
  const less = unwrapModuleNamespace(mod);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/css",
3
- "version": "1.0.0-rc.4",
3
+ "version": "1.0.0-rc.5",
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",