@quilted/rollup 0.3.2 → 0.4.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.
@@ -5,7 +5,6 @@ import {multiline} from '../shared/strings.ts';
5
5
  import MagicString from 'magic-string';
6
6
 
7
7
  const MODULE_PREFIX = 'quilt-async-module:';
8
- export const IMPORT_PREFIX = 'quilt-async-import:';
9
8
 
10
9
  export interface Options {
11
10
  preload?: boolean;
@@ -23,9 +22,7 @@ export function asyncModules({
23
22
  async resolveId(id, importer) {
24
23
  let prefix: string;
25
24
 
26
- if (id.startsWith(IMPORT_PREFIX)) {
27
- prefix = IMPORT_PREFIX;
28
- } else if (id.startsWith(MODULE_PREFIX)) {
25
+ if (id.startsWith(MODULE_PREFIX)) {
29
26
  prefix = MODULE_PREFIX;
30
27
  } else {
31
28
  return null;
@@ -52,23 +49,6 @@ export function asyncModules({
52
49
  }
53
50
  `;
54
51
 
55
- return code;
56
- }
57
-
58
- if (id.startsWith(`\0${IMPORT_PREFIX}`)) {
59
- const imported = id.replace(`\0${IMPORT_PREFIX}`, '');
60
- const moduleID = getModuleID({imported});
61
-
62
- const code = multiline`
63
- import * as AsyncModule from ${JSON.stringify(imported)};
64
-
65
- ((globalThis[Symbol.for('quilt')] ||= {}).asyncModules ||= new Map).set(${JSON.stringify(
66
- moduleID,
67
- )}, AsyncModule);
68
-
69
- export default AsyncModule;
70
- `;
71
-
72
52
  return {
73
53
  code,
74
54
  meta: {
@@ -79,19 +59,15 @@ export function asyncModules({
79
59
 
80
60
  return null;
81
61
  },
82
- transform: baseURL
83
- ? (code) =>
84
- code.replace(/__QUILT_ASSETS_BASE_URL__/g, JSON.stringify(baseURL))
85
- : undefined,
86
62
  async generateBundle(options, bundle) {
87
63
  if (preload) {
88
64
  switch (options.format) {
89
65
  case 'es': {
90
- await preloadAsyncAssetsInESMBundle(bundle);
66
+ await preloadAsyncAssetsInESMBundle(bundle, {baseURL});
91
67
  break;
92
68
  }
93
69
  case 'system': {
94
- await preloadAsyncAssetsInSystemJSBundle(bundle);
70
+ await preloadAsyncAssetsInSystemJSBundle(bundle, {baseURL});
95
71
  break;
96
72
  }
97
73
  }
@@ -104,7 +80,10 @@ function defaultModuleID({imported}: {imported: string}) {
104
80
  return path.relative(process.cwd(), imported).replace(/[\\/]/g, '-');
105
81
  }
106
82
 
107
- async function preloadAsyncAssetsInESMBundle(bundle: OutputBundle) {
83
+ async function preloadAsyncAssetsInESMBundle(
84
+ bundle: OutputBundle,
85
+ {baseURL = '/'}: {baseURL?: string} = {},
86
+ ) {
108
87
  const {parse: parseImports} = await import('es-module-lexer');
109
88
 
110
89
  for (const chunk of Object.values(bundle)) {
@@ -112,10 +91,10 @@ async function preloadAsyncAssetsInESMBundle(bundle: OutputBundle) {
112
91
  if (chunk.dynamicImports.length === 0) continue;
113
92
 
114
93
  const {code} = chunk;
115
-
116
94
  const newCode = new MagicString(code);
117
95
 
118
96
  const imports = (await parseImports(code))[0];
97
+ let hasReplacements = false;
119
98
 
120
99
  for (const imported of imports) {
121
100
  const {s: start, e: end, ss: importStart, d: dynamicStart} = imported;
@@ -130,11 +109,13 @@ async function preloadAsyncAssetsInESMBundle(bundle: OutputBundle) {
130
109
  importSource,
131
110
  chunk,
132
111
  bundle,
112
+ {baseURL},
133
113
  );
134
114
 
135
115
  // The only dependency is the file itself, no need to preload
136
116
  if (dependencies.size === 1) continue;
137
117
 
118
+ hasReplacements = true;
138
119
  const originalImport = code.slice(importStart, end + 1);
139
120
  newCode.overwrite(
140
121
  importStart,
@@ -143,22 +124,29 @@ async function preloadAsyncAssetsInESMBundle(bundle: OutputBundle) {
143
124
  );
144
125
  }
145
126
 
127
+ if (hasReplacements) {
128
+ newCode.prepend(getPreloadHelperFunction() + '\n');
129
+ }
130
+
146
131
  chunk.code = newCode.toString();
147
132
  }
148
133
  }
149
134
 
150
- async function preloadAsyncAssetsInSystemJSBundle(bundle: OutputBundle) {
135
+ async function preloadAsyncAssetsInSystemJSBundle(
136
+ bundle: OutputBundle,
137
+ {baseURL = '/'}: {baseURL?: string} = {},
138
+ ) {
151
139
  for (const chunk of Object.values(bundle)) {
152
140
  if (chunk.type !== 'chunk') continue;
153
141
  if (chunk.dynamicImports.length === 0) continue;
154
142
 
155
143
  const {code} = chunk;
156
-
157
144
  const newCode = new MagicString(code);
158
145
 
159
146
  const systemDynamicImportRegex = /\bmodule\.import\(([^)]*)\)/g;
160
147
 
161
148
  let match: RegExpExecArray | null;
149
+ let hasReplacements = false;
162
150
 
163
151
  while ((match = systemDynamicImportRegex.exec(code))) {
164
152
  const [originalImport, imported] = match;
@@ -170,10 +158,12 @@ async function preloadAsyncAssetsInSystemJSBundle(bundle: OutputBundle) {
170
158
  importSource,
171
159
  chunk,
172
160
  bundle,
161
+ {baseURL},
173
162
  );
174
163
 
175
164
  if (dependencies.size === 1) continue;
176
165
 
166
+ hasReplacements = true;
177
167
  newCode.overwrite(
178
168
  match.index,
179
169
  match.index + originalImport!.length,
@@ -181,6 +171,10 @@ async function preloadAsyncAssetsInSystemJSBundle(bundle: OutputBundle) {
181
171
  );
182
172
  }
183
173
 
174
+ if (hasReplacements) {
175
+ newCode.prepend(getPreloadHelperFunction() + '\n');
176
+ }
177
+
184
178
  chunk.code = newCode.toString();
185
179
  }
186
180
  }
@@ -189,17 +183,14 @@ function preloadContentForDependencies(
189
183
  dependencies: Iterable<string>,
190
184
  originalExpression: string,
191
185
  ) {
192
- return `Promise.resolve().then(() => globalThis[Symbol.for('quilt')]?.asyncModules?.preload?.(${Array.from(
193
- dependencies,
194
- )
195
- .map((dependency) => JSON.stringify(dependency))
196
- .join(',')})).then(function(){return ${originalExpression}})`;
186
+ return `__quilt_preload(${JSON.stringify(Array.from(dependencies))}).then(() => {return ${originalExpression}})`;
197
187
  }
198
188
 
199
189
  function getDependenciesForImport(
200
190
  imported: string,
201
191
  chunk: OutputChunk,
202
192
  bundle: OutputBundle,
193
+ {baseURL = '/'}: {baseURL?: string} = {},
203
194
  ) {
204
195
  const originalFilename = chunk.fileName;
205
196
  const dependencies = new Set<string>();
@@ -219,7 +210,9 @@ function getDependenciesForImport(
219
210
 
220
211
  if (chunk == null) return;
221
212
 
222
- dependencies.add(chunk.fileName);
213
+ const url = `${baseURL}${baseURL.endsWith('/') ? '' : '/'}${chunk.fileName}`;
214
+
215
+ dependencies.add(url);
223
216
 
224
217
  if (chunk.type !== 'chunk') return;
225
218
 
@@ -232,3 +225,97 @@ function getDependenciesForImport(
232
225
 
233
226
  return dependencies;
234
227
  }
228
+
229
+ function getPreloadHelperFunction({
230
+ type = 'module',
231
+ }: {type?: 'module' | 'script'} = {}) {
232
+ const scriptRel = JSON.stringify(
233
+ type === 'module' ? 'modulepreload' : 'preload',
234
+ );
235
+
236
+ return multiline`
237
+ const __quilt_preload=(()=>{const o=new Map,f=${scriptRel};class QuiltPreloadError extends Error{constructor(e,{cause:l}={}){super(\`Unable to preload \${e}\`,{cause:l}),this.source=e}}class QuiltPreloadErrorEvent extends Event{constructor(e){super("quilt:preload-error",{cancelable:!0}),this.error=e}}return function __quilt_preload(e){return e.length===0?Promise.resolve():Promise.all(e.map(s=>{const r=s.startsWith("/")?s:"/"+s;if(o.has(r))return;o.set(r,!0);const i=r.endsWith(".css");if(document.querySelector(\`link[href="\${r}"]\`)!=null)return;const t=document.createElement("link");if(i?t.rel="stylesheet":(t.as="script",t.rel=f),t.crossOrigin="",t.href=r,document.head.appendChild(t),i)return new Promise(a=>{t.addEventListener("load",()=>a()),t.addEventListener("error",h=>a(new QuiltPreloadError(r,{cause:h})))})})).then(s=>{for(const r of s)r!=null&&d(r)})};function d(n){const e=new QuiltPreloadErrorEvent(n);if(window.dispatchEvent(e),!e.defaultPrevented)throw n}})();
238
+ `;
239
+ }
240
+
241
+ // Source for minified code above:
242
+ //
243
+ // const __quilt_preload = (() => {
244
+ // const SEEN = new Map();
245
+ // const SCRIPT_REL = 'module';
246
+
247
+ // return function __quiltPreload(dependencies) {
248
+ // if (dependencies.length === 0) return Promise.resolve();
249
+
250
+ // const promise = Promise.all(
251
+ // dependencies.map((dependency) => {
252
+ // const resolved = dependency.startsWith('/')
253
+ // ? dependency
254
+ // : '/' + dependency;
255
+
256
+ // if (SEEN.has(resolved)) return;
257
+ // SEEN.set(resolved, true);
258
+
259
+ // const isCSS = resolved.endsWith('.css');
260
+
261
+ // if (document.querySelector(`link[href="${resolved}"]`) != null) {
262
+ // return;
263
+ // }
264
+
265
+ // const link = document.createElement('link');
266
+
267
+ // if (isCSS) {
268
+ // link.rel = 'stylesheet';
269
+ // } else {
270
+ // link.as = 'script';
271
+ // link.rel = scriptRel;
272
+ // }
273
+
274
+ // link.crossOrigin = '';
275
+ // link.href = resolved;
276
+ // document.head.appendChild(link);
277
+
278
+ // // We will only wait for CSS
279
+ // if (isCSS) {
280
+ // return new Promise((resolve) => {
281
+ // link.addEventListener('load', () => resolve());
282
+ // link.addEventListener('error', (error) =>
283
+ // resolve(new PreloadError(resolved, {cause: error})),
284
+ // );
285
+ // });
286
+ // }
287
+ // }),
288
+ // );
289
+
290
+ // return promise.then((results) => {
291
+ // for (const result of results) {
292
+ // if (result != null) {
293
+ // handlePreloadError(result);
294
+ // }
295
+ // }
296
+ // });
297
+ // };
298
+
299
+ // function handlePreloadError(error) {
300
+ // const event = new PreloadErrorEvent(error);
301
+ // window.dispatchEvent(event);
302
+
303
+ // if (!event.defaultPrevented) {
304
+ // throw error;
305
+ // }
306
+ // }
307
+
308
+ // class PreloadError extends Error {
309
+ // constructor(source, {cause} = {}) {
310
+ // super(`Unable to preload ${source}`, {cause});
311
+ // this.source = source;
312
+ // }
313
+ // }
314
+
315
+ // class PreloadErrorEvent extends Event {
316
+ // constructor(error) {
317
+ // super('quilt:preload-error', {cancelable: true});
318
+ // this.error = error;
319
+ // }
320
+ // }
321
+ // })();
package/source/server.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  import {multiline} from './shared/strings.ts';
13
13
  import {resolveEnvOption, type MagicModuleEnvOptions} from './features/env.ts';
14
- import {MAGIC_MODULE_ENTRY, MAGIC_MODULE_REQUEST_ROUTER} from './constants.ts';
14
+ import {MAGIC_MODULE_ENTRY, MAGIC_MODULE_HONO} from './constants.ts';
15
15
  import {createMagicModulePlugin} from './shared/magic-module.ts';
16
16
  import type {ModuleRuntime} from './module.ts';
17
17
 
@@ -33,17 +33,16 @@ export interface ServerOptions {
33
33
  entry?: string;
34
34
 
35
35
  /**
36
- * Whether this server code uses the `request-router` library to
37
- * define itself in a generic way, which can be adapted to a variety
38
- * of environments. By default, this is `'request-router'`, and when `'request-router'`,
39
- * the `entry` you specified must export an `RequestRouter` object as
40
- * its default export. When set to `false`, the app server will be built
41
- * as a basic server-side JavaScript project, without the special
42
- * `request-router` adaptor.
36
+ * Whether this server code uses `hono` to define itself in a generic way,
37
+ * which can be adapted to a variety of environments. By default, this is
38
+ * `'hono'`, and when `'hono'`, the `entry` you specified must export an
39
+ * `Hono` object as its default export. When set to `'custom'`, the app
40
+ * server will be built as a basic server-side JavaScript project, without
41
+ * the special `hono` adaptor.
43
42
  *
44
- * @default 'request-router'
43
+ * @default 'hono'
45
44
  */
46
- format?: 'request-router' | 'custom';
45
+ format?: 'hono' | 'custom';
47
46
 
48
47
  /**
49
48
  * Whether to include GraphQL-related code transformations.
@@ -76,12 +75,12 @@ export interface ServerRuntime extends ModuleRuntime {
76
75
  env?: string;
77
76
 
78
77
  /**
79
- * The content to use as the entry point when the server uses the `request-router`
80
- * format for their server. This file should import the request router instance for
81
- * this app from 'quilt:module/request-router', and create a server that is appropriate
78
+ * The content to use as the entry point when the server uses the `hono`
79
+ * format for their server. This file should import the hono instance for
80
+ * this app from 'quilt:module/hono', and create a server that is appropriate
82
81
  * for this runtime.
83
82
  */
84
- requestRouter?(): string;
83
+ hono?(): string;
85
84
  }
86
85
 
87
86
  export interface ServerOutputOptions
@@ -112,12 +111,12 @@ export interface ServerOutputOptions
112
111
  hash?: boolean | 'async-only';
113
112
  }
114
113
 
115
- export {MAGIC_MODULE_ENTRY, MAGIC_MODULE_REQUEST_ROUTER};
114
+ export {MAGIC_MODULE_ENTRY, MAGIC_MODULE_HONO};
116
115
 
117
116
  export async function quiltServer({
118
117
  root: rootPath = process.cwd(),
119
118
  entry,
120
- format = 'request-router',
119
+ format = 'hono',
121
120
  env,
122
121
  graphql = true,
123
122
  output,
@@ -163,7 +162,7 @@ export async function quiltServer({
163
162
  : await sourceForServer(project);
164
163
 
165
164
  const finalEntry =
166
- format === 'request-router'
165
+ format === 'hono'
167
166
  ? MAGIC_MODULE_ENTRY
168
167
  : (serverEntry ?? MAGIC_MODULE_ENTRY);
169
168
 
@@ -188,21 +187,19 @@ export async function quiltServer({
188
187
  removeBuildFiles([outputDirectory, reportDirectory], {root: project.root}),
189
188
  ];
190
189
 
191
- if (format === 'request-router') {
190
+ if (format === 'hono') {
192
191
  plugins.push(
193
192
  createMagicModulePlugin({
194
- name: '@quilted/magic-module/server-request-router',
195
- module: MAGIC_MODULE_REQUEST_ROUTER,
193
+ name: '@quilted/magic-module/server-hono',
194
+ module: MAGIC_MODULE_HONO,
196
195
  alias: serverEntry,
197
196
  }),
198
197
  createMagicModulePlugin({
199
- name: '@quilted/request-router',
198
+ name: '@quilted/hono',
200
199
  sideEffects: true,
201
200
  module: MAGIC_MODULE_ENTRY,
202
201
  source() {
203
- return (
204
- runtime.requestRouter?.() ?? nodeServerRuntime().requestRouter()
205
- );
202
+ return runtime.hono?.() ?? nodeServerRuntime().hono();
206
203
  },
207
204
  }),
208
205
  );
@@ -288,18 +285,31 @@ export function nodeServerRuntime({
288
285
  resolve: {
289
286
  exportConditions: ['node'],
290
287
  },
291
- requestRouter() {
288
+ hono() {
292
289
  return multiline`
293
- import requestRouter from ${JSON.stringify(
294
- MAGIC_MODULE_REQUEST_ROUTER,
295
- )};
290
+ import app from ${JSON.stringify(MAGIC_MODULE_HONO)};
296
291
 
297
- import {createHttpServer} from '@quilted/quilt/request-router/node';
292
+ import {serve} from '@quilted/quilt/hono/node';
298
293
 
299
294
  const port = ${port ?? 'Number.parseInt(process.env.PORT, 10)'};
300
295
  const host = ${host ? JSON.stringify(host) : 'process.env.HOST'};
301
-
302
- createHttpServer(requestRouter).listen(port, host);
296
+
297
+ const server = serve(app);
298
+
299
+ process.on('SIGINT', () => {
300
+ server.close();
301
+ process.exit(0);
302
+ });
303
+
304
+ process.on('SIGTERM', () => {
305
+ server.close((err) => {
306
+ if (err) {
307
+ console.error(err);
308
+ process.exit(1);
309
+ }
310
+ process.exit(0);
311
+ });
312
+ });
303
313
  `;
304
314
  },
305
315
  } satisfies ServerRuntime;