@compiled/vite-plugin 1.0.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.
@@ -0,0 +1,346 @@
1
+ import compiledVitePlugin from '../index';
2
+
3
+ describe('compiledVitePlugin', () => {
4
+ it('should create a plugin with the correct name', () => {
5
+ const plugin = compiledVitePlugin();
6
+
7
+ expect(plugin.name).toBe('@compiled/vite-plugin');
8
+ expect(plugin.enforce).toBe('pre');
9
+ });
10
+
11
+ it('should transform code with Compiled imports', async () => {
12
+ const plugin = compiledVitePlugin();
13
+ const code = `
14
+ import { css } from '@compiled/react';
15
+
16
+ export const Component = () => (
17
+ <div css={css({ color: 'red', fontSize: '12px' })}>
18
+ Hello
19
+ </div>
20
+ );
21
+ `;
22
+
23
+ const result = await plugin.transform!(code, 'test.tsx');
24
+
25
+ expect(result).toBeTruthy();
26
+ if (result && typeof result === 'object' && 'code' in result) {
27
+ // Check for the atomic class structure
28
+ expect(result.code).toContain('_syaz5scu');
29
+ expect(result.code).toContain('color:red');
30
+ expect(result.code).toContain('font-size:9pt'); // 12px gets normalized to 9pt
31
+ }
32
+ });
33
+
34
+ it('should skip files without Compiled imports', async () => {
35
+ const plugin = compiledVitePlugin();
36
+ const code = `
37
+ import React from 'react';
38
+
39
+ export const Component = () => <div>Hello</div>;
40
+ `;
41
+
42
+ const result = await plugin.transform!(code, 'test.tsx');
43
+
44
+ expect(result).toBeNull();
45
+ });
46
+
47
+ it('should skip non-JS/TS files', async () => {
48
+ const plugin = compiledVitePlugin();
49
+ const code = '.some-class { color: red; }';
50
+
51
+ const result = await plugin.transform!(code, 'test.css');
52
+
53
+ expect(result).toBeNull();
54
+ });
55
+
56
+ it('should skip node_modules/@compiled/react', async () => {
57
+ const plugin = compiledVitePlugin();
58
+ const code = `
59
+ import { css } from '@compiled/react';
60
+ export const styled = {};
61
+ `;
62
+
63
+ const result = await plugin.transform!(code, '/node_modules/@compiled/react/dist/index.js');
64
+
65
+ expect(result).toBeNull();
66
+ });
67
+
68
+ it('should handle styled components', async () => {
69
+ const plugin = compiledVitePlugin();
70
+ const code = `
71
+ import { styled } from '@compiled/react';
72
+
73
+ export const StyledDiv = styled.div({
74
+ color: 'blue',
75
+ padding: '8px',
76
+ });
77
+ `;
78
+
79
+ const result = await plugin.transform!(code, 'test.tsx');
80
+
81
+ expect(result).toBeTruthy();
82
+ if (result && typeof result === 'object' && 'code' in result) {
83
+ expect(result.code).toContain('color:blue');
84
+ // Padding gets split into longhand properties
85
+ expect(result.code).toContain('padding-top:8px');
86
+ expect(result.code).toContain('forwardRef'); // Styled components use forwardRef
87
+ }
88
+ });
89
+
90
+ it('should respect custom importSources', async () => {
91
+ const plugin = compiledVitePlugin({
92
+ importSources: ['@custom/styled'],
93
+ });
94
+ const code = `
95
+ import { css } from '@custom/styled';
96
+
97
+ export const Component = () => (
98
+ <div css={css({ margin: '16px' })}>
99
+ Hello
100
+ </div>
101
+ );
102
+ `;
103
+
104
+ const result = await plugin.transform!(code, 'test.tsx');
105
+
106
+ expect(result).toBeTruthy();
107
+ if (result && typeof result === 'object' && 'code' in result) {
108
+ // Margin gets split into longhand properties, and 16px may be normalized to 1pc
109
+ expect(result.code).toContain('margin-top:');
110
+ expect(result.code).toContain('CC'); // Check for Compiled runtime components
111
+ }
112
+ });
113
+
114
+ it('should handle errors gracefully', async () => {
115
+ const plugin = compiledVitePlugin();
116
+ const mockError = jest.fn();
117
+
118
+ // Create a mock context with an error method
119
+ const context = {
120
+ error: mockError,
121
+ };
122
+
123
+ const invalidCode = `
124
+ import { css } from '@compiled/react';
125
+
126
+ // Invalid syntax
127
+ const broken = css({
128
+ color
129
+ `;
130
+
131
+ await plugin.transform!.call(context, invalidCode, 'test.tsx');
132
+
133
+ expect(mockError).toHaveBeenCalled();
134
+ });
135
+
136
+ it('should apply default options', () => {
137
+ const plugin = compiledVitePlugin();
138
+
139
+ expect(plugin.name).toBe('@compiled/vite-plugin');
140
+ expect(plugin.enforce).toBe('pre');
141
+ });
142
+
143
+ it('should accept custom options', async () => {
144
+ const plugin = compiledVitePlugin({
145
+ bake: true,
146
+ extract: false,
147
+ ssr: false,
148
+ addComponentName: true,
149
+ });
150
+
151
+ const code = `
152
+ import { styled } from '@compiled/react';
153
+
154
+ export const Button = styled.button({ color: 'green' });
155
+ `;
156
+
157
+ const result = await plugin.transform!(code, 'test.tsx');
158
+
159
+ expect(result).toBeTruthy();
160
+ });
161
+
162
+ it('should handle keyframes', async () => {
163
+ const plugin = compiledVitePlugin();
164
+ const code = `
165
+ import { keyframes, css } from '@compiled/react';
166
+
167
+ const fadeIn = keyframes({
168
+ from: { opacity: 0 },
169
+ to: { opacity: 1 },
170
+ });
171
+
172
+ export const Component = () => (
173
+ <div css={css({ animation: \`\${fadeIn} 0.3s ease-out\` })}>
174
+ Animated
175
+ </div>
176
+ );
177
+ `;
178
+
179
+ const result = await plugin.transform!(code, 'test.tsx');
180
+
181
+ expect(result).toBeTruthy();
182
+ if (result && typeof result === 'object' && 'code' in result) {
183
+ // Should contain keyframes reference
184
+ expect(result.code).toContain('keyframes');
185
+ expect(result.code).toContain('opacity');
186
+ }
187
+ });
188
+
189
+ it('should handle cssMap', async () => {
190
+ const plugin = compiledVitePlugin();
191
+ const code = `
192
+ import { cssMap } from '@compiled/react';
193
+
194
+ const styles = cssMap({
195
+ primary: { backgroundColor: '#0052CC', color: 'white' },
196
+ secondary: { backgroundColor: '#E0E0E0', color: 'black' },
197
+ });
198
+
199
+ export const Component = ({ variant }) => (
200
+ <div css={styles[variant]}>
201
+ Button
202
+ </div>
203
+ );
204
+ `;
205
+
206
+ const result = await plugin.transform!(code, 'test.tsx');
207
+
208
+ expect(result).toBeTruthy();
209
+ if (result && typeof result === 'object' && 'code' in result) {
210
+ // Should contain the color values (normalized to lowercase)
211
+ expect(result.code).toContain('#0052cc');
212
+ expect(result.code).toContain('#fff');
213
+ }
214
+ });
215
+
216
+ it('should handle ClassNames component', async () => {
217
+ const plugin = compiledVitePlugin();
218
+ const code = `
219
+ import { ClassNames } from '@compiled/react';
220
+
221
+ export const Component = () => (
222
+ <ClassNames>
223
+ {({ css, style }) => (
224
+ <div
225
+ style={style}
226
+ className={css({ fontSize: '20px', fontWeight: 'bold' })}
227
+ >
228
+ Dynamic
229
+ </div>
230
+ )}
231
+ </ClassNames>
232
+ );
233
+ `;
234
+
235
+ const result = await plugin.transform!(code, 'test.tsx');
236
+
237
+ expect(result).toBeTruthy();
238
+ if (result && typeof result === 'object' && 'code' in result) {
239
+ expect(result.code).toContain('font-size');
240
+ expect(result.code).toContain('font-weight');
241
+ }
242
+ });
243
+
244
+ it('should handle nested pseudo-selectors', async () => {
245
+ const plugin = compiledVitePlugin();
246
+ const code = `
247
+ import { css } from '@compiled/react';
248
+
249
+ export const Component = () => (
250
+ <div css={css({
251
+ color: 'blue',
252
+ ':hover': { color: 'red' },
253
+ ':focus': { outline: '2px solid blue' },
254
+ })}>
255
+ Interactive
256
+ </div>
257
+ );
258
+ `;
259
+
260
+ const result = await plugin.transform!(code, 'test.tsx');
261
+
262
+ expect(result).toBeTruthy();
263
+ if (result && typeof result === 'object' && 'code' in result) {
264
+ expect(result.code).toContain('color:blue');
265
+ expect(result.code).toContain(':hover');
266
+ expect(result.code).toContain('color:red');
267
+ expect(result.code).toContain(':focus');
268
+ }
269
+ });
270
+
271
+ it('should handle media queries', async () => {
272
+ const plugin = compiledVitePlugin();
273
+ const code = `
274
+ import { css } from '@compiled/react';
275
+
276
+ export const Component = () => (
277
+ <div css={css({
278
+ padding: '16px',
279
+ '@media (min-width: 768px)': {
280
+ padding: '32px',
281
+ },
282
+ })}>
283
+ Responsive
284
+ </div>
285
+ );
286
+ `;
287
+
288
+ const result = await plugin.transform!(code, 'test.tsx');
289
+
290
+ expect(result).toBeTruthy();
291
+ if (result && typeof result === 'object' && 'code' in result) {
292
+ expect(result.code).toContain('padding');
293
+ expect(result.code).toContain('@media');
294
+ expect(result.code).toContain('min-width');
295
+ }
296
+ });
297
+
298
+ it('should handle template literal styles', async () => {
299
+ const plugin = compiledVitePlugin();
300
+ const code = `
301
+ import { css } from '@compiled/react';
302
+
303
+ export const Component = () => (
304
+ <div css={css\`
305
+ color: purple;
306
+ font-size: 18px;
307
+ &:hover {
308
+ color: darkpurple;
309
+ }
310
+ \`}>
311
+ Styled
312
+ </div>
313
+ );
314
+ `;
315
+
316
+ const result = await plugin.transform!(code, 'test.tsx');
317
+
318
+ expect(result).toBeTruthy();
319
+ if (result && typeof result === 'object' && 'code' in result) {
320
+ expect(result.code).toContain('color:purple');
321
+ expect(result.code).toContain('font-size');
322
+ }
323
+ });
324
+
325
+ it('should handle styled component with template literal', async () => {
326
+ const plugin = compiledVitePlugin();
327
+ const code = `
328
+ import { styled } from '@compiled/react';
329
+
330
+ export const Card = styled.div\`
331
+ background: white;
332
+ border-radius: 8px;
333
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
334
+ \`;
335
+ `;
336
+
337
+ const result = await plugin.transform!(code, 'test.tsx');
338
+
339
+ expect(result).toBeTruthy();
340
+ if (result && typeof result === 'object' && 'code' in result) {
341
+ expect(result.code).toContain('background');
342
+ expect(result.code).toContain('border-radius');
343
+ expect(result.code).toContain('box-shadow');
344
+ }
345
+ });
346
+ });
package/src/index.ts ADDED
@@ -0,0 +1,276 @@
1
+ import { parseAsync, transformFromAstAsync } from '@babel/core';
2
+ import type { PluginOptions as BabelPluginOptions } from '@compiled/babel-plugin';
3
+ import type {
4
+ PluginOptions as BabelStripRuntimePluginOptions,
5
+ BabelFileMetadata,
6
+ } from '@compiled/babel-plugin-strip-runtime';
7
+ import { sort } from '@compiled/css';
8
+ import { DEFAULT_IMPORT_SOURCES, DEFAULT_PARSER_BABEL_PLUGINS, toBoolean } from '@compiled/utils';
9
+ import type { OutputAsset, OutputBundle } from 'rollup';
10
+
11
+ import type { PluginOptions } from './types';
12
+ import { createDefaultResolver } from './utils';
13
+
14
+ /**
15
+ * Compiled Vite plugin.
16
+ *
17
+ * Transforms CSS-in-JS to atomic CSS at build time using Babel.
18
+ *
19
+ * @param userOptions - Plugin configuration options
20
+ * @returns Vite plugin object
21
+ */
22
+ export default function compiledVitePlugin(userOptions: PluginOptions = {}): any {
23
+ const options: PluginOptions = {
24
+ // Vite-specific
25
+ bake: true,
26
+ extract: false,
27
+ transformerBabelPlugins: undefined,
28
+ ssr: false,
29
+ extractStylesToDirectory: undefined,
30
+ sortShorthand: true,
31
+
32
+ // Babel-inherited
33
+ importReact: true,
34
+ nonce: undefined,
35
+ importSources: undefined,
36
+ optimizeCss: true,
37
+ resolver: undefined,
38
+ extensions: undefined,
39
+ parserBabelPlugins: undefined,
40
+ addComponentName: false,
41
+ classNameCompressionMap: undefined,
42
+ processXcss: undefined,
43
+ increaseSpecificity: undefined,
44
+ sortAtRules: true,
45
+ classHashPrefix: undefined,
46
+ flattenMultipleSelectors: undefined,
47
+
48
+ ...userOptions,
49
+ };
50
+
51
+ // Storage for collected style rules during transformation
52
+ const collectedStyleRules = new Set<string>();
53
+
54
+ // Store the emitted CSS filename for HTML injection
55
+ // This gets set in generateBundle after the file is emitted
56
+ let extractedCssFileName: string | undefined;
57
+
58
+ // Name used for the extracted CSS asset
59
+ const EXTRACTED_CSS_NAME = 'compiled-extracted.css';
60
+
61
+ return {
62
+ name: '@compiled/vite-plugin',
63
+ enforce: 'pre', // Run before other plugins
64
+
65
+ async transform(code: string, id: string): Promise<any> {
66
+ // Filter out node_modules (except for specific includes if needed)
67
+ if (id.includes('/node_modules/@compiled/react')) {
68
+ return null;
69
+ }
70
+
71
+ // Only process JS/TS/JSX/TSX files
72
+ if (!/\.[jt]sx?$/.test(id)) {
73
+ return null;
74
+ }
75
+
76
+ const importSources = [...DEFAULT_IMPORT_SOURCES, ...(options.importSources || [])];
77
+
78
+ // Bail early if Compiled (via an importSource) isn't in the module
79
+ if (!importSources.some((name) => code.includes(name))) {
80
+ return null;
81
+ }
82
+
83
+ try {
84
+ const includedFiles: string[] = [];
85
+
86
+ // Parse to AST using Babel
87
+ const ast = await parseAsync(code, {
88
+ filename: id,
89
+ babelrc: false,
90
+ configFile: false,
91
+ caller: { name: 'compiled' },
92
+ rootMode: 'upward-optional',
93
+ parserOpts: {
94
+ plugins: options.parserBabelPlugins ?? DEFAULT_PARSER_BABEL_PLUGINS,
95
+ },
96
+ plugins: options.transformerBabelPlugins ?? undefined,
97
+ });
98
+
99
+ if (!ast) {
100
+ return null;
101
+ }
102
+
103
+ // Disable stylesheet extraction in development mode
104
+ const isDevelopment = process.env.NODE_ENV === 'development';
105
+ const extract = options.extract && !isDevelopment;
106
+
107
+ // Transform using the Compiled Babel Plugin
108
+ const result = await transformFromAstAsync(ast, code, {
109
+ babelrc: false,
110
+ configFile: false,
111
+ sourceMaps: true,
112
+ filename: id,
113
+ parserOpts: {
114
+ plugins: options.parserBabelPlugins ?? DEFAULT_PARSER_BABEL_PLUGINS,
115
+ },
116
+ plugins: [
117
+ ...(options.transformerBabelPlugins ?? []),
118
+ options.bake && [
119
+ '@compiled/babel-plugin',
120
+ {
121
+ ...options,
122
+ // Turn off compressing class names if stylesheet extraction is off
123
+ classNameCompressionMap: extract && options.classNameCompressionMap,
124
+ onIncludedFiles: (files: string[]) => includedFiles.push(...files),
125
+ resolver: options.resolver ? options.resolver : createDefaultResolver(options),
126
+ cache: false,
127
+ } as BabelPluginOptions,
128
+ ],
129
+ extract && [
130
+ '@compiled/babel-plugin-strip-runtime',
131
+ {
132
+ compiledRequireExclude: options.ssr || extract,
133
+ extractStylesToDirectory: options.extractStylesToDirectory,
134
+ } as BabelStripRuntimePluginOptions,
135
+ ],
136
+ ].filter(toBoolean),
137
+ caller: {
138
+ name: 'compiled',
139
+ },
140
+ });
141
+
142
+ // Store metadata for CSS extraction if enabled
143
+ if (extract && result?.metadata) {
144
+ const metadata = result.metadata as BabelFileMetadata;
145
+ // Collect style rules from this file
146
+ if (metadata.styleRules && metadata.styleRules.length > 0) {
147
+ metadata.styleRules.forEach((rule: string) => collectedStyleRules.add(rule));
148
+ }
149
+ }
150
+
151
+ // Return transformed code and source map
152
+ if (result?.code) {
153
+ return {
154
+ code: result.code,
155
+ map: result.map ?? null,
156
+ };
157
+ }
158
+
159
+ return null;
160
+ } catch (e: unknown) {
161
+ // Throw error to be displayed by Vite
162
+ const error = e as Error;
163
+ this.error({
164
+ message: `[@compiled/vite-plugin] Failed to transform: ${error.message}`,
165
+ stack: error.stack,
166
+ });
167
+ }
168
+ },
169
+
170
+ generateBundle(_outputOptions: any, bundle: OutputBundle) {
171
+ // Post-process CSS assets to apply Compiled's sorting and deduplication
172
+ const isDevelopment = process.env.NODE_ENV === 'development';
173
+ const extract = options.extract && !isDevelopment;
174
+
175
+ // Process each CSS asset in the bundle
176
+ for (const [fileName, output] of Object.entries(bundle)) {
177
+ // Only process CSS assets
178
+ if (!fileName.endsWith('.css') || output.type !== 'asset') {
179
+ continue;
180
+ }
181
+
182
+ const asset = output as OutputAsset;
183
+ const cssContent = asset.source as string;
184
+
185
+ // Check if this CSS contains Compiled atomic classes (starts with underscore)
186
+ // This is a heuristic to identify CSS that came from .compiled.css files
187
+ if (cssContent.includes('._')) {
188
+ try {
189
+ // Apply Compiled's CSS sorting and deduplication
190
+ const sortConfig = {
191
+ sortAtRulesEnabled: options.sortAtRules,
192
+ sortShorthandEnabled: options.sortShorthand,
193
+ };
194
+
195
+ const sortedCss = sort(cssContent, sortConfig);
196
+
197
+ // Update the asset with sorted CSS
198
+ asset.source = sortedCss;
199
+ } catch (error) {
200
+ const err = error as Error;
201
+ this.warn({
202
+ message: `[@compiled/vite-plugin] Failed to sort CSS in ${fileName}: ${err.message}`,
203
+ });
204
+ }
205
+ }
206
+ }
207
+
208
+ // Also emit extracted styles if we collected any from local code
209
+ if (extract && collectedStyleRules.size > 0) {
210
+ try {
211
+ // Convert Set to array and sort for determinism
212
+ const allRules = Array.from(collectedStyleRules).sort();
213
+
214
+ // Join all rules and apply CSS sorting
215
+ const combinedCss = allRules.join('\n');
216
+ const sortConfig = {
217
+ sortAtRulesEnabled: options.sortAtRules,
218
+ sortShorthandEnabled: options.sortShorthand,
219
+ };
220
+
221
+ const sortedCss = sort(combinedCss, sortConfig);
222
+
223
+ // Emit the CSS file with content-based naming
224
+ // Vite will add a content hash to the filename automatically
225
+ this.emitFile({
226
+ type: 'asset',
227
+ name: EXTRACTED_CSS_NAME,
228
+ source: sortedCss,
229
+ });
230
+
231
+ // Mark that we've emitted the file so transformIndexHtml can inject it
232
+ // The actual filename will be determined in transformIndexHtml from the bundle
233
+ extractedCssFileName = EXTRACTED_CSS_NAME;
234
+ } catch (error) {
235
+ const err = error as Error;
236
+ this.warn({
237
+ message: `[@compiled/vite-plugin] Failed to generate CSS bundle: ${err.message}`,
238
+ });
239
+ }
240
+ }
241
+ },
242
+
243
+ transformIndexHtml(
244
+ _html: string,
245
+ ctx: { bundle?: OutputBundle; [key: string]: any }
246
+ ): { tag: string; attrs: Record<string, string>; injectTo: string }[] {
247
+ // Inject the extracted CSS file into HTML if it was emitted
248
+ if (!extractedCssFileName || !ctx.bundle) {
249
+ return [];
250
+ }
251
+
252
+ // Find the emitted CSS asset in the bundle by its name
253
+ // The actual fileName will have a content hash added by Vite
254
+ const cssAsset = Object.values(ctx.bundle).find(
255
+ (asset): asset is OutputAsset => asset.type === 'asset' && asset.name === EXTRACTED_CSS_NAME
256
+ );
257
+
258
+ if (!cssAsset) {
259
+ return [];
260
+ }
261
+
262
+ return [
263
+ {
264
+ tag: 'link',
265
+ attrs: {
266
+ rel: 'stylesheet',
267
+ href: `/${cssAsset.fileName}`,
268
+ },
269
+ injectTo: 'head',
270
+ },
271
+ ];
272
+ },
273
+ };
274
+ }
275
+
276
+ export type { PluginOptions as VitePluginOptions };
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { PluginItem } from '@babel/core';
2
+ import type { PluginOptions as BabelPluginOptions } from '@compiled/babel-plugin';
3
+
4
+ /**
5
+ * Vite plugin options extending the babel-plugin options with Vite-specific configuration.
6
+ */
7
+ export interface PluginOptions extends BabelPluginOptions {
8
+ /**
9
+ * Converts your source code into a Compiled component.
10
+ * Defaults to `true`.
11
+ */
12
+ bake?: boolean;
13
+
14
+ /**
15
+ * Extracts to CSS when `true`.
16
+ * Defaults to `false`.
17
+ */
18
+ extract?: boolean;
19
+
20
+ /**
21
+ * List of transformer babel plugins to be applied to evaluated files
22
+ *
23
+ * See the [babel docs](https://babeljs.io/docs/en/plugins/#transform-plugins)
24
+ */
25
+ transformerBabelPlugins?: PluginItem[];
26
+
27
+ /**
28
+ * Build in a node environment.
29
+ * Defaults to `false`.
30
+ */
31
+ ssr?: boolean;
32
+
33
+ /**
34
+ * When set, extract styles to an external CSS file.
35
+ */
36
+ extractStylesToDirectory?: { source: string; dest: string };
37
+
38
+ /**
39
+ * Whether to sort shorthand and longhand properties,
40
+ * eg. `margin` before `margin-top` for enforced determinism.
41
+ * Defaults to `true`.
42
+ */
43
+ sortShorthand?: boolean;
44
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,40 @@
1
+ import * as fs from 'fs';
2
+ import { dirname } from 'path';
3
+
4
+ import type { Resolver } from '@compiled/babel-plugin';
5
+
6
+ import type { PluginOptions } from './types';
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
9
+ const enhancedResolve = require('enhanced-resolve');
10
+
11
+ // Handle both ESM and CJS imports
12
+ const { CachedInputFileSystem, ResolverFactory } = enhancedResolve.CachedInputFileSystem
13
+ ? enhancedResolve
14
+ : enhancedResolve.default || enhancedResolve;
15
+
16
+ /**
17
+ * Creates a default resolver using enhanced-resolve.
18
+ * This is the same resolver used by webpack and other bundlers,
19
+ * providing robust module resolution with caching.
20
+ *
21
+ * @param config - Vite plugin configuration
22
+ * @returns Resolver compatible with @compiled/babel-plugin
23
+ */
24
+ export function createDefaultResolver(config: PluginOptions): Resolver {
25
+ const resolver = ResolverFactory.createResolver({
26
+ fileSystem: new CachedInputFileSystem(fs, 4000),
27
+ ...(config.extensions && {
28
+ extensions: config.extensions,
29
+ }),
30
+ // This makes the resolver invoke the callback synchronously
31
+ useSyncFileSystemCalls: true,
32
+ });
33
+
34
+ return {
35
+ // The resolver needs to be synchronous, as babel plugins must be synchronous
36
+ resolveSync(context: string, request: string) {
37
+ return resolver.resolveSync({}, dirname(context), request) as string;
38
+ },
39
+ };
40
+ }