auklet 0.2.4 → 0.2.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/README.md CHANGED
@@ -161,6 +161,7 @@ export const config = defineConfig({
161
161
  light: './src/themes/light.css',
162
162
  dark: './src/themes/dark.css',
163
163
  },
164
+ shared: ['./src/internal/**/*.css'],
164
165
  dependencies: {
165
166
  '@scope/ui': {
166
167
  entry: '/style.css',
@@ -170,3 +171,15 @@ export const config = defineConfig({
170
171
  },
171
172
  });
172
173
  ```
174
+
175
+ `styles.shared` declares same-package CSS fragments that component CSS may
176
+ import directly. Matched files must live under the current source root. The
177
+ pattern syntax is a small glob subset: `*`, `**`, and `?`.
178
+
179
+ For example, `components/CodeBlock/index.css` may import
180
+ `../../internal/syntaxHighlight.css` when it matches `styles.shared`; component
181
+ CSS outputs inline that shared CSS and its local helper CSS imports, while
182
+ package-level CSS dedupes repeated shared imports. Shared CSS cannot import
183
+ component CSS or theme CSS. Component-to-component CSS imports are still
184
+ rejected; package CSS dependencies should be expressed through
185
+ `styles.dependencies`.
package/dist/config.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare const aukletDefaultOptions: {
12
12
  };
13
13
  styles: {
14
14
  themes: {};
15
+ shared: never[];
15
16
  dependencies: {};
16
17
  };
17
18
  };
@@ -33,6 +34,7 @@ export declare function normalizeAukletConfig(config?: AukletConfig): {
33
34
  };
34
35
  styles: {
35
36
  themes: {};
37
+ shared: string[];
36
38
  dependencies: {
37
39
  [k: string]: {
38
40
  entry: string | string[] | undefined;
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { isArray, isString } from 'aidly';
1
2
  export const aukletConfigFiles = ['auklet.config.js', 'auklet.config.mjs'];
2
3
  export function isAukletConfigFile(file) {
3
4
  return aukletConfigFiles.includes(file);
@@ -13,6 +14,7 @@ export const aukletDefaultOptions = {
13
14
  },
14
15
  styles: {
15
16
  themes: {},
17
+ shared: [],
16
18
  dependencies: {},
17
19
  },
18
20
  };
@@ -21,6 +23,20 @@ const normalizeStyleDependency = (dependency) => ({
21
23
  themes: dependency.themes,
22
24
  components: dependency.components,
23
25
  });
26
+ const normalizeStyleShared = (shared) => {
27
+ if (isArray(shared)) {
28
+ for (const pattern of shared) {
29
+ if (!isString(pattern)) {
30
+ throw new Error('[config] styles.shared must be a string or an array of strings.');
31
+ }
32
+ }
33
+ return shared;
34
+ }
35
+ if (shared && !isString(shared)) {
36
+ throw new Error('[config] styles.shared must be a string or an array of strings.');
37
+ }
38
+ return shared ? [shared] : [];
39
+ };
24
40
  export function normalizeAukletConfig(config = {}) {
25
41
  const dependencies = config.styles?.dependencies ?? aukletDefaultOptions.styles.dependencies;
26
42
  return {
@@ -33,6 +49,7 @@ export function normalizeAukletConfig(config = {}) {
33
49
  },
34
50
  styles: {
35
51
  themes: config.styles?.themes ?? aukletDefaultOptions.styles.themes,
52
+ shared: normalizeStyleShared(config.styles?.shared ?? aukletDefaultOptions.styles.shared),
36
53
  dependencies: Object.fromEntries(Object.entries(dependencies).map(([packageName, dependency]) => [
37
54
  packageName,
38
55
  normalizeStyleDependency(dependency),
@@ -0,0 +1,7 @@
1
+ export type SharedStyleFileOptions = {
2
+ packageRoot: string;
3
+ sourceRoot: string;
4
+ styleFiles: Array<string>;
5
+ patterns: Array<string>;
6
+ };
7
+ export declare function createSharedStyleFileKeySet(options: SharedStyleFileOptions): Set<string>;
@@ -0,0 +1,59 @@
1
+ import path from 'node:path';
2
+ import { normalizeFileKey, toPosixPath } from '#auklet/utils';
3
+ export function createSharedStyleFileKeySet(options) {
4
+ const patterns = options.patterns.map((pattern) => createSharedStyleMatcher(options, pattern));
5
+ if (!patterns.length)
6
+ return new Set();
7
+ return new Set(options.styleFiles
8
+ .filter((styleFile) => patterns.some((matcher) => matcher(styleFile)))
9
+ .map((styleFile) => normalizeFileKey(styleFile)));
10
+ }
11
+ const createSharedStyleMatcher = (options, pattern) => {
12
+ const absolutePattern = normalizePatternPath(path.resolve(options.packageRoot, pattern));
13
+ if (!isInsideSourceRoot(absolutePattern, options.sourceRoot)) {
14
+ throw new Error(`[css] styles.shared pattern must resolve under source root: ${pattern}`);
15
+ }
16
+ const matcher = globToRegExp(absolutePattern);
17
+ return (file) => matcher.test(normalizeFileKey(file));
18
+ };
19
+ const normalizePatternPath = (value) => {
20
+ return toPosixPath(path.normalize(value));
21
+ };
22
+ const isInsideSourceRoot = (file, sourceRoot) => {
23
+ const relative = path.relative(sourceRoot, file);
24
+ return (Boolean(relative) &&
25
+ !relative.startsWith('..') &&
26
+ !path.isAbsolute(relative));
27
+ };
28
+ const globToRegExp = (pattern) => {
29
+ let source = '^';
30
+ for (let index = 0; index < pattern.length; index += 1) {
31
+ const char = pattern[index];
32
+ const nextChar = pattern[index + 1];
33
+ if (char === '*' && nextChar === '*') {
34
+ const afterGlob = pattern[index + 2];
35
+ if (afterGlob === '/') {
36
+ source += '(?:.*/)?';
37
+ index += 2;
38
+ }
39
+ else {
40
+ source += '.*';
41
+ index += 1;
42
+ }
43
+ continue;
44
+ }
45
+ if (char === '*') {
46
+ source += '[^/]*';
47
+ continue;
48
+ }
49
+ if (char === '?') {
50
+ source += '[^/]';
51
+ continue;
52
+ }
53
+ source += escapeRegExp(char);
54
+ }
55
+ return new RegExp(`${source}$`);
56
+ };
57
+ const escapeRegExp = (value) => {
58
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
59
+ };
@@ -26,4 +26,7 @@ export declare class StyleModuleEntryPlanner {
26
26
  private rejectCrossModuleStyleImports;
27
27
  private getStyleFileModuleDir;
28
28
  private toRelativeSourceFile;
29
+ private isImportedStyleFile;
30
+ private isSharedStyleFile;
31
+ private isInsideSourceRoot;
29
32
  }
@@ -42,11 +42,13 @@ export class StyleModuleEntryPlanner {
42
42
  }
43
43
  getOwnStyleDirs() {
44
44
  return Array.from(this.styleFilesByDir.entries())
45
- .filter(([, dirStyleFiles]) => dirStyleFiles.some((styleFile) => !this.importedStyleFiles.has(path.resolve(styleFile))))
45
+ .filter(([, dirStyleFiles]) => dirStyleFiles.some((styleFile) => !this.isImportedStyleFile(styleFile) &&
46
+ !this.isSharedStyleFile(styleFile)))
46
47
  .map(([sourceDir]) => sourceDir);
47
48
  }
48
49
  getOwnStyleFiles(sourceDir) {
49
- return (this.styleFilesByDir.get(sourceDir) ?? []).filter((styleFile) => !this.importedStyleFiles.has(path.resolve(styleFile)));
50
+ return (this.styleFilesByDir.get(sourceDir) ?? []).filter((styleFile) => !this.isImportedStyleFile(styleFile) &&
51
+ !this.isSharedStyleFile(styleFile));
50
52
  }
51
53
  rejectCrossModuleStyleImports() {
52
54
  if (!this.packageContext.normalizedConfig.modules)
@@ -54,6 +56,19 @@ export class StyleModuleEntryPlanner {
54
56
  const styleFileKeys = new Set(this.packageContext.styleFiles.map((file) => path.resolve(file)));
55
57
  const imports = this.packageContext.styleProcessor.collectImportedStyleFileReferences(this.packageContext.styleFiles);
56
58
  for (const item of imports) {
59
+ if (!this.isInsideSourceRoot(item.imported)) {
60
+ const importer = this.toRelativeSourceFile(item.importer);
61
+ const imported = toPosixPath(item.imported);
62
+ throw new Error(`[css] cross-package CSS import detected: ${importer} imports ${imported}. ` +
63
+ 'Use styles.dependencies to express package style dependencies.');
64
+ }
65
+ if (this.packageContext.shouldInlineSharedStyleImport(item))
66
+ continue;
67
+ if (this.packageContext.isSharedStyleFile(item.importer)) {
68
+ const importer = this.toRelativeSourceFile(item.importer);
69
+ const imported = this.toRelativeSourceFile(item.imported);
70
+ throw new Error(`[css] shared CSS import must target non-module source CSS: ${importer} imports ${imported}.`);
71
+ }
57
72
  if (!styleFileKeys.has(item.imported))
58
73
  continue;
59
74
  const importerModuleDir = this.getStyleFileModuleDir(item.importer);
@@ -82,4 +97,14 @@ export class StyleModuleEntryPlanner {
82
97
  toRelativeSourceFile(file) {
83
98
  return toPosixPath(path.relative(this.packageContext.sourceRoot, file));
84
99
  }
100
+ isImportedStyleFile(styleFile) {
101
+ return this.importedStyleFiles.has(path.resolve(styleFile));
102
+ }
103
+ isSharedStyleFile(styleFile) {
104
+ return this.packageContext.isSharedStyleFile(styleFile);
105
+ }
106
+ isInsideSourceRoot(file) {
107
+ const relative = path.relative(this.packageContext.sourceRoot, file);
108
+ return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
109
+ }
85
110
  }
@@ -1,6 +1,6 @@
1
1
  import { ModuleStyleImportCollector } from '#auklet/css/core/styleImports/collector';
2
2
  import { StyleModuleEntryPlanner } from '#auklet/css/core/styleModuleEntryPlanner';
3
- import { StyleProcessor } from '#auklet/css/core/styleProcessor';
3
+ import { type StyleFileImportReference, StyleProcessor } from '#auklet/css/core/styleProcessor';
4
4
  import { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleResolver';
5
5
  import type { ModuleStyleBuildConfig, NormalizedAukletConfig, ResolvedModuleStyleBuildContext } from '#auklet/types';
6
6
  export type StylePackageContextOptions = {
@@ -19,10 +19,21 @@ export declare class StylePackageContext {
19
19
  readonly themeFiles: Map<string, string>;
20
20
  readonly themeNames: Array<string>;
21
21
  readonly styleFiles: Array<string>;
22
+ private readonly sourceModuleDirs;
23
+ private readonly themeStyleFileKeys;
24
+ private readonly sharedStyleFileKeys;
22
25
  private moduleStyleEntryPlanner?;
23
26
  private moduleStyleImports?;
24
27
  constructor(options: StylePackageContextOptions);
25
28
  getStyleFiles(files: Array<string>): string[];
26
29
  getModuleStyleImports(): Map<string, string[]>;
27
30
  getModuleStyleEntryPlanner(): StyleModuleEntryPlanner;
31
+ isSharedStyleFile(file: string): boolean;
32
+ shouldInlineSharedStyleImport(reference: StyleFileImportReference): boolean;
33
+ private isSharedHelperStyleFile;
34
+ private getSourceModuleDirs;
35
+ private isSourceModuleStyleFile;
36
+ private isThemeStyleFile;
37
+ private isInsideSourceRoot;
38
+ private toSourceRelativePath;
28
39
  }
@@ -1,12 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { SOURCE_MODULE_RE } from '#auklet/css/constants';
3
4
  import { ModuleStyleImportCollector } from '#auklet/css/core/styleImports/collector';
4
5
  import { StyleModuleEntryPlanner } from '#auklet/css/core/styleModuleEntryPlanner';
5
- import { StyleProcessor } from '#auklet/css/core/styleProcessor';
6
+ import { StyleProcessor, } from '#auklet/css/core/styleProcessor';
6
7
  import { WorkspaceStyleResolver } from '#auklet/css/core/workspaceStyleResolver';
7
8
  import { createStyleFileKeySet } from '#auklet/css/core/style/files';
9
+ import { createSharedStyleFileKeySet } from '#auklet/css/core/style/shared';
8
10
  import { getThemeNames, resolveThemeStyleFiles, } from '#auklet/css/core/style/dependencies';
9
- import { fileWalker, normalizeFileKey } from '#auklet/utils';
11
+ import { fileWalker, getSourceModuleDir, normalizeFileKey, toPosixPath, } from '#auklet/utils';
10
12
  export class StylePackageContext {
11
13
  options;
12
14
  normalizedConfig;
@@ -18,6 +20,9 @@ export class StylePackageContext {
18
20
  themeFiles;
19
21
  themeNames;
20
22
  styleFiles;
23
+ sourceModuleDirs;
24
+ themeStyleFileKeys;
25
+ sharedStyleFileKeys;
21
26
  moduleStyleEntryPlanner;
22
27
  moduleStyleImports;
23
28
  constructor(options) {
@@ -33,13 +38,20 @@ export class StylePackageContext {
33
38
  : [];
34
39
  this.themeFiles = resolveThemeStyleFiles(normalizedConfig, context.packageRoot);
35
40
  this.themeNames = getThemeNames(normalizedConfig);
41
+ this.themeStyleFileKeys = createStyleFileKeySet(this.themeFiles.values());
42
+ this.sourceModuleDirs = this.getSourceModuleDirs(this.sourceFiles);
36
43
  this.styleFiles = this.getStyleFiles(this.sourceFiles);
44
+ this.sharedStyleFileKeys = createSharedStyleFileKeySet({
45
+ packageRoot: context.packageRoot,
46
+ sourceRoot: this.sourceRoot,
47
+ styleFiles: this.styleFiles,
48
+ patterns: normalizedConfig.styles.shared,
49
+ });
37
50
  }
38
51
  getStyleFiles(files) {
39
- const themeFileKeys = createStyleFileKeySet(this.themeFiles.values());
40
52
  return files
41
53
  .filter((file) => this.options.config.styleExtensions.includes(path.extname(file)))
42
- .filter((styleFile) => !themeFileKeys.has(normalizeFileKey(styleFile)));
54
+ .filter((styleFile) => !this.themeStyleFileKeys.has(normalizeFileKey(styleFile)));
43
55
  }
44
56
  getModuleStyleImports() {
45
57
  this.moduleStyleImports ??= this.importCollector.collect(this.sourceFiles, this.normalizedConfig);
@@ -49,4 +61,49 @@ export class StylePackageContext {
49
61
  this.moduleStyleEntryPlanner ??= new StyleModuleEntryPlanner(this);
50
62
  return this.moduleStyleEntryPlanner;
51
63
  }
64
+ isSharedStyleFile(file) {
65
+ return this.sharedStyleFileKeys.has(normalizeFileKey(file));
66
+ }
67
+ shouldInlineSharedStyleImport(reference) {
68
+ return ((this.isSharedStyleFile(reference.imported) &&
69
+ this.isSharedHelperStyleFile(reference.imported)) ||
70
+ (this.isSharedStyleFile(reference.importer) &&
71
+ this.isSharedHelperStyleFile(reference.imported)));
72
+ }
73
+ isSharedHelperStyleFile(file) {
74
+ return (this.isInsideSourceRoot(file) &&
75
+ !this.isThemeStyleFile(file) &&
76
+ !this.isSourceModuleStyleFile(file));
77
+ }
78
+ getSourceModuleDirs(files) {
79
+ return new Set(files
80
+ .filter((file) => SOURCE_MODULE_RE.test(file))
81
+ .map((sourceFile) => toPosixPath(getSourceModuleDir(path.relative(this.sourceRoot, sourceFile)))));
82
+ }
83
+ isSourceModuleStyleFile(file) {
84
+ const sourceRelative = this.toSourceRelativePath(file);
85
+ if (!sourceRelative)
86
+ return false;
87
+ const styleModuleDir = toPosixPath(getSourceModuleDir(sourceRelative));
88
+ if (this.sourceModuleDirs.has(styleModuleDir))
89
+ return true;
90
+ for (const sourceModuleDir of this.sourceModuleDirs) {
91
+ if (sourceRelative.startsWith(`${sourceModuleDir}/`))
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ isThemeStyleFile(file) {
97
+ return this.themeStyleFileKeys.has(normalizeFileKey(file));
98
+ }
99
+ isInsideSourceRoot(file) {
100
+ return this.toSourceRelativePath(file) !== null;
101
+ }
102
+ toSourceRelativePath(file) {
103
+ const relative = path.relative(this.sourceRoot, file);
104
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
105
+ return null;
106
+ }
107
+ return toPosixPath(relative);
108
+ }
52
109
  }
@@ -5,6 +5,9 @@ export type StyleFileImportReference = {
5
5
  importer: string;
6
6
  imported: string;
7
7
  };
8
+ export type StyleFileImportExpandOptions = {
9
+ shouldExpandImport?: (reference: StyleFileImportReference) => boolean;
10
+ };
8
11
  export declare class StyleProcessor {
9
12
  private readonly config;
10
13
  private readonly resolver;
@@ -13,7 +16,7 @@ export declare class StyleProcessor {
13
16
  appendImportRule(root: Root, specifier: string): void;
14
17
  stringify(root: Root): string;
15
18
  appendStyleContent(target: Root, content: string, from: string): void;
16
- readStyleFile(stylePath: string, seen?: Set<string>): string;
19
+ readStyleFile(stylePath: string, seen?: Set<string>, options?: StyleFileImportExpandOptions): string;
17
20
  collectImportedStyleFiles(styleFiles: Array<string>): Set<string>;
18
21
  collectImportedStyleFileReferences(styleFiles: Array<string>): StyleFileImportReference[];
19
22
  collectStyleImportSpecifiers(styleFiles: Array<string>): Set<string>;
@@ -32,7 +32,7 @@ export class StyleProcessor {
32
32
  }
33
33
  target.append(...(root.nodes ?? []));
34
34
  }
35
- readStyleFile(stylePath, seen = new Set()) {
35
+ readStyleFile(stylePath, seen = new Set(), options = {}) {
36
36
  if (!fs.existsSync(stylePath)) {
37
37
  return '';
38
38
  }
@@ -52,7 +52,13 @@ export class StyleProcessor {
52
52
  const importedPath = this.resolver.resolveStyleDependency(specifier, path.dirname(stylePath));
53
53
  if (!importedPath)
54
54
  continue;
55
- const content = this.readStyleFile(importedPath, seen);
55
+ const reference = {
56
+ importer: normalizedPath,
57
+ imported: path.resolve(importedPath),
58
+ };
59
+ if (options.shouldExpandImport?.(reference) === false)
60
+ continue;
61
+ const content = this.readStyleFile(importedPath, seen, options);
56
62
  if (!content.trim()) {
57
63
  rule.remove();
58
64
  continue;
@@ -1,6 +1,8 @@
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;
5
+ private readonly styleProcessor;
4
6
  constructor(options: FormatWriterOptions);
5
7
  copy(files: Array<string>, outRoot: string): void;
6
8
  }
@@ -1,16 +1,22 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
2
  import { writeStyleFile, } from '#auklet/css/production/format/shared';
4
3
  export class SourceStyleFileWriter {
5
4
  sourceRoot;
5
+ packageContext;
6
+ styleProcessor;
6
7
  constructor(options) {
7
8
  this.sourceRoot = options.packageContext.sourceRoot;
9
+ this.packageContext = options.packageContext;
10
+ this.styleProcessor = options.packageContext.styleProcessor;
8
11
  }
9
12
  copy(files, outRoot) {
10
13
  for (const sourceFile of files) {
11
14
  const relative = path.relative(this.sourceRoot, sourceFile);
12
15
  const target = path.join(outRoot, relative);
13
- writeStyleFile(target, fs.readFileSync(sourceFile, 'utf8'));
16
+ const content = this.styleProcessor.readStyleFile(sourceFile, undefined, {
17
+ shouldExpandImport: (reference) => this.packageContext.shouldInlineSharedStyleImport(reference),
18
+ });
19
+ writeStyleFile(target, content);
14
20
  }
15
21
  }
16
22
  }
@@ -53,6 +53,7 @@ export declare class ModuleStyleGraphRequestCache {
53
53
  };
54
54
  styles: {
55
55
  themes: {};
56
+ shared: string[];
56
57
  dependencies: {
57
58
  [k: string]: {
58
59
  entry: string | string[] | undefined;
package/dist/types.d.ts CHANGED
@@ -6,6 +6,7 @@ export type StyleDependencyGroup = {
6
6
  };
7
7
  export type StyleOptions = {
8
8
  themes?: Record<string, string>;
9
+ shared?: string | Array<string>;
9
10
  dependencies?: Record<string, StyleDependencyGroup>;
10
11
  };
11
12
  export type NormalizedStyleDependencyGroup = {
@@ -19,6 +20,7 @@ export interface NormalizedAukletConfig {
19
20
  modules: boolean;
20
21
  styles: {
21
22
  themes: Record<string, string>;
23
+ shared: Array<string>;
22
24
  dependencies: Record<string, NormalizedStyleDependencyGroup>;
23
25
  };
24
26
  build: Required<Pick<PackageBuildOptions, 'formats' | 'target' | 'platform'>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auklet",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "author": "chentao.arthur",
6
6
  "description": "Build utilities for TypeScript packages and module CSS output.",