@tamagui/next-plugin 1.0.1-beta.99 → 1.0.1-rc.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.
@@ -1,16 +1,22 @@
1
- import path from 'path'
1
+ import { existsSync, fstat } from 'fs'
2
+ import path, { dirname, join } from 'path'
2
3
 
3
4
  import type { TamaguiOptions } from '@tamagui/static'
4
5
  import browserslist from 'browserslist'
5
6
  import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
7
+ import buildResolver from 'esm-resolve'
6
8
  import MiniCSSExtractPlugin from 'mini-css-extract-plugin'
7
9
  import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css'
8
10
  import { getGlobalCssLoader } from 'next/dist/build/webpack/config/blocks/css/loaders'
9
- import { shouldExclude as shouldExcludeDefault } from 'tamagui-loader'
11
+ import { TamaguiPlugin, shouldExclude as shouldExcludeDefault } from 'tamagui-loader'
10
12
  import webpack from 'webpack'
11
13
 
12
14
  export type WithTamaguiProps = TamaguiOptions & {
15
+ useReactNativeWebLite: boolean
16
+ disableFontSupport?: boolean
17
+ enableCSSOptimizations?: boolean
13
18
  aliasReactPackages?: boolean
19
+ includeCSSTest?: RegExp | ((path: string) => boolean)
14
20
  shouldExtract?: (path: string, projectRoot: string) => boolean | undefined
15
21
  shouldExcludeFromServer?: (props: {
16
22
  context: string
@@ -20,21 +26,16 @@ export type WithTamaguiProps = TamaguiOptions & {
20
26
  }
21
27
 
22
28
  export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
23
- // allows configuration
24
- const shouldExclude = (path: string, projectRoot: string) => {
25
- const res = tamaguiOptions.shouldExtract?.(path, projectRoot)
26
- if (typeof res === 'boolean') {
27
- return !res
28
- }
29
- return shouldExcludeDefault(path, projectRoot)
30
- }
31
-
32
29
  return (nextConfig: any = {}) => {
33
30
  return {
34
31
  ...nextConfig,
35
32
  webpack: (webpackConfig: any, options: any) => {
36
33
  const { dir, config, dev, isServer } = options
37
34
 
35
+ const resolver = buildResolver(join(dir, 'index.js'), {
36
+ constraints: 'node',
37
+ })
38
+
38
39
  // @ts-ignore
39
40
  if (typeof globalThis['__DEV__'] === 'undefined') {
40
41
  // @ts-ignore
@@ -42,41 +43,104 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
42
43
  }
43
44
 
44
45
  const isNext12 = typeof options.config?.swcMinify === 'boolean'
46
+ if (!isNext12) {
47
+ throw new Error(`Next.js 12 only supported`)
48
+ }
49
+
45
50
  const prefix = `${isServer ? ' ssr ' : ' web '} |`
46
51
 
47
- const safeResolves = (
48
- ...resolves: ([string, string] | [string, string, [string, string]])[]
49
- ) => {
50
- let res: string[][] = []
51
- for (const [out, mod, replace] of resolves) {
52
+ const safeResolves = (resolves: [string, string][], multiple = false) => {
53
+ const res: string[][] = []
54
+ for (const [out, mod] of resolves) {
55
+ if (out.endsWith('$')) {
56
+ res.push([out, mod])
57
+ continue
58
+ }
52
59
  try {
53
- const next = [out, require.resolve(mod)]
54
- if (replace) {
55
- next[1] = next[1].replace(...replace)
60
+ res.push([out, resolveEsm(mod)])
61
+ if (multiple) {
62
+ res.push([out, resolveEsm(mod, true)])
56
63
  }
57
- res.push(next)
58
64
  } catch (err) {
59
- // console.log(prefix, `withTamagui skipping resolving ${out}`)
65
+ if (out.includes(`@gorhom/bottom-sheet`)) {
66
+ continue
67
+ }
68
+ if (process.env.DEBUG?.startsWith('tamagui')) {
69
+ // eslint-disable-next-line no-console
70
+ console.log(prefix, `withTamagui skipping resolving ${out}`, err)
71
+ }
60
72
  }
61
73
  }
62
- return Object.fromEntries(res)
74
+ return res
63
75
  }
64
76
 
65
- const alias = {
66
- ...(webpackConfig.resolve.alias || {}),
67
- ...safeResolves(
68
- ['react-native-svg', 'react-native-svg-web'],
77
+ const resolveEsm = (relativePath: string, onlyRequire = false) => {
78
+ if (isServer || onlyRequire) {
79
+ return require.resolve(relativePath)
80
+ }
81
+ const esm = resolver(relativePath)
82
+ return esm ? path.join(dir, esm) : require.resolve(relativePath)
83
+ }
84
+
85
+ const SEP = path.sep
86
+
87
+ // automatically compile our given components
88
+ const componentsFullPaths = safeResolves(
89
+ tamaguiOptions.components.map(
90
+ (moduleName) => [moduleName, moduleName] as [string, string]
91
+ ),
92
+ true
93
+ )
94
+
95
+ const componentsBaseDirs = componentsFullPaths.map(([_, fullPath]) => {
96
+ let rootPath = dirname(fullPath as string)
97
+ while (rootPath.length > 1) {
98
+ const pkg = join(rootPath, 'package.json')
99
+ const hasPkg = existsSync(pkg)
100
+ if (hasPkg) {
101
+ return rootPath
102
+ } else {
103
+ rootPath = join(rootPath, '..')
104
+ }
105
+ }
106
+ throw new Error(`Couldn't find package.json in any path above: ${fullPath}`)
107
+ })
108
+
109
+ function isInComponentModule(fullPath: string) {
110
+ return componentsBaseDirs.some((componentDir) => fullPath.startsWith(componentDir))
111
+ }
112
+
113
+ // allows configuration
114
+ const shouldExclude = (path: string, projectRoot: string) => {
115
+ const res = tamaguiOptions.shouldExtract?.(path, projectRoot)
116
+ if (typeof res === 'boolean') {
117
+ return !res
118
+ }
119
+ if (isInComponentModule(path)) {
120
+ return false
121
+ }
122
+ return shouldExcludeDefault(path, projectRoot)
123
+ }
124
+
125
+ const rnw = tamaguiOptions.useReactNativeWebLite
126
+ ? 'react-native-web-lite'
127
+ : 'react-native-web'
128
+
129
+ const tamaguiAliases = Object.fromEntries(
130
+ safeResolves([
131
+ ['@tamagui/core/reset.css', '@tamagui/core/reset.css'],
132
+ ['@tamagui/core', '@tamagui/core'],
133
+ ['react-native-svg', '@tamagui/react-native-svg'],
69
134
  // fixes https://github.com/kentcdodds/mdx-bundler/issues/143
70
135
  ['react/jsx-runtime.js', 'react/jsx-runtime'],
71
136
  ['react/jsx-runtime', 'react/jsx-runtime'],
72
137
  ['react/jsx-dev-runtime.js', 'react/jsx-dev-runtime'],
73
138
  ['react/jsx-dev-runtime', 'react/jsx-dev-runtime'],
74
- ['react-native$', 'react-native-web'],
75
139
  ['react-native-reanimated', 'react-native-reanimated'],
76
- ['expo-linear-gradient', '@tamagui/expo-linear-gradient'],
77
- ['react-native-web$', 'react-native-web'],
140
+ ['react-native$', rnw],
141
+ ['react-native-web$', rnw],
78
142
  ['@testing-library/react-native', '@tamagui/proxy-worm'],
79
- ['@gorhom/bottom-sheet$', '@gorhom/bottom-sheet', ['commonjs', 'module']],
143
+ ['@gorhom/bottom-sheet$', '@gorhom/bottom-sheet'],
80
144
  // fix reanimated 3
81
145
  ['react-native/Libraries/Renderer/shims/ReactFabric', '@tamagui/proxy-worm'],
82
146
  ...(tamaguiOptions.aliasReactPackages
@@ -84,12 +148,24 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
84
148
  ['react', 'react'],
85
149
  ['react-dom', 'react-dom'],
86
150
  ] as any)
87
- : [])
88
- ),
89
- // expo fix https://github.com/expo/expo/issues/9999
90
- 'react-native-web/src': require.resolve('react-native-web/dist'),
91
- // react: require.resolve('react'),
92
- // 'react-dom': require.resolve('react-dom'),
151
+ : []),
152
+ ])
153
+ )
154
+
155
+ const alias = {
156
+ ...(webpackConfig.resolve.alias || {}),
157
+ ...tamaguiAliases,
158
+ }
159
+
160
+ if (process.env.DEBUG) {
161
+ // eslint-disable-next-line no-console
162
+ console.log('Tamagui alias:', alias)
163
+ }
164
+
165
+ if (process.env.ANALYZE === 'true') {
166
+ Object.assign(webpackConfig.optimization, {
167
+ concatenateModules: false,
168
+ })
93
169
  }
94
170
 
95
171
  webpackConfig.resolve.alias = alias
@@ -105,16 +181,14 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
105
181
  const excludeExports = tamaguiOptions.excludeReactNativeWebExports
106
182
  if (Array.isArray(excludeExports)) {
107
183
  try {
108
- const regexStr = `\/react-native-web\/.*(${excludeExports.join('|')}).*\/`
184
+ const regexStr = `react-native-web(-lite)?/.*(${excludeExports.join('|')}).*js`
109
185
  const regex = new RegExp(regexStr)
110
186
  // console.log(prefix, 'exclude', regexStr)
111
187
  webpackConfig.plugins.push(
112
- new webpack.NormalModuleReplacementPlugin(
113
- regex,
114
- require.resolve('@tamagui/proxy-worm/empty-react-native-view')
115
- )
188
+ new webpack.NormalModuleReplacementPlugin(regex, resolveEsm('@tamagui/proxy-worm'))
116
189
  )
117
190
  } catch (err) {
191
+ // eslint-disable-next-line no-console
118
192
  console.warn(
119
193
  `Invalid names provided to excludeReactNativeWebExports: ${excludeExports.join(', ')}`
120
194
  )
@@ -123,6 +197,7 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
123
197
 
124
198
  if (process.env.IGNORE_TS_CONFIG_PATHS) {
125
199
  if (process.env.DEBUG) {
200
+ // eslint-disable-next-line no-console
126
201
  console.log(prefix, 'ignoring tsconfig paths')
127
202
  }
128
203
  if (webpackConfig.resolve.plugins[0]) {
@@ -131,9 +206,7 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
131
206
  }
132
207
  }
133
208
 
134
- // TODO document and make configurable
135
- // replaces minifier with css-minimizer-webpack-plugin which handles deduping atomic styles
136
- if (!dev) {
209
+ if (!dev && tamaguiOptions.enableCSSOptimizations) {
137
210
  const cssMin = webpackConfig.optimization.minimizer.find((x) =>
138
211
  x.toString().includes('css-minimizer-plugin')
139
212
  )
@@ -146,13 +219,9 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
146
219
  webpackConfig.optimization.minimizer.push(new CssMinimizerPlugin())
147
220
  }
148
221
 
149
- webpackConfig.resolve.extensions = [
150
- ...new Set(['.web.tsx', '.web.ts', '.web.js', ...webpackConfig.resolve.extensions]),
151
- ]
152
-
153
- // look for compiled js with jsx intact as specified by module:jsx
154
- webpackConfig.resolve.mainFields.unshift('module:jsx')
155
-
222
+ /**
223
+ * Server react-native compat
224
+ */
156
225
  if (isServer) {
157
226
  const externalize = (context: string, request: string) => {
158
227
  const fullPath = request[0] === '.' ? path.join(context, request) : request
@@ -168,32 +237,42 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
168
237
  }
169
238
  }
170
239
 
240
+ if (isInComponentModule(fullPath)) {
241
+ return false
242
+ }
243
+
244
+ if (fullPath.includes('react-native-web-lite')) {
245
+ // always inline react-native-web-lite due to errors where next.js resolved the path to esm
246
+ return false
247
+ }
248
+
171
249
  // must inline react-native so we can alias to react-native-web
172
- if (fullPath === 'react-native' || fullPath.startsWith('react-native/')) {
250
+ if (fullPath === 'react-native' || fullPath.startsWith(`react-native${SEP}`)) {
173
251
  return false
174
252
  }
175
253
 
176
254
  if (
177
255
  // feather icons uses react-native-svg which needs to be aliased
178
- // fullPath.includes('/feather-icons/') ||
256
+ // fullPath.includes('/lucide-icons/') ||
179
257
  fullPath.startsWith('react-native-web') ||
180
- fullPath.includes('node_modules/react-native-web') ||
181
- /^(react-dom|react)\/$/.test(fullPath)
258
+ fullPath.includes(`node_modules${SEP}react-native-web`) ||
259
+ new RegExp(`^(react-dom|react)${SEP}$`).test(fullPath)
182
260
  ) {
183
261
  return `commonjs ${fullPath}`
184
262
  }
185
263
  if (
186
- fullPath.includes('/moti/') ||
187
- fullPath.includes('/solito/') ||
264
+ fullPath.startsWith('moti') ||
265
+ fullPath.startsWith('solito') ||
188
266
  fullPath === 'tamagui' ||
189
- fullPath.startsWith('@tamagui/') ||
267
+ fullPath.startsWith('@tamagui') ||
190
268
  fullPath === 'react-native-safe-area-context' ||
269
+ fullPath === 'expo-linear-gradient' ||
191
270
  fullPath.startsWith('@react-navigation') ||
192
- fullPath === '@gorhom/bottom-sheet'
271
+ fullPath.startsWith('@gorhom')
193
272
  ) {
194
273
  return
195
274
  }
196
- if (/^\@?react-native-/.test(request)) {
275
+ if (/^@?react-native-/.test(request)) {
197
276
  return false
198
277
  }
199
278
  return true
@@ -228,115 +307,102 @@ export const withTamagui = (tamaguiOptions: WithTamaguiProps) => {
228
307
  ]
229
308
  }
230
309
 
231
- const oneOfRule = webpackConfig.module.rules.find((x) => !!x.oneOf)
310
+ const cssRules = webpackConfig.module.rules.find(
311
+ (rule) =>
312
+ Array.isArray(rule.oneOf) &&
313
+ rule.oneOf.some(
314
+ ({ test }) =>
315
+ typeof test === 'object' &&
316
+ typeof test.test === 'function' &&
317
+ test.test('filename.css')
318
+ )
319
+ ).oneOf
232
320
 
233
- const cssLoader = getGlobalCssLoader(
234
- // @ts-ignore
235
- {
236
- assetPrefix: options.config.assetPrefix || config.assetPrefix,
237
- future: nextConfig.future,
238
- experimental: nextConfig.experimental || {},
239
- isClient: !isServer,
240
- isServer,
241
- isDevelopment: dev,
242
- },
243
- // @ts-ignore
244
- () => lazyPostCSS(dir, getSupportedBrowsers(dir, dev)),
245
- []
246
- )
321
+ /**
322
+ * Font Support
323
+ */
324
+ if (cssRules) {
325
+ if (!tamaguiOptions.disableFontSupport) {
326
+ // fonts support
327
+ cssRules.unshift({
328
+ test: /\.(woff(2)?|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
329
+ use: [
330
+ {
331
+ loader: require.resolve('url-loader'),
332
+ options: {
333
+ limit: nextConfig.inlineFontLimit || 1024,
334
+ fallback: require.resolve('file-loader'),
335
+ publicPath: `${nextConfig.assetPrefix || ''}/_next/static/chunks/fonts/`,
336
+ outputPath: `${isServer ? '../' : ''}static/chunks/fonts/`,
337
+ name: '[name].[ext]',
338
+ },
339
+ },
340
+ ],
341
+ })
342
+ }
343
+
344
+ /**
345
+ * CSS Support
346
+ */
347
+ const cssLoader = getGlobalCssLoader(
348
+ // @ts-ignore
349
+ {
350
+ assetPrefix: options.config.assetPrefix || config.assetPrefix,
351
+ future: nextConfig.future,
352
+ experimental: nextConfig.experimental || {},
353
+ isEdgeRuntime: true,
354
+ isProduction: !dev,
355
+ targetWeb: true,
356
+ isClient: !isServer,
357
+ isServer,
358
+ isDevelopment: dev,
359
+ },
360
+ // @ts-ignore
361
+ () => lazyPostCSS(dir, getSupportedBrowsers(dir, dev)),
362
+ []
363
+ )
364
+
365
+ const cssTest = tamaguiOptions.includeCSSTest ?? /\.css$/
247
366
 
248
- if (oneOfRule) {
249
- if (!dev) {
367
+ if (!dev && tamaguiOptions.enableCSSOptimizations) {
250
368
  const postCSSLoader = cssLoader[cssLoader.length - 1]
251
369
  // replace nextjs picky style rules with simple minicssextract
252
- oneOfRule.oneOf.unshift({
253
- test: /\.css$/i,
370
+ cssRules.unshift({
371
+ test: cssTest,
254
372
  use: [MiniCSSExtractPlugin.loader, 'css-loader', postCSSLoader],
255
373
  sideEffects: true,
256
374
  })
257
375
  webpackConfig.plugins.push(
258
376
  new MiniCSSExtractPlugin({
259
- filename: 'static/css/[name].[contenthash].css',
377
+ filename: 'static/css/[contenthash].css',
260
378
  ignoreOrder: true,
261
- runtime: false,
262
379
  })
263
380
  )
264
381
  } else {
265
- oneOfRule.oneOf.unshift({
266
- test: /\.css$/i,
382
+ cssRules.unshift({
383
+ test: cssTest,
267
384
  sideEffects: true,
268
385
  use: cssLoader,
269
386
  })
270
387
  }
388
+ cssRules.unshift({
389
+ test: cssTest,
390
+ sideEffects: true,
391
+ use: cssLoader,
392
+ })
271
393
  }
272
394
 
273
- if (isNext12) {
274
- const firstOneOfRule = webpackConfig.module.rules.findIndex((x) => x && !!x.oneOf)
275
- const oneOfJSRules: any[] = webpackConfig.module.rules[firstOneOfRule].oneOf
276
- const afterSWCLoaderIndex =
277
- oneOfJSRules.findIndex(
278
- (x) => x && x.use && x.use.loader === 'next-swc-loader' && x.issuerLayer !== 'api'
279
- ) + 1
280
- const swcLoader = oneOfJSRules[afterSWCLoaderIndex]
281
-
282
- // put an earlier loader where we just do tamagui stuff before regular swc
283
- oneOfJSRules.splice(
284
- afterSWCLoaderIndex,
285
- 0,
286
- {
287
- test: /(bottom-sheet).*\.[tj]sx?$/,
288
- use: [
289
- {
290
- loader: 'babel-loader',
291
- options: {
292
- presets: ['@babel/preset-react'],
293
- plugins: ['react-native-reanimated/plugin'],
294
- },
295
- },
296
- ],
297
- },
298
- {
299
- test: /\.(jsx?|tsx?)$/,
300
- exclude: (path: string) => shouldExclude(path, options.dir),
301
- use: [
302
- ...[].concat(swcLoader.use),
303
- {
304
- loader: 'tamagui-loader',
305
- options: tamaguiOptions,
306
- },
307
- ],
308
- }
309
- )
310
- } else {
311
- // next 11 modify loader
312
- const [first, second, ...rest] = webpackConfig.module.rules
313
- webpackConfig.module.rules = [
314
- first,
315
- second,
316
- {
317
- test: /\.(tsx|jsx)$/,
318
- exclude: (path: string) => shouldExclude(path, options.dir),
319
- use: [
320
- 'thread-loader',
321
- {
322
- loader: 'esbuild-loader',
323
- options: {
324
- loader: 'tsx',
325
- minify: false,
326
- },
327
- },
328
- {
329
- loader: 'tamagui-loader',
330
- options: {
331
- prefixLogs: prefix,
332
- ...tamaguiOptions,
333
- },
334
- },
335
- ],
395
+ webpackConfig.plugins.push(
396
+ new TamaguiPlugin({
397
+ commonjs: isServer,
398
+ exclude: (path: string) => {
399
+ const res = shouldExclude(path, options.dir)
400
+ // console.log(`shouldExclude`, res, path)
401
+ return res
336
402
  },
337
- ...rest,
338
- ]
339
- }
403
+ ...tamaguiOptions,
404
+ })
405
+ )
340
406
 
341
407
  if (typeof nextConfig.webpack === 'function') {
342
408
  return nextConfig.webpack(webpackConfig, options)
@@ -355,7 +421,8 @@ function getSupportedBrowsers(dir, isDevelopment) {
355
421
  path: dir,
356
422
  env: isDevelopment ? 'development' : 'production',
357
423
  })
358
- } catch {}
359
-
424
+ } catch {
425
+ //
426
+ }
360
427
  return browsers
361
428
  }
package/types/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from './withTamagui';
2
- export { patchReactNativeWeb } from '@tamagui/static';
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,10 @@
1
1
  import type { TamaguiOptions } from '@tamagui/static';
2
2
  export declare type WithTamaguiProps = TamaguiOptions & {
3
+ useReactNativeWebLite: boolean;
4
+ disableFontSupport?: boolean;
5
+ enableCSSOptimizations?: boolean;
3
6
  aliasReactPackages?: boolean;
7
+ includeCSSTest?: RegExp | ((path: string) => boolean);
4
8
  shouldExtract?: (path: string, projectRoot: string) => boolean | undefined;
5
9
  shouldExcludeFromServer?: (props: {
6
10
  context: string;
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAK7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"withTamagui.d.ts","sourceRoot":"","sources":["../src/withTamagui.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AASrD,oBAAY,gBAAgB,GAAG,cAAc,GAAG;IAC9C,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAA;IAC1E,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE;QAChC,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;KACjB,KAAK,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CACnC,CAAA;AAED,eAAO,MAAM,WAAW,mBAAoB,gBAAgB,mBAUtC,GAAG,QA6TxB,CAAA"}