next-yak 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/dist/cssLiteral.d.ts +11 -0
- package/dist/cssLiteral.d.ts.map +1 -0
- package/dist/cssLiteral.js +63 -0
- package/dist/cssLiteral.js.map +1 -0
- package/dist/cssLiteral.jsx +63 -0
- package/dist/cssLiteral.jsx.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/styled.d.ts +183 -0
- package/dist/styled.d.ts.map +1 -0
- package/dist/styled.js +31 -0
- package/dist/styled.js.map +1 -0
- package/dist/styled.jsx +30 -0
- package/dist/styled.jsx.map +1 -0
- package/loaders/__tests__/cssloader.test.ts +210 -0
- package/loaders/__tests__/tsloader.test.ts +159 -0
- package/loaders/cssloader.cjs +183 -0
- package/loaders/lib/hash.cjs +60 -0
- package/loaders/lib/loadConfigOnce.cjs +17 -0
- package/loaders/lib/quasiClassifier.cjs +33 -0
- package/loaders/lib/replaceQuasiExpressionTokens.cjs +56 -0
- package/loaders/lib/stripCssComments.cjs +49 -0
- package/loaders/tsloader.cjs +240 -0
- package/loaders/withYak.cjs +52 -0
- package/loaders/withYak.d.ts +39 -0
- package/package.json +43 -0
- package/runtime/cssLiteral.tsx +100 -0
- package/runtime/index.ts +2 -0
- package/runtime/styled.tsx +48 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {describe, it, expect} from "vitest";
|
|
2
|
+
import cssloader from "../cssloader.cjs";
|
|
3
|
+
|
|
4
|
+
const loaderContext = {
|
|
5
|
+
resourcePath: "/some/special/path/page.tsx",
|
|
6
|
+
rootContext: "/some",
|
|
7
|
+
importModule: () => {
|
|
8
|
+
return {
|
|
9
|
+
replaces: {
|
|
10
|
+
queries: {
|
|
11
|
+
sm: "@media (min-width: 640px)",
|
|
12
|
+
md: "@media (min-width: 768px)",
|
|
13
|
+
lg: "@media (min-width: 1024px)",
|
|
14
|
+
xl: "@media (min-width: 1280px)",
|
|
15
|
+
xxl: "@media (min-width: 1536px)",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
getOptions: () => ({
|
|
21
|
+
configPath: "/some/special/path/config",
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("cssloader", () => {
|
|
26
|
+
// snapshot
|
|
27
|
+
it("should return the correct value", async () => {
|
|
28
|
+
expect(
|
|
29
|
+
await cssloader.call(
|
|
30
|
+
loaderContext,
|
|
31
|
+
`
|
|
32
|
+
import styles from "./page.module.css";
|
|
33
|
+
import { css } from "next-yak";
|
|
34
|
+
|
|
35
|
+
const headline = css\`
|
|
36
|
+
font-size: 2rem;
|
|
37
|
+
font-weight: bold;
|
|
38
|
+
color: red;
|
|
39
|
+
&:hover {
|
|
40
|
+
color: red;
|
|
41
|
+
}
|
|
42
|
+
\`;
|
|
43
|
+
`
|
|
44
|
+
)
|
|
45
|
+
).toMatchInlineSnapshot(`
|
|
46
|
+
".style0 {
|
|
47
|
+
font-size: 2rem;
|
|
48
|
+
font-weight: bold;
|
|
49
|
+
color: red;
|
|
50
|
+
&:hover {
|
|
51
|
+
color: red;
|
|
52
|
+
}
|
|
53
|
+
}"
|
|
54
|
+
`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should support nested css code", async () => {
|
|
58
|
+
expect(
|
|
59
|
+
await cssloader.call(
|
|
60
|
+
loaderContext,
|
|
61
|
+
`
|
|
62
|
+
import styles from "./page.module.css";
|
|
63
|
+
import { css } from "next-yak";
|
|
64
|
+
|
|
65
|
+
const x = Math.random();
|
|
66
|
+
const headline = css\`
|
|
67
|
+
font-size: 2rem;
|
|
68
|
+
font-weight: bold;
|
|
69
|
+
color: red;
|
|
70
|
+
\${x > 0.5 && css\`
|
|
71
|
+
color: blue;
|
|
72
|
+
\`}
|
|
73
|
+
\${x > 0.5 && css\`
|
|
74
|
+
color: blue;
|
|
75
|
+
\`}
|
|
76
|
+
&:hover {
|
|
77
|
+
color: \${x ? "red" : "blue"\};
|
|
78
|
+
}
|
|
79
|
+
\`;
|
|
80
|
+
`
|
|
81
|
+
)
|
|
82
|
+
).toMatchInlineSnapshot(`
|
|
83
|
+
".style0 {
|
|
84
|
+
font-size: 2rem;
|
|
85
|
+
font-weight: bold;
|
|
86
|
+
color: red;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.style1 {
|
|
90
|
+
color: blue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.style2 {
|
|
94
|
+
color: blue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.style0 {
|
|
98
|
+
&:hover {
|
|
99
|
+
color: var(--🦬18fi82j0);
|
|
100
|
+
}
|
|
101
|
+
}"
|
|
102
|
+
`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should ignores empty chunks if they include only a comment", async () => {
|
|
106
|
+
expect(
|
|
107
|
+
await cssloader.call(
|
|
108
|
+
loaderContext,
|
|
109
|
+
`
|
|
110
|
+
import styles from "./page.module.css";
|
|
111
|
+
import { css } from "next-yak";
|
|
112
|
+
|
|
113
|
+
const x = Math.random();
|
|
114
|
+
const headline = css\`
|
|
115
|
+
/* comment */
|
|
116
|
+
\${x > 0.5 && css\`
|
|
117
|
+
color: blue;
|
|
118
|
+
\`}
|
|
119
|
+
\`;
|
|
120
|
+
`
|
|
121
|
+
)
|
|
122
|
+
).toMatchInlineSnapshot(`
|
|
123
|
+
".style1 {
|
|
124
|
+
color: blue;
|
|
125
|
+
}"
|
|
126
|
+
`);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should support css variables", async () => {
|
|
131
|
+
expect(
|
|
132
|
+
await cssloader.call(
|
|
133
|
+
loaderContext,
|
|
134
|
+
`
|
|
135
|
+
import styles from "./page.module.css";
|
|
136
|
+
import { css } from "next-yak";
|
|
137
|
+
|
|
138
|
+
const headline = css\`
|
|
139
|
+
&:hover {
|
|
140
|
+
color: \${x ? "red" : "blue"\};
|
|
141
|
+
}
|
|
142
|
+
\`;
|
|
143
|
+
`
|
|
144
|
+
)
|
|
145
|
+
).toMatchInlineSnapshot(`
|
|
146
|
+
".style0 {
|
|
147
|
+
&:hover {
|
|
148
|
+
color: var(--🦬18fi82j0);
|
|
149
|
+
}
|
|
150
|
+
}"
|
|
151
|
+
`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should support css variables with spaces", async () => {
|
|
155
|
+
expect(
|
|
156
|
+
await cssloader.call(
|
|
157
|
+
loaderContext,
|
|
158
|
+
`
|
|
159
|
+
import styles from "./page.module.css";
|
|
160
|
+
import { css } from "next-yak";
|
|
161
|
+
|
|
162
|
+
const headline = css\`
|
|
163
|
+
transition: color \${duration} \${easing};
|
|
164
|
+
display: block;
|
|
165
|
+
\${css\`color: orange\`}
|
|
166
|
+
\`;
|
|
167
|
+
`
|
|
168
|
+
)
|
|
169
|
+
).toMatchInlineSnapshot(`
|
|
170
|
+
".style0 {
|
|
171
|
+
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
172
|
+
display: block;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.style1 { color: orange }"
|
|
176
|
+
`);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should replace breakpoint references with actual media queries", async () => {
|
|
180
|
+
expect(
|
|
181
|
+
await cssloader.call(
|
|
182
|
+
loaderContext,
|
|
183
|
+
`
|
|
184
|
+
import { css } from "next-yak";
|
|
185
|
+
import { queries } from "@/theme";
|
|
186
|
+
|
|
187
|
+
const headline = css\`
|
|
188
|
+
color: blue;
|
|
189
|
+
\${queries.sm} {
|
|
190
|
+
color: red;
|
|
191
|
+
}
|
|
192
|
+
transition: color \${duration} \${easing};
|
|
193
|
+
display: block;
|
|
194
|
+
\${css\`color: orange\`}
|
|
195
|
+
\`;
|
|
196
|
+
`
|
|
197
|
+
)
|
|
198
|
+
).toMatchInlineSnapshot(`
|
|
199
|
+
".style0 {
|
|
200
|
+
color: blue;
|
|
201
|
+
@media (min-width: 640px) {
|
|
202
|
+
color: red;
|
|
203
|
+
}
|
|
204
|
+
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
205
|
+
display: block;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.style1 { color: orange }"
|
|
209
|
+
`);
|
|
210
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import tsloader from "../tsloader.cjs";
|
|
2
|
+
import {describe, it, expect} from "vitest";
|
|
3
|
+
|
|
4
|
+
const loaderContext = {
|
|
5
|
+
resourcePath: "/some/special/path/page.tsx",
|
|
6
|
+
rootContext: "/some",
|
|
7
|
+
importModule: () => {
|
|
8
|
+
return {
|
|
9
|
+
replaces: {
|
|
10
|
+
queries: {
|
|
11
|
+
sm: "@media (min-width: 640px)",
|
|
12
|
+
md: "@media (min-width: 768px)",
|
|
13
|
+
lg: "@media (min-width: 1024px)",
|
|
14
|
+
xl: "@media (min-width: 1280px)",
|
|
15
|
+
xxl: "@media (min-width: 1536px)",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
getOptions: () => ({
|
|
21
|
+
configPath: "/some/special/path/config",
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("tsloader", () => {
|
|
26
|
+
// snapshot
|
|
27
|
+
it("should return the correct value", async () => {
|
|
28
|
+
expect(
|
|
29
|
+
await tsloader.call(
|
|
30
|
+
loaderContext,
|
|
31
|
+
`
|
|
32
|
+
"use client";
|
|
33
|
+
import styles from "./page.module.css";
|
|
34
|
+
import { css } from "next-yak";
|
|
35
|
+
|
|
36
|
+
type x = number;
|
|
37
|
+
|
|
38
|
+
const headline = css\`
|
|
39
|
+
font-size: 2rem;
|
|
40
|
+
font-weight: bold;
|
|
41
|
+
color: blue;
|
|
42
|
+
&:hover {
|
|
43
|
+
color: red;
|
|
44
|
+
}
|
|
45
|
+
\`;
|
|
46
|
+
|
|
47
|
+
export const Main = () => <h1 className={headline({}).className}>Hello World</h1>;
|
|
48
|
+
`
|
|
49
|
+
)
|
|
50
|
+
).toMatchInlineSnapshot(`
|
|
51
|
+
"\\"use client\\";
|
|
52
|
+
|
|
53
|
+
import styles from \\"./page.module.css\\";
|
|
54
|
+
import { css } from \\"next-yak\\";
|
|
55
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
56
|
+
type x = number;
|
|
57
|
+
const headline = css(__styleYak.style0);
|
|
58
|
+
export const Main = () => <h1 className={headline({}).className}>Hello World</h1>;"
|
|
59
|
+
`);
|
|
60
|
+
});
|
|
61
|
+
it("should support nested css code", async () => {
|
|
62
|
+
expect(
|
|
63
|
+
await tsloader.call(
|
|
64
|
+
loaderContext,
|
|
65
|
+
`
|
|
66
|
+
import styles from "./page.module.css";
|
|
67
|
+
import { css } from "next-yak";
|
|
68
|
+
|
|
69
|
+
const x = Math.random();
|
|
70
|
+
const headline = css\`
|
|
71
|
+
font-size: 2rem;
|
|
72
|
+
font-weight: bold;
|
|
73
|
+
color: red;
|
|
74
|
+
\${x > 0.5 && css\`
|
|
75
|
+
color: blue;
|
|
76
|
+
\`}
|
|
77
|
+
&:hover {
|
|
78
|
+
color: red;
|
|
79
|
+
}
|
|
80
|
+
\`;
|
|
81
|
+
`
|
|
82
|
+
)
|
|
83
|
+
).toMatchInlineSnapshot(`
|
|
84
|
+
"import styles from \\"./page.module.css\\";
|
|
85
|
+
import { css } from \\"next-yak\\";
|
|
86
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
87
|
+
const x = Math.random();
|
|
88
|
+
const headline = css(__styleYak.style0, x > 0.5 && css(__styleYak.style1));"
|
|
89
|
+
`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should support styled api", async () => {
|
|
93
|
+
expect(
|
|
94
|
+
await tsloader.call(
|
|
95
|
+
loaderContext,
|
|
96
|
+
`
|
|
97
|
+
import styles from "./page.module.css";
|
|
98
|
+
import { styled, css } from "next-yak";
|
|
99
|
+
|
|
100
|
+
const x = Math.random();
|
|
101
|
+
const Button = styled.button\`
|
|
102
|
+
font-size: 2rem;
|
|
103
|
+
font-weight: bold;
|
|
104
|
+
color: red;
|
|
105
|
+
\${x > 0.5 && css\`
|
|
106
|
+
color: blue;
|
|
107
|
+
\`}
|
|
108
|
+
&:hover {
|
|
109
|
+
color: red;
|
|
110
|
+
}
|
|
111
|
+
\`;
|
|
112
|
+
const FancyButton = styled(Button)\`
|
|
113
|
+
background-color: green;
|
|
114
|
+
\`;
|
|
115
|
+
`
|
|
116
|
+
)
|
|
117
|
+
).toMatchInlineSnapshot(`
|
|
118
|
+
"import styles from \\"./page.module.css\\";
|
|
119
|
+
import { styled, css } from \\"next-yak\\";
|
|
120
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
121
|
+
const x = Math.random();
|
|
122
|
+
const Button = styled.button(__styleYak.style0, x > 0.5 && css(__styleYak.style1));
|
|
123
|
+
const FancyButton = styled(Button)(__styleYak.style2);"
|
|
124
|
+
`);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should support css variables with spaces", async () => {
|
|
129
|
+
expect(
|
|
130
|
+
await tsloader.call(
|
|
131
|
+
loaderContext,
|
|
132
|
+
`
|
|
133
|
+
import styles from "./page.module.css";
|
|
134
|
+
import { css } from "next-yak";
|
|
135
|
+
import { easing } from "styleguide";
|
|
136
|
+
|
|
137
|
+
const headline = css\`
|
|
138
|
+
transition: color \${({i}) => i * 100 + "ms"} \${easing};
|
|
139
|
+
display: block;
|
|
140
|
+
\${css\`color: orange\`}
|
|
141
|
+
\${css\`color: blue\`}
|
|
142
|
+
\`;
|
|
143
|
+
`
|
|
144
|
+
)
|
|
145
|
+
).toMatchInlineSnapshot(`
|
|
146
|
+
"import styles from \\"./page.module.css\\";
|
|
147
|
+
import { css } from \\"next-yak\\";
|
|
148
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
149
|
+
import { easing } from \\"styleguide\\";
|
|
150
|
+
const headline = css(__styleYak.style0, css(__styleYak.style1), css(__styleYak.style2), {
|
|
151
|
+
\\"style\\": {
|
|
152
|
+
\\"--\\\\uD83E\\\\uDDAC18fi82j0\\": ({
|
|
153
|
+
i
|
|
154
|
+
}) => i * 100 + \\"ms\\",
|
|
155
|
+
\\"--\\\\uD83E\\\\uDDAC18fi82j1\\": easing
|
|
156
|
+
}
|
|
157
|
+
});"
|
|
158
|
+
`);
|
|
159
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/// @ts-check
|
|
2
|
+
const babel = require("@babel/core");
|
|
3
|
+
const quasiClassifier = require("./lib/quasiClassifier.cjs");
|
|
4
|
+
const replaceQuasiExpressionTokens = require("./lib/replaceQuasiExpressionTokens.cjs");
|
|
5
|
+
const loadConfigOnce = require("./lib/loadConfigOnce.cjs");
|
|
6
|
+
const murmurhash2_32_gc = require("./lib/hash.cjs");
|
|
7
|
+
const { relative, resolve } = require("path");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} source
|
|
11
|
+
* @this {any}
|
|
12
|
+
* @returns {Promise<string>}
|
|
13
|
+
*/
|
|
14
|
+
module.exports = async function cssLoader(source) {
|
|
15
|
+
// Config for replacing tokens in css template literals
|
|
16
|
+
// can be based on a typescript file
|
|
17
|
+
const options = this.getOptions();
|
|
18
|
+
const config = options.configPath ? await loadConfigOnce(
|
|
19
|
+
async () => await this.importModule(resolve(this.rootContext, options.configPath))
|
|
20
|
+
) : {};
|
|
21
|
+
const replaces = config.replaces || {};
|
|
22
|
+
|
|
23
|
+
// parse source with babel
|
|
24
|
+
const ast = babel.parseSync(source, {
|
|
25
|
+
filename: this.resourcePath,
|
|
26
|
+
plugins: [
|
|
27
|
+
[
|
|
28
|
+
"@babel/plugin-syntax-typescript",
|
|
29
|
+
{ isTSX: this.resourcePath.endsWith(".tsx") },
|
|
30
|
+
],
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (ast === null) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { types: t } = babel;
|
|
39
|
+
|
|
40
|
+
/** @type {{css?: string, styled?: string}} */
|
|
41
|
+
const localVarNames = {
|
|
42
|
+
css: undefined,
|
|
43
|
+
styled: undefined,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let index = 0;
|
|
47
|
+
let varIndex = 0;
|
|
48
|
+
/** @type {string | null} */
|
|
49
|
+
let hashedFile = null;
|
|
50
|
+
const { rootContext, resourcePath } = this;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* find all css template literals in ast
|
|
54
|
+
* @type {{ code: string, loc: number }[]}
|
|
55
|
+
*/
|
|
56
|
+
const cssCode = [];
|
|
57
|
+
babel.traverse(ast, {
|
|
58
|
+
/**
|
|
59
|
+
* @param {import("@babel/traverse").NodePath<import("@babel/types").ImportDeclaration>} path
|
|
60
|
+
*/
|
|
61
|
+
ImportDeclaration(path) {
|
|
62
|
+
const node = path.node;
|
|
63
|
+
if (
|
|
64
|
+
node.source.value !== "next-yak"
|
|
65
|
+
) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Process import specifiers
|
|
69
|
+
node.specifiers.forEach((specifier) => {
|
|
70
|
+
if (
|
|
71
|
+
!("imported" in specifier) ||
|
|
72
|
+
!specifier.imported ||
|
|
73
|
+
!t.isIdentifier(specifier.imported)
|
|
74
|
+
) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const importSpecifier = /** @type {babel.types.Identifier} */ (
|
|
79
|
+
specifier.imported
|
|
80
|
+
);
|
|
81
|
+
const localSpecifier = specifier.local || importSpecifier;
|
|
82
|
+
if (
|
|
83
|
+
importSpecifier.name === "styled" ||
|
|
84
|
+
importSpecifier.name === "css"
|
|
85
|
+
) {
|
|
86
|
+
localVarNames[importSpecifier.name] = localSpecifier.name;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* @param {import("@babel/traverse").NodePath<import("@babel/types").TaggedTemplateExpression>} path
|
|
92
|
+
*/
|
|
93
|
+
TaggedTemplateExpression(path) {
|
|
94
|
+
// Check if the tag name matches the imported 'css' or 'styled' variable
|
|
95
|
+
const tag = path.node.tag;
|
|
96
|
+
|
|
97
|
+
const isCssLiteral =
|
|
98
|
+
t.isIdentifier(tag) &&
|
|
99
|
+
/** @type {babel.types.Identifier} */ (tag).name === localVarNames.css;
|
|
100
|
+
const isStyledLiteral =
|
|
101
|
+
t.isMemberExpression(tag) &&
|
|
102
|
+
t.isIdentifier(
|
|
103
|
+
/** @type {babel.types.MemberExpression} */ (tag).object
|
|
104
|
+
) &&
|
|
105
|
+
/** @type {babel.types.Identifier} */ (
|
|
106
|
+
/** @type {babel.types.MemberExpression} */ (tag).object
|
|
107
|
+
).name === localVarNames.styled;
|
|
108
|
+
|
|
109
|
+
const isStyledCall =
|
|
110
|
+
t.isCallExpression(tag) &&
|
|
111
|
+
t.isIdentifier(
|
|
112
|
+
/** @type {babel.types.CallExpression} */ (tag).callee
|
|
113
|
+
) &&
|
|
114
|
+
/** @type {babel.types.Identifier} */ (
|
|
115
|
+
/** @type {babel.types.CallExpression} */ (tag).callee
|
|
116
|
+
).name === localVarNames.styled;
|
|
117
|
+
|
|
118
|
+
if (!isCssLiteral && !isStyledLiteral && !isStyledCall) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
replaceQuasiExpressionTokens(path.node.quasi, replaces, t);
|
|
123
|
+
|
|
124
|
+
// Keep the same selector for all quasis belonging to the same css block
|
|
125
|
+
const literalSelector = `.style${index++}`;
|
|
126
|
+
|
|
127
|
+
// Replace the tagged template expression with a call to the 'styled' function
|
|
128
|
+
const quasis = path.node.quasi.quasis;
|
|
129
|
+
const quasiTypes = quasis.map((quasi) =>
|
|
130
|
+
quasiClassifier(quasi.value.raw)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < quasis.length; i++) {
|
|
134
|
+
const quasi = quasis[i];
|
|
135
|
+
// skip empty quasis
|
|
136
|
+
if (quasiTypes[i].empty) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
let code = quasi.value.raw;
|
|
140
|
+
let isMerging = false;
|
|
141
|
+
// loop over all quasis belonging to the same css block
|
|
142
|
+
while (i < quasis.length - 1) {
|
|
143
|
+
const type = quasiTypes[i];
|
|
144
|
+
// expressions after a partial css are converted into css variables
|
|
145
|
+
if (
|
|
146
|
+
type.partialStart ||
|
|
147
|
+
type.partialEnd ||
|
|
148
|
+
(isMerging && type.empty)
|
|
149
|
+
) {
|
|
150
|
+
isMerging = true;
|
|
151
|
+
if (!hashedFile) {
|
|
152
|
+
const relativePath = relative(rootContext, resourcePath);
|
|
153
|
+
hashedFile = murmurhash2_32_gc(relativePath);
|
|
154
|
+
}
|
|
155
|
+
// replace the expression with a css variable
|
|
156
|
+
code += `var(--🦬${hashedFile}${varIndex++})`;
|
|
157
|
+
// as we are after the css block, we need to increment i
|
|
158
|
+
// to get the very next quasi
|
|
159
|
+
i++;
|
|
160
|
+
code += quasis[i].value.raw;
|
|
161
|
+
} else if (type.empty) {
|
|
162
|
+
// empty quasis are also added to keep spacings
|
|
163
|
+
// e.g. `transition: color ${duration} ${easing};`
|
|
164
|
+
i++;
|
|
165
|
+
code += quasis[i].value.raw;
|
|
166
|
+
} else {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
cssCode.push({
|
|
172
|
+
code: `${literalSelector} { ${code} }`,
|
|
173
|
+
loc: quasi.loc?.start.line || 0,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// sort by loc
|
|
180
|
+
cssCode.sort((a, b) => a.loc - b.loc);
|
|
181
|
+
|
|
182
|
+
return cssCode.map((code) => code.code).join("\n\n");
|
|
183
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS Implementation of MurmurHash2
|
|
3
|
+
*
|
|
4
|
+
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
|
|
5
|
+
* @see http://github.com/garycourt/murmurhash-js
|
|
6
|
+
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
|
|
7
|
+
* @see http://sites.google.com/site/murmurhash/
|
|
8
|
+
*
|
|
9
|
+
* @param {string} str ASCII only
|
|
10
|
+
* @return {string} Base 36 encoded hash result
|
|
11
|
+
*/
|
|
12
|
+
function murmurhash2_32_gc(str) {
|
|
13
|
+
let l = str.length;
|
|
14
|
+
let h = l;
|
|
15
|
+
let i = 0;
|
|
16
|
+
let k;
|
|
17
|
+
|
|
18
|
+
while (l >= 4) {
|
|
19
|
+
k =
|
|
20
|
+
(str.charCodeAt(i) & 0xff) |
|
|
21
|
+
((str.charCodeAt(++i) & 0xff) << 8) |
|
|
22
|
+
((str.charCodeAt(++i) & 0xff) << 16) |
|
|
23
|
+
((str.charCodeAt(++i) & 0xff) << 24);
|
|
24
|
+
|
|
25
|
+
k =
|
|
26
|
+
(k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
|
|
27
|
+
k ^= k >>> 24;
|
|
28
|
+
k =
|
|
29
|
+
(k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
|
|
30
|
+
|
|
31
|
+
h =
|
|
32
|
+
((h & 0xffff) * 0x5bd1e995 +
|
|
33
|
+
((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^
|
|
34
|
+
k;
|
|
35
|
+
|
|
36
|
+
l -= 4;
|
|
37
|
+
++i;
|
|
38
|
+
} // forgive existing code
|
|
39
|
+
|
|
40
|
+
/* eslint-disable no-fallthrough */ switch (l) {
|
|
41
|
+
case 3:
|
|
42
|
+
h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
|
|
43
|
+
case 2:
|
|
44
|
+
h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
|
|
45
|
+
case 1:
|
|
46
|
+
h ^= str.charCodeAt(i) & 0xff;
|
|
47
|
+
h =
|
|
48
|
+
(h & 0xffff) * 0x5bd1e995 +
|
|
49
|
+
((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
|
|
50
|
+
}
|
|
51
|
+
/* eslint-enable no-fallthrough */
|
|
52
|
+
|
|
53
|
+
h ^= h >>> 13;
|
|
54
|
+
h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
|
|
55
|
+
h ^= h >>> 15;
|
|
56
|
+
|
|
57
|
+
return (h >>> 0).toString(36);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = murmurhash2_32_gc;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {Promise<{ replaces?: Record<string, Record<string, string>> }>}
|
|
5
|
+
*/
|
|
6
|
+
let cache;
|
|
7
|
+
module.exports = function loadConfigOnce(loader) {
|
|
8
|
+
const config = cache || loader().catch((e) => {
|
|
9
|
+
console.error("Failed to load yak config:", e);
|
|
10
|
+
return {};
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
if (!cache) {
|
|
14
|
+
cache = config;
|
|
15
|
+
}
|
|
16
|
+
return config;
|
|
17
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// @ts-check
|
|
2
|
+
const stripCssComments = require("./stripCssComments.cjs");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks a quasiValue and returns its type
|
|
6
|
+
*
|
|
7
|
+
* - empty: no expressions, no text
|
|
8
|
+
* - partialStart: starts with a `{`
|
|
9
|
+
* - partialEnd: does not end with a `}` or `;`
|
|
10
|
+
*
|
|
11
|
+
* @param {string} quasiValue
|
|
12
|
+
* @returns {{
|
|
13
|
+
* empty: boolean,
|
|
14
|
+
* partialStart: boolean,
|
|
15
|
+
* partialEnd: boolean,
|
|
16
|
+
* }}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = function quasiClassifier(quasiValue) {
|
|
19
|
+
const trimmed = stripCssComments(quasiValue).trim();
|
|
20
|
+
if (trimmed === "") {
|
|
21
|
+
return {
|
|
22
|
+
empty: true,
|
|
23
|
+
partialStart: false,
|
|
24
|
+
partialEnd: false,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
empty: false,
|
|
30
|
+
partialStart: trimmed.startsWith("{"),
|
|
31
|
+
partialEnd: !trimmed.endsWith("}") && !trimmed.endsWith(";"),
|
|
32
|
+
}
|
|
33
|
+
}
|