下面是解析 Tailwindcss 背后的原理,知道背后的原理,能方便我们更加好的使用 Tailwindcss,同时我们也就能具备驾驭 ACSS 原理。
前置知识
- 了解并熟悉使用 postcss, Tailwimdcss 本质是 postcss 的一个插件
- 了解 postcss api
- 学会书写一个 postcss 插件
目录结构分析
源码分析的版本:
// Tailwindcss 版本
"version": "1.2.0",
// 所需的 Node 版本
"engines": {
"node": ">=8.9.0"
}
整体目录结构
.
├── LICENSE
├── README.md
├── __tests__
├── base.css
├── components.css
├── defaultConfig.js
├── defaultTheme.js
├── dist
├── jest
├── package.json
├── plugin.js
├── resolveConfig.js
├── screens.css
├── scripts
├── src
├── stubs
├── tailwind.css
├── utilities.css
└── yarn.lock
主源码目录结构 - // src 目录
$ tree -L 2
.
├── cli
│ ├── colors.js
│ ├── commands
│ ├── compile.js
│ ├── emoji.js
│ ├── main.js
│ └── utils.js
├── cli.js
├── constants.js
├── corePlugins.js
├── index.js
├── lib
│ ├── evaluateTailwindFunctions.js
│ ├── formatCSS.js
│ ├── getModuleDependencies.js
│ ├── registerConfigAsDependency.js
│ ├── substituteClassApplyAtRules.js
│ ├── substituteResponsiveAtRules.js
│ ├── substituteScreenAtRules.js
│ ├── substituteTailwindAtRules.js
│ └── substituteVariantsAtRules.js
├── plugins
│ ├── accessibility.js
│ ├── alignContent.js
│ ├── alignItems.js
│ ├── alignSelf.js
│ ├── appearance.js
│ ├── backgroundAttachment.js
│ ├── backgroundColor.js
│ ├── backgroundPosition.js
│ ├── backgroundRepeat.js
│ ├── backgroundSize.js
│ ├── borderCollapse.js
│ ├── borderColor.js
│ ├── borderRadius.js
│ ├── borderStyle.js
│ ├── borderWidth.js
│ ├── boxShadow.js
│ ├── boxSizing.js
│ ├── clear.js
│ ├── container.js
│ ├── css
│ ├── cursor.js
│ ├── display.js
│ ├── fill.js
│ ├── flex.js
│ ├── flexDirection.js
│ ├── flexGrow.js
│ ├── flexShrink.js
│ ├── flexWrap.js
│ ├── float.js
│ ├── fontFamily.js
│ ├── fontSize.js
│ ├── fontSmoothing.js
│ ├── fontStyle.js
│ ├── fontWeight.js
│ ├── gap.js
│ ├── gridAutoFlow.js
│ ├── gridColumn.js
│ ├── gridColumnEnd.js
│ ├── gridColumnStart.js
│ ├── gridRow.js
│ ├── gridRowEnd.js
│ ├── gridRowStart.js
│ ├── gridTemplateColumns.js
│ ├── gridTemplateRows.js
│ ├── height.js
│ ├── inset.js
│ ├── justifyContent.js
│ ├── letterSpacing.js
│ ├── lineHeight.js
│ ├── listStylePosition.js
│ ├── listStyleType.js
│ ├── margin.js
│ ├── maxHeight.js
│ ├── maxWidth.js
│ ├── minHeight.js
│ ├── minWidth.js
│ ├── objectFit.js
│ ├── objectPosition.js
│ ├── opacity.js
│ ├── order.js
│ ├── outline.js
│ ├── overflow.js
│ ├── padding.js
│ ├── placeholderColor.js
│ ├── pointerEvents.js
│ ├── position.js
│ ├── preflight.js
│ ├── resize.js
│ ├── rotate.js
│ ├── scale.js
│ ├── skew.js
│ ├── stroke.js
│ ├── strokeWidth.js
│ ├── tableLayout.js
│ ├── textAlign.js
│ ├── textColor.js
│ ├── textDecoration.js
│ ├── textTransform.js
│ ├── transform.js
│ ├── transformOrigin.js
│ ├── transitionDuration.js
│ ├── transitionProperty.js
│ ├── transitionTimingFunction.js
│ ├── translate.js
│ ├── userSelect.js
│ ├── verticalAlign.js
│ ├── visibility.js
│ ├── whitespace.js
│ ├── width.js
│ ├── wordBreak.js
│ └── zIndex.js
├── processTailwindFeatures.js
└── util
├── buildMediaQuery.js
├── buildSelectorVariant.js
├── cloneNodes.js
├── configurePlugins.js
├── createPlugin.js
├── createUtilityPlugin.js
├── escapeClassName.js
├── flattenColorPalette.js
├── generateVariantFunction.js
├── increaseSpecificity.js
├── negateValue.js
├── parseObjectStyles.js
├── prefixNegativeModifiers.js
├── prefixSelector.js
├── processPlugins.js
├── resolveConfig.js
├── responsive.js
└── wrapWithVariants.js
构建分析
构建结构输出,外部加载入口:
"main": "lib/index.js",
命令:
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
脚本:
"scripts": {
"prebabelify": "rimraf lib",
"babelify": "babel src --out-dir lib --copy-files",
"rebuild-fixtures": "npm run babelify && babel-node scripts/rebuildFixtures.js",
"prepare": "npm run babelify && babel-node scripts/build.js",
"style": "eslint .",
"test": "jest && eslint ."
},
依赖:
// 开发环境依赖
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/node": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-jest": "^25.1.0",
"clean-css": "^4.1.9",
"eslint": "^6.0.1",
"eslint-config-postcss": "^2.0.2",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.0.1",
"jest": "^25.1.0",
"prettier": "^1.7.4",
"rimraf": "^3.0.0"
},
// 生成环境依赖
"dependencies": {
"autoprefixer": "^9.4.5",
"bytes": "^3.0.0",
"chalk": "^3.0.0",
"detective": "^5.2.0",
"fs-extra": "^8.0.0",
"lodash": "^4.17.15",
"node-emoji": "^1.8.1",
"normalize.css": "^8.0.1",
"postcss": "^7.0.11",
"postcss-functions": "^3.0.0",
"postcss-js": "^2.0.0",
"postcss-nested": "^4.1.1",
"postcss-selector-parser": "^6.0.0",
"pretty-hrtime": "^1.0.3",
"reduce-css-calc": "^2.1.6",
"resolve": "^1.14.2"
},
暴露在 src 之外的文件或文件
- css 文件
// base.css
@tailwind base;
// components.css
@tailwind components;
// screen.css
@tailwind screens;
// utilities.css
@tailwind utilities;
// tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
- js 文件
// tailwindcss/defaultConfig.js
const cloneDeep = require('lodash/cloneDeep')
const defaultConfig = require('./stubs/defaultConfig.stub.js')
module.exports = cloneDeep(defaultConfig)
// tailwindcss/defaultTheme.js
const cloneDeep = require('lodash/cloneDeep')
const defaultConfig = require('./stubs/defaultConfig.stub.js')
module.exports = cloneDeep(defaultConfig.theme)
// tailwindcss/plugin.js
const createPlugin = require('./lib/util/createPlugin').default
module.exports = createPlugin
// tailwindcss/resolveConfig.js
const resolveConfigObjects = require('./lib/util/resolveConfig').default
const defaultConfig = require('./stubs/defaultConfig.stub.js')
module.exports = function resolveConfig(...configs) {
return resolveConfigObjects([...configs, defaultConfig])
}
- stubs 目录文件(本质就是 tailwindcss 的配置文件)
- tailwindcss/stubs/simpleConfig.stub.js
module.exports = {
theme: {
extend: {},
},
variants: {},
plugins: [],
}
- script 脚本
// build
import fs from 'fs'
import postcss from 'postcss'
import tailwind from '..'
import CleanCSS from 'clean-css'
function buildDistFile(filename) {
return new Promise((resolve, reject) => {
console.log(`Processing ./${filename}.css...`)
fs.readFile(`./${filename}.css`, (err, css) => {
if (err) throw err
return postcss([tailwind(), require('autoprefixer')])
.process(css, {
from: `./${filename}.css`,
to: `./dist/${filename}.css`,
map: { inline: false },
})
.then(result => {
fs.writeFileSync(`./dist/${filename}.css`, result.css)
if (result.map) {
fs.writeFileSync(`./dist/${filename}.css.map`, result.map)
}
return result
})
.then(result => {
const minified = new CleanCSS().minify(result.css)
fs.writeFileSync(`./dist/${filename}.min.css`, minified.styles)
})
.then(resolve)
.catch(error => {
console.log(error)
reject()
})
})
})
}
console.info('Building Tailwind!')
Promise.all([
buildDistFile('base'),
buildDistFile('components'),
buildDistFile('utilities'),
buildDistFile('tailwind'),
]).then(() => {
console.log('Finished Building Tailwind!')
})
// rebuildFixtures.js
import fs from 'fs'
import postcss from 'postcss'
import tailwind from '..'
function build({ from, to, config }) {
return new Promise((resolve, reject) => {
console.log(`Processing ./${from}...`)
fs.readFile(`./${from}`, (err, css) => {
if (err) throw err
return postcss([tailwind(config)])
.process(css, {
from: undefined,
})
.then(result => {
fs.writeFileSync(`./${to}`, result.css)
return result
})
.then(resolve)
.catch(error => {
console.log(error)
reject()
})
})
})
}
console.info('\nRebuilding fixtures...\n')
Promise.all([
build({
from: '__tests__/fixtures/tailwind-input.css',
to: '__tests__/fixtures/tailwind-output.css',
config: {},
}),
build({
from: '__tests__/fixtures/tailwind-input.css',
to: '__tests__/fixtures/tailwind-output-important.css',
config: { important: true },
}),
]).then(() => {
console.log('\nFinished rebuilding fixtures.')
console.log(
'\nPlease triple check that the fixture output matches what you expect before committing this change.'
)
})
主入口分析
index.js, 是一个标准的 postcss plugin 的写法
const plugin = postcss.plugin('tailwind', config => {
const plugins = []
const resolvedConfigPath = resolveConfigPath(config)
if (!_.isUndefined(resolvedConfigPath)) {
plugins.push(registerConfigAsDependency(resolvedConfigPath))
}
return postcss([
...plugins,
processTailwindFeatures(getConfigFunction(resolvedConfigPath || config)),
formatCSS,
])
})
module.exports = plugin
constants 常量
import path from 'path'
export const cli = 'tailwind'
export const defaultConfigFile = './tailwind.config.js'
export const defaultConfigStubFile = path.resolve(__dirname, '../stubs/defaultConfig.stub.js')
export const simpleConfigStubFile = path.resolve(__dirname, '../stubs/simpleConfig.stub.js')
cli 命令行入口
#!/usr/bin/env node
import main from './cli/main'
import * as utils from './cli/utils'
main(process.argv.slice(2)).catch(error => utils.die(error.stack))
核心插件的引入和导出
import preflight from './plugins/preflight'
import container from './plugins/container'
import accessibility from './plugins/accessibility'
import appearance from './plugins/appearance'
import backgroundAttachment from './plugins/backgroundAttachment'
import backgroundColor from './plugins/backgroundColor'
import backgroundPosition from './plugins/backgroundPosition'
import backgroundRepeat from './plugins/backgroundRepeat'
import backgroundSize from './plugins/backgroundSize'
import borderCollapse from './plugins/borderCollapse'
import borderColor from './plugins/borderColor'
import borderRadius from './plugins/borderRadius'
import borderStyle from './plugins/borderStyle'
import borderWidth from './plugins/borderWidth'
import boxSizing from './plugins/boxSizing'
import cursor from './plugins/cursor'
import display from './plugins/display'
import flexDirection from './plugins/flexDirection'
import flexWrap from './plugins/flexWrap'
import alignItems from './plugins/alignItems'
import alignSelf from './plugins/alignSelf'
import justifyContent from './plugins/justifyContent'
import alignContent from './plugins/alignContent'
import flex from './plugins/flex'
import flexGrow from './plugins/flexGrow'
import flexShrink from './plugins/flexShrink'
import order from './plugins/order'
import float from './plugins/float'
import clear from './plugins/clear'
import fontFamily from './plugins/fontFamily'
import fontWeight from './plugins/fontWeight'
import height from './plugins/height'
import lineHeight from './plugins/lineHeight'
import listStylePosition from './plugins/listStylePosition'
import listStyleType from './plugins/listStyleType'
import margin from './plugins/margin'
import maxHeight from './plugins/maxHeight'
import maxWidth from './plugins/maxWidth'
import minHeight from './plugins/minHeight'
import minWidth from './plugins/minWidth'
import objectFit from './plugins/objectFit'
import objectPosition from './plugins/objectPosition'
import opacity from './plugins/opacity'
import outline from './plugins/outline'
import overflow from './plugins/overflow'
import padding from './plugins/padding'
import placeholderColor from './plugins/placeholderColor'
import pointerEvents from './plugins/pointerEvents'
import position from './plugins/position'
import inset from './plugins/inset'
import resize from './plugins/resize'
import boxShadow from './plugins/boxShadow'
import fill from './plugins/fill'
import stroke from './plugins/stroke'
import strokeWidth from './plugins/strokeWidth'
import tableLayout from './plugins/tableLayout'
import textAlign from './plugins/textAlign'
import textColor from './plugins/textColor'
import fontSize from './plugins/fontSize'
import fontStyle from './plugins/fontStyle'
import textTransform from './plugins/textTransform'
import textDecoration from './plugins/textDecoration'
import fontSmoothing from './plugins/fontSmoothing'
import letterSpacing from './plugins/letterSpacing'
import userSelect from './plugins/userSelect'
import verticalAlign from './plugins/verticalAlign'
import visibility from './plugins/visibility'
import whitespace from './plugins/whitespace'
import wordBreak from './plugins/wordBreak'
import width from './plugins/width'
import zIndex from './plugins/zIndex'
import gap from './plugins/gap'
import gridAutoFlow from './plugins/gridAutoFlow'
import gridTemplateColumns from './plugins/gridTemplateColumns'
import gridColumn from './plugins/gridColumn'
import gridColumnStart from './plugins/gridColumnStart'
import gridColumnEnd from './plugins/gridColumnEnd'
import gridTemplateRows from './plugins/gridTemplateRows'
import gridRow from './plugins/gridRow'
import gridRowStart from './plugins/gridRowStart'
import gridRowEnd from './plugins/gridRowEnd'
import transform from './plugins/transform'
import transformOrigin from './plugins/transformOrigin'
import scale from './plugins/scale'
import rotate from './plugins/rotate'
import translate from './plugins/translate'
import skew from './plugins/skew'
import transitionProperty from './plugins/transitionProperty'
import transitionTimingFunction from './plugins/transitionTimingFunction'
import transitionDuration from './plugins/transitionDuration'
import configurePlugins from './util/configurePlugins'
export default function({ corePlugins: corePluginConfig }) {
return configurePlugins(corePluginConfig, {
preflight,
container,
accessibility,
appearance,
backgroundAttachment,
backgroundColor,
backgroundPosition,
backgroundRepeat,
backgroundSize,
borderCollapse,
borderColor,
borderRadius,
borderStyle,
borderWidth,
boxSizing,
cursor,
display,
flexDirection,
flexWrap,
alignItems,
alignSelf,
justifyContent,
alignContent,
flex,
flexGrow,
flexShrink,
order,
float,
clear,
fontFamily,
fontWeight,
height,
lineHeight,
listStylePosition,
listStyleType,
margin,
maxHeight,
maxWidth,
minHeight,
minWidth,
objectFit,
objectPosition,
opacity,
outline,
overflow,
padding,
placeholderColor,
pointerEvents,
position,
inset,
resize,
boxShadow,
fill,
stroke,
strokeWidth,
tableLayout,
textAlign,
textColor,
fontSize,
fontStyle,
textTransform,
textDecoration,
fontSmoothing,
letterSpacing,
userSelect,
verticalAlign,
visibility,
whitespace,
wordBreak,
width,
zIndex,
gap,
gridAutoFlow,
gridTemplateColumns,
gridColumn,
gridColumnStart,
gridColumnEnd,
gridTemplateRows,
gridRow,
gridRowStart,
gridRowEnd,
transform,
transformOrigin,
scale,
rotate,
translate,
skew,
transitionProperty,
transitionTimingFunction,
transitionDuration,
})
}
特性
import _ from 'lodash'
import postcss from 'postcss'
import substituteTailwindAtRules from './lib/substituteTailwindAtRules'
import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
import corePlugins from './corePlugins'
import processPlugins from './util/processPlugins'
export default function(getConfig) {
return function(css) {
const config = getConfig()
const processedPlugins = processPlugins([...corePlugins(config), ...config.plugins], config)
return postcss([
substituteTailwindAtRules(config, processedPlugins),
evaluateTailwindFunctions(config),
substituteVariantsAtRules(config, processedPlugins),
substituteResponsiveAtRules(config),
substituteScreenAtRules(config),
substituteClassApplyAtRules(config, processedPlugins.utilities),
]).process(css, { from: _.get(css, 'source.input.file') })
}
}
其他
todo
- 深入学习 postcss
- tailwindcss 工作流程分析
- tailwindcss cli
- 插件
- 负值