@quilted/rollup 0.2.31 → 0.2.33

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.
package/source/app.ts CHANGED
@@ -91,8 +91,15 @@ export interface AppOptions extends AppBaseOptions {
91
91
  /**
92
92
  * Customizes the server build of your application.
93
93
  */
94
- server?: Omit<AppServerOptions, keyof AppBaseOptions> &
95
- Pick<AppServerOptions, 'env'>;
94
+ server?:
95
+ | boolean
96
+ | (Omit<AppServerOptions, keyof AppBaseOptions> &
97
+ Pick<AppServerOptions, 'env'>);
98
+
99
+ /**
100
+ * Customizes the service worker build of your application.
101
+ */
102
+ serviceWorker?: boolean | AppServiceWorkerOptions;
96
103
 
97
104
  /**
98
105
  * Customizations to the application for the runtime it will execute in.
@@ -234,6 +241,55 @@ export interface AppServerOutputOptions
234
241
  hash?: boolean | 'async-only';
235
242
  }
236
243
 
244
+ export interface AppServiceWorkerOptions extends AppBaseOptions {
245
+ /**
246
+ * The entry module for this app’s service worker. By default, this module must export
247
+ * a `RequestRouter` object as its default export, which will be wrapped in
248
+ * the specific server runtime you configure. If you set the format to `'custom'`,
249
+ * this entry can be any content — it will be bundled as-is.
250
+ *
251
+ * If not provided, this will default to a file named `server`, `service`,
252
+ * or `backend` in your app’s root directory.
253
+ */
254
+ entry?: string;
255
+
256
+ /**
257
+ * Customizes the assets created for your application.
258
+ */
259
+ assets?: Pick<AppBrowserAssetsOptions, 'baseURL' | 'inline'>;
260
+
261
+ /**
262
+ * Customizes the output files created for your service worker.
263
+ */
264
+ output?: AppServiceWorkerOutputOptions;
265
+
266
+ /**
267
+ * Customizes the behavior of environment variables for your application.
268
+ */
269
+ env?: MagicModuleEnvOptions | MagicModuleEnvOptions['mode'];
270
+ }
271
+
272
+ export interface AppServiceWorkerOutputOptions
273
+ extends Pick<RollupNodePluginOptions, 'bundle'> {
274
+ /**
275
+ * Whether to minify assets created for this service worker.
276
+ *
277
+ * @default false
278
+ */
279
+ minify?: boolean;
280
+
281
+ /**
282
+ * Whether to add a hash to the output files for your service worker. You can set
283
+ * this to `true`, which includes a hash for all files, `false`, which never
284
+ * includes a hash, or `'async-only'`, which only includes a hash for files
285
+ * that are loaded asynchronously (that is, your entry file will not have a
286
+ * hash, but any files it loads will).
287
+ *
288
+ * @default 'async-only'
289
+ */
290
+ hash?: boolean | 'async-only';
291
+ }
292
+
237
293
  export interface AppRuntime {
238
294
  /**
239
295
  * Overrides to the assets for this application.
@@ -273,7 +329,8 @@ export async function quiltApp({
273
329
  graphql,
274
330
  assets,
275
331
  browser: browserOptions,
276
- server: serverOptions,
332
+ server: serverOptions = true,
333
+ serviceWorker: serviceWorkerOptions = false,
277
334
  runtime,
278
335
  }: AppOptions = {}) {
279
336
  const project = Project.load(root);
@@ -309,20 +366,44 @@ export async function quiltApp({
309
366
  );
310
367
  });
311
368
 
312
- optionPromises.push(
313
- quiltAppServer({
314
- root: project.root,
315
- app,
316
- graphql,
317
- runtime: runtime?.server,
318
- ...serverOptions,
319
- env: {
320
- ...resolveEnvOption(env),
321
- ...resolveEnvOption(serverOptions?.env),
322
- },
323
- assets: {...assets, ...serverOptions?.assets},
324
- }),
325
- );
369
+ if (serverOptions) {
370
+ const serverOptionsObject =
371
+ typeof serverOptions === 'object' ? serverOptions : {};
372
+
373
+ optionPromises.push(
374
+ quiltAppServer({
375
+ root: project.root,
376
+ app,
377
+ graphql,
378
+ runtime: runtime?.server,
379
+ ...serverOptionsObject,
380
+ env: {
381
+ ...resolveEnvOption(env),
382
+ ...resolveEnvOption(serverOptionsObject?.env),
383
+ },
384
+ assets: {...assets, ...serverOptionsObject?.assets},
385
+ }),
386
+ );
387
+ }
388
+
389
+ if (serviceWorkerOptions) {
390
+ const serviceWorkerOptionsObject =
391
+ typeof serviceWorkerOptions === 'object' ? serviceWorkerOptions : {};
392
+
393
+ optionPromises.push(
394
+ quiltAppServiceWorker({
395
+ root: project.root,
396
+ app,
397
+ graphql,
398
+ ...serviceWorkerOptionsObject,
399
+ env: {
400
+ ...resolveEnvOption(env),
401
+ ...resolveEnvOption(serviceWorkerOptionsObject?.env),
402
+ },
403
+ assets: {...assets, ...serviceWorkerOptionsObject?.assets},
404
+ }),
405
+ );
406
+ }
326
407
 
327
408
  return Promise.all(optionPromises);
328
409
  }
@@ -769,6 +850,197 @@ export function quiltAppServerInput({
769
850
  } satisfies Plugin;
770
851
  }
771
852
 
853
+ export async function quiltAppServiceWorker(
854
+ options: AppServiceWorkerOptions = {},
855
+ ) {
856
+ const {output, root = process.cwd()} = options;
857
+
858
+ const project = Project.load(root);
859
+ const hash = output?.hash ?? 'async-only';
860
+
861
+ const plugins = await quiltAppServiceWorkerPlugins({
862
+ ...options,
863
+ root,
864
+ });
865
+
866
+ return {
867
+ plugins,
868
+ output: {
869
+ format: 'iife',
870
+ dir: project.resolve(`build/service-worker`),
871
+ entryFileNames: `[name]${hash === true ? `.[hash]` : ''}.js`,
872
+ chunkFileNames: `[name]${
873
+ hash === true || hash === 'async-only' ? `.[hash]` : ''
874
+ }.js`,
875
+ assetFileNames: `[name]${hash === true ? `.[hash]` : ''}.[ext]`,
876
+ generatedCode: 'es2015',
877
+ },
878
+ } satisfies RollupOptions;
879
+ }
880
+
881
+ export async function quiltAppServiceWorkerPlugins({
882
+ root = process.cwd(),
883
+ app,
884
+ env,
885
+ entry,
886
+ graphql = true,
887
+ assets,
888
+ output,
889
+ }: AppServiceWorkerOptions = {}) {
890
+ const project = Project.load(root);
891
+ const mode = (typeof env === 'object' ? env?.mode : env) ?? 'production';
892
+
893
+ const baseURL = assets?.baseURL ?? '/assets/';
894
+ const assetsInline = assets?.inline ?? true;
895
+
896
+ const outputDirectory = project.resolve('build/service-worker');
897
+ const reportsDirectory = path.resolve(outputDirectory, '../reports');
898
+
899
+ const bundle = output?.bundle;
900
+ const minify = output?.minify ?? false;
901
+
902
+ const [
903
+ {visualizer},
904
+ {magicModuleEnv, replaceProcessEnv},
905
+ {sourceCode},
906
+ {react},
907
+ {tsconfigAliases},
908
+ {monorepoPackageAliases},
909
+ {css},
910
+ {rawAssets, staticAssets},
911
+ {asyncModules},
912
+ {esnext},
913
+ nodePlugins,
914
+ ] = await Promise.all([
915
+ import('rollup-plugin-visualizer'),
916
+ import('./features/env.ts'),
917
+ import('./features/source-code.ts'),
918
+ import('./features/react.ts'),
919
+ import('./features/typescript.ts'),
920
+ import('./features/node.ts'),
921
+ import('./features/css.ts'),
922
+ import('./features/assets.ts'),
923
+ import('./features/async.ts'),
924
+ import('./features/esnext.ts'),
925
+ getNodePlugins({
926
+ bundle,
927
+ resolve: {exportConditions: ['browser']},
928
+ }),
929
+ ]);
930
+
931
+ const plugins: InputPluginOption[] = [
932
+ quiltAppServiceWorkerInput({root: project.root, entry}),
933
+ ...nodePlugins,
934
+ replaceProcessEnv({mode}),
935
+ magicModuleEnv({
936
+ ...resolveEnvOption(env),
937
+ mode,
938
+ root: project.root,
939
+ }),
940
+ magicModuleAppComponent({entry: app, root: project.root}),
941
+ magicModuleAppAssetManifests(),
942
+ sourceCode({
943
+ mode,
944
+ // TODO
945
+ targets: ['defaults and not dead'],
946
+ babel: {
947
+ options(options) {
948
+ return {
949
+ ...options,
950
+ plugins: [
951
+ ...(options?.plugins ?? []),
952
+ require.resolve('@quilted/babel/async'),
953
+ [require.resolve('@quilted/babel/workers'), {noop: true}],
954
+ ],
955
+ };
956
+ },
957
+ },
958
+ }),
959
+ react(),
960
+ esnext({
961
+ mode,
962
+ // TODO
963
+ targets: ['defaults and not dead'],
964
+ }),
965
+ css({emit: false, minify}),
966
+ rawAssets(),
967
+ staticAssets({
968
+ emit: false,
969
+ baseURL,
970
+ inlineLimit: assetsInline
971
+ ? typeof assetsInline === 'boolean'
972
+ ? undefined
973
+ : assetsInline?.limit
974
+ : Number.POSITIVE_INFINITY,
975
+ }),
976
+ asyncModules({
977
+ baseURL,
978
+ preload: false,
979
+ moduleID: ({imported}) => path.relative(project.root, imported),
980
+ }),
981
+ removeBuildFiles([outputDirectory], {root: project.root}),
982
+ tsconfigAliases({root: project.root}),
983
+ monorepoPackageAliases({root: project.root}),
984
+ ];
985
+
986
+ if (graphql) {
987
+ const {graphql} = await import('./features/graphql.ts');
988
+ plugins.push(graphql({manifest: false}));
989
+ }
990
+
991
+ if (minify) {
992
+ const {minify} = await import('rollup-plugin-esbuild');
993
+ plugins.push(minify());
994
+ }
995
+
996
+ plugins.push(
997
+ visualizer({
998
+ template: 'treemap',
999
+ open: false,
1000
+ brotliSize: false,
1001
+ filename: path.join(
1002
+ reportsDirectory,
1003
+ `bundle-visualizer.service-worker.html`,
1004
+ ),
1005
+ }),
1006
+ );
1007
+
1008
+ return plugins;
1009
+ }
1010
+
1011
+ export function quiltAppServiceWorkerInput({
1012
+ root = process.cwd(),
1013
+ entry,
1014
+ }: Pick<AppServiceWorkerOptions, 'root' | 'entry'> = {}) {
1015
+ return {
1016
+ name: '@quilted/app-server/input',
1017
+ async options(options) {
1018
+ const serviceWorkerEntry =
1019
+ normalizeRollupInput(options.input) ??
1020
+ (await sourceEntryForAppServiceWorker({entry, root}));
1021
+
1022
+ if (serviceWorkerEntry == null) {
1023
+ throw new Error(
1024
+ `No service worker entry found. Please provide a \`service.entry\` option pointing to your service worker’s source code.`,
1025
+ );
1026
+ }
1027
+
1028
+ const finalEntryName =
1029
+ typeof serviceWorkerEntry === 'string'
1030
+ ? path.basename(serviceWorkerEntry).split('.').slice(0, -1).join('.')
1031
+ : 'service-worker';
1032
+
1033
+ return {
1034
+ ...options,
1035
+ input:
1036
+ typeof serviceWorkerEntry === 'string'
1037
+ ? {[finalEntryName]: serviceWorkerEntry}
1038
+ : serviceWorkerEntry,
1039
+ };
1040
+ },
1041
+ } satisfies Plugin;
1042
+ }
1043
+
772
1044
  export interface NodeAppServerRuntimeOptions extends NodeServerRuntimeOptions {
773
1045
  /**
774
1046
  * Whether the server should serve assets from the asset output directory.
@@ -1089,6 +1361,30 @@ export async function sourceEntryForAppServer({
1089
1361
  }
1090
1362
  }
1091
1363
 
1364
+ export async function sourceEntryForAppServiceWorker({
1365
+ entry,
1366
+ root = process.cwd(),
1367
+ }: {
1368
+ entry?: string;
1369
+ root?: string | URL;
1370
+ }) {
1371
+ const project = Project.load(root);
1372
+
1373
+ if (entry) {
1374
+ return project.resolve(entry);
1375
+ } else {
1376
+ const files = await project.glob(
1377
+ '{sw,service-worker}.{ts,tsx,mjs,js,jsx}',
1378
+ {
1379
+ nodir: true,
1380
+ absolute: true,
1381
+ },
1382
+ );
1383
+
1384
+ return files[0];
1385
+ }
1386
+ }
1387
+
1092
1388
  const FRAMEWORK_CHUNK_NAME = 'framework';
1093
1389
  const POLYFILLS_CHUNK_NAME = 'polyfills';
1094
1390
  const VENDOR_CHUNK_NAME = 'vendor';
@@ -98,11 +98,13 @@ export async function sourceEntriesForProject(project: Project) {
98
98
  const entries: Record<string, string> = {};
99
99
 
100
100
  if (typeof main === 'string') {
101
- entries['.'] = await resolveTargetFileAsSource(main, project);
101
+ const sourceFile = await resolveTargetFileAsSource(main, project);
102
+ if (sourceFile) entries['.'] = sourceFile;
102
103
  }
103
104
 
104
105
  if (typeof exports === 'string') {
105
- entries['.'] = await resolveTargetFileAsSource(exports, project);
106
+ const sourceFile = await resolveTargetFileAsSource(exports, project);
107
+ if (sourceFile) entries['.'] = sourceFile;
106
108
  return entries;
107
109
  } else if (exports == null || typeof exports !== 'object') {
108
110
  return entries;
@@ -136,17 +138,31 @@ export async function sourceEntriesForProject(project: Project) {
136
138
 
137
139
  const entryName =
138
140
  condition === 'default' ? exportPath : `${exportPath}#${condition}`;
139
- entries[entryName] = await resolveTargetFileAsSource(file, project);
141
+ const sourceFile = await resolveTargetFileAsSource(file, project);
142
+ if (sourceFile) entries[entryName] = sourceFile;
140
143
  }
141
144
  } else {
142
145
  const sourceFile = await resolveTargetFileAsSource(targetFile, project);
143
- entries[exportPath] = sourceFile;
146
+ if (sourceFile) entries[exportPath] = sourceFile;
144
147
  }
145
148
  }
146
149
 
147
150
  return entries;
148
151
  }
149
152
 
153
+ const ALLOWED_SOURCE_FILE_EXTENSIONS = new Set([
154
+ '.ts',
155
+ '.tsx',
156
+ '.mts',
157
+ '.cts',
158
+ '.js',
159
+ '.jsx',
160
+ '.mjs',
161
+ '.cjs',
162
+ '.json',
163
+ '.node',
164
+ ]);
165
+
150
166
  async function resolveTargetFileAsSource(file: string, project: Project) {
151
167
  const sourceFile = file.includes('/build/')
152
168
  ? (
@@ -162,5 +178,9 @@ async function resolveTargetFileAsSource(file: string, project: Project) {
162
178
  )[0]!
163
179
  : project.resolve(file);
164
180
 
181
+ if (!ALLOWED_SOURCE_FILE_EXTENSIONS.has(path.extname(sourceFile))) {
182
+ return undefined;
183
+ }
184
+
165
185
  return sourceFile;
166
186
  }
package/tsconfig.json CHANGED
@@ -1,11 +1,5 @@
1
1
  {
2
- "extends": "@quilted/typescript/tsconfig.project.json",
3
- "compilerOptions": {
4
- "rootDir": "source",
5
- "outDir": "build/typescript"
6
- },
7
- "include": ["source"],
8
- "exclude": ["**/*.test.ts", "**/*.test.tsx"],
2
+ "extends": "@quilted/typescript/tsconfig.package.json",
9
3
  "references": [
10
4
  {"path": "../assets"},
11
5
  {"path": "../babel"},