Skip to content

Commit 983e7c6

Browse files
Timothy Lindvalltimlindvall
Timothy Lindvall
authored andcommitted
feat: Compiled CSS importing in NodeJSImporter.
- Update import method in NodeJSImporter to support Compiled CSS files. This update supports both inline definitions and definition files. - Update tests to validate changes.
1 parent 62eb34e commit 983e7c6

File tree

8 files changed

+217
-12
lines changed

8 files changed

+217
-12
lines changed

packages/@css-blocks/core/src/importing/BaseImporter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Syntax } from "../BlockParser";
22
import { ResolvedConfiguration } from "../configuration";
33
import { REGEXP_COMMENT_DEFINITION_REF, REGEXP_COMMENT_FOOTER, REGEXP_COMMENT_HEADER } from "../PrecompiledDefinitions/compiled-comments";
44

5-
import { FileIdentifier, ImportedCompiledCssFileContents, ImportedFile, Importer } from "./Importer";
5+
import { FileIdentifier, ImportedCompiledCssFile, ImportedCompiledCssFileContents, ImportedFile, Importer } from "./Importer";
66

77
/**
88
* The BaseImporter is an abstract class that Importer implementations may extend from.
@@ -19,7 +19,7 @@ export abstract class BaseImporter implements Importer {
1919
/**
2020
* Import the file with the given metadata and return a string and meta data for it.
2121
*/
22-
abstract import(identifier: FileIdentifier, config: ResolvedConfiguration): Promise<ImportedFile>;
22+
abstract import(identifier: FileIdentifier, config: ResolvedConfiguration): Promise<ImportedFile | ImportedCompiledCssFile>;
2323
/**
2424
* The default name of the block used unless the block specifies one itself.
2525
*/

packages/@css-blocks/core/src/importing/NodeJsImporter.ts

+53-10
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import * as path from "path";
55

66
import { Syntax } from "../BlockParser";
77
import { ResolvedConfiguration } from "../configuration";
8+
import { isDefinitionUrlValid } from "../PrecompiledDefinitions/compiled-comments";
89

910
import { BaseImporter } from "./BaseImporter";
10-
import { FileIdentifier, ImportedFile } from "./Importer";
11+
import { FileIdentifier, ImportedCompiledCssFile, ImportedFile } from "./Importer";
1112

1213
const debug = debugGenerator("css-blocks:importer");
1314

@@ -134,15 +135,57 @@ export class NodeJsImporter extends BaseImporter {
134135
return path.relative(config.rootDir, identifier);
135136
}
136137

137-
async import(identifier: FileIdentifier, config: ResolvedConfiguration): Promise<ImportedFile> {
138+
async import(identifier: FileIdentifier, config: ResolvedConfiguration): Promise<ImportedFile | ImportedCompiledCssFile> {
138139
let contents = await readFile(identifier, "utf-8");
139-
// TODO: Add support for CompiledCSS files.
140-
return {
141-
type: "ImportedFile",
142-
syntax: this.syntax(identifier, config),
143-
identifier,
144-
defaultName: this.defaultName(identifier, config),
145-
contents,
146-
};
140+
141+
if (this.isCompiledBlockCSS(contents)) {
142+
const segmentedContents = this.segmentizeCompiledBlockCSS(contents);
143+
144+
// Need to determine if the definition URL is an external URL we should
145+
// follow, or embedded data.
146+
const dfnUrl = segmentedContents.definitionUrl;
147+
let dfnData: string | null = null;
148+
if (!isDefinitionUrlValid(dfnUrl)) {
149+
throw new Error(`Definition URL in Compiled CSS file is invalid.\nFile Identifier: ${identifier}\nDefinition URL: ${dfnUrl}`);
150+
}
151+
if (dfnUrl.startsWith("data:")) {
152+
// Parse this as embedded data.
153+
const [dfnHeader, dfnEncodedData] = dfnUrl.split(",");
154+
if (dfnHeader === "data:text/css;base64") {
155+
dfnData = Buffer.from(dfnEncodedData, "base64").toString("utf-8");
156+
} else {
157+
throw new Error(`Definition data is in unsupported encoding or format. Embedded data must be in text/css;base64 format.\nFormat given: ${dfnHeader}`);
158+
}
159+
} else {
160+
// Read in the definition data from the given path.
161+
const dfnIdentifier = this.identifier(identifier, dfnUrl, config);
162+
try {
163+
dfnData = await readFile(dfnIdentifier, "utf-8");
164+
} catch (e) {
165+
throw new Error(`Definition URL in Compiled CSS file is invalid.\nFile Identifier: ${identifier}\nDefinition URL: ${dfnUrl}\nThrown error: ${e.message}`);
166+
}
167+
}
168+
169+
if (!dfnData || dfnData.trim() === "") {
170+
throw new Error("Could not parse or read definition data.");
171+
}
172+
173+
return {
174+
type: "ImportedCompiledCssFile",
175+
syntax: Syntax.css,
176+
identifier,
177+
cssContents: segmentedContents.blockCssContents,
178+
blockId: segmentedContents.blockId,
179+
definitionContents: dfnData,
180+
};
181+
} else {
182+
return {
183+
type: "ImportedFile",
184+
syntax: this.syntax(identifier, config),
185+
identifier,
186+
defaultName: this.defaultName(identifier, config),
187+
contents,
188+
};
189+
}
147190
}
148191
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* This is a test compiled css file. */
2+
/*#css-blocks 7d97e*/
3+
.nav-7d97e {
4+
display: flex;
5+
}
6+
.nav-7d97e__entry {
7+
flex: 1;
8+
}
9+
.nav-7d97e__entry--active {
10+
font-weight: bold;
11+
}
12+
.nav-7d97e__entry--active.link-3c287 {
13+
font-weight: bold;
14+
}
15+
.nav-7d97e__entry:hover {
16+
text-shadow: 2px 2px 1px;
17+
}
18+
/*#blockDefinitionURL=data:text/css;base64,QGJsb2NrLXN5bnRheC12ZXJzaW9uIDE7CkBibG9jayBsaW5rIGZyb20gIi4uL3NoYXJlZC9saW5rLmNzcyI7CkBibG9jayBsaXN0IGZyb20gIi4uL3NoYXJlZC9saXN0LmNzcyI7CkBleHBvcnQgaXRlbSBmcm9tICIuLi9zaGFyZWQvaXRlbS5jc3MiOwoKOnNjb3BlIHsKICBibG9jay1pZDogIjdkOTdlIjsKICBibG9jay1jbGFzczogbmF2LTdkOTdlOwogIGJsb2NrLWludGVyZmFjZS1pbmRleDogMDsKICBibG9jay1hbGlhczogdG9wLW5hdjsKICBibG9jay1uYW1lOiBuYXY7CiAgZXh0ZW5kczogbGlzdDsKICBpbmhlcml0ZWQtc3R5bGVzOiAibGlzdFt0eXBlPW9yZGVyZWRdIiAxLCAibGlzdFt0eXBlPXVub3JkZXJlZF0iIDIsICJsaXN0W3R5cGU9aW5saW5lXSIgMywgImxpc3RbdHlwZT1ob3Jpem9udGFsXSIgNCwgImxpc3QuaXRlbSIgNSwgImxpc3QuaXRlbVtsYXN0XSIgNjsKfQoKLmVudHJ5IHsKICBibG9jay1pbnRlcmZhY2UtaW5kZXg6IDc7CiAgYmxvY2stYWxpYXM6IHRvcC1uYXYtZW50cnk7CiAgYmxvY2stY2xhc3M6IG5hdi03ZDk3ZV9fZW50cnk7Cn0KCi5lbnRyeVthY3RpdmVdIHsKICBibG9jay1pbnRlcmZhY2UtaW5kZXg6IDg7CiAgYmxvY2stY2xhc3M6IG5hdi03ZDk3ZV9fZW50cnktLWFjdGl2ZTsKICBmb250LXdlaWdodDogcmVzb2x2ZSgibGluayIpOwogIGZvbnQtd2VpZ2h0OiByZXNvbHZlLXNlbGYoKTsKfQ==*/
19+
/*#css-blocks end*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.nav-7d97e {
2+
display: flex;
3+
}
4+
.nav-7d97e__entry {
5+
flex: 1;
6+
}
7+
.nav-7d97e__entry--active {
8+
font-weight: bold;
9+
}
10+
.nav-7d97e__entry--active.link-3c287 {
11+
font-weight: bold;
12+
}
13+
.nav-7d97e__entry:hover {
14+
text-shadow: 2px 2px 1px;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@block-syntax-version 1;
2+
@block link from "../shared/link.css";
3+
@block list from "../shared/list.css";
4+
@export item from "../shared/item.css";
5+
6+
:scope {
7+
block-id: "7d97e";
8+
block-class: nav-7d97e;
9+
block-interface-index: 0;
10+
block-alias: top-nav;
11+
block-name: nav;
12+
extends: list;
13+
inherited-styles: "list[type=ordered]" 1, "list[type=unordered]" 2, "list[type=inline]" 3, "list[type=horizontal]" 4, "list.item" 5, "list.item[last]" 6;
14+
}
15+
16+
.entry {
17+
block-interface-index: 7;
18+
block-alias: top-nav-entry;
19+
block-class: nav-7d97e__entry;
20+
}
21+
22+
.entry[active] {
23+
block-interface-index: 8;
24+
block-class: nav-7d97e__entry--active;
25+
font-weight: resolve("link");
26+
font-weight: resolve-self();
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@block-syntax-version 1;
2+
@block link from "../shared/link.css";
3+
@block list from "../shared/list.css";
4+
@export item from "../shared/item.css";
5+
6+
:scope {
7+
block-id: "7d97e";
8+
block-class: nav-7d97e;
9+
block-interface-index: 0;
10+
block-alias: top-nav;
11+
block-name: nav;
12+
extends: list;
13+
inherited-styles: "list[type=ordered]" 1, "list[type=unordered]" 2, "list[type=inline]" 3, "list[type=horizontal]" 4, "list.item" 5, "list.item[last]" 6;
14+
}
15+
16+
.entry {
17+
block-interface-index: 7;
18+
block-alias: top-nav-entry;
19+
block-class: nav-7d97e__entry;
20+
}
21+
22+
.entry[active] {
23+
block-interface-index: 8;
24+
block-class: nav-7d97e__entry--active;
25+
font-weight: resolve("link");
26+
font-weight: resolve-self();
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* This is a test compiled css file. */
2+
/*#css-blocks 7d97e*/
3+
.nav-7d97e {
4+
display: flex;
5+
}
6+
.nav-7d97e__entry {
7+
flex: 1;
8+
}
9+
.nav-7d97e__entry--active {
10+
font-weight: bold;
11+
}
12+
.nav-7d97e__entry--active.link-3c287 {
13+
font-weight: bold;
14+
}
15+
.nav-7d97e__entry:hover {
16+
text-shadow: 2px 2px 1px;
17+
}
18+
/*#blockDefinitionURL=nav.block.css*/
19+
/*#css-blocks end*/

packages/@css-blocks/core/test/importing-test.ts

+55
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717

1818
const FIXTURES = path.resolve(__dirname, "..", "..", "test", "fixtures");
1919
const FSI_FIXTURES = path.join(FIXTURES, "filesystemImporter");
20+
const COMPILED_CSS_FIXTURES = path.join(FIXTURES, "compiledFileImporting");
2021
const ALIAS_FIXTURES = path.join(FIXTURES, "pathAliasImporter");
2122
const NODE_MODULE_FIXTURES = path.join(FIXTURES, "nodeModuleImporter");
2223

@@ -93,6 +94,60 @@ function testFSImporter(name: string, importer: Importer) {
9394
assert.fail(importedFile.type, "ImportedFile", "Mismatched type given");
9495
}
9596
});
97+
it("Can import Compiled CSS file with embedded definition data", async () => {
98+
const EMBEDDED_DFN_FIXTURES = path.join(COMPILED_CSS_FIXTURES, "embedded");
99+
const options = getConfiguration(EMBEDDED_DFN_FIXTURES);
100+
const ident = importer.identifier(null, "nav.css", options);
101+
const importedFile = await importer.import(ident, options);
102+
if (importedFile.type === "ImportedCompiledCssFile") {
103+
assert.equal(importedFile.identifier, ident);
104+
assert.equal(importedFile.syntax, Syntax.css);
105+
assert.equal(importedFile.blockId, "7d97e");
106+
assert.deepEqual(
107+
importedFile.cssContents.trim(),
108+
fs.readFileSync(
109+
path.join(COMPILED_CSS_FIXTURES, "expectedResults", "expectedCssContents.txt"),
110+
"utf-8",
111+
).trim(),
112+
);
113+
assert.deepEqual(
114+
importedFile.definitionContents.trim(),
115+
fs.readFileSync(
116+
path.join(COMPILED_CSS_FIXTURES, "expectedResults", "expectedDfnContents.txt"),
117+
"utf-8",
118+
).trim(),
119+
);
120+
} else {
121+
assert.fail(importedFile.type, "ImportedCompiledCssFile", "Mismatched type given");
122+
}
123+
});
124+
it("Can import Compiled CSS file with external definition path", async () => {
125+
const EXTERNAL_DFN_FIXTURES = path.join(COMPILED_CSS_FIXTURES, "externaldef");
126+
const options = getConfiguration(EXTERNAL_DFN_FIXTURES);
127+
const ident = importer.identifier(null, "nav.css", options);
128+
const importedFile = await importer.import(ident, options);
129+
if (importedFile.type === "ImportedCompiledCssFile") {
130+
assert.equal(importedFile.identifier, ident);
131+
assert.equal(importedFile.syntax, Syntax.css);
132+
assert.equal(importedFile.blockId, "7d97e");
133+
assert.deepEqual(
134+
importedFile.cssContents.trim(),
135+
fs.readFileSync(
136+
path.join(COMPILED_CSS_FIXTURES, "expectedResults", "expectedCssContents.txt"),
137+
"utf-8",
138+
).trim(),
139+
);
140+
assert.deepEqual(
141+
importedFile.definitionContents.trim(),
142+
fs.readFileSync(
143+
path.join(COMPILED_CSS_FIXTURES, "expectedResults", "expectedDfnContents.txt"),
144+
"utf-8",
145+
).trim(),
146+
);
147+
} else {
148+
assert.fail(importedFile.type, "ImportedCompiledCssFile", "Mismatched type given");
149+
}
150+
});
96151
});
97152
}
98153

0 commit comments

Comments
 (0)