@yak/react 0.0.0-stage → 10.0.0-rc.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/LICENSE +21 -0
- package/README.md +114 -2
- package/dist/context/baseContext.d.ts +5 -0
- package/dist/context/baseContext.js +8 -0
- package/dist/context/baseContext.js.map +1 -0
- package/dist/context/index.d.ts +20 -0
- package/dist/context/index.js +15 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/index.server.d.ts +9 -0
- package/dist/context/index.server.js +17 -0
- package/dist/context/index.server.js.map +1 -0
- package/dist/cross-file-resolver/index.d.ts +199 -0
- package/dist/cross-file-resolver/index.js +569 -0
- package/dist/cross-file-resolver/index.js.map +1 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +452 -0
- package/dist/internal.js +363 -0
- package/dist/internal.js.map +1 -0
- package/dist/isolated-source-eval/index.d.ts +30 -0
- package/dist/isolated-source-eval/index.js +317 -0
- package/dist/isolated-source-eval/index.js.map +1 -0
- package/dist/isolated-source-eval/worker.js +59 -0
- package/dist/isolated-source-eval/worker.js.map +1 -0
- package/dist/jsx-dev-runtime.d.ts +7 -0
- package/dist/jsx-dev-runtime.js +2 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime.d.ts +38 -0
- package/dist/jsx-runtime.js +2 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/loaders/turbo-loader.d.ts +15 -0
- package/dist/loaders/turbo-loader.js +816 -0
- package/dist/loaders/turbo-loader.js.map +1 -0
- package/dist/loaders/vite-plugin.d.ts +103 -0
- package/dist/loaders/vite-plugin.js +885 -0
- package/dist/loaders/vite-plugin.js.map +1 -0
- package/dist/loaders/webpack-loader.d.ts +12 -0
- package/dist/loaders/webpack-loader.js +710 -0
- package/dist/loaders/webpack-loader.js.map +1 -0
- package/dist/rsbuild/index.d.ts +29 -0
- package/dist/rsbuild/index.js +51 -0
- package/dist/rsbuild/index.js.map +1 -0
- package/dist/static/chunk-DK3Fl9T5.js +1 -0
- package/dist/static/index.d.ts +28 -0
- package/dist/static/index.js +2 -0
- package/dist/static/index.js.map +1 -0
- package/dist/withYak/index.d.ts +112 -0
- package/dist/withYak/index.js +171 -0
- package/dist/withYak/index.js.map +1 -0
- package/loaders/lib/resolveCrossFileSelectors.ts +161 -0
- package/loaders/turbo-evaluator.ts +134 -0
- package/loaders/turbo-loader.ts +184 -0
- package/loaders/vite-plugin.ts +28 -0
- package/loaders/webpack-loader.ts +41 -0
- package/package.json +155 -3
- package/rsbuild/index.ts +91 -0
- package/runtime/__tests__/atoms.test.tsx +48 -0
- package/runtime/__tests__/attrs.test.tsx +490 -0
- package/runtime/__tests__/cssLiteral.test.tsx +503 -0
- package/runtime/__tests__/cssProp.test.tsx +392 -0
- package/runtime/__tests__/cssPropTest.tsx +227 -0
- package/runtime/__tests__/foldedJsxParity.test.tsx +117 -0
- package/runtime/__tests__/styled.test.tsx +653 -0
- package/runtime/__tests__/tsconfig.json +18 -0
- package/runtime/__tests__/typeTest.tsx +374 -0
- package/runtime/atoms.tsx +36 -0
- package/runtime/context/README.md +84 -0
- package/runtime/context/baseContext.tsx +5 -0
- package/runtime/context/index.server.tsx +22 -0
- package/runtime/context/index.tsx +36 -0
- package/runtime/cssLiteral.tsx +214 -0
- package/runtime/globalStyle.tsx +54 -0
- package/runtime/index.ts +37 -0
- package/runtime/internal.ts +42 -0
- package/runtime/internals/mergeClassNames.ts +23 -0
- package/runtime/internals/mergeCssProp.ts +69 -0
- package/runtime/internals/propMarkers.ts +20 -0
- package/runtime/internals/unitPostFix.ts +34 -0
- package/runtime/jsx-dev-runtime.ts +6 -0
- package/runtime/jsx-runtime.ts +48 -0
- package/runtime/keyframes.tsx +30 -0
- package/runtime/mocks/cssLiteral.ts +74 -0
- package/runtime/mocks/globalStyle.ts +14 -0
- package/runtime/mocks/keyframes.ts +29 -0
- package/runtime/mocks/styled.ts +8 -0
- package/runtime/publicStyledApi.ts +159 -0
- package/runtime/styled.tsx +372 -0
- package/runtime/styledDom.tsx +139 -0
- package/withYak/allow-global-css.ts +94 -0
- package/withYak/index.ts +268 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Compilation, LoaderContext } from "webpack";
|
|
2
|
+
import {
|
|
3
|
+
ParseContext,
|
|
4
|
+
ParsedModule,
|
|
5
|
+
parseModule,
|
|
6
|
+
} from "yak-internals/cross-file-resolver/parse-module";
|
|
7
|
+
import {
|
|
8
|
+
ResolveContext,
|
|
9
|
+
resolveCrossFileConstant as genericResolveCrossFileConstant,
|
|
10
|
+
} from "yak-internals/cross-file-resolver/resolve";
|
|
11
|
+
import { parseExports } from "yak-internals/parse-exports";
|
|
12
|
+
import { YakConfigOptions } from "yak-internals/config";
|
|
13
|
+
|
|
14
|
+
export { parseExports };
|
|
15
|
+
|
|
16
|
+
const compilationCache = new WeakMap<
|
|
17
|
+
Compilation,
|
|
18
|
+
{
|
|
19
|
+
parsedFiles: Map<string, ParsedModule>;
|
|
20
|
+
}
|
|
21
|
+
>();
|
|
22
|
+
|
|
23
|
+
export async function resolveCrossFileConstant(
|
|
24
|
+
loader: LoaderContext<{}>,
|
|
25
|
+
pathContext: string,
|
|
26
|
+
css: string,
|
|
27
|
+
): Promise<string> {
|
|
28
|
+
const { resolved } = await genericResolveCrossFileConstant(
|
|
29
|
+
getResolveContext(loader),
|
|
30
|
+
loader.resourcePath,
|
|
31
|
+
css,
|
|
32
|
+
);
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getCompilationCache(loader: LoaderContext<YakConfigOptions>) {
|
|
37
|
+
const compilation = loader._compilation;
|
|
38
|
+
if (!compilation) {
|
|
39
|
+
throw new Error("Webpack compilation object not available");
|
|
40
|
+
}
|
|
41
|
+
let cache = compilationCache.get(compilation);
|
|
42
|
+
if (!cache) {
|
|
43
|
+
cache = {
|
|
44
|
+
parsedFiles: new Map(),
|
|
45
|
+
};
|
|
46
|
+
compilationCache.set(compilation, cache);
|
|
47
|
+
}
|
|
48
|
+
return cache;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getParseContext(loader: LoaderContext<YakConfigOptions>): ParseContext {
|
|
52
|
+
return {
|
|
53
|
+
cache: { parse: getCompilationCache(loader).parsedFiles },
|
|
54
|
+
async extractExports(modulePath) {
|
|
55
|
+
const sourceContents = new Promise<string>((resolve, reject) =>
|
|
56
|
+
loader.fs.readFile(modulePath, "utf-8", (err, result) => {
|
|
57
|
+
if (err) return reject(err);
|
|
58
|
+
resolve(result || "");
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
return parseExports(await sourceContents);
|
|
62
|
+
},
|
|
63
|
+
async getTransformed(modulePath) {
|
|
64
|
+
const transformedSource = new Promise<string>((resolve, reject) => {
|
|
65
|
+
loader.loadModule(modulePath, (err, source) => {
|
|
66
|
+
if (err) {
|
|
67
|
+
// When webpack reports "The loaded module contains errors",
|
|
68
|
+
// the actual errors are stored on the module in the compilation.
|
|
69
|
+
// Extract and report the real errors for better debugging.
|
|
70
|
+
const compilation = loader._compilation;
|
|
71
|
+
if (compilation) {
|
|
72
|
+
try {
|
|
73
|
+
for (const mod of compilation.modules) {
|
|
74
|
+
if ("resource" in mod && mod.resource === modulePath) {
|
|
75
|
+
const errors = mod.getErrors();
|
|
76
|
+
if (errors) {
|
|
77
|
+
const messages = Array.from(errors)
|
|
78
|
+
.map((e) => e.message)
|
|
79
|
+
.filter(Boolean);
|
|
80
|
+
if (messages.length > 0) {
|
|
81
|
+
return reject(new Error(messages.join("\n")));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// Ignore errors while trying to extract module errors
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return reject(err);
|
|
91
|
+
}
|
|
92
|
+
let sourceString: string;
|
|
93
|
+
if (typeof source === "string") {
|
|
94
|
+
sourceString = source;
|
|
95
|
+
} else if (source instanceof Buffer) {
|
|
96
|
+
sourceString = source.toString("utf-8");
|
|
97
|
+
} else if (source instanceof ArrayBuffer) {
|
|
98
|
+
sourceString = new TextDecoder("utf-8").decode(source);
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error("Invalid input type: code must be string, Buffer, or ArrayBuffer");
|
|
101
|
+
}
|
|
102
|
+
resolve(sourceString || "");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
return { code: await transformedSource };
|
|
106
|
+
},
|
|
107
|
+
async evaluateYakModule(modulePath) {
|
|
108
|
+
return loader.importModule(modulePath);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getResolveContext(loader: LoaderContext<YakConfigOptions>): ResolveContext {
|
|
114
|
+
const parseContext = getParseContext(loader);
|
|
115
|
+
return {
|
|
116
|
+
parse: (modulePath) => parseModule(parseContext, modulePath),
|
|
117
|
+
resolve: async (specifier, importer) => {
|
|
118
|
+
return resolveModule(loader, specifier, dirname(importer));
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Resolves a module by wrapping loader.resolve in a promise
|
|
125
|
+
*/
|
|
126
|
+
export async function resolveModule(
|
|
127
|
+
loader: LoaderContext<{}>,
|
|
128
|
+
moduleSpecifier: string,
|
|
129
|
+
context: string,
|
|
130
|
+
): Promise<string> {
|
|
131
|
+
return new Promise<string>((resolve, reject) => {
|
|
132
|
+
loader.resolve(context, moduleSpecifier, (err, result) => {
|
|
133
|
+
if (err) return reject(err);
|
|
134
|
+
if (!result) return reject(new Error(`Could not resolve ${moduleSpecifier}`));
|
|
135
|
+
resolve(result);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const DIRNAME_POSIX_REGEX =
|
|
141
|
+
/^((?:\.(?![^/]))|(?:(?:\/?|)(?:[\s\S]*?)))(?:\/+?|)(?:(?:\.{1,2}|[^/]+?|)(?:\.[^./]*|))(?:[/]*)$/;
|
|
142
|
+
const DIRNAME_WIN32_REGEX =
|
|
143
|
+
/^((?:\.(?![^\\]))|(?:(?:\\?|)(?:[\s\S]*?)))(?:\\+?|)(?:(?:\.{1,2}|[^\\]+?|)(?:\.[^.\\]*|))(?:[\\]*)$/;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Polyfill for `node:path` method dirname.
|
|
147
|
+
* Keeps yak independent from node api (therefore executable in browser)
|
|
148
|
+
*/
|
|
149
|
+
function dirname(path: string) {
|
|
150
|
+
let dirname = DIRNAME_POSIX_REGEX.exec(path)?.[1];
|
|
151
|
+
|
|
152
|
+
if (!dirname) {
|
|
153
|
+
dirname = DIRNAME_WIN32_REGEX.exec(path)?.[1];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!dirname) {
|
|
157
|
+
throw new Error(`Can't extract dirname from ${path}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return dirname;
|
|
161
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turbopack evaluator adapter.
|
|
3
|
+
*
|
|
4
|
+
* Bridges the low-level `Evaluator` (worker-thread module evaluator) and
|
|
5
|
+
* turbopack's loader lifecycle. A single `Evaluator` instance is kept as a
|
|
6
|
+
* singleton on `globalThis` because turbopack may create multiple loader
|
|
7
|
+
* instances that share the same process — `Symbol.for` keys guarantee all
|
|
8
|
+
* instances converge on the same evaluator and modification time map.
|
|
9
|
+
*
|
|
10
|
+
* Invalidation uses synchronous modification time polling rather than `fs.watch` because
|
|
11
|
+
* turbopack can re-run a loader before a filesystem watcher event fires,
|
|
12
|
+
* which would cause the evaluator to return stale cached results.
|
|
13
|
+
*/
|
|
14
|
+
import { statSync } from "node:fs";
|
|
15
|
+
import type { Evaluator } from "../isolated-source-eval/index.js";
|
|
16
|
+
|
|
17
|
+
const EVALUATOR_KEY = Symbol.for("@yak/react-evaluator");
|
|
18
|
+
const MTIMES_KEY = Symbol.for("@yak/react-mtimes");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A shared `Map` of file paths to their last known modification times, used to detect changes on disk
|
|
22
|
+
*
|
|
23
|
+
* e.g. bar.ts -> 1680000000000 (Sat Mar 28 2024 12:00:00 GMT+0000)
|
|
24
|
+
*
|
|
25
|
+
* fs.fileWatch is not used as it might be out of sync with turbopacks own file watching also turbopack
|
|
26
|
+
* spawns a loader pool which could cause a lot of file watchers to be created which can cause performance issues and hitting OS limits
|
|
27
|
+
*/
|
|
28
|
+
const getFileModificationTimes = () =>
|
|
29
|
+
((globalThis as typeof globalThis & { [MTIMES_KEY]?: Map<string, number> })[MTIMES_KEY] ??=
|
|
30
|
+
new Map<string, number>());
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns the shared evaluator, creating it on first call.
|
|
34
|
+
*
|
|
35
|
+
* Uses `Symbol.for` keys on `globalThis` so every turbopack loader instance
|
|
36
|
+
* in the process converges on the same evaluator and modification time map. A
|
|
37
|
+
* `beforeExit` handler terminates the worker threads when the process winds
|
|
38
|
+
* down.
|
|
39
|
+
*/
|
|
40
|
+
export const getEvaluatorSingleton = async (): Promise<Evaluator> => {
|
|
41
|
+
const processGlobal = globalThis as typeof globalThis & {
|
|
42
|
+
[EVALUATOR_KEY]?: Promise<Evaluator>;
|
|
43
|
+
};
|
|
44
|
+
if (!processGlobal[EVALUATOR_KEY]) {
|
|
45
|
+
processGlobal[EVALUATOR_KEY] = import("../isolated-source-eval/index.js").then(
|
|
46
|
+
({ createEvaluator }) => createEvaluator(),
|
|
47
|
+
);
|
|
48
|
+
// Kill the worker threads when the process exits
|
|
49
|
+
process.on("beforeExit", async () => {
|
|
50
|
+
const evaluator = await processGlobal[EVALUATOR_KEY]!;
|
|
51
|
+
await evaluator.dispose();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return processGlobal[EVALUATOR_KEY]!;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Turbopack runs the loader for the same compilation file-by-file, but multiple files can share the
|
|
59
|
+
* same dependency. This function detects which dependencies actually changed
|
|
60
|
+
* on disk and invalidates them once — this prevents re-evaluating the same module multiple times in the same compilation
|
|
61
|
+
*/
|
|
62
|
+
function invalidateDependenciesWithDiskModifications(evaluator: Evaluator): void {
|
|
63
|
+
const modificationTimes = getFileModificationTimes();
|
|
64
|
+
const modifiedDependencies: string[] = [];
|
|
65
|
+
for (const [dep, lastModificationTime] of modificationTimes) {
|
|
66
|
+
try {
|
|
67
|
+
const currentModificationTime = statSync(dep).mtimeMs;
|
|
68
|
+
if (currentModificationTime !== lastModificationTime) {
|
|
69
|
+
modifiedDependencies.push(dep);
|
|
70
|
+
modificationTimes.set(dep, currentModificationTime);
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
modifiedDependencies.push(dep);
|
|
74
|
+
modificationTimes.delete(dep);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (modifiedDependencies.length > 0) {
|
|
78
|
+
evaluator.invalidate(...modifiedDependencies);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Snapshots the current modification times of `deps` so the next evaluation can detect changes. */
|
|
83
|
+
function recordModificationTimes(deps: string[]): void {
|
|
84
|
+
const modificationTimes = getFileModificationTimes();
|
|
85
|
+
for (const dep of deps) {
|
|
86
|
+
try {
|
|
87
|
+
modificationTimes.set(dep, statSync(dep).mtimeMs);
|
|
88
|
+
} catch {
|
|
89
|
+
/* file may not exist */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates an evaluator function scoped to a single loader invocation.
|
|
96
|
+
*
|
|
97
|
+
* Before evaluation, checks whether any previously recorded dependency has
|
|
98
|
+
* changed on disk and invalidates the evaluator cache if so. After a
|
|
99
|
+
* successful evaluation, records fresh modification times and notifies the caller of
|
|
100
|
+
* every transitive dependency via `onDependency`.
|
|
101
|
+
*
|
|
102
|
+
* @returns A function that evaluates a yak module and returns its exports.
|
|
103
|
+
* The returned function accepts:
|
|
104
|
+
* - `modulePath` — absolute path to the module to evaluate.
|
|
105
|
+
* - `onDependency` — called once per transitive dependency so the loader
|
|
106
|
+
* can register file watchers with turbopack.
|
|
107
|
+
* @throws If the evaluated module throws at runtime — the original stack
|
|
108
|
+
* trace from the worker thread is preserved.
|
|
109
|
+
*/
|
|
110
|
+
export async function createCompilationEvaluator(): Promise<
|
|
111
|
+
(modulePath: string, onDependency?: (dep: string) => void) => Promise<Record<string, unknown>>
|
|
112
|
+
> {
|
|
113
|
+
const evaluator = await getEvaluatorSingleton();
|
|
114
|
+
invalidateDependenciesWithDiskModifications(evaluator);
|
|
115
|
+
|
|
116
|
+
return async (modulePath, onDependency?) => {
|
|
117
|
+
const result = await evaluator.evaluate(modulePath);
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
const error = new Error(result.error.message);
|
|
120
|
+
if (result.error.stack) {
|
|
121
|
+
error.stack = result.error.stack;
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
// Store times of newly discovered dependencies for next compilation
|
|
126
|
+
recordModificationTimes(result.dependencies);
|
|
127
|
+
if (onDependency) {
|
|
128
|
+
for (const dep of result.dependencies) {
|
|
129
|
+
onDependency(dep);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result.value;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { transform as swcTransform } from "@swc/core";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import type { LoaderContext } from "webpack";
|
|
5
|
+
import { parseModule } from "yak-internals/cross-file-resolver/parse-module";
|
|
6
|
+
import { resolveCrossFileConstant } from "yak-internals/cross-file-resolver/resolve";
|
|
7
|
+
import type { YakConfigOptions } from "../withYak/index.js";
|
|
8
|
+
import { createDebugLogger } from "yak-internals/debug-logger";
|
|
9
|
+
import { extractCss } from "yak-internals/extract-css";
|
|
10
|
+
import { parseExports } from "yak-internals/parse-exports";
|
|
11
|
+
import { getSwcParserOptions } from "yak-internals/swc-parser-options";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const yakSwcPluginPath = require.resolve("@yak/swc");
|
|
15
|
+
const dataUrlImportPrefix = 'import "data:text/css;base64,';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* This loader transforms styled-components styles to a static data-url import
|
|
19
|
+
* The compile-time nexy-yak transformation takes javascript/typescript as input,
|
|
20
|
+
* strips all inline css code and adds the css as static css urls
|
|
21
|
+
* e.g.: `import "data:text/css;base64,"`
|
|
22
|
+
*/
|
|
23
|
+
export default async function cssExtractLoader(
|
|
24
|
+
this: LoaderContext<{ yakOptions: YakConfigOptions; yakPluginOptions: any }>,
|
|
25
|
+
code: string,
|
|
26
|
+
sourceMap: string | undefined,
|
|
27
|
+
): Promise<string | void> {
|
|
28
|
+
const callback = this.async();
|
|
29
|
+
|
|
30
|
+
// process only files which include @yak/react for maximal compile performance
|
|
31
|
+
if (!code.includes("@yak/react")) {
|
|
32
|
+
return callback(null, code, sourceMap);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
yakPluginOptions,
|
|
37
|
+
yakOptions: { debug },
|
|
38
|
+
} = this.getOptions();
|
|
39
|
+
const debugLog = createDebugLogger(debug, this.rootContext);
|
|
40
|
+
const resolveTurbopack = this.getResolve({});
|
|
41
|
+
const transform = createTransform(yakPluginOptions, yakSwcPluginPath);
|
|
42
|
+
|
|
43
|
+
const resolveFn = (specifier: string, importer: string) => {
|
|
44
|
+
return new Promise<string>((resolve, reject) => {
|
|
45
|
+
resolveTurbopack(dirname(importer), specifier, (err, result) => {
|
|
46
|
+
if (err) return reject(err);
|
|
47
|
+
if (!result) return reject(new Error(`Could not resolve ${specifier}`));
|
|
48
|
+
resolve(result);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const crossFileDeps = new Set<string>();
|
|
54
|
+
let evaluate:
|
|
55
|
+
| Awaited<ReturnType<typeof import("./turbo-evaluator.js").createCompilationEvaluator>>
|
|
56
|
+
| undefined;
|
|
57
|
+
const fsReadFile = (filePath: string) => {
|
|
58
|
+
crossFileDeps.add(filePath);
|
|
59
|
+
return new Promise<string>((resolve, reject) =>
|
|
60
|
+
this.fs.readFile(filePath, "utf-8", (err, result) => {
|
|
61
|
+
if (err) return reject(err);
|
|
62
|
+
if (!result) return reject(new Error(`File not found: ${filePath}`));
|
|
63
|
+
resolve(result);
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await transform(code, this.resourcePath, this.rootContext, sourceMap);
|
|
70
|
+
debugLog("ts", result.code, this.resourcePath);
|
|
71
|
+
|
|
72
|
+
let css = extractCss(result.code);
|
|
73
|
+
debugLog("css", css, this.resourcePath);
|
|
74
|
+
|
|
75
|
+
const { resolved } = await resolveCrossFileConstant(
|
|
76
|
+
{
|
|
77
|
+
parse: (modulePath) => {
|
|
78
|
+
return parseModule(
|
|
79
|
+
{
|
|
80
|
+
extractExports: async (modulePath) => {
|
|
81
|
+
const sourceContents = await fsReadFile(modulePath);
|
|
82
|
+
return parseExports(sourceContents);
|
|
83
|
+
},
|
|
84
|
+
getTransformed: async (modulePath) => {
|
|
85
|
+
const sourceContent = await fsReadFile(modulePath);
|
|
86
|
+
return transform(sourceContent, modulePath, this.rootContext);
|
|
87
|
+
},
|
|
88
|
+
evaluateYakModule: async (modulePath: string) => {
|
|
89
|
+
crossFileDeps.add(modulePath);
|
|
90
|
+
/*
|
|
91
|
+
* Turbopack doesn't let us know when a compilation start so by using a singleton evaluator we
|
|
92
|
+
* we can at least ensture that we scan for file modifications only once per loader call
|
|
93
|
+
*/
|
|
94
|
+
evaluate ??= await (
|
|
95
|
+
await import("./turbo-evaluator.js")
|
|
96
|
+
).createCompilationEvaluator();
|
|
97
|
+
return evaluate(modulePath, (dep) => crossFileDeps.add(dep));
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
modulePath,
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
resolve: resolveFn,
|
|
104
|
+
},
|
|
105
|
+
this.resourcePath,
|
|
106
|
+
css,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Register cross-file dependencies so turbopack re-runs this loader
|
|
110
|
+
// when any dependency changes (analogous to webpack's this.addDependency)
|
|
111
|
+
for (const dep of crossFileDeps) {
|
|
112
|
+
this.addDependency(dep);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const codeWithCrossFileResolved = replaceDataUrlImport(result.code, resolved);
|
|
116
|
+
|
|
117
|
+
debugLog("css-resolved", resolved, this.resourcePath);
|
|
118
|
+
// swc returns a JSON string, which babel-loader rejects as inputSourceMap
|
|
119
|
+
return callback(
|
|
120
|
+
null,
|
|
121
|
+
codeWithCrossFileResolved,
|
|
122
|
+
result.map ? JSON.parse(result.map) : undefined,
|
|
123
|
+
);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Register cross-file dependencies even on error so turbopack re-runs
|
|
126
|
+
// this loader when a broken dependency is fixed.
|
|
127
|
+
for (const dep of crossFileDeps) {
|
|
128
|
+
this.addDependency(dep);
|
|
129
|
+
}
|
|
130
|
+
return callback(error instanceof Error ? error : new Error(String(error)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Swap the data-url import the SWC plugin emitted for one carrying the resolved css.
|
|
136
|
+
*
|
|
137
|
+
* Modules which import @yak/react without declaring styles get no such import and are
|
|
138
|
+
* returned unchanged. The import is matched from the start of the line so that source
|
|
139
|
+
* code merely mentioning a data url cannot be mistaken for it.
|
|
140
|
+
*/
|
|
141
|
+
function replaceDataUrlImport(code: string, resolvedCss: string): string {
|
|
142
|
+
const lines = code.split("\n");
|
|
143
|
+
const lineIndex = lines.findIndex((line) => line.trimStart().startsWith(dataUrlImportPrefix));
|
|
144
|
+
if (lineIndex === -1) {
|
|
145
|
+
return code;
|
|
146
|
+
}
|
|
147
|
+
lines[lineIndex] = `${dataUrlImportPrefix}${Buffer.from(resolvedCss).toString("base64")}";`;
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createTransform(yakPluginOptions: any, yakSwcPluginPath: string) {
|
|
152
|
+
return (data: string, modulePath: string, rootPath: string, sourceMap?: any) =>
|
|
153
|
+
// https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/loaders/next-swc-loader.ts#L143
|
|
154
|
+
swcTransform(data, {
|
|
155
|
+
filename: modulePath,
|
|
156
|
+
inputSourceMap: sourceMap,
|
|
157
|
+
sourceMaps: true,
|
|
158
|
+
sourceFileName: modulePath,
|
|
159
|
+
sourceRoot: rootPath,
|
|
160
|
+
jsc: {
|
|
161
|
+
// Derive the parser from the file name the same way Next.js does
|
|
162
|
+
parser: getSwcParserOptions(modulePath),
|
|
163
|
+
experimental: {
|
|
164
|
+
// Keep `import data from "./data.json" with { type: "json" }` intact
|
|
165
|
+
keepImportAttributes: true,
|
|
166
|
+
plugins: [[yakSwcPluginPath, yakPluginOptions]],
|
|
167
|
+
},
|
|
168
|
+
transform: {
|
|
169
|
+
react: {
|
|
170
|
+
runtime: "preserve",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
target: "es2022",
|
|
174
|
+
loose: false,
|
|
175
|
+
minify: {
|
|
176
|
+
compress: false,
|
|
177
|
+
mangle: false,
|
|
178
|
+
},
|
|
179
|
+
preserveAllComments: true,
|
|
180
|
+
},
|
|
181
|
+
minify: false,
|
|
182
|
+
isModule: true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createViteYakPlugin } from "yak-internals/vite-plugin-core";
|
|
2
|
+
|
|
3
|
+
export type { ViteYakPluginOptions } from "yak-internals/vite-plugin-core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vite plugin for @yak/react: compiles the css``, styled`` and css prop
|
|
7
|
+
* styles in your source code to plain CSS at build time.
|
|
8
|
+
*
|
|
9
|
+
* Add it before the framework's plugin so it sees your original source code.
|
|
10
|
+
*
|
|
11
|
+
* @usage
|
|
12
|
+
* ```ts
|
|
13
|
+
* // vite.config.ts
|
|
14
|
+
* import { defineConfig } from "vite";
|
|
15
|
+
* import react from "@vitejs/plugin-react-swc";
|
|
16
|
+
* import { viteYak } from "@yak/react/vite";
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* plugins: [viteYak(), react()],
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const viteYak = createViteYakPlugin({
|
|
24
|
+
name: "@yak/react",
|
|
25
|
+
reactRefreshReg: true,
|
|
26
|
+
excludePattern: /packages\/yak-react/,
|
|
27
|
+
foldStatic: true,
|
|
28
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { LoaderContext } from "webpack";
|
|
2
|
+
import type { YakConfigOptions } from "../withYak/index.js";
|
|
3
|
+
import { createDebugLogger } from "yak-internals/debug-logger";
|
|
4
|
+
import { extractCss } from "yak-internals/extract-css";
|
|
5
|
+
import { resolveCrossFileConstant } from "./lib/resolveCrossFileSelectors.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transform typescript to css
|
|
9
|
+
*
|
|
10
|
+
* This loader takes the cached result from the yak tsloader
|
|
11
|
+
* and extracts the css from the generated comments
|
|
12
|
+
*/
|
|
13
|
+
export default async function cssExtractLoader(
|
|
14
|
+
this: LoaderContext<YakConfigOptions>,
|
|
15
|
+
// Instead of the source code, we receive the extracted css
|
|
16
|
+
// from the yak-swc transformation
|
|
17
|
+
_code: string,
|
|
18
|
+
sourceMap: string | undefined,
|
|
19
|
+
): Promise<string | void> {
|
|
20
|
+
const callback = this.async();
|
|
21
|
+
// Load the module from the original typescript request (without !=! and the query)
|
|
22
|
+
return this.loadModule(this.resourcePath, (err, source) => {
|
|
23
|
+
if (err) {
|
|
24
|
+
return callback(err);
|
|
25
|
+
}
|
|
26
|
+
if (!source) {
|
|
27
|
+
return callback(new Error(`Source code for ${this.resourcePath} is empty`));
|
|
28
|
+
}
|
|
29
|
+
const { debug } = this.getOptions();
|
|
30
|
+
const debugLog = createDebugLogger(debug, this._compiler?.context ?? process.cwd());
|
|
31
|
+
|
|
32
|
+
debugLog("ts", source, this.resourcePath);
|
|
33
|
+
const css = extractCss(source);
|
|
34
|
+
debugLog("css", css, this.resourcePath);
|
|
35
|
+
|
|
36
|
+
return resolveCrossFileConstant(this, this.context, css).then((result) => {
|
|
37
|
+
debugLog("css-resolved", result, this.resourcePath);
|
|
38
|
+
return callback(null, result, sourceMap);
|
|
39
|
+
}, callback);
|
|
40
|
+
});
|
|
41
|
+
}
|