Add source map support to the CLI
This commit is contained in:
parent
acf5f3182e
commit
2f25e3ec3a
2 changed files with 268 additions and 5 deletions
|
|
@ -23,7 +23,7 @@ describe.each([
|
|||
'Standalone CLI',
|
||||
path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`),
|
||||
],
|
||||
])('%s', (_, command) => {
|
||||
])('%s', (kind, command) => {
|
||||
test(
|
||||
'production build',
|
||||
{
|
||||
|
|
@ -628,6 +628,198 @@ 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`)
|
||||
|
||||
console.log(await fs.read('dist/out.css'))
|
||||
|
||||
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`])
|
||||
|
||||
console.log(await fs.read('dist/out.css'))
|
||||
|
||||
// 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(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
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 { Scanner, type ChangedContent } from '@tailwindcss/oxide'
|
||||
import { existsSync, type Stats } from 'node:fs'
|
||||
|
|
@ -52,6 +59,11 @@ export function options() {
|
|||
description: 'The current working directory',
|
||||
default: '.',
|
||||
},
|
||||
'--map': {
|
||||
type: 'boolean | string',
|
||||
description: 'Generate a source map',
|
||||
default: false,
|
||||
},
|
||||
} satisfies Arg
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +117,21 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
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 input = args['--input']
|
||||
|
|
@ -120,7 +147,12 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
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
|
||||
|
||||
// Optimize the output
|
||||
|
|
@ -130,10 +162,14 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
let optimized = optimize(css, {
|
||||
file: args['--input'] ?? 'input.css',
|
||||
minify: args['--minify'] ?? false,
|
||||
map: map?.raw ?? undefined,
|
||||
})
|
||||
DEBUG && I.end('Optimize CSS')
|
||||
previous.css = css
|
||||
previous.optimizedCss = optimized.code
|
||||
if (optimized.map) {
|
||||
map = toSourceMap(optimized.map)
|
||||
}
|
||||
output = optimized.code
|
||||
} else {
|
||||
output = previous.optimizedCss
|
||||
|
|
@ -141,6 +177,18 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
}
|
||||
|
||||
// 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')
|
||||
if (args['--output'] && args['--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) {
|
||||
DEBUG && I.start('Setup compiler')
|
||||
let compiler = await compile(css, {
|
||||
from: args['--output'] ? (inputFilePath ?? 'stdin.css') : undefined,
|
||||
base: inputBasePath,
|
||||
onDependency(path) {
|
||||
fullRebuildPaths.push(path)
|
||||
|
|
@ -231,6 +280,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
|
||||
// Track the compiled CSS
|
||||
let compiledCss = ''
|
||||
let compiledMap: SourceMap | null = null
|
||||
|
||||
// Scan the entire `base` directory for full rebuilds.
|
||||
if (rebuildStrategy === 'full') {
|
||||
|
|
@ -269,6 +319,12 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(candidates)
|
||||
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.
|
||||
|
|
@ -288,9 +344,15 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(newCandidates)
|
||||
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()
|
||||
eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
|
|
@ -325,7 +387,16 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
DEBUG && I.start('Build CSS')
|
||||
let output = await handleError(() => compiler.build(candidates))
|
||||
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()
|
||||
eprintln(header())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue