@quilted/rollup 0.1.18 → 0.2.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/build/esm/app.mjs +442 -233
  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 +13 -18
  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 +126 -27
  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/workers.d.ts +52 -0
  44. package/build/typescript/features/workers.d.ts.map +1 -0
  45. package/build/typescript/index.d.ts +3 -2
  46. package/build/typescript/index.d.ts.map +1 -1
  47. package/build/typescript/module.d.ts +24 -6
  48. package/build/typescript/module.d.ts.map +1 -1
  49. package/build/typescript/package.d.ts +196 -4
  50. package/build/typescript/package.d.ts.map +1 -1
  51. package/build/typescript/server.d.ts +98 -0
  52. package/build/typescript/server.d.ts.map +1 -0
  53. package/build/typescript/shared/browserslist.d.ts +20 -3
  54. package/build/typescript/shared/browserslist.d.ts.map +1 -1
  55. package/build/typescript/shared/path.d.ts +2 -0
  56. package/build/typescript/shared/path.d.ts.map +1 -0
  57. package/build/typescript/shared/rollup.d.ts +27 -1
  58. package/build/typescript/shared/rollup.d.ts.map +1 -1
  59. package/configuration/rollup.config.js +40 -0
  60. package/package.json +61 -8
  61. package/source/app.ts +472 -109
  62. package/source/features/assets.ts +5 -7
  63. package/source/features/async.ts +249 -0
  64. package/source/features/css.ts +4 -2
  65. package/source/features/env.ts +6 -0
  66. package/source/features/esnext.ts +70 -0
  67. package/source/features/graphql.ts +4 -2
  68. package/source/features/source-code.ts +26 -8
  69. package/source/features/workers.ts +292 -0
  70. package/source/index.ts +4 -0
  71. package/source/module.ts +45 -19
  72. package/source/package.ts +394 -36
  73. package/source/server.ts +245 -0
  74. package/source/shared/browserslist.ts +208 -18
  75. package/source/shared/path.ts +5 -0
  76. package/source/shared/rollup.ts +102 -4
  77. package/tsconfig.json +6 -2
  78. package/build/cjs/app.cjs +0 -456
  79. package/build/cjs/constants.cjs +0 -13
  80. package/build/cjs/features/assets.cjs +0 -240
  81. package/build/cjs/features/css.cjs +0 -71
  82. package/build/cjs/features/env.cjs +0 -135
  83. package/build/cjs/features/graphql/transform.cjs +0 -186
  84. package/build/cjs/features/graphql.cjs +0 -86
  85. package/build/cjs/features/request-router.cjs +0 -31
  86. package/build/cjs/features/source-code.cjs +0 -54
  87. package/build/cjs/features/system-js.cjs +0 -36
  88. package/build/cjs/features/typescript.cjs +0 -56
  89. package/build/cjs/index.cjs +0 -13
  90. package/build/cjs/module.cjs +0 -121
  91. package/build/cjs/package.cjs +0 -170
  92. package/build/cjs/shared/browserslist.cjs +0 -25
  93. package/build/cjs/shared/magic-module.cjs +0 -32
  94. package/build/cjs/shared/package-json.cjs +0 -31
  95. package/build/cjs/shared/rollup.cjs +0 -72
  96. package/build/cjs/shared/strings.cjs +0 -16
  97. package/build/esnext/app.esnext +0 -429
  98. package/build/esnext/constants.esnext +0 -7
  99. package/build/esnext/features/assets.esnext +0 -215
  100. package/build/esnext/features/css.esnext +0 -69
  101. package/build/esnext/features/env.esnext +0 -112
  102. package/build/esnext/features/graphql/transform.esnext +0 -181
  103. package/build/esnext/features/graphql.esnext +0 -84
  104. package/build/esnext/features/request-router.esnext +0 -29
  105. package/build/esnext/features/source-code.esnext +0 -51
  106. package/build/esnext/features/system-js.esnext +0 -33
  107. package/build/esnext/features/typescript.esnext +0 -34
  108. package/build/esnext/index.esnext +0 -3
  109. package/build/esnext/module.esnext +0 -100
  110. package/build/esnext/package.esnext +0 -148
  111. package/build/esnext/shared/browserslist.esnext +0 -23
  112. package/build/esnext/shared/magic-module.esnext +0 -30
  113. package/build/esnext/shared/package-json.esnext +0 -10
  114. package/build/esnext/shared/rollup.esnext +0 -49
  115. package/build/esnext/shared/strings.esnext +0 -14
  116. package/build/typescript/env.d.ts +0 -55
  117. package/build/typescript/env.d.ts.map +0 -1
  118. package/build/typescript/graphql/transform.d.ts +0 -17
  119. package/build/typescript/graphql/transform.d.ts.map +0 -1
  120. package/build/typescript/graphql.d.ts +0 -6
  121. package/build/typescript/graphql.d.ts.map +0 -1
  122. package/build/typescript/request-router.d.ts +0 -15
  123. package/build/typescript/request-router.d.ts.map +0 -1
  124. package/build/typescript/shared/source-code.d.ts +0 -5
  125. package/build/typescript/shared/source-code.d.ts.map +0 -1
  126. 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`.
57
68
  */
58
- env?: MagicModuleEnvOptions;
69
+ env?: MagicModuleEnvOptions | MagicModuleEnvOptions['mode'];
59
70
  }
60
71
 
61
- export interface AppBrowserOptions extends AppOptions {
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.
86
+ */
87
+ server?: Omit<AppServerOptions, keyof AppBaseOptions> &
88
+ Pick<AppServerOptions, 'env'>;
89
+ }
90
+
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,14 +284,16 @@ export async function quiltAppBrowser({
119
284
  module,
120
285
  graphql = true,
121
286
  }: AppBrowserOptions = {}) {
122
- const root = fileURLToPath(rootPath);
123
- const mode =
124
- (typeof env === 'object' ? env?.mode : undefined) ?? 'production';
287
+ const root = resolveRoot(rootPath);
288
+ const mode = (typeof env === 'object' ? env?.mode : env) ?? 'production';
125
289
  const minify = assets?.minify ?? mode === 'production';
126
290
  const baseURL = assets?.baseURL ?? '/assets/';
291
+ const assetsInline = assets?.inline ?? true;
127
292
 
128
- const browserTarget = await getBrowserTargetDetails(assets?.targets, {root});
129
- const targetFilenamePart = browserTarget.name ? `.${browserTarget.name}` : '';
293
+ const browserGroup = await getBrowserGroupTargetDetails(assets?.targets, {
294
+ root,
295
+ });
296
+ const targetFilenamePart = browserGroup.name ? `.${browserGroup.name}` : '';
130
297
 
131
298
  const [
132
299
  {visualizer},
@@ -135,8 +302,12 @@ export async function quiltAppBrowser({
135
302
  {createTSConfigAliasPlugin},
136
303
  {css},
137
304
  {assetManifest, rawAssets, staticAssets},
305
+ {asyncModules},
138
306
  {systemJS},
307
+ {workers},
308
+ {esnext},
139
309
  nodePlugins,
310
+ packageJSON,
140
311
  ] = await Promise.all([
141
312
  import('rollup-plugin-visualizer'),
142
313
  import('./features/env.ts'),
@@ -144,37 +315,84 @@ export async function quiltAppBrowser({
144
315
  import('./features/typescript.ts'),
145
316
  import('./features/css.ts'),
146
317
  import('./features/assets.ts'),
318
+ import('./features/async.ts'),
147
319
  import('./features/system-js.ts'),
148
- getNodePlugins(),
320
+ import('./features/workers.ts'),
321
+ import('./features/esnext.ts'),
322
+ getNodePlugins({bundle: true}),
323
+ loadPackageJSON(root),
149
324
  ]);
150
325
 
151
326
  const plugins: Plugin[] = [
152
327
  ...nodePlugins,
153
328
  systemJS({minify}),
154
329
  replaceProcessEnv({mode}),
155
- magicModuleEnv({...env, mode}),
156
- 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
+ }),
157
352
  css({minify, emit: true}),
158
353
  rawAssets(),
159
- staticAssets({baseURL, emit: true}),
160
- removeBuildFiles(['build/assets', 'build/manifests', 'build/reports'], {
161
- 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
+ },
162
378
  }),
163
379
  ];
164
380
 
381
+ if (assets?.clean ?? true) {
382
+ plugins.push(
383
+ removeBuildFiles(['build/assets', 'build/manifests', 'build/reports'], {
384
+ root,
385
+ }),
386
+ );
387
+ }
388
+
165
389
  const tsconfigAliases = await createTSConfigAliasPlugin();
166
390
 
167
391
  if (tsconfigAliases) {
168
392
  plugins.push(tsconfigAliases);
169
393
  }
170
394
 
171
- const appEntry =
172
- app ??
173
- (await glob('{App,app,input}.{ts,tsx,mjs,js,jsx}', {
174
- cwd: root,
175
- nodir: true,
176
- absolute: true,
177
- }).then((files) => files[0]));
395
+ const appEntry = await resolveAppEntry(app, {root, packageJSON});
178
396
 
179
397
  if (appEntry) {
180
398
  plugins.push(magicModuleAppComponent({entry: appEntry}));
@@ -198,16 +416,15 @@ export async function quiltAppBrowser({
198
416
  plugins.push(minify());
199
417
  }
200
418
 
201
- const cacheKey = browserTarget.name
202
- ? {browserTarget: browserTarget.name}
203
- : undefined;
204
- const id = browserTarget.name ? browserTarget.name : undefined;
419
+ const cacheKey = new URLSearchParams();
420
+ if (browserGroup.name) {
421
+ cacheKey.set('browserGroup', browserGroup.name);
422
+ }
205
423
 
206
424
  plugins.push(
207
425
  assetManifest({
208
- id,
209
- cacheKey,
210
426
  baseURL,
427
+ cacheKey,
211
428
  file: path.resolve(`build/manifests/assets${targetFilenamePart}.json`),
212
429
  priority: assets?.priority,
213
430
  }),
@@ -221,14 +438,15 @@ export async function quiltAppBrowser({
221
438
  }),
222
439
  );
223
440
 
224
- const finalEntry =
225
- entry ??
226
- (await glob('{browser,client}.{ts,tsx,mjs,js,jsx}', {
227
- cwd: root,
228
- nodir: true,
229
- absolute: true,
230
- }).then((files) => files[0])) ??
231
- 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);
232
450
 
233
451
  return {
234
452
  input: finalEntry,
@@ -246,68 +464,104 @@ export async function quiltAppBrowser({
246
464
  defaultWarn(warning);
247
465
  },
248
466
  output: {
249
- // format: isESM ? 'esm' : 'systemjs',
250
- format: 'esm',
251
- dir: path.resolve(`build/assets`),
467
+ format: isESM ? 'esm' : 'systemjs',
468
+ dir: path.resolve(root, `build/assets`),
252
469
  entryFileNames: `app${targetFilenamePart}.[hash].js`,
253
470
  assetFileNames: `[name]${targetFilenamePart}.[hash].[ext]`,
254
471
  chunkFileNames: `[name]${targetFilenamePart}.[hash].js`,
255
472
  manualChunks: createManualChunksSorter(),
473
+ generatedCode: await rollupGenerateOptionsForBrowsers(
474
+ browserGroup.browsers,
475
+ ),
256
476
  },
257
477
  } satisfies RollupOptions;
258
478
  }
259
479
 
260
- export interface AppServerOptions extends AppOptions {
261
- /**
262
- * The entry module for the server of this app. This module must export a
263
- * `RequestRouter` object as its default export, which will be wrapped in
264
- * the specific server runtime you configure.
265
- */
266
- entry?: string;
267
-
268
- /**
269
- * Whether to minify the JavaScript outputs for your server.
270
- *
271
- * @default false
272
- */
273
- minify?: boolean;
274
- }
275
-
276
480
  export async function quiltAppServer({
481
+ root: rootPath = process.cwd(),
277
482
  app,
278
483
  env,
279
484
  entry,
485
+ format = 'request-router',
280
486
  graphql = true,
281
- minify = false,
487
+ assets,
488
+ output,
282
489
  }: AppServerOptions = {}) {
283
- const root = process.cwd();
284
- const mode =
285
- (typeof env === 'object' ? env?.mode : undefined) ?? 'production';
490
+ const root = resolveRoot(rootPath);
491
+ const mode = (typeof env === 'object' ? env?.mode : env) ?? 'production';
492
+
493
+ const baseURL = assets?.baseURL ?? '/assets/';
494
+ const assetsInline = assets?.inline ?? true;
495
+
496
+ const bundle = output?.bundle;
497
+ const minify = output?.minify ?? false;
498
+ const hash = output?.hash ?? 'async-only';
499
+ const outputFormat = output?.format ?? 'esmodules';
286
500
 
287
501
  const [
288
502
  {visualizer},
503
+ {magicModuleEnv, replaceProcessEnv},
289
504
  {sourceCode},
290
505
  {createTSConfigAliasPlugin},
291
506
  {css},
292
507
  {rawAssets, staticAssets},
293
- {magicModuleRequestRouterEntry},
508
+ {asyncModules},
509
+ {esnext},
294
510
  nodePlugins,
511
+ packageJSON,
295
512
  ] = await Promise.all([
296
513
  import('rollup-plugin-visualizer'),
514
+ import('./features/env.ts'),
297
515
  import('./features/source-code.ts'),
298
516
  import('./features/typescript.ts'),
299
517
  import('./features/css.ts'),
300
518
  import('./features/assets.ts'),
301
- import('./features/request-router.ts'),
302
- getNodePlugins(),
519
+ import('./features/async.ts'),
520
+ import('./features/esnext.ts'),
521
+ getNodePlugins({bundle}),
522
+ loadPackageJSON(root),
303
523
  ]);
304
524
 
305
525
  const plugins: Plugin[] = [
306
526
  ...nodePlugins,
307
- sourceCode({mode, targets: ['current node']}),
527
+ replaceProcessEnv({mode}),
528
+ magicModuleEnv({...resolveEnvOption(env), mode}),
529
+ sourceCode({
530
+ mode,
531
+ targets: ['current node'],
532
+ babel: {
533
+ options(options) {
534
+ return {
535
+ ...options,
536
+ plugins: [
537
+ ...(options?.plugins ?? []),
538
+ require.resolve('@quilted/babel/async'),
539
+ [require.resolve('@quilted/babel/workers'), {noop: true}],
540
+ ],
541
+ };
542
+ },
543
+ },
544
+ }),
545
+ esnext({
546
+ mode,
547
+ targets: ['current node'],
548
+ }),
308
549
  css({emit: false, minify}),
309
550
  rawAssets(),
310
- staticAssets({emit: false}),
551
+ staticAssets({
552
+ emit: false,
553
+ baseURL,
554
+ inlineLimit: assetsInline
555
+ ? typeof assetsInline === 'boolean'
556
+ ? undefined
557
+ : assetsInline?.limit
558
+ : Number.POSITIVE_INFINITY,
559
+ }),
560
+ asyncModules({
561
+ baseURL,
562
+ preload: false,
563
+ moduleID: ({imported}) => path.relative(root, imported),
564
+ }),
311
565
  removeBuildFiles(['build/server'], {root}),
312
566
  ];
313
567
 
@@ -317,35 +571,32 @@ export async function quiltAppServer({
317
571
  plugins.push(tsconfigAliases);
318
572
  }
319
573
 
320
- if (env) {
321
- const {magicModuleEnv, replaceProcessEnv} = await import(
322
- './features/env.ts'
323
- );
324
-
325
- if (typeof env === 'boolean') {
326
- plugins.push(replaceProcessEnv({mode}));
327
- plugins.push(magicModuleEnv({mode}));
328
- } else {
329
- plugins.push(replaceProcessEnv({mode}));
330
- plugins.push(magicModuleEnv({mode}));
331
- }
332
- }
333
-
334
- const appEntry =
335
- app ??
336
- (await glob('{App,app,input}.{ts,tsx,mjs,js,jsx}', {
337
- cwd: root,
338
- nodir: true,
339
- absolute: true,
340
- }).then((files) => files[0]));
574
+ const appEntry = await resolveAppEntry(app, {root, packageJSON});
341
575
 
342
576
  if (appEntry) {
343
577
  plugins.push(magicModuleAppComponent({entry: appEntry}));
344
578
  }
345
579
 
346
- plugins.push(magicModuleRequestRouterEntry());
347
- plugins.push(magicModuleAppRequestRouter({entry}));
348
- plugins.push(magicModuleAppAssetManifests());
580
+ const serverEntry = entry
581
+ ? path.resolve(root, entry)
582
+ : await glob('{server,service,backend}.{ts,tsx,mjs,js,jsx}', {
583
+ cwd: root,
584
+ nodir: true,
585
+ absolute: true,
586
+ }).then((files) => files[0]);
587
+
588
+ const finalEntry =
589
+ format === 'request-router'
590
+ ? MAGIC_MODULE_ENTRY
591
+ : serverEntry ?? MAGIC_MODULE_ENTRY;
592
+
593
+ plugins.push(
594
+ magicModuleAppServerEntry({
595
+ assets: {baseURL},
596
+ }),
597
+ magicModuleAppRequestRouter({entry: serverEntry}),
598
+ magicModuleAppAssetManifests(),
599
+ );
349
600
 
350
601
  if (graphql) {
351
602
  const {graphql} = await import('./features/graphql.ts');
@@ -366,15 +617,6 @@ export async function quiltAppServer({
366
617
  }),
367
618
  );
368
619
 
369
- const finalEntry =
370
- entry ??
371
- (await glob('{server,service,backend}.{ts,tsx,mjs,js,jsx}', {
372
- cwd: root,
373
- nodir: true,
374
- absolute: true,
375
- }).then((files) => files[0])) ??
376
- MAGIC_MODULE_ENTRY;
377
-
378
620
  return {
379
621
  input: finalEntry,
380
622
  plugins,
@@ -391,10 +633,15 @@ export async function quiltAppServer({
391
633
  defaultWarn(warning);
392
634
  },
393
635
  output: {
394
- // format: isESM ? 'esm' : 'systemjs',
395
- format: 'esm',
636
+ format:
637
+ outputFormat === 'commonjs' || outputFormat === 'cjs' ? 'cjs' : 'esm',
396
638
  dir: path.resolve(`build/server`),
397
- entryFileNames: 'server.js',
639
+ entryFileNames: `server${hash === true ? `.[hash]` : ''}.js`,
640
+ chunkFileNames: `[name]${
641
+ hash === true || hash === 'async-only' ? `.[hash]` : ''
642
+ }.js`,
643
+ assetFileNames: `[name]${hash === true ? `.[hash]` : ''}.[ext]`,
644
+ generatedCode: 'es2015',
398
645
  },
399
646
  } satisfies RollupOptions;
400
647
  }
@@ -479,6 +726,75 @@ export function magicModuleAppBrowserEntry({
479
726
  });
480
727
  }
481
728
 
729
+ export function magicModuleAppServerEntry({
730
+ host,
731
+ port,
732
+ assets,
733
+ format = 'module',
734
+ }: {
735
+ host?: string;
736
+ port?: number;
737
+ assets?: boolean | {baseURL: string};
738
+ format?: 'module' | 'commonjs';
739
+ } = {}) {
740
+ const baseURL = typeof assets === 'object' ? assets.baseURL : '/assets/';
741
+
742
+ return createMagicModulePlugin({
743
+ name: '@quilted/request-router/app-server',
744
+ module: MAGIC_MODULE_ENTRY,
745
+ sideEffects: true,
746
+ async source() {
747
+ const serveAssets = Boolean(assets);
748
+
749
+ return multiline`
750
+ ${serveAssets ? `import * as path from 'path';` : ''}
751
+ ${
752
+ serveAssets && format === 'module'
753
+ ? `import {fileURLToPath} from 'url';`
754
+ : ''
755
+ }
756
+ import {createServer} from 'http';
757
+
758
+ import requestRouter from ${JSON.stringify(
759
+ MAGIC_MODULE_REQUEST_ROUTER,
760
+ )};
761
+
762
+ import {createHttpRequestListener${
763
+ serveAssets ? ', serveStatic' : ''
764
+ }} from '@quilted/quilt/request-router/node';
765
+
766
+ const port = ${port ?? 'Number.parseInt(process.env.PORT, 10)'};
767
+ const host = ${host ? JSON.stringify(host) : 'process.env.HOST'};
768
+
769
+ ${
770
+ serveAssets
771
+ ? `const dirname = ${
772
+ format === 'module'
773
+ ? 'path.dirname(fileURLToPath(import.meta.url))'
774
+ : '__dirname'
775
+ };\nconst serve = serveStatic(path.resolve(dirname, '../assets'), {
776
+ baseUrl: ${JSON.stringify(baseURL)},
777
+ });`
778
+ : ''
779
+ }
780
+ const listener = createHttpRequestListener(requestRouter);
781
+
782
+ createServer(async (request, response) => {
783
+ ${
784
+ serveAssets
785
+ ? `if (request.url.startsWith(${JSON.stringify(
786
+ baseURL,
787
+ )})) return serve(request, response);`
788
+ : ''
789
+ }
790
+
791
+ await listener(request, response);
792
+ }).listen(port, host);
793
+ `;
794
+ },
795
+ });
796
+ }
797
+
482
798
  export function magicModuleAppAssetManifests() {
483
799
  return createMagicModulePlugin({
484
800
  name: '@quilted/magic-module/asset-manifests',
@@ -504,6 +820,8 @@ export function magicModuleAppAssetManifests() {
504
820
  (manifestA.priority ?? 0) - (manifestB.priority ?? 0),
505
821
  );
506
822
 
823
+ const browserGroupRegexes = await getBrowserGroupRegularExpressions();
824
+
507
825
  return multiline`
508
826
  import {BrowserAssetsFromManifests} from '@quilted/quilt/server';
509
827
 
@@ -513,12 +831,31 @@ export function magicModuleAppAssetManifests() {
513
831
  JSON.stringify(manifests),
514
832
  )});
515
833
 
834
+ const browserGroupTests = [
835
+ ${Object.entries(browserGroupRegexes)
836
+ .map(
837
+ ([name, test]) =>
838
+ `[${JSON.stringify(name)}, new RegExp(${JSON.stringify(
839
+ test.source,
840
+ )})]`,
841
+ )
842
+ .join(', ')}
843
+ ];
844
+
516
845
  // The default manifest is the last one, since it has the widest browser support.
517
846
  const defaultManifest = manifests.at(-1);
518
847
 
519
848
  super(manifests, {
520
849
  defaultManifest,
521
850
  cacheKey(request) {
851
+ const userAgent = request.headers.get('User-Agent');
852
+
853
+ if (userAgent) {
854
+ for (const [name, test] of browserGroupTests) {
855
+ if (test.test(userAgent)) return {browserGroup: name};
856
+ }
857
+ }
858
+
522
859
  return {};
523
860
  },
524
861
  });
@@ -548,7 +885,6 @@ const FRAMEWORK_TEST_STRINGS: (string | RegExp)[] = [
548
885
  ];
549
886
 
550
887
  const POLYFILL_TEST_STRINGS = [
551
- '/node_modules/@quilted/polyfills/',
552
888
  '/node_modules/core-js/',
553
889
  '/node_modules/whatwg-fetch/',
554
890
  '/node_modules/regenerator-runtime/',
@@ -628,3 +964,30 @@ function createManualChunksSorter(): GetManualChunk {
628
964
  return `${bundleBaseName}-${relativeId.split(path.sep)[0]?.split('.')[0]}`;
629
965
  };
630
966
  }
967
+
968
+ async function resolveAppEntry(
969
+ entry: string | undefined,
970
+ {root, packageJSON}: {root: string; packageJSON: PackageJSON},
971
+ ) {
972
+ if (entry) {
973
+ return path.resolve(root, entry);
974
+ }
975
+
976
+ if (typeof packageJSON.main === 'string') {
977
+ return path.resolve(root, packageJSON.main);
978
+ }
979
+
980
+ const rootEntry = (packageJSON.exports as any)?.['.'];
981
+
982
+ if (typeof rootEntry === 'string') {
983
+ return path.resolve(root, rootEntry);
984
+ }
985
+
986
+ const globbed = await glob('{App,app,index}.{ts,tsx,mjs,js,jsx}', {
987
+ cwd: root,
988
+ nodir: true,
989
+ absolute: true,
990
+ });
991
+
992
+ return globbed[0];
993
+ }