handoff-app 2.0.0-7 → 2.0.0-9
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/README.md +130 -14
- package/UPGRADE.md +20 -0
- package/dist/app-builder/build.js +10 -2
- package/dist/app-builder/client-config.js +3 -1
- package/dist/app-builder/watchers.js +17 -7
- package/dist/cache/build-cache.js +5 -1
- package/dist/catalog/normalize.js +3 -0
- package/dist/catalog/previews.js +5 -4
- package/dist/catalog/types.d.ts +8 -0
- package/dist/cli/auth/device.d.ts +6 -1
- package/dist/cli/auth/device.js +5 -2
- package/dist/cli/auth/store.d.ts +15 -6
- package/dist/cli/auth/store.js +72 -19
- package/dist/commands/login/index.d.ts +5 -0
- package/dist/commands/login/index.js +18 -6
- package/dist/commands/logout/index.d.ts +5 -0
- package/dist/commands/logout/index.js +54 -15
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/utils.d.ts +21 -7
- package/dist/commands/utils.js +11 -10
- package/dist/config/catalog-include.d.ts +2 -2
- package/dist/config/defaults.d.ts +3 -3
- package/dist/config/defaults.js +54 -48
- package/dist/config/env.d.ts +9 -0
- package/dist/config/env.js +43 -0
- package/dist/config/errors.d.ts +4 -0
- package/dist/config/errors.js +11 -0
- package/dist/config/from-env.d.ts +24 -0
- package/dist/config/from-env.js +7 -0
- package/dist/config/helpers.d.ts +2 -0
- package/dist/config/helpers.js +3 -1
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.js +6 -1
- package/dist/config/loader.d.ts +29 -11
- package/dist/config/loader.js +121 -46
- package/dist/config/resolve-env.d.ts +5 -0
- package/dist/config/resolve-env.js +102 -0
- package/dist/config/runtime.d.ts +3 -3
- package/dist/config/runtime.js +8 -10
- package/dist/config/validator.d.ts +2 -2
- package/dist/config/validator.js +2 -4
- package/dist/handlebars.d.ts +10 -2
- package/dist/index.d.ts +33 -4
- package/dist/index.js +60 -16
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.js +1 -1
- package/dist/registry/asset-storage/adapters/vercel-blob.js +1 -1
- package/dist/registry/asset-storage/resolve.d.ts +2 -2
- package/dist/registry/asset-storage/resolve.js +4 -1
- package/dist/registry/checkout/index.js +4 -4
- package/dist/registry/connection.d.ts +28 -24
- package/dist/registry/connection.js +53 -67
- package/dist/registry/db/driver.d.ts +4 -11
- package/dist/registry/db/driver.js +4 -16
- package/dist/registry/db/index.d.ts +1 -1
- package/dist/registry/db/index.js +1 -2
- package/dist/registry/publish/index.js +4 -4
- package/dist/store/filesystem.d.ts +2 -2
- package/dist/transformers/plugins/ssr-render.js +40 -15
- package/dist/transformers/preview/component/css.d.ts +1 -0
- package/dist/transformers/preview/component/css.js +85 -47
- package/dist/transformers/preview/pattern/html.js +3 -1
- package/dist/transformers/preview/types.d.ts +13 -0
- package/dist/transformers/utils/build.d.ts +19 -0
- package/dist/transformers/utils/build.js +83 -1
- package/dist/transformers/utils/module.js +2 -1
- package/dist/types/config.d.ts +54 -29
- package/docs/api.md +31 -0
- package/docs/cli.md +2 -2
- package/package.json +1 -1
- package/src/app/components/Component/PageSliceResolver.tsx +1 -0
- package/src/app/components/Component/Preview.tsx +380 -189
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
- package/src/app/pages/account/tokens.tsx +3 -1
- package/src/app-builder/build.ts +10 -2
- package/src/app-builder/client-config.ts +5 -3
- package/src/app-builder/watchers.ts +18 -8
- package/src/cache/build-cache.ts +5 -1
- package/src/catalog/README.md +3 -0
- package/src/catalog/normalize.ts +2 -0
- package/src/catalog/previews.ts +6 -3
- package/src/catalog/types.ts +8 -0
- package/src/cli/auth/device.ts +8 -3
- package/src/cli/auth/store.ts +75 -18
- package/src/commands/login/index.ts +20 -6
- package/src/commands/logout/index.ts +74 -20
- package/src/commands/types.ts +2 -0
- package/src/commands/utils.ts +14 -5
- package/src/config/README.md +61 -1
- package/src/config/catalog-include.ts +2 -2
- package/src/config/defaults.ts +24 -14
- package/src/config/env.ts +41 -0
- package/src/config/errors.ts +7 -0
- package/src/config/from-env.ts +26 -0
- package/src/config/helpers.ts +4 -2
- package/src/config/index.ts +4 -1
- package/src/config/loader.ts +158 -50
- package/src/config/resolve-env.ts +94 -0
- package/src/config/runtime.ts +12 -7
- package/src/config/validator.ts +4 -6
- package/src/handlebars.ts +8 -2
- package/src/index.d.ts +2 -2
- package/src/index.ts +85 -8
- package/src/pipeline/index.ts +1 -1
- package/src/registry/asset-storage/adapters/vercel-blob.ts +1 -1
- package/src/registry/asset-storage/resolve.ts +6 -3
- package/src/registry/checkout/index.ts +5 -5
- package/src/registry/connection.ts +72 -71
- package/src/registry/db/driver.ts +6 -17
- package/src/registry/db/index.ts +0 -1
- package/src/registry/publish/index.ts +5 -5
- package/src/store/filesystem.ts +2 -2
- package/src/transformers/plugins/ssr-render.ts +53 -16
- package/src/transformers/preview/component/css.ts +93 -50
- package/src/transformers/preview/pattern/html.ts +3 -1
- package/src/transformers/preview/types.ts +14 -0
- package/src/transformers/utils/build.ts +80 -0
- package/src/transformers/utils/module.ts +4 -2
- package/src/types/config.ts +51 -29
- package/templates/default/env.tpl +10 -0
- package/templates/default/gitignore.tpl +7 -0
- package/templates/default/handoff.config.js.tpl +21 -172
- package/templates/default/handoff.config.ts.tpl +21 -156
- package/templates/default/handoff.config.with-examples.js.tpl +20 -171
- package/templates/default/handoff.config.with-examples.ts.tpl +20 -155
|
@@ -121,12 +121,25 @@ function loadComponentSchemaAndModule(componentData, componentPath, handoff) {
|
|
|
121
121
|
* @param componentExport - Export holding the implementation; the default export when absent
|
|
122
122
|
* @returns Client-side hydration source code
|
|
123
123
|
*/
|
|
124
|
-
function generateClientHydrationSource(componentId, componentPath, componentExport) {
|
|
124
|
+
function generateClientHydrationSource(componentId, componentPath, componentExport, declarationPath) {
|
|
125
125
|
const importClause = componentExport && componentExport !== 'default' ? `{ ${componentExport} as Component }` : 'Component';
|
|
126
|
+
// A function cannot travel in the JSON props, so the client re-reads `render` from the module that
|
|
127
|
+
// defines it. Conditional, so an unaffected component does not ship its declaration's imports.
|
|
128
|
+
const declarationImport = declarationPath ? `import * as previewDeclaration from '${(0, vite_1.normalizePath)(declarationPath)}';` : '';
|
|
129
|
+
const resolveComponent = declarationPath
|
|
130
|
+
? `(mount) => {
|
|
131
|
+
const previewKey = mount.getAttribute('data-handoff-preview');
|
|
132
|
+
const render = previewKey ? previewDeclaration[previewKey]?.render : undefined;
|
|
133
|
+
return typeof render === 'function' ? render : Component;
|
|
134
|
+
}`
|
|
135
|
+
: '() => Component';
|
|
126
136
|
return `
|
|
127
137
|
import React from 'react';
|
|
128
138
|
import { hydrateRoot } from 'react-dom/client';
|
|
129
139
|
import ${importClause} from '${(0, vite_1.normalizePath)(componentPath)}';
|
|
140
|
+
${declarationImport}
|
|
141
|
+
|
|
142
|
+
const resolveComponent = ${resolveComponent};
|
|
130
143
|
|
|
131
144
|
const parseProps = (propsId) => {
|
|
132
145
|
const raw = propsId ? document.getElementById(propsId)?.textContent : '{}';
|
|
@@ -139,12 +152,14 @@ function generateClientHydrationSource(componentId, componentPath, componentExpo
|
|
|
139
152
|
|
|
140
153
|
const standaloneRoot = document.getElementById('${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}');
|
|
141
154
|
if (standaloneRoot && !standaloneRoot.hasAttribute('data-handoff-component')) {
|
|
142
|
-
|
|
155
|
+
const StandaloneComponent = resolveComponent(standaloneRoot);
|
|
156
|
+
hydrateRoot(standaloneRoot, <StandaloneComponent {...parseProps('${PLUGIN_CONSTANTS.PROPS_SCRIPT_ID}')} />);
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
const patternMounts = document.querySelectorAll('[data-handoff-component=' + JSON.stringify('${componentId}') + ']');
|
|
146
160
|
patternMounts.forEach((mount) => {
|
|
147
|
-
|
|
161
|
+
const MountComponent = resolveComponent(mount);
|
|
162
|
+
hydrateRoot(mount, <MountComponent {...parseProps(mount.getAttribute('data-handoff-props'))} />);
|
|
148
163
|
});
|
|
149
164
|
`;
|
|
150
165
|
}
|
|
@@ -156,11 +171,11 @@ function generateClientHydrationSource(componentId, componentPath, componentExpo
|
|
|
156
171
|
* artifact cannot be loaded.
|
|
157
172
|
* @param componentId - Component identifier
|
|
158
173
|
* @param previewTitle - Title for the preview
|
|
159
|
-
* @param renderedHtml - Server-rendered HTML
|
|
174
|
+
* @param renderedHtml - Server-rendered HTML, embedded verbatim so the client can hydrate it
|
|
160
175
|
* @param props - Component props as JSON
|
|
161
176
|
* @returns Complete HTML document
|
|
162
177
|
*/
|
|
163
|
-
function generateHtmlDocument(componentId, previewTitle, renderedHtml, props, sharedArtifacts, componentArtifacts) {
|
|
178
|
+
function generateHtmlDocument(componentId, previewKey, previewTitle, renderedHtml, props, sharedArtifacts, componentArtifacts) {
|
|
164
179
|
var _a;
|
|
165
180
|
const basePath = (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : '';
|
|
166
181
|
const clientArtifactUrl = (0, url_1.buildArtifactUrl)(clientArtifactPath(componentId), basePath);
|
|
@@ -193,7 +208,7 @@ function generateHtmlDocument(componentId, previewTitle, renderedHtml, props, sh
|
|
|
193
208
|
<title>${previewTitle}</title>
|
|
194
209
|
</head>
|
|
195
210
|
<body>
|
|
196
|
-
<div id="${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}">${renderedHtml}</div>
|
|
211
|
+
<div id="${PLUGIN_CONSTANTS.ROOT_ELEMENT_ID}" data-handoff-preview="${previewKey}">${renderedHtml}</div>
|
|
197
212
|
</body>
|
|
198
213
|
</html>`;
|
|
199
214
|
}
|
|
@@ -224,7 +239,7 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
|
|
|
224
239
|
},
|
|
225
240
|
generateBundle(_, bundle) {
|
|
226
241
|
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
-
var _a, _b;
|
|
242
|
+
var _a, _b, _c, _d, _e;
|
|
228
243
|
// Remove all JS chunks to prevent conflicts
|
|
229
244
|
for (const [fileName, chunkInfo] of Object.entries(bundle)) {
|
|
230
245
|
if (chunkInfo.type === 'chunk' && fileName.includes(PLUGIN_CONSTANTS.SCRIPT_ID)) {
|
|
@@ -275,15 +290,19 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
|
|
|
275
290
|
// `component/<id>.client.js` artifact. The hydration source only imports the component and
|
|
276
291
|
// reads props from the in-document `__APP_PROPS__` element, so it is identical across every
|
|
277
292
|
// preview of this component.
|
|
278
|
-
const
|
|
293
|
+
const declarationPath = Object.values((_a = componentData.previews) !== null && _a !== void 0 ? _a : {}).some((preview) => typeof preview.render === 'function')
|
|
294
|
+
? (_b = componentData.entries) === null || _b === void 0 ? void 0 : _b.declaration
|
|
295
|
+
: undefined;
|
|
296
|
+
const clientHydrationSource = generateClientHydrationSource(componentId, componentPath, componentData.componentExport, declarationPath);
|
|
279
297
|
const clientBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_CLIENT_BUILD_CONFIG), { logLevel: 'silent', stdin: {
|
|
280
298
|
contents: clientHydrationSource,
|
|
281
299
|
resolveDir: process.cwd(),
|
|
282
300
|
loader: 'tsx',
|
|
283
301
|
}, plugins: [(0, build_1.createReactResolvePlugin)(handoff.workingPath, handoff.modulePath)] });
|
|
284
|
-
const
|
|
302
|
+
const hookedClientBuildConfig = ((_d = (_c = handoff.config) === null || _c === void 0 ? void 0 : _c.hooks) === null || _d === void 0 ? void 0 : _d.clientBuildConfig)
|
|
285
303
|
? handoff.config.hooks.clientBuildConfig(clientBuildConfig)
|
|
286
304
|
: clientBuildConfig;
|
|
305
|
+
const finalClientBuildConfig = (0, build_1.withStyleLoaders)(hookedClientBuildConfig);
|
|
287
306
|
let clientBundleJs;
|
|
288
307
|
try {
|
|
289
308
|
const bundledClient = yield esbuild_1.default.build(finalClientBuildConfig);
|
|
@@ -315,11 +334,17 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
|
|
|
315
334
|
const componentArtifacts = (0, shared_artifacts_1.resolveComponentArtifactPresence)(handoff, componentId);
|
|
316
335
|
// Generate previews for each variation
|
|
317
336
|
for (const previewKey in componentData.previews) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
337
|
+
const preview = componentData.previews[previewKey];
|
|
338
|
+
const previewProps = preview.values;
|
|
339
|
+
// A synthetic pattern preview is not a named export, so the client looks up its
|
|
340
|
+
// `sourcePreview` instead. A preview without one has no `render` either.
|
|
341
|
+
const hydrationPreviewKey = (_e = preview.sourcePreview) !== null && _e !== void 0 ? _e : previewKey;
|
|
342
|
+
// Rendered as a component rather than called, so a preview can use hooks and own state.
|
|
343
|
+
const PreviewComponent = (typeof preview.render === 'function' ? preview.render : ReactComponent);
|
|
344
|
+
// The markup must reach the document exactly as rendered. Formatting it adds text nodes React
|
|
345
|
+
// does not expect, so it discards the server markup and re-renders the preview on the client.
|
|
346
|
+
const serverRenderedHtml = server_1.default.renderToString(react_1.default.createElement(PreviewComponent, previewProps));
|
|
347
|
+
finalHtml = generateHtmlDocument(componentId, hydrationPreviewKey, preview.title, serverRenderedHtml, previewProps, sharedArtifacts, componentArtifacts);
|
|
323
348
|
// Emit preview files
|
|
324
349
|
this.emitFile({
|
|
325
350
|
type: 'asset',
|
|
@@ -340,7 +365,7 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
|
|
|
340
365
|
templateFileName: path_1.default.basename(componentPath),
|
|
341
366
|
});
|
|
342
367
|
}
|
|
343
|
-
//
|
|
368
|
+
// The previews are already emitted, so this formats only the docs code view.
|
|
344
369
|
finalHtml = yield (0, html_1.formatHtml)(finalHtml);
|
|
345
370
|
componentData.format = 'react';
|
|
346
371
|
componentData.preview = '';
|
|
@@ -42,6 +42,7 @@ declare const buildComponentCss: (data: TransformComponentTokensResult, handoff:
|
|
|
42
42
|
story?: string;
|
|
43
43
|
schema?: string;
|
|
44
44
|
templates?: string;
|
|
45
|
+
declaration?: string;
|
|
45
46
|
};
|
|
46
47
|
renderer?: import("../../../index").RendererKind;
|
|
47
48
|
sourceFormat?: import("../../../index").SourceFormat;
|
|
@@ -18,6 +18,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
18
18
|
const vite_1 = require("vite");
|
|
19
19
|
const duration_1 = require("../../../utils/duration");
|
|
20
20
|
const logger_1 = require("../../../utils/logger");
|
|
21
|
+
const build_1 = require("../../utils/build");
|
|
21
22
|
const vite_config_1 = __importDefault(require("../../vite-config"));
|
|
22
23
|
const component_1 = require("../component");
|
|
23
24
|
const { pathToFileURL } = require('url');
|
|
@@ -88,14 +89,49 @@ const buildCssBundle = (_a) => __awaiter(void 0, [_a], void 0, function* ({ entr
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
});
|
|
92
|
+
const STYLE_SOURCE_EXTENSIONS = new Set(['.scss', '.sass', '.css']);
|
|
93
|
+
/**
|
|
94
|
+
* The stylesheets of one item, in cascade order and deduplicated by resolved path.
|
|
95
|
+
*
|
|
96
|
+
* Discovered imports come first and the declared entry last, so a declared entry can override what
|
|
97
|
+
* the component imports. A project that both declares and imports one file must build it once.
|
|
98
|
+
*/
|
|
99
|
+
const resolveStyleSources = (data, handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
+
var _a, _b;
|
|
101
|
+
const template = (_a = data.entries) === null || _a === void 0 ? void 0 : _a.template;
|
|
102
|
+
const discovered = template ? yield (0, build_1.collectStyleImports)(path_1.default.resolve(template), handoff) : [];
|
|
103
|
+
const declared = ((_b = data.entries) === null || _b === void 0 ? void 0 : _b.scss) ? [path_1.default.resolve(data.entries.scss)] : [];
|
|
104
|
+
const ordered = [];
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
for (const source of [...discovered, ...declared]) {
|
|
107
|
+
if (!seen.has(source) && STYLE_SOURCE_EXTENSIONS.has(path_1.default.extname(source)) && fs_extra_1.default.existsSync(source)) {
|
|
108
|
+
seen.add(source);
|
|
109
|
+
ordered.push(source);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return ordered;
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* A single Sass entry that `@use`s every source in order.
|
|
116
|
+
*
|
|
117
|
+
* `@use` emits a repeated file once and inlines a plain `.css` source, which `@import` would leave
|
|
118
|
+
* as an unresolvable URL. Namespaces are explicit because two sources sharing a basename would
|
|
119
|
+
* claim the same default namespace and fail the compile.
|
|
120
|
+
*/
|
|
121
|
+
const writeCombinedStyleEntry = (sources, outputPath, id) => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
|
+
const entryPath = path_1.default.resolve(outputPath, `.${id}.entry.scss`);
|
|
123
|
+
const rules = sources.map((source, index) => `@use '${source.split(path_1.default.sep).join('/')}' as ns${index};`);
|
|
124
|
+
yield fs_extra_1.default.outputFile(entryPath, `${rules.join('\n')}\n`);
|
|
125
|
+
return entryPath;
|
|
126
|
+
});
|
|
91
127
|
const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
128
|
var _a, _b, _c;
|
|
93
129
|
const id = data.id;
|
|
94
130
|
logger_1.Logger.debug(`buildComponentCss`, id);
|
|
95
131
|
const outputPath = (0, component_1.getComponentOutputPath)(handoff);
|
|
96
132
|
const builtCssPath = path_1.default.resolve(outputPath, `${id}.css`);
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
133
|
+
const sources = yield resolveStyleSources(data, handoff);
|
|
134
|
+
if (sources.length === 0) {
|
|
99
135
|
// Keep generated output aligned with the current component declaration.
|
|
100
136
|
yield fs_extra_1.default.remove(builtCssPath);
|
|
101
137
|
delete data['css'];
|
|
@@ -103,59 +139,61 @@ const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, f
|
|
|
103
139
|
delete data['sharedStyles'];
|
|
104
140
|
return data;
|
|
105
141
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
142
|
+
// A single source compiles directly, so its relative asset URLs and documented Sass stay as authored.
|
|
143
|
+
const combinedEntry = sources.length > 1 ? yield writeCombinedStyleEntry(sources, outputPath, id) : undefined;
|
|
144
|
+
const entry = combinedEntry !== null && combinedEntry !== void 0 ? combinedEntry : sources[0];
|
|
110
145
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
yield fs_extra_1.default.writeFile(path_1.default.resolve(outputPath, exports.SHARED_COMPONENT_CSS_FILE), data['sharedStyles']);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
delete data['css'];
|
|
151
|
-
delete data['sharedStyles'];
|
|
146
|
+
// Remove the previous artifact before rebuilding so "no emitted CSS"
|
|
147
|
+
// doesn't silently preserve stale output from an earlier build.
|
|
148
|
+
yield fs_extra_1.default.remove(builtCssPath);
|
|
149
|
+
// Read the original source
|
|
150
|
+
const primarySource = ((_a = data.entries) === null || _a === void 0 ? void 0 : _a.scss) ? path_1.default.resolve(data.entries.scss) : sources[0];
|
|
151
|
+
if (path_1.default.extname(primarySource) === '.scss') {
|
|
152
|
+
data['sass'] = yield fs_extra_1.default.readFile(primarySource, 'utf8');
|
|
153
|
+
}
|
|
154
|
+
// Setup SASS load paths
|
|
155
|
+
const loadPaths = [
|
|
156
|
+
path_1.default.resolve(handoff.workingPath),
|
|
157
|
+
path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.getProjectId()),
|
|
158
|
+
path_1.default.resolve(handoff.workingPath, 'node_modules'),
|
|
159
|
+
];
|
|
160
|
+
if ((_c = (_b = handoff.runtimeConfig) === null || _b === void 0 ? void 0 : _b.entries) === null || _c === void 0 ? void 0 : _c.scss) {
|
|
161
|
+
loadPaths.unshift(path_1.default.dirname(handoff.runtimeConfig.entries.scss));
|
|
162
|
+
}
|
|
163
|
+
yield buildCssBundle({
|
|
164
|
+
entry,
|
|
165
|
+
outputPath,
|
|
166
|
+
outputFilename: `${id}.css`,
|
|
167
|
+
loadPaths,
|
|
168
|
+
handoff,
|
|
169
|
+
});
|
|
170
|
+
// Read the built CSS
|
|
171
|
+
if (fs_extra_1.default.existsSync(builtCssPath)) {
|
|
172
|
+
const builtCss = yield fs_extra_1.default.readFile(builtCssPath, 'utf8');
|
|
173
|
+
data['css'] = builtCss;
|
|
174
|
+
// Handle shared styles — if the component SCSS uses the split marker,
|
|
175
|
+
// extract shared styles from the portion above it
|
|
176
|
+
const splitCSS = builtCss.split('/* COMPONENT STYLES*/');
|
|
177
|
+
if (splitCSS && splitCSS.length > 1) {
|
|
178
|
+
data['css'] = splitCSS[1];
|
|
179
|
+
data['sharedStyles'] = splitCSS[0];
|
|
180
|
+
yield fs_extra_1.default.writeFile(path_1.default.resolve(outputPath, exports.SHARED_COMPONENT_CSS_FILE), data['sharedStyles']);
|
|
152
181
|
}
|
|
153
182
|
}
|
|
183
|
+
else {
|
|
184
|
+
delete data['css'];
|
|
185
|
+
delete data['sharedStyles'];
|
|
186
|
+
}
|
|
154
187
|
}
|
|
155
188
|
catch (e) {
|
|
156
189
|
logger_1.Logger.error(`Failed to build CSS for "${id}"`);
|
|
157
190
|
throw e;
|
|
158
191
|
}
|
|
192
|
+
finally {
|
|
193
|
+
if (combinedEntry) {
|
|
194
|
+
yield fs_extra_1.default.remove(combinedEntry);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
159
197
|
return data;
|
|
160
198
|
});
|
|
161
199
|
/**
|
|
@@ -48,7 +48,9 @@ const composePatternHtml = (_patternId, patternTitle, fragments, basePath, share
|
|
|
48
48
|
cssHrefs.add((0, url_1.buildArtifactUrl)(`component/${componentId}.css`, basePath));
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
// React SSR separates adjacent text children with `<!-- -->`. The parser drops comments by
|
|
52
|
+
// default, which would make every composed React fragment fail to hydrate.
|
|
53
|
+
const doc = (0, node_html_parser_1.parse)(html, { comment: true });
|
|
52
54
|
const suffix = `_p${i}`;
|
|
53
55
|
const namespacedRootId = `root${suffix}`;
|
|
54
56
|
const namespacedPropsId = `__APP_PROPS__${suffix}`;
|
|
@@ -14,6 +14,8 @@ export type PageSliceType = 'BEST_PRACTICES' | 'COMPONENT_DISPLAY' | 'VALIDATION
|
|
|
14
14
|
export interface BasePageSlice {
|
|
15
15
|
type: PageSliceType;
|
|
16
16
|
}
|
|
17
|
+
/** A fluid preview renders at the width the reader's stage has, so it is never scaled down. */
|
|
18
|
+
export type PreviewWidth = 'fluid' | number;
|
|
17
19
|
export interface BestPracticesPageSlice extends BasePageSlice {
|
|
18
20
|
type: 'BEST_PRACTICES';
|
|
19
21
|
}
|
|
@@ -22,6 +24,8 @@ export interface ComponentDisplayPageSlice extends BasePageSlice {
|
|
|
22
24
|
showPreview?: boolean;
|
|
23
25
|
showCodeHighlight?: boolean;
|
|
24
26
|
defaultHeight?: string;
|
|
27
|
+
/** Viewport width for the preview, in pixels. Defaults to 'fluid'. */
|
|
28
|
+
defaultWidth?: PreviewWidth;
|
|
25
29
|
filterBy?: Filter;
|
|
26
30
|
}
|
|
27
31
|
export interface ValidationResultsPageSlice extends BasePageSlice {
|
|
@@ -100,6 +104,8 @@ export type ComponentObject = {
|
|
|
100
104
|
story?: string;
|
|
101
105
|
/** Optional path to schema file (if available) */
|
|
102
106
|
schema?: string;
|
|
107
|
+
/** Declaration file path; a preview `render` is read from it at build and hydration time. */
|
|
108
|
+
declaration?: string;
|
|
103
109
|
/** Optional path to templates directory */
|
|
104
110
|
templates?: string;
|
|
105
111
|
};
|
|
@@ -187,6 +193,8 @@ export type TransformComponentTokensResult = {
|
|
|
187
193
|
story?: string;
|
|
188
194
|
schema?: string;
|
|
189
195
|
templates?: string;
|
|
196
|
+
/** See `ComponentObject.entries.declaration`. */
|
|
197
|
+
declaration?: string;
|
|
190
198
|
};
|
|
191
199
|
renderer?: RendererKind;
|
|
192
200
|
/** See `ComponentObject.sourceFormat`. Distinct from `format`, the preview's code language. */
|
|
@@ -208,6 +216,11 @@ export type OptionalPreviewRender = {
|
|
|
208
216
|
};
|
|
209
217
|
url: string;
|
|
210
218
|
usage?: string;
|
|
219
|
+
/**
|
|
220
|
+
* Build-time only: the server renders it and the client re-reads it from the declaration module.
|
|
221
|
+
* `JSON.stringify` drops it from published artifacts.
|
|
222
|
+
*/
|
|
223
|
+
render?: (args: Record<string, any>) => unknown;
|
|
211
224
|
/** Optional source preview/story key used to derive this preview */
|
|
212
225
|
sourcePreview?: string;
|
|
213
226
|
};
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import esbuild from 'esbuild';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps co-located stylesheet imports out of the JavaScript graph; the CSS pipeline compiles them.
|
|
4
|
+
*
|
|
5
|
+
* esbuild has no `.scss` loader, and a `.css` import fails too because these builds run with
|
|
6
|
+
* `write: false` and no output path. `empty` rather than a plugin: `esbuild.buildSync` rejects
|
|
7
|
+
* plugins, and the declaration and config loaders are synchronous.
|
|
8
|
+
*
|
|
9
|
+
* CSS Modules are not covered — the empty module leaves the class map undefined.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STYLE_IMPORT_LOADERS: Record<string, esbuild.Loader>;
|
|
2
12
|
/**
|
|
3
13
|
* Default esbuild configuration for SSR builds
|
|
4
14
|
*/
|
|
@@ -7,6 +17,15 @@ export declare const DEFAULT_SSR_BUILD_CONFIG: esbuild.BuildOptions;
|
|
|
7
17
|
* Default esbuild configuration for client builds
|
|
8
18
|
*/
|
|
9
19
|
export declare const DEFAULT_CLIENT_BUILD_CONFIG: esbuild.BuildOptions;
|
|
20
|
+
/** Apply after a consumer hook: a hook that replaces `loader` would otherwise drop these. */
|
|
21
|
+
export declare function withStyleLoaders<T extends esbuild.BuildOptions>(config: T): T;
|
|
22
|
+
/**
|
|
23
|
+
* Stylesheet paths imported anywhere in a component's module graph, in cascade order.
|
|
24
|
+
*
|
|
25
|
+
* An unbuildable graph yields an empty list rather than throwing, leaving the CSS step with the
|
|
26
|
+
* declared entry.
|
|
27
|
+
*/
|
|
28
|
+
export declare function collectStyleImports(entryPath: string, handoff: any): Promise<string[]>;
|
|
10
29
|
/**
|
|
11
30
|
* Resolves a module from multiple search directories
|
|
12
31
|
* @param id - Module ID to resolve
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
5
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEFAULT_CLIENT_BUILD_CONFIG = exports.DEFAULT_SSR_BUILD_CONFIG = void 0;
|
|
15
|
+
exports.DEFAULT_CLIENT_BUILD_CONFIG = exports.DEFAULT_SSR_BUILD_CONFIG = exports.STYLE_IMPORT_LOADERS = void 0;
|
|
16
|
+
exports.withStyleLoaders = withStyleLoaders;
|
|
17
|
+
exports.collectStyleImports = collectStyleImports;
|
|
7
18
|
exports.resolveModule = resolveModule;
|
|
8
19
|
exports.createReactResolvePlugin = createReactResolvePlugin;
|
|
20
|
+
const esbuild_1 = __importDefault(require("esbuild"));
|
|
9
21
|
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const STYLE_EXTENSIONS = ['.css', '.scss', '.sass'];
|
|
23
|
+
/**
|
|
24
|
+
* Keeps co-located stylesheet imports out of the JavaScript graph; the CSS pipeline compiles them.
|
|
25
|
+
*
|
|
26
|
+
* esbuild has no `.scss` loader, and a `.css` import fails too because these builds run with
|
|
27
|
+
* `write: false` and no output path. `empty` rather than a plugin: `esbuild.buildSync` rejects
|
|
28
|
+
* plugins, and the declaration and config loaders are synchronous.
|
|
29
|
+
*
|
|
30
|
+
* CSS Modules are not covered — the empty module leaves the class map undefined.
|
|
31
|
+
*/
|
|
32
|
+
exports.STYLE_IMPORT_LOADERS = Object.fromEntries(STYLE_EXTENSIONS.map((extension) => [extension, 'empty']));
|
|
10
33
|
/**
|
|
11
34
|
* Default esbuild configuration for SSR builds
|
|
12
35
|
*/
|
|
@@ -17,6 +40,7 @@ exports.DEFAULT_SSR_BUILD_CONFIG = {
|
|
|
17
40
|
platform: 'node',
|
|
18
41
|
jsx: 'automatic',
|
|
19
42
|
external: ['react', 'react-dom', '@opentelemetry/api'],
|
|
43
|
+
loader: exports.STYLE_IMPORT_LOADERS,
|
|
20
44
|
};
|
|
21
45
|
/**
|
|
22
46
|
* Default esbuild configuration for client builds
|
|
@@ -29,7 +53,65 @@ exports.DEFAULT_CLIENT_BUILD_CONFIG = {
|
|
|
29
53
|
jsx: 'automatic',
|
|
30
54
|
sourcemap: false,
|
|
31
55
|
minify: false,
|
|
56
|
+
loader: exports.STYLE_IMPORT_LOADERS,
|
|
32
57
|
};
|
|
58
|
+
/** Apply after a consumer hook: a hook that replaces `loader` would otherwise drop these. */
|
|
59
|
+
function withStyleLoaders(config) {
|
|
60
|
+
return Object.assign(Object.assign({}, config), { loader: Object.assign(Object.assign({}, config.loader), exports.STYLE_IMPORT_LOADERS) });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Stylesheets of the module graph in cascade order: every import of a module before the module
|
|
64
|
+
* itself, siblings in source order.
|
|
65
|
+
*
|
|
66
|
+
* This is the order a bundler emits CSS in, and the cascade depends on it. A component that
|
|
67
|
+
* overrides a rule of a component it imports ties that rule on specificity, so the later of the
|
|
68
|
+
* two wins. esbuild's `onLoad` callbacks do not give this order: they fire roughly breadth-first,
|
|
69
|
+
* which puts a component's own stylesheet ahead of the ones it imports and reverses every such
|
|
70
|
+
* override.
|
|
71
|
+
*/
|
|
72
|
+
function orderStyleImports(metafile, entryPath) {
|
|
73
|
+
const entryKey = Object.keys(metafile.inputs).find((key) => path_1.default.resolve(key) === entryPath);
|
|
74
|
+
if (!entryKey)
|
|
75
|
+
return [];
|
|
76
|
+
const ordered = [];
|
|
77
|
+
const visited = new Set();
|
|
78
|
+
const visit = (key) => {
|
|
79
|
+
var _a, _b;
|
|
80
|
+
if (visited.has(key))
|
|
81
|
+
return;
|
|
82
|
+
visited.add(key);
|
|
83
|
+
for (const imported of (_b = (_a = metafile.inputs[key]) === null || _a === void 0 ? void 0 : _a.imports) !== null && _b !== void 0 ? _b : []) {
|
|
84
|
+
if (!imported.external && metafile.inputs[imported.path])
|
|
85
|
+
visit(imported.path);
|
|
86
|
+
}
|
|
87
|
+
if (STYLE_EXTENSIONS.includes(path_1.default.extname(key)))
|
|
88
|
+
ordered.push(path_1.default.resolve(key));
|
|
89
|
+
};
|
|
90
|
+
visit(entryKey);
|
|
91
|
+
return ordered;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Stylesheet paths imported anywhere in a component's module graph, in cascade order.
|
|
95
|
+
*
|
|
96
|
+
* An unbuildable graph yields an empty list rather than throwing, leaving the CSS step with the
|
|
97
|
+
* declared entry.
|
|
98
|
+
*/
|
|
99
|
+
function collectStyleImports(entryPath, handoff) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
var _a, _b;
|
|
102
|
+
const baseConfig = Object.assign(Object.assign({}, exports.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath], logLevel: 'silent' });
|
|
103
|
+
const hookedConfig = ((_b = (_a = handoff === null || handoff === void 0 ? void 0 : handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig) ? handoff.config.hooks.ssrBuildConfig(baseConfig) : baseConfig;
|
|
104
|
+
try {
|
|
105
|
+
// The `empty` loader keeps stylesheets out of the JavaScript, and the metafile still records
|
|
106
|
+
// them as inputs, so the graph carries the order.
|
|
107
|
+
const result = yield esbuild_1.default.build(Object.assign(Object.assign({}, withStyleLoaders(hookedConfig)), { metafile: true }));
|
|
108
|
+
return result.metafile ? orderStyleImports(result.metafile, entryPath) : [];
|
|
109
|
+
}
|
|
110
|
+
catch (_c) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
33
115
|
/**
|
|
34
116
|
* Resolves a module from multiple search directories
|
|
35
117
|
* @param id - Module ID to resolve
|
|
@@ -19,9 +19,10 @@ const build_1 = require("./build");
|
|
|
19
19
|
function createModuleBuildConfig(entryPath, handoff) {
|
|
20
20
|
var _a, _b;
|
|
21
21
|
const defaultBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath] });
|
|
22
|
-
|
|
22
|
+
const hookedBuildConfig = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig)
|
|
23
23
|
? handoff.config.hooks.ssrBuildConfig(defaultBuildConfig)
|
|
24
24
|
: defaultBuildConfig;
|
|
25
|
+
return (0, build_1.withStyleLoaders)(hookedBuildConfig);
|
|
25
26
|
}
|
|
26
27
|
function evaluateBuiltModule(code) {
|
|
27
28
|
const mod = { exports: {} };
|