@rocket/js 0.1.2 → 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 (117) hide show
  1. package/README.md +8 -2
  2. package/dist-types/exports/layouts/atlasBlog.d.ts +2 -0
  3. package/dist-types/exports/layouts/atlasBlog.d.ts.map +1 -0
  4. package/dist-types/exports/ssr.d.ts +1 -1
  5. package/dist-types/exports/types/rocket.d.ts +60 -2
  6. package/dist-types/exports/types/rocket.d.ts.map +1 -1
  7. package/dist-types/exports/types.d.ts +1 -1
  8. package/dist-types/exports/types.d.ts.map +1 -1
  9. package/dist-types/src/PageData.d.ts +3 -1
  10. package/dist-types/src/PageData.d.ts.map +1 -1
  11. package/dist-types/src/RocketJsDemo.d.ts.map +1 -1
  12. package/dist-types/src/SocialPreviewPlayground.d.ts +1 -0
  13. package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -1
  14. package/dist-types/src/asyncMessage.d.ts +5 -0
  15. package/dist-types/src/asyncMessage.d.ts.map +1 -1
  16. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -1
  17. package/dist-types/src/cli/RocketCli.d.ts.map +1 -1
  18. package/dist-types/src/cli/RocketStart.d.ts +6 -1
  19. package/dist-types/src/cli/RocketStart.d.ts.map +1 -1
  20. package/dist-types/src/component-hydration.d.ts.map +1 -1
  21. package/dist-types/src/components/RocketDrawer.d.ts +8 -0
  22. package/dist-types/src/components/RocketDrawer.d.ts.map +1 -1
  23. package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -1
  24. package/dist-types/src/config.d.ts.map +1 -1
  25. package/dist-types/src/feeds.d.ts +38 -0
  26. package/dist-types/src/feeds.d.ts.map +1 -0
  27. package/dist-types/src/hydration/hydrationLoader.d.ts +13 -4
  28. package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -1
  29. package/dist-types/src/icons.d.ts.map +1 -1
  30. package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts +42 -0
  31. package/dist-types/src/layouts/atlas/atlasBlogLayout.d.ts.map +1 -0
  32. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -1
  33. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -1
  34. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -1
  35. package/dist-types/src/layouts/layout-helper.d.ts +3 -3
  36. package/dist-types/src/layouts/layout-helper.d.ts.map +1 -1
  37. package/dist-types/src/markdownHook.d.ts.map +1 -1
  38. package/dist-types/src/menu.d.ts +8 -3
  39. package/dist-types/src/menu.d.ts.map +1 -1
  40. package/dist-types/src/menus/MainMenu.d.ts.map +1 -1
  41. package/dist-types/src/{development-page-module-loader.d.ts → page-module-loader.d.ts} +7 -2
  42. package/dist-types/src/page-module-loader.d.ts.map +1 -0
  43. package/dist-types/src/page-runtime.d.ts +11 -0
  44. package/dist-types/src/page-runtime.d.ts.map +1 -1
  45. package/dist-types/src/pages.d.ts.map +1 -1
  46. package/dist-types/src/prefer-project-packages-plugin.d.ts +9 -0
  47. package/dist-types/src/prefer-project-packages-plugin.d.ts.map +1 -0
  48. package/dist-types/src/project-package-resolution.d.ts +8 -0
  49. package/dist-types/src/project-package-resolution.d.ts.map +1 -0
  50. package/dist-types/src/resolve.d.ts.map +1 -1
  51. package/dist-types/src/route-pattern.d.ts +10 -0
  52. package/dist-types/src/route-pattern.d.ts.map +1 -0
  53. package/dist-types/src/siteDiscoverability.d.ts +4 -0
  54. package/dist-types/src/siteDiscoverability.d.ts.map +1 -1
  55. package/dist-types/src/socialPreviewImages.d.ts +2 -1
  56. package/dist-types/src/socialPreviewImages.d.ts.map +1 -1
  57. package/dist-types/src/ssr.d.ts +3 -0
  58. package/dist-types/src/ssr.d.ts.map +1 -1
  59. package/dist-types/src/static-params.d.ts +33 -0
  60. package/dist-types/src/static-params.d.ts.map +1 -0
  61. package/dist-types/src/transform.d.ts +4 -0
  62. package/dist-types/src/transform.d.ts.map +1 -1
  63. package/dist-types/src/wds-plugin.d.ts.map +1 -1
  64. package/exports/layouts/atlasBlog.js +11 -0
  65. package/exports/ssr.js +1 -1
  66. package/exports/types/rocket.test.ts +26 -0
  67. package/exports/types/rocket.ts +70 -2
  68. package/exports/types.ts +7 -0
  69. package/package.json +16 -31
  70. package/src/PageData.js +12 -2
  71. package/src/RocketJsDemo.js +1 -0
  72. package/src/SocialPreviewPlayground.js +26 -20
  73. package/src/asyncMessage.js +8 -2
  74. package/src/cli/RocketBuild.js +110 -13
  75. package/src/cli/RocketCli.js +11 -1
  76. package/src/cli/RocketStart.js +60 -16
  77. package/src/cli/cli.js +10 -1
  78. package/src/component-hydration.js +23 -3
  79. package/src/components/RocketDrawer.js +44 -10
  80. package/src/components/RocketSocialLink.js +26 -2
  81. package/src/config.js +33 -4
  82. package/src/feeds.js +212 -0
  83. package/src/hydration/hydrationLoader.js +80 -48
  84. package/src/icons.js +10 -1
  85. package/src/layouts/atlas/atlasBlog.css +143 -0
  86. package/src/layouts/atlas/atlasBlogLayout.js +234 -0
  87. package/src/layouts/atlas/atlasDocLayout.js +3 -2
  88. package/src/layouts/atlas/atlasHeroLayout.js +4 -6
  89. package/src/layouts/atlas/atlasNotFoundLayout.js +9 -21
  90. package/src/layouts/layout-helper.js +5 -5
  91. package/src/layouts/layout.js +2 -2
  92. package/src/loaded-page-module.js +3 -1
  93. package/src/main.js +2 -0
  94. package/src/markdownCompiler.js +2 -2
  95. package/src/markdownHook.js +7 -2
  96. package/src/menu.js +25 -19
  97. package/src/menus/MainMenu.js +10 -4
  98. package/src/{static-page-module-loader.js → page-module-loader.js} +6 -1
  99. package/src/page-pagination.js +2 -2
  100. package/src/page-runtime.js +48 -3
  101. package/src/pages.js +47 -56
  102. package/src/prefer-project-packages-plugin.js +31 -0
  103. package/src/project-package-resolution.js +36 -0
  104. package/src/publicAssets.js +2 -2
  105. package/src/resolve.js +32 -2
  106. package/src/route-pattern.js +21 -0
  107. package/src/siteDiscoverability.js +14 -1
  108. package/src/socialPreviewImages.js +80 -14
  109. package/src/ssr.js +3 -0
  110. package/src/standalone-demo-url.js +2 -2
  111. package/src/static-params.js +114 -0
  112. package/src/transform.js +93 -26
  113. package/src/wds-plugin.js +16 -3
  114. package/dist-types/src/development-page-module-loader.d.ts.map +0 -1
  115. package/dist-types/src/static-page-module-loader.d.ts +0 -15
  116. package/dist-types/src/static-page-module-loader.d.ts.map +0 -1
  117. package/src/development-page-module-loader.js +0 -29
@@ -0,0 +1,234 @@
1
+ import { html } from 'lit';
2
+ import { document } from '../layout-helper.js';
3
+ import { resolve } from '../../resolve.js';
4
+ import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
5
+ import { addBootstrapIconLibrary } from '../layout.js';
6
+ import { rocketDemoComponents } from '../../components.js';
7
+ import {
8
+ renderHeaderLogo,
9
+ renderHeaderNavLinks,
10
+ renderSocials,
11
+ renderStylesheets,
12
+ } from './atlasDocLayout.js';
13
+
14
+ /** @typedef {import('@rocket/js/types.js').BlogData} BlogData */
15
+ /** @typedef {import('@rocket/js/types.js').PageCollectionEntry} PageCollectionEntry */
16
+
17
+ /** @type {import('@rocket/js/types.js').Components} */
18
+ export const atlasBlogComponents = {
19
+ 'rocket-social-link': {
20
+ file: './components/RocketSocialLink.js',
21
+ className: 'RocketSocialLink',
22
+ loading: 'server',
23
+ },
24
+ ...rocketDemoComponents,
25
+ ...webAwesomeComponents,
26
+ };
27
+
28
+ /**
29
+ * @param {string} date date-only ISO string from Page Metadata
30
+ * @param {string | undefined} language
31
+ */
32
+ export function formatBlogDate(date, language) {
33
+ const parsed = new Date(`${date}T00:00:00Z`);
34
+ if (Number.isNaN(parsed.getTime())) {
35
+ return date;
36
+ }
37
+ return new Intl.DateTimeFormat(language || 'en', {
38
+ dateStyle: 'long',
39
+ timeZone: 'UTC',
40
+ }).format(parsed);
41
+ }
42
+
43
+ /**
44
+ * @param {string[] | undefined} tags
45
+ * @param {BlogData} data
46
+ */
47
+ export function renderBlogTags(tags, data) {
48
+ if (!tags || tags.length === 0) {
49
+ return '';
50
+ }
51
+ return html`
52
+ <ul class="atlas-blog-tags">
53
+ ${tags.map(
54
+ tag => html`
55
+ <li>
56
+ ${data.tagPathPrefix
57
+ ? html`<a href=${blogTagPath(data.tagPathPrefix, tag)} rel="tag">${tag}</a>`
58
+ : html`<span>${tag}</span>`}
59
+ </li>
60
+ `,
61
+ )}
62
+ </ul>
63
+ `;
64
+ }
65
+
66
+ /**
67
+ * @param {string} tagPathPrefix
68
+ * @param {string} tag
69
+ */
70
+ export function blogTagPath(tagPathPrefix, tag) {
71
+ const prefix = tagPathPrefix.endsWith('/') ? tagPathPrefix : `${tagPathPrefix}/`;
72
+ return `${prefix}${tag}/`;
73
+ }
74
+
75
+ /**
76
+ * @param {import('@rocket/js/types.js').PageMetadata} metadata
77
+ * @param {BlogData} data
78
+ * @param {string | undefined} language
79
+ */
80
+ export function renderBlogByline(metadata, data, language) {
81
+ const authors = metadata.authors || [];
82
+ if (!metadata.date && authors.length === 0 && !(metadata.tags || []).length) {
83
+ return '';
84
+ }
85
+ return html`
86
+ <div class="atlas-blog-byline">
87
+ ${metadata.date
88
+ ? html`<time datetime=${metadata.date}>${formatBlogDate(metadata.date, language)}</time>`
89
+ : ''}
90
+ ${authors.length ? html`<span class="atlas-blog-authors">${authors.join(', ')}</span>` : ''}
91
+ ${renderBlogTags(metadata.tags, data)}
92
+ </div>
93
+ `;
94
+ }
95
+
96
+ /**
97
+ * @param {PageCollectionEntry[]} items
98
+ * @param {BlogData} data
99
+ * @param {string | undefined} language
100
+ */
101
+ export function renderBlogPostList(items, data, language) {
102
+ if (items.length === 0) {
103
+ return html`<p class="atlas-blog-empty">No posts yet.</p>`;
104
+ }
105
+ return html`
106
+ <ol class="atlas-blog-post-list" reversed>
107
+ ${items.map(
108
+ item => html`
109
+ <li class="atlas-blog-post-card">
110
+ <h2><a href=${item.url}>${item.metadata.title}</a></h2>
111
+ ${item.metadata.date
112
+ ? html`<time datetime=${item.metadata.date}
113
+ >${formatBlogDate(item.metadata.date, language)}</time
114
+ >`
115
+ : ''}
116
+ ${item.metadata.description ? html`<p>${item.metadata.description}</p>` : ''}
117
+ ${renderBlogTags(item.metadata.tags, data)}
118
+ </li>
119
+ `,
120
+ )}
121
+ </ol>
122
+ `;
123
+ }
124
+
125
+ /**
126
+ * @param {import('@rocket/js/types.js').PagePagination | undefined} pagination
127
+ */
128
+ export function renderBlogPagination(pagination) {
129
+ if (!pagination || pagination.totalPages <= 1) {
130
+ return '';
131
+ }
132
+ return html`
133
+ <nav class="atlas-blog-pagination" aria-label="Blog pages">
134
+ ${pagination.previousPath
135
+ ? html`<a href=${pagination.previousPath} rel="prev">Newer posts</a>`
136
+ : html`<span></span>`}
137
+ <span>Page ${pagination.currentPage} of ${pagination.totalPages}</span>
138
+ ${pagination.nextPath
139
+ ? html`<a href=${pagination.nextPath} rel="next">Older posts</a>`
140
+ : html`<span></span>`}
141
+ </nav>
142
+ `;
143
+ }
144
+
145
+ /**
146
+ * Shared Atlas blog document chrome for post and index layouts.
147
+ *
148
+ * @param {import('../../PageData.js').PageData} pageData
149
+ * @param {BlogData} data
150
+ * @param {unknown} mainContent
151
+ */
152
+ function atlasBlogDocument(pageData, data, mainContent) {
153
+ addBootstrapIconLibrary(pageData);
154
+ const cssPath = resolve('@rocket/js/docs/assets/prism-one-light.css', import.meta);
155
+ const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
156
+ const headContent = data.headContent?.({ pageData });
157
+
158
+ return document(
159
+ pageData,
160
+ html`
161
+ <div class="atlas-blog">
162
+ <header class="atlas-header atlas-blog-header">
163
+ <a href=${data.headerData.homeLink} class="logo-header">
164
+ ${renderHeaderLogo(data.headerData.logo, siteName)}
165
+ </a>
166
+ <nav class="atlas-header-links" aria-label="Primary navigation">
167
+ ${renderHeaderNavLinks(data.headerData.navLinks)}
168
+ ${renderSocials(data.headerData.socials, siteName)}
169
+ </nav>
170
+ </header>
171
+ <main id="content" class="atlas-blog-content">${mainContent}</main>
172
+ </div>
173
+ `,
174
+ {
175
+ menu: false,
176
+ headContent: html`
177
+ <link rel="stylesheet" href=${cssPath} />
178
+ <link
179
+ rel="stylesheet"
180
+ href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
181
+ />
182
+ <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasDoc.css', import.meta)}" />
183
+ <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasBlog.css', import.meta)}" />
184
+ ${data.feedPath
185
+ ? html`<link
186
+ rel="alternate"
187
+ type="application/atom+xml"
188
+ href=${data.feedPath}
189
+ title=${siteName}
190
+ />`
191
+ : ''}
192
+ ${renderStylesheets(data.stylesheets)} ${headContent}
193
+ `,
194
+ },
195
+ );
196
+ }
197
+
198
+ /** @type {import('@rocket/js/types.js').Layout<BlogData>} */
199
+ export const atlasPostLayout = (pageData, data) => {
200
+ const language = pageData.siteHeadMetadata?.language;
201
+ return atlasBlogDocument(
202
+ pageData,
203
+ data,
204
+ html`
205
+ <article class="atlas-blog-post">
206
+ ${renderBlogByline(pageData.metadata, data, language)} ${pageData.content}
207
+ </article>
208
+ `,
209
+ );
210
+ };
211
+
212
+ /** @type {import('@rocket/js/types.js').Layout<BlogData>} */
213
+ export const atlasBlogIndexLayout = (pageData, data) => {
214
+ const language = pageData.siteHeadMetadata?.language;
215
+ const items = pageData.pagination?.items || [];
216
+ return atlasBlogDocument(
217
+ pageData,
218
+ data,
219
+ html`
220
+ <section class="atlas-blog-index">
221
+ <div class="atlas-blog-intro">
222
+ ${pageData.content}
223
+ ${data.feedPath
224
+ ? html`<a class="atlas-blog-feed-link" href=${data.feedPath}>
225
+ <rocket-icon library="bootstrap" name="rss" aria-hidden="true"></rocket-icon>
226
+ Feed
227
+ </a>`
228
+ : ''}
229
+ </div>
230
+ ${renderBlogPostList(items, data, language)} ${renderBlogPagination(pageData.pagination)}
231
+ </section>
232
+ `,
233
+ );
234
+ };
@@ -210,6 +210,7 @@ export const atlasDocLayout = (pageData, data) => {
210
210
  const serializedToc = serializeToc(pageData.toc);
211
211
  const navigationIconServerBudget =
212
212
  data.navigationIconServerBudget ?? DEFAULT_ATLAS_DOC_NAVIGATION_ICON_SERVER_BUDGET;
213
+ const headContent = data.headContent?.({ pageData });
213
214
 
214
215
  return document(
215
216
  pageData,
@@ -269,14 +270,14 @@ export const atlasDocLayout = (pageData, data) => {
269
270
  `,
270
271
  {
271
272
  menu: false,
272
- headerContent: html`
273
+ headContent: html`
273
274
  <link rel="stylesheet" href=${cssPath} />
274
275
  <link
275
276
  rel="stylesheet"
276
277
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
277
278
  />
278
279
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasDoc.css', import.meta)}" />
279
- ${renderStylesheets(data.stylesheets)}
280
+ ${renderStylesheets(data.stylesheets)} ${headContent}
280
281
  `,
281
282
  },
282
283
  );
@@ -262,10 +262,8 @@ export const atlasHeroLayout = (pageData, data) => {
262
262
  const heroEyebrow = hero.eyebrow ?? hero.sloganTop;
263
263
  const heroTitle = hero.title ?? hero.sloganBottom ?? pageData.title;
264
264
  const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
265
- const navLinks = data.headerData.navLinks ?? [
266
- { text: 'Docs', href: hero.documentationLink },
267
- { text: 'Examples', href: '/examples' },
268
- ];
265
+ const navLinks = data.headerData.navLinks ?? [{ text: 'Docs', href: hero.documentationLink }];
266
+ const headContent = data.headContent?.({ pageData });
269
267
 
270
268
  return document(
271
269
  pageData,
@@ -329,14 +327,14 @@ export const atlasHeroLayout = (pageData, data) => {
329
327
  `,
330
328
  {
331
329
  menu: false,
332
- headerContent: html`
330
+ headContent: html`
333
331
  <link rel="stylesheet" href=${cssPath} />
334
332
  <link
335
333
  rel="stylesheet"
336
334
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
337
335
  />
338
336
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasHero.css', import.meta)}" />
339
- ${renderStylesheets(data.stylesheets)}
337
+ ${renderStylesheets(data.stylesheets)} ${headContent}
340
338
  `,
341
339
  },
342
340
  );
@@ -1,27 +1,11 @@
1
1
  import { html } from 'lit';
2
2
  import { document } from '../layout-helper.js';
3
3
  import { resolve } from '../../resolve.js';
4
- import { renderStylesheets } from './atlasDocLayout.js';
4
+ import { renderHeaderLogo, renderStylesheets } from './atlasDocLayout.js';
5
5
 
6
6
  /** @type {import('@rocket/js/types.js').Components} */
7
7
  export const atlasNotFoundComponents = {};
8
8
 
9
- /**
10
- * Renders the header logo.
11
- * @param {Array<string>} logo
12
- */
13
- function renderHeaderLogo(logo) {
14
- if (logo.length === 1) {
15
- return html`<img src=${logo[0]} alt="Logo" width="130" />`;
16
- } else if (logo.length === 2) {
17
- return html`
18
- <img src=${logo[0]} alt="Logo" width="36" />
19
- <img src=${logo[1]} alt="Logo Text" width="88" />
20
- `;
21
- }
22
- return html`<b>Header logo should only contain one or two images.</b>`;
23
- }
24
-
25
9
  /**
26
10
  * @param {Array<{url: string, name: string, label?: string}>} socials
27
11
  */
@@ -33,6 +17,8 @@ function renderSocialLinks(socials) {
33
17
  export const atlasNotFoundLayout = (pageData, data) => {
34
18
  const homeLink = data.headerData.homeLink || '/';
35
19
  const rocketLogo = data.headerData.logo[0];
20
+ const siteName = pageData.siteHeadMetadata?.siteName ?? pageData.title;
21
+ const headContent = data.headContent?.({ pageData });
36
22
 
37
23
  return document(
38
24
  pageData,
@@ -40,7 +26,7 @@ export const atlasNotFoundLayout = (pageData, data) => {
40
26
  <div class="atlas-not-found">
41
27
  <header class="atlas-not-found-header">
42
28
  <a href=${homeLink} class="atlas-not-found-logo">
43
- ${renderHeaderLogo(data.headerData.logo)}
29
+ ${renderHeaderLogo(data.headerData.logo, siteName)}
44
30
  </a>
45
31
  <nav class="atlas-not-found-links" aria-label="Project links">
46
32
  ${renderSocialLinks(data.headerData.socials)}
@@ -54,18 +40,20 @@ export const atlasNotFoundLayout = (pageData, data) => {
54
40
  <div class="atlas-not-found-stars"></div>
55
41
  <div class="atlas-not-found-orbit"></div>
56
42
  <p class="atlas-not-found-code">404</p>
57
- <img class="atlas-not-found-rocket" src=${rocketLogo} alt="" />
43
+ ${rocketLogo
44
+ ? html`<img class="atlas-not-found-rocket" src=${rocketLogo} alt="" />`
45
+ : html``}
58
46
  </div>
59
47
  </main>
60
48
  </div>
61
49
  `,
62
50
  {
63
51
  menu: false,
64
- headerContent: html`<link
52
+ headContent: html`<link
65
53
  rel="stylesheet"
66
54
  href="${resolve('@rocket/js/layouts/atlasNotFound.css', import.meta)}"
67
55
  />
68
- ${renderStylesheets(data.stylesheets)}`,
56
+ ${renderStylesheets(data.stylesheets)} ${headContent}`,
69
57
  },
70
58
  );
71
59
  };
@@ -11,7 +11,7 @@ import { defaultHtmlMenu } from '../menu.js';
11
11
  * @param {Object} [options]
12
12
  * @param {LitHTML} [options.title]
13
13
  * @param {'html' | false} [options.menu] render a default menu. set to false if you want your own
14
- * @param {LitHTML} [options.headerContent]
14
+ * @param {LitHTML} [options.headContent]
15
15
  */
16
16
  export function document(
17
17
  data,
@@ -19,7 +19,7 @@ export function document(
19
19
  {
20
20
  title = data.siteHeadMetadata?.title ?? data.title,
21
21
  menu = 'html',
22
- headerContent = undefined,
22
+ headContent = undefined,
23
23
  } = {},
24
24
  ) {
25
25
  const siteHeadMetadata = data.siteHeadMetadata;
@@ -40,12 +40,12 @@ export function document(
40
40
  }
41
41
  </style>`
42
42
  : ''}
43
- ${data.clientCode} ${headerContent}
43
+ ${data.clientCode} ${headContent}
44
44
  </head>
45
45
  <body>
46
46
  ${menu
47
- ? html`<div id="menu">${defaultHtmlMenu(data.pageTree)}</div>
48
- <div id="content">${content}</div>`
47
+ ? html`<nav id="menu" aria-label="Site">${defaultHtmlMenu(data.pageTree)}</nav>
48
+ <main id="content">${content}</main>`
49
49
  : content}
50
50
  </body>
51
51
  </html>`;
@@ -9,7 +9,7 @@ export const layout = data => {
9
9
  addBootstrapIconLibrary(data);
10
10
  return document(data, data.content, {
11
11
  menu: 'html',
12
- headerContent: html`
12
+ headContent: html`
13
13
  <link
14
14
  rel="stylesheet"
15
15
  href="${resolve('@rocket/js/docs/assets/prism-one-light.css', import.meta)}"
@@ -27,7 +27,7 @@ export const singleDemoLayout = data => {
27
27
  addBootstrapIconLibrary(data);
28
28
  return document(data, data.content, {
29
29
  menu: false,
30
- headerContent: html`
30
+ headContent: html`
31
31
  <link
32
32
  rel="stylesheet"
33
33
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
@@ -92,6 +92,8 @@ function normalizeMarkdownPageContent(rawModule, { parseComponents }) {
92
92
  */
93
93
  function hasBrowserLoadedComponents(components) {
94
94
  return Object.values(components).some(
95
- component => component.loading === 'client' || component.loading.startsWith('hydrate:'),
95
+ component =>
96
+ component.loading === 'client' ||
97
+ (typeof component.loading === 'string' && component.loading.startsWith('hydrate:')),
96
98
  );
97
99
  }
package/src/main.js CHANGED
@@ -5,6 +5,7 @@ import customRocketPlugin from './wds-plugin.js';
5
5
  import { MessageChannel } from 'node:worker_threads';
6
6
  import { customElements } from '@lit-labs/ssr-dom-shim';
7
7
  import { readConfig } from './config.js';
8
+ import { preferProjectPackagesPlugin } from './prefer-project-packages-plugin.js';
8
9
 
9
10
  const configFilePath = process.argv[2] || undefined;
10
11
  const startOptions = readStartOptions(process.argv[3]);
@@ -40,6 +41,7 @@ let devServerConfig = {
40
41
  defaultIconLibrary: config.defaultIconLibrary,
41
42
  watch: startOptions.watch,
42
43
  }),
44
+ preferProjectPackagesPlugin(),
43
45
  ],
44
46
  open: startOptions.open ?? true,
45
47
  nodeResolve: { exportConditions: ['browser'] },
@@ -5,7 +5,7 @@ import rehypeRaw from 'rehype-raw';
5
5
  import { unified } from 'unified';
6
6
  import { visit } from 'unist-util-visit';
7
7
  import { extractMdCode } from './extractCode.js';
8
- import { mdToJs, mdToJsSingleDemo } from './transform.js';
8
+ import { isDemoCodeNode, mdToJs, mdToJsSingleDemo } from './transform.js';
9
9
  import { init, parse as parseExports } from 'es-module-lexer';
10
10
  import ts from 'typescript';
11
11
  import { parseRequestDemoMetadata } from './requestDemoMetadata.js';
@@ -235,7 +235,7 @@ function extractDemoNames() {
235
235
  /** @type {import('unist-util-visit').Visitor} */
236
236
  const visitor = _node => {
237
237
  const node = /** @type {import('mdast').Code} */ (_node);
238
- if (node.lang === 'js' && node.meta === 'demo' && typeof node.value === 'string') {
238
+ if (isDemoCodeNode(node) && typeof node.value === 'string') {
239
239
  for (const exported of parseExports(node.value)[1]) {
240
240
  demoNames.push(exported.ln || exported.n);
241
241
  }
@@ -102,7 +102,11 @@ export async function resolve(specifier, context, nextResolve) {
102
102
  },
103
103
  };
104
104
  if (match[0] === 'Setup' && isLocalFile(url)) {
105
- await sendPort.sendAndWait({
105
+ // Fire-and-forget: awaiting a reply here can deadlock. The main thread
106
+ // blocks in Atomics.wait during e.g. a nested module.register() call, so
107
+ // it may be unable to answer until this resolve returns. postMessage
108
+ // delivery is ordered, which is all the dependency tracking needs.
109
+ sendPort.postMessage({
106
110
  parent: getPath(/** @type {string} */ (context.parentURL)),
107
111
  url: getPath(resolve.url),
108
112
  });
@@ -116,7 +120,8 @@ export async function resolve(specifier, context, nextResolve) {
116
120
  ...resolve.importAttributes,
117
121
  type: 'cache' + lastReload.toString(36),
118
122
  };
119
- await sendPort.sendAndWait({
123
+ // Fire-and-forget, see above.
124
+ sendPort.postMessage({
120
125
  parent: getPath(/** @type {string} */ (context.parentURL)),
121
126
  url: getPath(resolve.url),
122
127
  });
package/src/menu.js CHANGED
@@ -34,13 +34,14 @@ export function treeFromPages(pages) {
34
34
  continue;
35
35
  }
36
36
  const parts = getMenuParts(url, module);
37
+ if (parts.some(part => part.startsWith(':'))) {
38
+ // Dynamic paths have no single public URL, so they get no menu entry.
39
+ continue;
40
+ }
37
41
  let obj = children;
38
42
  /** @type {any} */
39
43
  let last = {};
40
44
  for (const part of parts) {
41
- if (part.startsWith(':')) {
42
- break;
43
- }
44
45
  const get = obj.find(leaf => leaf.name === part);
45
46
  if (!get) {
46
47
  /** @type {import('@rocket/js/types.js').PageTree[]} */
@@ -137,19 +138,25 @@ function sortRecursive(children) {
137
138
  }
138
139
 
139
140
  /**
141
+ * The itemFn receives the rendered child list (or lit `nothing`) and must place
142
+ * it inside the returned item so nested lists stay valid HTML.
143
+ *
140
144
  * @param {import("@rocket/js/types.js").PageTree} tree
141
145
  * @param {string} listTagName
142
- * @param {(name: string, url: string, module: import("@rocket/js/types.js").Module, linkText: string) => import("lit").TemplateResult} itemFn
146
+ * @param {(name: string, url: string, module: import("@rocket/js/types.js").Module, linkText: string, children: import("lit").TemplateResult | typeof nothing) => import("lit").TemplateResult} itemFn
147
+ * @returns {import("lit").TemplateResult}
143
148
  */
144
149
  export function treeToHtml(tree, listTagName, itemFn) {
145
150
  return html`${unsafeHTML(`<${listTagName}>`)}${join(
146
- tree.children.map(page => {
147
- let a = itemFn(page.name, page.url, page.module, page.linkText);
148
- if (page.children.length) {
149
- a = html`${a}${treeToHtml(page, listTagName, itemFn)}`;
150
- }
151
- return a;
152
- }),
151
+ tree.children.map(page =>
152
+ itemFn(
153
+ page.name,
154
+ page.url,
155
+ page.module,
156
+ page.linkText,
157
+ page.children.length ? treeToHtml(page, listTagName, itemFn) : nothing,
158
+ ),
159
+ ),
153
160
  nothing,
154
161
  )}${unsafeHTML(`</${listTagName}>`)}`;
155
162
  }
@@ -158,14 +165,13 @@ export function treeToHtml(tree, listTagName, itemFn) {
158
165
  * @param {import('@rocket/js/types.js').PageTree} pageTree
159
166
  */
160
167
  export function defaultHtmlMenu(pageTree) {
161
- return treeToHtml(
162
- pageTree,
163
- 'ul',
164
- (_name, url, _module, linkText) =>
165
- html`<li>
166
- <a href=${url}>${linkText}</a>
167
- </li>`,
168
- );
168
+ return treeToHtml(pageTree, 'ul', (_name, url, module, linkText, children) => {
169
+ const menu = module.config.menu;
170
+ const noLink = !url || menu === false || (typeof menu === 'object' && menu?.noLink);
171
+ return html`<li>
172
+ ${noLink ? html`<span>${linkText}</span>` : html`<a href=${url}>${linkText}</a>`}${children}
173
+ </li>`;
174
+ });
169
175
  }
170
176
 
171
177
  /** Runs on: import-hook */
@@ -32,15 +32,21 @@ export class MainMenu extends LitElement {
32
32
  */
33
33
  list(page, depth = 0) {
34
34
  const title = page.linkText;
35
+ const isCurrent = page.url === this.currentPath;
35
36
  /** @type {any} */
36
- const classes = { current: page.url === this.currentPath };
37
+ const classes = { current: isCurrent };
37
38
  const level = `lvl-${depth}`;
38
39
  classes[level] = true;
39
40
  return html`
40
41
  <li>
41
- <a href=${ifDefined(page.menuNoLink ? undefined : page.url)} class=${classMap(classes)}
42
- >${title}</a
43
- >
42
+ ${page.menuNoLink
43
+ ? html`<span class=${classMap(classes)}>${title}</span>`
44
+ : html`<a
45
+ href=${ifDefined(page.url)}
46
+ class=${classMap(classes)}
47
+ aria-current=${ifDefined(isCurrent ? 'page' : undefined)}
48
+ >${title}</a
49
+ >`}
44
50
  ${page.children.length > 0
45
51
  ? html`<ul>
46
52
  ${page.children.map(child => this.list(child, depth + 1))}
@@ -3,7 +3,12 @@ import { parseComponents } from './components.js';
3
3
 
4
4
  /** @typedef {import('./page-runtime.js').PageModuleLoaderOptions} PageModuleLoaderOptions */
5
5
 
6
- export function createStaticPageModuleLoader() {
6
+ /**
7
+ * The Page Module Loader used by both the development server and static builds.
8
+ * Loading goes through Rocket's import hooks, which behave the same in both
9
+ * runtime environments.
10
+ */
11
+ export function createPageModuleLoader() {
7
12
  return {
8
13
  /**
9
14
  * @param {PageModuleLoaderOptions} options
@@ -1,4 +1,4 @@
1
- import { URLPattern } from 'urlpattern-polyfill';
1
+ import { routePattern } from './route-pattern.js';
2
2
  import { PageData } from './PageData.js';
3
3
  import { normalizeDocumentPath } from './standalone-demo-url.js';
4
4
 
@@ -229,7 +229,7 @@ function parsePaginatedArchivePath(pathname) {
229
229
  * @param {string} routePath
230
230
  */
231
231
  function matchPagePath(pathname, origin, routePath) {
232
- const pattern = new URLPattern({ pathname: routePath });
232
+ const pattern = routePattern(routePath);
233
233
  return pattern.exec(pathname, origin);
234
234
  }
235
235