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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ ecij({
});
```

## Limitations

- The `css` tag must be imported directly from `'ecij'` (aliasing it is fine,
e.g. `import { css as styled } from 'ecij'`). Re-exporting the tag through
another module is not supported and leaves the templates untransformed.
- Interpolations must statically resolve to strings or numbers; dynamic or
complex expressions cause the css`` block to be skipped with a warning.

## Development

### Building
Expand Down Expand Up @@ -167,5 +175,4 @@ npm test -- -u

## TODO

- Full import/export handling (default/namespace import/export)
- Sourcemaps
851 changes: 734 additions & 117 deletions src/index.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/fixtures/ambiguous-a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const accent = 'red';
1 change: 1 addition & 0 deletions test/fixtures/ambiguous-b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const accent = 'blue';
5 changes: 5 additions & 0 deletions test/fixtures/ambiguous-barrel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @ts-nocheck — the ambiguity below is deliberate.
// `accent` is provided by two different bindings — per ESM the name is
// ambiguous and not exported at all.
export * from './ambiguous-a';
export * from './ambiguous-b';
10 changes: 10 additions & 0 deletions test/fixtures/ambiguous.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @ts-nocheck — `accent` is ambiguous, so the namespace exposes no such member
import { css } from 'ecij';

import * as ambiguous from './ambiguous-barrel';

// At runtime `ambiguous.accent` is undefined (ambiguous star exports are
// excluded from namespace objects) — the plugin must warn, not pick one.
export const usesAmbiguous = css`
color: ${ambiguous.accent};
`;
8 changes: 8 additions & 0 deletions test/fixtures/barrel-a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { css } from 'ecij';

export const aColor = 'aqua';

export const aClass = css`
/* a */
color: ${aColor};
`;
12 changes: 12 additions & 0 deletions test/fixtures/barrel-b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css } from 'ecij';

export const bColor = 'beige';

export const bClass = css`
/* b */
color: ${bColor};
`;

// `b` also defines `shared` — barrel-c re-exports an explicit `shared`
// that should win over the `export *` collision.
export const shared = 'b-loses';
1 change: 1 addition & 0 deletions test/fixtures/barrel-c.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const shared = 'c-wins';
1 change: 1 addition & 0 deletions test/fixtures/barrel-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dColor = 'darkcyan';
11 changes: 11 additions & 0 deletions test/fixtures/barrel-deep-only.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { css } from 'ecij';

// Resolving `dColor` probes barrel-a and barrel-b (which fail to provide it)
// before the nested barrel — their stylesheets must NOT be dragged into this
// bundle, since nothing from them is used here.
import { dColor } from './barrel';

export const usesOnlyDeep = css`
/* uses-only-deep */
color: ${dColor};
`;
4 changes: 4 additions & 0 deletions test/fixtures/barrel-nested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Nested barrel — re-exports through another barrel layer.
// `dColor` is reachable from the top-level barrel only through this hop.
export * from './barrel-a';
export * from './barrel-d';
39 changes: 39 additions & 0 deletions test/fixtures/barrel.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css } from 'ecij';

// Pull a mix of names through a barrel file:
// - `aColor`, `aClass` come from barrel-a via `export *`
// - `bColor`, `bClass` come from barrel-b via `export *`
// - `shared` is exposed by both barrel-b (via `export *`) and barrel-c (explicit
// `export { shared }`); the explicit re-export must win
// - `aClass` is also reachable through a nested barrel, but should still resolve
// to the same value (two star paths to the *same* binding are not ambiguous).
// - `dColor` is only reachable through the nested barrel (depth-2 `export *`).
import { aColor, aClass, bColor, bClass, shared, dColor } from './barrel';

export const usesBarrelA = css`
/* uses-a */
color: ${aColor};

&.${aClass} {
color: red;
}
`;

export const usesBarrelB = css`
/* uses-b */
color: ${bColor};

&.${bClass} {
color: red;
}
`;

export const usesBarrelShared = css`
/* uses-shared */
color: ${shared};
`;

export const usesBarrelDeep = css`
/* uses-deep */
color: ${dColor};
`;
10 changes: 10 additions & 0 deletions test/fixtures/barrel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// A barrel file with a mix of explicit re-exports and `export *` aggregation.
// Explicit named re-exports must take precedence over `export *` collisions.
export { shared } from './barrel-c';
export * from './barrel-a';
export * from './barrel-b';
export * from './barrel-nested';

// Local export that shadows `aColor` re-exported via `export * from './barrel-a'`.
// Per spec, an explicit named export wins over `export *` for the same name.
export const aColor = 'locally-overridden';
9 changes: 9 additions & 0 deletions test/fixtures/broken-export.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from 'ecij';

import { brokenClass } from './broken-export';

export const usesBrokenClass = css`
&.${brokenClass} {
color: red;
}
`;
11 changes: 11 additions & 0 deletions test/fixtures/broken-export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { css } from 'ecij';

function dynamicPadding(): number {
return 4;
}

// Extraction of this declaration fails (complex interpolation), so its class
// name must not leak into consumers as if its rule existed.
export const brokenClass = css`
padding: ${dynamicPadding()}px;
`;
17 changes: 17 additions & 0 deletions test/fixtures/cycle-a.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { css } from 'ecij';

// Mutual import cycle: this module uses cycle-b's class while cycle-b uses
// this module's class. Resolution must neither deadlock nor lose classes that
// are already extracted when the cycle is entered.
import { bClass } from './cycle-b';

export const aClass = css`
/* cycle-a */
color: red;
`;

export const usesB = css`
&.${bClass} {
color: blue;
}
`;
10 changes: 10 additions & 0 deletions test/fixtures/cycle-b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { css } from 'ecij';

import { aClass } from './cycle-a.input';

export const bClass = css`
/* cycle-b */
&.${aClass} {
color: green;
}
`;
5 changes: 5 additions & 0 deletions test/fixtures/default-passthrough-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default 'mediumseagreen';

// Decoy: resolving the re-exported default-import binding below must pick the
// default export above, not this same-named named export.
export const passthroughDefault = 'wrong-named-export';
5 changes: 5 additions & 0 deletions test/fixtures/default-passthrough.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// oxc normalizes this pair into a re-export entry that points at the *local*
// binding name instead of 'default' — the plugin must map it back.
import passthroughDefault from './default-passthrough-source';

export { passthroughDefault };
7 changes: 7 additions & 0 deletions test/fixtures/export-default-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { css } from 'ecij';

// Default-exported css tagged template (no local binding)
export default css`
/* default css */
border: 2px dashed teal;
`;
2 changes: 2 additions & 0 deletions test/fixtures/export-default-literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Default-exported string literal
export default 'royalblue';
9 changes: 9 additions & 0 deletions test/fixtures/export-default-local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from 'ecij';

const localClass = css`
/* default-local */
display: grid;
`;

// Default export pointing at a locally-bound css class
export default localClass;
2 changes: 2 additions & 0 deletions test/fixtures/export-default-negative.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Default-exported signed number literal
export default -5;
9 changes: 9 additions & 0 deletions test/fixtures/export-default-wrapped-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from 'ecij';

// Default-exported css tagged template behind a TS `as` assertion — the
// wrapper (like parentheses) must be unwrapped for the declaration to be
// recognized.
export default css`
/* wrapped-default */
display: flex;
` as string;
6 changes: 6 additions & 0 deletions test/fixtures/external-barrel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @ts-nocheck — '@acme/tokens' deliberately does not exist; it is marked
// external in the consuming test's build options.
// The first star source is an external package that cannot be parsed —
// resolution must skip it gracefully and find the value in the next source.
export * from '@acme/tokens';
export * from './external-tokens';
8 changes: 8 additions & 0 deletions test/fixtures/external-star.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { css } from 'ecij';

import { brandColor } from './external-barrel';

export const usesExternalBarrel = css`
/* uses external-barrel */
color: ${brandColor};
`;
1 change: 1 addition & 0 deletions test/fixtures/external-tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const brandColor = 'goldenrod';
49 changes: 49 additions & 0 deletions test/fixtures/import-export-hardening.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { css } from 'ecij';

// Default import re-exported under its local name (`import d from 'mod'; export { d };`)
import { passthroughDefault } from './default-passthrough';
// Default exports with static expressions the evaluator must unwrap
import negativePad from './export-default-negative';
import wrappedClass from './export-default-wrapped-css';
// Namespace re-export reached through a chained named re-export
import { styles as chainedStyles } from './ns-chain';
// Namespace re-export reached through an `export *` aggregation
import { styles as starChainedStyles } from './ns-chain-star';
// Nested namespace member access (`ns.inner.member`)
import * as outerNs from './reexports-namespace';

export const usesPassthroughDefault = css`
/* uses passthrough-default */
color: ${passthroughDefault};
`;

export const usesChainedNamespace = css`
/* uses chained-namespace */
color: ${chainedStyles.accentColor};
`;

export const usesStarChainedNamespace = css`
/* uses star-chained-namespace */
font-size: ${starChainedStyles.accentSize}px;
`;

export const usesNestedNamespaceMember = css`
/* uses nested-namespace-member */
background: ${outerNs.styles.accentColor};

&.${outerNs.styles.accentClass} {
color: red;
}
`;

export const usesNegativeDefault = css`
/* uses negative-default */
margin: ${negativePad}px;
`;

export const usesWrappedDefaultCss = css`
/* uses wrapped-default-css */
&.${wrappedClass} {
color: red;
}
`;
Loading
Loading