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
50 changes: 50 additions & 0 deletions src/index.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,56 @@ describe("postcss-assign-layer", () => {
);
});

it("keeps @charset and ICSS :import/:export rules outside the layer", async () => {
await run(
`@charset "UTF-8";
:import("./tokens.css") { primary: primary; }
a { color: FireBrick; }
:export { primary: primary; }
i { color: WhiteSmoke; }`,
`@charset "UTF-8";
:import("./tokens.css") { primary: primary; }
:export { primary: primary; }
@layer components {
a { color: FireBrick; }
i { color: WhiteSmoke; }
}`,
undefined,
{ from: path.resolve("test/fixtures/component.module.css") }
);
});

it("keeps @namespace outside the layer", async () => {
await run(
`@charset "UTF-8";
@namespace svg url(http://www.w3.org/2000/svg);
svg|a { color: FireBrick; }`,
`@charset "UTF-8";
@namespace svg url(http://www.w3.org/2000/svg);
@layer components {
svg|a { color: FireBrick; }
}`,
undefined,
{ from: path.resolve("test/fixtures/component.module.css") }
);
});

it("emits a hoisted @charset as the first bytes of the output", async () => {
// Asserts on raw output because prettier would strip the leading whitespace.
const result = await postcss([plugin()]).process(
`/* license */\n@charset "UTF-8";\na { color: FireBrick; }`,
{ from: path.resolve("test/fixtures/component.module.css") }
);
expect(result.css.startsWith(`@charset "UTF-8";`)).toBe(true);
});

it("does not hoist ICSS-looking rules out of non-matching files", async () => {
const input = `a { color: FireBrick; } :export { primary: red; }`;
await run(input, input, undefined, {
from: path.resolve("test/fixtures/base.css"),
});
});

it("allows multiple patterns and layers", async () => {
const basePath = path.resolve("test/fixtures/base.css");
const componentPath = path.resolve("test/fixtures/component.module.css");
Expand Down
17 changes: 17 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ const plugin: PluginCreator<PluginOptions> = (
}
}

if (layerNames.length === 0) return;

// `@charset` must be the first thing in a stylesheet, `@namespace` is only valid at
// the top level, and css-loader only reads ICSS `:import`/`:export` rules at the
// root, so these stay outside the layer.
const rootOnly = root.nodes.filter(
(node) =>
(node.type === "atrule" &&
(node.name === "charset" || node.name === "namespace")) ||
(node.type === "rule" && /^:(import|export)\b/.test(node.selector))
);
for (const node of rootOnly) node.remove();
// Browsers only honor `@charset` as the very first bytes of the file.
if (rootOnly[0]) rootOnly[0].raws.before = "";

for (const layerName of layerNames) {
const layer = new AtRule({
name: "layer",
Expand All @@ -46,6 +61,8 @@ const plugin: PluginCreator<PluginOptions> = (
root.removeAll();
root.append(layer);
}

root.prepend(rootOnly);
Comment thread
jasikpark marked this conversation as resolved.
Resolved
},
};
};
Expand Down
Loading