@quilted/rollup 0.1.19 → 0.2.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/build/esm/app.mjs +450 -222
  3. package/build/esm/constants.mjs +5 -5
  4. package/build/esm/features/assets.mjs +93 -81
  5. package/build/esm/features/async.mjs +186 -0
  6. package/build/esm/features/css.mjs +26 -39
  7. package/build/esm/features/env.mjs +47 -44
  8. package/build/esm/features/esnext.mjs +57 -0
  9. package/build/esm/features/graphql/transform.mjs +60 -56
  10. package/build/esm/features/graphql.mjs +65 -47
  11. package/build/esm/features/request-router.mjs +6 -4
  12. package/build/esm/features/source-code.mjs +54 -28
  13. package/build/esm/features/system-js.mjs +29 -20
  14. package/build/esm/features/typescript.mjs +13 -10
  15. package/build/esm/features/workers.mjs +173 -0
  16. package/build/esm/index.mjs +3 -2
  17. package/build/esm/module.mjs +69 -62
  18. package/build/esm/package.mjs +275 -84
  19. package/build/esm/server.mjs +118 -0
  20. package/build/esm/shared/browserslist.mjs +141 -16
  21. package/build/esm/shared/magic-module.mjs +9 -7
  22. package/build/esm/shared/package-json.mjs +7 -1
  23. package/build/esm/shared/path.mjs +7 -0
  24. package/build/esm/shared/rollup.mjs +89 -25
  25. package/build/esm/shared/strings.mjs +7 -6
  26. package/build/tsconfig.tsbuildinfo +1 -1
  27. package/build/typescript/app.d.ts +132 -29
  28. package/build/typescript/app.d.ts.map +1 -1
  29. package/build/typescript/features/assets.d.ts +1 -2
  30. package/build/typescript/features/assets.d.ts.map +1 -1
  31. package/build/typescript/features/async.d.ts +10 -0
  32. package/build/typescript/features/async.d.ts.map +1 -0
  33. package/build/typescript/features/css.d.ts +2 -1
  34. package/build/typescript/features/css.d.ts.map +1 -1
  35. package/build/typescript/features/env.d.ts +1 -0
  36. package/build/typescript/features/env.d.ts.map +1 -1
  37. package/build/typescript/features/esnext.d.ts +9 -0
  38. package/build/typescript/features/esnext.d.ts.map +1 -0
  39. package/build/typescript/features/graphql.d.ts +2 -2
  40. package/build/typescript/features/graphql.d.ts.map +1 -1
  41. package/build/typescript/features/source-code.d.ts +9 -3
  42. package/build/typescript/features/source-code.d.ts.map +1 -1
  43. package/build/typescript/features/system-js.d.ts +4 -1
  44. package/build/typescript/features/system-js.d.ts.map +1 -1
  45. package/build/typescript/features/workers.d.ts +52 -0
  46. package/build/typescript/features/workers.d.ts.map +1 -0
  47. package/build/typescript/index.d.ts +3 -2
  48. package/build/typescript/index.d.ts.map +1 -1
  49. package/build/typescript/module.d.ts +24 -6
  50. package/build/typescript/module.d.ts.map +1 -1
  51. package/build/typescript/package.d.ts +196 -4
  52. package/build/typescript/package.d.ts.map +1 -1
  53. package/build/typescript/server.d.ts +98 -0
  54. package/build/typescript/server.d.ts.map +1 -0
  55. package/build/typescript/shared/browserslist.d.ts +20 -3
  56. package/build/typescript/shared/browserslist.d.ts.map +1 -1
  57. package/build/typescript/shared/path.d.ts +2 -0
  58. package/build/typescript/shared/path.d.ts.map +1 -0
  59. package/build/typescript/shared/rollup.d.ts +27 -1
  60. package/build/typescript/shared/rollup.d.ts.map +1 -1
  61. package/configuration/rollup.config.js +40 -0
  62. package/package.json +62 -9
  63. package/source/app.ts +475 -99
  64. package/source/features/assets.ts +5 -7
  65. package/source/features/async.ts +249 -0
  66. package/source/features/css.ts +4 -2
  67. package/source/features/env.ts +6 -0
  68. package/source/features/esnext.ts +70 -0
  69. package/source/features/graphql.ts +4 -2
  70. package/source/features/source-code.ts +27 -9
  71. package/source/features/system-js.ts +25 -2
  72. package/source/features/workers.ts +292 -0
  73. package/source/index.ts +4 -0
  74. package/source/module.ts +45 -19
  75. package/source/package.ts +394 -36
  76. package/source/server.ts +245 -0
  77. package/source/shared/browserslist.ts +208 -18
  78. package/source/shared/path.ts +5 -0
  79. package/source/shared/rollup.ts +102 -4
  80. package/tsconfig.json +6 -2
  81. package/build/cjs/app.cjs +0 -441
  82. package/build/cjs/constants.cjs +0 -13
  83. package/build/cjs/features/assets.cjs +0 -240
  84. package/build/cjs/features/css.cjs +0 -71
  85. package/build/cjs/features/env.cjs +0 -135
  86. package/build/cjs/features/graphql/transform.cjs +0 -186
  87. package/build/cjs/features/graphql.cjs +0 -86
  88. package/build/cjs/features/request-router.cjs +0 -31
  89. package/build/cjs/features/source-code.cjs +0 -54
  90. package/build/cjs/features/system-js.cjs +0 -36
  91. package/build/cjs/features/typescript.cjs +0 -56
  92. package/build/cjs/index.cjs +0 -13
  93. package/build/cjs/module.cjs +0 -121
  94. package/build/cjs/package.cjs +0 -170
  95. package/build/cjs/shared/browserslist.cjs +0 -25
  96. package/build/cjs/shared/magic-module.cjs +0 -32
  97. package/build/cjs/shared/package-json.cjs +0 -31
  98. package/build/cjs/shared/rollup.cjs +0 -72
  99. package/build/cjs/shared/strings.cjs +0 -16
  100. package/build/esnext/app.esnext +0 -414
  101. package/build/esnext/constants.esnext +0 -7
  102. package/build/esnext/features/assets.esnext +0 -215
  103. package/build/esnext/features/css.esnext +0 -69
  104. package/build/esnext/features/env.esnext +0 -112
  105. package/build/esnext/features/graphql/transform.esnext +0 -181
  106. package/build/esnext/features/graphql.esnext +0 -84
  107. package/build/esnext/features/request-router.esnext +0 -29
  108. package/build/esnext/features/source-code.esnext +0 -51
  109. package/build/esnext/features/system-js.esnext +0 -33
  110. package/build/esnext/features/typescript.esnext +0 -34
  111. package/build/esnext/index.esnext +0 -3
  112. package/build/esnext/module.esnext +0 -100
  113. package/build/esnext/package.esnext +0 -148
  114. package/build/esnext/shared/browserslist.esnext +0 -23
  115. package/build/esnext/shared/magic-module.esnext +0 -30
  116. package/build/esnext/shared/package-json.esnext +0 -10
  117. package/build/esnext/shared/rollup.esnext +0 -49
  118. package/build/esnext/shared/strings.esnext +0 -14
  119. package/build/typescript/env.d.ts +0 -55
  120. package/build/typescript/env.d.ts.map +0 -1
  121. package/build/typescript/graphql/transform.d.ts +0 -17
  122. package/build/typescript/graphql/transform.d.ts.map +0 -1
  123. package/build/typescript/graphql.d.ts +0 -6
  124. package/build/typescript/graphql.d.ts.map +0 -1
  125. package/build/typescript/request-router.d.ts +0 -15
  126. package/build/typescript/request-router.d.ts.map +0 -1
  127. package/build/typescript/shared/source-code.d.ts +0 -5
  128. package/build/typescript/shared/source-code.d.ts.map +0 -1
  129. package/quilt.project.ts +0 -5
package/source/app.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as path from 'path';
2
2
  import * as fs from 'fs/promises';
3
3
  import {glob} from 'glob';
4
- import {fileURLToPath} from 'url';
4
+ import {createRequire} from 'module';
5
5
 
6
6
  import type {Plugin, RollupOptions, GetManualChunk} from 'rollup';
7
7
  import type {AssetsBuildManifest} from '@quilted/assets';
@@ -12,17 +12,27 @@ import {
12
12
  MAGIC_MODULE_BROWSER_ASSETS,
13
13
  MAGIC_MODULE_REQUEST_ROUTER,
14
14
  } from './constants.ts';
15
- import type {MagicModuleEnvOptions} from './features/env.ts';
15
+ import {resolveEnvOption, type MagicModuleEnvOptions} from './features/env.ts';
16
16
 
17
17
  import {multiline} from './shared/strings.ts';
18
- import {getNodePlugins, removeBuildFiles} from './shared/rollup.ts';
18
+ import {
19
+ getNodePlugins,
20
+ removeBuildFiles,
21
+ type RollupNodePluginOptions,
22
+ } from './shared/rollup.ts';
19
23
  import {createMagicModulePlugin} from './shared/magic-module.ts';
20
24
  import {
21
- getBrowserTargetDetails,
22
- type BrowserTargetSelection,
25
+ targetsSupportModules,
26
+ getBrowserGroups,
27
+ getBrowserGroupTargetDetails,
28
+ getBrowserGroupRegularExpressions,
29
+ rollupGenerateOptionsForBrowsers,
30
+ type BrowserGroupTargetSelection,
23
31
  } from './shared/browserslist.ts';
32
+ import {loadPackageJSON, type PackageJSON} from './shared/package-json.ts';
33
+ import {resolveRoot} from './shared/path.ts';
24
34
 
25
- export interface AppOptions {
35
+ export interface AppBaseOptions {
26
36
  /**
27
37
  * The root directory containing the source code for your application.
28
38
  */
@@ -52,13 +62,33 @@ export interface AppOptions {
52
62
 
53
63
  /**
54
64
  * Customizes the behavior of environment variables for your application. You
55
- * can further customize the environment variables provided during server-side
56
- * rendering by passing `server.env`.
65
+ * can further customize the environment variables provided to browser assets
66
+ * by passing the `browser.env`, and those passed during server-side rendering
67
+ * by passing `server.env`.
68
+ */
69
+ env?: MagicModuleEnvOptions | MagicModuleEnvOptions['mode'];
70
+ }
71
+
72
+ export interface AppOptions extends AppBaseOptions {
73
+ /**
74
+ * Customizes the browser build of your application.
75
+ */
76
+ browser?: Omit<AppBrowserOptions, keyof AppBaseOptions> &
77
+ Pick<AppBrowserOptions, 'env'>;
78
+
79
+ /**
80
+ * Customizes the assets created for your application.
81
+ */
82
+ assets?: AppBrowserOptions['assets'];
83
+
84
+ /**
85
+ * Customizes the server build of your application.
57
86
  */
58
- env?: MagicModuleEnvOptions;
87
+ server?: Omit<AppServerOptions, keyof AppBaseOptions> &
88
+ Pick<AppServerOptions, 'env'>;
59
89
  }
60
90
 
61
- export interface AppBrowserOptions extends AppOptions {
91
+ export interface AppBrowserOptions extends AppBaseOptions {
62
92
  /**
63
93
  * The entry module for this browser. This should be an absolute path, or relative
64
94
  * path from the root directory containing your project. This entry should be the
@@ -99,15 +129,150 @@ export interface AppBrowserModuleOptions {
99
129
 
100
130
  export interface AppBrowserAssetsOptions {
101
131
  /**
102
- * Whether to minify assets created by Quilt.
132
+ * Whether to minify assets created for you application.
103
133
  *
104
134
  * @default true
105
135
  */
106
136
  minify?: boolean;
107
137
 
108
138
  baseURL?: string;
109
- targets?: BrowserTargetSelection;
139
+ targets?: BrowserGroupTargetSelection;
110
140
  priority?: number;
141
+ clean?: boolean;
142
+
143
+ /**
144
+ * Controls how assets like images are inlined into your bundles JavaScript.
145
+ */
146
+ inline?:
147
+ | boolean
148
+ | {
149
+ /**
150
+ * The maximum size in bytes that an asset should be in order to
151
+ * be inlined into the bundle. Defaults to `4096`.
152
+ */
153
+ limit?: number;
154
+ };
155
+ }
156
+
157
+ export interface AppServerOptions extends AppBaseOptions {
158
+ /**
159
+ * The entry module for this app’s server. By default, this module must export
160
+ * a `RequestRouter` object as its default export, which will be wrapped in
161
+ * the specific server runtime you configure. If you set the format to `'custom'`,
162
+ * this entry can be any content — it will be bundled as-is.
163
+ *
164
+ * If not provided, this will default to a file named `server`, `service`,
165
+ * or `backend` in your app’s root directory.
166
+ */
167
+ entry?: string;
168
+
169
+ /**
170
+ * Whether this server code uses the `request-router` library to
171
+ * define itself in a generic way, which can be adapted to a variety
172
+ * of environments. By default, this is `'request-router'`, and when `'request-router'`,
173
+ * the `entry` you specified must export an `RequestRouter` object as
174
+ * its default export. When set to `false`, the app server will be built
175
+ * as a basic server-side JavaScript project, without the special
176
+ * `request-router` adaptor.
177
+ *
178
+ * @default 'request-router'
179
+ */
180
+ format?: 'request-router' | 'custom';
181
+
182
+ /**
183
+ * Customizes the assets created for your application.
184
+ */
185
+ assets?: Pick<AppBrowserAssetsOptions, 'baseURL' | 'inline'>;
186
+
187
+ /**
188
+ * Customizes the output files created for your server.
189
+ */
190
+ output?: AppServerOutputOptions;
191
+ }
192
+
193
+ export interface AppServerOutputOptions
194
+ extends Pick<RollupNodePluginOptions, 'bundle'> {
195
+ /**
196
+ * Whether to minify assets created for this server.
197
+ *
198
+ * @default false
199
+ */
200
+ minify?: boolean;
201
+
202
+ /**
203
+ * Whether to add a hash to the output files for your server. You can set
204
+ * this to `true`, which includes a hash for all files, `false`, which never
205
+ * includes a hash, or `'async-only'`, which only includes a hash for files
206
+ * that are loaded asynchronously (that is, your entry file will not have a
207
+ * hash, but any files it loads will).
208
+ *
209
+ * @default 'async-only'
210
+ */
211
+ hash?: boolean | 'async-only';
212
+
213
+ /**
214
+ * What module format to use for the server output.
215
+ *
216
+ * @default 'esmodules'
217
+ */
218
+ format?: 'esmodules' | 'esm' | 'es' | 'commonjs' | 'cjs';
219
+ }
220
+
221
+ const require = createRequire(import.meta.url);
222
+
223
+ export async function quiltApp({
224
+ root: rootPath = process.cwd(),
225
+ app,
226
+ env,
227
+ graphql,
228
+ assets,
229
+ browser: browserOptions,
230
+ server: serverOptions,
231
+ }: AppOptions = {}) {
232
+ const root = resolveRoot(rootPath);
233
+
234
+ const browserGroups = await getBrowserGroups({root});
235
+ const browserGroupEntries = Object.entries(browserGroups);
236
+ const hasMultipleBrowserGroups = browserGroupEntries.length > 1;
237
+
238
+ const optionPromises: Promise<RollupOptions>[] = [];
239
+
240
+ browserGroupEntries.forEach(([name, browsers], index) => {
241
+ optionPromises.push(
242
+ quiltAppBrowser({
243
+ root,
244
+ app,
245
+ graphql,
246
+ ...browserOptions,
247
+ env: {
248
+ ...resolveEnvOption(env),
249
+ ...resolveEnvOption(browserOptions?.env),
250
+ },
251
+ assets: {
252
+ ...assets,
253
+ ...browserOptions?.assets,
254
+ // Only clean on the first build, otherwise each subsequent build removes
255
+ // assets from the previous ones.
256
+ clean: index === 0,
257
+ priority: index,
258
+ targets: hasMultipleBrowserGroups ? {name, browsers} : browsers,
259
+ },
260
+ }),
261
+ );
262
+ });
263
+
264
+ optionPromises.push(
265
+ quiltAppServer({
266
+ root,
267
+ app,
268
+ graphql,
269
+ ...serverOptions,
270
+ env: {...resolveEnvOption(env), ...resolveEnvOption(serverOptions?.env)},
271
+ assets: {...assets, ...serverOptions?.assets},
272
+ }),
273
+ );
274
+
275
+ return Promise.all(optionPromises);
111
276
  }
112
277
 
113
278
  export async function quiltAppBrowser({
@@ -119,15 +284,16 @@ export async function quiltAppBrowser({
119
284
  module,
120
285
  graphql = true,
121
286
  }: AppBrowserOptions = {}) {
122
- const root =
123
- typeof rootPath === 'string' ? rootPath : fileURLToPath(rootPath);
124
- const mode =
125
- (typeof env === 'object' ? env?.mode : undefined) ?? 'production';
287
+ const root = resolveRoot(rootPath);
288
+ const mode = (typeof env === 'object' ? env?.mode : env) ?? 'production';
126
289
  const minify = assets?.minify ?? mode === 'production';
127
290
  const baseURL = assets?.baseURL ?? '/assets/';
291
+ const assetsInline = assets?.inline ?? true;
128
292
 
129
- const browserTarget = await getBrowserTargetDetails(assets?.targets, {root});
130
- const targetFilenamePart = browserTarget.name ? `.${browserTarget.name}` : '';
293
+ const browserGroup = await getBrowserGroupTargetDetails(assets?.targets, {
294
+ root,
295
+ });
296
+ const targetFilenamePart = browserGroup.name ? `.${browserGroup.name}` : '';
131
297
 
132
298
  const [
133
299
  {visualizer},
@@ -136,8 +302,12 @@ export async function quiltAppBrowser({
136
302
  {createTSConfigAliasPlugin},
137
303
  {css},
138
304
  {assetManifest, rawAssets, staticAssets},
305
+ {asyncModules},
139
306
  {systemJS},
307
+ {workers},
308
+ {esnext},
140
309
  nodePlugins,
310
+ packageJSON,
141
311
  ] = await Promise.all([
142
312
  import('rollup-plugin-visualizer'),
143
313
  import('./features/env.ts'),
@@ -145,37 +315,84 @@ export async function quiltAppBrowser({
145
315
  import('./features/typescript.ts'),
146
316
  import('./features/css.ts'),
147
317
  import('./features/assets.ts'),
318
+ import('./features/async.ts'),
148
319
  import('./features/system-js.ts'),
149
- getNodePlugins(),
320
+ import('./features/workers.ts'),
321
+ import('./features/esnext.ts'),
322
+ getNodePlugins({bundle: true}),
323
+ loadPackageJSON(root),
150
324
  ]);
151
325
 
152
326
  const plugins: Plugin[] = [
153
327
  ...nodePlugins,
154
328
  systemJS({minify}),
155
329
  replaceProcessEnv({mode}),
156
- magicModuleEnv({...env, mode}),
157
- sourceCode({mode, targets: browserTarget.browsers}),
330
+ magicModuleEnv({...resolveEnvOption(env), mode}),
331
+ sourceCode({
332
+ mode,
333
+ targets: browserGroup.browsers,
334
+ babel: {
335
+ useBuiltIns: 'entry',
336
+ options(options) {
337
+ return {
338
+ ...options,
339
+ plugins: [
340
+ ...(options?.plugins ?? []),
341
+ require.resolve('@quilted/babel/async'),
342
+ require.resolve('@quilted/babel/workers'),
343
+ ],
344
+ };
345
+ },
346
+ },
347
+ }),
348
+ esnext({
349
+ mode,
350
+ targets: browserGroup.browsers,
351
+ }),
158
352
  css({minify, emit: true}),
159
353
  rawAssets(),
160
- staticAssets({baseURL, emit: true}),
161
- removeBuildFiles(['build/assets', 'build/manifests', 'build/reports'], {
162
- root,
354
+ staticAssets({
355
+ baseURL,
356
+ emit: true,
357
+ inlineLimit: assetsInline
358
+ ? typeof assetsInline === 'boolean'
359
+ ? undefined
360
+ : assetsInline?.limit
361
+ : Number.POSITIVE_INFINITY,
362
+ }),
363
+ asyncModules({
364
+ baseURL,
365
+ preload: true,
366
+ moduleID: ({imported}) => path.relative(root, imported),
367
+ }),
368
+ workers({
369
+ baseURL,
370
+ outputOptions: {
371
+ format: 'iife',
372
+ inlineDynamicImports: true,
373
+ dir: path.resolve(root, `build/assets`),
374
+ entryFileNames: `[name]${targetFilenamePart}.[hash].js`,
375
+ assetFileNames: `[name]${targetFilenamePart}.[hash].[ext]`,
376
+ chunkFileNames: `[name]${targetFilenamePart}.[hash].js`,
377
+ },
163
378
  }),
164
379
  ];
165
380
 
381
+ if (assets?.clean ?? true) {
382
+ plugins.push(
383
+ removeBuildFiles(['build/assets', 'build/manifests', 'build/reports'], {
384
+ root,
385
+ }),
386
+ );
387
+ }
388
+
166
389
  const tsconfigAliases = await createTSConfigAliasPlugin();
167
390
 
168
391
  if (tsconfigAliases) {
169
392
  plugins.push(tsconfigAliases);
170
393
  }
171
394
 
172
- const appEntry =
173
- app ??
174
- (await glob('{App,app,input}.{ts,tsx,mjs,js,jsx}', {
175
- cwd: root,
176
- nodir: true,
177
- absolute: true,
178
- }).then((files) => files[0]));
395
+ const appEntry = await resolveAppEntry(app, {root, packageJSON});
179
396
 
180
397
  if (appEntry) {
181
398
  plugins.push(magicModuleAppComponent({entry: appEntry}));
@@ -199,16 +416,15 @@ export async function quiltAppBrowser({
199
416
  plugins.push(minify());
200
417
  }
201
418
 
202
- const cacheKey = browserTarget.name
203
- ? {browserTarget: browserTarget.name}
204
- : undefined;
205
- const id = browserTarget.name ? browserTarget.name : undefined;
419
+ const cacheKey = new URLSearchParams();
420
+ if (browserGroup.name) {
421
+ cacheKey.set('browserGroup', browserGroup.name);
422
+ }
206
423
 
207
424
  plugins.push(
208
425
  assetManifest({
209
- id,
210
- cacheKey,
211
426
  baseURL,
427
+ cacheKey,
212
428
  file: path.resolve(`build/manifests/assets${targetFilenamePart}.json`),
213
429
  priority: assets?.priority,
214
430
  }),
@@ -222,17 +438,21 @@ export async function quiltAppBrowser({
222
438
  }),
223
439
  );
224
440
 
225
- const finalEntry =
226
- entry ??
227
- (await glob('{browser,client}.{ts,tsx,mjs,js,jsx}', {
228
- cwd: root,
229
- nodir: true,
230
- absolute: true,
231
- }).then((files) => files[0])) ??
232
- MAGIC_MODULE_ENTRY;
441
+ const finalEntry = entry
442
+ ? path.resolve(root, entry)
443
+ : (await glob('{browser,client,web}.{ts,tsx,mjs,js,jsx}', {
444
+ cwd: root,
445
+ nodir: true,
446
+ absolute: true,
447
+ }).then((files) => files[0])) ?? MAGIC_MODULE_ENTRY;
448
+
449
+ const isESM = await targetsSupportModules(browserGroup.browsers);
233
450
 
234
451
  return {
235
- input: finalEntry,
452
+ // If we are using the "magic entry", give it an explicit name of `browser`.
453
+ // Otherwise, Rollup will use the file name as the output name.
454
+ input:
455
+ finalEntry === MAGIC_MODULE_ENTRY ? {browser: finalEntry} : finalEntry,
236
456
  plugins,
237
457
  onwarn(warning, defaultWarn) {
238
458
  // Removes annoying warnings for React-focused libraries that
@@ -247,43 +467,39 @@ export async function quiltAppBrowser({
247
467
  defaultWarn(warning);
248
468
  },
249
469
  output: {
250
- // format: isESM ? 'esm' : 'systemjs',
251
- format: 'esm',
252
- dir: path.resolve(`build/assets`),
253
- entryFileNames: `app${targetFilenamePart}.[hash].js`,
470
+ format: isESM ? 'esm' : 'systemjs',
471
+ dir: path.resolve(root, `build/assets`),
472
+ entryFileNames: `[name]${targetFilenamePart}.[hash].js`,
254
473
  assetFileNames: `[name]${targetFilenamePart}.[hash].[ext]`,
255
474
  chunkFileNames: `[name]${targetFilenamePart}.[hash].js`,
256
475
  manualChunks: createManualChunksSorter(),
476
+ generatedCode: await rollupGenerateOptionsForBrowsers(
477
+ browserGroup.browsers,
478
+ ),
257
479
  },
258
480
  } satisfies RollupOptions;
259
481
  }
260
482
 
261
- export interface AppServerOptions extends AppOptions {
262
- /**
263
- * The entry module for the server of this app. This module must export a
264
- * `RequestRouter` object as its default export, which will be wrapped in
265
- * the specific server runtime you configure.
266
- */
267
- entry?: string;
268
-
269
- /**
270
- * Whether to minify the JavaScript outputs for your server.
271
- *
272
- * @default false
273
- */
274
- minify?: boolean;
275
- }
276
-
277
483
  export async function quiltAppServer({
484
+ root: rootPath = process.cwd(),
278
485
  app,
279
486
  env,
280
487
  entry,
488
+ format = 'request-router',
281
489
  graphql = true,
282
- minify = false,
490
+ assets,
491
+ output,
283
492
  }: AppServerOptions = {}) {
284
- const root = process.cwd();
285
- const mode =
286
- (typeof env === 'object' ? env?.mode : undefined) ?? 'production';
493
+ const root = resolveRoot(rootPath);
494
+ const mode = (typeof env === 'object' ? env?.mode : env) ?? 'production';
495
+
496
+ const baseURL = assets?.baseURL ?? '/assets/';
497
+ const assetsInline = assets?.inline ?? true;
498
+
499
+ const bundle = output?.bundle;
500
+ const minify = output?.minify ?? false;
501
+ const hash = output?.hash ?? 'async-only';
502
+ const outputFormat = output?.format ?? 'esmodules';
287
503
 
288
504
  const [
289
505
  {visualizer},
@@ -292,8 +508,10 @@ export async function quiltAppServer({
292
508
  {createTSConfigAliasPlugin},
293
509
  {css},
294
510
  {rawAssets, staticAssets},
295
- {magicModuleRequestRouterEntry},
511
+ {asyncModules},
512
+ {esnext},
296
513
  nodePlugins,
514
+ packageJSON,
297
515
  ] = await Promise.all([
298
516
  import('rollup-plugin-visualizer'),
299
517
  import('./features/env.ts'),
@@ -301,18 +519,52 @@ export async function quiltAppServer({
301
519
  import('./features/typescript.ts'),
302
520
  import('./features/css.ts'),
303
521
  import('./features/assets.ts'),
304
- import('./features/request-router.ts'),
305
- getNodePlugins(),
522
+ import('./features/async.ts'),
523
+ import('./features/esnext.ts'),
524
+ getNodePlugins({bundle}),
525
+ loadPackageJSON(root),
306
526
  ]);
307
527
 
308
528
  const plugins: Plugin[] = [
309
529
  ...nodePlugins,
310
530
  replaceProcessEnv({mode}),
311
- magicModuleEnv({...env, mode}),
312
- sourceCode({mode, targets: ['current node']}),
531
+ magicModuleEnv({...resolveEnvOption(env), mode}),
532
+ sourceCode({
533
+ mode,
534
+ targets: ['current node'],
535
+ babel: {
536
+ options(options) {
537
+ return {
538
+ ...options,
539
+ plugins: [
540
+ ...(options?.plugins ?? []),
541
+ require.resolve('@quilted/babel/async'),
542
+ [require.resolve('@quilted/babel/workers'), {noop: true}],
543
+ ],
544
+ };
545
+ },
546
+ },
547
+ }),
548
+ esnext({
549
+ mode,
550
+ targets: ['current node'],
551
+ }),
313
552
  css({emit: false, minify}),
314
553
  rawAssets(),
315
- staticAssets({emit: false}),
554
+ staticAssets({
555
+ emit: false,
556
+ baseURL,
557
+ inlineLimit: assetsInline
558
+ ? typeof assetsInline === 'boolean'
559
+ ? undefined
560
+ : assetsInline?.limit
561
+ : Number.POSITIVE_INFINITY,
562
+ }),
563
+ asyncModules({
564
+ baseURL,
565
+ preload: false,
566
+ moduleID: ({imported}) => path.relative(root, imported),
567
+ }),
316
568
  removeBuildFiles(['build/server'], {root}),
317
569
  ];
318
570
 
@@ -322,21 +574,30 @@ export async function quiltAppServer({
322
574
  plugins.push(tsconfigAliases);
323
575
  }
324
576
 
325
- const appEntry =
326
- app ??
327
- (await glob('{App,app,input}.{ts,tsx,mjs,js,jsx}', {
328
- cwd: root,
329
- nodir: true,
330
- absolute: true,
331
- }).then((files) => files[0]));
577
+ const appEntry = await resolveAppEntry(app, {root, packageJSON});
332
578
 
333
579
  if (appEntry) {
334
580
  plugins.push(magicModuleAppComponent({entry: appEntry}));
335
581
  }
336
582
 
583
+ const serverEntry = entry
584
+ ? path.resolve(root, entry)
585
+ : await glob('{server,service,backend}.{ts,tsx,mjs,js,jsx}', {
586
+ cwd: root,
587
+ nodir: true,
588
+ absolute: true,
589
+ }).then((files) => files[0]);
590
+
591
+ const finalEntry =
592
+ format === 'request-router'
593
+ ? MAGIC_MODULE_ENTRY
594
+ : serverEntry ?? MAGIC_MODULE_ENTRY;
595
+
337
596
  plugins.push(
338
- magicModuleRequestRouterEntry(),
339
- magicModuleAppRequestRouter({entry}),
597
+ magicModuleAppServerEntry({
598
+ assets: {baseURL},
599
+ }),
600
+ magicModuleAppRequestRouter({entry: serverEntry}),
340
601
  magicModuleAppAssetManifests(),
341
602
  );
342
603
 
@@ -359,17 +620,11 @@ export async function quiltAppServer({
359
620
  }),
360
621
  );
361
622
 
362
- const finalEntry =
363
- entry ??
364
- (await glob('{server,service,backend}.{ts,tsx,mjs,js,jsx}', {
365
- cwd: root,
366
- nodir: true,
367
- absolute: true,
368
- }).then((files) => files[0])) ??
369
- MAGIC_MODULE_ENTRY;
370
-
371
623
  return {
372
- input: finalEntry,
624
+ // If we are using the "magic entry", give it an explicit name of `server`.
625
+ // Otherwise, Rollup will use the file name as the output name.
626
+ input:
627
+ finalEntry === MAGIC_MODULE_ENTRY ? {server: finalEntry} : finalEntry,
373
628
  plugins,
374
629
  onwarn(warning, defaultWarn) {
375
630
  // Removes annoying warnings for React-focused libraries that
@@ -384,10 +639,15 @@ export async function quiltAppServer({
384
639
  defaultWarn(warning);
385
640
  },
386
641
  output: {
387
- // format: isESM ? 'esm' : 'systemjs',
388
- format: 'esm',
642
+ format:
643
+ outputFormat === 'commonjs' || outputFormat === 'cjs' ? 'cjs' : 'esm',
389
644
  dir: path.resolve(`build/server`),
390
- entryFileNames: 'server.js',
645
+ entryFileNames: `[name]${hash === true ? `.[hash]` : ''}.js`,
646
+ chunkFileNames: `[name]${
647
+ hash === true || hash === 'async-only' ? `.[hash]` : ''
648
+ }.js`,
649
+ assetFileNames: `[name]${hash === true ? `.[hash]` : ''}.[ext]`,
650
+ generatedCode: 'es2015',
391
651
  },
392
652
  } satisfies RollupOptions;
393
653
  }
@@ -472,6 +732,75 @@ export function magicModuleAppBrowserEntry({
472
732
  });
473
733
  }
474
734
 
735
+ export function magicModuleAppServerEntry({
736
+ host,
737
+ port,
738
+ assets,
739
+ format = 'module',
740
+ }: {
741
+ host?: string;
742
+ port?: number;
743
+ assets?: boolean | {baseURL: string};
744
+ format?: 'module' | 'commonjs';
745
+ } = {}) {
746
+ const baseURL = typeof assets === 'object' ? assets.baseURL : '/assets/';
747
+
748
+ return createMagicModulePlugin({
749
+ name: '@quilted/request-router/app-server',
750
+ module: MAGIC_MODULE_ENTRY,
751
+ sideEffects: true,
752
+ async source() {
753
+ const serveAssets = Boolean(assets);
754
+
755
+ return multiline`
756
+ ${serveAssets ? `import * as path from 'path';` : ''}
757
+ ${
758
+ serveAssets && format === 'module'
759
+ ? `import {fileURLToPath} from 'url';`
760
+ : ''
761
+ }
762
+ import {createServer} from 'http';
763
+
764
+ import requestRouter from ${JSON.stringify(
765
+ MAGIC_MODULE_REQUEST_ROUTER,
766
+ )};
767
+
768
+ import {createHttpRequestListener${
769
+ serveAssets ? ', serveStatic' : ''
770
+ }} from '@quilted/quilt/request-router/node';
771
+
772
+ const port = ${port ?? 'Number.parseInt(process.env.PORT, 10)'};
773
+ const host = ${host ? JSON.stringify(host) : 'process.env.HOST'};
774
+
775
+ ${
776
+ serveAssets
777
+ ? `const dirname = ${
778
+ format === 'module'
779
+ ? 'path.dirname(fileURLToPath(import.meta.url))'
780
+ : '__dirname'
781
+ };\nconst serve = serveStatic(path.resolve(dirname, '../assets'), {
782
+ baseUrl: ${JSON.stringify(baseURL)},
783
+ });`
784
+ : ''
785
+ }
786
+ const listener = createHttpRequestListener(requestRouter);
787
+
788
+ createServer(async (request, response) => {
789
+ ${
790
+ serveAssets
791
+ ? `if (request.url.startsWith(${JSON.stringify(
792
+ baseURL,
793
+ )})) return serve(request, response);`
794
+ : ''
795
+ }
796
+
797
+ await listener(request, response);
798
+ }).listen(port, host);
799
+ `;
800
+ },
801
+ });
802
+ }
803
+
475
804
  export function magicModuleAppAssetManifests() {
476
805
  return createMagicModulePlugin({
477
806
  name: '@quilted/magic-module/asset-manifests',
@@ -497,6 +826,8 @@ export function magicModuleAppAssetManifests() {
497
826
  (manifestA.priority ?? 0) - (manifestB.priority ?? 0),
498
827
  );
499
828
 
829
+ const browserGroupRegexes = await getBrowserGroupRegularExpressions();
830
+
500
831
  return multiline`
501
832
  import {BrowserAssetsFromManifests} from '@quilted/quilt/server';
502
833
 
@@ -506,12 +837,31 @@ export function magicModuleAppAssetManifests() {
506
837
  JSON.stringify(manifests),
507
838
  )});
508
839
 
840
+ const browserGroupTests = [
841
+ ${Object.entries(browserGroupRegexes)
842
+ .map(
843
+ ([name, test]) =>
844
+ `[${JSON.stringify(name)}, new RegExp(${JSON.stringify(
845
+ test.source,
846
+ )})]`,
847
+ )
848
+ .join(', ')}
849
+ ];
850
+
509
851
  // The default manifest is the last one, since it has the widest browser support.
510
852
  const defaultManifest = manifests.at(-1);
511
853
 
512
854
  super(manifests, {
513
855
  defaultManifest,
514
856
  cacheKey(request) {
857
+ const userAgent = request.headers.get('User-Agent');
858
+
859
+ if (userAgent) {
860
+ for (const [name, test] of browserGroupTests) {
861
+ if (test.test(userAgent)) return {browserGroup: name};
862
+ }
863
+ }
864
+
515
865
  return {};
516
866
  },
517
867
  });
@@ -541,7 +891,6 @@ const FRAMEWORK_TEST_STRINGS: (string | RegExp)[] = [
541
891
  ];
542
892
 
543
893
  const POLYFILL_TEST_STRINGS = [
544
- '/node_modules/@quilted/polyfills/',
545
894
  '/node_modules/core-js/',
546
895
  '/node_modules/whatwg-fetch/',
547
896
  '/node_modules/regenerator-runtime/',
@@ -621,3 +970,30 @@ function createManualChunksSorter(): GetManualChunk {
621
970
  return `${bundleBaseName}-${relativeId.split(path.sep)[0]?.split('.')[0]}`;
622
971
  };
623
972
  }
973
+
974
+ async function resolveAppEntry(
975
+ entry: string | undefined,
976
+ {root, packageJSON}: {root: string; packageJSON: PackageJSON},
977
+ ) {
978
+ if (entry) {
979
+ return path.resolve(root, entry);
980
+ }
981
+
982
+ if (typeof packageJSON.main === 'string') {
983
+ return path.resolve(root, packageJSON.main);
984
+ }
985
+
986
+ const rootEntry = (packageJSON.exports as any)?.['.'];
987
+
988
+ if (typeof rootEntry === 'string') {
989
+ return path.resolve(root, rootEntry);
990
+ }
991
+
992
+ const globbed = await glob('{App,app,index}.{ts,tsx,mjs,js,jsx}', {
993
+ cwd: root,
994
+ nodir: true,
995
+ absolute: true,
996
+ });
997
+
998
+ return globbed[0];
999
+ }