Support loading plugins by package / file name (#12087)
* Cleanup test Strings are going to actually do something now * Refactor plugin loading in `resolveConfig` This should keep the same instance of a plugin in use too. Maybe even reduce memory usage?! idk we’ll see * Support simple plugin configs * Update types * Update changelog
This commit is contained in:
parent
aaca7c4c4c
commit
05d0a6f8d2
7 changed files with 133 additions and 26 deletions
|
|
@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Explicitly configure Lightning CSS features, and prefer user browserslist over default browserslist ([#11402](https://github.com/tailwindlabs/tailwindcss/pull/11402), [#11412](https://github.com/tailwindlabs/tailwindcss/pull/11412))
|
||||
- Extend default `opacity` scale to include all steps of 5 ([#11832](https://github.com/tailwindlabs/tailwindcss/pull/11832))
|
||||
- Update Preflight `html` styles to include shadow DOM `:host` pseudo-class ([#11200](https://github.com/tailwindlabs/tailwindcss/pull/11200))
|
||||
- Support loading plugins by package / file name ([#12087](https://github.com/tailwindlabs/tailwindcss/pull/12087))
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -210,27 +210,51 @@ function resolveFunctionKeys(object) {
|
|||
}, {})
|
||||
}
|
||||
|
||||
function extractPluginConfigs(configs) {
|
||||
function resolvePlugins(configs) {
|
||||
let pluginGroups = []
|
||||
let allConfigs = []
|
||||
|
||||
configs.forEach((config) => {
|
||||
allConfigs = [...allConfigs, config]
|
||||
for (let config of configs) {
|
||||
allConfigs.push(config)
|
||||
|
||||
const plugins = config?.plugins ?? []
|
||||
let plugins = []
|
||||
|
||||
if (plugins.length === 0) {
|
||||
return
|
||||
}
|
||||
for (let plugin of config?.plugins ?? []) {
|
||||
// TODO: If we want to support ESM plugins then a handful of things will have to become async
|
||||
if (typeof plugin === 'string') {
|
||||
// If the plugin is specified as a string then it's just the package name
|
||||
plugin = require(plugin)
|
||||
plugin = plugin.default ?? plugin
|
||||
} else if (Array.isArray(plugin)) {
|
||||
// If the plugin is specified as an array then it's a package name and optional options object
|
||||
// [name] or [name, options]
|
||||
let [pkg, options = undefined] = plugin
|
||||
plugin = require(pkg)
|
||||
plugin = plugin.default ?? plugin
|
||||
plugin = plugin(options)
|
||||
}
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.__isOptionsFunction) {
|
||||
plugin = plugin()
|
||||
}
|
||||
allConfigs = [...allConfigs, ...extractPluginConfigs([plugin?.config ?? {}])]
|
||||
})
|
||||
})
|
||||
|
||||
return allConfigs
|
||||
// We're explicitly skipping registering child plugins
|
||||
// This will change in v4
|
||||
let [, childConfigs] = resolvePlugins([plugin?.config ?? {}])
|
||||
|
||||
plugins.push(plugin)
|
||||
allConfigs.push(...childConfigs)
|
||||
}
|
||||
|
||||
pluginGroups.push(plugins)
|
||||
}
|
||||
|
||||
// Reverse the order of the plugin groups
|
||||
// This matches the old `reduceRight` behavior of the old `resolvePluginLists`
|
||||
// Why? No idea.
|
||||
let plugins = pluginGroups.reverse().flat()
|
||||
|
||||
return [plugins, allConfigs]
|
||||
}
|
||||
|
||||
function resolveCorePlugins(corePluginConfigs) {
|
||||
|
|
@ -244,17 +268,11 @@ function resolveCorePlugins(corePluginConfigs) {
|
|||
return result
|
||||
}
|
||||
|
||||
function resolvePluginLists(pluginLists) {
|
||||
const result = [...pluginLists].reduceRight((resolved, pluginList) => {
|
||||
return [...resolved, ...pluginList]
|
||||
}, [])
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default function resolveConfig(configs) {
|
||||
let [plugins, pluginConfigs] = resolvePlugins(configs)
|
||||
|
||||
let allConfigs = [
|
||||
...extractPluginConfigs(configs),
|
||||
...pluginConfigs,
|
||||
{
|
||||
prefix: '',
|
||||
important: false,
|
||||
|
|
@ -269,7 +287,7 @@ export default function resolveConfig(configs) {
|
|||
mergeExtensions(mergeThemes(allConfigs.map((t) => t?.theme ?? {})))
|
||||
),
|
||||
corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)),
|
||||
plugins: resolvePluginLists(configs.map((c) => c?.plugins ?? [])),
|
||||
plugins,
|
||||
},
|
||||
...allConfigs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1914,3 +1914,63 @@ test('custom properties are not converted to kebab-case when added to base layer
|
|||
expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
|
||||
})
|
||||
})
|
||||
|
||||
test('plugins can loaded by package name / path', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'example-1' }],
|
||||
plugins: [`${__dirname}/fixtures/plugins/example.cjs`],
|
||||
}
|
||||
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example-1 {
|
||||
color: red;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('named plugins can specify options', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'ex-example-1 example-1' }],
|
||||
plugins: [[`${__dirname}/fixtures/plugins/example.cjs`, { prefix: 'ex-' }]],
|
||||
}
|
||||
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ex-example-1 {
|
||||
color: red;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('named plugins resolve default export', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'example-1' }],
|
||||
plugins: [`${__dirname}/fixtures/plugins/example.default.cjs`],
|
||||
}
|
||||
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example-1 {
|
||||
color: red;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('named plugins resolve default export when using options', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'example-1 ex-example-1' }],
|
||||
plugins: [[`${__dirname}/fixtures/plugins/example.default.cjs`, { prefix: 'ex-' }]],
|
||||
}
|
||||
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ex-example-1 {
|
||||
color: red;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
|
|
|||
11
tests/fixtures/plugins/example.cjs
vendored
Normal file
11
tests/fixtures/plugins/example.cjs
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const plugin = require('../../../plugin.js')
|
||||
|
||||
module.exports = plugin.withOptions(function ({ prefix = '' } = {}) {
|
||||
return function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
[`.${prefix}example-1`]: {
|
||||
color: 'red',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
11
tests/fixtures/plugins/example.default.cjs
vendored
Normal file
11
tests/fixtures/plugins/example.default.cjs
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const plugin = require('../../../plugin.js')
|
||||
|
||||
module.exports.default = plugin.withOptions(function ({ prefix = '' } = {}) {
|
||||
return function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
[`.${prefix}example-1`]: {
|
||||
color: 'red',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -1680,16 +1680,20 @@ test('core plugin configurations stack', () => {
|
|||
})
|
||||
|
||||
test('plugins are merged', () => {
|
||||
let p1 = { config: { order: '1' } }
|
||||
let p2 = { config: { order: '2' } }
|
||||
let p3 = { config: { order: '3' } }
|
||||
|
||||
const userConfig = {
|
||||
plugins: ['3'],
|
||||
plugins: [p3],
|
||||
}
|
||||
|
||||
const otherConfig = {
|
||||
plugins: ['2'],
|
||||
plugins: [p2],
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
plugins: ['1'],
|
||||
plugins: [p1],
|
||||
prefix: '',
|
||||
important: false,
|
||||
separator: ':',
|
||||
|
|
@ -1704,7 +1708,7 @@ test('plugins are merged', () => {
|
|||
important: false,
|
||||
separator: ':',
|
||||
theme: {},
|
||||
plugins: ['1', '2', '3'],
|
||||
plugins: [p1, p2, p3],
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
2
types/config.d.ts
vendored
2
types/config.d.ts
vendored
|
|
@ -342,6 +342,8 @@ export type PluginsConfig = (
|
|||
(options: any): { handler: PluginCreator; config?: Partial<Config> }
|
||||
__isOptionsFunction: true
|
||||
}
|
||||
| string
|
||||
| [string, Record<string, any>]
|
||||
)[]
|
||||
|
||||
// Top level config related
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue