@rocket/js 0.1.0 → 0.1.2

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 (43) hide show
  1. package/README.md +20 -26
  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/exports/types/rocket.d.ts +8 -0
  6. package/dist-types/exports/types/rocket.d.ts.map +1 -1
  7. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -1
  8. package/dist-types/src/cli/RocketInit.d.ts +5 -1
  9. package/dist-types/src/cli/RocketInit.d.ts.map +1 -1
  10. package/dist-types/src/cli/RocketStart.d.ts +34 -2
  11. package/dist-types/src/cli/RocketStart.d.ts.map +1 -1
  12. package/dist-types/src/components.d.ts +2 -0
  13. package/dist-types/src/components.d.ts.map +1 -1
  14. package/dist-types/src/icons.d.ts +12 -0
  15. package/dist-types/src/icons.d.ts.map +1 -1
  16. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts +4 -0
  17. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -1
  18. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -1
  19. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -1
  20. package/dist-types/src/layouts/layout.d.ts +2 -4
  21. package/dist-types/src/layouts/layout.d.ts.map +1 -1
  22. package/dist-types/src/standalone-demo-url.d.ts.map +1 -1
  23. package/dist-types/src/transform.d.ts.map +1 -1
  24. package/dist-types/src/wds-plugin.d.ts +1 -0
  25. package/dist-types/src/wds-plugin.d.ts.map +1 -1
  26. package/exports/components.js +1 -1
  27. package/exports/icons.js +3 -0
  28. package/exports/layout.js +1 -1
  29. package/exports/types/rocket.ts +8 -0
  30. package/package.json +2 -2
  31. package/src/cli/RocketBuild.js +38 -2
  32. package/src/cli/RocketInit.js +401 -36
  33. package/src/cli/RocketStart.js +96 -30
  34. package/src/components.js +19 -0
  35. package/src/icons.js +15 -0
  36. package/src/layouts/atlas/atlasDocLayout.js +10 -15
  37. package/src/layouts/atlas/atlasHeroLayout.js +10 -13
  38. package/src/layouts/atlas/atlasNotFoundLayout.js +5 -3
  39. package/src/layouts/layout.js +2 -12
  40. package/src/main.js +21 -4
  41. package/src/standalone-demo-url.js +21 -8
  42. package/src/transform.js +89 -2
  43. package/src/wds-plugin.js +14 -9
@@ -5,6 +5,9 @@ import { debounce } from '../debounce.js';
5
5
  import path from 'path';
6
6
 
7
7
  export class RocketStart {
8
+ /** @type {RocketStartOptions} */
9
+ startOptions = {};
10
+
8
11
  /**
9
12
  * @param {import('commander').Command} program
10
13
  * @param {import('./RocketCli.js').RocketCli} cli
@@ -12,17 +15,29 @@ export class RocketStart {
12
15
  async setupCommand(program, cli) {
13
16
  this.cli = cli;
14
17
 
15
- program.command('start').action(async () => {
16
- await cli.getConfig();
17
- await this.start();
18
- });
18
+ program
19
+ .command('start')
20
+ .option('-p, --port <port>', 'port for the development server')
21
+ .option('--no-open', 'do not open the browser')
22
+ .option('--no-watch', 'disable automatic file watching and reloads')
23
+ .action(async options => {
24
+ const startOptions = normalizeStartOptions(options);
25
+ await cli.getConfig();
26
+ await this.start(startOptions);
27
+ });
19
28
  }
20
29
 
21
- spawnServer() {
30
+ /**
31
+ * @param {RocketStartOptions} [options]
32
+ */
33
+ spawnServer(options = {}) {
22
34
  process.stdin.setRawMode?.(true);
23
35
  return spawn(
24
36
  'node',
25
- [path.join(import.meta.dirname, '../main.js'), this.cli?.configFilePath || ''],
37
+ startServerArgs({
38
+ configFilePath: this.cli?.configFilePath,
39
+ options,
40
+ }),
26
41
  {
27
42
  stdio: 'inherit',
28
43
  cwd: process.cwd(),
@@ -32,36 +47,42 @@ export class RocketStart {
32
47
 
33
48
  restartServer() {
34
49
  this.server?.kill();
35
- this.server = this.spawnServer();
50
+ this.server = this.spawnServer(this.startOptions);
36
51
  console.clear();
37
52
  console.log('Restarting rocket server...');
38
53
  }
39
54
 
40
- async start() {
41
- this.server = this.spawnServer();
55
+ /**
56
+ * @param {RocketStartOptions} [options]
57
+ */
58
+ async start(options = {}) {
59
+ this.startOptions = options;
60
+ this.server = this.spawnServer(options);
42
61
 
43
- // watch config
44
- watch(
45
- path.join(process.cwd(), this.cli?.configFilePath || 'rocket-config.js'),
46
- debounce(async (_, filename) => {
47
- if (!filename) {
48
- return;
49
- }
50
- this.restartServer();
51
- }, 100),
52
- );
62
+ if (options.watch !== false) {
63
+ // watch config
64
+ watch(
65
+ path.join(process.cwd(), this.cli?.configFilePath || 'rocket-config.js'),
66
+ debounce(async (_, filename) => {
67
+ if (!filename) {
68
+ return;
69
+ }
70
+ this.restartServer();
71
+ }, 100),
72
+ );
53
73
 
54
- // watch src
55
- watch(
56
- import.meta.dirname,
57
- { recursive: true },
58
- debounce(async (_, filename) => {
59
- if (!filename) {
60
- return;
61
- }
62
- this.restartServer();
63
- }, 100),
64
- );
74
+ // watch src
75
+ watch(
76
+ import.meta.dirname,
77
+ { recursive: true },
78
+ debounce(async (_, filename) => {
79
+ if (!filename) {
80
+ return;
81
+ }
82
+ this.restartServer();
83
+ }, 100),
84
+ );
85
+ }
65
86
 
66
87
  process.stdin.on('data', data => {
67
88
  const char = data.toString();
@@ -77,3 +98,48 @@ export class RocketStart {
77
98
  console.log('Restart the server with Ctrl+R');
78
99
  }
79
100
  }
101
+
102
+ /**
103
+ * @typedef {{ port?: number; open?: boolean; watch?: boolean }} RocketStartOptions
104
+ */
105
+
106
+ /**
107
+ * @param {{ port?: string; open?: boolean; watch?: boolean }} options
108
+ * @returns {RocketStartOptions}
109
+ */
110
+ export function normalizeStartOptions(options) {
111
+ return {
112
+ ...(options.port !== undefined ? { port: parsePortOption(options.port) } : {}),
113
+ ...(options.open !== undefined ? { open: options.open } : {}),
114
+ ...(options.watch !== undefined ? { watch: options.watch } : {}),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * @param {{ configFilePath?: string; options?: RocketStartOptions }} input
120
+ */
121
+ export function startServerArgs({ configFilePath, options = {} }) {
122
+ return [
123
+ path.join(import.meta.dirname, '../main.js'),
124
+ configFilePath || '',
125
+ JSON.stringify(options),
126
+ ];
127
+ }
128
+
129
+ /**
130
+ * @param {string} value
131
+ */
132
+ function parsePortOption(value) {
133
+ if (!/^\d+$/.test(value)) {
134
+ throw new Error(
135
+ `Invalid --port ${JSON.stringify(value)}. Expected an integer from 1 to 65535.`,
136
+ );
137
+ }
138
+ const port = Number(value);
139
+ if (port < 1 || port > 65535) {
140
+ throw new Error(
141
+ `Invalid --port ${JSON.stringify(value)}. Expected an integer from 1 to 65535.`,
142
+ );
143
+ }
144
+ return port;
145
+ }
package/src/components.js CHANGED
@@ -1,6 +1,25 @@
1
1
  import { resolve } from '@rocket/js/resolve.js';
2
2
  import { createComponentHydration } from './component-hydration.js';
3
3
 
4
+ /** @type {import('@rocket/js/types.js').Components} */
5
+ export const rocketDemoComponents = {
6
+ 'rocket-code-block': {
7
+ file: './RocketCodeBlock.js',
8
+ className: 'RocketCodeBlock',
9
+ loading: 'hydrate:onVisible',
10
+ },
11
+ 'rocket-js-demo': {
12
+ file: './RocketJsDemo.js',
13
+ className: 'RocketJsDemo',
14
+ loading: 'client',
15
+ },
16
+ 'rocket-request-demo': {
17
+ file: './RocketRequestDemo.js',
18
+ className: 'RocketRequestDemo',
19
+ loading: 'client',
20
+ },
21
+ };
22
+
4
23
  /**
5
24
  * @param {import('@rocket/js/types.js').Components} components
6
25
  */
package/src/icons.js CHANGED
@@ -46,6 +46,21 @@ export function iconsFromPath(files) {
46
46
  };
47
47
  }
48
48
 
49
+ export const rocketBootstrapIconLibraries = {
50
+ bootstrap: iconsFromPackage('bootstrap-icons', 'icons/*.svg'),
51
+ };
52
+
53
+ export const rocketDefaultBootstrapIconLibrary = 'bootstrap';
54
+
55
+ /**
56
+ * @param {{ addIconLibraries: (iconLibraries: import('@rocket/js/types.js').IconLibrariesConfig, options?: { defaultIconLibrary?: string }) => void }} pageData
57
+ */
58
+ export function addBootstrapIconLibrary(pageData) {
59
+ pageData.addIconLibraries(rocketBootstrapIconLibraries, {
60
+ defaultIconLibrary: rocketDefaultBootstrapIconLibrary,
61
+ });
62
+ }
63
+
49
64
  export class IconAssetStore {
50
65
  constructor() {
51
66
  /** @type {Map<string, { url: string; svg: string; library: string; name: string }>} */
@@ -5,6 +5,7 @@ import { resolve } from '../../resolve.js';
5
5
  import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
6
6
  import { addBootstrapIconLibrary } from '../layout.js';
7
7
  import { pageNavigationLinks } from '../../menus/pageNavigation.js';
8
+ import { rocketDemoComponents } from '../../components.js';
8
9
 
9
10
  export const DEFAULT_ATLAS_DOC_NAVIGATION_ICON_SERVER_BUDGET = 35;
10
11
 
@@ -35,21 +36,7 @@ export const atlasDocComponents = {
35
36
  className: 'RocketSocialLink',
36
37
  loading: 'server',
37
38
  },
38
- 'rocket-code-block': {
39
- file: './RocketCodeBlock.js',
40
- className: 'RocketCodeBlock',
41
- loading: 'hydrate:onVisible',
42
- },
43
- 'rocket-js-demo': {
44
- file: './RocketJsDemo.js',
45
- className: 'RocketJsDemo',
46
- loading: 'client',
47
- },
48
- 'rocket-request-demo': {
49
- file: './RocketRequestDemo.js',
50
- className: 'RocketRequestDemo',
51
- loading: 'client',
52
- },
39
+ ...rocketDemoComponents,
53
40
  ...webAwesomeComponents,
54
41
  };
55
42
 
@@ -132,6 +119,13 @@ export function renderHeaderNav(links = []) {
132
119
  `;
133
120
  }
134
121
 
122
+ /**
123
+ * @param {string[] | undefined} stylesheets
124
+ */
125
+ export function renderStylesheets(stylesheets = []) {
126
+ return stylesheets.map(href => html`<link rel="stylesheet" href=${href} />`);
127
+ }
128
+
135
129
  /**
136
130
  * @param {import('@rocket/js/types.js').PageMetadataCustomValue | undefined} customValue
137
131
  * @returns {import('@rocket/js/types.js').AtlasDocAsideTip | false | undefined}
@@ -282,6 +276,7 @@ export const atlasDocLayout = (pageData, data) => {
282
276
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
283
277
  />
284
278
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasDoc.css', import.meta)}" />
279
+ ${renderStylesheets(data.stylesheets)}
285
280
  `,
286
281
  },
287
282
  );
@@ -3,7 +3,13 @@ import { document } from '../layout-helper.js';
3
3
  import { resolve } from '../../resolve.js';
4
4
  import { webAwesomeComponents } from '@rocket/js/components/web-awesome.js';
5
5
  import { addBootstrapIconLibrary } from '../layout.js';
6
- import { renderHeaderLogo, renderHeaderNav, renderSocials } from './atlasDocLayout.js';
6
+ import {
7
+ renderHeaderLogo,
8
+ renderHeaderNav,
9
+ renderSocials,
10
+ renderStylesheets,
11
+ } from './atlasDocLayout.js';
12
+ import { rocketDemoComponents } from '../../components.js';
7
13
 
8
14
  /** @type {import('@rocket/js/types.js').Components} */
9
15
  export const atlasHeroComponents = {
@@ -27,21 +33,11 @@ export const atlasHeroComponents = {
27
33
  className: 'FeatureList',
28
34
  loading: 'server',
29
35
  },
36
+ ...rocketDemoComponents,
30
37
  'rocket-code-block': {
31
- file: './RocketCodeBlock.js',
32
- className: 'RocketCodeBlock',
38
+ ...rocketDemoComponents['rocket-code-block'],
33
39
  loading: 'hydrate:onClientLoad',
34
40
  },
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
41
  ...webAwesomeComponents,
46
42
  };
47
43
 
@@ -340,6 +336,7 @@ export const atlasHeroLayout = (pageData, data) => {
340
336
  href="${resolve('@awesome.me/webawesome/dist/styles/webawesome.css', import.meta)}"
341
337
  />
342
338
  <link rel="stylesheet" href="${resolve('@rocket/js/layouts/atlasHero.css', import.meta)}" />
339
+ ${renderStylesheets(data.stylesheets)}
343
340
  `,
344
341
  },
345
342
  );
@@ -1,6 +1,7 @@
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
5
 
5
6
  /** @type {import('@rocket/js/types.js').Components} */
6
7
  export const atlasNotFoundComponents = {};
@@ -61,9 +62,10 @@ export const atlasNotFoundLayout = (pageData, data) => {
61
62
  {
62
63
  menu: false,
63
64
  headerContent: html`<link
64
- rel="stylesheet"
65
- href="${resolve('@rocket/js/layouts/atlasNotFound.css', import.meta)}"
66
- />`,
65
+ rel="stylesheet"
66
+ href="${resolve('@rocket/js/layouts/atlasNotFound.css', import.meta)}"
67
+ />
68
+ ${renderStylesheets(data.stylesheets)}`,
67
69
  },
68
70
  );
69
71
  };
@@ -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
  },