@quilted/rollup 0.4.4 → 0.4.6
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 +114 -0
- package/build/esm/app.mjs +6 -4
- package/build/esm/features/assets.mjs +74 -28
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/app.d.ts +9 -3
- package/build/typescript/app.d.ts.map +1 -1
- package/build/typescript/features/assets.d.ts.map +1 -1
- package/package.json +2 -2
- package/source/app.ts +20 -6
- package/source/features/assets.ts +130 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,119 @@
|
|
|
1
1
|
# @quilted/rollup
|
|
2
2
|
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#890](https://github.com/lemonmade/quilt/pull/890) [`b0f2334`](https://github.com/lemonmade/quilt/commit/b0f23340945280c951998bf77b3be8b8df13338c) Thanks [@lemonmade](https://github.com/lemonmade)! - Redesign asset loading APIs for better performance and clearer structure.
|
|
8
|
+
|
|
9
|
+
## Breaking changes
|
|
10
|
+
|
|
11
|
+
### `@quilted/assets`: `BrowserAssetsEntry` redesigned
|
|
12
|
+
|
|
13
|
+
The `BrowserAssetsEntry` type has been completely restructured. The previous flat `scripts` and `styles` arrays have been replaced with structured `script` and `style` objects that separate the entry asset from its dependencies:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// Before
|
|
17
|
+
interface BrowserAssetsEntry {
|
|
18
|
+
scripts: Asset[];
|
|
19
|
+
styles: Asset[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// After
|
|
23
|
+
interface BrowserAssetsEntry {
|
|
24
|
+
script?: {
|
|
25
|
+
asset: Asset;
|
|
26
|
+
syncDependencies: readonly Asset[];
|
|
27
|
+
asyncDependencies: readonly Asset[];
|
|
28
|
+
};
|
|
29
|
+
style?: {
|
|
30
|
+
asset: Asset;
|
|
31
|
+
syncDependencies: readonly Asset[];
|
|
32
|
+
asyncDependencies: readonly Asset[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This separation enables the renderer to treat the entry script, its preloadable sync dependencies, and its async dependencies differently in the HTML output.
|
|
38
|
+
|
|
39
|
+
### `@quilted/assets`: `BrowserAssets.modules()` return type changed
|
|
40
|
+
|
|
41
|
+
`modules()` now returns `readonly BrowserAssetsEntry[]` (one entry per module ID) instead of a single merged `BrowserAssetsEntry`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Before
|
|
45
|
+
modules(modules: Iterable<...>, options?): BrowserAssetsEntry;
|
|
46
|
+
|
|
47
|
+
// After
|
|
48
|
+
modules(modules: Iterable<string>, options?): readonly BrowserAssetsEntry[];
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `@quilted/assets`: `BrowserAssetModuleSelector` removed
|
|
52
|
+
|
|
53
|
+
The `BrowserAssetModuleSelector` interface and the `modules` field on `BrowserAssetSelector` have been removed. If you want to get the asset details for modules in addition to the entrypoints, use `BrowserAssets.modules()` instead.
|
|
54
|
+
|
|
55
|
+
### `@quilted/assets`: `AssetBuildManifest` module entry format changed
|
|
56
|
+
|
|
57
|
+
`AssetBuildManifest.modules` values changed from `number[]` to the new `AssetBuildModuleEntry` tuple type. The tuple uses positional slots for each asset category and is serialized in JSON as an object with numeric string keys (omitting empty positions):
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
type AssetBuildModuleEntry = [
|
|
61
|
+
script?: number, // [0] entry JS chunk
|
|
62
|
+
style?: number, // [1] entry CSS file
|
|
63
|
+
scriptSync?: number[], // [2] sync JS dependency indices
|
|
64
|
+
styleSync?: number[], // [3] CSS from sync JS dependencies
|
|
65
|
+
scriptAsync?: number[], // [4] dynamic JS dependency indices
|
|
66
|
+
styleAsync?: number[], // [5] CSS from dynamic JS dependencies
|
|
67
|
+
];
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `@quilted/browser`: `BrowserResponseAssets.get()` return type changed
|
|
71
|
+
|
|
72
|
+
`get()` now returns `string[]` (module IDs) instead of `BrowserAssetModuleSelector[]`.
|
|
73
|
+
|
|
74
|
+
## New features
|
|
75
|
+
|
|
76
|
+
### `@quilted/preact-browser`: `BrowserApp` class
|
|
77
|
+
|
|
78
|
+
A new `BrowserApp` class simplifies constructing and running a browser app. It handles waiting for the `#app` DOM element via `MutationObserver`, which is necessary now that the entry script runs with the `async` attribute:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {BrowserApp} from '@quilted/quilt/browser';
|
|
82
|
+
import {BrowserAppContext} from '~/context/browser.ts';
|
|
83
|
+
import {App} from './App.tsx';
|
|
84
|
+
|
|
85
|
+
const context = new BrowserAppContext();
|
|
86
|
+
const app = new BrowserApp(<App context={context} />, {context});
|
|
87
|
+
await app.hydrate();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Render behavior changes
|
|
91
|
+
|
|
92
|
+
### Entry script rendered as `async` module
|
|
93
|
+
|
|
94
|
+
The browser entry script is now rendered as `<script type="module" async>` instead of a blocking `<script type="module">`. This means the script no longer blocks HTML parsing, and does not wait for DOMContentLoaded. This change is meant to allow streaming HTML responses to begin executing JavaScript earlier.
|
|
95
|
+
|
|
96
|
+
### Sync dependencies rendered as `modulepreload` links
|
|
97
|
+
|
|
98
|
+
Sync JS dependencies (previously rendered as additional `<script type="module">` tags) are now rendered as `<link rel="modulepreload">` hints. This tells the browser to fetch them eagerly without executing them, since the entry script will import them when it runs.
|
|
99
|
+
|
|
100
|
+
### Stylesheets rendered after all script references
|
|
101
|
+
|
|
102
|
+
Entry and async stylesheets are now emitted from the `<HTMLTemplate.Assets async />` placeholder, after all script and modulepreload tags. This ensures the stylesheet `<link>` elements appear after all JS references in the HTML.
|
|
103
|
+
|
|
104
|
+
### Asset deduplication across streamed chunks
|
|
105
|
+
|
|
106
|
+
Asset deduplication (preventing the same `src`/`href` from appearing more than once) now works across all streamed HTML chunks within a single response, not just within a single placeholder.
|
|
107
|
+
|
|
108
|
+
- Updated dependencies [[`b0f2334`](https://github.com/lemonmade/quilt/commit/b0f23340945280c951998bf77b3be8b8df13338c)]:
|
|
109
|
+
- @quilted/assets@0.1.10
|
|
110
|
+
|
|
111
|
+
## 0.4.5
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- [`3c4a5ac`](https://github.com/lemonmade/quilt/commit/3c4a5ac934e1648be6843f8cd880a53f7c29aeb4) Thanks [@lemonmade](https://github.com/lemonmade)! - Improve asset output for Cloudflare plugin
|
|
116
|
+
|
|
3
117
|
## 0.4.4
|
|
4
118
|
|
|
5
119
|
### Patch Changes
|
package/build/esm/app.mjs
CHANGED
|
@@ -88,6 +88,7 @@ async function quiltApp({
|
|
|
88
88
|
async function quiltAppBrowser(options = {}) {
|
|
89
89
|
const { root = process.cwd(), assets, runtime } = options;
|
|
90
90
|
const project = Project.load(root);
|
|
91
|
+
const baseURL = assets?.baseURL ?? "/assets/";
|
|
91
92
|
const [plugins, browserGroup] = await Promise.all([
|
|
92
93
|
quiltAppBrowserPlugins(options),
|
|
93
94
|
getBrowserGroupTargetDetails(assets?.targets, {
|
|
@@ -99,13 +100,12 @@ async function quiltAppBrowser(options = {}) {
|
|
|
99
100
|
targetsSupportModules(browserGroup.browsers),
|
|
100
101
|
rollupGenerateOptionsForBrowsers(browserGroup.browsers)
|
|
101
102
|
]);
|
|
103
|
+
const outputDirectory = assets?.directory ? assets.directory : runtime?.assets?.directory ? typeof runtime.assets.directory === "function" ? runtime.assets.directory({ baseURL }) : runtime.assets.directory : `build/assets`;
|
|
102
104
|
const rollupOptions = {
|
|
103
105
|
plugins,
|
|
104
106
|
output: {
|
|
105
107
|
format: isESM ? "esm" : "systemjs",
|
|
106
|
-
dir: project.resolve(
|
|
107
|
-
assets?.directory ?? runtime?.assets?.directory ?? `build/assets`
|
|
108
|
-
),
|
|
108
|
+
dir: project.resolve(outputDirectory),
|
|
109
109
|
entryFileNames: `[name]${targetFilenamePart}.[hash].js`,
|
|
110
110
|
assetFileNames: `[name]${targetFilenamePart}.[hash].[ext]`,
|
|
111
111
|
chunkFileNames: `[name]${targetFilenamePart}.[hash].js`,
|
|
@@ -118,7 +118,9 @@ async function quiltAppBrowser(options = {}) {
|
|
|
118
118
|
preserveEntrySignatures: false
|
|
119
119
|
};
|
|
120
120
|
if (runtime?.browser?.rollup) {
|
|
121
|
-
rollupOptions.plugins.push(
|
|
121
|
+
rollupOptions.plugins.push(
|
|
122
|
+
runtime.browser.rollup(rollupOptions, { assets: { baseURL, ...assets } })
|
|
123
|
+
);
|
|
122
124
|
}
|
|
123
125
|
return rollupOptions;
|
|
124
126
|
}
|
|
@@ -31,6 +31,12 @@ async function writeManifestForBundle(bundle, {
|
|
|
31
31
|
if (output.type !== "chunk") continue;
|
|
32
32
|
dependencyMap.set(output.fileName, output.imports);
|
|
33
33
|
}
|
|
34
|
+
const facadeToChunk = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const output of outputs) {
|
|
36
|
+
if (output.type === "chunk" && output.facadeModuleId) {
|
|
37
|
+
facadeToChunk.set(output.facadeModuleId, output.fileName);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
34
40
|
const assets = [];
|
|
35
41
|
const assetIdMap = /* @__PURE__ */ new Map();
|
|
36
42
|
function getAssetId(file2) {
|
|
@@ -54,9 +60,8 @@ async function writeManifestForBundle(bundle, {
|
|
|
54
60
|
for (const output of outputs) {
|
|
55
61
|
if (output.type === "asset") {
|
|
56
62
|
if (output.name && output.fileName.endsWith(".js")) {
|
|
57
|
-
manifest.modules[output.name] =
|
|
58
|
-
|
|
59
|
-
getAssetId
|
|
63
|
+
manifest.modules[output.name] = makeEntry({
|
|
64
|
+
script: getAssetId(output.fileName)
|
|
60
65
|
});
|
|
61
66
|
manifest.entries[`./${output.name}`] = output.name;
|
|
62
67
|
}
|
|
@@ -77,17 +82,23 @@ async function writeManifestForBundle(bundle, {
|
|
|
77
82
|
manifest.entries[entry] = moduleID;
|
|
78
83
|
}
|
|
79
84
|
const isCSS = moduleID.endsWith(".css");
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
if (isCSS) {
|
|
86
|
+
const cssFiles = [output.fileName, ...output.imports].filter(
|
|
87
|
+
(f) => f.endsWith(".css")
|
|
88
|
+
);
|
|
89
|
+
manifest.modules[moduleID] = makeEntry({
|
|
90
|
+
style: cssFiles[0] != null ? getAssetId(cssFiles[0]) : void 0,
|
|
91
|
+
styleSync: cssFiles.length > 1 ? cssFiles.slice(1).map(getAssetId) : void 0
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
const asyncFileNames = (output.dynamicImports ?? []).map((mid) => facadeToChunk.get(mid)).filter((f) => f != null);
|
|
95
|
+
manifest.modules[moduleID] = buildModuleEntry(
|
|
96
|
+
output.fileName,
|
|
97
|
+
output.imports,
|
|
98
|
+
asyncFileNames,
|
|
99
|
+
{ dependencyMap, getAssetId }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
manifest.assets = await Promise.all(assets);
|
|
93
104
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
@@ -141,26 +152,61 @@ async function normalizeInlineSource({
|
|
|
141
152
|
function defaultModuleID({ imported }) {
|
|
142
153
|
return imported.startsWith("/") ? path.relative(process.cwd(), imported) : imported.startsWith("\0") ? imported.replace("\0", "") : imported;
|
|
143
154
|
}
|
|
144
|
-
function
|
|
155
|
+
function makeEntry({
|
|
156
|
+
script,
|
|
157
|
+
style,
|
|
158
|
+
scriptSync,
|
|
159
|
+
styleSync,
|
|
160
|
+
scriptAsync,
|
|
161
|
+
styleAsync
|
|
162
|
+
}) {
|
|
163
|
+
const entry = {};
|
|
164
|
+
if (script != null) entry[0] = script;
|
|
165
|
+
if (style != null) entry[1] = style;
|
|
166
|
+
if (scriptSync?.length) entry[2] = scriptSync;
|
|
167
|
+
if (styleSync?.length) entry[3] = styleSync;
|
|
168
|
+
if (scriptAsync?.length) entry[4] = scriptAsync;
|
|
169
|
+
if (styleAsync?.length) entry[5] = styleAsync;
|
|
170
|
+
return entry;
|
|
171
|
+
}
|
|
172
|
+
function buildModuleEntry(entryFileName, syncImports, asyncFileNames, {
|
|
145
173
|
dependencyMap,
|
|
146
174
|
getAssetId
|
|
147
175
|
}) {
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
176
|
+
const syncJsFiles = /* @__PURE__ */ new Set();
|
|
177
|
+
const entryStyleFiles = [];
|
|
178
|
+
const syncStyleFiles = /* @__PURE__ */ new Set();
|
|
179
|
+
for (const file of syncImports) {
|
|
180
|
+
if (file.endsWith(".css")) {
|
|
181
|
+
entryStyleFiles.push(file);
|
|
182
|
+
} else {
|
|
183
|
+
visitJsDep(file, syncJsFiles, syncStyleFiles, dependencyMap);
|
|
155
184
|
}
|
|
156
|
-
};
|
|
157
|
-
for (const file of files) {
|
|
158
|
-
addFile(file);
|
|
159
185
|
}
|
|
160
|
-
|
|
161
|
-
|
|
186
|
+
const asyncStyleFiles = /* @__PURE__ */ new Set();
|
|
187
|
+
const visitedAsync = /* @__PURE__ */ new Set();
|
|
188
|
+
for (const asyncFileName of asyncFileNames) {
|
|
189
|
+
visitJsDep(asyncFileName, visitedAsync, asyncStyleFiles, dependencyMap);
|
|
190
|
+
}
|
|
191
|
+
return makeEntry({
|
|
192
|
+
script: getAssetId(entryFileName),
|
|
193
|
+
style: entryStyleFiles[0] != null ? getAssetId(entryStyleFiles[0]) : void 0,
|
|
194
|
+
scriptSync: syncJsFiles.size > 0 ? [...syncJsFiles].map(getAssetId) : void 0,
|
|
195
|
+
styleSync: syncStyleFiles.size > 0 ? [...syncStyleFiles].map(getAssetId) : void 0,
|
|
196
|
+
scriptAsync: asyncFileNames.length > 0 ? asyncFileNames.map(getAssetId) : void 0,
|
|
197
|
+
styleAsync: asyncStyleFiles.size > 0 ? [...asyncStyleFiles].map(getAssetId) : void 0
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function visitJsDep(file, visitedJs, styleFiles, dependencyMap) {
|
|
201
|
+
if (visitedJs.has(file)) return;
|
|
202
|
+
visitedJs.add(file);
|
|
203
|
+
for (const dep of dependencyMap.get(file) ?? []) {
|
|
204
|
+
if (dep.endsWith(".css")) {
|
|
205
|
+
styleFiles.add(dep);
|
|
206
|
+
} else {
|
|
207
|
+
visitJsDep(dep, visitedJs, styleFiles, dependencyMap);
|
|
208
|
+
}
|
|
162
209
|
}
|
|
163
|
-
return assets;
|
|
164
210
|
}
|
|
165
211
|
const QUERY_PATTERN = /\?.*$/s;
|
|
166
212
|
const HASH_PATTERN = /#.*$/s;
|