auklet 0.2.6 → 0.2.8

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 CHANGED
@@ -181,8 +181,10 @@ pattern syntax is a small glob subset: `*`, `**`, and `?`.
181
181
 
182
182
  For example, `components/CodeBlock/index.css` may import
183
183
  `../../internal/syntaxHighlight.css` when it matches `styles.shared`; component
184
- CSS outputs inline that shared CSS and its local helper CSS imports, while
185
- package-level CSS dedupes repeated shared imports. Shared CSS cannot import
186
- component CSS or theme CSS. Component-to-component CSS imports are still
187
- rejected; package CSS dependencies should be expressed through
188
- `styles.dependencies`.
184
+ CSS outputs keep that `@import` relationship instead of expanding the shared
185
+ rules into every component output. The shared CSS file is still copied as its
186
+ own source-level CSS file. `dist/index.css` remains the full package CSS
187
+ aggregate, while format-level and component-level CSS entries preserve the
188
+ import graph as much as possible. Shared CSS cannot import component CSS or
189
+ theme CSS. Component-to-component CSS imports are still rejected; package CSS
190
+ dependencies should be expressed through `styles.dependencies`.
@@ -62,7 +62,7 @@ export class StyleModuleEntryPlanner {
62
62
  throw new Error(`[css] cross-package CSS import detected: ${importer} imports ${imported}. ` +
63
63
  'Use styles.dependencies to express package style dependencies.');
64
64
  }
65
- if (this.packageContext.shouldInlineSharedStyleImport(item))
65
+ if (this.packageContext.shouldAllowSharedStyleImport(item))
66
66
  continue;
67
67
  if (this.packageContext.isSharedStyleFile(item.importer)) {
68
68
  const importer = this.toRelativeSourceFile(item.importer);
@@ -22,14 +22,19 @@ export declare class StylePackageContext {
22
22
  private readonly sourceModuleDirs;
23
23
  private readonly themeStyleFileKeys;
24
24
  private readonly sharedStyleFileKeys;
25
+ private hasValidatedSourceRootLocalStyleImports;
26
+ private hasValidatedPreservedLocalStyleImports;
25
27
  private moduleStyleEntryPlanner?;
26
28
  private moduleStyleImports?;
27
29
  constructor(options: StylePackageContextOptions);
28
30
  getStyleFiles(files: Array<string>): string[];
29
31
  getModuleStyleImports(): Map<string, string[]>;
30
32
  getModuleStyleEntryPlanner(): StyleModuleEntryPlanner;
33
+ getStyleEntryFiles(): string[];
31
34
  isSharedStyleFile(file: string): boolean;
32
- shouldInlineSharedStyleImport(reference: StyleFileImportReference): boolean;
35
+ shouldAllowSharedStyleImport(reference: StyleFileImportReference): boolean;
36
+ assertPreservedLocalStyleImports(): void;
37
+ assertNoSourceRootEscapingLocalStyleImports(): void;
33
38
  private isSharedHelperStyleFile;
34
39
  private getSourceModuleDirs;
35
40
  private isSourceModuleStyleFile;
@@ -23,6 +23,8 @@ export class StylePackageContext {
23
23
  sourceModuleDirs;
24
24
  themeStyleFileKeys;
25
25
  sharedStyleFileKeys;
26
+ hasValidatedSourceRootLocalStyleImports = false;
27
+ hasValidatedPreservedLocalStyleImports = false;
26
28
  moduleStyleEntryPlanner;
27
29
  moduleStyleImports;
28
30
  constructor(options) {
@@ -61,15 +63,35 @@ export class StylePackageContext {
61
63
  this.moduleStyleEntryPlanner ??= new StyleModuleEntryPlanner(this);
62
64
  return this.moduleStyleEntryPlanner;
63
65
  }
66
+ getStyleEntryFiles() {
67
+ const importedStyleFiles = this.styleProcessor.collectImportedStyleFiles(this.styleFiles);
68
+ return this.styleFiles.filter((styleFile) => !importedStyleFiles.has(path.resolve(styleFile)));
69
+ }
64
70
  isSharedStyleFile(file) {
65
71
  return this.sharedStyleFileKeys.has(normalizeFileKey(file));
66
72
  }
67
- shouldInlineSharedStyleImport(reference) {
73
+ shouldAllowSharedStyleImport(reference) {
68
74
  return ((this.isSharedStyleFile(reference.imported) &&
69
75
  this.isSharedHelperStyleFile(reference.imported)) ||
70
76
  (this.isSharedStyleFile(reference.importer) &&
71
77
  this.isSharedHelperStyleFile(reference.imported)));
72
78
  }
79
+ assertPreservedLocalStyleImports() {
80
+ if (this.hasValidatedPreservedLocalStyleImports)
81
+ return;
82
+ this.assertNoSourceRootEscapingLocalStyleImports();
83
+ this.styleProcessor.assertNoLocalStyleImportCycles(this.styleFiles);
84
+ this.hasValidatedPreservedLocalStyleImports = true;
85
+ }
86
+ assertNoSourceRootEscapingLocalStyleImports() {
87
+ if (this.hasValidatedSourceRootLocalStyleImports)
88
+ return;
89
+ this.styleProcessor.assertNoSourceRootEscapingLocalStyleImports([
90
+ ...this.styleFiles,
91
+ ...this.themeFiles.values(),
92
+ ]);
93
+ this.hasValidatedSourceRootLocalStyleImports = true;
94
+ }
73
95
  isSharedHelperStyleFile(file) {
74
96
  return (this.isInsideSourceRoot(file) &&
75
97
  !this.isThemeStyleFile(file) &&
@@ -4,8 +4,10 @@ import type { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleReso
4
4
  export type StyleFileImportReference = {
5
5
  importer: string;
6
6
  imported: string;
7
+ specifier: string;
7
8
  };
8
9
  export type StyleFileImportExpandOptions = {
10
+ mapImportSpecifier?: (reference: StyleFileImportReference) => string;
9
11
  shouldExpandImport?: (reference: StyleFileImportReference) => boolean;
10
12
  };
11
13
  export declare class StyleProcessor {
@@ -17,9 +19,17 @@ export declare class StyleProcessor {
17
19
  stringify(root: Root): string;
18
20
  appendStyleContent(target: Root, content: string, from: string): void;
19
21
  readStyleFile(stylePath: string, seen?: Set<string>, options?: StyleFileImportExpandOptions): string;
22
+ private isSourceStyleImportSpecifier;
23
+ private throwMissingLocalStyleImport;
20
24
  collectImportedStyleFiles(styleFiles: Array<string>): Set<string>;
21
25
  collectImportedStyleFileReferences(styleFiles: Array<string>): StyleFileImportReference[];
22
26
  collectStyleImportSpecifiers(styleFiles: Array<string>): Set<string>;
27
+ assertNoLocalStyleImportCycles(styleFiles: Array<string>): void;
28
+ assertNoSourceRootEscapingLocalStyleImports(styleFiles: Array<string>): void;
23
29
  private parse;
24
30
  private parseImportSpecifier;
31
+ private resolveStyleImportReference;
32
+ private assertSourceRootLocalStyleImport;
33
+ private collectLocalStyleImportReferences;
34
+ private parseImportSpecifierReference;
25
35
  }
@@ -44,29 +44,39 @@ export class StyleProcessor {
44
44
  const root = this.parse(css, stylePath);
45
45
  const imports = [];
46
46
  root.walkAtRules('import', (rule) => {
47
- const specifier = this.parseImportSpecifier(rule.params);
48
- if (specifier)
49
- imports.push({ rule, specifier });
47
+ const reference = this.parseImportSpecifierReference(rule.params);
48
+ if (reference)
49
+ imports.push({ rule, ...reference });
50
50
  });
51
- for (const { rule, specifier } of imports) {
52
- const importedPath = this.resolver.resolveStyleDependency(specifier, path.dirname(stylePath));
53
- if (!importedPath)
54
- continue;
55
- const reference = {
56
- importer: normalizedPath,
57
- imported: path.resolve(importedPath),
58
- };
59
- if (options.shouldExpandImport?.(reference) === false)
51
+ for (const { rule, specifier, specifierStart, specifierEnd } of imports) {
52
+ const { reference, isLocalStyleImport } = this.resolveStyleImportReference(specifier, stylePath);
53
+ this.assertSourceRootLocalStyleImport(reference, isLocalStyleImport);
54
+ const mappedSpecifier = options.mapImportSpecifier?.(reference);
55
+ if (mappedSpecifier) {
56
+ rule.params = `${rule.params.slice(0, specifierStart)}${mappedSpecifier}${rule.params.slice(specifierEnd)}`;
57
+ }
58
+ if (options.shouldExpandImport?.(reference) === false) {
60
59
  continue;
61
- const content = this.readStyleFile(importedPath, seen, options);
60
+ }
61
+ const content = this.readStyleFile(reference.imported, seen, options);
62
62
  if (!content.trim()) {
63
63
  rule.remove();
64
64
  continue;
65
65
  }
66
- rule.replaceWith(...(this.parse(content, importedPath).nodes ?? []));
66
+ rule.replaceWith(...(this.parse(content, reference.imported).nodes ?? []));
67
67
  }
68
68
  return root.toString();
69
69
  }
70
+ isSourceStyleImportSpecifier(specifier, importedPath) {
71
+ if (specifier.startsWith('#'))
72
+ return true;
73
+ return (this.config.styleExtensions.includes(path.extname(importedPath ?? specifier)) &&
74
+ (specifier.startsWith('.') ||
75
+ (importedPath ? this.resolver.isInsideSourceRoot(importedPath) : false)));
76
+ }
77
+ throwMissingLocalStyleImport(specifier, stylePath) {
78
+ throw new Error(`[css] local CSS import not found: ${specifier} from ${stylePath}`);
79
+ }
70
80
  collectImportedStyleFiles(styleFiles) {
71
81
  return new Set(this.collectImportedStyleFileReferences(styleFiles).map((item) => item.imported));
72
82
  }
@@ -77,14 +87,13 @@ export class StyleProcessor {
77
87
  const root = this.parse(css, styleFile);
78
88
  root.walkAtRules('import', (rule) => {
79
89
  const specifier = this.parseImportSpecifier(rule.params);
80
- if (!specifier?.startsWith('.') ||
81
- !this.config.styleExtensions.includes(path.extname(specifier))) {
90
+ if (!specifier)
91
+ return;
92
+ const { reference, isLocalStyleImport } = this.resolveStyleImportReference(specifier, styleFile);
93
+ if (!isLocalStyleImport) {
82
94
  return;
83
95
  }
84
- imports.push({
85
- importer: path.resolve(styleFile),
86
- imported: path.resolve(path.dirname(styleFile), specifier),
87
- });
96
+ imports.push(reference);
88
97
  });
89
98
  }
90
99
  return imports;
@@ -102,17 +111,117 @@ export class StyleProcessor {
102
111
  }
103
112
  return specifiers;
104
113
  }
114
+ assertNoLocalStyleImportCycles(styleFiles) {
115
+ const visited = new Set();
116
+ const visiting = new Set();
117
+ const stack = [];
118
+ const visit = (styleFile) => {
119
+ const normalizedPath = path.resolve(styleFile);
120
+ if (visited.has(normalizedPath))
121
+ return;
122
+ if (visiting.has(normalizedPath)) {
123
+ const cycleStart = stack.indexOf(normalizedPath);
124
+ const cycle = cycleStart >= 0
125
+ ? [...stack.slice(cycleStart), normalizedPath]
126
+ : [normalizedPath, normalizedPath];
127
+ throw new Error(`[css] circular CSS import detected: ${cycle.join(' -> ')}`);
128
+ }
129
+ if (!fs.existsSync(normalizedPath))
130
+ return;
131
+ visiting.add(normalizedPath);
132
+ stack.push(normalizedPath);
133
+ for (const reference of this.collectLocalStyleImportReferences(normalizedPath)) {
134
+ visit(reference.imported);
135
+ }
136
+ stack.pop();
137
+ visiting.delete(normalizedPath);
138
+ visited.add(normalizedPath);
139
+ };
140
+ for (const styleFile of styleFiles) {
141
+ visit(styleFile);
142
+ }
143
+ }
144
+ assertNoSourceRootEscapingLocalStyleImports(styleFiles) {
145
+ for (const styleFile of styleFiles) {
146
+ if (!fs.existsSync(styleFile))
147
+ continue;
148
+ for (const reference of this.collectLocalStyleImportReferences(path.resolve(styleFile))) {
149
+ this.assertSourceRootLocalStyleImport(reference, true);
150
+ }
151
+ }
152
+ }
105
153
  parse(code, from) {
106
154
  // Keep parsing behind one method so future style languages can transform
107
155
  // to CSS before PostCSS reads the final stylesheet.
108
156
  return postcss.parse(code, { from });
109
157
  }
110
158
  parseImportSpecifier(params) {
159
+ return this.parseImportSpecifierReference(params)?.specifier ?? null;
160
+ }
161
+ resolveStyleImportReference(specifier, stylePath) {
162
+ const fromDir = path.dirname(stylePath);
163
+ const sourceStylePath = this.resolver.resolveSourceStyleDependency(specifier, fromDir);
164
+ const isSourceStyleSpecifier = this.isSourceStyleImportSpecifier(specifier, sourceStylePath);
165
+ if (isSourceStyleSpecifier) {
166
+ if (!sourceStylePath || !fs.existsSync(sourceStylePath)) {
167
+ this.throwMissingLocalStyleImport(specifier, stylePath);
168
+ }
169
+ return {
170
+ reference: {
171
+ importer: path.resolve(stylePath),
172
+ imported: path.resolve(sourceStylePath),
173
+ specifier,
174
+ },
175
+ isLocalStyleImport: true,
176
+ };
177
+ }
178
+ const importedPath = this.resolver.resolveStyleDependency(specifier, fromDir);
179
+ return {
180
+ reference: {
181
+ importer: path.resolve(stylePath),
182
+ imported: path.resolve(importedPath),
183
+ specifier,
184
+ },
185
+ isLocalStyleImport: false,
186
+ };
187
+ }
188
+ assertSourceRootLocalStyleImport(reference, isLocalStyleImport) {
189
+ if (!isLocalStyleImport ||
190
+ !this.resolver.isInsideSourceRoot(reference.importer) ||
191
+ this.resolver.isInsideSourceRoot(reference.imported)) {
192
+ return;
193
+ }
194
+ throw new Error(`[css] local CSS import escapes source root: ${reference.specifier} from ${reference.importer}`);
195
+ }
196
+ collectLocalStyleImportReferences(styleFile) {
197
+ const imports = [];
198
+ const css = fs.readFileSync(styleFile, 'utf8');
199
+ const root = this.parse(css, styleFile);
200
+ root.walkAtRules('import', (rule) => {
201
+ const specifier = this.parseImportSpecifier(rule.params);
202
+ if (!specifier)
203
+ return;
204
+ const { reference, isLocalStyleImport } = this.resolveStyleImportReference(specifier, styleFile);
205
+ if (!isLocalStyleImport) {
206
+ return;
207
+ }
208
+ imports.push(reference);
209
+ });
210
+ return imports;
211
+ }
212
+ parseImportSpecifierReference(params) {
111
213
  const value = params.trim();
214
+ const valueOffset = params.indexOf(value);
112
215
  const first = value[0];
113
216
  if (first === '"' || first === "'") {
114
217
  const end = value.indexOf(first, 1);
115
- return end > 0 ? value.slice(1, end) : null;
218
+ return end > 0
219
+ ? {
220
+ specifier: value.slice(1, end),
221
+ specifierStart: valueOffset + 1,
222
+ specifierEnd: valueOffset + end,
223
+ }
224
+ : null;
116
225
  }
117
226
  if (!value.startsWith('url(')) {
118
227
  return null;
@@ -124,8 +233,24 @@ export class StyleProcessor {
124
233
  const quote = url[0];
125
234
  if (quote === '"' || quote === "'") {
126
235
  const quoteEnd = url.indexOf(quote, 1);
127
- return quoteEnd > 0 ? url.slice(1, quoteEnd) : null;
236
+ const quotedValueStart = value.indexOf(quote, 4);
237
+ return quoteEnd > 0
238
+ ? {
239
+ specifier: url.slice(1, quoteEnd),
240
+ specifierStart: valueOffset + quotedValueStart + 1,
241
+ specifierEnd: valueOffset + quotedValueStart + quoteEnd,
242
+ }
243
+ : null;
128
244
  }
129
- return url || null;
245
+ if (!url)
246
+ return null;
247
+ const rawUrl = value.slice(4, end);
248
+ const leadingSpaces = rawUrl.length - rawUrl.trimStart().length;
249
+ const trailingSpaces = rawUrl.length - rawUrl.trimEnd().length;
250
+ return {
251
+ specifier: url,
252
+ specifierStart: valueOffset + 4 + leadingSpaces,
253
+ specifierEnd: valueOffset + end - trailingSpaces,
254
+ };
130
255
  }
131
256
  }
@@ -3,8 +3,13 @@ export declare class WorkspaceStyleResolver {
3
3
  private readonly config;
4
4
  private readonly context;
5
5
  private readonly require;
6
+ readonly sourceRoot: string;
6
7
  constructor(config: ModuleStyleBuildConfig, context: ResolvedModuleStyleBuildContext);
7
8
  resolveStyleDependency(specifier: string, fromDir?: string): string;
9
+ resolveSourceStyleDependency(specifier: string, fromDir?: string): string | null;
10
+ isInsideSourceRoot(file: string): boolean;
11
+ isStyleFile(file: string): boolean;
8
12
  toOutputStyleSpecifier(specifier: string, outRoot: string): string;
9
13
  toExternalStyleSpecifier(specifier: string, outRoot: string): string;
14
+ private resolveSourceImportPaths;
10
15
  }
@@ -1,20 +1,26 @@
1
1
  import path from 'node:path';
2
2
  import { createRequire } from 'node:module';
3
3
  import { NODE_MODULES_DIR } from '#auklet/css/constants';
4
+ import { resolvePackageImportsSourceImport } from '#auklet/css/core/resolvers/packageImports';
5
+ import { resolveTsconfigPathsSourceImport } from '#auklet/css/core/resolvers/tsconfigPaths';
4
6
  import { createExternalStyleSpecifier, createOutputStyleSpecifier, } from '#auklet/css/core/style/specifier';
5
7
  export class WorkspaceStyleResolver {
6
8
  config;
7
9
  context;
8
10
  require;
11
+ sourceRoot;
9
12
  constructor(config, context) {
10
13
  this.config = config;
11
14
  this.context = context;
15
+ this.sourceRoot = path.isAbsolute(context.sourceDir)
16
+ ? context.sourceDir
17
+ : path.join(context.packageRoot, context.sourceDir);
12
18
  this.require = createRequire(path.join(this.context.packageRoot, 'package.json'));
13
19
  }
14
20
  resolveStyleDependency(specifier, fromDir = this.context.packageRoot) {
15
- if (specifier.startsWith('.')) {
16
- return path.resolve(fromDir, specifier);
17
- }
21
+ const sourceStyleDependency = this.resolveSourceStyleDependency(specifier, fromDir);
22
+ if (sourceStyleDependency)
23
+ return sourceStyleDependency;
18
24
  try {
19
25
  return this.require.resolve(specifier, {
20
26
  paths: [this.context.packageRoot],
@@ -24,6 +30,26 @@ export class WorkspaceStyleResolver {
24
30
  return path.resolve(this.context.packageRoot, NODE_MODULES_DIR, specifier);
25
31
  }
26
32
  }
33
+ resolveSourceStyleDependency(specifier, fromDir = this.context.packageRoot) {
34
+ if (specifier.startsWith('.')) {
35
+ return path.resolve(fromDir, specifier);
36
+ }
37
+ for (const sourceRelativePath of this.resolveSourceImportPaths(specifier)) {
38
+ const file = path.join(this.sourceRoot, sourceRelativePath);
39
+ if (this.isStyleFile(file))
40
+ return file;
41
+ }
42
+ return null;
43
+ }
44
+ isInsideSourceRoot(file) {
45
+ const relative = path.relative(this.sourceRoot, file);
46
+ return (Boolean(relative) &&
47
+ !relative.startsWith('..') &&
48
+ !path.isAbsolute(relative));
49
+ }
50
+ isStyleFile(file) {
51
+ return this.config.styleExtensions.includes(path.extname(file));
52
+ }
27
53
  toOutputStyleSpecifier(specifier, outRoot) {
28
54
  return createOutputStyleSpecifier(specifier, {
29
55
  currentOutputFormat: path.basename(outRoot),
@@ -39,4 +65,10 @@ export class WorkspaceStyleResolver {
39
65
  externalStyleFile: this.config.output.externalStyleFile,
40
66
  });
41
67
  }
68
+ resolveSourceImportPaths(specifier) {
69
+ return [
70
+ ...resolvePackageImportsSourceImport(this.context.packageRoot, this.sourceRoot, specifier),
71
+ ...resolveTsconfigPathsSourceImport(this.context.packageRoot, this.sourceRoot, specifier),
72
+ ];
73
+ }
42
74
  }
@@ -21,6 +21,7 @@ export class ModuleStyleBuilder {
21
21
  const normalizedConfig = normalizeAukletConfig(rawConfig);
22
22
  const context = this.createBuildContext(normalizedConfig);
23
23
  const packageContext = this.createPackageContext(context, normalizedConfig);
24
+ packageContext.assertNoSourceRootEscapingLocalStyleImports();
24
25
  const writerOptions = {
25
26
  config: this.config,
26
27
  context,
@@ -1,5 +1,6 @@
1
1
  import { type FormatWriterOptions } from '#auklet/css/production/format/shared';
2
2
  export declare class ModuleStyleWriter {
3
+ private readonly sourceRoot;
3
4
  private readonly config;
4
5
  private readonly packageContext;
5
6
  private readonly styleProcessor;
@@ -1,23 +1,23 @@
1
1
  import path from 'node:path';
2
- import { writeStyleFile, } from '#auklet/css/production/format/shared';
2
+ import { toRelativeImportSpecifier, writeStyleFile, } from '#auklet/css/production/format/shared';
3
3
  export class ModuleStyleWriter {
4
+ sourceRoot;
4
5
  config;
5
6
  packageContext;
6
7
  styleProcessor;
7
8
  constructor(options) {
8
9
  this.config = options.config;
9
10
  this.packageContext = options.packageContext;
11
+ this.sourceRoot = options.packageContext.sourceRoot;
10
12
  this.styleProcessor = options.packageContext.styleProcessor;
11
13
  }
12
14
  write(outRoot) {
13
15
  const target = path.join(outRoot, this.config.output.styleDir, this.config.output.moduleStyleFile);
14
- const seen = new Set();
16
+ const targetDir = path.dirname(target);
15
17
  const root = this.styleProcessor.createRoot();
16
- for (const styleFile of this.packageContext.styleFiles) {
17
- const content = this.styleProcessor.readStyleFile(styleFile, seen);
18
- if (content.trim()) {
19
- this.styleProcessor.appendStyleContent(root, content, styleFile);
20
- }
18
+ for (const styleFile of this.packageContext.getStyleEntryFiles()) {
19
+ const outputStyleFile = path.join(outRoot, path.relative(this.sourceRoot, styleFile));
20
+ this.styleProcessor.appendImportRule(root, toRelativeImportSpecifier(targetDir, outputStyleFile));
21
21
  }
22
22
  if (!root.nodes?.length)
23
23
  return null;
@@ -1,7 +1,7 @@
1
1
  import { type FormatWriterOptions } from '#auklet/css/production/format/shared';
2
2
  export declare class SourceStyleFileWriter {
3
3
  private readonly sourceRoot;
4
- private readonly packageContext;
4
+ private readonly resolver;
5
5
  private readonly styleProcessor;
6
6
  constructor(options: FormatWriterOptions);
7
7
  copy(files: Array<string>, outRoot: string): void;
@@ -1,12 +1,12 @@
1
1
  import path from 'node:path';
2
- import { writeStyleFile, } from '#auklet/css/production/format/shared';
2
+ import { toRelativeImportSpecifier, writeStyleFile, } from '#auklet/css/production/format/shared';
3
3
  export class SourceStyleFileWriter {
4
4
  sourceRoot;
5
- packageContext;
5
+ resolver;
6
6
  styleProcessor;
7
7
  constructor(options) {
8
8
  this.sourceRoot = options.packageContext.sourceRoot;
9
- this.packageContext = options.packageContext;
9
+ this.resolver = options.packageContext.resolver;
10
10
  this.styleProcessor = options.packageContext.styleProcessor;
11
11
  }
12
12
  copy(files, outRoot) {
@@ -14,7 +14,13 @@ export class SourceStyleFileWriter {
14
14
  const relative = path.relative(this.sourceRoot, sourceFile);
15
15
  const target = path.join(outRoot, relative);
16
16
  const content = this.styleProcessor.readStyleFile(sourceFile, undefined, {
17
- shouldExpandImport: (reference) => this.packageContext.shouldInlineSharedStyleImport(reference),
17
+ mapImportSpecifier: (reference) => {
18
+ if (!this.resolver.isInsideSourceRoot(reference.imported)) {
19
+ return reference.specifier;
20
+ }
21
+ return toRelativeImportSpecifier(path.dirname(target), path.join(outRoot, path.relative(this.sourceRoot, reference.imported)));
22
+ },
23
+ shouldExpandImport: () => false,
18
24
  });
19
25
  writeStyleFile(target, content);
20
26
  }
@@ -29,6 +29,7 @@ export class ModuleStyleOutputWriter {
29
29
  this.moduleEntryWriter = new ModuleStyleEntryWriter(options);
30
30
  }
31
31
  write() {
32
+ this.packageContext.assertPreservedLocalStyleImports();
32
33
  const moduleEntries = this.createModuleEntries();
33
34
  const outputs = [];
34
35
  for (const format of this.config.output.outputFormats) {
@@ -10,10 +10,15 @@ export declare class StyleCodeFactory {
10
10
  private createDependencyStyleCode;
11
11
  private createExternalStyleCode;
12
12
  private createThemeStyleCode;
13
+ private createFullModuleStyleCode;
13
14
  private createModuleStyleCode;
14
15
  private createSourceModuleStyleCode;
16
+ private createOwnSourceStyleCode;
15
17
  private toDevModuleImportSpecifier;
16
18
  private toDevExternalStyleSpecifier;
17
19
  private parsePackageStyleIdInRequest;
18
20
  private withDependencyPackage;
21
+ private normalizeTopLevelImports;
22
+ private isTopLevelImportRule;
23
+ private isImportPreludeNode;
19
24
  }
@@ -1,10 +1,12 @@
1
1
  import path from 'node:path';
2
+ import postcss from 'postcss';
2
3
  import { mergeLoadResults } from '#auklet/css/vite/moduleGraph/loadResult';
3
4
  import { parsePackageStyleId } from '#auklet/css/vite/moduleGraph/styleId';
4
5
  import { toDevDependencyImportSpecifier } from '#auklet/css/vite/moduleGraph/devDependency';
5
6
  import { EXTERNAL_ENTRY, MODULE_ENTRY, STYLE_ENTRY, THEMES_ENTRY_PREFIX, } from '#auklet/css/constants';
6
7
  import { createModuleStyleEntryPlan, createExternalEntryParts, createStyleEntryParts, createThemeEntryParts, } from '#auklet/css/core/style/entries';
7
8
  import { createDevExternalStyleSpecifier, createDevModuleStyleSpecifier, createImportCode, removeStyleExtension, } from '#auklet/css/core/style/specifier';
9
+ import { toFsSpecifier } from '#auklet/utils';
8
10
  // 生成 Vite/dev 虚拟 CSS;production writer 共享入口语义,但写入真实文件。
9
11
  export class StyleCodeFactory {
10
12
  config;
@@ -12,7 +14,7 @@ export class StyleCodeFactory {
12
14
  this.config = config;
13
15
  }
14
16
  async createPackageStyleCode(parsed, cache) {
15
- return cache.getLoadResult(parsed, () => this.createUncachedPackageStyleCode(parsed, cache));
17
+ return cache.getLoadResult(parsed, async () => this.normalizeTopLevelImports(await this.createUncachedPackageStyleCode(parsed, cache)));
16
18
  }
17
19
  async createUncachedPackageStyleCode(parsed, cache) {
18
20
  const context = await cache.getContext(parsed);
@@ -43,7 +45,7 @@ export class StyleCodeFactory {
43
45
  else {
44
46
  result = await this.createSourceModuleStyleCode(context, cache, parsed.stylePath);
45
47
  }
46
- return cache.writePersistentLoadResult(parsed, context, result);
48
+ return cache.writePersistentLoadResult(parsed, context, this.normalizeTopLevelImports(result));
47
49
  }
48
50
  async createStyleCode(context, cache) {
49
51
  const results = [];
@@ -56,7 +58,7 @@ export class StyleCodeFactory {
56
58
  results.push(await this.createThemeStyleCode(context, cache, part.themeNames, false));
57
59
  continue;
58
60
  }
59
- results.push(this.createModuleStyleCode(context));
61
+ results.push(this.createFullModuleStyleCode(context));
60
62
  }
61
63
  return mergeLoadResults(...results);
62
64
  }
@@ -124,7 +126,7 @@ export class StyleCodeFactory {
124
126
  watchFiles: [],
125
127
  });
126
128
  }
127
- createModuleStyleCode(context) {
129
+ createFullModuleStyleCode(context) {
128
130
  const { styleFiles } = context.packageContext;
129
131
  const root = context.styleProcessor.createRoot();
130
132
  const seen = new Set();
@@ -139,7 +141,19 @@ export class StyleCodeFactory {
139
141
  watchFiles: [...context.configPaths, ...styleFiles],
140
142
  };
141
143
  }
144
+ createModuleStyleCode(context) {
145
+ context.packageContext.assertPreservedLocalStyleImports();
146
+ const { styleFiles } = context.packageContext;
147
+ const imports = context.packageContext
148
+ .getStyleEntryFiles()
149
+ .map((styleFile) => toFsSpecifier(styleFile));
150
+ return {
151
+ code: createImportCode(imports),
152
+ watchFiles: [...context.configPaths, ...styleFiles],
153
+ };
154
+ }
142
155
  async createSourceModuleStyleCode(context, cache, stylePath) {
156
+ context.packageContext.assertPreservedLocalStyleImports();
143
157
  const sourceModuleDir = removeStyleExtension(stylePath);
144
158
  const { styleFiles, sourceFiles } = context.packageContext;
145
159
  const entry = createModuleStyleEntryPlan(context.packageContext, sourceModuleDir);
@@ -152,8 +166,12 @@ export class StyleCodeFactory {
152
166
  const result = this.toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir);
153
167
  const parsed = this.parsePackageStyleIdInRequest(result, cache);
154
168
  if (parsed) {
155
- const loadResult = await this.createPackageStyleCode(parsed, cache);
156
- moduleStyleResults.push(this.withDependencyPackage(loadResult, parsed.packageName));
169
+ const dependencyResult = await this.createPackageStyleCode(parsed, cache);
170
+ moduleStyleResults.push(this.withDependencyPackage({
171
+ ...dependencyResult,
172
+ code: '',
173
+ }, parsed.packageName));
174
+ moduleStyleSpecifiers.push(result);
157
175
  continue;
158
176
  }
159
177
  const resolvedSpecifier = toDevDependencyImportSpecifier(context, result);
@@ -163,17 +181,7 @@ export class StyleCodeFactory {
163
181
  moduleStyleWatchFiles.push(resolvedSpecifier.watchFile);
164
182
  }
165
183
  }
166
- const root = context.styleProcessor.createRoot();
167
- const seen = new Set();
168
- for (const ownStyleFile of entry.ownStyleFiles) {
169
- const content = context.styleProcessor.readStyleFile(ownStyleFile, seen);
170
- if (content.trim()) {
171
- context.styleProcessor.appendStyleContent(root, content, ownStyleFile);
172
- }
173
- }
174
- const ownStyleCode = root.nodes?.length
175
- ? context.styleProcessor.stringify(root)
176
- : '';
184
+ const ownStyleCode = this.createOwnSourceStyleCode(context, entry.ownStyleFiles);
177
185
  return mergeLoadResults(...moduleStyleResults, {
178
186
  code: [createImportCode(moduleStyleSpecifiers), ownStyleCode]
179
187
  .filter((code) => code.trim())
@@ -187,6 +195,24 @@ export class StyleCodeFactory {
187
195
  cacheInputFiles: moduleStyleCacheInputFiles,
188
196
  });
189
197
  }
198
+ createOwnSourceStyleCode(context, styleFiles) {
199
+ const root = context.styleProcessor.createRoot();
200
+ for (const styleFile of styleFiles) {
201
+ const content = context.styleProcessor.readStyleFile(styleFile, undefined, {
202
+ mapImportSpecifier: (reference) => {
203
+ if (!context.resolver.isInsideSourceRoot(reference.imported)) {
204
+ return reference.specifier;
205
+ }
206
+ return toFsSpecifier(reference.imported);
207
+ },
208
+ shouldExpandImport: () => false,
209
+ });
210
+ if (content.trim()) {
211
+ context.styleProcessor.appendStyleContent(root, content, styleFile);
212
+ }
213
+ }
214
+ return root.nodes?.length ? context.styleProcessor.stringify(root) : '';
215
+ }
190
216
  toDevModuleImportSpecifier(specifier, context, cache, sourceStyleDir) {
191
217
  return createDevModuleStyleSpecifier(specifier, {
192
218
  sourceStyleDir,
@@ -214,4 +240,65 @@ export class StyleCodeFactory {
214
240
  dependencyPackages: Array.from(new Set([packageName, ...(result.dependencyPackages ?? [])])),
215
241
  };
216
242
  }
243
+ normalizeTopLevelImports(result) {
244
+ if (!result.code.includes('@import'))
245
+ return result;
246
+ const lateImports = [];
247
+ const removedImports = [];
248
+ const seenImportParams = new Set();
249
+ const root = postcss.parse(result.code);
250
+ let hasChanges = false;
251
+ let hasSeenStyleNode = false;
252
+ for (const node of root.nodes ?? []) {
253
+ if (this.isImportPreludeNode(node))
254
+ continue;
255
+ if (this.isTopLevelImportRule(node)) {
256
+ if (seenImportParams.has(node.params)) {
257
+ hasChanges = true;
258
+ removedImports.push(node);
259
+ continue;
260
+ }
261
+ seenImportParams.add(node.params);
262
+ if (hasSeenStyleNode) {
263
+ hasChanges = true;
264
+ lateImports.push(node.clone());
265
+ removedImports.push(node);
266
+ }
267
+ continue;
268
+ }
269
+ hasSeenStyleNode = true;
270
+ }
271
+ if (!hasChanges)
272
+ return result;
273
+ for (const node of removedImports) {
274
+ node.remove();
275
+ }
276
+ const firstStyleNode = root.nodes?.find((node) => !this.isImportPreludeNode(node) && !this.isTopLevelImportRule(node));
277
+ if (firstStyleNode) {
278
+ for (const rule of lateImports) {
279
+ firstStyleNode.before(rule);
280
+ }
281
+ }
282
+ else {
283
+ for (const rule of lateImports) {
284
+ root.append(rule);
285
+ }
286
+ }
287
+ return {
288
+ ...result,
289
+ code: root.toString(),
290
+ };
291
+ }
292
+ isTopLevelImportRule(node) {
293
+ return node.type === 'atrule' && node.name === 'import';
294
+ }
295
+ isImportPreludeNode(node) {
296
+ if (node.type === 'comment')
297
+ return true;
298
+ if (node.type !== 'atrule')
299
+ return false;
300
+ if (node.name === 'charset')
301
+ return true;
302
+ return node.name === 'layer' && !node.nodes?.length;
303
+ }
217
304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",