Compare commits
41 commits
main
...
feat/v4-so
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be8cc9538c | ||
|
|
3551daad37 | ||
|
|
ec0453dfec | ||
|
|
83e2cd296d | ||
|
|
f3fc8137f2 | ||
|
|
347d9e2971 | ||
|
|
e3561fba8e | ||
|
|
58932d5a25 | ||
|
|
1992ba55ab | ||
|
|
8fdb8baaa1 | ||
|
|
db5d97403e | ||
|
|
78915dc156 | ||
|
|
2e8d099475 | ||
|
|
5e2fd98f60 | ||
|
|
65e2c17627 | ||
|
|
c9cb8a7deb | ||
|
|
502bda71b8 | ||
|
|
00358d0686 | ||
|
|
bb78c59770 | ||
|
|
2f25e3ec3a | ||
|
|
acf5f3182e | ||
|
|
2302668b48 | ||
|
|
b191165f44 | ||
|
|
a0a9a33952 | ||
|
|
9a9b47a1fa | ||
|
|
6b9ace2679 | ||
|
|
6febfd966a | ||
|
|
fcd4e2d2bc | ||
|
|
386e72158c | ||
|
|
3143eb6e7a | ||
|
|
3bc80cda67 | ||
|
|
c129f48cae | ||
|
|
e95e6e239f | ||
|
|
f08f89d7ba | ||
|
|
8319bd83b3 | ||
|
|
65409a98d9 | ||
|
|
da86e231d4 | ||
|
|
e7652b7a71 | ||
|
|
c53f344871 | ||
|
|
68ce361ef4 | ||
|
|
a4cd44d426 |
44 changed files with 4013 additions and 180 deletions
|
|
@ -23,7 +23,7 @@ describe.each([
|
||||||
'Standalone CLI',
|
'Standalone CLI',
|
||||||
path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`),
|
path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`),
|
||||||
],
|
],
|
||||||
])('%s', (_, command) => {
|
])('%s', (kind, command) => {
|
||||||
test(
|
test(
|
||||||
'production build',
|
'production build',
|
||||||
{
|
{
|
||||||
|
|
@ -628,6 +628,194 @@ describe.each([
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'production build + inline source maps',
|
||||||
|
{
|
||||||
|
fs: {
|
||||||
|
'package.json': json`
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss": "workspace:^",
|
||||||
|
"@tailwindcss/cli": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'ssrc/index.html': html`
|
||||||
|
<div class="flex"></div>
|
||||||
|
`,
|
||||||
|
'src/index.css': css`
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
/* */
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
||||||
|
await exec(`${command} --input src/index.css --output dist/out.css --map`)
|
||||||
|
|
||||||
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||||
|
|
||||||
|
// Make sure we can find a source map
|
||||||
|
let map = parseSourceMap(await fs.read('dist/out.css'))
|
||||||
|
|
||||||
|
expect(map.at(1, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '/*! tailwi...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(2, 0)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: '.flex {...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(3, 2)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: 'display: f...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(4, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '}...',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'production build + separate source maps',
|
||||||
|
{
|
||||||
|
fs: {
|
||||||
|
'package.json': json`
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss": "workspace:^",
|
||||||
|
"@tailwindcss/cli": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'ssrc/index.html': html`
|
||||||
|
<div class="flex"></div>
|
||||||
|
`,
|
||||||
|
'src/index.css': css`
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
/* */
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
||||||
|
await exec(`${command} --input src/index.css --output dist/out.css --map dist/out.css.map`)
|
||||||
|
|
||||||
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||||
|
|
||||||
|
// Make sure we can find a source map
|
||||||
|
let map = parseSourceMap({
|
||||||
|
map: await fs.read('dist/out.css.map'),
|
||||||
|
content: await fs.read('dist/out.css'),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(1, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '/*! tailwi...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(2, 0)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: '.flex {...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(3, 2)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: 'display: f...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(4, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '}...',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Skipped because Lightning CSS has a bug with source maps containing
|
||||||
|
// license comments and it breaks stuff.
|
||||||
|
test.skip(
|
||||||
|
'production build + minify + source maps',
|
||||||
|
{
|
||||||
|
fs: {
|
||||||
|
'package.json': json`
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tailwindcss": "workspace:^",
|
||||||
|
"@tailwindcss/cli": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'ssrc/index.html': html`
|
||||||
|
<div class="flex"></div>
|
||||||
|
`,
|
||||||
|
'src/index.css': css`
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
/* */
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
||||||
|
await exec(`${command} --input src/index.css --output dist/out.css --minify --map`)
|
||||||
|
|
||||||
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||||
|
|
||||||
|
// Make sure we can find a source map
|
||||||
|
let map = parseSourceMap(await fs.read('dist/out.css'))
|
||||||
|
|
||||||
|
expect(map.at(1, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '/*! tailwi...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(2, 0)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: '.flex {...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(3, 2)).toMatchObject({
|
||||||
|
source:
|
||||||
|
kind === 'CLI'
|
||||||
|
? expect.stringContaining('node_modules/tailwindcss/utilities.css')
|
||||||
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: 'display: f...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(4, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '}...',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dedent": "1.5.3",
|
"dedent": "1.5.3",
|
||||||
"fast-glob": "^3.3.3"
|
"fast-glob": "^3.3.3",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -712,3 +712,68 @@ test(
|
||||||
await retryAssertion(async () => expect(await fs.read('dist/out.css')).toEqual(''))
|
await retryAssertion(async () => expect(await fs.read('dist/out.css')).toEqual(''))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'dev mode + source maps',
|
||||||
|
{
|
||||||
|
fs: {
|
||||||
|
'package.json': json`
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"postcss": "^8",
|
||||||
|
"postcss-cli": "^11",
|
||||||
|
"tailwindcss": "workspace:^",
|
||||||
|
"@tailwindcss/postcss": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'postcss.config.js': js`
|
||||||
|
module.exports = {
|
||||||
|
map: { inline: true },
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'src/index.html': html`
|
||||||
|
<div class="flex"></div>
|
||||||
|
`,
|
||||||
|
'src/index.css': css`
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
@source not inline("inline");
|
||||||
|
/* */
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ fs, exec, expect, parseSourceMap }) => {
|
||||||
|
await exec('pnpm postcss src/index.css --output dist/out.css')
|
||||||
|
|
||||||
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||||
|
|
||||||
|
let map = parseSourceMap(await fs.read('dist/out.css'))
|
||||||
|
|
||||||
|
expect(map.at(1, 0)).toMatchObject({
|
||||||
|
source: '<no source>',
|
||||||
|
original: '(none)',
|
||||||
|
generated: '/*! tailwi...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(2, 0)).toMatchObject({
|
||||||
|
source: expect.stringContaining('node_modules/tailwindcss/utilities.css'),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: '.flex {...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(3, 2)).toMatchObject({
|
||||||
|
source: expect.stringContaining('node_modules/tailwindcss/utilities.css'),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: 'display: f...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(4, 0)).toMatchObject({
|
||||||
|
source: expect.stringContaining('node_modules/tailwindcss/utilities.css'),
|
||||||
|
original: ';...',
|
||||||
|
generated: '}...',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import fs from 'node:fs/promises'
|
||||||
import { platform, tmpdir } from 'node:os'
|
import { platform, tmpdir } from 'node:os'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { stripVTControlCharacters } from 'node:util'
|
import { stripVTControlCharacters } from 'node:util'
|
||||||
|
import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||||
import { test as defaultTest, type ExpectStatic } from 'vitest'
|
import { test as defaultTest, type ExpectStatic } from 'vitest'
|
||||||
|
import { createLineTable } from '../packages/tailwindcss/src/source-maps/line-table'
|
||||||
import { escape } from '../packages/tailwindcss/src/utils/escape'
|
import { escape } from '../packages/tailwindcss/src/utils/escape'
|
||||||
|
|
||||||
const REPO_ROOT = path.join(__dirname, '..')
|
const REPO_ROOT = path.join(__dirname, '..')
|
||||||
|
|
@ -42,6 +44,7 @@ interface TestContext {
|
||||||
expect: ExpectStatic
|
expect: ExpectStatic
|
||||||
exec(command: string, options?: ChildProcessOptions, execOptions?: ExecOptions): Promise<string>
|
exec(command: string, options?: ChildProcessOptions, execOptions?: ExecOptions): Promise<string>
|
||||||
spawn(command: string, options?: ChildProcessOptions): Promise<SpawnedProcess>
|
spawn(command: string, options?: ChildProcessOptions): Promise<SpawnedProcess>
|
||||||
|
parseSourceMap(opts: string | SourceMapOptions): SourceMap
|
||||||
fs: {
|
fs: {
|
||||||
write(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>
|
write(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>
|
||||||
create(filePaths: string[]): Promise<void>
|
create(filePaths: string[]): Promise<void>
|
||||||
|
|
@ -104,6 +107,7 @@ export function test(
|
||||||
let context = {
|
let context = {
|
||||||
root,
|
root,
|
||||||
expect: options.expect,
|
expect: options.expect,
|
||||||
|
parseSourceMap,
|
||||||
async exec(
|
async exec(
|
||||||
command: string,
|
command: string,
|
||||||
childProcessOptions: ChildProcessOptions = {},
|
childProcessOptions: ChildProcessOptions = {},
|
||||||
|
|
@ -591,7 +595,9 @@ export async function fetchStyles(base: string, path = '/'): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return stylesheets.reduce((acc, css) => {
|
return stylesheets.reduce((acc, css) => {
|
||||||
return acc + '\n' + css
|
if (acc.length > 0) acc += '\n'
|
||||||
|
acc += css
|
||||||
|
return acc
|
||||||
}, '')
|
}, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -603,3 +609,82 @@ async function gracefullyRemove(dir: string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SOURCE_MAP_COMMENT = /^\/\*# sourceMappingURL=data:application\/json;base64,(.*) \*\/$/
|
||||||
|
|
||||||
|
export interface SourceMap {
|
||||||
|
at(
|
||||||
|
line: number,
|
||||||
|
column: number,
|
||||||
|
): {
|
||||||
|
source: string | null
|
||||||
|
original: string
|
||||||
|
generated: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SourceMapOptions {
|
||||||
|
/**
|
||||||
|
* A raw source map
|
||||||
|
*
|
||||||
|
* This may be a string or an object. Strings will be decoded.
|
||||||
|
*/
|
||||||
|
map: string | object
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of the generated file the source map is for
|
||||||
|
*/
|
||||||
|
content: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoding of the source map
|
||||||
|
*
|
||||||
|
* Can be used to decode a base64 map (e.g. an inline source map URI)
|
||||||
|
*/
|
||||||
|
encoding?: BufferEncoding
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSourceMap(opts: string | SourceMapOptions): SourceMap {
|
||||||
|
if (typeof opts === 'string') {
|
||||||
|
let lines = opts.trimEnd().split('\n')
|
||||||
|
let comment = lines.at(-1) ?? ''
|
||||||
|
let map = String(comment).match(SOURCE_MAP_COMMENT)?.[1] ?? null
|
||||||
|
if (!map) throw new Error('No source map comment found')
|
||||||
|
|
||||||
|
return parseSourceMap({
|
||||||
|
map,
|
||||||
|
content: lines.slice(0, -1).join('\n'),
|
||||||
|
encoding: 'base64',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rawMap: RawSourceMap
|
||||||
|
let content = opts.content
|
||||||
|
|
||||||
|
if (typeof opts.map === 'object') {
|
||||||
|
rawMap = opts.map as RawSourceMap
|
||||||
|
} else {
|
||||||
|
rawMap = JSON.parse(Buffer.from(opts.map, opts.encoding ?? 'utf-8').toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = new SourceMapConsumer(rawMap)
|
||||||
|
let generatedTable = createLineTable(content)
|
||||||
|
|
||||||
|
return {
|
||||||
|
at(line: number, column: number) {
|
||||||
|
let pos = map.originalPositionFor({ line, column })
|
||||||
|
let source = pos.source ? map.sourceContentFor(pos.source) : null
|
||||||
|
let originalTable = createLineTable(source ?? '')
|
||||||
|
let originalOffset = originalTable.findOffset(pos)
|
||||||
|
let generatedOffset = generatedTable.findOffset({ line, column })
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: pos.source,
|
||||||
|
original: source
|
||||||
|
? source.slice(originalOffset, originalOffset + 10).trim() + '...'
|
||||||
|
: '(none)',
|
||||||
|
generated: content.slice(generatedOffset, generatedOffset + 10).trim() + '...',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
93
integrations/vite/source-maps.test.ts
Normal file
93
integrations/vite/source-maps.test.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils'
|
||||||
|
|
||||||
|
test(
|
||||||
|
`dev build`,
|
||||||
|
{
|
||||||
|
fs: {
|
||||||
|
'package.json': json`
|
||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "workspace:^",
|
||||||
|
"tailwindcss": "workspace:^"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"lightningcss": "^1.26.0",
|
||||||
|
"vite": "^6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'vite.config.ts': ts`
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [tailwindcss()],
|
||||||
|
css: {
|
||||||
|
devSourcemap: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
'index.html': html`
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="./src/index.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex">Hello, world!</div>
|
||||||
|
</body>
|
||||||
|
`,
|
||||||
|
'src/index.css': css`
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
/* */
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ fs, spawn, expect, parseSourceMap }) => {
|
||||||
|
// Source maps only work in development mode in Vite
|
||||||
|
let process = await spawn('pnpm vite dev')
|
||||||
|
await process.onStdout((m) => m.includes('ready in'))
|
||||||
|
|
||||||
|
let url = ''
|
||||||
|
await process.onStdout((m) => {
|
||||||
|
let match = /Local:\s*(http.*)\//.exec(m)
|
||||||
|
if (match) url = match[1]
|
||||||
|
return Boolean(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
let styles = await retryAssertion(async () => {
|
||||||
|
let styles = await fetchStyles(url, '/index.html')
|
||||||
|
|
||||||
|
// Wait until we have the right CSS
|
||||||
|
expect(styles).toContain(candidate`flex`)
|
||||||
|
|
||||||
|
return styles
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make sure we can find a source map
|
||||||
|
let map = parseSourceMap(styles)
|
||||||
|
|
||||||
|
expect(map.at(1, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '/*! tailwi...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(2, 0)).toMatchObject({
|
||||||
|
source: expect.stringContaining('node_modules/tailwindcss/utilities.css'),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: '.flex {...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(3, 2)).toMatchObject({
|
||||||
|
source: expect.stringContaining('node_modules/tailwindcss/utilities.css'),
|
||||||
|
original: '@tailwind...',
|
||||||
|
generated: 'display: f...',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map.at(4, 0)).toMatchObject({
|
||||||
|
source: null,
|
||||||
|
original: '(none)',
|
||||||
|
generated: '}...',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -116,6 +116,7 @@ async function loadStylesheet(id: string, base: string) {
|
||||||
function load() {
|
function load() {
|
||||||
if (id === 'tailwindcss') {
|
if (id === 'tailwindcss') {
|
||||||
return {
|
return {
|
||||||
|
path: 'virtual:tailwindcss/index.css',
|
||||||
base,
|
base,
|
||||||
content: assets.css.index,
|
content: assets.css.index,
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +126,7 @@ async function loadStylesheet(id: string, base: string) {
|
||||||
id === './preflight.css'
|
id === './preflight.css'
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
path: 'virtual:tailwindcss/preflight.css',
|
||||||
base,
|
base,
|
||||||
content: assets.css.preflight,
|
content: assets.css.preflight,
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +136,7 @@ async function loadStylesheet(id: string, base: string) {
|
||||||
id === './theme.css'
|
id === './theme.css'
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
path: 'virtual:tailwindcss/theme.css',
|
||||||
base,
|
base,
|
||||||
content: assets.css.theme,
|
content: assets.css.theme,
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +146,7 @@ async function loadStylesheet(id: string, base: string) {
|
||||||
id === './utilities.css'
|
id === './utilities.css'
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
path: 'virtual:tailwindcss/utilities.css',
|
||||||
base,
|
base,
|
||||||
content: assets.css.utilities,
|
content: assets.css.utilities,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import watcher from '@parcel/watcher'
|
import watcher from '@parcel/watcher'
|
||||||
import { compile, env, Instrumentation, optimize } from '@tailwindcss/node'
|
import {
|
||||||
|
compile,
|
||||||
|
env,
|
||||||
|
Instrumentation,
|
||||||
|
optimize,
|
||||||
|
toSourceMap,
|
||||||
|
type SourceMap,
|
||||||
|
} from '@tailwindcss/node'
|
||||||
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
||||||
import { Scanner, type ChangedContent } from '@tailwindcss/oxide'
|
import { Scanner, type ChangedContent } from '@tailwindcss/oxide'
|
||||||
import { existsSync, type Stats } from 'node:fs'
|
import { existsSync, type Stats } from 'node:fs'
|
||||||
|
|
@ -52,6 +59,11 @@ export function options() {
|
||||||
description: 'The current working directory',
|
description: 'The current working directory',
|
||||||
default: '.',
|
default: '.',
|
||||||
},
|
},
|
||||||
|
'--map': {
|
||||||
|
type: 'boolean | string',
|
||||||
|
description: 'Generate a source map',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
} satisfies Arg
|
} satisfies Arg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +117,21 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user passes `{bin} build --map -` then this likely means they want to output the map inline
|
||||||
|
// this is the default behavior of `{bin build} --map` to inform the user of that
|
||||||
|
if (args['--map'] === '-') {
|
||||||
|
eprintln(header())
|
||||||
|
eprintln()
|
||||||
|
eprintln(`Use --map without a value to inline the source map`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the map as an absolute path. If the output is true then we
|
||||||
|
// don't need to resolve it because it'll be an inline source map
|
||||||
|
if (args['--map'] && args['--map'] !== true) {
|
||||||
|
args['--map'] = path.resolve(base, args['--map'])
|
||||||
|
}
|
||||||
|
|
||||||
let start = process.hrtime.bigint()
|
let start = process.hrtime.bigint()
|
||||||
|
|
||||||
let input = args['--input']
|
let input = args['--input']
|
||||||
|
|
@ -120,27 +147,48 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
optimizedCss: '',
|
optimizedCss: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
async function write(css: string, args: Result<ReturnType<typeof options>>, I: Instrumentation) {
|
async function write(
|
||||||
|
css: string,
|
||||||
|
map: SourceMap | null,
|
||||||
|
args: Result<ReturnType<typeof options>>,
|
||||||
|
I: Instrumentation,
|
||||||
|
) {
|
||||||
let output = css
|
let output = css
|
||||||
|
|
||||||
// Optimize the output
|
// Optimize the output
|
||||||
if (args['--minify'] || args['--optimize']) {
|
if (args['--minify'] || args['--optimize']) {
|
||||||
if (css !== previous.css) {
|
if (css !== previous.css) {
|
||||||
DEBUG && I.start('Optimize CSS')
|
DEBUG && I.start('Optimize CSS')
|
||||||
let optimizedCss = optimize(css, {
|
let optimized = optimize(css, {
|
||||||
file: args['--input'] ?? 'input.css',
|
file: args['--input'] ?? 'input.css',
|
||||||
minify: args['--minify'] ?? false,
|
minify: args['--minify'] ?? false,
|
||||||
|
map: map?.raw ?? undefined,
|
||||||
})
|
})
|
||||||
DEBUG && I.end('Optimize CSS')
|
DEBUG && I.end('Optimize CSS')
|
||||||
previous.css = css
|
previous.css = css
|
||||||
previous.optimizedCss = optimizedCss
|
previous.optimizedCss = optimized.code
|
||||||
output = optimizedCss
|
if (optimized.map) {
|
||||||
|
map = toSourceMap(optimized.map)
|
||||||
|
}
|
||||||
|
output = optimized.code
|
||||||
} else {
|
} else {
|
||||||
output = previous.optimizedCss
|
output = previous.optimizedCss
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the output
|
// Write the output
|
||||||
|
if (map) {
|
||||||
|
// Inline the source map
|
||||||
|
if (args['--map'] === true) {
|
||||||
|
output += `\n`
|
||||||
|
output += map.inline
|
||||||
|
} else if (typeof args['--map'] === 'string') {
|
||||||
|
DEBUG && I.start('Write source map')
|
||||||
|
await outputFile(args['--map'], map.raw)
|
||||||
|
DEBUG && I.end('Write source map')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG && I.start('Write output')
|
DEBUG && I.start('Write output')
|
||||||
if (args['--output'] && args['--output'] !== '-') {
|
if (args['--output'] && args['--output'] !== '-') {
|
||||||
await outputFile(args['--output'], output)
|
await outputFile(args['--output'], output)
|
||||||
|
|
@ -160,6 +208,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
async function createCompiler(css: string, I: Instrumentation) {
|
async function createCompiler(css: string, I: Instrumentation) {
|
||||||
DEBUG && I.start('Setup compiler')
|
DEBUG && I.start('Setup compiler')
|
||||||
let compiler = await compile(css, {
|
let compiler = await compile(css, {
|
||||||
|
from: args['--output'] ? (inputFilePath ?? 'stdin.css') : undefined,
|
||||||
base: inputBasePath,
|
base: inputBasePath,
|
||||||
onDependency(path) {
|
onDependency(path) {
|
||||||
fullRebuildPaths.push(path)
|
fullRebuildPaths.push(path)
|
||||||
|
|
@ -231,6 +280,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
|
|
||||||
// Track the compiled CSS
|
// Track the compiled CSS
|
||||||
let compiledCss = ''
|
let compiledCss = ''
|
||||||
|
let compiledMap: SourceMap | null = null
|
||||||
|
|
||||||
// Scan the entire `base` directory for full rebuilds.
|
// Scan the entire `base` directory for full rebuilds.
|
||||||
if (rebuildStrategy === 'full') {
|
if (rebuildStrategy === 'full') {
|
||||||
|
|
@ -269,6 +319,12 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
DEBUG && I.start('Build CSS')
|
DEBUG && I.start('Build CSS')
|
||||||
compiledCss = compiler.build(candidates)
|
compiledCss = compiler.build(candidates)
|
||||||
DEBUG && I.end('Build CSS')
|
DEBUG && I.end('Build CSS')
|
||||||
|
|
||||||
|
if (args['--map']) {
|
||||||
|
DEBUG && I.start('Build Source Map')
|
||||||
|
compiledMap = compiler.buildSourceMap() as any
|
||||||
|
DEBUG && I.end('Build Source Map')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan changed files only for incremental rebuilds.
|
// Scan changed files only for incremental rebuilds.
|
||||||
|
|
@ -288,9 +344,15 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
DEBUG && I.start('Build CSS')
|
DEBUG && I.start('Build CSS')
|
||||||
compiledCss = compiler.build(newCandidates)
|
compiledCss = compiler.build(newCandidates)
|
||||||
DEBUG && I.end('Build CSS')
|
DEBUG && I.end('Build CSS')
|
||||||
|
|
||||||
|
if (args['--map']) {
|
||||||
|
DEBUG && I.start('Build Source Map')
|
||||||
|
compiledMap = compiler.buildSourceMap() as any
|
||||||
|
DEBUG && I.end('Build Source Map')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await write(compiledCss, args, I)
|
await write(compiledCss, compiledMap, args, I)
|
||||||
|
|
||||||
let end = process.hrtime.bigint()
|
let end = process.hrtime.bigint()
|
||||||
eprintln(`Done in ${formatDuration(end - start)}`)
|
eprintln(`Done in ${formatDuration(end - start)}`)
|
||||||
|
|
@ -325,7 +387,16 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
||||||
DEBUG && I.start('Build CSS')
|
DEBUG && I.start('Build CSS')
|
||||||
let output = await handleError(() => compiler.build(candidates))
|
let output = await handleError(() => compiler.build(candidates))
|
||||||
DEBUG && I.end('Build CSS')
|
DEBUG && I.end('Build CSS')
|
||||||
await write(output, args, I)
|
|
||||||
|
let map: SourceMap | null = null
|
||||||
|
|
||||||
|
if (args['--map']) {
|
||||||
|
DEBUG && I.start('Build Source Map')
|
||||||
|
map = await handleError(() => toSourceMap(compiler.buildSourceMap()))
|
||||||
|
DEBUG && I.end('Build Source Map')
|
||||||
|
}
|
||||||
|
|
||||||
|
await write(output, map, args, I)
|
||||||
|
|
||||||
let end = process.hrtime.bigint()
|
let end = process.hrtime.bigint()
|
||||||
eprintln(header())
|
eprintln(header())
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ampproject/remapping": "^2.3.0",
|
||||||
"enhanced-resolve": "^5.18.1",
|
"enhanced-resolve": "^5.18.1",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"tailwindcss": "workspace:*",
|
"lightningcss": "catalog:",
|
||||||
"lightningcss": "catalog:"
|
"magic-string": "^0.30.17",
|
||||||
|
"source-map-js": "^1.2.1",
|
||||||
|
"tailwindcss": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import EnhancedResolve from 'enhanced-resolve'
|
||||||
import { createJiti, type Jiti } from 'jiti'
|
import { createJiti, type Jiti } from 'jiti'
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import fsPromises from 'node:fs/promises'
|
import fsPromises from 'node:fs/promises'
|
||||||
import path, { dirname } from 'node:path'
|
import path from 'node:path'
|
||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
import {
|
import {
|
||||||
__unstable__loadDesignSystem as ___unstable__loadDesignSystem,
|
__unstable__loadDesignSystem as ___unstable__loadDesignSystem,
|
||||||
|
|
@ -21,6 +21,7 @@ export type Resolver = (id: string, base: string) => Promise<string | false | un
|
||||||
|
|
||||||
export interface CompileOptions {
|
export interface CompileOptions {
|
||||||
base: string
|
base: string
|
||||||
|
from?: string
|
||||||
onDependency: (path: string) => void
|
onDependency: (path: string) => void
|
||||||
shouldRewriteUrls?: boolean
|
shouldRewriteUrls?: boolean
|
||||||
polyfills?: Polyfills
|
polyfills?: Polyfills
|
||||||
|
|
@ -31,6 +32,7 @@ export interface CompileOptions {
|
||||||
|
|
||||||
function createCompileOptions({
|
function createCompileOptions({
|
||||||
base,
|
base,
|
||||||
|
from,
|
||||||
polyfills,
|
polyfills,
|
||||||
onDependency,
|
onDependency,
|
||||||
shouldRewriteUrls,
|
shouldRewriteUrls,
|
||||||
|
|
@ -41,6 +43,7 @@ function createCompileOptions({
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
polyfills,
|
polyfills,
|
||||||
|
from,
|
||||||
async loadModule(id: string, base: string) {
|
async loadModule(id: string, base: string) {
|
||||||
return loadModule(id, base, onDependency, customJsResolver)
|
return loadModule(id, base, onDependency, customJsResolver)
|
||||||
},
|
},
|
||||||
|
|
@ -125,7 +128,8 @@ export async function loadModule(
|
||||||
|
|
||||||
let module = await importModule(pathToFileURL(resolvedPath).href)
|
let module = await importModule(pathToFileURL(resolvedPath).href)
|
||||||
return {
|
return {
|
||||||
base: dirname(resolvedPath),
|
path: resolvedPath,
|
||||||
|
base: path.dirname(resolvedPath),
|
||||||
module: module.default ?? module,
|
module: module.default ?? module,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +148,8 @@ export async function loadModule(
|
||||||
onDependency(file)
|
onDependency(file)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
base: dirname(resolvedPath),
|
path: resolvedPath,
|
||||||
|
base: path.dirname(resolvedPath),
|
||||||
module: module.default ?? module,
|
module: module.default ?? module,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +169,7 @@ async function loadStylesheet(
|
||||||
let file = await globalThis.__tw_readFile(resolvedPath, 'utf-8')
|
let file = await globalThis.__tw_readFile(resolvedPath, 'utf-8')
|
||||||
if (file) {
|
if (file) {
|
||||||
return {
|
return {
|
||||||
|
path: resolvedPath,
|
||||||
base: path.dirname(resolvedPath),
|
base: path.dirname(resolvedPath),
|
||||||
content: file,
|
content: file,
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +178,7 @@ async function loadStylesheet(
|
||||||
|
|
||||||
let file = await fsPromises.readFile(resolvedPath, 'utf-8')
|
let file = await fsPromises.readFile(resolvedPath, 'utf-8')
|
||||||
return {
|
return {
|
||||||
|
path: resolvedPath,
|
||||||
base: path.dirname(resolvedPath),
|
base: path.dirname(resolvedPath),
|
||||||
content: file,
|
content: file,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export * from './compile'
|
||||||
export * from './instrumentation'
|
export * from './instrumentation'
|
||||||
export * from './normalize-path'
|
export * from './normalize-path'
|
||||||
export * from './optimize'
|
export * from './optimize'
|
||||||
|
export * from './source-maps'
|
||||||
export { env }
|
export { env }
|
||||||
|
|
||||||
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
|
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export * from './compile'
|
||||||
export * from './instrumentation'
|
export * from './instrumentation'
|
||||||
export * from './normalize-path'
|
export * from './normalize-path'
|
||||||
export * from './optimize'
|
export * from './optimize'
|
||||||
|
export * from './source-maps'
|
||||||
export { env }
|
export { env }
|
||||||
|
|
||||||
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
|
// In Bun, ESM modules will also populate `require.cache`, so the module hook is
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,42 @@
|
||||||
|
import remapping from '@ampproject/remapping'
|
||||||
import { Features, transform } from 'lightningcss'
|
import { Features, transform } from 'lightningcss'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
|
||||||
|
export interface OptimizeOptions {
|
||||||
|
/**
|
||||||
|
* The file being transformed
|
||||||
|
*/
|
||||||
|
file?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabled minified output
|
||||||
|
*/
|
||||||
|
minify?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output source map before optimization
|
||||||
|
*
|
||||||
|
* If omitted a resulting source map will not be available
|
||||||
|
*/
|
||||||
|
map?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformResult {
|
||||||
|
code: string
|
||||||
|
map: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export function optimize(
|
export function optimize(
|
||||||
input: string,
|
input: string,
|
||||||
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
|
{ file = 'input.css', minify = false, map }: OptimizeOptions = {},
|
||||||
): string {
|
): TransformResult {
|
||||||
function optimize(code: Buffer | Uint8Array) {
|
function optimize(code: Buffer | Uint8Array, map: string | undefined) {
|
||||||
return transform({
|
return transform({
|
||||||
filename: file,
|
filename: file,
|
||||||
code: code as any,
|
code,
|
||||||
minify,
|
minify,
|
||||||
sourceMap: false,
|
sourceMap: typeof map !== 'undefined',
|
||||||
|
inputSourceMap: map,
|
||||||
drafts: {
|
drafts: {
|
||||||
customMedia: true,
|
customMedia: true,
|
||||||
},
|
},
|
||||||
|
|
@ -25,16 +52,39 @@ export function optimize(
|
||||||
chrome: 111 << 16,
|
chrome: 111 << 16,
|
||||||
},
|
},
|
||||||
errorRecovery: true,
|
errorRecovery: true,
|
||||||
}).code
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Running Lightning CSS twice to ensure that adjacent rules are merged after
|
// Running Lightning CSS twice to ensure that adjacent rules are merged after
|
||||||
// nesting is applied. This creates a more optimized output.
|
// nesting is applied. This creates a more optimized output.
|
||||||
let out = optimize(optimize(Buffer.from(input))).toString()
|
let result = optimize(Buffer.from(input), map)
|
||||||
|
map = result.map?.toString()
|
||||||
|
|
||||||
|
result = optimize(result.code, map)
|
||||||
|
map = result.map?.toString()
|
||||||
|
|
||||||
|
let code = result.code.toString()
|
||||||
|
|
||||||
// Work around an issue where the media query range syntax transpilation
|
// Work around an issue where the media query range syntax transpilation
|
||||||
// generates code that is invalid with `@media` queries level 3.
|
// generates code that is invalid with `@media` queries level 3.
|
||||||
out = out.replaceAll('@media not (', '@media not all and (')
|
let magic = new MagicString(code)
|
||||||
|
magic.replaceAll('@media not (', '@media not all and (')
|
||||||
|
|
||||||
return out
|
// We have to use a source-map-preserving method of replacing the content
|
||||||
|
// which requires the use of Magic String + remapping(…) to make sure
|
||||||
|
// the resulting map is correct
|
||||||
|
if (map !== undefined && magic.hasChanged()) {
|
||||||
|
let magicMap = magic.generateMap({ source: 'original', hires: 'boundary' }).toString()
|
||||||
|
|
||||||
|
let remapped = remapping([magicMap, map], () => null)
|
||||||
|
|
||||||
|
map = remapped.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
code = magic.toString()
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
map,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
packages/@tailwindcss-node/src/source-maps.ts
Normal file
59
packages/@tailwindcss-node/src/source-maps.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { SourceMapGenerator } from 'source-map-js'
|
||||||
|
import type { DecodedSource, DecodedSourceMap } from '../../tailwindcss/src/source-maps/source-map'
|
||||||
|
import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
|
||||||
|
|
||||||
|
export type { DecodedSource, DecodedSourceMap }
|
||||||
|
export interface SourceMap {
|
||||||
|
readonly raw: string
|
||||||
|
readonly inline: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeSourceMap(map: DecodedSourceMap): string {
|
||||||
|
let generator = new SourceMapGenerator()
|
||||||
|
|
||||||
|
let id = 1
|
||||||
|
let sourceTable = new DefaultMap<
|
||||||
|
DecodedSource | null,
|
||||||
|
{
|
||||||
|
url: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
>((src) => {
|
||||||
|
return {
|
||||||
|
url: src?.url ?? `<unknown ${id++}>`,
|
||||||
|
content: src?.content ?? '<none>',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let mapping of map.mappings) {
|
||||||
|
let original = sourceTable.get(mapping.originalPosition?.source ?? null)
|
||||||
|
|
||||||
|
generator.addMapping({
|
||||||
|
generated: mapping.generatedPosition,
|
||||||
|
original: mapping.originalPosition,
|
||||||
|
source: original.url,
|
||||||
|
name: mapping.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
generator.setSourceContent(original.url, original.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSourceMap(map: DecodedSourceMap | string): SourceMap {
|
||||||
|
let raw = typeof map === 'string' ? map : serializeSourceMap(map)
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw,
|
||||||
|
get inline() {
|
||||||
|
let tmp = ''
|
||||||
|
|
||||||
|
tmp += '/*# sourceMappingURL=data:application/json;base64,'
|
||||||
|
tmp += Buffer.from(raw, 'utf-8').toString('base64')
|
||||||
|
tmp += ' */\n'
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,54 @@
|
||||||
import postcss, {
|
import postcss, {
|
||||||
|
Input,
|
||||||
type ChildNode as PostCssChildNode,
|
type ChildNode as PostCssChildNode,
|
||||||
type Container as PostCssContainerNode,
|
type Container as PostCssContainerNode,
|
||||||
type Root as PostCssRoot,
|
type Root as PostCssRoot,
|
||||||
type Source as PostcssSource,
|
type Source as PostcssSource,
|
||||||
} from 'postcss'
|
} from 'postcss'
|
||||||
import { atRule, comment, decl, rule, type AstNode } from '../../tailwindcss/src/ast'
|
import { atRule, comment, decl, rule, type AstNode } from '../../tailwindcss/src/ast'
|
||||||
|
import { createLineTable, type LineTable } from '../../tailwindcss/src/source-maps/line-table'
|
||||||
|
import type { Source, SourceLocation } from '../../tailwindcss/src/source-maps/source'
|
||||||
|
import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
|
||||||
|
|
||||||
const EXCLAMATION_MARK = 0x21
|
const EXCLAMATION_MARK = 0x21
|
||||||
|
|
||||||
export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undefined): PostCssRoot {
|
export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undefined): PostCssRoot {
|
||||||
|
let inputMap = new DefaultMap<Source, Input>((src) => {
|
||||||
|
return new Input(src.code, {
|
||||||
|
map: source?.input.map,
|
||||||
|
from: src.file ?? undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let lineTables = new DefaultMap<Source, LineTable>((src) => createLineTable(src.code))
|
||||||
|
|
||||||
let root = postcss.root()
|
let root = postcss.root()
|
||||||
root.source = source
|
root.source = source
|
||||||
|
|
||||||
|
function toSource(loc: SourceLocation | undefined): PostcssSource | undefined {
|
||||||
|
// Use the fallback if this node has no location info in the AST
|
||||||
|
if (!loc) return
|
||||||
|
if (!loc[0]) return
|
||||||
|
|
||||||
|
let table = lineTables.get(loc[0])
|
||||||
|
let start = table.find(loc[1])
|
||||||
|
let end = table.find(loc[2])
|
||||||
|
|
||||||
|
return {
|
||||||
|
input: inputMap.get(loc[0]),
|
||||||
|
start: {
|
||||||
|
line: start.line,
|
||||||
|
column: start.column + 1,
|
||||||
|
offset: loc[1],
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: end.line,
|
||||||
|
column: end.column + 1,
|
||||||
|
offset: loc[2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function transform(node: AstNode, parent: PostCssContainerNode) {
|
function transform(node: AstNode, parent: PostCssContainerNode) {
|
||||||
// Declaration
|
// Declaration
|
||||||
if (node.kind === 'declaration') {
|
if (node.kind === 'declaration') {
|
||||||
|
|
@ -20,14 +57,14 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
|
||||||
value: node.value ?? '',
|
value: node.value ?? '',
|
||||||
important: node.important,
|
important: node.important,
|
||||||
})
|
})
|
||||||
astNode.source = source
|
astNode.source = toSource(node.src)
|
||||||
parent.append(astNode)
|
parent.append(astNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule
|
// Rule
|
||||||
else if (node.kind === 'rule') {
|
else if (node.kind === 'rule') {
|
||||||
let astNode = postcss.rule({ selector: node.selector })
|
let astNode = postcss.rule({ selector: node.selector })
|
||||||
astNode.source = source
|
astNode.source = toSource(node.src)
|
||||||
astNode.raws.semicolon = true
|
astNode.raws.semicolon = true
|
||||||
parent.append(astNode)
|
parent.append(astNode)
|
||||||
for (let child of node.nodes) {
|
for (let child of node.nodes) {
|
||||||
|
|
@ -38,7 +75,7 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
|
||||||
// AtRule
|
// AtRule
|
||||||
else if (node.kind === 'at-rule') {
|
else if (node.kind === 'at-rule') {
|
||||||
let astNode = postcss.atRule({ name: node.name.slice(1), params: node.params })
|
let astNode = postcss.atRule({ name: node.name.slice(1), params: node.params })
|
||||||
astNode.source = source
|
astNode.source = toSource(node.src)
|
||||||
astNode.raws.semicolon = true
|
astNode.raws.semicolon = true
|
||||||
parent.append(astNode)
|
parent.append(astNode)
|
||||||
for (let child of node.nodes) {
|
for (let child of node.nodes) {
|
||||||
|
|
@ -53,7 +90,7 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
|
||||||
// spaces.
|
// spaces.
|
||||||
astNode.raws.left = ''
|
astNode.raws.left = ''
|
||||||
astNode.raws.right = ''
|
astNode.raws.right = ''
|
||||||
astNode.source = source
|
astNode.source = toSource(node.src)
|
||||||
parent.append(astNode)
|
parent.append(astNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,18 +112,38 @@ export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undef
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
|
export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
|
||||||
|
let inputMap = new DefaultMap<Input, Source>((input) => ({
|
||||||
|
file: input.file ?? input.id ?? null,
|
||||||
|
code: input.css,
|
||||||
|
}))
|
||||||
|
|
||||||
|
function toSource(node: PostCssChildNode): SourceLocation | undefined {
|
||||||
|
let source = node.source
|
||||||
|
if (!source) return
|
||||||
|
|
||||||
|
let input = source.input
|
||||||
|
if (!input) return
|
||||||
|
if (source.start === undefined) return
|
||||||
|
if (source.end === undefined) return
|
||||||
|
|
||||||
|
return [inputMap.get(input), source.start.offset, source.end.offset]
|
||||||
|
}
|
||||||
|
|
||||||
function transform(
|
function transform(
|
||||||
node: PostCssChildNode,
|
node: PostCssChildNode,
|
||||||
parent: Extract<AstNode, { nodes: AstNode[] }>['nodes'],
|
parent: Extract<AstNode, { nodes: AstNode[] }>['nodes'],
|
||||||
) {
|
) {
|
||||||
// Declaration
|
// Declaration
|
||||||
if (node.type === 'decl') {
|
if (node.type === 'decl') {
|
||||||
parent.push(decl(node.prop, node.value, node.important))
|
let astNode = decl(node.prop, node.value, node.important)
|
||||||
|
astNode.src = toSource(node)
|
||||||
|
parent.push(astNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule
|
// Rule
|
||||||
else if (node.type === 'rule') {
|
else if (node.type === 'rule') {
|
||||||
let astNode = rule(node.selector)
|
let astNode = rule(node.selector)
|
||||||
|
astNode.src = toSource(node)
|
||||||
node.each((child) => transform(child, astNode.nodes))
|
node.each((child) => transform(child, astNode.nodes))
|
||||||
parent.push(astNode)
|
parent.push(astNode)
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +151,7 @@ export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
|
||||||
// AtRule
|
// AtRule
|
||||||
else if (node.type === 'atrule') {
|
else if (node.type === 'atrule') {
|
||||||
let astNode = atRule(`@${node.name}`, node.params)
|
let astNode = atRule(`@${node.name}`, node.params)
|
||||||
|
astNode.src = toSource(node)
|
||||||
node.each((child) => transform(child, astNode.nodes))
|
node.each((child) => transform(child, astNode.nodes))
|
||||||
parent.push(astNode)
|
parent.push(astNode)
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +159,9 @@ export function postCssAstToCssAst(root: PostCssRoot): AstNode[] {
|
||||||
// Comment
|
// Comment
|
||||||
else if (node.type === 'comment') {
|
else if (node.type === 'comment') {
|
||||||
if (node.text.charCodeAt(0) !== EXCLAMATION_MARK) return
|
if (node.text.charCodeAt(0) !== EXCLAMATION_MARK) return
|
||||||
parent.push(comment(node.text))
|
let astNode = comment(node.text)
|
||||||
|
astNode.src = toSource(node)
|
||||||
|
parent.push(astNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown
|
// Unknown
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||||
|
|
||||||
DEBUG && I.start('Create compiler')
|
DEBUG && I.start('Create compiler')
|
||||||
let compiler = await compileAst(ast, {
|
let compiler = await compileAst(ast, {
|
||||||
|
from: result.opts.from,
|
||||||
base: inputBasePath,
|
base: inputBasePath,
|
||||||
shouldRewriteUrls: true,
|
shouldRewriteUrls: true,
|
||||||
onDependency: (path) => context.fullRebuildPaths.push(path),
|
onDependency: (path) => context.fullRebuildPaths.push(path),
|
||||||
|
|
@ -282,13 +283,13 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||||
DEBUG && I.end('AST -> CSS')
|
DEBUG && I.end('AST -> CSS')
|
||||||
|
|
||||||
DEBUG && I.start('Lightning CSS')
|
DEBUG && I.start('Lightning CSS')
|
||||||
let ast = optimizeCss(css, {
|
let optimized = optimizeCss(css, {
|
||||||
minify: typeof optimize === 'object' ? optimize.minify : true,
|
minify: typeof optimize === 'object' ? optimize.minify : true,
|
||||||
})
|
})
|
||||||
DEBUG && I.end('Lightning CSS')
|
DEBUG && I.end('Lightning CSS')
|
||||||
|
|
||||||
DEBUG && I.start('CSS -> PostCSS AST')
|
DEBUG && I.start('CSS -> PostCSS AST')
|
||||||
context.optimizedPostCssAst = postcss.parse(ast, result.opts)
|
context.optimizedPostCssAst = postcss.parse(optimized.code, result.opts)
|
||||||
DEBUG && I.end('CSS -> PostCSS AST')
|
DEBUG && I.end('CSS -> PostCSS AST')
|
||||||
|
|
||||||
DEBUG && I.end('Optimization')
|
DEBUG && I.end('Optimization')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
import { compile, env, Features, Instrumentation, normalizePath, optimize } from '@tailwindcss/node'
|
import {
|
||||||
|
compile,
|
||||||
|
env,
|
||||||
|
Features,
|
||||||
|
Instrumentation,
|
||||||
|
normalizePath,
|
||||||
|
optimize,
|
||||||
|
toSourceMap,
|
||||||
|
} from '@tailwindcss/node'
|
||||||
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
import { clearRequireCache } from '@tailwindcss/node/require-cache'
|
||||||
import { Scanner } from '@tailwindcss/oxide'
|
import { Scanner } from '@tailwindcss/oxide'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
|
|
@ -34,7 +42,15 @@ export default function tailwindcss(): Plugin[] {
|
||||||
function customJsResolver(id: string, base: string) {
|
function customJsResolver(id: string, base: string) {
|
||||||
return jsResolver(id, base, true, isSSR)
|
return jsResolver(id, base, true, isSSR)
|
||||||
}
|
}
|
||||||
return new Root(id, config!.root, customCssResolver, customJsResolver)
|
return new Root(
|
||||||
|
id,
|
||||||
|
config!.root,
|
||||||
|
// Currently, Vite only supports CSS source maps in development and they
|
||||||
|
// are off by default. Check to see if we need them or not.
|
||||||
|
config?.css.devSourcemap ?? false,
|
||||||
|
customCssResolver,
|
||||||
|
customJsResolver,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -68,14 +84,14 @@ export default function tailwindcss(): Plugin[] {
|
||||||
|
|
||||||
let root = roots.get(id)
|
let root = roots.get(id)
|
||||||
|
|
||||||
let generated = await root.generate(src, (file) => this.addWatchFile(file), I)
|
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
|
||||||
if (!generated) {
|
if (!result) {
|
||||||
roots.delete(id)
|
roots.delete(id)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)')
|
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)')
|
||||||
return { code: generated }
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -93,18 +109,21 @@ export default function tailwindcss(): Plugin[] {
|
||||||
|
|
||||||
let root = roots.get(id)
|
let root = roots.get(id)
|
||||||
|
|
||||||
let generated = await root.generate(src, (file) => this.addWatchFile(file), I)
|
let result = await root.generate(src, (file) => this.addWatchFile(file), I)
|
||||||
if (!generated) {
|
if (!result) {
|
||||||
roots.delete(id)
|
roots.delete(id)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)')
|
DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)')
|
||||||
|
|
||||||
DEBUG && I.start('[@tailwindcss/vite] Optimize CSS')
|
DEBUG && I.start('[@tailwindcss/vite] Optimize CSS')
|
||||||
generated = optimize(generated, { minify })
|
result = optimize(result.code, {
|
||||||
|
minify,
|
||||||
|
map: result.map,
|
||||||
|
})
|
||||||
DEBUG && I.end('[@tailwindcss/vite] Optimize CSS')
|
DEBUG && I.end('[@tailwindcss/vite] Optimize CSS')
|
||||||
|
|
||||||
return { code: generated }
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] satisfies Plugin[]
|
] satisfies Plugin[]
|
||||||
|
|
@ -173,6 +192,7 @@ class Root {
|
||||||
private id: string,
|
private id: string,
|
||||||
private base: string,
|
private base: string,
|
||||||
|
|
||||||
|
private enableSourceMaps: boolean,
|
||||||
private customCssResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
private customCssResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
||||||
private customJsResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
private customJsResolver: (id: string, base: string) => Promise<string | false | undefined>,
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -183,7 +203,13 @@ class Root {
|
||||||
content: string,
|
content: string,
|
||||||
_addWatchFile: (file: string) => void,
|
_addWatchFile: (file: string) => void,
|
||||||
I: Instrumentation,
|
I: Instrumentation,
|
||||||
): Promise<string | false> {
|
): Promise<
|
||||||
|
| {
|
||||||
|
code: string
|
||||||
|
map: string | undefined
|
||||||
|
}
|
||||||
|
| false
|
||||||
|
> {
|
||||||
let inputPath = idToPath(this.id)
|
let inputPath = idToPath(this.id)
|
||||||
|
|
||||||
function addWatchFile(file: string) {
|
function addWatchFile(file: string) {
|
||||||
|
|
@ -215,6 +241,7 @@ class Root {
|
||||||
DEBUG && I.start('Setup compiler')
|
DEBUG && I.start('Setup compiler')
|
||||||
let addBuildDependenciesPromises: Promise<void>[] = []
|
let addBuildDependenciesPromises: Promise<void>[] = []
|
||||||
this.compiler = await compile(content, {
|
this.compiler = await compile(content, {
|
||||||
|
from: this.enableSourceMaps ? this.id : undefined,
|
||||||
base: inputBase,
|
base: inputBase,
|
||||||
shouldRewriteUrls: true,
|
shouldRewriteUrls: true,
|
||||||
onDependency: (path) => {
|
onDependency: (path) => {
|
||||||
|
|
@ -313,10 +340,17 @@ class Root {
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG && I.start('Build CSS')
|
DEBUG && I.start('Build CSS')
|
||||||
let result = this.compiler.build([...this.candidates])
|
let code = this.compiler.build([...this.candidates])
|
||||||
DEBUG && I.end('Build CSS')
|
DEBUG && I.end('Build CSS')
|
||||||
|
|
||||||
return result
|
DEBUG && I.start('Build Source Map')
|
||||||
|
let map = this.enableSourceMaps ? toSourceMap(this.compiler.buildSourceMap()).raw : undefined
|
||||||
|
DEBUG && I.end('Build Source Map')
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
map,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addBuildDependency(path: string) {
|
private async addBuildDependency(path: string) {
|
||||||
|
|
|
||||||
|
|
@ -127,9 +127,12 @@
|
||||||
"utilities.css"
|
"utilities.css"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@ampproject/remapping": "^2.3.0",
|
||||||
"@tailwindcss/oxide": "workspace:^",
|
"@tailwindcss/oxide": "workspace:^",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
|
"dedent": "1.5.3",
|
||||||
"lightningcss": "catalog:",
|
"lightningcss": "catalog:",
|
||||||
"dedent": "1.5.3"
|
"magic-string": "^0.30.17",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Features } from '.'
|
||||||
import { rule, toCss, walk, WalkAction, type AstNode } from './ast'
|
import { rule, toCss, walk, WalkAction, type AstNode } from './ast'
|
||||||
import { compileCandidates } from './compile'
|
import { compileCandidates } from './compile'
|
||||||
import type { DesignSystem } from './design-system'
|
import type { DesignSystem } from './design-system'
|
||||||
|
import type { SourceLocation } from './source-maps/source'
|
||||||
import { DefaultMap } from './utils/default-map'
|
import { DefaultMap } from './utils/default-map'
|
||||||
|
|
||||||
export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||||
|
|
@ -155,17 +156,59 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
|
||||||
let node = parent.nodes[i]
|
let node = parent.nodes[i]
|
||||||
if (node.kind !== 'at-rule' || node.name !== '@apply') continue
|
if (node.kind !== 'at-rule' || node.name !== '@apply') continue
|
||||||
|
|
||||||
let candidates = node.params.split(/\s+/g)
|
let parts = node.params.split(/(\s+)/g)
|
||||||
|
let candidateOffsets: Record<string, number> = {}
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
for (let [idx, part] of parts.entries()) {
|
||||||
|
if (idx % 2 === 0) candidateOffsets[part] = offset
|
||||||
|
offset += part.length
|
||||||
|
}
|
||||||
|
|
||||||
// Replace the `@apply` rule with the actual utility classes
|
// Replace the `@apply` rule with the actual utility classes
|
||||||
{
|
{
|
||||||
// Parse the candidates to an AST that we can replace the `@apply` rule
|
// Parse the candidates to an AST that we can replace the `@apply` rule
|
||||||
// with.
|
// with.
|
||||||
let candidateAst = compileCandidates(candidates, designSystem, {
|
let candidates = Object.keys(candidateOffsets)
|
||||||
|
let compiled = compileCandidates(candidates, designSystem, {
|
||||||
onInvalidCandidate: (candidate) => {
|
onInvalidCandidate: (candidate) => {
|
||||||
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
|
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
|
||||||
},
|
},
|
||||||
}).astNodes
|
})
|
||||||
|
|
||||||
|
let src = node.src
|
||||||
|
|
||||||
|
let candidateAst = compiled.astNodes.map((node) => {
|
||||||
|
let candidate = compiled.nodeSorting.get(node)?.candidate
|
||||||
|
let candidateOffset = candidate ? candidateOffsets[candidate] : undefined
|
||||||
|
|
||||||
|
node = structuredClone(node)
|
||||||
|
|
||||||
|
if (!src || !candidate || candidateOffset === undefined) {
|
||||||
|
// While the original nodes may have come from an `@utility` we still
|
||||||
|
// want to replace the source because the `@apply` is ultimately the
|
||||||
|
// reason the node was emitted into the AST.
|
||||||
|
walk([node], (node) => {
|
||||||
|
node.src = src
|
||||||
|
})
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidateSrc: SourceLocation = [src[0], src[1], src[2]]
|
||||||
|
|
||||||
|
candidateSrc[1] += 7 /* '@apply '.length */ + candidateOffset
|
||||||
|
candidateSrc[2] = candidateSrc[1] + candidate.length
|
||||||
|
|
||||||
|
// While the original nodes may have come from an `@utility` we still
|
||||||
|
// want to replace the source because the `@apply` is ultimately the
|
||||||
|
// reason the node was emitted into the AST.
|
||||||
|
walk([node], (node) => {
|
||||||
|
node.src = candidateSrc
|
||||||
|
})
|
||||||
|
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
|
||||||
// Collect the nodes to insert in place of the `@apply` rule. When a rule
|
// Collect the nodes to insert in place of the `@apply` rule. When a rule
|
||||||
// was used, we want to insert its children instead of the rule because we
|
// was used, we want to insert its children instead of the rule because we
|
||||||
|
|
|
||||||
28
packages/tailwindcss/src/ast.bench.ts
Normal file
28
packages/tailwindcss/src/ast.bench.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { bench } from 'vitest'
|
||||||
|
import { toCss } from './ast'
|
||||||
|
import * as CSS from './css-parser'
|
||||||
|
|
||||||
|
const css = String.raw
|
||||||
|
const input = css`
|
||||||
|
@theme {
|
||||||
|
--color-primary: #333;
|
||||||
|
}
|
||||||
|
@tailwind utilities;
|
||||||
|
.foo {
|
||||||
|
color: red;
|
||||||
|
/* comment */
|
||||||
|
&:hover {
|
||||||
|
color: blue;
|
||||||
|
@apply font-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const ast = CSS.parse(input)
|
||||||
|
|
||||||
|
bench('toCss', () => {
|
||||||
|
toCss(ast)
|
||||||
|
})
|
||||||
|
|
||||||
|
bench('toCss with source maps', () => {
|
||||||
|
toCss(ast, true)
|
||||||
|
})
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Polyfills } from '.'
|
import { Polyfills } from '.'
|
||||||
import { parseAtRule } from './css-parser'
|
import { parseAtRule } from './css-parser'
|
||||||
import type { DesignSystem } from './design-system'
|
import type { DesignSystem } from './design-system'
|
||||||
|
import type { Source, SourceLocation } from './source-maps/source'
|
||||||
import { Theme, ThemeOptions } from './theme'
|
import { Theme, ThemeOptions } from './theme'
|
||||||
import { DefaultMap } from './utils/default-map'
|
import { DefaultMap } from './utils/default-map'
|
||||||
import { extractUsedVariables } from './utils/variables'
|
import { extractUsedVariables } from './utils/variables'
|
||||||
|
|
@ -12,6 +13,9 @@ export type StyleRule = {
|
||||||
kind: 'rule'
|
kind: 'rule'
|
||||||
selector: string
|
selector: string
|
||||||
nodes: AstNode[]
|
nodes: AstNode[]
|
||||||
|
|
||||||
|
src?: SourceLocation
|
||||||
|
dst?: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AtRule = {
|
export type AtRule = {
|
||||||
|
|
@ -19,6 +23,9 @@ export type AtRule = {
|
||||||
name: string
|
name: string
|
||||||
params: string
|
params: string
|
||||||
nodes: AstNode[]
|
nodes: AstNode[]
|
||||||
|
|
||||||
|
src?: SourceLocation
|
||||||
|
dst?: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Declaration = {
|
export type Declaration = {
|
||||||
|
|
@ -26,22 +33,34 @@ export type Declaration = {
|
||||||
property: string
|
property: string
|
||||||
value: string | undefined
|
value: string | undefined
|
||||||
important: boolean
|
important: boolean
|
||||||
|
|
||||||
|
src?: SourceLocation
|
||||||
|
dst?: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Comment = {
|
export type Comment = {
|
||||||
kind: 'comment'
|
kind: 'comment'
|
||||||
value: string
|
value: string
|
||||||
|
|
||||||
|
src?: SourceLocation
|
||||||
|
dst?: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Context = {
|
export type Context = {
|
||||||
kind: 'context'
|
kind: 'context'
|
||||||
context: Record<string, string | boolean>
|
context: Record<string, string | boolean>
|
||||||
nodes: AstNode[]
|
nodes: AstNode[]
|
||||||
|
|
||||||
|
src?: undefined
|
||||||
|
dst?: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AtRoot = {
|
export type AtRoot = {
|
||||||
kind: 'at-root'
|
kind: 'at-root'
|
||||||
nodes: AstNode[]
|
nodes: AstNode[]
|
||||||
|
|
||||||
|
src?: undefined
|
||||||
|
dst?: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Rule = StyleRule | AtRule
|
export type Rule = StyleRule | AtRule
|
||||||
|
|
@ -378,10 +397,13 @@ export function optimizeAst(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fallback = decl(property, initialValue ?? 'initial')
|
||||||
|
fallback.src = node.src
|
||||||
|
|
||||||
if (inherits) {
|
if (inherits) {
|
||||||
propertyFallbacksRoot.push(decl(property, initialValue ?? 'initial'))
|
propertyFallbacksRoot.push(fallback)
|
||||||
} else {
|
} else {
|
||||||
propertyFallbacksUniversal.push(decl(property, initialValue ?? 'initial'))
|
propertyFallbacksUniversal.push(fallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,6 +645,7 @@ export function optimizeAst(
|
||||||
value: ValueParser.toCss(ast),
|
value: ValueParser.toCss(ast),
|
||||||
}
|
}
|
||||||
let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [declaration])
|
let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [declaration])
|
||||||
|
colorMixQuery.src = declaration.src
|
||||||
parent.splice(idx, 1, fallback, colorMixQuery)
|
parent.splice(idx, 1, fallback, colorMixQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -632,11 +655,15 @@ export function optimizeAst(
|
||||||
let fallbackAst = []
|
let fallbackAst = []
|
||||||
|
|
||||||
if (propertyFallbacksRoot.length > 0) {
|
if (propertyFallbacksRoot.length > 0) {
|
||||||
fallbackAst.push(rule(':root, :host', propertyFallbacksRoot))
|
let wrapper = rule(':root, :host', propertyFallbacksRoot)
|
||||||
|
wrapper.src = propertyFallbacksRoot[0].src
|
||||||
|
fallbackAst.push(wrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyFallbacksUniversal.length > 0) {
|
if (propertyFallbacksUniversal.length > 0) {
|
||||||
fallbackAst.push(rule('*, ::before, ::after, ::backdrop', propertyFallbacksUniversal))
|
let wrapper = rule('*, ::before, ::after, ::backdrop', propertyFallbacksUniversal)
|
||||||
|
wrapper.src = propertyFallbacksUniversal[0].src
|
||||||
|
fallbackAst.push(wrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fallbackAst.length > 0) {
|
if (fallbackAst.length > 0) {
|
||||||
|
|
@ -658,30 +685,43 @@ export function optimizeAst(
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let layerPropertiesStatement = atRule('@layer', 'properties', [])
|
||||||
|
layerPropertiesStatement.src = fallbackAst[0].src
|
||||||
|
|
||||||
newAst.splice(
|
newAst.splice(
|
||||||
firstValidNodeIndex < 0 ? newAst.length : firstValidNodeIndex,
|
firstValidNodeIndex < 0 ? newAst.length : firstValidNodeIndex,
|
||||||
0,
|
0,
|
||||||
atRule('@layer', 'properties', []),
|
layerPropertiesStatement,
|
||||||
)
|
)
|
||||||
|
|
||||||
newAst.push(
|
let block = rule('@layer properties', [
|
||||||
rule('@layer properties', [
|
atRule(
|
||||||
atRule(
|
'@supports',
|
||||||
'@supports',
|
// We can't write a supports query for `@property` directly so we have to test for
|
||||||
// We can't write a supports query for `@property` directly so we have to test for
|
// features that are added around the same time in Mozilla and Safari.
|
||||||
// features that are added around the same time in Mozilla and Safari.
|
'((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b))))',
|
||||||
'((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b))))',
|
fallbackAst,
|
||||||
fallbackAst,
|
),
|
||||||
),
|
])
|
||||||
]),
|
|
||||||
)
|
block.src = fallbackAst[0].src
|
||||||
|
block.nodes[0].src = fallbackAst[0].src
|
||||||
|
|
||||||
|
newAst.push(block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAst
|
return newAst
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toCss(ast: AstNode[]) {
|
export function toCss(ast: AstNode[], track?: boolean) {
|
||||||
|
let pos = 0
|
||||||
|
|
||||||
|
let source: Source = {
|
||||||
|
file: null,
|
||||||
|
code: '',
|
||||||
|
}
|
||||||
|
|
||||||
function stringify(node: AstNode, depth = 0): string {
|
function stringify(node: AstNode, depth = 0): string {
|
||||||
let css = ''
|
let css = ''
|
||||||
let indent = ' '.repeat(depth)
|
let indent = ' '.repeat(depth)
|
||||||
|
|
@ -689,15 +729,70 @@ export function toCss(ast: AstNode[]) {
|
||||||
// Declaration
|
// Declaration
|
||||||
if (node.kind === 'declaration') {
|
if (node.kind === 'declaration') {
|
||||||
css += `${indent}${node.property}: ${node.value}${node.important ? ' !important' : ''};\n`
|
css += `${indent}${node.property}: ${node.value}${node.important ? ' !important' : ''};\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// node.property
|
||||||
|
let start = pos
|
||||||
|
pos += node.property.length
|
||||||
|
|
||||||
|
// `: `
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
// node.value
|
||||||
|
pos += node.value?.length ?? 0
|
||||||
|
|
||||||
|
// !important
|
||||||
|
if (node.important) {
|
||||||
|
pos += 11
|
||||||
|
}
|
||||||
|
|
||||||
|
let end = pos
|
||||||
|
|
||||||
|
// `;\n`
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
node.dst = [source, start, end]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule
|
// Rule
|
||||||
else if (node.kind === 'rule') {
|
else if (node.kind === 'rule') {
|
||||||
css += `${indent}${node.selector} {\n`
|
css += `${indent}${node.selector} {\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// node.selector
|
||||||
|
let start = pos
|
||||||
|
pos += node.selector.length
|
||||||
|
|
||||||
|
// ` `
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
let end = pos
|
||||||
|
node.dst = [source, start, end]
|
||||||
|
|
||||||
|
// `{\n`
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
|
|
||||||
for (let child of node.nodes) {
|
for (let child of node.nodes) {
|
||||||
css += stringify(child, depth + 1)
|
css += stringify(child, depth + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
css += `${indent}}\n`
|
css += `${indent}}\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// `}\n`
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtRule
|
// AtRule
|
||||||
|
|
@ -710,22 +805,97 @@ export function toCss(ast: AstNode[]) {
|
||||||
// @layer base, components, utilities;
|
// @layer base, components, utilities;
|
||||||
// ```
|
// ```
|
||||||
if (node.nodes.length === 0) {
|
if (node.nodes.length === 0) {
|
||||||
return `${indent}${node.name} ${node.params};\n`
|
let css = `${indent}${node.name} ${node.params};\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// node.name
|
||||||
|
let start = pos
|
||||||
|
pos += node.name.length
|
||||||
|
|
||||||
|
// ` `
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
// node.params
|
||||||
|
pos += node.params.length
|
||||||
|
let end = pos
|
||||||
|
|
||||||
|
// `;\n`
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
node.dst = [source, start, end]
|
||||||
|
}
|
||||||
|
|
||||||
|
return css
|
||||||
}
|
}
|
||||||
|
|
||||||
css += `${indent}${node.name}${node.params ? ` ${node.params} ` : ' '}{\n`
|
css += `${indent}${node.name}${node.params ? ` ${node.params} ` : ' '}{\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// node.name
|
||||||
|
let start = pos
|
||||||
|
pos += node.name.length
|
||||||
|
|
||||||
|
if (node.params) {
|
||||||
|
// ` `
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
// node.params
|
||||||
|
pos += node.params.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// ` `
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
let end = pos
|
||||||
|
node.dst = [source, start, end]
|
||||||
|
|
||||||
|
// `{\n`
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
|
|
||||||
for (let child of node.nodes) {
|
for (let child of node.nodes) {
|
||||||
css += stringify(child, depth + 1)
|
css += stringify(child, depth + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
css += `${indent}}\n`
|
css += `${indent}}\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// `}\n`
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
else if (node.kind === 'comment') {
|
else if (node.kind === 'comment') {
|
||||||
css += `${indent}/*${node.value}*/\n`
|
css += `${indent}/*${node.value}*/\n`
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
// indent
|
||||||
|
pos += indent.length
|
||||||
|
|
||||||
|
// The comment itself. We do this instead of just the inside because
|
||||||
|
// it seems more useful to have the entire comment span tracked.
|
||||||
|
let start = pos
|
||||||
|
pos += 2 + node.value.length + 2
|
||||||
|
let end = pos
|
||||||
|
|
||||||
|
node.dst = [source, start, end]
|
||||||
|
|
||||||
|
// `\n`
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// These should've been handled already by `prepareAstForPrinting` which
|
// These should've been handled already by `optimizeAst` which
|
||||||
// means we can safely ignore them here. We return an empty string
|
// means we can safely ignore them here. We return an empty string
|
||||||
// immediately to signal that something went wrong.
|
// immediately to signal that something went wrong.
|
||||||
else if (node.kind === 'context' || node.kind === 'at-root') {
|
else if (node.kind === 'context' || node.kind === 'at-root') {
|
||||||
|
|
@ -743,12 +913,11 @@ export function toCss(ast: AstNode[]) {
|
||||||
let css = ''
|
let css = ''
|
||||||
|
|
||||||
for (let node of ast) {
|
for (let node of ast) {
|
||||||
let result = stringify(node)
|
css += stringify(node, 0)
|
||||||
if (result !== '') {
|
|
||||||
css += result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source.code = css
|
||||||
|
|
||||||
return css
|
return css
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,21 @@ import { atRule, context, walk, WalkAction, type AstNode } from './ast'
|
||||||
import * as CSS from './css-parser'
|
import * as CSS from './css-parser'
|
||||||
import * as ValueParser from './value-parser'
|
import * as ValueParser from './value-parser'
|
||||||
|
|
||||||
type LoadStylesheet = (id: string, basedir: string) => Promise<{ base: string; content: string }>
|
type LoadStylesheet = (
|
||||||
|
id: string,
|
||||||
|
basedir: string,
|
||||||
|
) => Promise<{
|
||||||
|
path: string
|
||||||
|
base: string
|
||||||
|
content: string
|
||||||
|
}>
|
||||||
|
|
||||||
export async function substituteAtImports(
|
export async function substituteAtImports(
|
||||||
ast: AstNode[],
|
ast: AstNode[],
|
||||||
base: string,
|
base: string,
|
||||||
loadStylesheet: LoadStylesheet,
|
loadStylesheet: LoadStylesheet,
|
||||||
recurseCount = 0,
|
recurseCount = 0,
|
||||||
|
track = false,
|
||||||
) {
|
) {
|
||||||
let features = Features.None
|
let features = Features.None
|
||||||
let promises: Promise<void>[] = []
|
let promises: Promise<void>[] = []
|
||||||
|
|
@ -45,10 +53,11 @@ export async function substituteAtImports(
|
||||||
}
|
}
|
||||||
|
|
||||||
let loaded = await loadStylesheet(uri, base)
|
let loaded = await loadStylesheet(uri, base)
|
||||||
let ast = CSS.parse(loaded.content)
|
let ast = CSS.parse(loaded.content, { from: track ? loaded.path : undefined })
|
||||||
await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1)
|
await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1, track)
|
||||||
|
|
||||||
contextNode.nodes = buildImportNodes(
|
contextNode.nodes = buildImportNodes(
|
||||||
|
node,
|
||||||
[context({ base: loaded.base }, ast)],
|
[context({ base: loaded.base }, ast)],
|
||||||
layer,
|
layer,
|
||||||
media,
|
media,
|
||||||
|
|
@ -140,6 +149,7 @@ export function parseImportParams(params: ValueParser.ValueAstNode[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildImportNodes(
|
function buildImportNodes(
|
||||||
|
importNode: AstNode,
|
||||||
importedAst: AstNode[],
|
importedAst: AstNode[],
|
||||||
layer: string | null,
|
layer: string | null,
|
||||||
media: string | null,
|
media: string | null,
|
||||||
|
|
@ -148,15 +158,21 @@ function buildImportNodes(
|
||||||
let root = importedAst
|
let root = importedAst
|
||||||
|
|
||||||
if (layer !== null) {
|
if (layer !== null) {
|
||||||
root = [atRule('@layer', layer, root)]
|
let node = atRule('@layer', layer, root)
|
||||||
|
node.src = importNode.src
|
||||||
|
root = [node]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media !== null) {
|
if (media !== null) {
|
||||||
root = [atRule('@media', media, root)]
|
let node = atRule('@media', media, root)
|
||||||
|
node.src = importNode.src
|
||||||
|
root = [node]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supports !== null) {
|
if (supports !== null) {
|
||||||
root = [atRule('@supports', supports[0] === '(' ? supports : `(${supports})`, root)]
|
let node = atRule('@supports', supports[0] === '(' ? supports : `(${supports})`, root)
|
||||||
|
node.src = importNode.src
|
||||||
|
root = [node]
|
||||||
}
|
}
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@ export async function applyCompatibilityHooks({
|
||||||
path: string,
|
path: string,
|
||||||
base: string,
|
base: string,
|
||||||
resourceHint: 'plugin' | 'config',
|
resourceHint: 'plugin' | 'config',
|
||||||
) => Promise<{ module: any; base: string }>
|
) => Promise<{
|
||||||
|
path: string
|
||||||
|
base: string
|
||||||
|
module: any
|
||||||
|
}>
|
||||||
sources: { base: string; pattern: string; negated: boolean }[]
|
sources: { base: string; pattern: string; negated: boolean }[]
|
||||||
}) {
|
}) {
|
||||||
let features = Features.None
|
let features = Features.None
|
||||||
|
|
|
||||||
|
|
@ -1156,6 +1156,7 @@ test('utilities must be prefixed', async () => {
|
||||||
|
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => ({
|
loadModule: async (id, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: 'tw' },
|
module: { prefix: 'tw' },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1183,6 +1184,7 @@ test('utilities must be prefixed', async () => {
|
||||||
// Non-prefixed utilities are ignored
|
// Non-prefixed utilities are ignored
|
||||||
compiler = await compile(input, {
|
compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => ({
|
loadModule: async (id, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: 'tw' },
|
module: { prefix: 'tw' },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1202,6 +1204,7 @@ test('utilities used in @apply must be prefixed', async () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => ({
|
loadModule: async (id, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: 'tw' },
|
module: { prefix: 'tw' },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1228,6 +1231,7 @@ test('utilities used in @apply must be prefixed', async () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => ({
|
loadModule: async (id, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: 'tw' },
|
module: { prefix: 'tw' },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1258,6 +1262,7 @@ test('Prefixes configured in CSS take precedence over those defined in JS config
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: 'tw' },
|
module: { prefix: 'tw' },
|
||||||
}
|
}
|
||||||
|
|
@ -1282,6 +1287,7 @@ test('a prefix must be letters only', async () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { prefix: '__' },
|
module: { prefix: '__' },
|
||||||
}
|
}
|
||||||
|
|
@ -1305,6 +1311,7 @@ test('important: `#app`', async () => {
|
||||||
|
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (_, base) => ({
|
loadModule: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { important: '#app' },
|
module: { important: '#app' },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1342,6 +1349,7 @@ test('important: true', async () => {
|
||||||
|
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (_, base) => ({
|
loadModule: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: { important: true },
|
module: { important: true },
|
||||||
}),
|
}),
|
||||||
|
|
@ -1378,6 +1386,7 @@ test('blocklisted candidates are not generated', async () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: {
|
module: {
|
||||||
blocklist: ['bg-white'],
|
blocklist: ['bg-white'],
|
||||||
|
|
@ -1421,6 +1430,7 @@ test('blocklisted candidates cannot be used with `@apply`', async () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: {
|
module: {
|
||||||
blocklist: ['bg-white'],
|
blocklist: ['bg-white'],
|
||||||
|
|
@ -1473,6 +1483,7 @@ test('old theme values are merged with their renamed counterparts in the CSS the
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ theme }) {
|
module: plugin(function ({ theme }) {
|
||||||
didCallPluginFn()
|
didCallPluginFn()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ addBase, theme }) {
|
function ({ addBase, theme }) {
|
||||||
|
|
@ -80,6 +81,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ addUtilities, theme }) {
|
module: plugin(function ({ addUtilities, theme }) {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -116,6 +118,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -164,6 +167,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -211,6 +215,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -265,6 +270,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ addUtilities, theme }) {
|
module: plugin(function ({ addUtilities, theme }) {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -313,6 +319,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ addUtilities, theme }) {
|
function ({ addUtilities, theme }) {
|
||||||
|
|
@ -398,6 +405,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -452,6 +460,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -499,6 +508,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ matchUtilities, theme }) {
|
module: plugin(function ({ matchUtilities, theme }) {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -557,6 +567,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -611,6 +622,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -660,6 +672,7 @@ describe('theme', async () => {
|
||||||
await compile(input, {
|
await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ theme }) {
|
function ({ theme }) {
|
||||||
|
|
@ -695,6 +708,7 @@ describe('theme', async () => {
|
||||||
let { build } = await compile(input, {
|
let { build } = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ matchUtilities, theme }) {
|
module: plugin(function ({ matchUtilities, theme }) {
|
||||||
function utility(name: string, themeKey: string) {
|
function utility(name: string, themeKey: string) {
|
||||||
|
|
@ -942,6 +956,7 @@ describe('theme', async () => {
|
||||||
await compile(input, {
|
await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
({ theme }) => {
|
({ theme }) => {
|
||||||
|
|
@ -984,6 +999,7 @@ describe('theme', async () => {
|
||||||
await compile(input, {
|
await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(({ theme }) => {
|
module: plugin(({ theme }) => {
|
||||||
fn(theme('transitionTimingFunction.DEFAULT'))
|
fn(theme('transitionTimingFunction.DEFAULT'))
|
||||||
|
|
@ -1015,6 +1031,7 @@ describe('theme', async () => {
|
||||||
await compile(input, {
|
await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(({ theme }) => {
|
module: plugin(({ theme }) => {
|
||||||
fn(theme('color.red.100'))
|
fn(theme('color.red.100'))
|
||||||
|
|
@ -1043,6 +1060,7 @@ describe('theme', async () => {
|
||||||
await compile(input, {
|
await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(({ theme }) => {
|
module: plugin(({ theme }) => {
|
||||||
fn(theme('i.do.not.exist'))
|
fn(theme('i.do.not.exist'))
|
||||||
|
|
@ -1069,6 +1087,7 @@ describe('theme', async () => {
|
||||||
let { build } = await compile(input, {
|
let { build } = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(({ addUtilities, matchUtilities }) => {
|
module: plugin(({ addUtilities, matchUtilities }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -1124,6 +1143,7 @@ describe('theme', async () => {
|
||||||
let { build } = await compile(input, {
|
let { build } = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ matchUtilities }) {
|
module: plugin(function ({ matchUtilities }) {
|
||||||
function utility(name: string, themeKey: string) {
|
function utility(name: string, themeKey: string) {
|
||||||
|
|
@ -1342,6 +1362,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -1400,6 +1421,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ matchUtilities, theme }) {
|
module: plugin(function ({ matchUtilities, theme }) {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -1446,6 +1468,7 @@ describe('theme', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(
|
module: plugin(
|
||||||
function ({ matchUtilities, theme }) {
|
function ({ matchUtilities, theme }) {
|
||||||
|
|
@ -1493,6 +1516,7 @@ describe('addBase', () => {
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
if (id === 'inside') {
|
if (id === 'inside') {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ addBase }) {
|
module: plugin(function ({ addBase }) {
|
||||||
addBase({ inside: { color: 'red' } })
|
addBase({ inside: { color: 'red' } })
|
||||||
|
|
@ -1500,6 +1524,7 @@ describe('addBase', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(function ({ addBase }) {
|
module: plugin(function ({ addBase }) {
|
||||||
addBase({ outside: { color: 'red' } })
|
addBase({ outside: { color: 'red' } })
|
||||||
|
|
@ -1508,10 +1533,11 @@ describe('addBase', () => {
|
||||||
},
|
},
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '',
|
||||||
content: css`
|
content: css`
|
||||||
@plugin "inside";
|
@plugin "inside";
|
||||||
`,
|
`,
|
||||||
base: '',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -1533,6 +1559,8 @@ describe('addBase', () => {
|
||||||
|
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin(function ({ addBase }) {
|
module: plugin(function ({ addBase }) {
|
||||||
addBase({
|
addBase({
|
||||||
':root': {
|
':root': {
|
||||||
|
|
@ -1542,7 +1570,6 @@ describe('addBase', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1571,6 +1598,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', '&:hover, &:focus')
|
addVariant('hocus', '&:hover, &:focus')
|
||||||
|
|
@ -1605,6 +1633,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', ['&:hover', '&:focus'])
|
addVariant('hocus', ['&:hover', '&:focus'])
|
||||||
|
|
@ -1640,6 +1669,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
|
|
@ -1677,6 +1707,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
|
|
@ -1728,6 +1759,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant(
|
addVariant(
|
||||||
|
|
@ -1769,6 +1801,7 @@ describe('addVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
|
|
@ -1811,6 +1844,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('potato', (flavor) => `.potato-${flavor} &`)
|
matchVariant('potato', (flavor) => `.potato-${flavor} &`)
|
||||||
|
|
@ -1845,6 +1879,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
|
matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
|
||||||
|
|
@ -1883,6 +1918,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant(
|
matchVariant(
|
||||||
|
|
@ -1929,6 +1965,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('tooltip', (side) => `&${side}`, {
|
matchVariant('tooltip', (side) => `&${side}`, {
|
||||||
|
|
@ -1968,6 +2005,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('alphabet', (side) => `&${side}`, {
|
matchVariant('alphabet', (side) => `&${side}`, {
|
||||||
|
|
@ -2010,6 +2048,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('test', (selector) =>
|
matchVariant('test', (selector) =>
|
||||||
|
|
@ -2042,6 +2081,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2094,6 +2134,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2149,6 +2190,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2221,6 +2263,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2275,6 +2318,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2347,6 +2391,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2415,6 +2460,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
|
||||||
|
|
@ -2495,6 +2541,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('foo', (value) => `.foo${value} &`, {
|
matchVariant('foo', (value) => `.foo${value} &`, {
|
||||||
|
|
@ -2529,6 +2576,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('foo', (value) => `.foo${value} &`)
|
matchVariant('foo', (value) => `.foo${value} &`)
|
||||||
|
|
@ -2553,6 +2601,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
|
matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
|
||||||
|
|
@ -2585,6 +2634,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
|
matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
|
||||||
|
|
@ -2615,6 +2665,7 @@ describe('matchVariant', () => {
|
||||||
{
|
{
|
||||||
loadModule: async (id, base) => {
|
loadModule: async (id, base) => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchVariant }: PluginAPI) => {
|
module: ({ matchVariant }: PluginAPI) => {
|
||||||
matchVariant('my-container', (value, { modifier }) => {
|
matchVariant('my-container', (value, { modifier }) => {
|
||||||
|
|
@ -2669,6 +2720,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -2710,6 +2762,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities([
|
addUtilities([
|
||||||
|
|
@ -2743,6 +2796,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities([
|
addUtilities([
|
||||||
|
|
@ -2782,6 +2836,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities([
|
addUtilities([
|
||||||
|
|
@ -2816,6 +2871,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -2857,6 +2913,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -2913,6 +2970,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -2942,6 +3000,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -2985,6 +3044,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -3026,6 +3086,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -3122,6 +3183,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -3175,6 +3237,7 @@ describe('addUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addUtilities }: PluginAPI) => {
|
module: ({ addUtilities }: PluginAPI) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -3240,6 +3303,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3320,6 +3384,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3361,6 +3426,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3410,6 +3476,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3480,6 +3547,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3554,6 +3622,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3609,6 +3678,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3647,6 +3717,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3691,6 +3762,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3818,6 +3890,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3903,6 +3976,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -3961,6 +4035,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -4028,6 +4103,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities({
|
matchUtilities({
|
||||||
|
|
@ -4056,6 +4132,7 @@ describe('matchUtilities()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(base) {
|
async loadModule(base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchUtilities }: PluginAPI) => {
|
module: ({ matchUtilities }: PluginAPI) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -4122,6 +4199,7 @@ describe('addComponents()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ addComponents }: PluginAPI) => {
|
module: ({ addComponents }: PluginAPI) => {
|
||||||
addComponents({
|
addComponents({
|
||||||
|
|
@ -4190,6 +4268,7 @@ describe('matchComponents()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ matchComponents }: PluginAPI) => {
|
module: ({ matchComponents }: PluginAPI) => {
|
||||||
matchComponents(
|
matchComponents(
|
||||||
|
|
@ -4235,6 +4314,7 @@ describe('prefix()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ prefix }: PluginAPI) => {
|
module: ({ prefix }: PluginAPI) => {
|
||||||
fn(prefix('btn'))
|
fn(prefix('btn'))
|
||||||
|
|
@ -4258,6 +4338,7 @@ describe('config()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ config }: PluginAPI) => {
|
module: ({ config }: PluginAPI) => {
|
||||||
fn(config())
|
fn(config())
|
||||||
|
|
@ -4285,6 +4366,7 @@ describe('config()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ config }: PluginAPI) => {
|
module: ({ config }: PluginAPI) => {
|
||||||
fn(config('theme'))
|
fn(config('theme'))
|
||||||
|
|
@ -4310,6 +4392,7 @@ describe('config()', () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: ({ config }: PluginAPI) => {
|
module: ({ config }: PluginAPI) => {
|
||||||
fn(config('somekey', 'defaultvalue'))
|
fn(config('somekey', 'defaultvalue'))
|
||||||
|
|
|
||||||
|
|
@ -412,8 +412,9 @@ describe('--theme(…)', () => {
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
module: () => {},
|
path: '',
|
||||||
base: '/root',
|
base: '/root',
|
||||||
|
module: () => {},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -771,7 +772,11 @@ describe('theme(…)', () => {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({ module: {}, base: '/root' }),
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
|
module: {},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1196,6 +1201,8 @@ describe('in plugins', () => {
|
||||||
{
|
{
|
||||||
async loadModule() {
|
async loadModule() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin(({ addBase, addUtilities }) => {
|
module: plugin(({ addBase, addUtilities }) => {
|
||||||
addBase({
|
addBase({
|
||||||
'.my-base-rule': {
|
'.my-base-rule': {
|
||||||
|
|
@ -1212,7 +1219,6 @@ describe('in plugins', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1253,6 +1259,8 @@ describe('in JS config files', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: {
|
module: {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|
@ -1279,7 +1287,6 @@ describe('in JS config files', () => {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -1314,6 +1321,7 @@ test('replaces CSS theme() function with values inside imported stylesheets', as
|
||||||
{
|
{
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base: '/bar.css',
|
base: '/bar.css',
|
||||||
content: css`
|
content: css`
|
||||||
.red {
|
.red {
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,7 @@ const cssFile = readFileSync(currentFolder + './preflight.css', 'utf-8')
|
||||||
bench('css-parser on preflight.css', () => {
|
bench('css-parser on preflight.css', () => {
|
||||||
CSS.parse(cssFile)
|
CSS.parse(cssFile)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bench('CSS with sourcemaps', () => {
|
||||||
|
CSS.parse(cssFile, { from: 'input.css' })
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||||
kind: 'comment',
|
kind: 'comment',
|
||||||
value: `!
|
value: `!
|
||||||
* License #2
|
* License #2
|
||||||
`,
|
`.replaceAll(/\r?\n/g, lineEndings === 'Windows' ? '\r\n' : '\n'),
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
@ -368,7 +368,7 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
/* A comment */
|
/* A comment */
|
||||||
content: 'Hello, world!';
|
content: 'Hello, world!';
|
||||||
}`,
|
}`.replaceAll(/\r?\n/g, lineEndings === 'Windows' ? '\r\n' : '\n'),
|
||||||
important: false,
|
important: false,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
@ -396,7 +396,7 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
/* A comment ; */
|
/* A comment ; */
|
||||||
content: 'Hello, world!';
|
content: 'Hello, world!';
|
||||||
}`,
|
}`.replaceAll(/\r?\n/g, lineEndings === 'Windows' ? '\r\n' : '\n'),
|
||||||
important: false,
|
important: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -406,7 +406,7 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
/* A comment } */
|
/* A comment } */
|
||||||
content: 'Hello, world!';
|
content: 'Hello, world!';
|
||||||
}`,
|
}`.replaceAll(/\r?\n/g, lineEndings === 'Windows' ? '\r\n' : '\n'),
|
||||||
important: false,
|
important: false,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
type Declaration,
|
type Declaration,
|
||||||
type Rule,
|
type Rule,
|
||||||
} from './ast'
|
} from './ast'
|
||||||
|
import type { Source } from './source-maps/source'
|
||||||
|
|
||||||
const BACKSLASH = 0x5c
|
const BACKSLASH = 0x5c
|
||||||
const SLASH = 0x2f
|
const SLASH = 0x2f
|
||||||
|
|
@ -18,6 +19,7 @@ const SINGLE_QUOTE = 0x27
|
||||||
const COLON = 0x3a
|
const COLON = 0x3a
|
||||||
const SEMICOLON = 0x3b
|
const SEMICOLON = 0x3b
|
||||||
const LINE_BREAK = 0x0a
|
const LINE_BREAK = 0x0a
|
||||||
|
const CARRIAGE_RETURN = 0xd
|
||||||
const SPACE = 0x20
|
const SPACE = 0x20
|
||||||
const TAB = 0x09
|
const TAB = 0x09
|
||||||
const OPEN_CURLY = 0x7b
|
const OPEN_CURLY = 0x7b
|
||||||
|
|
@ -30,9 +32,17 @@ const DASH = 0x2d
|
||||||
const AT_SIGN = 0x40
|
const AT_SIGN = 0x40
|
||||||
const EXCLAMATION_MARK = 0x21
|
const EXCLAMATION_MARK = 0x21
|
||||||
|
|
||||||
export function parse(input: string) {
|
export interface ParseOptions {
|
||||||
if (input[0] === '\uFEFF') input = input.slice(1)
|
from?: string
|
||||||
input = input.replaceAll('\r\n', '\n')
|
}
|
||||||
|
|
||||||
|
export function parse(input: string, opts?: ParseOptions) {
|
||||||
|
let source: Source | null = opts?.from ? { file: opts.from, code: input } : null
|
||||||
|
|
||||||
|
// Note: it is important that any transformations of the input string
|
||||||
|
// *before* processing do NOT change the length of the string. This
|
||||||
|
// would invalidate the mechanism used to track source locations.
|
||||||
|
if (input[0] === '\uFEFF') input = ' ' + input.slice(1)
|
||||||
|
|
||||||
let ast: AstNode[] = []
|
let ast: AstNode[] = []
|
||||||
let licenseComments: Comment[] = []
|
let licenseComments: Comment[] = []
|
||||||
|
|
@ -45,11 +55,22 @@ export function parse(input: string) {
|
||||||
let buffer = ''
|
let buffer = ''
|
||||||
let closingBracketStack = ''
|
let closingBracketStack = ''
|
||||||
|
|
||||||
|
// The start of the first non-whitespace character in the buffer
|
||||||
|
let bufferStart = 0
|
||||||
|
|
||||||
let peekChar
|
let peekChar
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
let currentChar = input.charCodeAt(i)
|
let currentChar = input.charCodeAt(i)
|
||||||
|
|
||||||
|
// Skip over the CR in CRLF. This allows code below to only check for a line
|
||||||
|
// break even if we're looking at a Windows newline. Peeking the input still
|
||||||
|
// has to check for CRLF but that happens less often.
|
||||||
|
if (currentChar === CARRIAGE_RETURN) {
|
||||||
|
peekChar = input.charCodeAt(i + 1)
|
||||||
|
if (peekChar === LINE_BREAK) continue
|
||||||
|
}
|
||||||
|
|
||||||
// Current character is a `\` therefore the next character is escaped,
|
// Current character is a `\` therefore the next character is escaped,
|
||||||
// consume it together with the next character and continue.
|
// consume it together with the next character and continue.
|
||||||
//
|
//
|
||||||
|
|
@ -61,6 +82,7 @@ export function parse(input: string) {
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
if (currentChar === BACKSLASH) {
|
if (currentChar === BACKSLASH) {
|
||||||
|
if (buffer === '') bufferStart = i
|
||||||
buffer += input.slice(i, i + 2)
|
buffer += input.slice(i, i + 2)
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +126,13 @@ export function parse(input: string) {
|
||||||
// Collect all license comments so that we can hoist them to the top of
|
// Collect all license comments so that we can hoist them to the top of
|
||||||
// the AST.
|
// the AST.
|
||||||
if (commentString.charCodeAt(2) === EXCLAMATION_MARK) {
|
if (commentString.charCodeAt(2) === EXCLAMATION_MARK) {
|
||||||
licenseComments.push(comment(commentString.slice(2, -2)))
|
let node = comment(commentString.slice(2, -2))
|
||||||
|
licenseComments.push(node)
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, start, i + 1]
|
||||||
|
node.dst = [source, start, i + 1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +174,11 @@ export function parse(input: string) {
|
||||||
// ^ Missing "
|
// ^ Missing "
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
else if (peekChar === SEMICOLON && input.charCodeAt(j + 1) === LINE_BREAK) {
|
else if (
|
||||||
|
peekChar === SEMICOLON &&
|
||||||
|
(input.charCodeAt(j + 1) === LINE_BREAK ||
|
||||||
|
(input.charCodeAt(j + 1) === CARRIAGE_RETURN && input.charCodeAt(j + 2) === LINE_BREAK))
|
||||||
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unterminated string: ${input.slice(start, j + 1) + String.fromCharCode(currentChar)}`,
|
`Unterminated string: ${input.slice(start, j + 1) + String.fromCharCode(currentChar)}`,
|
||||||
)
|
)
|
||||||
|
|
@ -162,7 +194,10 @@ export function parse(input: string) {
|
||||||
// ^ Missing "
|
// ^ Missing "
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
else if (peekChar === LINE_BREAK) {
|
else if (
|
||||||
|
peekChar === LINE_BREAK ||
|
||||||
|
(peekChar === CARRIAGE_RETURN && input.charCodeAt(j + 1) === LINE_BREAK)
|
||||||
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unterminated string: ${input.slice(start, j) + String.fromCharCode(currentChar)}`,
|
`Unterminated string: ${input.slice(start, j) + String.fromCharCode(currentChar)}`,
|
||||||
)
|
)
|
||||||
|
|
@ -178,7 +213,12 @@ export function parse(input: string) {
|
||||||
else if (
|
else if (
|
||||||
(currentChar === SPACE || currentChar === LINE_BREAK || currentChar === TAB) &&
|
(currentChar === SPACE || currentChar === LINE_BREAK || currentChar === TAB) &&
|
||||||
(peekChar = input.charCodeAt(i + 1)) &&
|
(peekChar = input.charCodeAt(i + 1)) &&
|
||||||
(peekChar === SPACE || peekChar === LINE_BREAK || peekChar === TAB)
|
(peekChar === SPACE ||
|
||||||
|
peekChar === LINE_BREAK ||
|
||||||
|
peekChar === TAB ||
|
||||||
|
(peekChar === CARRIAGE_RETURN &&
|
||||||
|
(peekChar = input.charCodeAt(i + 2)) &&
|
||||||
|
peekChar == LINE_BREAK))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -289,6 +329,11 @@ export function parse(input: string) {
|
||||||
let declaration = parseDeclaration(buffer, colonIdx)
|
let declaration = parseDeclaration(buffer, colonIdx)
|
||||||
if (!declaration) throw new Error(`Invalid custom property, expected a value`)
|
if (!declaration) throw new Error(`Invalid custom property, expected a value`)
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
declaration.src = [source, start, i]
|
||||||
|
declaration.dst = [source, start, i]
|
||||||
|
}
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.nodes.push(declaration)
|
parent.nodes.push(declaration)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -309,6 +354,11 @@ export function parse(input: string) {
|
||||||
else if (currentChar === SEMICOLON && buffer.charCodeAt(0) === AT_SIGN) {
|
else if (currentChar === SEMICOLON && buffer.charCodeAt(0) === AT_SIGN) {
|
||||||
node = parseAtRule(buffer)
|
node = parseAtRule(buffer)
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, bufferStart, i]
|
||||||
|
node.dst = [source, bufferStart, i]
|
||||||
|
}
|
||||||
|
|
||||||
// At-rule is nested inside of a rule, attach it to the parent.
|
// At-rule is nested inside of a rule, attach it to the parent.
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.nodes.push(node)
|
parent.nodes.push(node)
|
||||||
|
|
@ -345,6 +395,11 @@ export function parse(input: string) {
|
||||||
throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
declaration.src = [source, bufferStart, i]
|
||||||
|
declaration.dst = [source, bufferStart, i]
|
||||||
|
}
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.nodes.push(declaration)
|
parent.nodes.push(declaration)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -364,6 +419,12 @@ export function parse(input: string) {
|
||||||
// At this point `buffer` should resemble a selector or an at-rule.
|
// At this point `buffer` should resemble a selector or an at-rule.
|
||||||
node = rule(buffer.trim())
|
node = rule(buffer.trim())
|
||||||
|
|
||||||
|
// Track the source location for source maps
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, bufferStart, i]
|
||||||
|
node.dst = [source, bufferStart, i]
|
||||||
|
}
|
||||||
|
|
||||||
// Attach the rule to the parent in case it's nested.
|
// Attach the rule to the parent in case it's nested.
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.nodes.push(node)
|
parent.nodes.push(node)
|
||||||
|
|
@ -410,6 +471,12 @@ export function parse(input: string) {
|
||||||
if (buffer.charCodeAt(0) === AT_SIGN) {
|
if (buffer.charCodeAt(0) === AT_SIGN) {
|
||||||
node = parseAtRule(buffer)
|
node = parseAtRule(buffer)
|
||||||
|
|
||||||
|
// Track the source location for source maps
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, bufferStart, i]
|
||||||
|
node.dst = [source, bufferStart, i]
|
||||||
|
}
|
||||||
|
|
||||||
// At-rule is nested inside of a rule, attach it to the parent.
|
// At-rule is nested inside of a rule, attach it to the parent.
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.nodes.push(node)
|
parent.nodes.push(node)
|
||||||
|
|
@ -446,6 +513,11 @@ export function parse(input: string) {
|
||||||
let node = parseDeclaration(buffer, colonIdx)
|
let node = parseDeclaration(buffer, colonIdx)
|
||||||
if (!node) throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
if (!node) throw new Error(`Invalid declaration: \`${buffer.trim()}\``)
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, bufferStart, i]
|
||||||
|
node.dst = [source, bufferStart, i]
|
||||||
|
}
|
||||||
|
|
||||||
parent.nodes.push(node)
|
parent.nodes.push(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -495,6 +567,8 @@ export function parse(input: string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer === '') bufferStart = i
|
||||||
|
|
||||||
buffer += String.fromCharCode(currentChar)
|
buffer += String.fromCharCode(currentChar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,7 +577,15 @@ export function parse(input: string) {
|
||||||
// means that we have an at-rule that is not terminated with a semicolon at
|
// means that we have an at-rule that is not terminated with a semicolon at
|
||||||
// the end of the input.
|
// the end of the input.
|
||||||
if (buffer.charCodeAt(0) === AT_SIGN) {
|
if (buffer.charCodeAt(0) === AT_SIGN) {
|
||||||
ast.push(parseAtRule(buffer))
|
let node = parseAtRule(buffer)
|
||||||
|
|
||||||
|
// Track the source location for source maps
|
||||||
|
if (source) {
|
||||||
|
node.src = [source, bufferStart, input.length]
|
||||||
|
node.dst = [source, bufferStart, input.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.push(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we are done parsing then everything should be balanced. If we still
|
// When we are done parsing then everything should be balanced. If we still
|
||||||
|
|
@ -525,6 +607,9 @@ export function parse(input: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule {
|
export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule {
|
||||||
|
let name = buffer
|
||||||
|
let params = ''
|
||||||
|
|
||||||
// Assumption: The smallest at-rule in CSS right now is `@page`, this means
|
// Assumption: The smallest at-rule in CSS right now is `@page`, this means
|
||||||
// that we can always skip the first 5 characters and start at the
|
// that we can always skip the first 5 characters and start at the
|
||||||
// sixth (at index 5).
|
// sixth (at index 5).
|
||||||
|
|
@ -545,13 +630,13 @@ export function parseAtRule(buffer: string, nodes: AstNode[] = []): AtRule {
|
||||||
for (let i = 5 /* '@page'.length */; i < buffer.length; i++) {
|
for (let i = 5 /* '@page'.length */; i < buffer.length; i++) {
|
||||||
let currentChar = buffer.charCodeAt(i)
|
let currentChar = buffer.charCodeAt(i)
|
||||||
if (currentChar === SPACE || currentChar === OPEN_PAREN) {
|
if (currentChar === SPACE || currentChar === OPEN_PAREN) {
|
||||||
let name = buffer.slice(0, i).trim()
|
name = buffer.slice(0, i)
|
||||||
let params = buffer.slice(i).trim()
|
params = buffer.slice(i)
|
||||||
return atRule(name, params, nodes)
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return atRule(buffer.trim(), '', nodes)
|
return atRule(name.trim(), params.trim(), nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDeclaration(
|
function parseDeclaration(
|
||||||
|
|
|
||||||
|
|
@ -126,11 +126,12 @@ describe('compiling CSS', () => {
|
||||||
{
|
{
|
||||||
async loadStylesheet(id) {
|
async loadStylesheet(id) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '',
|
||||||
content: fs.readFileSync(
|
content: fs.readFileSync(
|
||||||
path.resolve(__dirname, '..', id === 'tailwindcss' ? 'index.css' : id),
|
path.resolve(__dirname, '..', id === 'tailwindcss' ? 'index.css' : id),
|
||||||
'utf-8',
|
'utf-8',
|
||||||
),
|
),
|
||||||
base: '',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -401,6 +402,7 @@ describe('@apply', () => {
|
||||||
{
|
{
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base: '/bar.css',
|
base: '/bar.css',
|
||||||
content: css`
|
content: css`
|
||||||
.foo {
|
.foo {
|
||||||
|
|
@ -2320,6 +2322,8 @@ describe('Parsing theme values from CSS', () => {
|
||||||
{
|
{
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '',
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
--color-tomato: #e10c04;
|
--color-tomato: #e10c04;
|
||||||
|
|
@ -2329,7 +2333,6 @@ describe('Parsing theme values from CSS', () => {
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
`,
|
`,
|
||||||
base: '',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2407,6 +2410,8 @@ describe('Parsing theme values from CSS', () => {
|
||||||
{
|
{
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '',
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
--color-tomato: #e10c04;
|
--color-tomato: #e10c04;
|
||||||
|
|
@ -2416,7 +2421,6 @@ describe('Parsing theme values from CSS', () => {
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
`,
|
`,
|
||||||
base: '',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2704,6 +2708,8 @@ describe('Parsing theme values from CSS', () => {
|
||||||
{
|
{
|
||||||
loadModule: async () => {
|
loadModule: async () => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin(({}) => {}, {
|
module: plugin(({}) => {}, {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|
@ -2714,7 +2720,6 @@ describe('Parsing theme values from CSS', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2750,6 +2755,8 @@ describe('Parsing theme values from CSS', () => {
|
||||||
{
|
{
|
||||||
loadModule: async () => {
|
loadModule: async () => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: {
|
module: {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|
@ -2760,7 +2767,6 @@ describe('Parsing theme values from CSS', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2839,10 +2845,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', '&:hover, &:focus')
|
addVariant('hocus', '&:hover, &:focus')
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -2857,10 +2864,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', '&:hover, &:focus')
|
addVariant('hocus', '&:hover, &:focus')
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -2877,10 +2885,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', '&:hover, &:focus')
|
addVariant('hocus', '&:hover, &:focus')
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -2899,6 +2908,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin.withOptions((options) => {
|
module: plugin.withOptions((options) => {
|
||||||
expect(options).toEqual({
|
expect(options).toEqual({
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
|
@ -2912,7 +2923,6 @@ describe('plugins', () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -2951,6 +2961,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin.withOptions((options) => {
|
module: plugin.withOptions((options) => {
|
||||||
expect(options).toEqual({
|
expect(options).toEqual({
|
||||||
'is-null': null,
|
'is-null': null,
|
||||||
|
|
@ -2971,7 +2983,6 @@ describe('plugins', () => {
|
||||||
|
|
||||||
return () => {}
|
return () => {}
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -2991,6 +3002,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin.withOptions((options) => {
|
module: plugin.withOptions((options) => {
|
||||||
return ({ addUtilities }) => {
|
return ({ addUtilities }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -3000,7 +3013,6 @@ describe('plugins', () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -3029,6 +3041,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: plugin(({ addUtilities }) => {
|
module: plugin(({ addUtilities }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
'.text-primary': {
|
'.text-primary': {
|
||||||
|
|
@ -3036,7 +3050,6 @@ describe('plugins', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -3054,7 +3067,11 @@ describe('plugins', () => {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({ module: plugin(() => {}), base: '/root' }),
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
|
module: plugin(() => {}),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
|
@ -3075,7 +3092,11 @@ describe('plugins', () => {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({ module: plugin(() => {}), base: '/root' }),
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
|
module: plugin(() => {}),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
|
@ -3099,10 +3120,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', '&:hover, &:focus')
|
addVariant('hocus', '&:hover, &:focus')
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -3131,10 +3153,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', ['&:hover', '&:focus'])
|
addVariant('hocus', ['&:hover', '&:focus'])
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -3164,13 +3187,14 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
'&:hover': '@slot',
|
'&:hover': '@slot',
|
||||||
'&:focus': '@slot',
|
'&:focus': '@slot',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -3199,6 +3223,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
'@media (hover: hover)': {
|
'@media (hover: hover)': {
|
||||||
|
|
@ -3207,7 +3233,6 @@ describe('plugins', () => {
|
||||||
'&:focus': '@slot',
|
'&:focus': '@slot',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -3248,6 +3273,8 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('hocus', {
|
addVariant('hocus', {
|
||||||
'&': {
|
'&': {
|
||||||
|
|
@ -3257,7 +3284,6 @@ describe('plugins', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -3286,10 +3312,11 @@ describe('plugins', () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addVariant }: PluginAPI) => {
|
module: ({ addVariant }: PluginAPI) => {
|
||||||
addVariant('dark', '&:is([data-theme=dark] *)')
|
addVariant('dark', '&:is([data-theme=dark] *)')
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -4142,6 +4169,8 @@ test('addBase', async () => {
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({ addBase }: PluginAPI) => {
|
module: ({ addBase }: PluginAPI) => {
|
||||||
addBase({
|
addBase({
|
||||||
body: {
|
body: {
|
||||||
|
|
@ -4149,7 +4178,6 @@ test('addBase', async () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -4181,6 +4209,7 @@ it("should error when `layer(…)` is used, but it's not the first param", async
|
||||||
{
|
{
|
||||||
async loadStylesheet() {
|
async loadStylesheet() {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base: '/bar.css',
|
base: '/bar.css',
|
||||||
content: css`
|
content: css`
|
||||||
.foo {
|
.foo {
|
||||||
|
|
@ -4201,6 +4230,8 @@ describe('`@reference "…" imports`', () => {
|
||||||
let loadStylesheet = async (id: string, base: string) => {
|
let loadStylesheet = async (id: string, base: string) => {
|
||||||
if (id === './foo/baz.css') {
|
if (id === './foo/baz.css') {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root/foo',
|
||||||
content: css`
|
content: css`
|
||||||
.foo {
|
.foo {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
@ -4213,14 +4244,14 @@ describe('`@reference "…" imports`', () => {
|
||||||
}
|
}
|
||||||
@custom-variant hocus (&:hover, &:focus);
|
@custom-variant hocus (&:hover, &:focus);
|
||||||
`,
|
`,
|
||||||
base: '/root/foo',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root/foo',
|
||||||
content: css`
|
content: css`
|
||||||
@import './foo/baz.css';
|
@import './foo/baz.css';
|
||||||
`,
|
`,
|
||||||
base: '/root/foo',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4249,19 +4280,21 @@ describe('`@reference "…" imports`', () => {
|
||||||
let loadStylesheet = async (id: string, base: string) => {
|
let loadStylesheet = async (id: string, base: string) => {
|
||||||
if (id === './foo/baz.css') {
|
if (id === './foo/baz.css') {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root/foo',
|
||||||
content: css`
|
content: css`
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
base: '/root/foo',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root/foo',
|
||||||
content: css`
|
content: css`
|
||||||
@import './foo/baz.css';
|
@import './foo/baz.css';
|
||||||
`,
|
`,
|
||||||
base: '/root/foo',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4311,6 +4344,8 @@ describe('`@reference "…" imports`', () => {
|
||||||
['animate-spin', 'match-utility-initial', 'match-components-initial'],
|
['animate-spin', 'match-utility-initial', 'match-components-initial'],
|
||||||
{
|
{
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
module: ({
|
module: ({
|
||||||
addBase,
|
addBase,
|
||||||
addUtilities,
|
addUtilities,
|
||||||
|
|
@ -4344,7 +4379,6 @@ describe('`@reference "…" imports`', () => {
|
||||||
{ values: { initial: 'initial' } },
|
{ values: { initial: 'initial' } },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
base: '/root',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -4366,22 +4400,26 @@ describe('`@reference "…" imports`', () => {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case './one.css': {
|
case './one.css': {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
content: css`
|
content: css`
|
||||||
@import './two.css' layer(two);
|
@import './two.css' layer(two);
|
||||||
`,
|
`,
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case './two.css': {
|
case './two.css': {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
content: css`
|
content: css`
|
||||||
@import './three.css' layer(three);
|
@import './three.css' layer(three);
|
||||||
`,
|
`,
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case './three.css': {
|
case './three.css': {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root',
|
||||||
content: css`
|
content: css`
|
||||||
.foo {
|
.foo {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
@ -4400,10 +4438,11 @@ describe('`@reference "…" imports`', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
base: '/root',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error('unreachable')
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|
@ -4438,6 +4477,8 @@ describe('`@reference "…" imports`', () => {
|
||||||
test('supports `@import "…" reference` syntax', async () => {
|
test('supports `@import "…" reference` syntax', async () => {
|
||||||
let loadStylesheet = async () => {
|
let loadStylesheet = async () => {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
|
base: '/root/foo',
|
||||||
content: css`
|
content: css`
|
||||||
.foo {
|
.foo {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
@ -4450,7 +4491,6 @@ describe('`@reference "…" imports`', () => {
|
||||||
}
|
}
|
||||||
@custom-variant hocus (&:hover, &:focus);
|
@custom-variant hocus (&:hover, &:focus);
|
||||||
`,
|
`,
|
||||||
base: '/root/foo',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import { applyVariant, compileCandidates } from './compile'
|
||||||
import { substituteFunctions } from './css-functions'
|
import { substituteFunctions } from './css-functions'
|
||||||
import * as CSS from './css-parser'
|
import * as CSS from './css-parser'
|
||||||
import { buildDesignSystem, type DesignSystem } from './design-system'
|
import { buildDesignSystem, type DesignSystem } from './design-system'
|
||||||
|
import { createSourceMap, type DecodedSourceMap } from './source-maps/source-map'
|
||||||
import { Theme, ThemeOptions } from './theme'
|
import { Theme, ThemeOptions } from './theme'
|
||||||
import { createCssUtility } from './utilities'
|
import { createCssUtility } from './utilities'
|
||||||
import { expand } from './utils/brace-expansion'
|
import { expand } from './utils/brace-expansion'
|
||||||
|
|
@ -51,13 +52,25 @@ export const enum Polyfills {
|
||||||
|
|
||||||
type CompileOptions = {
|
type CompileOptions = {
|
||||||
base?: string
|
base?: string
|
||||||
|
from?: string
|
||||||
polyfills?: Polyfills
|
polyfills?: Polyfills
|
||||||
loadModule?: (
|
loadModule?: (
|
||||||
id: string,
|
id: string,
|
||||||
base: string,
|
base: string,
|
||||||
resourceHint: 'plugin' | 'config',
|
resourceHint: 'plugin' | 'config',
|
||||||
) => Promise<{ module: Plugin | Config; base: string }>
|
) => Promise<{
|
||||||
loadStylesheet?: (id: string, base: string) => Promise<{ content: string; base: string }>
|
path: string
|
||||||
|
base: string
|
||||||
|
module: Plugin | Config
|
||||||
|
}>
|
||||||
|
loadStylesheet?: (
|
||||||
|
id: string,
|
||||||
|
base: string,
|
||||||
|
) => Promise<{
|
||||||
|
path: string
|
||||||
|
base: string
|
||||||
|
content: string
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
function throwOnLoadModule(): never {
|
function throwOnLoadModule(): never {
|
||||||
|
|
@ -125,6 +138,7 @@ async function parseCss(
|
||||||
ast: AstNode[],
|
ast: AstNode[],
|
||||||
{
|
{
|
||||||
base = '',
|
base = '',
|
||||||
|
from,
|
||||||
loadModule = throwOnLoadModule,
|
loadModule = throwOnLoadModule,
|
||||||
loadStylesheet = throwOnLoadStylesheet,
|
loadStylesheet = throwOnLoadStylesheet,
|
||||||
}: CompileOptions = {},
|
}: CompileOptions = {},
|
||||||
|
|
@ -132,7 +146,7 @@ async function parseCss(
|
||||||
let features = Features.None
|
let features = Features.None
|
||||||
ast = [contextNode({ base }, ast)] as AstNode[]
|
ast = [contextNode({ base }, ast)] as AstNode[]
|
||||||
|
|
||||||
features |= await substituteAtImports(ast, base, loadStylesheet)
|
features |= await substituteAtImports(ast, base, loadStylesheet, 0, from !== undefined)
|
||||||
|
|
||||||
let important = null as boolean | null
|
let important = null as boolean | null
|
||||||
let theme = new Theme()
|
let theme = new Theme()
|
||||||
|
|
@ -528,7 +542,7 @@ async function parseCss(
|
||||||
|
|
||||||
if (child.kind === 'comment') return
|
if (child.kind === 'comment') return
|
||||||
if (child.kind === 'declaration' && child.property.startsWith('--')) {
|
if (child.kind === 'declaration' && child.property.startsWith('--')) {
|
||||||
theme.add(unescape(child.property), child.value ?? '', themeOptions)
|
theme.add(unescape(child.property), child.value ?? '', themeOptions, child.src)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -546,6 +560,7 @@ async function parseCss(
|
||||||
// theme later, and delete any other `@theme` rules.
|
// theme later, and delete any other `@theme` rules.
|
||||||
if (!firstThemeRule) {
|
if (!firstThemeRule) {
|
||||||
firstThemeRule = styleRule(':root, :host', [])
|
firstThemeRule = styleRule(':root, :host', [])
|
||||||
|
firstThemeRule.src = node.src
|
||||||
replaceWith([firstThemeRule])
|
replaceWith([firstThemeRule])
|
||||||
} else {
|
} else {
|
||||||
replaceWith([])
|
replaceWith([])
|
||||||
|
|
@ -593,8 +608,9 @@ async function parseCss(
|
||||||
|
|
||||||
for (let [key, value] of designSystem.theme.entries()) {
|
for (let [key, value] of designSystem.theme.entries()) {
|
||||||
if (value.options & ThemeOptions.REFERENCE) continue
|
if (value.options & ThemeOptions.REFERENCE) continue
|
||||||
|
let node = decl(escape(key), value.value)
|
||||||
nodes.push(decl(escape(key), value.value))
|
node.src = value.src
|
||||||
|
nodes.push(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyframesRules = designSystem.theme.getKeyframes()
|
let keyframesRules = designSystem.theme.getKeyframes()
|
||||||
|
|
@ -748,6 +764,16 @@ export async function compileAst(
|
||||||
onInvalidCandidate,
|
onInvalidCandidate,
|
||||||
}).astNodes
|
}).astNodes
|
||||||
|
|
||||||
|
if (opts.from) {
|
||||||
|
walk(newNodes, (node) => {
|
||||||
|
// We do this conditionally to preserve source locations from both
|
||||||
|
// `@utility` and `@custom-variant`. Even though generated nodes are
|
||||||
|
// cached this should be fine because `utilitiesNode.src` should not
|
||||||
|
// change without a full rebuild which destroys the cache.
|
||||||
|
node.src ??= utilitiesNode.src
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// If no new ast nodes were generated, then we can return the original
|
// If no new ast nodes were generated, then we can return the original
|
||||||
// CSS. This currently assumes that we only add new ast nodes and never
|
// CSS. This currently assumes that we only add new ast nodes and never
|
||||||
// remove any.
|
// remove any.
|
||||||
|
|
@ -766,6 +792,8 @@ export async function compileAst(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { DecodedSourceMap }
|
||||||
|
|
||||||
export async function compile(
|
export async function compile(
|
||||||
css: string,
|
css: string,
|
||||||
opts: CompileOptions = {},
|
opts: CompileOptions = {},
|
||||||
|
|
@ -774,8 +802,9 @@ export async function compile(
|
||||||
root: Root
|
root: Root
|
||||||
features: Features
|
features: Features
|
||||||
build(candidates: string[]): string
|
build(candidates: string[]): string
|
||||||
|
buildSourceMap(): DecodedSourceMap
|
||||||
}> {
|
}> {
|
||||||
let ast = CSS.parse(css)
|
let ast = CSS.parse(css, { from: opts.from })
|
||||||
let api = await compileAst(ast, opts)
|
let api = await compileAst(ast, opts)
|
||||||
let compiledAst = ast
|
let compiledAst = ast
|
||||||
let compiledCss = css
|
let compiledCss = css
|
||||||
|
|
@ -789,11 +818,17 @@ export async function compile(
|
||||||
return compiledCss
|
return compiledCss
|
||||||
}
|
}
|
||||||
|
|
||||||
compiledCss = toCss(newAst)
|
compiledCss = toCss(newAst, !!opts.from)
|
||||||
compiledAst = newAst
|
compiledAst = newAst
|
||||||
|
|
||||||
return compiledCss
|
return compiledCss
|
||||||
},
|
},
|
||||||
|
|
||||||
|
buildSourceMap() {
|
||||||
|
return createSourceMap({
|
||||||
|
ast: compiledAst,
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,10 +172,12 @@ test('Utilities do not show wrapping selector in intellisense', async () => {
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
base: '',
|
base: '',
|
||||||
module: {
|
module: {
|
||||||
important: '#app',
|
important: '#app',
|
||||||
|
|
@ -208,6 +210,7 @@ test('Utilities, when marked as important, show as important in intellisense', a
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
|
|
@ -239,10 +242,12 @@ test('Static utilities from plugins are listed in hovers and completions', async
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
base: '',
|
base: '',
|
||||||
module: plugin(({ addUtilities }) => {
|
module: plugin(({ addUtilities }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -274,10 +279,12 @@ test('Functional utilities from plugins are listed in hovers and completions', a
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
base: '',
|
base: '',
|
||||||
module: plugin(({ matchUtilities }) => {
|
module: plugin(({ matchUtilities }) => {
|
||||||
matchUtilities(
|
matchUtilities(
|
||||||
|
|
@ -420,10 +427,12 @@ test('Custom at-rule variants do not show up as a value under `group`', async ()
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
loadModule: async () => ({
|
loadModule: async () => ({
|
||||||
|
path: '',
|
||||||
base: '',
|
base: '',
|
||||||
module: plugin(({ addVariant }) => {
|
module: plugin(({ addVariant }) => {
|
||||||
addVariant('variant-3', '@media baz')
|
addVariant('variant-3', '@media baz')
|
||||||
|
|
@ -510,6 +519,7 @@ test('Custom functional @utility', async () => {
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
|
|
@ -587,6 +597,7 @@ test('Theme keys with underscores are suggested with underscores', async () => {
|
||||||
|
|
||||||
let design = await __unstable__loadDesignSystem(input, {
|
let design = await __unstable__loadDesignSystem(input, {
|
||||||
loadStylesheet: async (_, base) => ({
|
loadStylesheet: async (_, base) => ({
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: '@tailwind utilities;',
|
content: '@tailwind utilities;',
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ test('JS theme functions do not use the prefix', async () => {
|
||||||
{
|
{
|
||||||
async loadModule(id, base) {
|
async loadModule(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
module: plugin(({ addUtilities, theme }) => {
|
module: plugin(({ addUtilities, theme }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
|
|
@ -206,6 +207,7 @@ test('a prefix can be configured via @import theme(…)', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
async loadStylesheet(id, base) {
|
async loadStylesheet(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
|
|
@ -250,6 +252,7 @@ test('a prefix can be configured via @import theme(…)', async () => {
|
||||||
compiler = await compile(input, {
|
compiler = await compile(input, {
|
||||||
async loadStylesheet(id, base) {
|
async loadStylesheet(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
|
|
@ -275,6 +278,7 @@ test('a prefix can be configured via @import prefix(…)', async () => {
|
||||||
let compiler = await compile(input, {
|
let compiler = await compile(input, {
|
||||||
async loadStylesheet(id, base) {
|
async loadStylesheet(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
|
|
@ -314,6 +318,7 @@ test('a prefix can be configured via @import prefix(…)', async () => {
|
||||||
compiler = await compile(input, {
|
compiler = await compile(input, {
|
||||||
async loadStylesheet(id, base) {
|
async loadStylesheet(id, base) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base,
|
base,
|
||||||
content: css`
|
content: css`
|
||||||
@theme {
|
@theme {
|
||||||
|
|
|
||||||
13
packages/tailwindcss/src/source-maps/line-table.bench.ts
Normal file
13
packages/tailwindcss/src/source-maps/line-table.bench.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
import { bench } from 'vitest'
|
||||||
|
import { createLineTable } from './line-table'
|
||||||
|
|
||||||
|
const currentFolder = fileURLToPath(new URL('..', import.meta.url))
|
||||||
|
const cssFile = readFileSync(currentFolder + '../preflight.css', 'utf-8')
|
||||||
|
const table = createLineTable(cssFile)
|
||||||
|
|
||||||
|
bench('line table lookups', () => {
|
||||||
|
for (let i = 0; i < cssFile.length; ++i) table.find(i)
|
||||||
|
})
|
||||||
87
packages/tailwindcss/src/source-maps/line-table.test.ts
Normal file
87
packages/tailwindcss/src/source-maps/line-table.test.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import dedent from 'dedent'
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
import { createLineTable } from './line-table'
|
||||||
|
|
||||||
|
const css = dedent
|
||||||
|
|
||||||
|
test('line tables', () => {
|
||||||
|
let text = css`
|
||||||
|
.foo {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
let table = createLineTable(`${text}\n`)
|
||||||
|
|
||||||
|
// Line 1: `.foo {\n`
|
||||||
|
expect(table.find(0)).toEqual({ line: 1, column: 0 })
|
||||||
|
expect(table.find(1)).toEqual({ line: 1, column: 1 })
|
||||||
|
expect(table.find(2)).toEqual({ line: 1, column: 2 })
|
||||||
|
expect(table.find(3)).toEqual({ line: 1, column: 3 })
|
||||||
|
expect(table.find(4)).toEqual({ line: 1, column: 4 })
|
||||||
|
expect(table.find(5)).toEqual({ line: 1, column: 5 })
|
||||||
|
expect(table.find(6)).toEqual({ line: 1, column: 6 })
|
||||||
|
|
||||||
|
// Line 2: ` color: red;\n`
|
||||||
|
expect(table.find(6 + 1)).toEqual({ line: 2, column: 0 })
|
||||||
|
expect(table.find(6 + 2)).toEqual({ line: 2, column: 1 })
|
||||||
|
expect(table.find(6 + 3)).toEqual({ line: 2, column: 2 })
|
||||||
|
expect(table.find(6 + 4)).toEqual({ line: 2, column: 3 })
|
||||||
|
expect(table.find(6 + 5)).toEqual({ line: 2, column: 4 })
|
||||||
|
expect(table.find(6 + 6)).toEqual({ line: 2, column: 5 })
|
||||||
|
expect(table.find(6 + 7)).toEqual({ line: 2, column: 6 })
|
||||||
|
expect(table.find(6 + 8)).toEqual({ line: 2, column: 7 })
|
||||||
|
expect(table.find(6 + 9)).toEqual({ line: 2, column: 8 })
|
||||||
|
expect(table.find(6 + 10)).toEqual({ line: 2, column: 9 })
|
||||||
|
expect(table.find(6 + 11)).toEqual({ line: 2, column: 10 })
|
||||||
|
expect(table.find(6 + 12)).toEqual({ line: 2, column: 11 })
|
||||||
|
expect(table.find(6 + 13)).toEqual({ line: 2, column: 12 })
|
||||||
|
|
||||||
|
// Line 3: `}\n`
|
||||||
|
expect(table.find(20 + 1)).toEqual({ line: 3, column: 0 })
|
||||||
|
expect(table.find(20 + 2)).toEqual({ line: 3, column: 1 })
|
||||||
|
|
||||||
|
// After the new line
|
||||||
|
expect(table.find(22 + 1)).toEqual({ line: 4, column: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('line tables findOffset', () => {
|
||||||
|
let text = css`
|
||||||
|
.foo {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
let table = createLineTable(`${text}\n`)
|
||||||
|
|
||||||
|
// Line 1: `.foo {\n`
|
||||||
|
expect(table.findOffset({ line: 1, column: 0 })).toEqual(0)
|
||||||
|
expect(table.findOffset({ line: 1, column: 1 })).toEqual(1)
|
||||||
|
expect(table.findOffset({ line: 1, column: 2 })).toEqual(2)
|
||||||
|
expect(table.findOffset({ line: 1, column: 3 })).toEqual(3)
|
||||||
|
expect(table.findOffset({ line: 1, column: 4 })).toEqual(4)
|
||||||
|
expect(table.findOffset({ line: 1, column: 5 })).toEqual(5)
|
||||||
|
expect(table.findOffset({ line: 1, column: 6 })).toEqual(6)
|
||||||
|
|
||||||
|
// Line 2: ` color: red;\n`
|
||||||
|
expect(table.findOffset({ line: 2, column: 0 })).toEqual(6 + 1)
|
||||||
|
expect(table.findOffset({ line: 2, column: 1 })).toEqual(6 + 2)
|
||||||
|
expect(table.findOffset({ line: 2, column: 2 })).toEqual(6 + 3)
|
||||||
|
expect(table.findOffset({ line: 2, column: 3 })).toEqual(6 + 4)
|
||||||
|
expect(table.findOffset({ line: 2, column: 4 })).toEqual(6 + 5)
|
||||||
|
expect(table.findOffset({ line: 2, column: 5 })).toEqual(6 + 6)
|
||||||
|
expect(table.findOffset({ line: 2, column: 6 })).toEqual(6 + 7)
|
||||||
|
expect(table.findOffset({ line: 2, column: 7 })).toEqual(6 + 8)
|
||||||
|
expect(table.findOffset({ line: 2, column: 8 })).toEqual(6 + 9)
|
||||||
|
expect(table.findOffset({ line: 2, column: 9 })).toEqual(6 + 10)
|
||||||
|
expect(table.findOffset({ line: 2, column: 10 })).toEqual(6 + 11)
|
||||||
|
expect(table.findOffset({ line: 2, column: 11 })).toEqual(6 + 12)
|
||||||
|
expect(table.findOffset({ line: 2, column: 12 })).toEqual(6 + 13)
|
||||||
|
|
||||||
|
// Line 3: `}\n`
|
||||||
|
expect(table.findOffset({ line: 3, column: 0 })).toEqual(20 + 1)
|
||||||
|
expect(table.findOffset({ line: 3, column: 1 })).toEqual(20 + 2)
|
||||||
|
|
||||||
|
// After the new line
|
||||||
|
expect(table.findOffset({ line: 4, column: 0 })).toEqual(22 + 1)
|
||||||
|
})
|
||||||
100
packages/tailwindcss/src/source-maps/line-table.ts
Normal file
100
packages/tailwindcss/src/source-maps/line-table.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* Line offset tables are the key to generating our source maps. They allow us
|
||||||
|
* to store indexes with our AST nodes and later convert them into positions as
|
||||||
|
* when given the source that the indexes refer to.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LINE_BREAK = 0x0a
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A position in source code
|
||||||
|
*
|
||||||
|
* https://tc39.es/ecma426/#sec-position-record-type
|
||||||
|
*/
|
||||||
|
export interface Position {
|
||||||
|
/** The line number, one-based */
|
||||||
|
line: number
|
||||||
|
|
||||||
|
/** The column/character number, one-based */
|
||||||
|
column: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A table that lets you turn an offset into a line number and column
|
||||||
|
*/
|
||||||
|
export interface LineTable {
|
||||||
|
/**
|
||||||
|
* Find the line/column position in the source code for a given offset
|
||||||
|
*
|
||||||
|
* Searching for a given offset takes O(log N) time where N is the number of
|
||||||
|
* lines of code.
|
||||||
|
*
|
||||||
|
* @param offset The index for which to find the position
|
||||||
|
*/
|
||||||
|
find(offset: number): Position
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the most likely byte offset for given a position
|
||||||
|
*
|
||||||
|
* @param offset The position for which to find the byte offset
|
||||||
|
*/
|
||||||
|
findOffset(pos: Position): number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a lookup table to allow for efficient line/column lookups based on
|
||||||
|
* offsets in the source code.
|
||||||
|
*
|
||||||
|
* Creating this table is an O(N) operation where N is the length of the source
|
||||||
|
*/
|
||||||
|
export function createLineTable(source: string): LineTable {
|
||||||
|
let table: number[] = [0]
|
||||||
|
|
||||||
|
// Compute the offsets for the start of each line
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
if (source.charCodeAt(i) === LINE_BREAK) {
|
||||||
|
table.push(i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function find(offset: number) {
|
||||||
|
// Based on esbuild's binary search for line numbers
|
||||||
|
let line = 0
|
||||||
|
let count = table.length
|
||||||
|
while (count > 0) {
|
||||||
|
// `| 0` improves performance (in V8 at least)
|
||||||
|
let mid = (count | 0) >> 1
|
||||||
|
let i = line + mid
|
||||||
|
if (table[i] <= offset) {
|
||||||
|
line = i + 1
|
||||||
|
count = count - mid - 1
|
||||||
|
} else {
|
||||||
|
count = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line -= 1
|
||||||
|
|
||||||
|
let column = offset - table[line]
|
||||||
|
|
||||||
|
return {
|
||||||
|
line: line + 1,
|
||||||
|
column: column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOffset({ line, column }: Position) {
|
||||||
|
line -= 1
|
||||||
|
line = Math.min(Math.max(line, 0), table.length - 1)
|
||||||
|
|
||||||
|
let offsetA = table[line]
|
||||||
|
let offsetB = table[line + 1] ?? offsetA
|
||||||
|
|
||||||
|
return Math.min(Math.max(offsetA + column, 0), offsetB)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
find,
|
||||||
|
findOffset,
|
||||||
|
}
|
||||||
|
}
|
||||||
2002
packages/tailwindcss/src/source-maps/source-map.test.ts
Normal file
2002
packages/tailwindcss/src/source-maps/source-map.test.ts
Normal file
File diff suppressed because it is too large
Load diff
206
packages/tailwindcss/src/source-maps/source-map.ts
Normal file
206
packages/tailwindcss/src/source-maps/source-map.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { walk, type AstNode } from '../ast'
|
||||||
|
import { DefaultMap } from '../utils/default-map'
|
||||||
|
import { createLineTable, type LineTable, type Position } from './line-table'
|
||||||
|
import type { Source } from './source'
|
||||||
|
|
||||||
|
// https://tc39.es/ecma426/#sec-original-position-record-type
|
||||||
|
export interface OriginalPosition extends Position {
|
||||||
|
source: DecodedSource
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "decoded" sourcemap
|
||||||
|
*
|
||||||
|
* @see https://tc39.es/ecma426/#decoded-source-map-record
|
||||||
|
*/
|
||||||
|
export interface DecodedSourceMap {
|
||||||
|
file: string | null
|
||||||
|
sources: DecodedSource[]
|
||||||
|
mappings: DecodedMapping[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "decoded" source
|
||||||
|
*
|
||||||
|
* @see https://tc39.es/ecma426/#decoded-source-record
|
||||||
|
*/
|
||||||
|
export interface DecodedSource {
|
||||||
|
url: string | null
|
||||||
|
content: string | null
|
||||||
|
ignore: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "decoded" mapping
|
||||||
|
*
|
||||||
|
* @see https://tc39.es/ecma426/#decoded-mapping-record
|
||||||
|
*/
|
||||||
|
export interface DecodedMapping {
|
||||||
|
// https://tc39.es/ecma426/#sec-original-position-record-type
|
||||||
|
originalPosition: OriginalPosition | null
|
||||||
|
|
||||||
|
// https://tc39.es/ecma426/#sec-position-record-type
|
||||||
|
generatedPosition: Position
|
||||||
|
|
||||||
|
name: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a source map from the given AST.
|
||||||
|
*
|
||||||
|
* Our AST is build from flat CSS strings but there are many because we handle
|
||||||
|
* `@import`. This means that different nodes can have a different source.
|
||||||
|
*
|
||||||
|
* Instead of taking an input source map, we take the input CSS string we were
|
||||||
|
* originally given, as well as the source text for any imported files, and
|
||||||
|
* use that to generate a source map.
|
||||||
|
*
|
||||||
|
* We then require the use of other tools that can translate one or more
|
||||||
|
* "input" source maps into a final output source map. For example,
|
||||||
|
* `@ampproject/remapping` can be used to handle this.
|
||||||
|
*
|
||||||
|
* This also ensures that tools that expect "local" source maps are able to
|
||||||
|
* consume the source map we generate.
|
||||||
|
*
|
||||||
|
* The source map type we generate may be a bit different from "raw" source maps
|
||||||
|
* that the `source-map-js` package uses. It's a "decoded" source map that is
|
||||||
|
* represented by an object graph. It's identical to "decoded" source map from
|
||||||
|
* the ECMA-426 spec for source maps.
|
||||||
|
*
|
||||||
|
* Note that the spec itself is still evolving which means our implementation
|
||||||
|
* may need to evolve to match it.
|
||||||
|
*
|
||||||
|
* This can easily be converted to a "raw" source map by any tool that needs to.
|
||||||
|
**/
|
||||||
|
export function createSourceMap({ ast }: { ast: AstNode[] }) {
|
||||||
|
// Compute line tables for both the original and generated source lazily so we
|
||||||
|
// don't have to do it during parsing or printing.
|
||||||
|
let lineTables = new DefaultMap<Source, LineTable>((src) => createLineTable(src.code))
|
||||||
|
let sourceTable = new DefaultMap<Source, DecodedSource>((src) => ({
|
||||||
|
url: src.file,
|
||||||
|
content: src.code,
|
||||||
|
ignore: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Convert each mapping to a set of positions
|
||||||
|
let map: DecodedSourceMap = {
|
||||||
|
file: null,
|
||||||
|
sources: [],
|
||||||
|
mappings: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the indexes from the mappings
|
||||||
|
walk(ast, (node: AstNode) => {
|
||||||
|
if (!node.src || !node.dst) return
|
||||||
|
|
||||||
|
let originalSource = sourceTable.get(node.src[0])
|
||||||
|
if (!originalSource.content) return
|
||||||
|
|
||||||
|
let originalTable = lineTables.get(node.src[0])
|
||||||
|
let generatedTable = lineTables.get(node.dst[0])
|
||||||
|
|
||||||
|
let originalSlice = originalSource.content.slice(node.src[1], node.src[2])
|
||||||
|
|
||||||
|
// Source maps only encode single locations — not multi-line ranges
|
||||||
|
// So to properly emulate this we'll scan the original text for multiple
|
||||||
|
// lines and create mappings for each of those lines that point to the
|
||||||
|
// destination node (whether it spans multiple lines or not)
|
||||||
|
//
|
||||||
|
// This is not 100% accurate if both the source and destination preserve
|
||||||
|
// their newlines but this only happens in the case of custom properties
|
||||||
|
//
|
||||||
|
// This is _good enough_
|
||||||
|
let offset = 0
|
||||||
|
for (let line of originalSlice.split('\n')) {
|
||||||
|
if (line.trim() !== '') {
|
||||||
|
let originalStart = originalTable.find(node.src[1] + offset)
|
||||||
|
let generatedStart = generatedTable.find(node.dst[1])
|
||||||
|
|
||||||
|
map.mappings.push({
|
||||||
|
name: null,
|
||||||
|
originalPosition: {
|
||||||
|
source: originalSource,
|
||||||
|
...originalStart,
|
||||||
|
},
|
||||||
|
generatedPosition: generatedStart,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += line.length
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let originalEnd = originalTable.find(node.src[2])
|
||||||
|
let generatedEnd = generatedTable.find(node.dst[2])
|
||||||
|
|
||||||
|
map.mappings.push({
|
||||||
|
name: null,
|
||||||
|
originalPosition: {
|
||||||
|
source: originalSource,
|
||||||
|
...originalEnd,
|
||||||
|
},
|
||||||
|
generatedPosition: generatedEnd,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Populate
|
||||||
|
for (let source of lineTables.keys()) {
|
||||||
|
map.sources.push(sourceTable.get(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the mappings in ascending order
|
||||||
|
map.mappings.sort((a, b) => {
|
||||||
|
let aOriginal = a.originalPosition!
|
||||||
|
let aGenerated = a.generatedPosition!
|
||||||
|
let bOriginal = b.originalPosition!
|
||||||
|
let bGenerated = b.generatedPosition!
|
||||||
|
|
||||||
|
let aSource = map.sources.indexOf(aOriginal.source)
|
||||||
|
let bSource = map.sources.indexOf(bOriginal.source)
|
||||||
|
|
||||||
|
return (
|
||||||
|
aGenerated.line - bGenerated.line ||
|
||||||
|
aGenerated.column - bGenerated.column ||
|
||||||
|
aSource - bSource ||
|
||||||
|
aOriginal.line - bOriginal.line ||
|
||||||
|
aOriginal.column - bGenerated.column
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTranslationMap({
|
||||||
|
original,
|
||||||
|
generated,
|
||||||
|
}: {
|
||||||
|
original: string
|
||||||
|
generated: string
|
||||||
|
}) {
|
||||||
|
// Compute line tables for both the original and generated source lazily so we
|
||||||
|
// don't have to do it during parsing or printing.
|
||||||
|
let originalTable = createLineTable(original)
|
||||||
|
let generatedTable = createLineTable(generated)
|
||||||
|
|
||||||
|
type Translation = [
|
||||||
|
originalStart: Position,
|
||||||
|
originalEnd: Position,
|
||||||
|
generatedStart: Position | null,
|
||||||
|
generatedEnd: Position | null,
|
||||||
|
]
|
||||||
|
|
||||||
|
return (node: AstNode) => {
|
||||||
|
if (!node.src) return []
|
||||||
|
|
||||||
|
let translations: Translation[] = []
|
||||||
|
|
||||||
|
translations.push([
|
||||||
|
originalTable.find(node.src[1]),
|
||||||
|
originalTable.find(node.src[2]),
|
||||||
|
node.dst ? generatedTable.find(node.dst[1]) : null,
|
||||||
|
node.dst ? generatedTable.find(node.dst[2]) : null,
|
||||||
|
])
|
||||||
|
|
||||||
|
return translations
|
||||||
|
}
|
||||||
|
}
|
||||||
27
packages/tailwindcss/src/source-maps/source.ts
Normal file
27
packages/tailwindcss/src/source-maps/source.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* The source code for one or more nodes in the AST
|
||||||
|
*
|
||||||
|
* This generally corresponds to a stylesheet
|
||||||
|
*/
|
||||||
|
export interface Source {
|
||||||
|
/**
|
||||||
|
* The path to the file that contains the referenced source code
|
||||||
|
*
|
||||||
|
* If this references the *output* source code, this is `null`.
|
||||||
|
*/
|
||||||
|
file: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The referenced source code
|
||||||
|
*/
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file and offsets within it that this node covers
|
||||||
|
*
|
||||||
|
* This can represent either:
|
||||||
|
* - A location in the original CSS which caused this node to be created
|
||||||
|
* - A location in the output CSS where this node resides
|
||||||
|
*/
|
||||||
|
export type SourceLocation = [source: Source, start: number, end: number]
|
||||||
120
packages/tailwindcss/src/source-maps/visualizer.ts
Normal file
120
packages/tailwindcss/src/source-maps/visualizer.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { DefaultMap } from '../utils/default-map'
|
||||||
|
import type { DecodedMapping, DecodedSource, DecodedSourceMap } from './source-map'
|
||||||
|
|
||||||
|
interface RangeMapping {
|
||||||
|
id: number
|
||||||
|
source: DecodedSource
|
||||||
|
line: number
|
||||||
|
start: number
|
||||||
|
end: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function visualize(generated: string, map: DecodedSourceMap) {
|
||||||
|
let outputSource: DecodedSource = {
|
||||||
|
url: 'output.css',
|
||||||
|
content: generated,
|
||||||
|
ignore: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group mappings by source file
|
||||||
|
let bySource = new DefaultMap<DecodedSource, DefaultMap<number, DecodedMapping[]>>(
|
||||||
|
() => new DefaultMap<number, DecodedMapping[]>(() => []),
|
||||||
|
)
|
||||||
|
|
||||||
|
let mappingIds = new Map<DecodedMapping, number>()
|
||||||
|
|
||||||
|
let nextId = 1
|
||||||
|
for (let mapping of map.mappings) {
|
||||||
|
let pos = mapping.originalPosition
|
||||||
|
if (!pos) continue
|
||||||
|
let source = pos.source
|
||||||
|
if (!source) continue
|
||||||
|
bySource.get(source).get(pos.line).push(mapping)
|
||||||
|
mappingIds.set(mapping, nextId++)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let mapping of map.mappings) {
|
||||||
|
let pos = mapping.generatedPosition
|
||||||
|
if (!pos) continue
|
||||||
|
bySource.get(outputSource).get(pos.line).push(mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxIdSize = Math.ceil(Math.log10(Math.max(...mappingIds.values())))
|
||||||
|
// `#number `
|
||||||
|
let gutterSize = 3 + maxIdSize
|
||||||
|
|
||||||
|
let output = ''
|
||||||
|
output += '\n'
|
||||||
|
output += 'SOURCES\n'
|
||||||
|
|
||||||
|
for (let source of bySource.keys()) {
|
||||||
|
if (source === outputSource) continue
|
||||||
|
output += '- '
|
||||||
|
output += source.url ?? 'unknown'
|
||||||
|
output += '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
output += '\n'
|
||||||
|
output += 'VISUALIZATION\n'
|
||||||
|
|
||||||
|
for (let [source, byLine] of bySource) {
|
||||||
|
if (!source.content) continue
|
||||||
|
|
||||||
|
output += ' '.repeat(gutterSize)
|
||||||
|
output += `/* `
|
||||||
|
if (source === outputSource) {
|
||||||
|
output += `output`
|
||||||
|
} else {
|
||||||
|
output += `input: `
|
||||||
|
output += source.url ?? 'unknown'
|
||||||
|
}
|
||||||
|
output += ` */\n`
|
||||||
|
|
||||||
|
let lines = source.content.split('\n').entries()
|
||||||
|
for (let [lineNum, line] of lines) {
|
||||||
|
output += ' '.repeat(gutterSize)
|
||||||
|
output += line
|
||||||
|
output += '\n'
|
||||||
|
|
||||||
|
let pairs: DecodedMapping[][] = []
|
||||||
|
|
||||||
|
// Get all mappings for this line
|
||||||
|
let lineMappings = byLine.get(lineNum + 1)
|
||||||
|
|
||||||
|
// Group consecutive mappings into pairs
|
||||||
|
for (let i = 0; i < lineMappings.length; i += 2) {
|
||||||
|
let pair = [lineMappings[i]]
|
||||||
|
if (i + 1 < lineMappings.length) {
|
||||||
|
pair.push(lineMappings[i + 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.push(pair)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [start, end] of pairs) {
|
||||||
|
let id = mappingIds.get(start)
|
||||||
|
if (!id) continue
|
||||||
|
|
||||||
|
let startPos = source === outputSource ? start.generatedPosition : start.originalPosition
|
||||||
|
if (!startPos) continue
|
||||||
|
|
||||||
|
let endPos = source === outputSource ? end?.generatedPosition : end?.originalPosition
|
||||||
|
|
||||||
|
output += '#'
|
||||||
|
output += `${Math.floor((id + 1) / 2)}`.padEnd(maxIdSize, ' ')
|
||||||
|
output += ' '
|
||||||
|
output += ' '.repeat(startPos.column)
|
||||||
|
|
||||||
|
if (endPos) {
|
||||||
|
output += '-'.repeat(Math.max(0, endPos.column - startPos.column))
|
||||||
|
} else {
|
||||||
|
output += '^'
|
||||||
|
}
|
||||||
|
|
||||||
|
output += '\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
@ -7,12 +7,14 @@ export async function compileCss(
|
||||||
options: Parameters<typeof compile>[1] = {},
|
options: Parameters<typeof compile>[1] = {},
|
||||||
) {
|
) {
|
||||||
let { build } = await compile(css, options)
|
let { build } = await compile(css, options)
|
||||||
return optimize(build(candidates)).trim()
|
return optimize(build(candidates)).code.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run(candidates: string[]) {
|
export async function run(candidates: string[]) {
|
||||||
let { build } = await compile('@tailwind utilities;')
|
let { build } = await compile('@tailwind utilities;')
|
||||||
return optimize(build(candidates)).trim()
|
return optimize(build(candidates)).code.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const optimizeCss = optimize
|
export function optimizeCss(input: string) {
|
||||||
|
return optimize(input).code
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { type AtRule } from './ast'
|
import { type AtRule, type Declaration } from './ast'
|
||||||
import { escape, unescape } from './utils/escape'
|
import { escape, unescape } from './utils/escape'
|
||||||
|
|
||||||
export const enum ThemeOptions {
|
export const enum ThemeOptions {
|
||||||
|
|
@ -40,11 +40,18 @@ export class Theme {
|
||||||
public prefix: string | null = null
|
public prefix: string | null = null
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private values = new Map<string, { value: string; options: ThemeOptions }>(),
|
private values = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
value: string
|
||||||
|
options: ThemeOptions
|
||||||
|
src: Declaration['src']
|
||||||
|
}
|
||||||
|
>(),
|
||||||
private keyframes = new Set<AtRule>([]),
|
private keyframes = new Set<AtRule>([]),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
add(key: string, value: string, options = ThemeOptions.NONE): void {
|
add(key: string, value: string, options = ThemeOptions.NONE, src?: Declaration['src']): void {
|
||||||
if (key.endsWith('-*')) {
|
if (key.endsWith('-*')) {
|
||||||
if (value !== 'initial') {
|
if (value !== 'initial') {
|
||||||
throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``)
|
throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``)
|
||||||
|
|
@ -68,7 +75,7 @@ export class Theme {
|
||||||
if (value === 'initial') {
|
if (value === 'initial') {
|
||||||
this.values.delete(key)
|
this.values.delete(key)
|
||||||
} else {
|
} else {
|
||||||
this.values.set(key, { value, options })
|
this.values.set(key, { value, options, src })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2526,8 +2526,9 @@ test('matchVariant sorts deterministically', async () => {
|
||||||
|
|
||||||
for (let classList of classLists) {
|
for (let classList of classLists) {
|
||||||
let output = await compileCss('@tailwind utilities; @plugin "./plugin.js";', classList, {
|
let output = await compileCss('@tailwind utilities; @plugin "./plugin.js";', classList, {
|
||||||
loadModule(id: string) {
|
async loadModule(id: string) {
|
||||||
return {
|
return {
|
||||||
|
path: '',
|
||||||
base: '/',
|
base: '/',
|
||||||
module: createPlugin(({ matchVariant }) => {
|
module: createPlugin(({ matchVariant }) => {
|
||||||
matchVariant('is-data', (value) => `&:is([data-${value}])`, {
|
matchVariant('is-data', (value) => `&:is([data-${value}])`, {
|
||||||
|
|
|
||||||
|
|
@ -2216,7 +2216,7 @@ async function render(page: Page, content: string, extraCss: string = '') {
|
||||||
let scanner = new Scanner({})
|
let scanner = new Scanner({})
|
||||||
let candidates = scanner.scanFiles([{ content, extension: 'html' }])
|
let candidates = scanner.scanFiles([{ content, extension: 'html' }])
|
||||||
|
|
||||||
let styles = optimize(build(candidates))
|
let { code: styles } = optimize(build(candidates))
|
||||||
|
|
||||||
content = `<style type="text/css">${styles}</style>${content}`
|
content = `<style type="text/css">${styles}</style>${content}`
|
||||||
await page.setContent(content)
|
await page.setContent(content)
|
||||||
|
|
|
||||||
66
pnpm-lock.yaml
generated
66
pnpm-lock.yaml
generated
|
|
@ -188,6 +188,9 @@ importers:
|
||||||
fast-glob:
|
fast-glob:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
source-map-js:
|
||||||
|
specifier: ^1.2.1
|
||||||
|
version: 1.2.1
|
||||||
|
|
||||||
packages/@tailwindcss-browser:
|
packages/@tailwindcss-browser:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
|
@ -227,6 +230,9 @@ importers:
|
||||||
|
|
||||||
packages/@tailwindcss-node:
|
packages/@tailwindcss-node:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ampproject/remapping':
|
||||||
|
specifier: ^2.3.0
|
||||||
|
version: 2.3.0
|
||||||
enhanced-resolve:
|
enhanced-resolve:
|
||||||
specifier: ^5.18.1
|
specifier: ^5.18.1
|
||||||
version: 5.18.1
|
version: 5.18.1
|
||||||
|
|
@ -236,6 +242,12 @@ importers:
|
||||||
lightningcss:
|
lightningcss:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm)
|
version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm)
|
||||||
|
magic-string:
|
||||||
|
specifier: ^0.30.17
|
||||||
|
version: 0.30.17
|
||||||
|
source-map-js:
|
||||||
|
specifier: ^1.2.1
|
||||||
|
version: 1.2.1
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../tailwindcss
|
version: link:../tailwindcss
|
||||||
|
|
@ -437,6 +449,9 @@ importers:
|
||||||
|
|
||||||
packages/tailwindcss:
|
packages/tailwindcss:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@ampproject/remapping':
|
||||||
|
specifier: ^2.3.0
|
||||||
|
version: 2.3.0
|
||||||
'@tailwindcss/oxide':
|
'@tailwindcss/oxide':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../crates/node
|
version: link:../../crates/node
|
||||||
|
|
@ -449,6 +464,12 @@ importers:
|
||||||
lightningcss:
|
lightningcss:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm)
|
version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm)
|
||||||
|
magic-string:
|
||||||
|
specifier: ^0.30.17
|
||||||
|
version: 0.30.17
|
||||||
|
source-map-js:
|
||||||
|
specifier: ^1.2.1
|
||||||
|
version: 1.2.1
|
||||||
|
|
||||||
playgrounds/nextjs:
|
playgrounds/nextjs:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -2068,7 +2089,6 @@ packages:
|
||||||
'@parcel/watcher-darwin-arm64@2.5.1':
|
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@parcel/watcher-darwin-x64@2.5.0':
|
'@parcel/watcher-darwin-x64@2.5.0':
|
||||||
|
|
@ -2080,7 +2100,6 @@ packages:
|
||||||
'@parcel/watcher-darwin-x64@2.5.1':
|
'@parcel/watcher-darwin-x64@2.5.1':
|
||||||
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@parcel/watcher-freebsd-x64@2.5.0':
|
'@parcel/watcher-freebsd-x64@2.5.0':
|
||||||
|
|
@ -2128,7 +2147,6 @@ packages:
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
||||||
|
|
@ -2140,7 +2158,6 @@ packages:
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
||||||
|
|
@ -2152,7 +2169,6 @@ packages:
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.0':
|
'@parcel/watcher-linux-x64-musl@2.5.0':
|
||||||
|
|
@ -2164,7 +2180,6 @@ packages:
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@parcel/watcher-wasm@2.5.0':
|
'@parcel/watcher-wasm@2.5.0':
|
||||||
|
|
@ -2206,7 +2221,6 @@ packages:
|
||||||
'@parcel/watcher-win32-x64@2.5.1':
|
'@parcel/watcher-win32-x64@2.5.1':
|
||||||
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@parcel/watcher@2.5.0':
|
'@parcel/watcher@2.5.0':
|
||||||
|
|
@ -2621,7 +2635,6 @@ packages:
|
||||||
|
|
||||||
bun@1.2.11:
|
bun@1.2.11:
|
||||||
resolution: {integrity: sha512-9brVfsp6/TYVsE3lCl1MUxoyKhvljqyL1MNPErgwsOaS9g4Gzi2nY+W5WtRAXGzLrgz5jzsoGHHwyH/rTeRCIg==}
|
resolution: {integrity: sha512-9brVfsp6/TYVsE3lCl1MUxoyKhvljqyL1MNPErgwsOaS9g4Gzi2nY+W5WtRAXGzLrgz5jzsoGHHwyH/rTeRCIg==}
|
||||||
cpu: [arm64, x64, aarch64]
|
|
||||||
os: [darwin, linux, win32]
|
os: [darwin, linux, win32]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
|
@ -3480,13 +3493,11 @@ packages:
|
||||||
lightningcss-darwin-arm64@1.29.2:
|
lightningcss-darwin-arm64@1.29.2:
|
||||||
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
|
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
lightningcss-darwin-x64@1.29.2:
|
lightningcss-darwin-x64@1.29.2:
|
||||||
resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
|
resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
lightningcss-freebsd-x64@1.29.2:
|
lightningcss-freebsd-x64@1.29.2:
|
||||||
|
|
@ -3504,25 +3515,21 @@ packages:
|
||||||
lightningcss-linux-arm64-gnu@1.29.2:
|
lightningcss-linux-arm64-gnu@1.29.2:
|
||||||
resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
|
resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.29.2:
|
lightningcss-linux-arm64-musl@1.29.2:
|
||||||
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
|
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.29.2:
|
lightningcss-linux-x64-gnu@1.29.2:
|
||||||
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
|
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.29.2:
|
lightningcss-linux-x64-musl@1.29.2:
|
||||||
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
|
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.29.2:
|
lightningcss-win32-arm64-msvc@1.29.2:
|
||||||
|
|
@ -3534,7 +3541,6 @@ packages:
|
||||||
lightningcss-win32-x64-msvc@1.29.2:
|
lightningcss-win32-x64-msvc@1.29.2:
|
||||||
resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
|
resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
lightningcss@1.29.2:
|
lightningcss@1.29.2:
|
||||||
|
|
@ -3592,8 +3598,8 @@ packages:
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
magic-string@0.30.11:
|
magic-string@0.30.17:
|
||||||
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
|
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||||
|
|
||||||
merge-stream@2.0.0:
|
merge-stream@2.0.0:
|
||||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
|
|
@ -4113,10 +4119,6 @@ packages:
|
||||||
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
|
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
source-map-js@1.2.0:
|
|
||||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -6156,7 +6158,7 @@ snapshots:
|
||||||
'@vitest/snapshot@2.0.5':
|
'@vitest/snapshot@2.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 2.0.5
|
'@vitest/pretty-format': 2.0.5
|
||||||
magic-string: 0.30.11
|
magic-string: 0.30.17
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|
||||||
'@vitest/spy@2.0.5':
|
'@vitest/spy@2.0.5':
|
||||||
|
|
@ -6817,7 +6819,7 @@ snapshots:
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
enhanced-resolve: 5.18.1
|
enhanced-resolve: 5.18.1
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
get-tsconfig: 4.8.1
|
get-tsconfig: 4.8.1
|
||||||
is-bun-module: 1.2.1
|
is-bun-module: 1.2.1
|
||||||
|
|
@ -6836,7 +6838,7 @@ snapshots:
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
enhanced-resolve: 5.18.1
|
enhanced-resolve: 5.18.1
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
get-tsconfig: 4.8.1
|
get-tsconfig: 4.8.1
|
||||||
is-bun-module: 1.2.1
|
is-bun-module: 1.2.1
|
||||||
|
|
@ -6849,7 +6851,7 @@ snapshots:
|
||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2)):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -6860,7 +6862,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2)):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -6882,7 +6884,7 @@ snapshots:
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.15.1
|
is-core-module: 2.15.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
@ -6911,7 +6913,7 @@ snapshots:
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.25.1(jiti@2.4.2)
|
eslint: 9.25.1(jiti@2.4.2)
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2))
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.15.1
|
is-core-module: 2.15.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
@ -7560,7 +7562,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
magic-string@0.30.11:
|
magic-string@0.30.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
|
@ -7865,7 +7867,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.7
|
nanoid: 3.3.7
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.0
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
postcss@8.4.47:
|
postcss@8.4.47:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -8087,8 +8089,6 @@ snapshots:
|
||||||
|
|
||||||
slash@5.1.0: {}
|
slash@5.1.0: {}
|
||||||
|
|
||||||
source-map-js@1.2.0: {}
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
|
|
@ -8523,7 +8523,7 @@ snapshots:
|
||||||
chai: 5.1.1
|
chai: 5.1.1
|
||||||
debug: 4.3.6
|
debug: 4.3.6
|
||||||
execa: 8.0.1
|
execa: 8.0.1
|
||||||
magic-string: 0.30.11
|
magic-string: 0.30.17
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
std-env: 3.7.0
|
std-env: 3.7.0
|
||||||
tinybench: 2.9.0
|
tinybench: 2.9.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue