Skip to content

Commit 05d0a6f

Browse files
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
1 parent aaca7c4 commit 05d0a6f

File tree

7 files changed

+135
-28
lines changed

7 files changed

+135
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- 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))
3333
- Extend default `opacity` scale to include all steps of 5 ([#11832](https://github.com/tailwindlabs/tailwindcss/pull/11832))
3434
- Update Preflight `html` styles to include shadow DOM `:host` pseudo-class ([#11200](https://github.com/tailwindlabs/tailwindcss/pull/11200))
35+
- Support loading plugins by package / file name ([#12087](https://github.com/tailwindlabs/tailwindcss/pull/12087))
3536

3637
### Changed
3738

src/util/resolveConfig.js

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -210,27 +210,51 @@ function resolveFunctionKeys(object) {
210210
}, {})
211211
}
212212

213-
function extractPluginConfigs(configs) {
213+
function resolvePlugins(configs) {
214+
let pluginGroups = []
214215
let allConfigs = []
215216

216-
configs.forEach((config) => {
217-
allConfigs = [...allConfigs, config]
218-
219-
const plugins = config?.plugins ?? []
220-
221-
if (plugins.length === 0) {
222-
return
223-
}
217+
for (let config of configs) {
218+
allConfigs.push(config)
219+
220+
let plugins = []
221+
222+
for (let plugin of config?.plugins ?? []) {
223+
// TODO: If we want to support ESM plugins then a handful of things will have to become async
224+
if (typeof plugin === 'string') {
225+
// If the plugin is specified as a string then it's just the package name
226+
plugin = require(plugin)
227+
plugin = plugin.default ?? plugin
228+
} else if (Array.isArray(plugin)) {
229+
// If the plugin is specified as an array then it's a package name and optional options object
230+
// [name] or [name, options]
231+
let [pkg, options = undefined] = plugin
232+
plugin = require(pkg)
233+
plugin = plugin.default ?? plugin
234+
plugin = plugin(options)
235+
}
224236

225-
plugins.forEach((plugin) => {
226237
if (plugin.__isOptionsFunction) {
227238
plugin = plugin()
228239
}
229-
allConfigs = [...allConfigs, ...extractPluginConfigs([plugin?.config ?? {}])]
230-
})
231-
})
232240

233-
return allConfigs
241+
// We're explicitly skipping registering child plugins
242+
// This will change in v4
243+
let [, childConfigs] = resolvePlugins([plugin?.config ?? {}])
244+
245+
plugins.push(plugin)
246+
allConfigs.push(...childConfigs)
247+
}
248+
249+
pluginGroups.push(plugins)
250+
}
251+
252+
// Reverse the order of the plugin groups
253+
// This matches the old `reduceRight` behavior of the old `resolvePluginLists`
254+
// Why? No idea.
255+
let plugins = pluginGroups.reverse().flat()
256+
257+
return [plugins, allConfigs]
234258
}
235259

236260
function resolveCorePlugins(corePluginConfigs) {
@@ -244,17 +268,11 @@ function resolveCorePlugins(corePluginConfigs) {
244268
return result
245269
}
246270

247-
function resolvePluginLists(pluginLists) {
248-
const result = [...pluginLists].reduceRight((resolved, pluginList) => {
249-
return [...resolved, ...pluginList]
250-
}, [])
251-
252-
return result
253-
}
254-
255271
export default function resolveConfig(configs) {
272+
let [plugins, pluginConfigs] = resolvePlugins(configs)
273+
256274
let allConfigs = [
257-
...extractPluginConfigs(configs),
275+
...pluginConfigs,
258276
{
259277
prefix: '',
260278
important: false,
@@ -269,7 +287,7 @@ export default function resolveConfig(configs) {
269287
mergeExtensions(mergeThemes(allConfigs.map((t) => t?.theme ?? {})))
270288
),
271289
corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)),
272-
plugins: resolvePluginLists(configs.map((c) => c?.plugins ?? [])),
290+
plugins,
273291
},
274292
...allConfigs
275293
)

tests/custom-plugins.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,3 +1914,63 @@ test('custom properties are not converted to kebab-case when added to base layer
19141914
expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
19151915
})
19161916
})
1917+
1918+
test('plugins can loaded by package name / path', async () => {
1919+
let config = {
1920+
content: [{ raw: 'example-1' }],
1921+
plugins: [`${__dirname}/fixtures/plugins/example.cjs`],
1922+
}
1923+
1924+
let result = await run('@tailwind utilities', config)
1925+
1926+
expect(result.css).toMatchFormattedCss(css`
1927+
.example-1 {
1928+
color: red;
1929+
}
1930+
`)
1931+
})
1932+
1933+
test('named plugins can specify options', async () => {
1934+
let config = {
1935+
content: [{ raw: 'ex-example-1 example-1' }],
1936+
plugins: [[`${__dirname}/fixtures/plugins/example.cjs`, { prefix: 'ex-' }]],
1937+
}
1938+
1939+
let result = await run('@tailwind utilities', config)
1940+
1941+
expect(result.css).toMatchFormattedCss(css`
1942+
.ex-example-1 {
1943+
color: red;
1944+
}
1945+
`)
1946+
})
1947+
1948+
test('named plugins resolve default export', async () => {
1949+
let config = {
1950+
content: [{ raw: 'example-1' }],
1951+
plugins: [`${__dirname}/fixtures/plugins/example.default.cjs`],
1952+
}
1953+
1954+
let result = await run('@tailwind utilities', config)
1955+
1956+
expect(result.css).toMatchFormattedCss(css`
1957+
.example-1 {
1958+
color: red;
1959+
}
1960+
`)
1961+
})
1962+
1963+
test('named plugins resolve default export when using options', async () => {
1964+
let config = {
1965+
content: [{ raw: 'example-1 ex-example-1' }],
1966+
plugins: [[`${__dirname}/fixtures/plugins/example.default.cjs`, { prefix: 'ex-' }]],
1967+
}
1968+
1969+
let result = await run('@tailwind utilities', config)
1970+
1971+
expect(result.css).toMatchFormattedCss(css`
1972+
.ex-example-1 {
1973+
color: red;
1974+
}
1975+
`)
1976+
})

tests/fixtures/plugins/example.cjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const plugin = require('../../../plugin.js')
2+
3+
module.exports = plugin.withOptions(function ({ prefix = '' } = {}) {
4+
return function ({ addUtilities }) {
5+
addUtilities({
6+
[`.${prefix}example-1`]: {
7+
color: 'red',
8+
},
9+
})
10+
}
11+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const plugin = require('../../../plugin.js')
2+
3+
module.exports.default = plugin.withOptions(function ({ prefix = '' } = {}) {
4+
return function ({ addUtilities }) {
5+
addUtilities({
6+
[`.${prefix}example-1`]: {
7+
color: 'red',
8+
},
9+
})
10+
}
11+
})

tests/resolveConfig.test.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,16 +1680,20 @@ test('core plugin configurations stack', () => {
16801680
})
16811681

16821682
test('plugins are merged', () => {
1683+
let p1 = { config: { order: '1' } }
1684+
let p2 = { config: { order: '2' } }
1685+
let p3 = { config: { order: '3' } }
1686+
16831687
const userConfig = {
1684-
plugins: ['3'],
1688+
plugins: [p3],
16851689
}
16861690

16871691
const otherConfig = {
1688-
plugins: ['2'],
1692+
plugins: [p2],
16891693
}
16901694

16911695
const defaultConfig = {
1692-
plugins: ['1'],
1696+
plugins: [p1],
16931697
prefix: '',
16941698
important: false,
16951699
separator: ':',
@@ -1704,7 +1708,7 @@ test('plugins are merged', () => {
17041708
important: false,
17051709
separator: ':',
17061710
theme: {},
1707-
plugins: ['1', '2', '3'],
1711+
plugins: [p1, p2, p3],
17081712
})
17091713
})
17101714

types/config.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ export type PluginsConfig = (
342342
(options: any): { handler: PluginCreator; config?: Partial<Config> }
343343
__isOptionsFunction: true
344344
}
345+
| string
346+
| [string, Record<string, any>]
345347
)[]
346348

347349
// Top level config related

0 commit comments

Comments
 (0)