next-yak 0.2.7 → 0.3.0

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.
@@ -39,259 +39,339 @@ var import_path = __toESM(require("path"), 1);
39
39
  var import_core = __toESM(require("@babel/core"), 1);
40
40
  var import_plugin_syntax_typescript = __toESM(require("@babel/plugin-syntax-typescript"), 1);
41
41
  var import_getCssModuleLocalIdent = require("next/dist/build/webpack/config/blocks/css/loaders/getCssModuleLocalIdent.js");
42
- var yakCssImportRegex = /--yak-css-import\:\s*url\("([^"]+)"\)/g;
43
- async function resolveCrossFileSelectors(loader, css) {
44
- let fileBasedResolveCache = /* @__PURE__ */ new Map();
42
+ var yakCssImportRegex = (
43
+ // Make mixin and selector non optional once we dropped support for the babel plugin
44
+ /--yak-css-import\:\s*url\("([^"]+)",?(|mixin|selector)\);?/g
45
+ );
46
+ var compilationCache = /* @__PURE__ */ new WeakMap();
47
+ async function resolveCrossFileConstant(loader, pathContext, css) {
45
48
  const matches = [...css.matchAll(yakCssImportRegex)].map((match) => {
46
- const [fullMatch, encodedArguments] = match;
49
+ const [fullMatch, encodedArguments, importKind] = match;
47
50
  const [moduleSpecifier, ...specifier] = encodedArguments.split(":").map((entry) => decodeURIComponent(entry));
48
- const position = match.index;
49
- if (specifier.length === 0) {
50
- throw new Error(
51
- `Invalid module import selector ${fullMatch} - no specifier provided`
52
- );
53
- }
54
51
  return {
52
+ encodedArguments,
55
53
  moduleSpecifier,
56
54
  specifier,
57
- position,
55
+ importKind,
56
+ position: match.index,
58
57
  size: fullMatch.length
59
58
  };
60
59
  });
61
- const firstMatchPosition = matches[0]?.position;
62
- if (firstMatchPosition === void 0) {
60
+ if (matches.length === 0)
63
61
  return css;
64
- }
65
- let result = "";
66
- for (let i = matches.length - 1; i >= 0; i--) {
67
- const { moduleSpecifier, specifier, position, size } = matches[i];
68
- const resolved = await resolveCrossFileValue(
69
- moduleSpecifier,
70
- specifier,
71
- fileBasedResolveCache,
72
- loader
62
+ try {
63
+ const exportCache = /* @__PURE__ */ new Map();
64
+ const resolvedValues = await Promise.all(
65
+ matches.map(({ moduleSpecifier, specifier, encodedArguments }) => {
66
+ const resolvedFromCache = exportCache.get(encodedArguments);
67
+ const resolvedValue = resolvedFromCache || parseModule(loader, moduleSpecifier, pathContext).then(
68
+ (parsedModule) => resolveModuleSpecifierRecursively(
69
+ loader,
70
+ parsedModule,
71
+ specifier
72
+ )
73
+ );
74
+ if (!resolvedFromCache) {
75
+ exportCache.set(encodedArguments, resolvedValue);
76
+ }
77
+ return resolvedValue;
78
+ })
73
79
  );
74
- if (resolved.type === "unsupported") {
75
- throw new Error(
76
- `yak could not import ${specifier.join(
77
- "."
78
- )} from ${moduleSpecifier} - only styled-components, strings and numbers are supported`
79
- );
80
+ let result = css;
81
+ for (let i = matches.length - 1; i >= 0; i--) {
82
+ const { position, size, importKind, specifier } = matches[i];
83
+ const resolved = resolvedValues[i];
84
+ if (importKind === "selector") {
85
+ if (resolved.type === "mixin") {
86
+ throw new Error(
87
+ `Found mixin but expected a selector - did you forget a semicolon after \`${specifier.join(
88
+ "."
89
+ )}\`?`
90
+ );
91
+ }
92
+ }
93
+ const replacement = resolved.type === "styled-component" ? `:global(.${(0, import_getCssModuleLocalIdent.getCssModuleLocalIdent)(
94
+ {
95
+ rootContext: loader.rootContext,
96
+ resourcePath: resolved.from
97
+ },
98
+ null,
99
+ resolved.name,
100
+ {}
101
+ )})` : resolved.value;
102
+ result = result.slice(0, position) + String(replacement) + result.slice(position + size);
80
103
  }
81
- const replacement = resolved.type === "styled-component" ? `:global(.${(0, import_getCssModuleLocalIdent.getCssModuleLocalIdent)(
82
- {
83
- rootContext: loader.rootContext,
84
- resourcePath: resolved.from
85
- },
86
- null,
87
- resolved.name,
88
- {}
89
- )})` : getConstantFromResolvedValue(resolved.value, specifier.slice(1));
90
- result = String(replacement) + css.slice(position + size, matches[i + 1]?.position) + result;
104
+ return result;
105
+ } catch (error) {
106
+ throw new Error(
107
+ `Error resolving cross-file selectors: ${error.message}
108
+ File: ${loader.resourcePath}`
109
+ );
91
110
  }
92
- result = css.slice(0, firstMatchPosition) + result;
93
- return result;
94
111
  }
95
- var getConstantFromResolvedValue = (record, specifier) => {
96
- let current = record;
97
- for (const key of specifier) {
98
- if (typeof current === "string" || typeof current === "number") {
99
- throw new Error(
100
- `Could not resolve ${specifier.join(".")} in ${JSON.stringify(record)}`
101
- );
102
- }
103
- current = current[key];
112
+ async function parseModule(loader, moduleSpecifier, context) {
113
+ const compilation = loader._compilation;
114
+ if (!compilation) {
115
+ throw new Error("Webpack compilation object not available");
104
116
  }
105
- if (typeof current === "string" || typeof current === "number") {
106
- return current;
117
+ let cache = compilationCache.get(compilation);
118
+ if (!cache) {
119
+ cache = /* @__PURE__ */ new Map();
120
+ compilationCache.set(compilation, cache);
107
121
  }
108
- throw new Error(
109
- `Could not resolve ${specifier.join(".")} in ${JSON.stringify(record)}`
110
- );
111
- };
112
- async function resolveCrossFileValue(moduleSpecifier, specifier, resolveCache, loader) {
113
- const isYak = moduleSpecifier.endsWith(".yak");
114
- let resolvedModule;
115
- if (!isYak) {
116
- const resolveKey = `${moduleSpecifier} : ${specifier[0]}`;
117
- let resolvedFromCache = resolveCache.get(resolveKey);
118
- resolvedModule = resolvedFromCache || resolveIdentifier(loader, loader.context, moduleSpecifier, specifier[0]);
119
- if (!resolvedFromCache) {
120
- resolveCache.set(resolveKey, resolvedModule);
121
- }
122
- } else {
123
- let resolvedFromCache = resolveCache.get(moduleSpecifier);
124
- resolvedModule = resolvedFromCache || resolveYakModule(loader, moduleSpecifier);
125
- if (!resolvedFromCache) {
126
- resolveCache.set(moduleSpecifier, resolvedModule);
127
- }
128
- resolvedModule = resolvedModule.then((moduleValues) => {
129
- if (moduleValues.type !== "record") {
130
- throw new Error("resolveYakModule returns always a record");
131
- }
132
- const value = moduleValues.value[specifier[0]];
133
- if (typeof value === "string" || typeof value === "number") {
134
- return {
135
- type: "constant",
136
- value
137
- };
138
- }
139
- if (value && (Array.isArray(value) || typeof value === "object")) {
140
- return {
141
- type: "record",
142
- value
143
- };
144
- }
145
- throw new Error(
146
- `Could not find export ${specifier[0]} in ${moduleSpecifier}`
147
- );
148
- });
122
+ const cacheKey = import_path.default.resolve(context, moduleSpecifier);
123
+ let filePromise = cache.get(cacheKey);
124
+ if (!filePromise) {
125
+ filePromise = (async () => {
126
+ const resolved = await new Promise((resolve, reject) => {
127
+ loader.resolve(context, moduleSpecifier, (err, result) => {
128
+ if (err)
129
+ return reject(err);
130
+ if (!result)
131
+ return reject(new Error(`Could not resolve ${moduleSpecifier}`));
132
+ resolve(result);
133
+ });
134
+ });
135
+ return parseFile(loader, resolved);
136
+ })();
137
+ cache.set(cacheKey, filePromise);
149
138
  }
150
- return resolvedModule;
139
+ loader.addDependency((await filePromise).filePath);
140
+ return filePromise;
151
141
  }
152
- async function resolveIdentifier(loader, context, sourcePath, identifier) {
153
- const resolved = await new Promise((resolve, reject) => {
154
- loader.resolve(context, sourcePath, (err, result) => {
155
- if (err) {
156
- return reject(err);
157
- }
158
- if (!result) {
159
- return reject(new Error(`Could not resolve ${sourcePath}`));
160
- }
161
- resolve(result);
142
+ async function parseFile(loader, filePath) {
143
+ const isYak = filePath.endsWith(".yak.ts") || filePath.endsWith(".yak.tsx") || filePath.endsWith(".yak.js") || filePath.endsWith(".yak.jsx");
144
+ const isTSX = filePath.endsWith(".tsx");
145
+ try {
146
+ if (isYak) {
147
+ const module2 = await loader.importModule(filePath);
148
+ const mappedModule = Object.fromEntries(
149
+ Object.entries(module2).map(([key, value]) => {
150
+ if (typeof value === "string" || typeof value === "number") {
151
+ return [key, { type: "constant", value }];
152
+ } else if (value && (typeof value === "object" || Array.isArray(value))) {
153
+ return [key, { type: "record", value }];
154
+ } else {
155
+ return [key, { type: "unsupported" }];
156
+ }
157
+ })
158
+ );
159
+ return { type: "yak", exports: mappedModule, filePath };
160
+ }
161
+ const sourceContents = new Promise(
162
+ (resolve, reject) => loader.fs.readFile(filePath, "utf-8", (err, result) => {
163
+ if (err)
164
+ return reject(err);
165
+ resolve(result || "");
166
+ })
167
+ );
168
+ const tranformedSource = new Promise((resolve, reject) => {
169
+ loader.loadModule(filePath, (err, source) => {
170
+ if (err)
171
+ return reject(err);
172
+ resolve(source || "");
173
+ });
162
174
  });
163
- });
164
- loader.addDependency(resolved);
165
- const exports = await getAllExports(
166
- loader,
167
- resolved,
168
- resolved.endsWith(".tsx")
169
- );
170
- const exportForIdentifier = exports[identifier];
171
- if (!exportForIdentifier) {
172
- throw new Error(`Could not find export ${identifier} in ${resolved}
173
- Currently only named exports are supported.
174
- Available exports: ${Object.keys(exports).join(", ")}`);
175
- }
176
- if (exportForIdentifier.type === "styled-component") {
175
+ const exports = await parseExports(await sourceContents, isTSX);
176
+ const mixins = parseMixins(await tranformedSource);
177
+ await Promise.all(
178
+ Object.entries(mixins).map(async ([name, mixin]) => {
179
+ mixins[name] = {
180
+ type: "mixin",
181
+ value: await resolveCrossFileConstant(
182
+ loader,
183
+ import_path.default.dirname(filePath),
184
+ mixin.value
185
+ )
186
+ };
187
+ })
188
+ );
177
189
  return {
178
- type: "styled-component",
179
- from: resolved,
180
- name: exportForIdentifier.name
190
+ type: "regular",
191
+ exports: {
192
+ ...exports,
193
+ ...mixins
194
+ },
195
+ filePath
181
196
  };
197
+ } catch (error) {
198
+ throw new Error(
199
+ `Error parsing file ${filePath}: ${error.message}`
200
+ );
182
201
  }
183
- return exportForIdentifier.type !== "named-export" ? exportForIdentifier : resolveIdentifier(
184
- loader,
185
- import_path.default.dirname(resolved),
186
- exportForIdentifier.from,
187
- exportForIdentifier.name
188
- );
189
202
  }
190
- var exportsCache = /* @__PURE__ */ new WeakMap();
191
- async function getAllExports(loader, source, isTSX) {
192
- const compilationCache = loader._compilation && exportsCache.get(loader._compilation)?.get(source);
193
- if (compilationCache) {
194
- return compilationCache;
195
- }
196
- const sourceContents = await new Promise(
197
- (resolve, reject) => loader.fs.readFile(source, "utf-8", (err, result2) => {
198
- if (err) {
199
- return reject(err);
200
- }
201
- resolve(result2 || "");
202
- })
203
- );
204
- let result = {};
205
- import_core.default.transformSync(sourceContents, {
206
- configFile: false,
207
- plugins: [
208
- [import_plugin_syntax_typescript.default, { isTSX }],
209
- [
210
- () => ({
211
- visitor: {
212
- ExportNamedDeclaration({ node }) {
213
- if (node.source) {
214
- node.specifiers.forEach((specifier) => {
215
- const exportSource = node.source?.value;
216
- if (specifier.exported.type === "Identifier" && specifier.exported.name && specifier.type === "ExportSpecifier" && specifier.local.type === "Identifier" && specifier.local.name && exportSource) {
217
- result[specifier.exported.name] = {
218
- type: "named-export",
219
- name: specifier.local.name,
220
- from: exportSource
221
- };
222
- }
223
- });
224
- } else if (node.declaration?.type === "VariableDeclaration") {
225
- node.declaration.declarations.forEach((declaration) => {
226
- if (declaration.id.type === "Identifier" && declaration.id.name && declaration.init) {
227
- if (declaration.init.type === "CallExpression" || declaration.init.type === "TaggedTemplateExpression") {
228
- result[declaration.id.name] = {
229
- type: "styled-component",
230
- name: declaration.id.name
231
- };
232
- } else if (declaration.init.type === "StringLiteral" || declaration.init.type === "NumericLiteral") {
233
- result[declaration.id.name] = {
234
- type: "constant",
235
- value: declaration.init.value
236
- };
237
- } else if (declaration.init.type === "TemplateLiteral" && declaration.init.quasis.length === 1) {
238
- result[declaration.id.name] = {
239
- type: "constant",
240
- value: declaration.init.quasis[0].value.raw
241
- };
242
- } else if (declaration.init.type === "ObjectExpression") {
243
- result[declaration.id.name] = {
244
- type: "record",
245
- value: parseObjectExpression(declaration.init)
246
- };
247
- } else {
248
- result[declaration.id.name] = {
249
- type: "unsupported",
250
- name: declaration.id.name
203
+ async function parseExports(sourceContents, isTSX) {
204
+ let exports = {};
205
+ try {
206
+ import_core.default.transformSync(sourceContents, {
207
+ configFile: false,
208
+ plugins: [
209
+ [import_plugin_syntax_typescript.default, { isTSX }],
210
+ [
211
+ () => ({
212
+ visitor: {
213
+ ExportNamedDeclaration({ node }) {
214
+ if (node.source) {
215
+ node.specifiers.forEach((specifier) => {
216
+ if (specifier.type === "ExportSpecifier" && specifier.exported.type === "Identifier" && specifier.local.type === "Identifier") {
217
+ exports[specifier.exported.name] = {
218
+ type: "re-export",
219
+ from: node.source.value,
220
+ imported: specifier.local.name
251
221
  };
252
222
  }
223
+ });
224
+ } else if (node.declaration?.type === "VariableDeclaration") {
225
+ node.declaration.declarations.forEach((declaration) => {
226
+ if (declaration.id.type === "Identifier" && declaration.init) {
227
+ exports[declaration.id.name] = parseExportValueExpression(
228
+ declaration.init
229
+ );
230
+ }
231
+ });
232
+ }
233
+ },
234
+ ExportAllDeclaration({ node }) {
235
+ if (Object.keys(exports).length === 0) {
236
+ exports["*"] ||= {
237
+ type: "star-export",
238
+ from: []
239
+ };
240
+ if (exports["*"].type !== "star-export") {
241
+ throw new Error("Invalid star export state");
253
242
  }
254
- });
243
+ exports["*"].from.push(node.source.value);
244
+ }
255
245
  }
256
- },
257
- ExportDefaultDeclaration() {
258
246
  }
259
- }
260
- })
247
+ })
248
+ ]
261
249
  ]
262
- ]
263
- });
264
- if (loader._compilation) {
265
- const compilationCache2 = exportsCache.get(loader._compilation);
266
- const exportsPerFile = compilationCache2 || /* @__PURE__ */ new Map();
267
- exportsPerFile.set(source, result);
268
- if (!compilationCache2) {
269
- exportsCache.set(loader._compilation, exportsPerFile);
270
- }
250
+ });
251
+ return exports;
252
+ } catch (error) {
253
+ throw new Error(`Error parsing exports: ${error.message}`);
271
254
  }
272
- return result;
255
+ }
256
+ function parseMixins(sourceContents) {
257
+ const mixinParts = sourceContents.split("/*YAK EXPORTED MIXIN:");
258
+ let mixins = {};
259
+ for (let i = 1; i < mixinParts.length; i++) {
260
+ const [comment] = mixinParts[i].split("*/", 1);
261
+ const position = comment.indexOf("\n");
262
+ const name = comment.slice(0, position);
263
+ const value = comment.slice(position + 1);
264
+ mixins[name] = { type: "mixin", value };
265
+ }
266
+ return mixins;
267
+ }
268
+ function parseExportValueExpression(node) {
269
+ if (node.type === "CallExpression" || node.type === "TaggedTemplateExpression") {
270
+ return { type: "styled-component" };
271
+ } else if (node.type === "StringLiteral" || node.type === "NumericLiteral") {
272
+ return { type: "constant", value: node.value };
273
+ } else if (node.type === "TemplateLiteral" && node.quasis.length === 1) {
274
+ return { type: "constant", value: node.quasis[0].value.raw };
275
+ } else if (node.type === "ObjectExpression") {
276
+ return { type: "record", value: parseObjectExpression(node) };
277
+ }
278
+ return { type: "unsupported" };
273
279
  }
274
280
  function parseObjectExpression(node) {
275
281
  let result = {};
276
282
  for (const property of node.properties) {
277
283
  if (property.type === "ObjectProperty" && property.key.type === "Identifier") {
278
284
  const key = property.key.name;
279
- if (property.value.type === "StringLiteral" || property.value.type === "NumericLiteral") {
280
- result[key] = property.value.value;
281
- } else if (property.value.type === "TemplateLiteral" && property.value.quasis.length === 1) {
282
- result[key] = property.value.quasis[0].value.raw;
283
- } else if (property.value.type === "ObjectExpression") {
284
- result[key] = parseObjectExpression(property.value);
285
- }
285
+ result[key] = parseExportValueExpression(
286
+ property.value
287
+ );
286
288
  }
287
289
  }
288
290
  return result;
289
291
  }
290
- function resolveYakModule(loader, moduleSpecifier) {
291
- return loader.importModule(moduleSpecifier).then((module2) => ({
292
- type: "record",
293
- value: module2
294
- }));
292
+ async function resolveModuleSpecifierRecursively(loader, module2, specifier) {
293
+ try {
294
+ const exportName = specifier[0];
295
+ let exportValue = module2.exports[exportName];
296
+ if (exportValue === void 0) {
297
+ const starExport = module2.exports["*"];
298
+ if (starExport?.type === "star-export") {
299
+ if (starExport.from.length > 1) {
300
+ throw new Error(
301
+ `Could not resolve ${specifier.join(".")} in module ${module2.filePath} - Multiple star exports are not supported for performance reasons`
302
+ );
303
+ }
304
+ exportValue = {
305
+ type: "re-export",
306
+ from: starExport.from[0],
307
+ imported: exportName
308
+ };
309
+ } else {
310
+ throw new Error(
311
+ `Could not resolve "${specifier.join(".")}" in module ${module2.filePath}`
312
+ );
313
+ }
314
+ }
315
+ if (exportValue.type === "re-export") {
316
+ const importedModule = await parseModule(
317
+ loader,
318
+ exportValue.from,
319
+ import_path.default.dirname(module2.filePath)
320
+ );
321
+ return resolveModuleSpecifierRecursively(loader, importedModule, [
322
+ exportValue.imported,
323
+ ...specifier.slice(1)
324
+ ]);
325
+ }
326
+ if (exportValue.type === "styled-component") {
327
+ return {
328
+ type: "styled-component",
329
+ from: module2.filePath,
330
+ name: specifier[specifier.length - 1]
331
+ };
332
+ } else if (exportValue.type === "constant") {
333
+ return { type: "constant", value: exportValue.value };
334
+ } else if (exportValue.type === "record") {
335
+ let current = exportValue.value;
336
+ let depth = 0;
337
+ do {
338
+ if (typeof current === "string" || typeof current === "number") {
339
+ return {
340
+ type: "constant",
341
+ value: current
342
+ };
343
+ } else if (!current || typeof current !== "object" && !Array.isArray(current)) {
344
+ throw new Error(
345
+ `Error unpacking Record/Array "${exportName}".
346
+ Key "${specifier[depth]}" was of type "${typeof current}" but only String and Number are supported`
347
+ );
348
+ }
349
+ depth++;
350
+ if (depth === specifier.length && "__yak" in current) {
351
+ return { type: "mixin", value: current["__yak"] };
352
+ } else {
353
+ current = current[specifier[depth]];
354
+ }
355
+ } while (current);
356
+ if (specifier[depth] === void 0) {
357
+ throw new Error(
358
+ `Error unpacking Record/Array - could not extract \`${specifier.slice(0, depth).join(".")}\` is not a string or number`
359
+ );
360
+ }
361
+ throw new Error(
362
+ `Error unpacking Record/Array - could not extract \`${specifier[depth]}\` from \`${specifier.slice(0, depth).join(".")}\``
363
+ );
364
+ } else if (exportValue.type === "mixin") {
365
+ return { type: "mixin", value: exportValue.value };
366
+ }
367
+ throw new Error(
368
+ `Error unpacking Record/Array - unexpected exportValue "${exportValue.type}" for specifier "${specifier.join(".")}"`
369
+ );
370
+ } catch (error) {
371
+ throw new Error(
372
+ `Error resolving from module ${module2.filePath}: ${error.message}`
373
+ );
374
+ }
295
375
  }
296
376
 
297
377
  // loaders/css-loader.ts
@@ -307,7 +387,7 @@ async function cssExtractLoader(_code, sourceMap) {
307
387
  debugLog("ts", source);
308
388
  const css = extractCss(source);
309
389
  debugLog("css", css);
310
- return resolveCrossFileSelectors(this, css).then((result) => {
390
+ return resolveCrossFileConstant(this, this.context, css).then((result) => {
311
391
  debugLog("css resolved", css);
312
392
  return callback(null, result, sourceMap);
313
393
  }, callback);