Skip to content

Commit 407a5c3

Browse files
authored
Add TypeScript types for the tailwind.config.js file (#7891)
* add generate-types script This script will generate the full list of core plugins, which will allow you to get code completion for the `corePlugins` section. It will also generate all the colors (and deprecated colors) which is used in multiple places in the config. * add types for the `tailwind.config.js` config file * annotate stubs with a JSDoc pointing to the types * add types to package.json - Updated the files to make sure that the types are being published - Add a `types` section in the `package.json`, otherwise your editor by default will look for the `DefinitelyTyped` types which got me really confused for a second. - Added some scripts to make sure that the generation of types happens when needed (before tests and before building). This way you never ever have to think about generating them when working on Tailwind CSS internals. * re-export types top-level Having a `colors.d.ts` next to the `colors.js` file allows us to type the `colors.js` file and your editor will pickup the types from `colors.d.ts`. * also publish generated types * update changelog * enable TypeScript only when using `init --types` for now * update tests to verify that `--types` works
1 parent 0578f7b commit 407a5c3

13 files changed

+455
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
- Support customizing class name when using `darkMode: 'class'` ([#5800](https://github.com/tailwindlabs/tailwindcss/pull/5800))
3737
- Add `--poll` option to the CLI ([#7725](https://github.com/tailwindlabs/tailwindcss/pull/7725))
3838
- Add new `border-spacing` utilities ([#7102](https://github.com/tailwindlabs/tailwindcss/pull/7102))
39+
- Add TypeScript types for the `tailwind.config.js` file ([#7891](https://github.com/tailwindlabs/tailwindcss/pull/7891))
3940

4041
## [3.0.23] - 2022-02-16
4142

colors.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { DefaultColors } from './types/generated/colors'
2+
declare const colors: DefaultColors
3+
export = colors

defaultConfig.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { Config } from './types/config'
2+
declare const config: Config
3+
export = config

defaultTheme.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { Config } from './types/config'
2+
declare const theme: Config['theme']
3+
export = theme

integrations/tailwindcss-cli/tests/cli.test.js

+41
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,46 @@ describe('Init command', () => {
319319
// multiple keys in `theme` exists. However it loads `tailwindcss/colors`
320320
// which doesn't exists in this context.
321321
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
322+
323+
expect(await readOutputFile('../full.config.js')).not.toContain(
324+
`/** @type {import('tailwindcss/types').Config} */`
325+
)
326+
})
327+
328+
test('--types', async () => {
329+
cleanupFile('simple.config.js')
330+
331+
let { combined } = await $(`${EXECUTABLE} init simple.config.js --types`)
332+
333+
expect(combined).toMatchInlineSnapshot(`
334+
"
335+
Created Tailwind CSS config file: simple.config.js
336+
"
337+
`)
338+
339+
expect(await readOutputFile('../simple.config.js')).toContain(
340+
`/** @type {import('tailwindcss/types').Config} */`
341+
)
342+
})
343+
344+
test('--full --types', async () => {
345+
cleanupFile('full.config.js')
346+
347+
let { combined } = await $(`${EXECUTABLE} init full.config.js --full --types`)
348+
349+
expect(combined).toMatchInlineSnapshot(`
350+
"
351+
Created Tailwind CSS config file: full.config.js
352+
"
353+
`)
354+
355+
// Not a clean way to test this. We could require the file and verify that
356+
// multiple keys in `theme` exists. However it loads `tailwindcss/colors`
357+
// which doesn't exists in this context.
358+
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
359+
expect(await readOutputFile('../full.config.js')).toContain(
360+
`/** @type {import('tailwindcss/types').Config} */`
361+
)
322362
})
323363

324364
test('--postcss', async () => {
@@ -351,6 +391,7 @@ describe('Init command', () => {
351391
Options:
352392
-f, --full Initialize a full \`tailwind.config.js\` file
353393
-p, --postcss Initialize a \`postcss.config.js\` file
394+
--types Add TypeScript types for the \`tailwind.config.js\` file
354395
-h, --help Display usage information
355396
`)
356397
)

package.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
55
"license": "MIT",
66
"main": "lib/index.js",
7-
"style": "dist/tailwind.css",
7+
"types": "types/index.d.ts",
88
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
99
"bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
1010
"homepage": "https://tailwindcss.com",
@@ -13,18 +13,20 @@
1313
"tailwindcss": "lib/cli.js"
1414
},
1515
"scripts": {
16-
"preswcify": "npm run generate:plugin-list && rimraf lib",
16+
"preswcify": "npm run generate && rimraf lib",
1717
"swcify": "swc src --out-dir lib --copy-files",
1818
"postswcify": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js",
1919
"rebuild-fixtures": "npm run swcify && node -r @swc/register scripts/rebuildFixtures.js",
2020
"prepublishOnly": "npm install --force && npm run swcify",
2121
"style": "eslint .",
22-
"pretest": "npm run generate:plugin-list",
22+
"pretest": "npm run generate",
2323
"test": "jest",
2424
"test:integrations": "npm run test --prefix ./integrations",
2525
"install:integrations": "node scripts/install-integrations.js",
2626
"posttest": "npm run style",
27-
"generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js"
27+
"generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js",
28+
"generate:types": "node -r @swc/register scripts/generate-types.js",
29+
"generate": "npm run generate:plugin-list && npm run generate:types"
2830
},
2931
"files": [
3032
"src/*",
@@ -34,6 +36,8 @@
3436
"scripts/*.js",
3537
"stubs/*.stub.js",
3638
"nesting/*",
39+
"types/**/*",
40+
"*.d.ts",
3741
"*.css",
3842
"*.js"
3943
],

plugin.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Config, PluginCreator } from './types/config'
2+
declare function createPlugin(
3+
plugin: PluginCreator,
4+
config?: Config
5+
): { handler: PluginCreator; config?: Config }
6+
export = createPlugin

scripts/generate-types.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import prettier from 'prettier'
2+
import { corePlugins } from '../src/corePlugins'
3+
import colors from '../src/public/colors'
4+
import fs from 'fs'
5+
import path from 'path'
6+
7+
fs.writeFileSync(
8+
path.join(process.cwd(), 'types', 'generated', 'corePluginList.d.ts'),
9+
`export type CorePluginList = ${Object.keys(corePlugins)
10+
.map((p) => `'${p}'`)
11+
.join(' | ')}`
12+
)
13+
14+
let colorsWithoutDeprecatedColors = Object.fromEntries(
15+
Object.entries(Object.getOwnPropertyDescriptors(colors))
16+
.filter(([_, { value }]) => {
17+
return typeof value !== 'undefined'
18+
})
19+
.map(([name, definition]) => [name, definition.value])
20+
)
21+
22+
let deprecatedColors = Object.entries(Object.getOwnPropertyDescriptors(colors))
23+
.filter(([_, { value }]) => {
24+
return typeof value === 'undefined'
25+
})
26+
.map(([name, definition]) => {
27+
let warn = console.warn
28+
let messages = []
29+
console.warn = (...args) => messages.push(args.pop())
30+
definition.get()
31+
console.warn = warn
32+
let message = messages.join(' ').trim()
33+
let newColor = message.match(/renamed to `(.*)`/)[1]
34+
return `/** @deprecated ${message} */${name}: DefaultColors['${newColor}'],`
35+
})
36+
.join('\n')
37+
38+
fs.writeFileSync(
39+
path.join(process.cwd(), 'types', 'generated', 'colors.d.ts'),
40+
prettier.format(
41+
`export interface DefaultColors { ${JSON.stringify(colorsWithoutDeprecatedColors).slice(
42+
1,
43+
-1
44+
)}\n${deprecatedColors}\n}`,
45+
{
46+
semi: false,
47+
singleQuote: true,
48+
printWidth: 100,
49+
parser: 'typescript',
50+
}
51+
)
52+
)

src/cli.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ let commands = {
151151
args: {
152152
'--full': { type: Boolean, description: 'Initialize a full `tailwind.config.js` file' },
153153
'--postcss': { type: Boolean, description: 'Initialize a `postcss.config.js` file' },
154+
'--types': {
155+
type: Boolean,
156+
description: 'Add TypeScript types for the `tailwind.config.js` file',
157+
},
154158
'-f': '--full',
155159
'-p': '--postcss',
156160
},
@@ -209,7 +213,7 @@ if (
209213
help({
210214
usage: [
211215
'tailwindcss [--input input.css] [--output output.css] [--watch] [options...]',
212-
'tailwindcss init [--full] [--postcss] [options...]',
216+
'tailwindcss init [--full] [--postcss] [--types] [options...]',
213217
],
214218
commands: Object.keys(commands)
215219
.filter((command) => command !== 'build')
@@ -336,6 +340,13 @@ function init() {
336340
'utf8'
337341
)
338342

343+
if (args['--types']) {
344+
let typesHeading = "/** @type {import('tailwindcss/types').Config} */"
345+
stubFile =
346+
stubFile.replace(`module.exports = `, `${typesHeading}\nconst config = `) +
347+
'\nmodule.exports = config'
348+
}
349+
339350
// Change colors import
340351
stubFile = stubFile.replace('../colors', 'tailwindcss/colors')
341352

types.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { Config } from './types/config'

0 commit comments

Comments
 (0)