Skip to content

Commit

Permalink
Use import to load plugins (#14132)
Browse files Browse the repository at this point in the history
Alternative to #14110

This PR changes the way how we load plugins to be compatible with ES6
async `import`s. This allows us to load plugins even inside the browser
but it comes at a downside: We now have to change the `compile` API to
return a `Promise`...

So most of this PR is rewriting all of the call sites of `compile` to
expect a promise instead of the object.

---------

Co-authored-by: Jordan Pittman <[email protected]>
  • Loading branch information
philipp-spiess and thecrypticace authored Aug 8, 2024
1 parent d55852a commit 921b4b6
Show file tree
Hide file tree
Showing 15 changed files with 1,520 additions and 1,223 deletions.
136 changes: 135 additions & 1 deletion integrations/postcss/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'node:path'
import { candidate, css, html, js, json, test, yaml } from '../utils'

test(
'production build',
'production build (string)',
{
fs: {
'package.json': json`{}`,
Expand Down Expand Up @@ -69,6 +69,140 @@ test(
},
)

test(
'production build (ESM)',
{
fs: {
'package.json': json`{}`,
'pnpm-workspace.yaml': yaml`
#
packages:
- project-a
`,
'project-a/package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'project-a/postcss.config.mjs': js`
import tailwindcss from '@tailwindcss/postcss'
export default {
plugins: [tailwindcss()],
}
`,
'project-a/index.html': html`
<div
class="underline 2xl:font-bold hocus:underline inverted:flex"
></div>
`,
'project-a/plugin.js': js`
module.exports = function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/utilities';
@source '../../project-b/src/**/*.js';
@plugin '../plugin.js';
`,
'project-a/src/index.js': js`
const className = "content-['a/src/index.js']"
module.exports = { className }
`,
'project-b/src/index.js': js`
const className = "content-['b/src/index.js']"
module.exports = { className }
`,
},
},
async ({ root, fs, exec }) => {
await exec('pnpm postcss src/index.css --output dist/out.css', {
cwd: path.join(root, 'project-a'),
})

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`underline`,
candidate`content-['a/src/index.js']`,
candidate`content-['b/src/index.js']`,
candidate`inverted:flex`,
candidate`hocus:underline`,
])
},
)

test(
'production build (CJS)',
{
fs: {
'package.json': json`{}`,
'pnpm-workspace.yaml': yaml`
#
packages:
- project-a
`,
'project-a/package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'project-a/postcss.config.cjs': js`
let tailwindcss = require('@tailwindcss/postcss')
module.exports = {
plugins: [tailwindcss()],
}
`,
'project-a/index.html': html`
<div
class="underline 2xl:font-bold hocus:underline inverted:flex"
></div>
`,
'project-a/plugin.js': js`
module.exports = function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'project-a/src/index.css': css`
@import 'tailwindcss/utilities';
@source '../../project-b/src/**/*.js';
@plugin '../plugin.js';
`,
'project-a/src/index.js': js`
const className = "content-['a/src/index.js']"
module.exports = { className }
`,
'project-b/src/index.js': js`
const className = "content-['b/src/index.js']"
module.exports = { className }
`,
},
},
async ({ root, fs, exec }) => {
await exec('pnpm postcss src/index.css --output dist/out.css', {
cwd: path.join(root, 'project-a'),
})

await fs.expectFileToContain('project-a/dist/out.css', [
candidate`underline`,
candidate`content-['a/src/index.js']`,
candidate`content-['b/src/index.js']`,
candidate`inverted:flex`,
candidate`hocus:underline`,
])
},
)

test(
'watch mode',
{
Expand Down
17 changes: 8 additions & 9 deletions integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ export function test(
rejectDisposal?.(new Error(`spawned process (${command}) did not exit in time`)),
ASSERTION_TIMEOUT,
)
disposePromise.finally(() => clearTimeout(timer))
disposePromise.finally(() => {
clearTimeout(timer)
})
return disposePromise
}
disposables.push(dispose)
Expand Down Expand Up @@ -294,19 +296,16 @@ export function test(

async function dispose() {
await Promise.all(disposables.map((dispose) => dispose()))
try {
if (debug) return
await fs.rm(root, { recursive: true, maxRetries: 5, force: true })
} catch (err) {
if (!process.env.CI) {
throw err
}

// Skip removing the directory in CI beause it can stall on Windows
if (!process.env.CI && !debug) {
await fs.rm(root, { recursive: true, force: true })
}
}

options.onTestFinished(dispose)

await testCallback(context)
return await testCallback(context)
},
)
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"build": "turbo build --filter=!./playgrounds/* && node ./scripts/pack-packages.mjs",
"dev": "turbo dev --filter=!./playgrounds/*",
"test": "cargo test && vitest run",
"test:integrations": "vitest --root=./integrations",
"test:integrations": "vitest --root=./integrations --no-file-parallelism",
"test:ui": "pnpm run --filter=tailwindcss test:ui",
"tdd": "vitest",
"bench": "vitest bench",
Expand Down
15 changes: 8 additions & 7 deletions packages/@tailwindcss-cli/src/commands/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import watcher from '@parcel/watcher'
import { clearCache, scanDir, type ChangedContent } from '@tailwindcss/oxide'
import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
import { createRequire } from 'module'
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
import { pathToFileURL } from 'node:url'
import postcss from 'postcss'
import atImport from 'postcss-import'
import * as tailwindcss from 'tailwindcss'
Expand All @@ -21,7 +21,6 @@ import {
} from '../../utils/renderer'
import { resolve } from '../../utils/resolve'
import { drainStdin, outputFile } from './utils'
const require = createRequire(import.meta.url)

const css = String.raw

Expand Down Expand Up @@ -132,18 +131,20 @@ export async function handle(args: Result<ReturnType<typeof options>>) {

function compile(css: string) {
return tailwindcss.compile(css, {
loadPlugin: (pluginPath) => {
loadPlugin: async (pluginPath) => {
if (pluginPath[0] === '.') {
return require(path.resolve(inputBasePath, pluginPath))
return import(pathToFileURL(path.resolve(inputBasePath, pluginPath)).href).then(
(m) => m.default ?? m,
)
}

return require(pluginPath)
return import(pluginPath).then((m) => m.default ?? m)
},
})
}

// Compile the input
let compiler = compile(input)
let compiler = await compile(input)
let scanDirResult = scanDir({
base, // Root directory, mainly used for auto content detection
sources: compiler.globs.map((pattern) => ({
Expand Down Expand Up @@ -208,7 +209,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
)

// Create a new compiler, given the new `input`
compiler = compile(input)
compiler = await compile(input)

// Re-scan the directory to get the new `candidates`
scanDirResult = scanDir({
Expand Down
1 change: 1 addition & 0 deletions packages/@tailwindcss-postcss/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { unlink, writeFile } from 'node:fs/promises'
import postcss from 'postcss'
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
// @ts-ignore
import tailwindcss from './index'

// We give this file path to PostCSS for processing.
Expand Down
22 changes: 14 additions & 8 deletions packages/@tailwindcss-postcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { scanDir } from '@tailwindcss/oxide'
import fs from 'fs'
import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
import { pathToFileURL } from 'node:url'
import path from 'path'
import postcss, { AtRule, type AcceptedPlugin, type PluginCreator } from 'postcss'
import postcssImport from 'postcss-import'
Expand Down Expand Up @@ -43,7 +44,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
let cache = new DefaultMap(() => {
return {
mtimes: new Map<string, number>(),
compiler: null as null | ReturnType<typeof compile>,
compiler: null as null | Awaited<ReturnType<typeof compile>>,
css: '',
optimizedCss: '',
}
Expand Down Expand Up @@ -73,26 +74,30 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
hasTailwind = true
}
},
OnceExit(root, { result }) {
async OnceExit(root, { result }) {
let inputFile = result.opts.from ?? ''
console.log({ inputFile })
let context = cache.get(inputFile)
let inputBasePath = path.dirname(path.resolve(inputFile))
console.log({ inputBasePath })

function createCompiler() {
return compile(root.toString(), {
loadPlugin: (pluginPath) => {
loadPlugin: async (pluginPath) => {
if (pluginPath[0] === '.') {
return require(path.resolve(inputBasePath, pluginPath))
return import(pathToFileURL(path.resolve(inputBasePath, pluginPath)).href).then(
(m) => m.default ?? m,
)
}

return require(pluginPath)
return import(pluginPath).then((m) => m.default ?? m)
},
})
}

// Setup the compiler if it doesn't exist yet. This way we can
// guarantee a `build()` function is available.
context.compiler ??= createCompiler()
context.compiler ??= await createCompiler()

let rebuildStrategy: 'full' | 'incremental' = 'incremental'

Expand Down Expand Up @@ -158,7 +163,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
}

if (rebuildStrategy === 'full') {
context.compiler = createCompiler()
context.compiler = await createCompiler()
css = context.compiler.build(hasTailwind ? scanDirResult.candidates : [])
} else if (rebuildStrategy === 'incremental') {
css = context.compiler.build!(scanDirResult.candidates)
Expand Down Expand Up @@ -203,4 +208,5 @@ function optimizeCss(
}).code.toString()
}

export default Object.assign(tailwindcss, { postcss: true }) as PluginCreator<PluginOptions>
// This is used instead of `export default` to work around a bug in `postcss-load-config`
module.exports = Object.assign(tailwindcss, { postcss: true }) as PluginCreator<PluginOptions>
1 change: 0 additions & 1 deletion packages/@tailwindcss-postcss/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default defineConfig({
format: ['esm', 'cjs'],
clean: true,
minify: true,
splitting: true,
cjsInterop: true,
dts: true,
entry: ['src/index.ts'],
Expand Down
Loading

0 comments on commit 921b4b6

Please sign in to comment.