@wyw-in-js/transform 0.2.3 → 0.4.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.
@@ -23,7 +23,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.createStylisPreprocessor = exports.createStylisUrlReplacePlugin = exports.stylisGlobalPlugin = exports.transformUrl = void 0;
26
+ exports.createStylisPreprocessor = exports.createKeyframeSuffixerPlugin = exports.createStylisUrlReplacePlugin = exports.stylisGlobalPlugin = exports.transformUrl = void 0;
27
+ /* eslint-disable no-continue */
27
28
  const path = __importStar(require("path"));
28
29
  const stylis_1 = require("stylis");
29
30
  const POSIX_SEP = path.posix.sep;
@@ -38,11 +39,51 @@ function transformUrl(url, outputFilename, sourceFilename, platformPath = path)
38
39
  return relative.split(platformPath.sep).join(POSIX_SEP);
39
40
  }
40
41
  exports.transformUrl = transformUrl;
42
+ const DEFINED_KEYFRAMES = Symbol('definedKeyframes');
43
+ const ORIGINAL_VALUE_KEY = Symbol('originalValue');
44
+ const IS_GLOBAL_KEYFRAMES = Symbol('isGlobalKeyframes');
45
+ const getOriginalElementValue = (element) => {
46
+ return element ? element[ORIGINAL_VALUE_KEY] ?? element.value : '';
47
+ };
48
+ function throwIfNotProd(key, value, type) {
49
+ if (process.env.NODE_ENV !== 'production') {
50
+ throw new Error(`"element.${key}" has type "${type}" (${JSON.stringify(value, null, 2)}), it's not expected. Please report a bug if it happens.`);
51
+ }
52
+ return false;
53
+ }
54
+ function childrenIsString(children) {
55
+ return (typeof children === 'string' ||
56
+ throwIfNotProd('children', children, 'Element[]'));
57
+ }
58
+ function propsAreStrings(props) {
59
+ return Array.isArray(props) || throwIfNotProd('props', props, 'string');
60
+ }
61
+ function propsIsString(props) {
62
+ return (typeof props === 'string' || throwIfNotProd('props', props, 'string[]'));
63
+ }
64
+ const isDeclaration = (element) => {
65
+ return (element.type === stylis_1.DECLARATION &&
66
+ propsIsString(element.props) &&
67
+ childrenIsString(element.children));
68
+ };
69
+ const isKeyframes = (element) => {
70
+ return element.type === stylis_1.KEYFRAMES && propsAreStrings(element.props);
71
+ };
72
+ const isRuleset = (element) => {
73
+ return element.type === stylis_1.RULESET && propsAreStrings(element.props);
74
+ };
41
75
  /**
42
76
  * Stylis plugin that mimics :global() selector behavior from Stylis v3.
43
77
  */
44
78
  const stylisGlobalPlugin = (element) => {
45
- function getGlobalSelectorModifiers(value) {
79
+ function getGlobalSelectorModifiers(el) {
80
+ const { parent } = el;
81
+ const value = getOriginalElementValue(el);
82
+ const parentValue = getOriginalElementValue(parent);
83
+ if ((parent?.children.length === 0 && parentValue.includes(':global(')) ||
84
+ (parent && !value.includes(':global('))) {
85
+ return getGlobalSelectorModifiers(parent);
86
+ }
46
87
  const match = value.match(/(&\f( )?)?:global\(/);
47
88
  if (match === null) {
48
89
  throw new Error(`Failed to match :global() selector in "${value}". Please report a bug if it happens.`);
@@ -53,62 +94,53 @@ const stylisGlobalPlugin = (element) => {
53
94
  includeSpaceDelimiter: !!spaceDelimiter,
54
95
  };
55
96
  }
56
- switch (element.type) {
57
- case stylis_1.RULESET:
58
- if (typeof element.props === 'string') {
59
- if (process.env.NODE_ENV !== 'production') {
60
- throw new Error(`"element.props" has type "string" (${JSON.stringify(element.props, null, 2)}), it's not expected. Please report a bug if it happens.`);
61
- }
62
- return;
97
+ if (!isRuleset(element)) {
98
+ return;
99
+ }
100
+ Object.assign(element, {
101
+ props: element.props.map((cssSelector) => {
102
+ // The value can be changed by other middlewares, but we need an original one with `&`
103
+ Object.assign(element, { [ORIGINAL_VALUE_KEY]: element.value });
104
+ // Avoids calling tokenize() on every string
105
+ if (!cssSelector.includes(':global(')) {
106
+ return cssSelector;
63
107
  }
64
- Object.assign(element, {
65
- props: element.props.map((cssSelector) => {
66
- // Avoids calling tokenize() on every string
67
- if (!cssSelector.includes(':global(')) {
68
- return cssSelector;
69
- }
70
- if (element.children.length === 0) {
71
- Object.assign(element, {
72
- global: getGlobalSelectorModifiers(element.value),
73
- });
74
- return cssSelector;
75
- }
76
- const { includeBaseSelector, includeSpaceDelimiter } = element.parent?.global || getGlobalSelectorModifiers(element.value);
77
- const tokens = (0, stylis_1.tokenize)(cssSelector);
78
- let selector = '';
79
- for (let i = 0, len = tokens.length; i < len; i++) {
80
- const token = tokens[i];
81
- //
82
- // Match for ":global("
83
- if (token === ':' && tokens[i + 1] === 'global') {
84
- //
85
- // Match for ":global()"
86
- if (tokens[i + 2] === '()') {
87
- selector = [
88
- ...tokens.slice(i + 4),
89
- includeSpaceDelimiter ? ' ' : '',
90
- ...(includeBaseSelector ? tokens.slice(0, i - 1) : []),
91
- includeSpaceDelimiter ? '' : ' ',
92
- ].join('');
93
- break;
94
- }
95
- //
96
- // Match for ":global(selector)"
97
- selector = [
98
- tokens[i + 2].slice(1, -1),
99
- includeSpaceDelimiter ? ' ' : '',
100
- ...(includeBaseSelector ? tokens.slice(0, i - 1) : []),
101
- includeSpaceDelimiter ? '' : ' ',
102
- ].join('');
103
- break;
104
- }
108
+ if (element.children.length === 0) {
109
+ return cssSelector;
110
+ }
111
+ const { includeBaseSelector, includeSpaceDelimiter } = getGlobalSelectorModifiers(element);
112
+ const tokens = (0, stylis_1.tokenize)(cssSelector);
113
+ let selector = '';
114
+ for (let i = 0, len = tokens.length; i < len; i++) {
115
+ const token = tokens[i];
116
+ //
117
+ // Match for ":global("
118
+ if (token === ':' && tokens[i + 1] === 'global') {
119
+ //
120
+ // Match for ":global()"
121
+ if (tokens[i + 2] === '()') {
122
+ selector = [
123
+ ...tokens.slice(i + 4),
124
+ includeSpaceDelimiter ? ' ' : '',
125
+ ...(includeBaseSelector ? tokens.slice(0, i - 1) : []),
126
+ includeSpaceDelimiter ? '' : ' ',
127
+ ].join('');
128
+ break;
105
129
  }
106
- return selector;
107
- }),
108
- });
109
- break;
110
- default:
111
- }
130
+ //
131
+ // Match for ":global(selector)"
132
+ selector = [
133
+ tokens[i + 2].slice(1, -1),
134
+ includeSpaceDelimiter ? ' ' : '',
135
+ ...(includeBaseSelector ? tokens.slice(0, i - 1) : []),
136
+ includeSpaceDelimiter ? '' : ' ',
137
+ ].join('');
138
+ break;
139
+ }
140
+ }
141
+ return selector;
142
+ }),
143
+ });
112
144
  };
113
145
  exports.stylisGlobalPlugin = stylisGlobalPlugin;
114
146
  function createStylisUrlReplacePlugin(filename, outputFilename) {
@@ -117,20 +149,116 @@ function createStylisUrlReplacePlugin(filename, outputFilename) {
117
149
  // When writing to a file, we need to adjust the relative paths inside url(..) expressions.
118
150
  // It'll allow css-loader to resolve an imported asset properly.
119
151
  // eslint-disable-next-line no-param-reassign
120
- element.return = element.value.replace(/\b(url\((["']?))(\.[^)]+?)(\2\))/g, (match, p1, p2, p3, p4) => p1 + transformUrl(p3, outputFilename, filename) + p4);
152
+ element.return = element.value.replace(/\b(url\((["']?))(\.[^)]+?)(\2\))/g, (_match, p1, _p2, p3, p4) => p1 + transformUrl(p3, outputFilename, filename) + p4);
121
153
  }
122
154
  };
123
155
  }
124
156
  exports.createStylisUrlReplacePlugin = createStylisUrlReplacePlugin;
157
+ function createKeyframeSuffixerPlugin() {
158
+ const prefixes = ['webkit', 'moz', 'ms', 'o', ''].map((i) => i ? `-${i}-` : '');
159
+ const getPrefixedProp = (prop) => prefixes.map((prefix) => `${prefix}${prop}`);
160
+ const buildPropsRegexp = (prop, isAtRule) => {
161
+ const [at, colon] = isAtRule ? ['@', ''] : ['', ':'];
162
+ return new RegExp(`^(${at}(?:${getPrefixedProp(prop).join('|')})${colon})\\s*`);
163
+ };
164
+ const animationNameRegexp = /:global\(([\w_-]+)\)|([\w_-]+)/;
165
+ const getReplacer = (startsWith, searchValue, replacer) => {
166
+ return (input) => {
167
+ const [fullMatch] = input.match(startsWith) ?? [];
168
+ if (fullMatch === undefined) {
169
+ return input;
170
+ }
171
+ const rest = input.slice(fullMatch.length);
172
+ return fullMatch + rest.replace(searchValue, replacer);
173
+ };
174
+ };
175
+ const elementToKeyframeSuffix = (el) => {
176
+ if (el.parent) {
177
+ return elementToKeyframeSuffix(el.parent);
178
+ }
179
+ return el.value.replaceAll(/[^a-zA-Z0-9_-]/g, '');
180
+ };
181
+ const animationPropsSet = new Set([
182
+ ...getPrefixedProp('animation'),
183
+ ...getPrefixedProp('animation-name'),
184
+ ]);
185
+ const getDefinedKeyframes = (element) => {
186
+ if (element[DEFINED_KEYFRAMES]) {
187
+ return element[DEFINED_KEYFRAMES];
188
+ }
189
+ if (element.parent) {
190
+ return getDefinedKeyframes(element.parent);
191
+ }
192
+ const keyframes = new Set();
193
+ for (const sibling of element.siblings ?? []) {
194
+ if (!isKeyframes(sibling) || sibling[IS_GLOBAL_KEYFRAMES] === true) {
195
+ continue;
196
+ }
197
+ keyframes.add(sibling.props[0]);
198
+ }
199
+ Object.assign(element, { [DEFINED_KEYFRAMES]: keyframes });
200
+ return keyframes;
201
+ };
202
+ return (element) => {
203
+ if (isKeyframes(element) && element.parent) {
204
+ const suffix = elementToKeyframeSuffix(element);
205
+ const replaceFn = (_match, globalMatch, scopedMatch) => globalMatch || `${scopedMatch}-${suffix}`;
206
+ Object.assign(element, {
207
+ [IS_GLOBAL_KEYFRAMES]: element.props[0]?.startsWith(':global(') ?? false,
208
+ props: element.props.map(getReplacer(/^\s*/, animationNameRegexp, replaceFn)),
209
+ value: getReplacer(buildPropsRegexp('keyframes', true), animationNameRegexp, replaceFn)(element.value),
210
+ });
211
+ return;
212
+ }
213
+ if (isDeclaration(element)) {
214
+ const suffix = elementToKeyframeSuffix(element);
215
+ const keys = [
216
+ 'children',
217
+ 'return',
218
+ 'value',
219
+ ];
220
+ if (animationPropsSet.has(element.props)) {
221
+ const scopedKeyframes = getDefinedKeyframes(element);
222
+ const patch = Object.fromEntries(keys.map((key) => {
223
+ const tokens = (0, stylis_1.tokenize)(element[key]);
224
+ let result = '';
225
+ for (let i = 0; i < tokens.length; i += 1) {
226
+ if (tokens[i] === ':' &&
227
+ tokens[i + 1] === 'global' &&
228
+ tokens[i + 2].startsWith('(')) {
229
+ const globalName = tokens[i + 2].substring(1, tokens[i + 2].length - 1);
230
+ i += 2;
231
+ result += globalName;
232
+ if (tokens[i + 1] !== ';') {
233
+ result += ' ';
234
+ }
235
+ continue;
236
+ }
237
+ if (scopedKeyframes.has(tokens[i])) {
238
+ result += `${tokens[i]}-${suffix}`;
239
+ continue;
240
+ }
241
+ result += tokens[i];
242
+ }
243
+ return [key, result];
244
+ }));
245
+ Object.assign(element, patch);
246
+ }
247
+ }
248
+ };
249
+ }
250
+ exports.createKeyframeSuffixerPlugin = createKeyframeSuffixerPlugin;
251
+ const isMiddleware = (obj) => obj !== null;
125
252
  function createStylisPreprocessor(options) {
126
253
  function stylisPreprocess(selector, text) {
127
254
  const compiled = (0, stylis_1.compile)(`${selector} {${text}}\n`);
128
255
  return (0, stylis_1.serialize)(compiled, (0, stylis_1.middleware)([
129
256
  createStylisUrlReplacePlugin(options.filename, options.outputFilename),
130
257
  exports.stylisGlobalPlugin,
131
- stylis_1.prefixer,
258
+ options.prefixer === false ? null : stylis_1.prefixer,
259
+ createKeyframeSuffixerPlugin(),
132
260
  stylis_1.stringify,
133
- ]));
261
+ ].filter(isMiddleware)));
134
262
  }
135
263
  return stylisPreprocess;
136
264
  }