@tamagui/next-plugin 1.0.1-beta.20 → 1.0.1-beta.200

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