Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/emotion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ Generate source maps for Emotion CSS. Source maps help trace styles back to thei

### `autoLabel`

- **Type:** `'never' | 'dev-only' | 'always'`
- **Type:** `'never' | 'dev-only' | 'always' | 'runtime'`
- **Default:** `'dev-only'`

Controls when debug labels are added to styled components and `css` calls.
Controls when debug labels are added to styled components, `css`, and `keyframes` calls.

- `'never'` — Never add labels
- `'dev-only'` — Only add labels in development mode
- `'always'` — Always add labels
- `'runtime'` — Add labels unless `process.env.NODE_ENV === "production"` at runtime

### `labelFormat`

Expand Down
66 changes: 41 additions & 25 deletions packages/emotion/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { createLabelWithInfo } from './label.js'
import path from 'node:path'
import hashString from '@emotion/hash'

const RUNTIME_PRODUCTION_CONDITION = 'process.env.NODE_ENV === "production"'

export type { EmotionPluginOptions } from './types.js'

interface RecordData {
Expand Down Expand Up @@ -83,6 +85,8 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
return false
case 'dev-only':
return isDev
case 'runtime':
return false
default:
autoLabel satisfies never
return false
Expand Down Expand Up @@ -112,6 +116,26 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
return label
}

function createLabelProperty(context: string | null): string | null {
const label = escapeJSString(createLabel(context, false))
if (autoLabel === 'runtime') {
return `...(${RUNTIME_PRODUCTION_CONDITION} ? {} : { label: "${label}" })`
}
return shouldAddLabel() ? `label: "${label}"` : null
}

function createLabelArgument(
kind: ExprKind,
context: string | null,
terminateBeforeSourcemap: boolean,
): string | null {
const label = escapeJSString(createRuntimeLabel(kind, context, terminateBeforeSourcemap))
if (autoLabel === 'runtime') {
return `...(${RUNTIME_PRODUCTION_CONDITION} ? [] : ["${label}"])`
}
return shouldAddLabel() ? `"${label}"` : null
}

function makeSourceMap(offset: number): string | null {
if (!(sourceMapEnabled ?? isDev)) return null
const pos = getPos(sourceContent, offset)
Expand Down Expand Up @@ -155,9 +179,9 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi

// Add label and source map (unless in JSX element context)
if (!inJsx) {
if (includeLabel && shouldAddLabel()) {
const label = createRuntimeLabel(kind, labelContext, true)
parts.push(`"${escapeJSString(label)}"`)
if (includeLabel) {
const labelArgument = createLabelArgument(kind, labelContext, true)
if (labelArgument) parts.push(labelArgument)
}
const sm = makeSourceMap(sourceMapOffset)
if (sm) {
Expand Down Expand Up @@ -369,10 +393,8 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
isFullReplace: true,
apply: (getTarget) => {
let labelObj = `target: "${getTarget()}"`
if (shouldAddLabel()) {
const label = createLabel(labelContext, false)
labelObj += `, label: "${escapeJSString(label)}"`
}
const labelProperty = createLabelProperty(labelContext)
if (labelProperty) labelObj += `, ${labelProperty}`

const styledArgs = buildTaggedTemplateArgs(
quasi,
Expand Down Expand Up @@ -444,10 +466,8 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
const styledName = s.slice(tag.callee.start, tag.callee.end)
const target = getTarget()
let labelObj = `target: "${target}"`
if (shouldAddLabel()) {
const label = createLabel(labelContext, false)
labelObj += `, label: "${escapeJSString(label)}"`
}
const labelProperty = createLabelProperty(labelContext)
if (labelProperty) labelObj += `, ${labelProperty}`

// Extract existing args from styled(Component, ...)
const existingArgs = tag.arguments
Expand Down Expand Up @@ -522,11 +542,11 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
apply: () => {
s.appendLeft(node.start, '/* @__PURE__ */ ')
let hasTrailingComma = checkTrailingCommaExistence(s.original, node.end - 1)
if (shouldAddLabel()) {
const label = createRuntimeLabel(kind, labelContext, false)
const labelArgument = createLabelArgument(kind, labelContext, false)
if (labelArgument) {
s.appendRight(
node.end - 1,
`${maybeComma(!hasTrailingComma)}"${escapeJSString(label)}"`,
`${maybeComma(!hasTrailingComma)}${labelArgument}`,
)
hasTrailingComma = false
}
Expand Down Expand Up @@ -564,10 +584,8 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
apply: (getTarget) => {
s.appendLeft(node.start, '/* @__PURE__ */ ')
let labelObj = `target: "${getTarget()}"`
if (shouldAddLabel()) {
const label = createLabel(labelContext, false)
labelObj += `, label: "${escapeJSString(label)}"`
}
const labelProperty = createLabelProperty(labelContext)
if (labelProperty) labelObj += `, ${labelProperty}`
// Add { target, label } as second arg to inner call
if (callee.arguments.length === 1) {
// Insert before inner call's closing )
Expand Down Expand Up @@ -646,10 +664,8 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
if (!wasInJsx) {
labelObj += `target: "${getTarget()}"`
s.appendLeft(node.start, '/* @__PURE__ */ ')
if (shouldAddLabel()) {
const label = createLabel(labelContext, false)
labelObj += `, label: "${escapeJSString(label)}"`
}
const labelProperty = createLabelProperty(labelContext)
if (labelProperty) labelObj += `, ${labelProperty}`
}
const styledName = s.slice(callee.object.start, callee.object.end)
const propName = callee.property.name
Expand Down Expand Up @@ -696,11 +712,11 @@ export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugi
apply: () => {
s.appendLeft(node.start, '/* @__PURE__ */ ')
let hasTrailingComma = checkTrailingCommaExistence(s.original, node.end - 1)
if (shouldAddLabel()) {
const label = createRuntimeLabel(kind, labelContext, false)
const labelArgument = createLabelArgument(kind, labelContext, false)
if (labelArgument) {
s.appendRight(
node.end - 1,
`${maybeComma(!hasTrailingComma)}"${escapeJSString(label)}"`,
`${maybeComma(!hasTrailingComma)}${labelArgument}`,
)
hasTrailingComma = false
}
Expand Down
5 changes: 3 additions & 2 deletions packages/emotion/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ export interface EmotionPluginOptions {
sourceMap?: boolean

/**
* When to add debug labels to styled components.
* When to add debug labels to styled components, `css`, and `keyframes`.
* - 'never': Never add labels
* - 'dev-only': Only add labels in development mode (default)
* - 'always': Always add labels
* - 'runtime': Add labels unless `process.env.NODE_ENV` is 'production' at runtime
* @default 'dev-only'
*/
autoLabel?: 'never' | 'dev-only' | 'always'
autoLabel?: 'never' | 'dev-only' | 'always' | 'runtime'

/**
* Label format template.
Expand Down
5 changes: 5 additions & 0 deletions packages/emotion/tests/fixtures/runtime-labels/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"autoLabel": "runtime",
"labelFormat": "[filename]-[local]",
"sourceMap": true
}
26 changes: 26 additions & 0 deletions packages/emotion/tests/fixtures/runtime-labels/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled from '@emotion/styled'
import { css, keyframes } from '@emotion/react'
import * as emotion from '@emotion/react'

const Component = 'div'

export const StyledMemberTag = styled.button`color: hotpink;`
export const StyledComponentTag = styled(Component)`color: hotpink;`
export const StyledNestedCall = styled('button', {
shouldForwardProp: () => true,
})({ color: 'hotpink' })
export const StyledMemberCall = styled.button({ color: 'hotpink' })
export const cssTag = css`color: hotpink;`
export const keyframesTag = keyframes`from { opacity: 0; } to { opacity: 1; }`
export const cssCall = css({ color: 'hotpink' })
export const keyframesCall = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })
export const namespaceCssTag = emotion.css`color: hotpink;`
export const namespaceKeyframesTag = emotion.keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
export const namespaceCssCall = emotion.css({ color: 'hotpink' })
export const namespaceKeyframesCall = emotion.keyframes({
from: { opacity: 0 },
to: { opacity: 1 },
})
38 changes: 38 additions & 0 deletions packages/emotion/tests/fixtures/runtime-labels/output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import styled from "@emotion/styled";
import * as emotion from "@emotion/react";
import { css, keyframes } from "@emotion/react";
//#region virtual:entry.tsx
const Component = "div";
const StyledMemberTag = /* @__PURE__ */ styled("button", {
target: "e1r3sr4i0",
...process.env.NODE_ENV === "production" ? {} : { label: "virtual-entry-StyledMemberTag" }
})("color:hotpink;", "/*# sourceMappingURL=[sourcemap] */");
const StyledComponentTag = /* @__PURE__ */ styled(Component, {
target: "e1r3sr4i1",
...process.env.NODE_ENV === "production" ? {} : { label: "virtual-entry-StyledComponentTag" }
})("color:hotpink;", "/*# sourceMappingURL=[sourcemap] */");
const StyledNestedCall = /* @__PURE__ */ styled("button", {
shouldForwardProp: () => true,
target: "e1r3sr4i2",
...process.env.NODE_ENV === "production" ? {} : { label: "virtual-entry-StyledNestedCall" }
})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */");
const StyledMemberCall = /* @__PURE__ */ styled("button", {
target: "e1r3sr4i3",
...process.env.NODE_ENV === "production" ? {} : { label: "virtual-entry-StyledMemberCall" }
})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */");
const cssTag = /* @__PURE__ */ css("color:hotpink;", ...process.env.NODE_ENV === "production" ? [] : ["label:virtual-entry-cssTag;"], "/*# sourceMappingURL=[sourcemap] */");
const keyframesTag = /* @__PURE__ */ keyframes("from{opacity:0;}to{opacity:1;}", ...process.env.NODE_ENV === "production" ? [] : ["virtual-entry-keyframesTag"], "/*# sourceMappingURL=[sourcemap] */");
const cssCall = /* @__PURE__ */ css({ color: "hotpink" }, ...process.env.NODE_ENV === "production" ? [] : ["label:virtual-entry-cssCall"], "/*# sourceMappingURL=[sourcemap] */");
const keyframesCall = /* @__PURE__ */ keyframes({
from: { opacity: 0 },
to: { opacity: 1 }
}, ...process.env.NODE_ENV === "production" ? [] : ["virtual-entry-keyframesCall"], "/*# sourceMappingURL=[sourcemap] */");
const namespaceCssTag = /* @__PURE__ */ emotion.css("color:hotpink;", ...process.env.NODE_ENV === "production" ? [] : ["label:virtual-entry-namespaceCssTag;"], "/*# sourceMappingURL=[sourcemap] */");
const namespaceKeyframesTag = /* @__PURE__ */ emotion.keyframes("from{opacity:0;}to{opacity:1;}", ...process.env.NODE_ENV === "production" ? [] : ["virtual-entry-namespaceKeyframesTag"], "/*# sourceMappingURL=[sourcemap] */");
const namespaceCssCall = /* @__PURE__ */ emotion.css({ color: "hotpink" }, ...process.env.NODE_ENV === "production" ? [] : ["label:virtual-entry-namespaceCssCall"], "/*# sourceMappingURL=[sourcemap] */");
const namespaceKeyframesCall = /* @__PURE__ */ emotion.keyframes({
from: { opacity: 0 },
to: { opacity: 1 }
}, ...process.env.NODE_ENV === "production" ? [] : ["virtual-entry-namespaceKeyframesCall"], "/*# sourceMappingURL=[sourcemap] */");
//#endregion
export { StyledComponentTag, StyledMemberCall, StyledMemberTag, StyledNestedCall, cssCall, cssTag, keyframesCall, keyframesTag, namespaceCssCall, namespaceCssTag, namespaceKeyframesCall, namespaceKeyframesTag };
5 changes: 5 additions & 0 deletions packages/emotion/tests/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ async function transform(

const build = await rolldown({
input: virtualEntry,
transform: {
define: {
'process.env.NODE_ENV': 'process.env.NODE_ENV',
},
},
plugins: [
{
name: 'virtual',
Expand Down
Loading