Skip to content

Commit

Permalink
feat: support new twind features
Browse files Browse the repository at this point in the history
  • Loading branch information
sastan committed Mar 23, 2021
1 parent f0e5285 commit e0e592d
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 181 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ typings/

# Yarn Integrity file
.yarn-integrity
demo
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# @twind/cli

<div align="center">

# @twind/cli

[![MIT License](https://flat.badgen.net/github/license/tw-in-js/twind-cli)](https://github.com/tw-in-js/twind-cli/blob/main/LICENSE)
[![Latest Release](https://flat.badgen.net/npm/v/@twind/cli?icon=npm&label&cache=10800&color=blue)](https://www.npmjs.com/package/@twind/cli)
[![Github](https://flat.badgen.net/badge/icon/tw-in-js%2Ftwind-cli?icon=github&label)](https://github.com/tw-in-js/twind-cli)
Expand Down Expand Up @@ -41,10 +42,10 @@ twind -w
# Generate beautified css file
twind -b

# Use different twind config (esm or cjs)
# Use different twind config (ts, esm, or cjs)
twind -c src/twind.config.js

# Use different tailwind config (esm or cjs)
# Use different tailwind config (ts, esm, or cjs)
twind -c tailwind.prod.js
```

Expand Down
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"bin": {
"twind": "bin/twind.js"
},
"browser": false,
"sideEffects": false,
"// The 'module', 'unpkg' and 'types' fields are added by distilt": "",
"main": "./src/index.ts",
"// Each entry is expanded into several bundles (module, script, types, require, node, and default)": "",
Expand All @@ -38,16 +40,18 @@
},
"dependencies": {
"chokidar": "^3.5.1",
"esbuild": "^0.8.36",
"esbuild": "^0.9.6",
"find-up": "^5.0.0",
"ignore": "^5.1.8",
"kleur": "^4.1.4",
"locate-path": "^6.0.0",
"p-debounce": "^2.1.0",
"p-event": "^4.2.0",
"sade": "^1.7.4",
"sucrase": "^3.17.1",
"supports-color": "^8.1.1",
"time-span": "^4.0.0",
"twind": ">=0.15.8",
"twind": "^0.16.9",
"v8-compile-cache": "^2.2.0"
},
"peerDependencies": {
Expand All @@ -64,8 +68,8 @@
"@types/sade": "^1.7.2",
"@types/supports-color": "^7.2.0",
"c8": "^7.3.5",
"distilt": "^0.9.6",
"esbuild-register": "^2.0.0",
"distilt": "^0.10.4",
"esbuild-register": "^2.3.0",
"esm": "^3.2.25",
"prettier": "^2.0.5",
"typescript": "^4.1.3",
Expand All @@ -76,6 +80,7 @@
"build": "distilt",
"format": "prettier --write --ignore-path .gitignore .",
"release": "npx np --contents dist",
"ts": "node -r esbuild-register -r esm",
"test": "uvu -r esm -r esbuild-register . test.ts",
"test:coverage": "c8 --src index.ts --all -r lcov -r text yarn test",
"test:watch": "watchlist . -- yarn test",
Expand Down
4 changes: 2 additions & 2 deletions src/__fixtures__/test.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export default function App() {
return (
<div className="App text-center">
<div className=" text-center [lang]:bg-purple-200 [asas]:underline m-[10] grid-cols-[1fr,200px,minmax(15%,500px)]">
{/* Delete the two lines below */}
<h1
className={`text-5xl font-bold pt-20 bottom-[5px] @sm:top-[10px] <sm:top-[5px] >sm:top-[15px]`}
className={`text-5xl font-bold p-20 bottom-[5px] sm:top-[10px] sm:top-[5px] >sm:top-[15px]`}
>
This is a starter template for you!
</h1>
Expand Down
125 changes: 125 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as Path from 'path'
import Module from 'module'
import { fileURLToPath } from 'url'

import type { Configuration } from 'twind'
import locatePath from 'locate-path'
import kleur from 'kleur'

const TWIND_CONFIG_FILES = [
'twind.config.ts',
'twind.config.mjs',
'twind.config.js',
'twind.config.cjs',
]

const TAILWIND_CONFIG_FILES = [
'tailwind.config.ts',
'tailwind.config.mjs',
'tailwind.config.js',
'tailwind.config.cjs',
]

export const findConfig = async (cwd = process.cwd()): Promise<string | undefined> => {
return locatePath(
[
...TWIND_CONFIG_FILES,
...TWIND_CONFIG_FILES.map((file) => Path.join('config', file)),
...TWIND_CONFIG_FILES.map((file) => Path.join('src', file)),
...TWIND_CONFIG_FILES.map((file) => Path.join('public', file)),
...TAILWIND_CONFIG_FILES,
],
{ cwd },
)
}

export const loadFile = <T>(file: string, cwd = process.cwd()): T => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const from = fileURLToPath(import.meta.url)

const require = Module.createRequire?.(from) || Module.createRequireFromPath(from)

require('sucrase/register')

// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(Path.resolve(cwd, file)) as T

// const source = project.readFile(Path.resolve(cwd, file))

// if (!source) {
// return {} as T
// }

// const result = transform(source, {
// transforms: ['typescript', 'imports'],
// filePath: file,
// })

// const module = { exports: {} as { default?: Configuration } & Configuration }

// new Function('exports', 'require', 'module', '__filename', '__dirname', result.code)(
// module.exports,
// Module.createRequire?.(file) || Module.createRequireFromPath(file),
// module,
// file,
// Path.dirname(file),
// )

// return module.exports as T
} catch {
return {} as T
}
}

export type TConfiguration = Configuration & { purge?: string[] | { content?: string[] } }

export const loadConfig = (configFile: string, cwd = process.cwd()): TConfiguration => {
const exports = loadFile<{ default: Configuration } & Configuration>(configFile, cwd)

const config = exports.default || exports || {}

// could be tailwind config
if (
(Array.isArray(config.plugins) ||
// Twind has variants as {key: string}; Tailwind array or object
Object.values(config.variants || {}).some((value) => typeof value == 'object') ||
typeof config.prefix == 'string',
'presets' in config ||
'separator' in config ||
'variantOrder' in config ||
'corePlugins' in config ||
'purge' in config)
) {
console.error(
kleur.red(
`${kleur.bold(
Path.relative(process.cwd(), configFile),
)} is a tailwindcss configuration file – ${kleur.bold(
'only',
)} the theme, darkMode, purge files are used`,
),
)

return {
theme: config.theme,
darkMode: config.darkMode,
purge: (config as any).purge,
}
}

return config
}

export const getConfig = async (
cwd = process.cwd(),
configFile?: string,
): Promise<TConfiguration & { configFile: string | undefined }> => {
configFile ??= await findConfig(cwd)

return {
...(configFile ? loadConfig(Path.resolve(cwd, configFile), cwd) : {}),
configFile,
}
}
13 changes: 6 additions & 7 deletions src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ const removeInvalidCandidate = (candidate: string): boolean => {
// Remove candiate if it matches the following rules
// - no lower case char
!/[a-z]/.test(candidate) ||
// - containing uppercase letters
// - non number fractions and decimals
// - Starts with an uppercase char
// - ending with -, /, @, $, &
// - white space only
/[A-Z]|\D[/.]\D|[-/@$&]$|^\s*$/.test(candidate) ||
/^[A-Z]|[-/@$&]$|^\s*$/.test(candidate) ||
// Either of the following two must match
// support @sm:..., >sm:..., <sm:...
/^[@<>][^:]+:/.test(candidate) !=
// - starts with <:#.,;?\d[\]%/$&@_
// support @sm:..., >sm:..., <sm:..., [lang]:
/^[@<>[][^:]+:/.test(candidate) !=
// - starts with <:#.,;?\d\]%/$&@_
// - v-*: (vue)
// - aria-*
// - url like
Expand All @@ -40,7 +39,7 @@ const removeInvalidCandidate = (candidate: string): boolean => {
}

export const extractRulesFromString = (content: string): string[] => {
return (content.match(/[^>"'`\s(){}[\]=][^<>"'`\s(){}=]*[^<>"'`\s(){}=:#.,;?]/g) || [])
return (content.match(/[^>"'`\s(){}\]=][^<>"'`\s]*[^<>"'`\s(){}=:#.,;?]/g) || [])
.map(cleanCandidate)
.filter(removeInvalidCandidate)
}
Expand Down
3 changes: 0 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
/* eslint-env node */
// ^^^^ This comment is need to prevent browser bundles of this file

export * from './cli'
export * from './run'
98 changes: 6 additions & 92 deletions src/run.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { Stats } from 'fs'
import fs from 'fs/promises'
import path from 'path'
import Module from 'module'

import type { Service } from 'esbuild'
import kleur from 'kleur'
import { startService } from 'esbuild'
import timeSpan from 'time-span'
import { transform } from 'esbuild'

import type { TW, Configuration, Mode } from 'twind'
import type { VirtualSheet } from 'twind/sheets'
Expand All @@ -15,75 +13,7 @@ import { virtualSheet } from 'twind/sheets'

import { watch } from './watch'
import { extractRulesFromFile } from './extract'
import findUp from 'find-up'

const tryLoadConfig = async (
configFile: string,
esbuild: Service,
): Promise<Configuration & { purge?: string[] | { content?: string[] } }> => {
const result = await esbuild.build({
bundle: true,
entryPoints: [configFile],
format: 'cjs',
minify: false,
platform: 'node',
target: `node${process.versions.node}`,
sourcemap: true,
splitting: false,
write: false,
})

const module = { exports: {} as { default?: Configuration } & Configuration }

new Function(
'exports',
'require',
'module',
'__filename',
'__dirname',
result.outputFiles[0].text,
)(
module.exports,
Module.createRequire?.(configFile) || Module.createRequireFromPath(configFile),
module,
configFile,
path.dirname(configFile),
)

const config = module.exports.default || module.exports || {}

// could be tailwind config
if (
(Array.isArray(config.plugins) ||
// Twind has variants as {key: string}; Tailwind array or object
Object.values(config.variants || {}).some((value) => typeof value == 'object') ||
typeof config.prefix == 'string',
'presets' in config ||
'important' in config ||
'separator' in config ||
'variantOrder' in config ||
'corePlugins' in config ||
'purge' in config)
) {
console.error(
kleur.red(
`${kleur.bold(
path.relative(process.cwd(), configFile),
)} is a tailwindcss configuration file – ${kleur.bold(
'only',
)} the theme, darkMode, purge files are used`,
),
)

return {
theme: config.theme,
darkMode: config.darkMode,
purge: (config as any).purge,
}
}

return config
}
import { findConfig, loadConfig as tryLoadConfig } from './config'

export interface RunOptions {
config?: string
Expand All @@ -96,28 +26,12 @@ export interface RunOptions {
}

export const run = async (globs: string[], options: RunOptions = {}): Promise<void> => {
const esbuild = await startService()

try {
await run$(globs, options, esbuild)
} finally {
esbuild.stop()
}
}

const run$ = async (globs: string[], options: RunOptions, esbuild: Service): Promise<void> => {
kleur.enabled = !!options.color

options.cwd = path.resolve(options.cwd || '.')

const configFile =
(options.config && path.resolve(options.cwd, options.config)) ||
(await findUp(['twind.config.js', 'twind.config.mjs', 'twind.config.cjs', 'twind.config.ts'], {
cwd: options.cwd,
})) ||
(await findUp(['tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.cjs', 'tailwind.config.ts'], {
cwd: options.cwd,
}))
(options.config && path.resolve(options.cwd, options.config)) || (await findConfig(options.cwd))

// Track unknown rules
const unknownRules = new Set<string>()
Expand All @@ -138,13 +52,13 @@ const run$ = async (globs: string[], options: RunOptions, esbuild: Service): Pro
// The initial run is not counted -> -1, initialRun=0, first run=1
let runCount = -1

const loadConfig = async (): Promise<{ sheet: VirtualSheet; tw: TW }> => {
const loadConfig = (): { sheet: VirtualSheet; tw: TW } => {
let config: Configuration & { purge?: string[] | { content?: string[] } } = {}

if (configFile) {
const configEndTime = timeSpan()

config = await tryLoadConfig(configFile, esbuild)
config = tryLoadConfig(configFile, options.cwd)

console.error(
kleur.green(
Expand Down Expand Up @@ -277,7 +191,7 @@ const run$ = async (globs: string[], options: RunOptions, esbuild: Service): Pro
let css = sheet.target.join('\n')
if (options.beautify || !options.watch) {
const cssEndTime = timeSpan()
const result = await esbuild.transform(css, {
const result = await transform(css, {
minify: !options.beautify,
loader: 'css',
sourcemap: false,
Expand Down

0 comments on commit e0e592d

Please sign in to comment.