Add the @tailwindcss/node module to handle shared node specific dependencies between our clients

This commit is contained in:
Philipp Spiess 2024-08-23 14:51:37 +02:00
parent d607f0a40e
commit a5c379386b
17 changed files with 304 additions and 41 deletions

View file

@ -67,11 +67,10 @@ test(
}
`,
'postcss.config.mjs': js`
import tailwindcss from '@tailwindcss/postcss'
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
'@tailwindcss/postcss': {},
},
plugins: [tailwindcss()],
}
`,
'index.html': html`
@ -103,3 +102,128 @@ test(
])
},
)
test(
'Config files (CJS, watch mode)',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'postcss.config.js': js`
/** @type {import('postcss-load-config').Config} */
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'index.html': html`
<div class="text-primary"></div>
`,
'tailwind.config.js': js`
let myColor = require('./my-color.js')
module.exports = {
theme: {
extend: {
colors: {
primary: myColor,
},
},
},
}
`,
'my-color.js': js`module.exports = 'blue'`,
'src/index.css': css`
@import 'tailwindcss';
@config '../tailwind.config.js';
`,
},
},
async ({ fs, spawn }) => {
await spawn('pnpm postcss src/index.css --output dist/out.css --watch --verbose')
await fs.expectFileToContain('dist/out.css', [
//
candidate`text-primary`,
'color: blue',
])
await fs.write('my-color.js', js`module.exports = 'red'`)
await fs.expectFileToContain('dist/out.css', [
//
candidate`text-primary`,
'color: red',
])
},
)
test(
'Config files (ESM, watch mode)',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"tailwindcss": "workspace:^",
"@tailwindcss/postcss": "workspace:^"
}
}
`,
'postcss.config.mjs': js`
import tailwindcss from '@tailwindcss/postcss'
/** @type {import('postcss-load-config').Config} */
export default {
plugins: [tailwindcss()],
}
`,
'index.html': html`
<div class="text-primary"></div>
`,
'tailwind.config.mjs': js`
import myColor from './my-color.mjs'
export default {
theme: {
extend: {
colors: {
primary: myColor,
},
},
},
}
`,
'my-color.mjs': js`export default 'blue'`,
'src/index.css': css`
@import 'tailwindcss';
@config '../tailwind.config.mjs';
`,
},
},
async ({ fs, spawn }) => {
await spawn('pnpm postcss src/index.css --output dist/out.css --watch --verbose')
await fs.expectFileToContain('dist/out.css', [
//
candidate`text-primary`,
'color: blue',
])
await fs.write('my-color.mjs', js`export default 'red'`)
await fs.expectFileToContain('dist/out.css', [
//
candidate`text-primary`,
'color: red',
])
},
)

View file

@ -257,11 +257,9 @@ test(
},
},
async ({ root, fs, spawn }) => {
let process = await spawn(
'pnpm postcss src/index.css --output dist/out.css --watch --verbose',
{ cwd: path.join(root, 'project-a') },
)
await process.onStderr((message) => message.includes('Waiting for file changes...'))
await spawn('pnpm postcss src/index.css --output dist/out.css --watch --verbose', {
cwd: path.join(root, 'project-a'),
})
await fs.expectFileToContain('project-a/dist/out.css', [
candidate`underline`,

View file

@ -1,3 +0,0 @@
import { register } from 'node:module'
register(import.meta.resolve('@tailwindcss/cli/esm-loader'))

View file

@ -19,14 +19,10 @@
"tailwindcss": "./dist/index.mjs"
},
"exports": {
"./package.json": "./package.json",
"./esm-hook": "./hook/esm.hook.mjs",
"./esm-loader": "./hook/esm.loader.mjs",
"./require-cache": "./hook/require-cache.cjs"
"./package.json": "./package.json"
},
"files": [
"dist",
"hook"
"dist"
],
"publishConfig": {
"provenance": true,
@ -34,13 +30,14 @@
},
"dependencies": {
"@parcel/watcher": "^2.4.1",
"@tailwindcss/node": "workspace:^",
"@tailwindcss/oxide": "workspace:^",
"enhanced-resolve": "^5.17.1",
"lightningcss": "catalog:",
"mri": "^1.2.0",
"picocolors": "^1.0.1",
"postcss": "^8.4.41",
"postcss-import": "^16.1.0",
"postcss": "^8.4.41",
"tailwindcss": "workspace:^"
},
"devDependencies": {

View file

@ -1,4 +1,8 @@
import '@tailwindcss/node/esm-cache-hook'
import watcher from '@parcel/watcher'
import { getModuleDependencies } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner, type ChangedContent } from '@tailwindcss/oxide'
import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
import { Features, transform } from 'lightningcss'
@ -11,7 +15,6 @@ import atImport from 'postcss-import'
import * as tailwindcss from 'tailwindcss'
import type { Arg, Result } from '../../utils/args'
import { Disposables } from '../../utils/disposables'
import { getModuleDependencies } from '../../utils/get-module-dependencies'
import {
eprintln,
formatDuration,
@ -23,10 +26,6 @@ import {
import { resolveCssId } from '../../utils/resolve'
import { drainStdin, outputFile } from './utils'
import '@tailwindcss/cli/esm-hook'
// @ts-ignore
import clearRequireCache from '@tailwindcss/cli/require-cache'
const css = String.raw
export function options() {
@ -155,7 +154,6 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
return import(configPath).then((m) => m.default ?? m)
}
console.log('RELOAD')
let resolvedPath = path.resolve(inputBasePath, configPath)
fullRebuildPaths.push(resolvedPath)
fullRebuildPaths.push(...getModuleDependencies(resolvedPath))

View file

@ -0,0 +1,40 @@
<p align="center">
<a href="https://tailwindcss.com" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg">
<img alt="Tailwind CSS" src="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg" width="350" height="70" style="max-width: 100%;">
</picture>
</a>
</p>
<p align="center">
A utility-first CSS framework for rapidly building custom user interfaces.
</p>
<p align="center">
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=next" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
<a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
<a href="https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>
</p>
---
## Documentation
For full documentation, visit [tailwindcss.com](https://tailwindcss.com).
## Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions)
For chatting with others using the framework:
[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe)
## Contributing
If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**.

View file

@ -0,0 +1,43 @@
{
"name": "@tailwindcss/node",
"version": "4.0.0-alpha.19",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tailwindlabs/tailwindcss.git",
"directory": "packages/@tailwindcss-node"
},
"bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
"homepage": "https://tailwindcss.com",
"scripts": {
"build": "tsup-node",
"dev": "pnpm run build -- --watch"
},
"files": [
"dist/"
],
"publishConfig": {
"provenance": true,
"access": "public"
},
"exports": {
".": {
"types": "./dist/index.d.mjs",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./require-cache": {
"types": "./dist/require-cache.d.ts",
"default": "./dist/require-cache.js"
},
"./esm-cache-hook": {
"types": "./dist/esm-cache.hook.d.mts",
"default": "./dist/esm-cache.hook.mjs"
},
"./esm-cache-loader": {
"types": "./dist/esm-cache.loader.d.mts",
"default": "./dist/esm-cache.loader.mjs"
}
}
}

View file

@ -0,0 +1,8 @@
import { register } from 'node:module'
import { pathToFileURL } from 'node:url'
if (typeof require === 'undefined') {
register(import.meta.resolve('@tailwindcss/node/esm-cache-loader'))
} else {
register(pathToFileURL(require.resolve('@tailwindcss/node/esm-cache-loader')))
}

View file

@ -1,6 +1,6 @@
import { isBuiltin } from 'node:module'
import { isBuiltin, type ResolveHook } from 'node:module'
export async function resolve(specifier, context, nextResolve) {
export let resolve: ResolveHook = async (specifier, context, nextResolve) => {
const result = await nextResolve(specifier, context)
if (result.url === import.meta.url) return result
@ -15,8 +15,6 @@ export async function resolve(specifier, context, nextResolve) {
let url = new URL(result.url)
url.searchParams.set('id', id)
console.log({ url })
return {
...result,
url: `${url}`,

View file

@ -1,5 +1,5 @@
import fs from 'fs'
import path from 'path'
import fs from 'node:fs'
import path from 'node:path'
let jsExtensions = ['.js', '.cjs', '.mjs']

View file

@ -0,0 +1,3 @@
import './esm-cache.hook.mjs'
export * from './get-module-dependencies'

View file

@ -1,4 +1,4 @@
module.exports = function clearRequireCache() {
export function clearRequireCache() {
for (const key in require.cache) {
delete require.cache[key]
}

View file

@ -0,0 +1,3 @@
{
"extends": "../tsconfig.base.json",
}

View file

@ -0,0 +1,23 @@
import { defineConfig } from 'tsup'
export default defineConfig([
{
format: ['cjs', 'esm'],
minify: true,
dts: true,
entry: ['src/index.ts'],
},
{
format: ['esm'],
minify: true,
dts: true,
entry: ['src/esm-cache.hook.mts', 'src/esm-cache.loader.mts'],
},
{
format: ['cjs'],
minify: true,
dts: true,
entry: ['src/require-cache.cts'],
},
])

View file

@ -30,6 +30,7 @@
}
},
"dependencies": {
"@tailwindcss/node": "workspace:^",
"@tailwindcss/oxide": "workspace:^",
"lightningcss": "catalog:",
"postcss-import": "^16.1.0",

View file

@ -1,3 +1,5 @@
import { getModuleDependencies } from '@tailwindcss/node'
// import '@tailwindcss/node/esm-cache-hook'
import { Scanner } from '@tailwindcss/oxide'
import fs from 'fs'
import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
@ -47,6 +49,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
compiler: null as null | Awaited<ReturnType<typeof compile>>,
css: '',
optimizedCss: '',
fullRebuildPaths: [] as string[],
}
})
@ -80,25 +83,32 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
let inputBasePath = path.dirname(path.resolve(inputFile))
function createCompiler() {
context.fullRebuildPaths = []
return compile(root.toString(), {
loadPlugin: async (pluginPath) => {
if (pluginPath[0] === '.') {
return import(pathToFileURL(path.resolve(inputBasePath, pluginPath)).href).then(
(m) => m.default ?? m,
)
if (pluginPath[0] !== '.') {
return import(pluginPath).then((m) => m.default ?? m)
}
return import(pluginPath).then((m) => m.default ?? m)
let resolvedPath = path.resolve(inputBasePath, pluginPath)
context.fullRebuildPaths.push(resolvedPath)
context.fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
(m) => m.default ?? m,
)
},
loadConfig: async (configPath) => {
if (configPath[0] === '.') {
return import(pathToFileURL(path.resolve(inputBasePath, configPath)).href).then(
(m) => m.default ?? m,
)
if (configPath[0] !== '.') {
return import(configPath).then((m) => m.default ?? m)
}
return import(configPath).then((m) => m.default ?? m)
let resolvedPath = path.resolve(inputBasePath, configPath)
context.fullRebuildPaths.push(resolvedPath)
context.fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
(m) => m.default ?? m,
)
},
})
}
@ -111,11 +121,23 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
// Track file modification times to CSS files
{
// console.log({ fullRebuildPaths: context.fullRebuildPaths })
for (let file of context.fullRebuildPaths) {
result.messages.push({
type: 'dependency',
plugin: '@tailwindcss/postcss',
file,
parent: result.opts.from,
})
}
let files = result.messages.flatMap((message) => {
if (message.type !== 'dependency') return []
return message.file
})
files.push(inputFile)
// console.log({ files })
for (let file of files) {
let changedTime = fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? null
if (changedTime === null) {

8
pnpm-lock.yaml generated
View file

@ -138,6 +138,9 @@ importers:
'@parcel/watcher':
specifier: ^2.4.1
version: 2.4.1(patch_hash=pnjyuz76kbyy7yxsvyvmenfmha)
'@tailwindcss/node':
specifier: workspace:^
version: link:../@tailwindcss-node
'@tailwindcss/oxide':
specifier: workspace:^
version: link:../../crates/node
@ -170,8 +173,13 @@ importers:
specifier: workspace:^
version: link:../internal-postcss-fix-relative-paths
packages/@tailwindcss-node: {}
packages/@tailwindcss-postcss:
dependencies:
'@tailwindcss/node':
specifier: workspace:^
version: link:../@tailwindcss-node
'@tailwindcss/oxide':
specifier: workspace:^
version: link:../../crates/node