1- import type { CSSModule , MatchesPattern , Resolver } from './type.js' ;
1+ import type { CSSModule , MatchesPattern , Resolver , Token , TokenImporter } from './type.js' ;
22
33export const STYLES_EXPORT_NAME = 'styles' ;
44
55export interface CreateDtsOptions {
66 resolver : Resolver ;
77 matchesPattern : MatchesPattern ;
8+ namedExports : boolean ;
89}
910
1011interface CodeMapping {
@@ -29,8 +30,122 @@ interface LinkedCodeMapping extends CodeMapping {
2930 generatedLengths : number [ ] ;
3031}
3132
33+ interface CreateDtsResult {
34+ text : string ;
35+ mapping : CodeMapping ;
36+ linkedCodeMapping : LinkedCodeMapping ;
37+ }
38+
39+ /**
40+ * Create a d.ts file.
41+ */
42+ export function createDts ( cssModules : CSSModule , options : CreateDtsOptions ) : CreateDtsResult {
43+ // Filter external files
44+ const tokenImporters = cssModules . tokenImporters . filter ( ( tokenImporter ) => {
45+ const resolved = options . resolver ( tokenImporter . from , { request : cssModules . fileName } ) ;
46+ return resolved !== undefined && options . matchesPattern ( resolved ) ;
47+ } ) ;
48+ if ( options . namedExports ) {
49+ return createNamedExportsDts ( cssModules . localTokens , tokenImporters ) ;
50+ } else {
51+ return createDefaultExportDts ( cssModules . localTokens , tokenImporters ) ;
52+ }
53+ }
54+
3255/**
33- * Create a d.ts file from a CSS module file.
56+ * Create a d.ts file with named exports.
57+ * @example
58+ * If the CSS module file is:
59+ * ```css
60+ * @import './a.module.css';
61+ * @value local1: string;
62+ * @value imported1, imported2 as aliasedImported2 from './b.module.css';
63+ * .local2 { color: red }
64+ * ```
65+ * The d.ts file would be:
66+ * ```ts
67+ * // @ts -nocheck
68+ * export var local1: string;
69+ * export var local2: string;
70+ * export * from './a.module.css';
71+ * export {
72+ * imported1,
73+ * imported2 as aliasedImported2,
74+ * } from './b.module.css';
75+ * ```
76+ */
77+ function createNamedExportsDts (
78+ localTokens : Token [ ] ,
79+ tokenImporters : TokenImporter [ ] ,
80+ ) : { text : string ; mapping : CodeMapping ; linkedCodeMapping : LinkedCodeMapping } {
81+ const mapping : CodeMapping = { sourceOffsets : [ ] , lengths : [ ] , generatedOffsets : [ ] } ;
82+ const linkedCodeMapping : LinkedCodeMapping = {
83+ sourceOffsets : [ ] ,
84+ lengths : [ ] ,
85+ generatedOffsets : [ ] ,
86+ generatedLengths : [ ] ,
87+ } ;
88+
89+ // MEMO: Depending on the TypeScript compilation options, the generated type definition file contains compile errors.
90+ // For example, it contains `Top-level 'await' expressions are only allowed when the 'module' option is set to ...` error.
91+ //
92+ // If `--skipLibCheck` is false, those errors will be reported by `tsc`. However, these are negligible errors.
93+ // Therefore, `@ts-nocheck` is added to the generated type definition file.
94+ let text = `// @ts-nocheck\n` ;
95+
96+ for ( const token of localTokens ) {
97+ text += `export var ` ;
98+ mapping . sourceOffsets . push ( token . loc . start . offset ) ;
99+ mapping . generatedOffsets . push ( text . length ) ;
100+ mapping . lengths . push ( token . name . length ) ;
101+ text += `${ token . name } : string;\n` ;
102+ }
103+ for ( const tokenImporter of tokenImporters ) {
104+ if ( tokenImporter . type === 'import' ) {
105+ text += `export * from ` ;
106+ mapping . sourceOffsets . push ( tokenImporter . fromLoc . start . offset - 1 ) ;
107+ mapping . lengths . push ( tokenImporter . from . length + 2 ) ;
108+ mapping . generatedOffsets . push ( text . length ) ;
109+ text += `'${ tokenImporter . from } ';\n` ;
110+ } else {
111+ text += `export {\n` ;
112+ // eslint-disable-next-line no-loop-func
113+ tokenImporter . values . forEach ( ( value ) => {
114+ const localName = value . localName ?? value . name ;
115+ const localLoc = value . localLoc ?? value . loc ;
116+ text += ` ` ;
117+ if ( 'localName' in value ) {
118+ mapping . sourceOffsets . push ( value . loc . start . offset ) ;
119+ mapping . lengths . push ( value . name . length ) ;
120+ mapping . generatedOffsets . push ( text . length ) ;
121+ linkedCodeMapping . generatedOffsets . push ( text . length ) ;
122+ linkedCodeMapping . generatedLengths . push ( value . name . length ) ;
123+ text += `${ value . name } as ` ;
124+ mapping . sourceOffsets . push ( localLoc . start . offset ) ;
125+ mapping . lengths . push ( localName . length ) ;
126+ mapping . generatedOffsets . push ( text . length ) ;
127+ linkedCodeMapping . sourceOffsets . push ( text . length ) ;
128+ linkedCodeMapping . lengths . push ( localName . length ) ;
129+ text += `${ localName } ,\n` ;
130+ } else {
131+ mapping . sourceOffsets . push ( value . loc . start . offset ) ;
132+ mapping . lengths . push ( value . name . length ) ;
133+ mapping . generatedOffsets . push ( text . length ) ;
134+ text += `${ value . name } ,\n` ;
135+ }
136+ } ) ;
137+ text += `} from ` ;
138+ mapping . sourceOffsets . push ( tokenImporter . fromLoc . start . offset - 1 ) ;
139+ mapping . lengths . push ( tokenImporter . from . length + 2 ) ;
140+ mapping . generatedOffsets . push ( text . length ) ;
141+ text += `'${ tokenImporter . from } ';\n` ;
142+ }
143+ }
144+ return { text, mapping, linkedCodeMapping } ;
145+ }
146+
147+ /**
148+ * Create a d.ts file with a default export.
34149 * @example
35150 * If the CSS module file is:
36151 * ```css
@@ -52,9 +167,9 @@ interface LinkedCodeMapping extends CodeMapping {
52167 * export default styles;
53168 * ```
54169 */
55- export function createDts (
56- { fileName , localTokens, tokenImporters : _tokenImporters } : CSSModule ,
57- options : CreateDtsOptions ,
170+ function createDefaultExportDts (
171+ localTokens : Token [ ] ,
172+ tokenImporters : TokenImporter [ ] ,
58173) : { text : string ; mapping : CodeMapping ; linkedCodeMapping : LinkedCodeMapping } {
59174 const mapping : CodeMapping = { sourceOffsets : [ ] , lengths : [ ] , generatedOffsets : [ ] } ;
60175 const linkedCodeMapping : LinkedCodeMapping = {
@@ -64,12 +179,6 @@ export function createDts(
64179 generatedLengths : [ ] ,
65180 } ;
66181
67- // Filter external files
68- const tokenImporters = _tokenImporters . filter ( ( tokenImporter ) => {
69- const resolved = options . resolver ( tokenImporter . from , { request : fileName } ) ;
70- return resolved !== undefined && options . matchesPattern ( resolved ) ;
71- } ) ;
72-
73182 // MEMO: Depending on the TypeScript compilation options, the generated type definition file contains compile errors.
74183 // For example, it contains `Top-level 'await' expressions are only allowed when the 'module' option is set to ...` error.
75184 //
0 commit comments