@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
@@ -17,11 +17,10 @@ import type {
17
17
  } from '@quilted/assets';
18
18
 
19
19
  export interface AssetManifestOptions {
20
- id?: string;
21
20
  file: string;
22
21
  baseURL: string;
23
22
  priority?: number;
24
- cacheKey?: Record<string, any>;
23
+ cacheKey?: URLSearchParams;
25
24
  }
26
25
 
27
26
  export function assetManifest(manifestOptions: AssetManifestOptions): Plugin {
@@ -36,7 +35,7 @@ export function assetManifest(manifestOptions: AssetManifestOptions): Plugin {
36
35
  async function writeManifestForBundle(
37
36
  this: PluginContext,
38
37
  bundle: OutputBundle,
39
- {id, file, baseURL, cacheKey, priority}: AssetManifestOptions,
38
+ {file, baseURL, cacheKey, priority}: AssetManifestOptions,
40
39
  {format}: NormalizedOutputOptions,
41
40
  ) {
42
41
  const outputs = Object.values(bundle);
@@ -77,10 +76,9 @@ async function writeManifestForBundle(
77
76
  return id;
78
77
  }
79
78
 
80
- const manifest: AssetsBuildManifest<any> = {
81
- id,
79
+ const manifest: AssetsBuildManifest = {
82
80
  priority,
83
- cacheKey,
81
+ cacheKey: cacheKey && cacheKey.size > 0 ? cacheKey.toString() : undefined,
84
82
  assets,
85
83
  attributes: format === 'es' ? {scripts: {type: 'module'}} : undefined,
86
84
  entries: {
@@ -101,7 +99,7 @@ async function writeManifestForBundle(
101
99
  if (originalModuleId == null) continue;
102
100
 
103
101
  // This metadata is added by the rollup plugin for @quilted/async
104
- const moduleId = this.getModuleInfo(originalModuleId)?.meta.quilt?.moduleId;
102
+ const moduleId = this.getModuleInfo(originalModuleId)?.meta.quilt?.moduleID;
105
103
 
106
104
  if (moduleId == null) continue;
107
105
 
@@ -0,0 +1,249 @@
1
+ import {createHash} from 'crypto';
2
+ import {posix, sep} from 'path';
3
+
4
+ import type {Plugin, OutputChunk, OutputBundle} from 'rollup';
5
+ import {multiline} from '../shared/strings.ts';
6
+ import MagicString from 'magic-string';
7
+
8
+ const MODULE_PREFIX = 'quilt-async-module:';
9
+ const IMPORT_PREFIX = 'quilt-async-import:';
10
+
11
+ export interface Options {
12
+ preload?: boolean;
13
+ baseURL?: string;
14
+ moduleID?(details: {imported: string}): string;
15
+ }
16
+
17
+ export function asyncModules({
18
+ preload = true,
19
+ baseURL = '/assets/',
20
+ moduleID: getModuleID = defaultModuleID,
21
+ }: Options = {}): Plugin {
22
+ return {
23
+ name: '@quilted/async',
24
+ async resolveId(id, importer) {
25
+ if (id.startsWith(IMPORT_PREFIX)) return `\0${id}`;
26
+ if (!id.startsWith(MODULE_PREFIX)) return null;
27
+
28
+ const imported = id.replace(MODULE_PREFIX, '');
29
+
30
+ const resolved = await this.resolve(imported, importer, {
31
+ skipSelf: true,
32
+ });
33
+
34
+ if (resolved == null) return null;
35
+
36
+ return `\0${MODULE_PREFIX}${resolved.id}`;
37
+ },
38
+ resolveDynamicImport(specifier) {
39
+ if (
40
+ typeof specifier === 'string' &&
41
+ specifier.startsWith(IMPORT_PREFIX)
42
+ ) {
43
+ return `\0${specifier}`;
44
+ }
45
+
46
+ return null;
47
+ },
48
+ async load(id: string) {
49
+ if (id.startsWith(`\0${MODULE_PREFIX}`)) {
50
+ const imported = id.replace(`\0${MODULE_PREFIX}`, '');
51
+ const moduleID = getModuleID({imported});
52
+
53
+ const code = multiline`
54
+ const id = ${JSON.stringify(moduleID)};
55
+
56
+ export default function createAsyncModule(load) {
57
+ return {
58
+ id,
59
+ import: () => import(${JSON.stringify(
60
+ `${IMPORT_PREFIX}${imported}`,
61
+ )}).then((module) => module.default),
62
+ };
63
+ }
64
+ `;
65
+
66
+ return code;
67
+ }
68
+
69
+ if (id.startsWith(`\0${IMPORT_PREFIX}`)) {
70
+ const imported = id.replace(`\0${IMPORT_PREFIX}`, '');
71
+ const moduleID = getModuleID({imported});
72
+
73
+ const code = multiline`
74
+ import * as AsyncModule from ${JSON.stringify(imported)};
75
+
76
+ ((globalThis[Symbol.for('quilt')] ??= {}).AsyncModules ??= new Map).set(${JSON.stringify(
77
+ moduleID,
78
+ )}, AsyncModule);
79
+
80
+ export default AsyncModule;
81
+ `;
82
+
83
+ return {
84
+ code,
85
+ meta: {
86
+ quilt: {moduleID},
87
+ },
88
+ };
89
+ }
90
+
91
+ return null;
92
+ },
93
+ transform: baseURL
94
+ ? (code) =>
95
+ code.replace(/__QUILT_ASSETS_BASE_URL__/g, JSON.stringify(baseURL))
96
+ : undefined,
97
+ async generateBundle(options, bundle) {
98
+ if (preload) {
99
+ switch (options.format) {
100
+ case 'es': {
101
+ await preloadAsyncAssetsInESMBundle(bundle);
102
+ break;
103
+ }
104
+ case 'system': {
105
+ await preloadAsyncAssetsInSystemJSBundle(bundle);
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ },
111
+ };
112
+ }
113
+
114
+ function defaultModuleID({imported}: {imported: string}) {
115
+ const name = imported.split(sep).pop()!.split('.')[0]!;
116
+
117
+ const hash = createHash('sha256')
118
+ .update(imported)
119
+ .digest('hex')
120
+ .substring(0, 8);
121
+
122
+ return `${name}_${hash}`;
123
+ }
124
+
125
+ async function preloadAsyncAssetsInESMBundle(bundle: OutputBundle) {
126
+ const {parse: parseImports} = await import('es-module-lexer');
127
+
128
+ for (const chunk of Object.values(bundle)) {
129
+ if (chunk.type !== 'chunk') continue;
130
+ if (chunk.dynamicImports.length === 0) continue;
131
+
132
+ const {code} = chunk;
133
+
134
+ const newCode = new MagicString(code);
135
+
136
+ const imports = (await parseImports(code))[0];
137
+
138
+ for (const imported of imports) {
139
+ const {s: start, e: end, ss: importStart, d: dynamicStart} = imported;
140
+
141
+ // es-module-lexer only sets `d >= 0` when the import is a dynamic one
142
+ if (dynamicStart < 0) continue;
143
+
144
+ // Get rid of the quotes
145
+ const importSource = code.slice(start + 1, end - 1);
146
+
147
+ const dependencies = getDependenciesForImport(
148
+ importSource,
149
+ chunk,
150
+ bundle,
151
+ );
152
+
153
+ // The only dependency is the file itself, no need to preload
154
+ if (dependencies.size === 1) continue;
155
+
156
+ const originalImport = code.slice(importStart, end + 1);
157
+ newCode.overwrite(
158
+ importStart,
159
+ end + 1,
160
+ preloadContentForDependencies(dependencies, originalImport),
161
+ );
162
+ }
163
+
164
+ chunk.code = newCode.toString();
165
+ }
166
+ }
167
+
168
+ async function preloadAsyncAssetsInSystemJSBundle(bundle: OutputBundle) {
169
+ for (const chunk of Object.values(bundle)) {
170
+ if (chunk.type !== 'chunk') continue;
171
+ if (chunk.dynamicImports.length === 0) continue;
172
+
173
+ const {code} = chunk;
174
+
175
+ const newCode = new MagicString(code);
176
+
177
+ const systemDynamicImportRegex = /\bmodule\.import\(([^)]*)\)/g;
178
+
179
+ let match: RegExpExecArray | null;
180
+
181
+ while ((match = systemDynamicImportRegex.exec(code))) {
182
+ const [originalImport, imported] = match;
183
+
184
+ // Get rid of surrounding space and quotes
185
+ const importSource = imported!.trim().slice(1, imported!.length - 1);
186
+
187
+ const dependencies = getDependenciesForImport(
188
+ importSource,
189
+ chunk,
190
+ bundle,
191
+ );
192
+
193
+ if (dependencies.size === 1) continue;
194
+
195
+ newCode.overwrite(
196
+ match.index,
197
+ match.index + originalImport!.length,
198
+ preloadContentForDependencies(dependencies, originalImport!),
199
+ );
200
+ }
201
+
202
+ chunk.code = newCode.toString();
203
+ }
204
+ }
205
+
206
+ function preloadContentForDependencies(
207
+ dependencies: Iterable<string>,
208
+ originalExpression: string,
209
+ ) {
210
+ return `Promise.resolve().then(() => globalThis[Symbol.for('quilt')]?.AsyncModules?.preload?.(${Array.from(
211
+ dependencies,
212
+ )
213
+ .map((dependency) => JSON.stringify(dependency))
214
+ .join(',')})).then(function(){return ${originalExpression}})`;
215
+ }
216
+
217
+ function getDependenciesForImport(
218
+ imported: string,
219
+ chunk: OutputChunk,
220
+ bundle: OutputBundle,
221
+ ) {
222
+ const originalFilename = chunk.fileName;
223
+ const dependencies = new Set<string>();
224
+ const analyzed = new Set<string>();
225
+
226
+ const normalizedFile = posix.join(posix.dirname(originalFilename), imported);
227
+
228
+ const addDependencies = (filename: string) => {
229
+ if (filename === originalFilename) return;
230
+ if (analyzed.has(filename)) return;
231
+
232
+ analyzed.add(filename);
233
+ const chunk = bundle[filename];
234
+
235
+ if (chunk == null) return;
236
+
237
+ dependencies.add(chunk.fileName);
238
+
239
+ if (chunk.type !== 'chunk') return;
240
+
241
+ for (const imported of chunk.imports) {
242
+ addDependencies(imported);
243
+ }
244
+ };
245
+
246
+ addDependencies(normalizedFile);
247
+
248
+ return dependencies;
249
+ }
@@ -3,12 +3,13 @@ import type {Plugin} from 'rollup';
3
3
  export interface Options {
4
4
  minify?: boolean;
5
5
  emit?: boolean;
6
+ targets?: string[];
6
7
  }
7
8
 
8
9
  const CSS_REGEX = /\.css$/;
9
10
  const CSS_MODULE_REGEX = /\.module\.css$/;
10
11
 
11
- export function css({minify = true, emit = true}: Options) {
12
+ export function css({minify = true, emit = true, targets}: Options) {
12
13
  const styles = new Map<string, string>();
13
14
 
14
15
  return {
@@ -16,13 +17,14 @@ export function css({minify = true, emit = true}: Options) {
16
17
  async transform(code, id) {
17
18
  if (!CSS_REGEX.test(id)) return;
18
19
 
19
- const {transform} = await import('lightningcss');
20
+ const {transform, browserslistToTargets} = await import('lightningcss');
20
21
 
21
22
  const transformed = transform({
22
23
  filename: id,
23
24
  code: new TextEncoder().encode(code),
24
25
  cssModules: CSS_MODULE_REGEX.test(id),
25
26
  minify: emit && minify,
27
+ targets: targets ? browserslistToTargets(targets) : undefined,
26
28
  });
27
29
 
28
30
  styles.set(id, new TextDecoder().decode(transformed.code));
@@ -69,6 +69,12 @@ export interface MagicModuleEnvOptions {
69
69
  | {roots?: never; files?: string[]};
70
70
  }
71
71
 
72
+ export function resolveEnvOption(
73
+ option?: MagicModuleEnvOptions['mode'] | MagicModuleEnvOptions,
74
+ ) {
75
+ return typeof option === 'string' ? {mode: option} : option ?? {};
76
+ }
77
+
72
78
  export function magicModuleEnv({
73
79
  mode,
74
80
  dotenv = {roots: ['.', 'configuration']},
@@ -0,0 +1,70 @@
1
+ import {createRequire} from 'module';
2
+
3
+ import babel, {type RollupBabelInputPluginOptions} from '@rollup/plugin-babel';
4
+ import esbuild from 'rollup-plugin-esbuild';
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ export function esnext({
9
+ mode,
10
+ targets,
11
+ babel: useBabel = true,
12
+ }: {
13
+ mode?: 'development' | 'production';
14
+ targets?: readonly string[];
15
+ babel?:
16
+ | boolean
17
+ | {
18
+ options?(
19
+ options: RollupBabelInputPluginOptions,
20
+ ): RollupBabelInputPluginOptions | void;
21
+ };
22
+ }) {
23
+ if (!useBabel) {
24
+ return esbuild({
25
+ // Support very modern features
26
+ include: /\.esnext$/,
27
+ target: 'es2022',
28
+ loaders: {
29
+ '.esnext': 'js',
30
+ },
31
+ });
32
+ }
33
+
34
+ let babelOptions: RollupBabelInputPluginOptions = {
35
+ envName: mode,
36
+ configFile: false,
37
+ babelrc: false,
38
+ presets: [
39
+ [
40
+ require.resolve('@babel/preset-env'),
41
+ {
42
+ useBuiltIns: false,
43
+ bugfixes: true,
44
+ shippedProposals: true,
45
+ // I thought I wanted this, but it seems to break the `targets` option
46
+ // passed as a root argument.
47
+ // ignoreBrowserslistConfig: targets != null,
48
+ } satisfies import('@babel/preset-env').Options,
49
+ ],
50
+ ],
51
+ plugins: [
52
+ [
53
+ require.resolve('@babel/plugin-proposal-decorators'),
54
+ {version: '2023-01'},
55
+ ],
56
+ ],
57
+ include: /\.esnext$/,
58
+ extensions: ['.esnext'],
59
+ babelHelpers: 'bundled',
60
+ skipPreflightCheck: true,
61
+ // Babel doesn’t like this option being set to `undefined`.
62
+ ...(targets ? {targets: targets as string[]} : {}),
63
+ };
64
+
65
+ if (typeof useBabel === 'object') {
66
+ babelOptions = useBabel.options?.(babelOptions) ?? babelOptions;
67
+ }
68
+
69
+ return babel(babelOptions);
70
+ }
@@ -10,11 +10,11 @@ import {
10
10
  extractGraphQLImports,
11
11
  } from './graphql/transform.ts';
12
12
 
13
- export interface Options {
13
+ export interface GraphQLOptions {
14
14
  manifest?: string | boolean;
15
15
  }
16
16
 
17
- export function graphql({manifest}: Options = {}): Plugin {
17
+ export function graphql({manifest}: GraphQLOptions = {}): Plugin {
18
18
  const shouldWriteManifest = Boolean(manifest);
19
19
  const manifestPath =
20
20
  typeof manifest === 'string' ? manifest : `manifests/graphql.json`;
@@ -75,6 +75,8 @@ export function graphql({manifest}: Options = {}): Plugin {
75
75
  }
76
76
  }
77
77
 
78
+ if (Object.keys(operations).length === 0) return;
79
+
78
80
  await mkdir(dirname(manifestPath), {recursive: true});
79
81
  await writeFile(manifestPath, JSON.stringify(operations, null, 2));
80
82
  },
@@ -1,30 +1,43 @@
1
1
  import {createRequire} from 'module';
2
2
 
3
- import babel from '@rollup/plugin-babel';
3
+ import babel, {type RollupBabelInputPluginOptions} from '@rollup/plugin-babel';
4
4
  import esbuild from 'rollup-plugin-esbuild';
5
+ import type {Options as PresetEnvOptions} from '@babel/preset-env';
5
6
 
6
7
  const require = createRequire(import.meta.url);
7
8
 
8
9
  export function sourceCode({
9
10
  mode,
10
11
  targets,
12
+ react = true,
11
13
  babel: useBabel = true,
12
14
  }: {
13
15
  mode?: 'development' | 'production';
14
- targets?: string[];
15
- babel?: boolean;
16
+ react?: boolean | 'react' | 'preact';
17
+ targets?: readonly string[];
18
+ babel?:
19
+ | boolean
20
+ | {
21
+ useBuiltIns?: PresetEnvOptions['useBuiltIns'];
22
+ options?(
23
+ options: RollupBabelInputPluginOptions,
24
+ ): RollupBabelInputPluginOptions | void;
25
+ };
16
26
  }) {
17
27
  if (!useBabel) {
18
28
  return esbuild({
19
29
  // Support very modern features
20
30
  target: 'es2022',
21
31
  jsx: 'automatic',
22
- jsxImportSource: 'react',
32
+ jsxImportSource: typeof react === 'string' ? react : 'react',
23
33
  exclude: 'node_modules/**',
24
34
  });
25
35
  }
26
36
 
27
- return babel({
37
+ const babelOverride = typeof useBabel === 'boolean' ? {} : useBabel;
38
+ const useBuiltIns = babelOverride.useBuiltIns;
39
+
40
+ let babelOptions: RollupBabelInputPluginOptions = {
28
41
  envName: mode,
29
42
  configFile: false,
30
43
  babelrc: false,
@@ -34,16 +47,17 @@ export function sourceCode({
34
47
  require.resolve('@babel/preset-react'),
35
48
  {
36
49
  runtime: 'automatic',
37
- importSource: 'react',
50
+ importSource: typeof react === 'string' ? react : 'react',
38
51
  development: mode === 'development',
39
52
  },
40
53
  ],
41
54
  [
42
55
  require.resolve('@babel/preset-env'),
43
56
  {
44
- useBuiltIns: false,
45
57
  bugfixes: true,
46
58
  shippedProposals: true,
59
+ useBuiltIns,
60
+ corejs: useBuiltIns ? 3 : undefined,
47
61
  // I thought I wanted this, but it seems to break the `targets` option
48
62
  // passed as a root argument.
49
63
  // ignoreBrowserslistConfig: targets != null,
@@ -71,6 +85,10 @@ export function sourceCode({
71
85
  babelHelpers: 'bundled',
72
86
  skipPreflightCheck: true,
73
87
  // Babel doesn’t like this option being set to `undefined`.
74
- ...(targets ? {targets} : {}),
75
- });
88
+ ...(targets ? {targets: targets as string[]} : {}),
89
+ };
90
+
91
+ babelOptions = babelOverride.options?.(babelOptions) ?? babelOptions;
92
+
93
+ return babel(babelOptions);
76
94
  }
@@ -2,11 +2,14 @@ import {readFile} from 'fs/promises';
2
2
  import {createRequire} from 'module';
3
3
 
4
4
  import type {Plugin} from 'rollup';
5
+ import MagicString from 'magic-string';
6
+
7
+ import {multiline} from '../shared/strings';
5
8
 
6
9
  export function systemJS({minify = false} = {}) {
7
10
  return {
8
11
  name: '@quilted/system-js',
9
- async renderChunk(_, chunk, options) {
12
+ async renderChunk(code, chunk, options) {
10
13
  if (options.format !== 'system' || !chunk.isEntry) return null;
11
14
 
12
15
  const require = createRequire(import.meta.url);
@@ -30,7 +33,27 @@ export function systemJS({minify = false} = {}) {
30
33
 
31
34
  chunk.imports.unshift(this.getFileName(fileHandle));
32
35
 
33
- return null;
36
+ const newCode = new MagicString(code);
37
+
38
+ // We force the systemjs loader to immediately load the entrypoint. This allows the
39
+ // scripts to be marked as `defer`; systemjs automatically runs an entrypoint when it
40
+ // is the last script on the page, but only when scripts run in the `loading` document
41
+ // ready state.
42
+ //
43
+ // @see https://github.com/systemjs/systemjs/pull/2216
44
+ newCode.append(multiline`
45
+ if (document.currentScript) {
46
+ System.import(document.currentScript.src);
47
+ } else {
48
+ var scripts = document.getElementsByTagName('script');
49
+ System.import(scripts[scripts.length - 1].src);
50
+ }
51
+ `);
52
+
53
+ return {
54
+ code: newCode.toString(),
55
+ map: newCode.generateMap({hires: true}),
56
+ };
34
57
  },
35
58
  } satisfies Plugin;
36
59
  }