@rocket/js 0.1.0 → 0.1.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 (36) hide show
  1. package/README.md +14 -5
  2. package/dist-types/exports/components.d.ts +1 -1
  3. package/dist-types/exports/icons.d.ts +1 -1
  4. package/dist-types/exports/layout.d.ts +1 -1
  5. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -1
  6. package/dist-types/src/cli/RocketInit.d.ts.map +1 -1
  7. package/dist-types/src/cli/RocketStart.d.ts +34 -2
  8. package/dist-types/src/cli/RocketStart.d.ts.map +1 -1
  9. package/dist-types/src/components.d.ts +2 -0
  10. package/dist-types/src/components.d.ts.map +1 -1
  11. package/dist-types/src/icons.d.ts +12 -0
  12. package/dist-types/src/icons.d.ts.map +1 -1
  13. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -1
  14. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -1
  15. package/dist-types/src/layouts/layout.d.ts +2 -4
  16. package/dist-types/src/layouts/layout.d.ts.map +1 -1
  17. package/dist-types/src/standalone-demo-url.d.ts.map +1 -1
  18. package/dist-types/src/transform.d.ts.map +1 -1
  19. package/dist-types/src/wds-plugin.d.ts +1 -0
  20. package/dist-types/src/wds-plugin.d.ts.map +1 -1
  21. package/exports/components.js +1 -1
  22. package/exports/icons.js +3 -0
  23. package/exports/layout.js +1 -1
  24. package/package.json +1 -1
  25. package/src/cli/RocketBuild.js +38 -2
  26. package/src/cli/RocketInit.js +337 -24
  27. package/src/cli/RocketStart.js +96 -30
  28. package/src/components.js +19 -0
  29. package/src/icons.js +15 -0
  30. package/src/layouts/atlas/atlasDocLayout.js +2 -15
  31. package/src/layouts/atlas/atlasHeroLayout.js +3 -12
  32. package/src/layouts/layout.js +2 -12
  33. package/src/main.js +21 -4
  34. package/src/standalone-demo-url.js +21 -8
  35. package/src/transform.js +89 -2
  36. package/src/wds-plugin.js +14 -9
@@ -4,6 +4,7 @@ import { resolve } from '../../resolve.js';
4
4
  import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
5
5
  import { addBootstrapIconLibrary } from '../layout.js';
6
6
  import { renderHeaderLogo, renderHeaderNav, renderSocials } from './atlasDocLayout.js';
7
+ import { rocketDemoComponents } from '../../components.js';
7
8
 
8
9
  /** @type {import('@rocket/js/types.js').Components} */
9
10
  export const atlasHeroComponents = {
@@ -27,21 +28,11 @@ export const atlasHeroComponents = {
27
28
  className: 'FeatureList',
28
29
  loading: 'server',
29
30
  },
31
+ ...rocketDemoComponents,
30
32
  'rocket-code-block': {
31
- file: './RocketCodeBlock.js',
32
- className: 'RocketCodeBlock',
33
+ ...rocketDemoComponents['rocket-code-block'],
33
34
  loading: 'hydrate:onClientLoad',
34
35
  },
35
- 'rocket-js-demo': {
36
- file: './RocketJsDemo.js',
37
- className: 'RocketJsDemo',
38
- loading: 'client',
39
- },
40
- 'rocket-request-demo': {
41
- file: './RocketRequestDemo.js',
42
- className: 'RocketRequestDemo',
43
- loading: 'client',
44
- },
45
36
  ...webAwesomeComponents,
46
37
  };
47
38
 
@@ -2,7 +2,7 @@
2
2
  import { html } from 'lit';
3
3
  import { document } from './layout-helper.js';
4
4
  import { resolve } from '../resolve.js';
5
- import { iconsFromPackage } from '../icons.js';
5
+ import { addBootstrapIconLibrary } from '../icons.js';
6
6
 
7
7
  /** @type {import('@rocket/js/types.js').Layout<null>} */
8
8
  export const layout = data => {
@@ -49,14 +49,4 @@ export const singleDemoLayout = data => {
49
49
  });
50
50
  };
51
51
 
52
- /**
53
- * @param {import('@rocket/js/PageData.js').PageData} pageData
54
- */
55
- export function addBootstrapIconLibrary(pageData) {
56
- pageData.addIconLibraries(
57
- {
58
- bootstrap: iconsFromPackage('bootstrap-icons', 'icons/*.svg'),
59
- },
60
- { defaultIconLibrary: 'bootstrap' },
61
- );
62
- }
52
+ export { addBootstrapIconLibrary };
package/src/main.js CHANGED
@@ -6,7 +6,8 @@ import { MessageChannel } from 'node:worker_threads';
6
6
  import { customElements } from '@lit-labs/ssr-dom-shim';
7
7
  import { readConfig } from './config.js';
8
8
 
9
- const configFilePath = process.argv.at(-1);
9
+ const configFilePath = process.argv[2] || undefined;
10
+ const startOptions = readStartOptions(process.argv[3]);
10
11
 
11
12
  customElements.define = (name, ctor) => {
12
13
  customElements.__definitions.set(name, {
@@ -37,12 +38,13 @@ let devServerConfig = {
37
38
  urlLifecycle: config.urlLifecycle,
38
39
  iconLibraries: config.iconLibraries,
39
40
  defaultIconLibrary: config.defaultIconLibrary,
41
+ watch: startOptions.watch,
40
42
  }),
41
43
  ],
42
- open: true,
44
+ open: startOptions.open ?? true,
43
45
  nodeResolve: { exportConditions: ['browser'] },
44
- watch: true,
45
- port: 8888,
46
+ watch: startOptions.watch ?? true,
47
+ port: startOptions.port ?? 8888,
46
48
  };
47
49
 
48
50
  devServerConfig = config.adjustDevServerConfig(devServerConfig);
@@ -53,3 +55,18 @@ const server = await startDevServer({
53
55
  readCliArgs: false,
54
56
  readFileConfig: false,
55
57
  });
58
+
59
+ /**
60
+ * @param {string | undefined} value
61
+ * @returns {{ port?: number; open?: boolean; watch?: boolean }}
62
+ */
63
+ function readStartOptions(value) {
64
+ if (!value) {
65
+ return {};
66
+ }
67
+ const parsed = JSON.parse(value);
68
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
69
+ return {};
70
+ }
71
+ return /** @type {{ port?: number; open?: boolean; watch?: boolean }} */ (parsed);
72
+ }
@@ -89,14 +89,16 @@ export function matchStandaloneDemoUrl(pathname, origin, pages) {
89
89
  if (!isMarkdownPage(page) || !page.demoNames?.includes(standaloneDemo.demoName)) {
90
90
  continue;
91
91
  }
92
- const match = matchPagePath(standaloneDemo.parentPathname, origin, routePath);
93
- if (match) {
94
- return {
95
- page,
96
- routePath,
97
- params: match.pathname.groups,
98
- variant: { kind: 'standalone-demo', demoName: standaloneDemo.demoName },
99
- };
92
+ for (const parentPathname of parentPathnameCandidates(standaloneDemo.parentPathname)) {
93
+ const match = matchPagePath(parentPathname, origin, routePath);
94
+ if (match) {
95
+ return {
96
+ page,
97
+ routePath,
98
+ params: match.pathname.groups,
99
+ variant: { kind: 'standalone-demo', demoName: standaloneDemo.demoName },
100
+ };
101
+ }
100
102
  }
101
103
  }
102
104
  return null;
@@ -126,6 +128,17 @@ function matchPagePath(pathname, origin, routePath) {
126
128
  return pattern.exec(pathname, origin);
127
129
  }
128
130
 
131
+ /**
132
+ * @param {string} parentPathname
133
+ */
134
+ function parentPathnameCandidates(parentPathname) {
135
+ const documentPath = normalizeDocumentPath(parentPathname);
136
+ if (documentPath === parentPathname) {
137
+ return [parentPathname];
138
+ }
139
+ return [parentPathname, documentPath];
140
+ }
141
+
129
142
  /**
130
143
  * @param {Page} page
131
144
  */
package/src/transform.js CHANGED
@@ -13,6 +13,7 @@ import { visit } from 'unist-util-visit';
13
13
  import { init, parse as parseExports } from 'es-module-lexer';
14
14
  import { headLinesToTree } from './menu.js';
15
15
  import { parseRequestDemoMetadata } from './requestDemoMetadata.js';
16
+ import ts from 'typescript';
16
17
 
17
18
  /** @type {import('rehype-autolink-headings').Options} */
18
19
  const headingAnchorOptions = {
@@ -119,13 +120,14 @@ export function contentFn(data, layout) {
119
120
  */
120
121
  async function makeJsFile(serverCode, clientCode, markdown, headlines) {
121
122
  let litImport = "import { html } from 'lit'";
122
- if (/import\s*?{(?:\n|.)*?html(\n|.)*}.*/m.test(serverCode)) {
123
+ const normalizedServerCode = normalizeLayoutExportBindings(serverCode);
124
+ if (/import\s*?{(?:\n|.)*?html(\n|.)*}.*/m.test(normalizedServerCode)) {
123
125
  litImport = '';
124
126
  }
125
127
  return `
126
128
  import {render} from '@lit-labs/ssr';
127
129
  ${litImport}
128
- ${serverCode}
130
+ ${normalizedServerCode}
129
131
  export function contentFn(data, defaultLayout) {
130
132
  let renderLayout = defaultLayout;
131
133
  if (typeof layout !== 'undefined') {
@@ -143,6 +145,91 @@ export function contentFn(data, defaultLayout) {
143
145
  }`;
144
146
  }
145
147
 
148
+ /**
149
+ * Direct ESM re-exports do not create local bindings, but the generated Markdown module calls the
150
+ * selected layout from inside contentFn. Rewriting only direct `layout` re-exports keeps the public
151
+ * Page syntax working while preserving the generated function's local binding lookup.
152
+ *
153
+ * @param {string} serverCode
154
+ * @returns {string}
155
+ */
156
+ function normalizeLayoutExportBindings(serverCode) {
157
+ if (typeof serverCode !== 'string') {
158
+ return '';
159
+ }
160
+ const sourceFile = ts.createSourceFile(
161
+ 'page-server.js',
162
+ serverCode,
163
+ ts.ScriptTarget.Latest,
164
+ true,
165
+ ts.ScriptKind.JS,
166
+ );
167
+ /** @type {{start: number; end: number; text: string}[]} */
168
+ const replacements = [];
169
+
170
+ for (const statement of sourceFile.statements) {
171
+ if (
172
+ !ts.isExportDeclaration(statement) ||
173
+ !statement.moduleSpecifier ||
174
+ !statement.exportClause ||
175
+ !ts.isNamedExports(statement.exportClause)
176
+ ) {
177
+ continue;
178
+ }
179
+
180
+ const layoutExport = statement.exportClause.elements.find(
181
+ element => element.name.text === 'layout',
182
+ );
183
+ if (!layoutExport) {
184
+ continue;
185
+ }
186
+
187
+ const moduleSpecifier = statement.moduleSpecifier.getText(sourceFile);
188
+ const attributes = statement.attributes ? ` ${statement.attributes.getText(sourceFile)}` : '';
189
+ const importedName = (layoutExport.propertyName || layoutExport.name).getText(sourceFile);
190
+ const layoutImportSpecifier =
191
+ importedName === 'layout' ? 'layout' : `${importedName} as layout`;
192
+ const replacementLines = [
193
+ `import { ${layoutImportSpecifier} } from ${moduleSpecifier}${attributes};`,
194
+ 'export { layout };',
195
+ ];
196
+
197
+ const remainingExports = statement.exportClause.elements.filter(
198
+ element => element !== layoutExport,
199
+ );
200
+ if (remainingExports.length) {
201
+ const remainingSpecifiers = remainingExports.map(element => {
202
+ if (element.propertyName) {
203
+ return `${element.propertyName.getText(sourceFile)} as ${element.name.getText(sourceFile)}`;
204
+ }
205
+ return element.name.getText(sourceFile);
206
+ });
207
+ replacementLines.push(
208
+ `export { ${remainingSpecifiers.join(', ')} } from ${moduleSpecifier}${attributes};`,
209
+ );
210
+ }
211
+
212
+ replacements.push({
213
+ start: statement.getStart(sourceFile),
214
+ end: statement.end,
215
+ text: replacementLines.join('\n'),
216
+ });
217
+ }
218
+
219
+ if (!replacements.length) {
220
+ return serverCode;
221
+ }
222
+
223
+ let normalizedCode = serverCode;
224
+ for (const replacement of replacements.toReversed()) {
225
+ normalizedCode =
226
+ normalizedCode.slice(0, replacement.start) +
227
+ replacement.text +
228
+ normalizedCode.slice(replacement.end);
229
+ }
230
+ return normalizedCode;
231
+ }
232
+
146
233
  function parseDemos() {
147
234
  /** @type {{name: string, code: string}[]} */
148
235
  const demos = [];
package/src/wds-plugin.js CHANGED
@@ -24,12 +24,13 @@ let pageRegistry = new Map();
24
24
  let modules = new Map();
25
25
 
26
26
  /** @typedef {import('@rocket/js/types.js').UrlLifecycleConfig} UrlLifecycleConfig */
27
- /** @typedef {{ urlLifecycle?: UrlLifecycleConfig; siteHeadMetadata?: import('@rocket/js/types.js').SiteHeadMetadataConfig; siteOrigin?: string; siteDiscoverability?: import('@rocket/js/types.js').SiteDiscoverabilityConfig; iconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig; defaultIconLibrary?: string; captureSocialPreviewImage?: import('./socialPreviewImages.js').SocialPreviewCapture }} RocketDevServerPluginOptions */
27
+ /** @typedef {{ urlLifecycle?: UrlLifecycleConfig; siteHeadMetadata?: import('@rocket/js/types.js').SiteHeadMetadataConfig; siteOrigin?: string; siteDiscoverability?: import('@rocket/js/types.js').SiteDiscoverabilityConfig; iconLibraries?: import('@rocket/js/types.js').IconLibrariesConfig; defaultIconLibrary?: string; captureSocialPreviewImage?: import('./socialPreviewImages.js').SocialPreviewCapture; watch?: boolean }} RocketDevServerPluginOptions */
28
28
  /** @typedef {RocketDevServerPluginOptions | UrlLifecycleConfig} RocketDevServerPluginInput */
29
29
 
30
30
  /** @type {(include: string[], exclude: (string | RegExp)[], resolverPort: import('node:worker_threads').MessagePort, options?: RocketDevServerPluginInput) => import('@web/dev-server-core').Plugin} */
31
31
  export default (include, exclude, resolverPort, options = {}) => {
32
32
  const pluginOptions = normalizePluginOptions(options);
33
+ const watchEnabled = pluginOptions.watch !== false;
33
34
  const iconAssetStore = createIconAssetStore();
34
35
  /** @type {import('node:fs').FSWatcher[]} */
35
36
  const watchers = [];
@@ -65,19 +66,23 @@ export default (include, exclude, resolverPort, options = {}) => {
65
66
  } else {
66
67
  modules.set(message.url, new Set([message.parent]));
67
68
  }
68
- watchFileDirectory(message.url);
69
+ if (watchEnabled) {
70
+ watchFileDirectory(message.url);
71
+ }
69
72
  resolverPort.postMessage('ok');
70
73
  });
71
74
  pageRegistry = await getPages(process.cwd(), include, exclude);
72
75
  validateDevelopmentPublicAssets(pluginOptions);
73
- pageRegistry.forEach(page => {
74
- watchFileDirectory(page.file);
75
- });
76
- modules.forEach((_, url) => {
77
- watchFileDirectory(url);
78
- });
76
+ if (watchEnabled) {
77
+ pageRegistry.forEach(page => {
78
+ watchFileDirectory(page.file);
79
+ });
80
+ modules.forEach((_, url) => {
81
+ watchFileDirectory(url);
82
+ });
83
+ }
79
84
  const publicDir = path.join(process.cwd(), PUBLIC_ASSETS_DIR);
80
- if (fs.existsSync(publicDir)) {
85
+ if (watchEnabled && fs.existsSync(publicDir)) {
81
86
  fileWatcher?.add(publicDir);
82
87
  }
83
88
  },