@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.
- package/CHANGELOG.md +16 -0
- package/build/esm/app.mjs +19 -47
- package/build/esm/constants.mjs +2 -2
- package/build/esm/features/async.mjs +34 -31
- package/build/esm/index.mjs +1 -1
- package/build/esm/server.mjs +29 -16
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/app.d.ts +13 -25
- package/build/typescript/app.d.ts.map +1 -1
- package/build/typescript/constants.d.ts +1 -1
- package/build/typescript/constants.d.ts.map +1 -1
- package/build/typescript/features/async.d.ts +0 -1
- package/build/typescript/features/async.d.ts.map +1 -1
- package/build/typescript/server.d.ts +15 -16
- package/build/typescript/server.d.ts.map +1 -1
- package/package.json +3 -3
- package/source/app.ts +28 -86
- package/source/constants.ts +1 -1
- package/source/features/async.ts +124 -37
- package/source/server.ts +41 -31
package/source/features/async.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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 `
|
|
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
|
-
|
|
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,
|
|
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
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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 '
|
|
43
|
+
* @default 'hono'
|
|
45
44
|
*/
|
|
46
|
-
format?: '
|
|
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 `
|
|
80
|
-
* format for their server. This file should import the
|
|
81
|
-
* this app from 'quilt:module/
|
|
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
|
-
|
|
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,
|
|
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 = '
|
|
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 === '
|
|
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 === '
|
|
190
|
+
if (format === 'hono') {
|
|
192
191
|
plugins.push(
|
|
193
192
|
createMagicModulePlugin({
|
|
194
|
-
name: '@quilted/magic-module/server-
|
|
195
|
-
module:
|
|
193
|
+
name: '@quilted/magic-module/server-hono',
|
|
194
|
+
module: MAGIC_MODULE_HONO,
|
|
196
195
|
alias: serverEntry,
|
|
197
196
|
}),
|
|
198
197
|
createMagicModulePlugin({
|
|
199
|
-
name: '@quilted/
|
|
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
|
-
|
|
288
|
+
hono() {
|
|
292
289
|
return multiline`
|
|
293
|
-
import
|
|
294
|
-
MAGIC_MODULE_REQUEST_ROUTER,
|
|
295
|
-
)};
|
|
290
|
+
import app from ${JSON.stringify(MAGIC_MODULE_HONO)};
|
|
296
291
|
|
|
297
|
-
import {
|
|
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
|
-
|
|
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;
|