Compare commits

..

19 Commits

Author SHA1 Message Date
github-actions[bot]
0ca2bc6205 Version Packages 2022-08-23 11:52:54 +02:00
Thomas Allmer
e53e0ebd6d chore: beta announcement tweets/blog 2022-08-23 11:30:32 +02:00
Thomas Allmer
87c10ec1d3 feat(launch): work without JS if Declarative Shadow Dom is supported 2022-08-22 01:16:11 +02:00
Thomas Allmer
d7e461ca31 feat(launch): replace option logoSrc, logoAlt with logoSmall TemplateResult 2022-08-22 00:14:22 +02:00
Thomas Allmer
a12adf2cb5 fix(launch): add padding above slogan on homepage 2022-08-21 22:42:59 +02:00
Thomas Allmer
acf84416dc chore(examples): add --open to preview & run build 2022-08-21 22:41:11 +02:00
Thomas Allmer
a48dcd849b feat(cli): introduce "rocket lint" 2022-08-21 20:16:28 +02:00
Thomas Allmer
0ed3d6d0e9 fix(engine): support fragments when adjusting urls 2022-08-21 11:04:05 +02:00
Thomas Allmer
57ec19fecc chore(cli): remove "rocket init" command 2022-08-20 20:50:12 +02:00
github-actions[bot]
8b48fb9760 Version Packages 2022-08-20 20:25:47 +02:00
Thomas Allmer
39206a1738 feat(cli): start writes to _site-dev and only clears that folder 2022-08-20 20:23:22 +02:00
Thomas Allmer
cbfb0f91e2 feat(cli): add "rocket preview" command to verify a production build 2022-08-20 20:23:22 +02:00
github-actions[bot]
58692147e9 Version Packages 2022-08-19 19:19:37 +02:00
Thomas Allmer
8dedc56afa feat(cli): add start message with local and network url 2022-08-19 19:15:01 +02:00
Thomas Allmer
bcbfae332d chore: docs update public folder is in site/public 2022-08-18 21:27:46 +02:00
Thomas Allmer
0fae0037d8 chore: set default file path to site/pages in getting started 2022-08-18 20:39:48 +02:00
Thomas Allmer
390335da18 fix(engine): reparse html if pageTree requests a second pass 2022-08-17 16:51:54 +02:00
Thomas Allmer
6d2f469d26 chore: nice open graph text for tools 2022-08-16 11:37:33 +02:00
“Nirmal
94a6f54585 docs: correct path of examples link. 2022-08-16 09:07:35 +02:00
102 changed files with 1699 additions and 973 deletions

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "npm start", "dev": "npm start",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -12,7 +12,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "npm start", "dev": "npm start",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,8 +1,8 @@
{ {
"title": "Example Blog", "title": "Example Blog",
"h1": "\n \n ", "h1": "My Blog",
"name": "Example Blog", "name": "Example Blog",
"menuLinkText": "\n \n ", "menuLinkText": "My Blog",
"url": "/", "url": "/",
"outputRelativeFilePath": "index.html", "outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js", "sourceRelativeFilePath": "index.rocket.js",
@@ -20,7 +20,7 @@
"children": [ "children": [
{ {
"title": "", "title": "",
"h1": "\n \n ", "h1": "My Blog",
"headlinesWithId": [ "headlinesWithId": [
{ {
"text": "About", "text": "About",
@@ -28,8 +28,8 @@
"level": 1 "level": 1
} }
], ],
"name": "\n \n ", "name": "My Blog",
"menuLinkText": "\n \n ", "menuLinkText": "My Blog",
"url": "/about/", "url": "/about/",
"outputRelativeFilePath": "about/index.html", "outputRelativeFilePath": "about/index.html",
"sourceRelativeFilePath": "about.rocket.md", "sourceRelativeFilePath": "about.rocket.md",
@@ -60,9 +60,9 @@
"children": [ "children": [
{ {
"title": "Hello world!", "title": "Hello world!",
"h1": "\n \n ", "h1": "My Blog",
"name": "Hello world!", "name": "Hello world!",
"menuLinkText": "\n \n ", "menuLinkText": "My Blog",
"url": "/blog/hello-world/", "url": "/blog/hello-world/",
"outputRelativeFilePath": "blog/hello-world/index.html", "outputRelativeFilePath": "blog/hello-world/index.html",
"sourceRelativeFilePath": "blog/hello-world.rocket.md", "sourceRelativeFilePath": "blog/hello-world.rocket.md",
@@ -82,9 +82,9 @@
}, },
{ {
"title": "With Image!", "title": "With Image!",
"h1": "\n \n ", "h1": "My Blog",
"name": "With Image!", "name": "With Image!",
"menuLinkText": "\n \n ", "menuLinkText": "My Blog",
"url": "/blog/with-image/", "url": "/blog/with-image/",
"outputRelativeFilePath": "blog/with-image/index.html", "outputRelativeFilePath": "blog/with-image/index.html",
"sourceRelativeFilePath": "blog/with-image.rocket.md", "sourceRelativeFilePath": "blog/with-image.rocket.md",

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "npm start", "dev": "npm start",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "rocket start --open", "dev": "rocket start --open",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "npm start", "dev": "npm start",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -0,0 +1,24 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'aliens.rocket.md';
import { layout, components } from './recursive.data.js';
export { layout, components };
export async function registerCustomElements() {
// server-only components
// prettier-ignore
customElements.define('rocket-content-area', await import('@rocket/components/content-area.js').then(m => m.RocketContentArea));
// prettier-ignore
customElements.define('rocket-header-scroll-menu', await import('@rocket/components/header-scroll-menu.js').then(m => m.RocketHeaderScrollMenu));
// prettier-ignore
customElements.define('rocket-columns', await import('@rocket/components/columns.js').then(m => m.RocketColumns));
}
/* END - Rocket auto generated - do not touch */
export const title = 'Aliens';
```
Vastness is bearable only through love Cambrian explosion a still more glorious dawn awaits Euclid consciousness extraordinary claims require extraordinary evidence. Realm of the galaxies invent the universe culture made in the interiors of collapsing stars Drake Equation with pretty stories for which there's little good evidence. Bits of moving fluff preserve and cherish that pale blue dot shores of the cosmic ocean the ash of stellar alchemy brain is the seed of intelligence courage of our questions.
With pretty stories for which there's little good evidence not a sunrise but a galaxyrise rich in heavy atoms consciousness the sky calls to us rings of Uranus. Shores of the cosmic ocean shores of the cosmic ocean a very small stage in a vast cosmic arena of brilliant syntheses laws of physics muse about? Invent the universe finite but unbounded extraordinary claims require extraordinary evidence made in the interiors of collapsing stars muse about invent the universe.
The ash of stellar alchemy Cambrian explosion how far away kindling the energy hidden in matter cosmic ocean descended from astronomers. The carbon in our apple pies a very small stage in a vast cosmic arena brain is the seed of intelligence the only home we've ever known the carbon in our apple pies the carbon in our apple pies? Corpus callosum muse about citizens of distant epochs finite but unbounded extraordinary claims require extraordinary evidence finite but unbounded and billions upon billions upon billions upon billions upon billions upon billions upon billions.

View File

@@ -0,0 +1,30 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'humans.rocket.md';
import { layout, components } from './recursive.data.js';
export { layout, components };
export async function registerCustomElements() {
// server-only components
// prettier-ignore
customElements.define('rocket-content-area', await import('@rocket/components/content-area.js').then(m => m.RocketContentArea));
// prettier-ignore
customElements.define('rocket-header-scroll-menu', await import('@rocket/components/header-scroll-menu.js').then(m => m.RocketHeaderScrollMenu));
// prettier-ignore
customElements.define('rocket-columns', await import('@rocket/components/columns.js').then(m => m.RocketColumns));
}
/* END - Rocket auto generated - do not touch */
export const title = 'Humans';
```
Astonishment dispassionate extraterrestrial observer Drake Equation radio telescope Hypatia of brilliant syntheses. Vastness is bearable only through love Vangelis a mote of dust suspended in a sunbeam rings of Uranus vanquish the impossible rings of Uranus? Emerged into consciousness a billion trillion laws of physics emerged into consciousness bits of moving fluff as a patch of light. Sea of Tranquility something incredible is waiting to be known permanence of the stars something incredible is waiting to be known network of wormholes invent the universe.
Orion's sword the carbon in our apple pies tesseract made in the interiors of collapsing stars take root and flourish Jean-François Champollion. The ash of stellar alchemy rich in heavy atoms tingling of the spine invent the universe shores of the cosmic ocean star stuff harvesting star light. Bits of moving fluff courage of our questions as a patch of light preserve and cherish that pale blue dot citizens of distant epochs concept of the number one.
Two ghostly white figures in coveralls and helmets are softly dancing dispassionate extraterrestrial observer tesseract finite but unbounded the sky calls to us shores of the cosmic ocean? Muse about tingling of the spine stirred by starlight muse about not a sunrise but a galaxyrise venture. Concept of the number one are creatures of the cosmos something incredible is waiting to be known courage of our questions the only home we've ever known rich in heavy atoms.
Across the centuries light years billions upon billions bits of moving fluff permanence of the stars consciousness. Shores of the cosmic ocean realm of the galaxies vastness is bearable only through love Sea of Tranquility network of wormholes are creatures of the cosmos? Extraordinary claims require extraordinary evidence finite but unbounded made in the interiors of collapsing stars network of wormholes finite but unbounded radio telescope. Invent the universe from which we spring with pretty stories for which there's little good evidence Euclid inconspicuous motes of rock and gas something incredible is waiting to be known.
Birth dream of the mind's eye prime number at the edge of forever a billion trillion permanence of the stars? Vanquish the impossible two ghostly white figures in coveralls and helmets are softly dancing vastness is bearable only through love citizens of distant epochs something incredible is waiting to be known hydrogen atoms. Two ghostly white figures in coveralls and helmets are softly dancing shores of the cosmic ocean the ash of stellar alchemy Apollonius of Perga vastness is bearable only through love network of wormholes.
Gathered by gravity Sea of Tranquility galaxies astonishment hearts of the stars venture. Citizens of distant epochs the carbon in our apple pies encyclopaedia galactica birth brain is the seed of intelligence permanence of the stars? A still more glorious dawn awaits concept of the number one a mote of dust suspended in a sunbeam dream of the mind's eye Euclid a very small stage in a vast cosmic arena and billions upon billions upon billions upon billions upon billions upon billions upon billions.

View File

@@ -128,30 +128,32 @@ export const needsLoader = true;
<rocket-columns> <rocket-columns>
<rocket-card> <rocket-card>
<h4 slot="title">No astrophysicist</h4> <h4 slot="title">Life</h4>
<p> <p>
would deny the possibility of life. I think we're not creative enough to imagine what life As a scientist, I want to go to Mars and back to asteroids and the Moon because I'm a
would be like on another planet. Show me a dead alien. Better yet, show me a live one! scientist. But I can tell you, I'm not so naive a scientist to think that the nation might
not have geopolitical reasons for going into space.
</p> </p>
<a slot="cta" class="cta" href="./life.rocket.html">Details</a> <a slot="cta" class="cta" href="./life.rocket.md">Details</a>
</rocket-card> </rocket-card>
<rocket-card> <rocket-card>
<h4 slot="title">No astrophysicist</h4> <h4 slot="title">Aliens</h4>
<p> <p>
would deny the possibility of life. I think we're not creative enough to imagine what life Vastness is bearable only through love Cambrian explosion a still more glorious dawn awaits
would be like on another planet. Show me a dead alien. Better yet, show me a live one! Euclid consciousness extraordinary claims require extraordinary evidence.
</p> </p>
<a slot="cta" class="cta" href="./life.rocket.html">Details</a> <a slot="cta" class="cta" href="./aliens.rocket.md">Details</a>
</rocket-card> </rocket-card>
<rocket-card> <rocket-card>
<h4 slot="title">No astrophysicist</h4> <h4 slot="title">Humans</h4>
<p> <p>
would deny the possibility of life. I think we're not creative enough to imagine what life Astonishment dispassionate extraterrestrial observer Drake Equation radio telescope Hypatia
would be like on another planet. Show me a dead alien. Better yet, show me a live one! of brilliant syntheses. Vastness is bearable only through love Vangelis a mote of dust
suspended in a sunbeam rings of Uranus vanquish the impossible rings of Uranus?
</p> </p>
<a slot="cta" class="cta" href="./life.rocket.html">Details</a> <a slot="cta" class="cta" href="./humans.rocket.md">Details</a>
</rocket-card> </rocket-card>
</rocket-columns> </rocket-columns>
</rocket-content-area> </rocket-content-area>

View File

@@ -1,24 +1,24 @@
{ {
"title": "Spark Rocket Example Template", "title": "Spark Rocket Example Template",
"h1": "\n \n \n ", "h1": "Home",
"headlinesWithId": [ "headlinesWithId": [
{ {
"text": "Home", "text": "Home",
"id": "home", "id": "home",
"level": 1, "level": 1,
"rawText": "\n \n \n " "rawText": "@keyframes textAnimIn {\n 0% {\n transform: translate3d(0, -120%, 0);\n }\n\n 100% {\n transform: translate3d(0, 0%, 0);\n }\n }\n\n @keyframes textAnimOut {\n 0% {\n transform: translate3d(0, 0%, 0);\n }\n\n 50% {\n transform: translate3d(0, -20%, 0);\n }\n\n 100% {\n transform: translate3d(0, 120%, 0);\n }\n }\n\n p {\n position: absolute;\n top: 0;\n margin: 0;\n transform: translate3d(0, -120%, 0);\n }\n\n .anim-static {\n transform: translate3d(0, 0, 0);\n }\n\n .anim-in {\n animation: textAnimIn 0.6s 0.3s forwards;\n }\n\n .anim-out {\n animation: textAnimOut 0.6s forwards;\n }\n\n :host {\n position: block;\n position: relative;\n overflow: hidden;\n display: block;\n height: 1.5em;\n }\n div {\n display: flex;\n max-width: 960px;\n margin: 0 auto;\n }\n slot {\n display: block;\n flex: 1;\n }\n \n Welcome\n \n Hello\n \n Aloa\n lit-part-->\n to the world of science."
}, },
{ {
"text": "Credit", "text": "Credit",
"id": "credit", "id": "credit",
"level": 2, "level": 2,
"rawText": "\n People credit me for \n \n " "rawText": "People credit me for \n making the universe interesting,"
}, },
{ {
"text": "Knowledge", "text": "Knowledge",
"id": "knowledge", "id": "knowledge",
"level": 2, "level": 2,
"rawText": "\n I've known from long ago \n \n " "rawText": "I've known from long ago \n that the universe was calling me"
}, },
{ {
"text": "Sign Up", "text": "Sign Up",
@@ -30,7 +30,7 @@
"text": "Say", "text": "Say",
"id": "say", "id": "say",
"level": 2, "level": 2,
"rawText": "\n What others \n about us?\n " "rawText": "What others say \n about us?"
}, },
{ {
"text": "FAQ", "text": "FAQ",
@@ -39,7 +39,7 @@
} }
], ],
"name": "Spark Rocket Example Template", "name": "Spark Rocket Example Template",
"menuLinkText": "\n \n \n ", "menuLinkText": "Home",
"url": "/", "url": "/",
"outputRelativeFilePath": "index.html", "outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.html", "sourceRelativeFilePath": "index.rocket.html",
@@ -69,6 +69,70 @@
"description": "Everyone can code a website!", "description": "Everyone can code a website!",
"needsLoader": true, "needsLoader": true,
"children": [ "children": [
{
"title": "Aliens",
"h1": "Aliens",
"name": "Aliens",
"menuLinkText": "Aliens",
"url": "/aliens/",
"outputRelativeFilePath": "aliens/index.html",
"sourceRelativeFilePath": "aliens.rocket.md",
"level": 1,
"components": {
"rocket-columns": "@rocket/components/columns.js::RocketColumns",
"rocket-card": "@rocket/components/card.js::RocketCard",
"rocket-content-area": "@rocket/components/content-area.js::RocketContentArea",
"rocket-details": "@rocket/components/details.js::RocketDetails",
"rocket-drawer": "@rocket/components/drawer.js::RocketDrawer",
"rocket-feature-small": "@rocket/components/feature-small.js::RocketFeatureSmall",
"rocket-header": "@rocket/components/header.js::RocketHeader",
"rocket-header-scroll-menu": "@rocket/components/header-scroll-menu.js::RocketHeaderScrollMenu",
"rocket-icon": "@rocket/components/icon.js::RocketIcon",
"rocket-icon-card": "@rocket/components/icon-card.js::RocketIconCard",
"rocket-main": "@rocket/components/main.js::RocketMain",
"rocket-main-docs": "@rocket/components/main-docs.js::RocketMainDocs",
"rocket-opengraph-overview": "@rocket/components/open-graph-overview.js::RocketOpenGraphOverview",
"rocket-rotating-text": "@rocket/components/rotating-text.js::RocketRotatingText",
"rocket-social-link": "@rocket/components/social-link.js::RocketSocialLink",
"rocket-testimonial-small": "@rocket/components/testimonial-small.js::RocketTestimonialSmall",
"inline-notification": "@rocket/components/inline-notification.js::InlineNotification",
"permanent-notification": "@rocket/components/permanent-notification.js::PermanentNotification",
"block-blue": "@rocket/spark/block-blue.js::BlockBlue",
"block-features": "@rocket/spark/block-features.js::BlockFeatures"
}
},
{
"title": "Humans",
"h1": "Humans",
"name": "Humans",
"menuLinkText": "Humans",
"url": "/humans/",
"outputRelativeFilePath": "humans/index.html",
"sourceRelativeFilePath": "humans.rocket.md",
"level": 1,
"components": {
"rocket-columns": "@rocket/components/columns.js::RocketColumns",
"rocket-card": "@rocket/components/card.js::RocketCard",
"rocket-content-area": "@rocket/components/content-area.js::RocketContentArea",
"rocket-details": "@rocket/components/details.js::RocketDetails",
"rocket-drawer": "@rocket/components/drawer.js::RocketDrawer",
"rocket-feature-small": "@rocket/components/feature-small.js::RocketFeatureSmall",
"rocket-header": "@rocket/components/header.js::RocketHeader",
"rocket-header-scroll-menu": "@rocket/components/header-scroll-menu.js::RocketHeaderScrollMenu",
"rocket-icon": "@rocket/components/icon.js::RocketIcon",
"rocket-icon-card": "@rocket/components/icon-card.js::RocketIconCard",
"rocket-main": "@rocket/components/main.js::RocketMain",
"rocket-main-docs": "@rocket/components/main-docs.js::RocketMainDocs",
"rocket-opengraph-overview": "@rocket/components/open-graph-overview.js::RocketOpenGraphOverview",
"rocket-rotating-text": "@rocket/components/rotating-text.js::RocketRotatingText",
"rocket-social-link": "@rocket/components/social-link.js::RocketSocialLink",
"rocket-testimonial-small": "@rocket/components/testimonial-small.js::RocketTestimonialSmall",
"inline-notification": "@rocket/components/inline-notification.js::InlineNotification",
"permanent-notification": "@rocket/components/permanent-notification.js::PermanentNotification",
"block-blue": "@rocket/spark/block-blue.js::BlockBlue",
"block-features": "@rocket/spark/block-features.js::BlockFeatures"
}
},
{ {
"title": "Life", "title": "Life",
"h1": "Life", "h1": "Life",

View File

@@ -21,7 +21,7 @@ export const layoutData = {
Modern Web<br /> Modern Web<br />
Internet 12<br /> Internet 12<br />
0000 Web<br /> 0000 Web<br />
<a href="#">office@modern-web.dev</a><br /> <a href="#">hello@modern-web.dev</a><br />
<a href="#">0000 / 11223344</a> <a href="#">0000 / 11223344</a>
</div> </div>

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "rocket build", "build": "rocket build",
"dev": "npm start", "dev": "npm start",
"preview": "rocket preview", "preview": "rocket preview --open",
"start": "NODE_DEBUG=engine:rendering rocket start --open" "start": "NODE_DEBUG=engine:rendering rocket start --open"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -12,6 +12,10 @@ export async function registerCustomElements() {
'rocket-header', 'rocket-header',
await import('@rocket/components/header.js').then(m => m.RocketHeader), await import('@rocket/components/header.js').then(m => m.RocketHeader),
); );
customElements.define(
'launch-blog-preview',
await import('@rocket/launch/blog-preview.js').then(m => m.LaunchBlogPreview),
);
customElements.define( customElements.define(
'launch-blog-overview', 'launch-blog-overview',
await import('@rocket/launch/blog-overview.js').then(m => m.LaunchBlogOverview), await import('@rocket/launch/blog-overview.js').then(m => m.LaunchBlogOverview),

View File

@@ -1,8 +1,8 @@
{ {
"title": "Welcome to Rocket", "title": "Welcome to Rocket",
"h1": "\n \n ", "h1": "",
"name": "\n \n ", "name": "Welcome to Rocket",
"menuLinkText": "\n \n ", "menuLinkText": "Welcome to Rocket",
"url": "/", "url": "/",
"outputRelativeFilePath": "index.html", "outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js", "sourceRelativeFilePath": "index.rocket.js",

View File

@@ -35,8 +35,9 @@
"setup:ts-configs": "node scripts/generate-ts-configs.mjs", "setup:ts-configs": "node scripts/generate-ts-configs.mjs",
"start:experimental": "NODE_DEBUG=engine:rendering node --no-warnings --experimental-loader ./packages/engine/src/litCssLoader.js packages/cli/src/cli.js start --open", "start:experimental": "NODE_DEBUG=engine:rendering node --no-warnings --experimental-loader ./packages/engine/src/litCssLoader.js packages/cli/src/cli.js start --open",
"start": "NODE_DEBUG=engine:rendering node --trace-warnings packages/cli/src/cli.js start --open", "start": "NODE_DEBUG=engine:rendering node --trace-warnings packages/cli/src/cli.js start --open",
"preview": "node packages/cli/src/cli.js preview --open",
"test": "yarn test:node && yarn test:web", "test": "yarn test:node && yarn test:web",
"test:integration": "playwright test packages/*/test-node/*.spec.js", "test:integration": "playwright test packages/*/test-node/*.spec.js --retries=3",
"test:node": "yarn test:unit && yarn test:integration", "test:node": "yarn test:unit && yarn test:integration",
"test:unit": "node --trace-warnings ./node_modules/.bin/mocha --require ./scripts/testMochaGlobalHooks.js \"packages/*/test-node/**/*.test.{ts,js,mjs,cjs}\" -- --timeout 8000 --reporter dot --exit", "test:unit": "node --trace-warnings ./node_modules/.bin/mocha --require ./scripts/testMochaGlobalHooks.js \"packages/*/test-node/**/*.test.{ts,js,mjs,cjs}\" -- --timeout 8000 --reporter dot --exit",
"test:web": "web-test-runner", "test:web": "web-test-runner",

View File

@@ -1,5 +1,49 @@
# @rocket/cli # @rocket/cli
## 0.20.3
### Patch Changes
- a48dcd8: Introducing `rocket lint` to verify if all your links are correct.
There are two modes:
```bash
# check existing production build in _site (need to execute "rocket build" before)
rocket lint
# run a fast html only build and then check it
rocket lint --build-html
```
- Updated dependencies [0ed3d6d]
- @rocket/engine@0.2.7
## 0.20.2
### Patch Changes
- 39206a1: `rocket start` now outputs to `_site-dev` instead of `_site`.
- 39206a1: `rocket start` clears only its output folder (defaults to `_site-dev`)
- cbfb0f9: Add `rocket preview` command to enable fast checking of the production build
## 0.20.1
### Patch Changes
- 8dedc56: Add start message for `rocket start`
```
🚀 Rocket Engine v0.2.5
🚧 Local: http://localhost:8000/
🌐 Network: http://xxx.xxx.xxx.xxx:8000/
```
- Updated dependencies [8dedc56]
- Updated dependencies [390335d]
- @rocket/engine@0.2.6
## 0.20.0 ## 0.20.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/cli", "name": "@rocket/cli",
"version": "0.20.0", "version": "0.20.3",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -53,16 +53,18 @@
], ],
"dependencies": { "dependencies": {
"@rocket/building-rollup": "^0.4.0", "@rocket/building-rollup": "^0.4.0",
"@rocket/engine": "^0.2.0", "@rocket/engine": "^0.2.7",
"@web/rollup-plugin-copy": "^0.3.0", "check-html-links": "^0.2.3",
"colorette": "^2.0.16", "colorette": "^2.0.16",
"commander": "^9.0.0", "commander": "^9.0.0",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"ip": "^1.1.5",
"plugins-manager": "^0.3.0", "plugins-manager": "^0.3.0",
"puppeteer": "^13.0.0" "puppeteer": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/ip": "^1.1.0",
"koa-proxy": "^1.0.0-alpha.3" "koa-proxy": "^1.0.0-alpha.3"
}, },
"types": "./dist-types/src/index.d.ts", "types": "./dist-types/src/index.d.ts",

View File

@@ -1,78 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import { Engine } from '@rocket/engine/server';
import { gatherFiles } from '@rocket/engine';
import { fromRollup } from '@web/dev-server-rollup';
import { rollup } from 'rollup';
import path from 'path'; import path from 'path';
import { rollupPluginHTML } from '@web/rollup-plugin-html';
import { createMpaConfig, createServiceWorkerConfig } from '@rocket/building-rollup';
import { adjustPluginOptions } from 'plugins-manager';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { readFile, unlink, writeFile } from 'fs/promises'; import { readFile, writeFile } from 'fs/promises';
import puppeteer from 'puppeteer'; import { buildHtml } from './build/buildHtml.js';
import { buildOpenGraphImages } from './build/buildOpenGraphImages.js';
/** import { buildJavaScriptOptimizedOutput } from './build/buildJavaScriptOptimizedOutput.js';
* @param {object} config
*/
async function buildAndWrite(config) {
const bundle = await rollup(config);
if (Array.isArray(config.output)) {
await bundle.write(config.output[0]);
await bundle.write(config.output[1]);
} else {
await bundle.write(config.output);
}
}
async function productionBuild(config) {
const defaultSetupPlugins = [];
if (config.pathPrefix) {
defaultSetupPlugins.push(
adjustPluginOptions(rollupPluginHTML, { absolutePathPrefix: config.pathPrefix }),
);
}
const mpaConfig = createMpaConfig({
input: '**/*.html',
output: {
dir: config.outputDir,
},
// custom
rootDir: path.resolve(config.outputDevDir),
absoluteBaseUrl: config.absoluteBaseUrl,
setupPlugins: [
...defaultSetupPlugins,
...config.setupDevServerAndBuildPlugins,
...config.setupBuildPlugins,
],
});
const finalConfig =
typeof config.adjustBuildOptions === 'function'
? config.adjustBuildOptions(mpaConfig)
: mpaConfig;
await buildAndWrite(finalConfig);
const { serviceWorkerSourcePath } = config;
if (existsSync(serviceWorkerSourcePath)) {
const serviceWorkerConfig = createServiceWorkerConfig({
input: serviceWorkerSourcePath,
output: {
file: path.join(path.resolve(config.outputDir), config.serviceWorkerName),
},
});
await buildAndWrite(serviceWorkerConfig);
}
}
export class RocketBuild { export class RocketBuild {
/**
* @param {import('commander').Command} program
* @param {import('./RocketCli.js').RocketCli} cli
*/
async setupCommand(program, cli) { async setupCommand(program, cli) {
this.cli = cli; this.cli = cli;
@@ -87,30 +27,30 @@ export class RocketBuild {
} }
async build() { async build() {
await this.cli.events.dispatchEventDone('build-start'); if (!this.cli) {
return;
this.engine = new Engine(); }
this.engine.setOptions({ // for typescript as `this.cli.options.outputDir` supports other inputs as well
docsDir: this.cli.options.inputDir, // but the cli will normalize it to a string before calling plugins
outputDir: this.cli.options.outputDevDir, if (typeof this.cli.options.outputDir !== 'string') {
setupPlugins: this.cli.options.setupEnginePlugins, return;
longFileHeaderWidth: this.cli.options.longFileHeaderWidth,
longFileHeaderComment: this.cli.options.longFileHeaderComment,
renderMode: 'production',
clearOutputDir: this.cli.options.clearOutputDir,
});
console.log('Engine building...');
await this.engine.build({ autoStop: this.cli.options.buildAutoStop });
if (this.cli.options.buildOpenGraphImages) {
console.log('Generating Open Graph Images...');
await this.buildOpenGraphImages();
} }
if (this.cli.options.buildOptimize) { await this.cli.events.dispatchEventDone('build-start');
// 1. build html
this.engine = await buildHtml(this.cli);
// 2. build open graph images
if (this.cli.options.buildOpenGraphImages) {
console.log('Generating Open Graph Images...');
await buildOpenGraphImages(this.cli);
}
// 3. build optimized output
if (this.cli.options.buildOptimize && this.engine) {
console.log('Optimize Production Build...'); console.log('Optimize Production Build...');
await productionBuild(this.cli.options); await buildJavaScriptOptimizedOutput(this.cli, this.engine);
await this.engine.copyPublicFilesTo(this.cli.options.outputDir);
} }
// hackfix 404.html by making all asset urls absolute (rollup always makes them relative) which will break if netlify serves the content form a different url // hackfix 404.html by making all asset urls absolute (rollup always makes them relative) which will break if netlify serves the content form a different url
@@ -128,87 +68,4 @@ export class RocketBuild {
await this.cli.events.dispatchEventDone('build-end'); await this.cli.events.dispatchEventDone('build-end');
} }
async buildOpenGraphImages() {
const openGraphFiles = await gatherFiles(this.cli.options.outputDevDir, {
fileEndings: ['.opengraph.html'],
});
if (openGraphFiles.length === 0) {
return;
}
// TODO: enable URL support in the Engine and remove this "workaround"
if (
typeof this.cli.options.inputDir !== 'string' ||
typeof this.cli.options.outputDevDir !== 'string'
) {
return;
}
const withWrap = this.cli.options.setupDevServerAndBuildPlugins
? this.cli.options.setupDevServerAndBuildPlugins.map(modFunction => {
modFunction.wrapPlugin = fromRollup;
return modFunction;
})
: [];
this.engine = new Engine();
this.engine.setOptions({
docsDir: this.cli.options.inputDir,
outputDir: this.cli.options.outputDevDir,
setupPlugins: this.cli.options.setupEnginePlugins,
open: false,
clearOutputDir: false,
adjustDevServerOptions: this.cli.options.adjustDevServerOptions,
setupDevServerMiddleware: this.cli.options.setupDevServerMiddleware,
setupDevServerPlugins: [...this.cli.options.setupDevServerPlugins, ...withWrap],
});
try {
await this.engine.start();
const browser = await puppeteer.launch();
const page = await browser.newPage();
// In 2022 Twitter & Facebook recommend a size of 1200x628 - we capture with 2 dpr for retina displays
await page.setViewport({
width: 1200,
height: 628,
deviceScaleFactor: 2,
});
for (const openGraphFile of openGraphFiles) {
const relUrl = path.relative(this.cli.options.outputDevDir, openGraphFile);
const imagePath = openGraphFile.replace('.opengraph.html', '.opengraph.png');
const htmlPath = openGraphFile.replace('.opengraph.html', '.html');
const relImageUrl = path.basename(imagePath);
let htmlString = await readFile(htmlPath, 'utf8');
if (!htmlString.includes('<meta property="og:image"')) {
if (htmlString.includes('</head>')) {
htmlString = htmlString.replace(
'</head>',
[
' <meta property="og:image:width" content="2400">',
' <meta property="og:image:height" content="1256">',
` <meta property="og:image" content="./${relImageUrl}">`,
' </head>',
].join('\n'),
);
}
}
const url = `http://localhost:${this.engine.devServer.config.port}/${relUrl}`;
await page.goto(url, { waitUntil: 'networkidle0' });
await page.screenshot({ path: imagePath });
await unlink(openGraphFile);
await writeFile(htmlPath, htmlString);
}
await browser.close();
await this.engine.stop();
} catch (e) {
console.log('Could not start dev server to generate open graph images');
console.error(e);
}
}
} }

View File

@@ -2,8 +2,9 @@
import { Command } from 'commander'; import { Command } from 'commander';
import { RocketStart } from './RocketStart.js'; import { RocketStart } from './RocketStart.js';
import { RocketBuild } from './RocketBuild.js'; import { RocketBuild } from './RocketBuild.js';
import { RocketInit } from './RocketInit.js'; import { RocketLint } from './RocketLint.js';
import { RocketUpgrade } from './RocketUpgrade.js'; import { RocketUpgrade } from './RocketUpgrade.js';
import { RocketPreview } from './RocketPreview.js';
// import { ignore } from './images/ignore.js'; // import { ignore } from './images/ignore.js';
import path from 'path'; import path from 'path';
@@ -34,7 +35,7 @@ export class RocketCli {
open: false, open: false,
cwd: process.cwd(), cwd: process.cwd(),
inputDir: 'FALLBACK', inputDir: 'FALLBACK',
outputDir: '_site', outputDir: 'FALLBACK',
outputDevDir: '_site-dev', outputDevDir: '_site-dev',
serviceWorkerSourcePath: '', serviceWorkerSourcePath: '',
@@ -53,6 +54,10 @@ export class RocketCli {
absoluteBaseUrl: '', absoluteBaseUrl: '',
clearOutputDir: true, clearOutputDir: true,
lint: {
buildHtml: false,
},
// /** @type {{[key: string]: ImagePreset}} */ // /** @type {{[key: string]: ImagePreset}} */
// imagePresets: { // imagePresets: {
// responsive: { // responsive: {
@@ -93,6 +98,9 @@ export class RocketCli {
if (this.options.inputDir === 'FALLBACK') { if (this.options.inputDir === 'FALLBACK') {
this.options.inputDir = path.join(this.options.cwd, 'site', 'pages'); this.options.inputDir = path.join(this.options.cwd, 'site', 'pages');
} }
if (this.options.outputDir === 'FALLBACK') {
this.options.outputDir = path.join(this.options.cwd, '_site');
}
if (this.options.inputDir instanceof URL) { if (this.options.inputDir instanceof URL) {
this.options.inputDir = this.options.inputDir.pathname; this.options.inputDir = this.options.inputDir.pathname;
} }
@@ -122,7 +130,6 @@ export class RocketCli {
} }
async prepare() { async prepare() {
await this.clearOutputDirs();
if (!this.options.presets) { if (!this.options.presets) {
return; return;
} }
@@ -177,9 +184,9 @@ export class RocketCli {
let pluginsMeta = [ let pluginsMeta = [
{ plugin: RocketStart, options: {} }, { plugin: RocketStart, options: {} },
{ plugin: RocketBuild, options: {} }, { plugin: RocketBuild, options: {} },
{ plugin: RocketInit, options: {} }, { plugin: RocketLint, options: {} },
// { plugin: RocketLint },
{ plugin: RocketUpgrade, options: {} }, { plugin: RocketUpgrade, options: {} },
{ plugin: RocketPreview, options: {} },
]; ];
if (Array.isArray(this.options.setupCliPlugins)) { if (Array.isArray(this.options.setupCliPlugins)) {
@@ -230,10 +237,13 @@ export class RocketCli {
} }
} }
async clearOutputDirs() { async clearOutputDir() {
if (this.options.outputDir && existsSync(this.options.outputDir)) { if (this.options.outputDir && existsSync(this.options.outputDir)) {
await rm(this.options.outputDir, { recursive: true, force: true }); await rm(this.options.outputDir, { recursive: true, force: true });
} }
}
async clearOutputDevDir() {
if (this.options.outputDevDir && existsSync(this.options.outputDevDir)) { if (this.options.outputDevDir && existsSync(this.options.outputDevDir)) {
await rm(this.options.outputDevDir, { recursive: true, force: true }); await rm(this.options.outputDevDir, { recursive: true, force: true });
} }

View File

@@ -1,86 +0,0 @@
import path from 'path';
import fs from 'fs-extra';
import { fileURLToPath } from 'url';
export class RocketInit {
/**
* @param {import('commander').Command} program
* @param {import('./RocketCli.js').RocketCli} cli
*/
async setupCommand(program, cli) {
this.cli = cli;
program
.command('init')
.option(
'-o, --output-dir <path>',
'path to where to put the source files [default to current directory]',
)
.action(async cliOptions => {
// cli.setOptions(cliOptions);
this.outputDir = cliOptions.outputDir ? path.resolve(cliOptions.outputDir) : process.cwd();
await this.init();
});
}
async init() {
if (!this.outputDir) {
return;
}
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const initFilesDir = path.join(moduleDir, 'init-files');
const packageJsonPath = path.join(this.outputDir, 'package.json');
if (!(await fs.pathExists(packageJsonPath))) {
await fs.writeJson(packageJsonPath, {});
}
await fs.copy(initFilesDir, this.outputDir, {
errorOnExist: true,
filter: file => !(file.endsWith('_gitignore') || file.endsWith('README.md')),
});
const packageJson = await fs.readJson(packageJsonPath);
await fs.writeJson(
packageJsonPath,
{
...packageJson,
type: 'module',
scripts: {
...packageJson.scripts,
start: 'NODE_DEBUG=engine:rendering rocket start --open',
build: 'NODE_DEBUG=engine:rendering rocket build',
},
imports: {
'#images/*': './docs/__shared/*',
},
exports: {
'.': './src/index.js',
},
},
{ spaces: 2 },
);
const gitignorePath = path.join(this.outputDir, '.gitignore');
await fs.ensureFile(gitignorePath);
await fs.appendFile(
gitignorePath,
await fs.readFile(path.join(initFilesDir, '_gitignore'), 'utf8'),
);
const readmePath = path.join(this.outputDir, 'README.md');
await fs.ensureFile(readmePath);
await fs.appendFile(
readmePath,
await fs.readFile(path.join(initFilesDir, '_gitignore'), 'utf8'),
);
console.log('All files have been created 🎉');
console.log('Start developing by running `npm start`');
process.exit(0);
}
}

View File

@@ -1,81 +1,85 @@
// /* eslint-disable */ /* eslint-disable @typescript-eslint/ban-ts-comment */
// // @ts-nocheck
// /** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */ // @ts-ignore
import { CheckHtmlLinksCli } from 'check-html-links';
import { bold, gray } from 'colorette';
import { existsSync } from 'fs';
import path from 'path';
import { buildHtml } from './build/buildHtml.js';
// import { CheckHtmlLinksCli } from 'check-html-links'; export class RocketLint {
options = {
buildHtml: false,
};
// export class RocketLint { /**
// static pluginName = 'RocketLint'; * @param {import('commander').Command} program
// commands = ['start', 'build', 'lint']; * @param {import('./RocketCli.js').RocketCli} cli
*/
async setupCommand(program, cli) {
this.cli = cli;
this.active = true;
// /** program
// * @param {RocketCliOptions} config .command('lint')
// */ .option('-i, --input-dir <path>', 'path to where to search for source files')
// setupCommand(config) { .option('-b, --build-html', 'do a quick html only build and then check links')
// if (config.command === 'lint') { .action(async options => {
// config.watch = false; const { cliOptions, ...lintOptions } = options;
// } cli.setOptions({
// return config; ...cliOptions,
// } lint: lintOptions,
});
this.options = { ...this.options, ...cli.options.lint };
cli.activePlugin = this;
// /** await this.lint();
// * @param {object} options });
// * @param {RocketCliOptions} options.config }
// * @param {any} options.argv
// */
// async setup({ config, argv, eleventy }) {
// this.__argv = argv;
// this.config = {
// lintInputDir: config.outputDevDir,
// lintExecutesEleventyBefore: true,
// ...config,
// };
// this.eleventy = eleventy;
// }
// async lintCommand() { async lint() {
// if (this.config.lintExecutesEleventyBefore) { if (!this.cli) {
// await this.eleventy.write(); return;
// // updated will trigger linting }
// } else {
// await this.__lint();
// }
// }
// async __lint() { // for typescript as `this.cli.options.outputDir` supports other inputs as well
// if (this.config?.pathPrefix) { // but the cli will normalize it to a string before calling plugins
// console.log('INFO: RocketLint currently does not support being used with a pathPrefix'); if (
// return; typeof this.cli.options.outputDevDir !== 'string' ||
// } typeof this.cli.options.outputDir !== 'string'
) {
return;
}
// const checkLinks = new CheckHtmlLinksCli(); if (this.options.buildHtml) {
// checkLinks.setOptions({ await buildHtml(this.cli);
// ...this.config.checkLinks, }
// rootDir: this.config.lintInputDir,
// printOnError: false,
// continueOnError: true,
// });
// const { errors, message } = await checkLinks.run(); const folderToCheck = this.options.buildHtml
// if (errors.length > 0) { ? this.cli.options.outputDevDir
// if (this.config.command === 'start') { : this.cli.options.outputDir;
// console.log(message);
// } else {
// throw new Error(message);
// }
// }
// }
// async postCommand() { const rootIndexHtml = path.join(folderToCheck, 'index.html');
// if (this.config.watch === false) { if (!existsSync(rootIndexHtml)) {
// await this.__lint(); console.log(`${bold(`👀 Linting Production Build`)}`);
// } console.log('');
// } console.log(` 🛑 No index.html found in the build directory ${gray(`${rootIndexHtml}`)}`);
console.log(' 🤔 Did you forget to run `rocket build` before?');
console.log('');
return;
}
// async updated() { // eslint-disable-next-line @typescript-eslint/no-unused-vars
// if (this.config.watch === true) { const { buildHtml: _drop, ...userCheckHtmlLinksOptions } = this.options;
// await this.__lint();
// } const checkLinks = new CheckHtmlLinksCli();
// } checkLinks.setOptions({
// } rootDir: folderToCheck,
printOnError: true,
continueOnError: false,
...userCheckHtmlLinksOptions,
});
await checkLinks.run();
}
}

View File

@@ -0,0 +1,79 @@
import { logPreviewMessage } from './preview/logPreviewMessage.js';
import { startDevServer } from '@web/dev-server';
import path from 'path';
import { existsSync } from 'fs';
import { gray, bold } from 'colorette';
export class RocketPreview {
/**
* @param {import('commander').Command} program
* @param {import('./RocketCli.js').RocketCli} cli
*/
async setupCommand(program, cli) {
this.cli = cli;
this.active = true;
program
.command('preview')
.option('-i, --input-dir <path>', 'path to the folder with the build html files')
.option('-o, --open', 'automatically open the browser')
.action(async cliOptions => {
cli.setOptions(cliOptions);
cli.activePlugin = this;
await this.preview();
});
}
async preview() {
if (!this.cli) {
return;
}
// for typescript as `this.cli.options.outputDir` supports other inputs as well
// but the cli will normalize it to a string before calling plugins
if (
typeof this.cli.options.inputDir !== 'string' ||
typeof this.cli.options.outputDir !== 'string'
) {
return;
}
const rootIndexHtml = path.join(this.cli.options.outputDir, 'index.html');
if (!existsSync(rootIndexHtml)) {
console.log(`${bold(`👀 Previewing Production Build`)}`);
console.log('');
console.log(` 🛑 No index.html found in the build directory ${gray(`${rootIndexHtml}`)}`);
console.log(' 🤔 Did you forget to run `rocket build` before?');
console.log('');
return;
}
/** @type {import('@web/dev-server').DevServerConfig} */
const config = {
open: this.cli.options.open,
rootDir: this.cli.options.outputDir,
clearTerminalOnReload: false,
};
try {
this.devServer = await startDevServer({
config,
logStartMessage: false,
readCliArgs: false,
readFileConfig: false,
// argv: this.__argv,
});
logPreviewMessage({ devServerOptions: this.devServer.config }, console);
} catch (e) {
console.log('🛑 Starting preview server failed');
console.error(e);
}
}
async stop() {
if (this.devServer) {
await this.devServer.stop();
}
}
}

View File

@@ -1,6 +1,7 @@
import { fromRollup } from '@web/dev-server-rollup'; import { fromRollup } from '@web/dev-server-rollup';
import { Engine } from '@rocket/engine/server'; import { Engine } from '@rocket/engine/server';
import { logStartMessage } from './start/logStartMessage.js';
export class RocketStart { export class RocketStart {
/** @type {Engine | undefined} */ /** @type {Engine | undefined} */
@@ -30,11 +31,12 @@ export class RocketStart {
if (!this.cli) { if (!this.cli) {
return; return;
} }
await this.cli.clearOutputDevDir();
// TODO: enable URL support in the Engine and remove this "workaround" // TODO: enable URL support in the Engine and remove this "workaround"
if ( if (
typeof this.cli.options.inputDir !== 'string' || typeof this.cli.options.inputDir !== 'string' ||
typeof this.cli.options.outputDir !== 'string' typeof this.cli.options.outputDevDir !== 'string'
) { ) {
return; return;
} }
@@ -49,7 +51,7 @@ export class RocketStart {
this.engine = new Engine(); this.engine = new Engine();
this.engine.setOptions({ this.engine.setOptions({
docsDir: this.cli.options.inputDir, docsDir: this.cli.options.inputDir,
outputDir: this.cli.options.outputDir, outputDir: this.cli.options.outputDevDir,
setupPlugins: this.cli.options.setupEnginePlugins, setupPlugins: this.cli.options.setupEnginePlugins,
open: this.cli.options.open, open: this.cli.options.open,
longFileHeaderWidth: this.cli.options.longFileHeaderWidth, longFileHeaderWidth: this.cli.options.longFileHeaderWidth,
@@ -60,7 +62,14 @@ export class RocketStart {
setupDevServerPlugins: [...this.cli.options.setupDevServerPlugins, ...withWrap], setupDevServerPlugins: [...this.cli.options.setupDevServerPlugins, ...withWrap],
}); });
try { try {
console.log('🚀 Engines online'); this.engine.events.on('devServerStarted', () => {
if (this.engine?.devServer) {
logStartMessage(
{ devServerOptions: this.engine.devServer?.config, engine: this.engine },
console,
);
}
});
await this.engine.start(); await this.engine.start();
} catch (e) { } catch (e) {
console.log('Engine start errored'); console.log('Engine start errored');
@@ -71,7 +80,6 @@ export class RocketStart {
async stop({ hard = true } = {}) { async stop({ hard = true } = {}) {
if (this.engine) { if (this.engine) {
await this.engine.stop({ hard }); await this.engine.stop({ hard });
console.log('🚀 Engines offline');
} }
} }
} }

View File

@@ -0,0 +1,28 @@
import { Engine } from '@rocket/engine/server';
/**
* @param {import('../RocketCli.js').RocketCli} cli
* @returns
*/
export async function buildHtml(cli) {
// TODO: enable URL support in the Engine and remove this typescript "workaround"
if (typeof cli.options.inputDir !== 'string' || typeof cli.options.outputDevDir !== 'string') {
return;
}
await cli.clearOutputDevDir();
const engine = new Engine();
engine.setOptions({
docsDir: cli.options.inputDir,
outputDir: cli.options.outputDevDir,
setupPlugins: cli.options.setupEnginePlugins,
longFileHeaderWidth: cli.options.longFileHeaderWidth,
longFileHeaderComment: cli.options.longFileHeaderComment,
renderMode: 'production',
clearOutputDir: cli.options.clearOutputDir,
});
console.log('Engine building...');
await engine.build({ autoStop: cli.options.buildAutoStop });
return engine;
}

View File

@@ -0,0 +1,88 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import path from 'path';
import { existsSync } from 'fs';
import { rollup } from 'rollup';
// @ts-ignore
import { createMpaConfig, createServiceWorkerConfig } from '@rocket/building-rollup';
// import { rollupPluginHTML } from '@web/rollup-plugin-html';
// import { adjustPluginOptions } from 'plugins-manager';
/**
* @param {import('rollup').RollupOptions} config
*/
async function buildAndWrite(config) {
if (!config.output) {
return;
}
const bundle = await rollup(config);
if (Array.isArray(config.output)) {
await bundle.write(config.output[0]);
await bundle.write(config.output[1]);
} else {
await bundle.write(config.output);
}
}
/**
* @param {import('../RocketCli.js').RocketCli} cli
* @param {import('@rocket/engine/server').Engine} engine
* @returns
*/
export async function buildJavaScriptOptimizedOutput(cli, engine) {
const config = cli.options;
// for typescript as `this.cli.options.outputDir` supports other inputs as well
// but the cli will normalize it to a string before calling plugins
if (typeof config.outputDir !== 'string' || typeof config.outputDevDir !== 'string') {
return;
}
await cli.clearOutputDir();
// TODO: pathPrefix is currently not supported
// const defaultSetupPlugins = [];
// if (config.pathPrefix) {
// defaultSetupPlugins.push(
// adjustPluginOptions(rollupPluginHTML, { absolutePathPrefix: config.pathPrefix }),
// );
// }
const mpaConfig = createMpaConfig({
input: '**/*.html',
output: {
dir: config.outputDir,
},
// custom
rootDir: path.resolve(config.outputDevDir),
absoluteBaseUrl: config.absoluteBaseUrl,
setupPlugins: [
// ...defaultSetupPlugins,
...config.setupDevServerAndBuildPlugins,
...config.setupBuildPlugins,
],
});
const finalConfig =
typeof config.adjustBuildOptions === 'function'
? config.adjustBuildOptions(mpaConfig)
: mpaConfig;
await buildAndWrite(finalConfig);
const { serviceWorkerSourcePath } = config;
if (existsSync(serviceWorkerSourcePath)) {
const serviceWorkerConfig = createServiceWorkerConfig({
input: serviceWorkerSourcePath,
output: {
file: path.join(path.resolve(config.outputDir), config.serviceWorkerName),
},
});
await buildAndWrite(serviceWorkerConfig);
}
// copy static files over
await engine.copyPublicFilesTo(config.outputDir);
}

View File

@@ -0,0 +1,95 @@
import { gatherFiles } from '@rocket/engine';
import { Engine } from '@rocket/engine/server';
import { fromRollup } from '@web/dev-server-rollup';
import { readFile, unlink, writeFile } from 'fs/promises';
import puppeteer from 'puppeteer';
import path from 'path';
/**
* @param {import('../RocketCli.js').RocketCli} cli
* @returns
*/
export async function buildOpenGraphImages(cli) {
const openGraphFiles = await gatherFiles(cli.options.outputDevDir, {
fileEndings: ['.opengraph.html'],
});
if (openGraphFiles.length === 0) {
return;
}
// TODO: enable URL support in the Engine and remove this typescript "workaround"
if (typeof cli.options.inputDir !== 'string' || typeof cli.options.outputDevDir !== 'string') {
return;
}
const withWrap = cli.options.setupDevServerAndBuildPlugins
? cli.options.setupDevServerAndBuildPlugins.map(modFunction => {
modFunction.wrapPlugin = fromRollup;
return modFunction;
})
: [];
const engine = new Engine();
engine.setOptions({
docsDir: cli.options.inputDir,
outputDir: cli.options.outputDevDir,
setupPlugins: cli.options.setupEnginePlugins,
open: false,
clearOutputDir: false,
adjustDevServerOptions: cli.options.adjustDevServerOptions,
setupDevServerMiddleware: cli.options.setupDevServerMiddleware,
setupDevServerPlugins: [...cli.options.setupDevServerPlugins, ...withWrap],
});
try {
await engine.start();
if (!engine?.devServer?.config.port) {
return;
}
const browser = await puppeteer.launch();
const page = await browser.newPage();
// In 2022 Twitter & Facebook recommend a size of 1200x628 - we capture with 2 dpr for retina displays
await page.setViewport({
width: 1200,
height: 628,
deviceScaleFactor: 2,
});
for (const openGraphFile of openGraphFiles) {
const relUrl = path.relative(cli.options.outputDevDir, openGraphFile);
const imagePath = openGraphFile.replace('.opengraph.html', '.opengraph.png');
const htmlPath = openGraphFile.replace('.opengraph.html', '.html');
const relImageUrl = path.basename(imagePath);
let htmlString = await readFile(htmlPath, 'utf8');
if (!htmlString.includes('<meta property="og:image"')) {
if (htmlString.includes('</head>')) {
htmlString = htmlString.replace(
'</head>',
[
' <meta property="og:image:width" content="2400">',
' <meta property="og:image:height" content="1256">',
` <meta property="og:image" content="./${relImageUrl}">`,
' </head>',
].join('\n'),
);
}
}
const url = `http://localhost:${engine.devServer.config.port}/${relUrl}`;
await page.goto(url, { waitUntil: 'networkidle0' });
await page.screenshot({ path: imagePath });
await unlink(openGraphFile);
await writeFile(htmlPath, htmlString);
}
await browser.close();
await engine.stop();
} catch (e) {
console.log('Could not start dev server to generate open graph images');
console.error(e);
}
}

View File

@@ -0,0 +1,34 @@
import ip from 'ip';
import { white, cyan } from 'colorette';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
/**
*
* @param {DevServerConfig} devServerOptions
* @param {string} host
* @param {string} path
* @returns {string}
*/
export function createAddress(devServerOptions, host, path) {
return `http${devServerOptions.http2 ? 's' : ''}://${host}:${devServerOptions.port}${path}`;
}
/**
*
* @param {DevServerConfig} devServerOptions
* @param {console} logger
* @param {string} openPath
*/
export function logNetworkAddress(devServerOptions, logger, openPath) {
try {
const address = ip.address();
if (typeof address === 'string') {
logger.log(
`${white(' 🌐 Network:')} ${cyan(createAddress(devServerOptions, address, openPath))}`,
);
}
} catch (_a) {
//
}
}

View File

@@ -1,10 +0,0 @@
{
"gitdoc.enabled": false,
"typescript.tsdk": "node_modules/typescript/lib",
"files.exclude": {
"**/*-mdjs-generated.js": true,
},
"search.exclude": {
"**/*-mdjs-generated.js": true,
}
}

View File

@@ -1,9 +0,0 @@
# Welcome to Rocket
Get started with
```bash
npm start
```
Create new pages by calling them `my-page.rocket.md` and by putting them into `docs/`.

View File

@@ -1,8 +0,0 @@
## npm
node_modules
npm-debug.log
## Rocket ignore files
*-mdjs-generated.js
_site
_site-dev

View File

@@ -1,6 +0,0 @@
import { rocketLaunch } from '@rocket/launch';
export default /** @type {import('@rocket/cli').RocketCliOptions} */ ({
absoluteBaseUrl: 'http://localhost:8080',
presets: [rocketLaunch()],
});

View File

@@ -1,29 +0,0 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '10--guides/10--first-pages/10--getting-started.rocket.md';
import { pageTree, layout, html } from '../../recursive.data.js';
export { pageTree, layout, html };
/* END - Rocket auto generated - do not touch */
```
# Getting Started
Rocket has the following prerequisites:
- [Node 14+](https://nodejs.org/en/)
Make sure they are installed before proceeding.
## Setup
The fastest way to get started is by using an existing preset like the launch preset.
<inline-notification type="warning">
If you don't want to use the `module` package type, make sure to rename the generated config file to `rocket.config.mjs`.
</inline-notification>
```js script
import '@rocket/launch/inline-notification/inline-notification.js';
```

View File

@@ -1,24 +0,0 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '10--guides/10--first-pages/20--custom-layout.rocket.md';
import { pageTree } from '../../recursive.data.js';
export { pageTree };
/* END - Rocket auto generated - do not touch */
import { html } from 'lit';
export const layout = data => html`
<html>
<head></head>
<body>
<p>A FULLY custom layout</p>
${data.content()}
<a href="../index.rocket.md">Go back to Guides</a>
</body>
</html>
`;
```
# Custom Layout
Here is my markdown content.

View File

@@ -1,43 +0,0 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '10--guides/10--first-pages/20--images.rocket.md';
import { pageTree, layout, html } from '../../recursive.data.js';
export { pageTree, layout, html };
/* END - Rocket auto generated - do not touch */
```
# Images
We can add a private package import to your
👉 `package.json`
```json
"imports": {
"#images/*": "./docs/__shared/*"
},
```
With that we can then use `resolve:[[npm resolve name]]` in our urls.
```md
![rocket image](resolve:#images/rocket-image.jpg)
```
<div style="width: 50%">
![rocket image](resolve:#images/rocket-image.jpg)
</div>
You can also include images from dependencies.
```md
![astronaut](resolve:@rocket/launch/assets/404/astronaut.svg)
```
<div style="width: 50%">
![astronaut](resolve:@rocket/launch/assets/404/astronaut.svg)
</div>

View File

@@ -1,12 +0,0 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '10--guides/10--first-pages/index.rocket.js';
import { pageTree, layout } from '../../recursive.data.js';
export { pageTree, layout };
/* END - Rocket auto generated - do not touch */
import { html } from 'lit';
export default () => html`
<h1>First Pages</h1>
<meta name="menu:exclude" content="true" />
`;

View File

@@ -1,17 +0,0 @@
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '10--guides/index.rocket.md';
import { pageTree, layout, html } from '../recursive.data.js';
export { pageTree, layout, html };
/* END - Rocket auto generated - do not touch */
import { ChildListMenu } from '@rocket/engine';
```
# Learning Rocket
<meta name="menu:link.text" content="Guides">
Rocket helps you generate static pages from Markdown files while giving you the flexibility to sprinkle in some JavaScript where needed.
${pageTree.renderMenu(new ChildListMenu(), sourceRelativeFilePath)}

View File

@@ -1,16 +0,0 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '20--docs/index.rocket.js';
import { pageTree, layout, html } from '../recursive.data.js';
export { pageTree, layout, html };
/* END - Rocket auto generated - do not touch */
import { ChildListMenu } from '@rocket/engine';
export default () => html`
<meta name="menu:link.text" content="Docs" />
<h1>Documentation</h1>
<p>Here you will find all the details for each of the packages/systems we offer.</p>
${pageTree.renderMenu(new ChildListMenu(), sourceRelativeFilePath)}
`;

View File

@@ -1,21 +0,0 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '20--docs/star-wars.rocket.js';
import { pageTree, layout, html } from '../recursive.data.js';
export { pageTree, layout, html };
/* END - Rocket auto generated - do not touch */
import cache from '@11ty/eleventy-cache-assets';
const films = await cache('https://swapi.dev/api/films/', {
duration: '1d',
type: 'json',
});
export default () => html`
<h1>Star Wars</h1>
<h2>Films:</h2>
<ul>
${films.results.map(film => html`<li>${film.title} (${film.release_date})</li>`)}
</ul>
`;

View File

@@ -1,11 +0,0 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '404.html.rocket.js';
import { pageTree, html } from './recursive.data.js';
export { pageTree, html };
/* END - Rocket auto generated - do not touch */
import { Layout404 } from '@rocket/launch';
export const layout = new Layout404();
export default () => '';

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,45 +0,0 @@
export const footerMenu = [
{
name: 'Discover',
children: [
{
text: 'Blog',
href: '/blog/',
},
{
text: 'Help and Feedback',
href: 'https://github.com/modernweb-dev/rocket/issues',
},
],
},
{
name: 'Follow',
children: [
{
text: 'GitHub',
href: 'https://github.com/modernweb-dev/rocket',
},
{
text: 'Twitter',
href: 'https://twitter.com/modern_web_dev',
},
{
text: 'Slack',
href: '/about/slack/',
},
],
},
{
name: 'Support',
children: [
{
text: 'Sponsor',
href: '/about/sponsor/',
},
{
text: 'Contribute',
href: 'https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md',
},
],
},
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

View File

@@ -1,52 +0,0 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.js';
import { pageTree } from './recursive.data.js';
export { pageTree };
/* END - Rocket auto generated - do not touch */
import { html } from 'lit';
import { LayoutHome } from '@rocket/launch';
import { footerMenu } from './__shared/footerMenu.js';
export const layout = new LayoutHome({
pageTree,
footerMenu,
slogan: 'The modern web setup for static sites with a sprinkle of JavaScript.',
callToActionItems: [
{ text: 'Follow Guides', href: '/guides/' },
{ text: 'Browse Docs', href: '/docs/' },
],
background: '/home-background.svg',
reasonHeader: 'Why Rocket?',
reasons: [
{
header: 'Small',
text: 'No overblown tools or frontend frameworks, add JavaScript and/or Web Components only on pages where needed.',
},
{
header: 'Pre-Rendered',
text: 'Statically generated content means less JavaScript to ship and process.',
},
{
header: 'Zero Configuration',
text: 'Automatic code splitting, filesystem based routing, and JavaScript in Markdown.',
},
{
header: 'Meta Framework',
text: html`Build on top of giants like <a href="https://www.11ty.dev/">Eleventy</a>,
<a href="https://rollupjs.org/">Rollup</a>, and
<a href="https://www.modern-web.dev/">Modern Web</a>.`,
},
{
header: 'Powerful Default Template',
text: 'Provide content and you are ready to go.',
},
{
header: 'Ready for Production',
text: 'Optimized for a smaller build size, faster dev compilation and dozens of other improvements.',
},
],
});
export default () => '';

View File

@@ -1,121 +0,0 @@
{
"title": "Rocket | Rocket",
"h1": "Rocket",
"name": "Rocket",
"menuLinkText": "Rocket",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",
"level": 0,
"children": [
{
"title": "Learning Rocket | Rocket",
"h1": "Learning Rocket",
"headlinesWithId": [
{
"text": "Learning Rocket",
"id": "learning-rocket",
"level": 1
}
],
"menuLinkText": "Guides",
"name": "Learning Rocket",
"url": "/guides/",
"outputRelativeFilePath": "guides/index.html",
"sourceRelativeFilePath": "10--guides/index.rocket.md",
"level": 1,
"children": [
{
"title": "First Pages | Rocket",
"h1": "First Pages",
"menuNoLink": true,
"name": "First Pages",
"menuLinkText": "First Pages",
"url": "/guides/first-pages/",
"outputRelativeFilePath": "guides/first-pages/index.html",
"sourceRelativeFilePath": "10--guides/10--first-pages/index.rocket.js",
"level": 2,
"children": [
{
"title": "Getting Started | Rocket",
"h1": "Getting Started",
"headlinesWithId": [
{
"text": "Getting Started",
"id": "getting-started",
"level": 1
},
{
"text": "Setup",
"id": "setup",
"level": 2
}
],
"name": "Getting Started",
"menuLinkText": "Getting Started",
"url": "/guides/first-pages/getting-started/",
"outputRelativeFilePath": "guides/first-pages/getting-started/index.html",
"sourceRelativeFilePath": "10--guides/10--first-pages/10--getting-started.rocket.md",
"level": 3
},
{
"h1": "Custom Layout",
"headlinesWithId": [
{
"text": "Custom Layout",
"id": "custom-layout",
"level": 1
}
],
"name": "Custom Layout",
"menuLinkText": "Custom Layout",
"url": "/guides/first-pages/custom-layout/",
"outputRelativeFilePath": "guides/first-pages/custom-layout/index.html",
"sourceRelativeFilePath": "10--guides/10--first-pages/20--custom-layout.rocket.md",
"level": 3
},
{
"title": "Images | Rocket",
"h1": "Images",
"headlinesWithId": [
{
"text": "Images",
"id": "images",
"level": 1
}
],
"name": "Images",
"menuLinkText": "Images",
"url": "/guides/first-pages/images/",
"outputRelativeFilePath": "guides/first-pages/images/index.html",
"sourceRelativeFilePath": "10--guides/10--first-pages/20--images.rocket.md",
"level": 3
}
]
}
]
},
{
"title": "Documentation | Rocket",
"menuLinkText": "Docs",
"h1": "Documentation",
"name": "Documentation",
"url": "/docs/",
"outputRelativeFilePath": "docs/index.html",
"sourceRelativeFilePath": "20--docs/index.rocket.js",
"level": 1,
"children": [
{
"title": "Star Wars | Rocket",
"h1": "Star Wars",
"name": "Star Wars",
"menuLinkText": "Star Wars",
"url": "/docs/star-wars/",
"outputRelativeFilePath": "docs/star-wars/index.html",
"sourceRelativeFilePath": "20--docs/star-wars.rocket.js",
"level": 2
}
]
}
]
}

View File

@@ -1,16 +0,0 @@
import { PageTree } from '@rocket/engine';
import { LayoutSidebar } from '@rocket/launch';
import { footerMenu } from './__shared/footerMenu.js';
import { html } from 'lit';
export const pageTree = new PageTree({
inputDir: new URL('./', import.meta.url),
outputDir: new URL('../_site', import.meta.url),
});
await pageTree.restore();
export const layout = new LayoutSidebar({ pageTree, footerMenu });
export { html };
// export const openGraphLayout = new OpenGraphLayoutLogo();

View File

@@ -1,33 +0,0 @@
{
"compilerOptions": {
"module": "esnext",
"outDir": "./dist-types",
"rootDir": ".",
"composite": true,
"allowJs": true,
"checkJs": true,
"emitDeclarationOnly": true,
"target": "esnext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"downlevelIteration": true,
"strict": true,
"moduleResolution": "Node",
"typeRoots": ["@types", "./types"],
"types": ["node"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": false,
"forceConsistentCasingInFileNames": true,
"lib": [
"DOM",
"DOM.Iterable",
"ES6",
"ES2017",
// Allows array.flatMap. import `array-flat-polyfill` to cover node10
"ES2019.array",
"ScriptHost"
]
}
}

View File

@@ -0,0 +1,33 @@
import { white, bold, cyan, gray } from 'colorette';
import { createAddress, logNetworkAddress } from '../helpers/infoMessages.js';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
/**
* @param {{ devServerOptions: DevServerConfig}} options
* @param {console} logger
*/
export function logPreviewMessage({ devServerOptions }, logger) {
const prettyHost = devServerOptions.hostname ?? 'localhost';
let openPath = typeof devServerOptions.open === 'string' ? devServerOptions.open : '/';
if (!openPath.startsWith('/')) {
openPath = `/${openPath}`;
}
logger.log(`${bold(`👀 Previewing Production Build`)}`);
logger.log('');
logger.log(
`${white(' 🚧 Local:')} ${cyan(createAddress(devServerOptions, prettyHost, openPath))}`,
);
logNetworkAddress(devServerOptions, logger, openPath);
const sourceDir = devServerOptions.rootDir;
if (sourceDir) {
logger.log(`${white(' 📝 Source:')} ${cyan(sourceDir)}`);
}
logger.log('');
logger.log(
gray(
'If what you see works as expected then you can upload "source" to your production web server.',
),
);
}

View File

@@ -0,0 +1,24 @@
import { white, bold, cyan, gray } from 'colorette';
import { createAddress, logNetworkAddress } from '../helpers/infoMessages.js';
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
/**
* @param {{ devServerOptions: DevServerConfig, engine: import('@rocket/engine/server').Engine}} options
* @param {console} logger
*/
export function logStartMessage({ devServerOptions, engine }, logger) {
const prettyHost = devServerOptions.hostname ?? 'localhost';
let openPath = typeof devServerOptions.open === 'string' ? devServerOptions.open : '/';
if (!openPath.startsWith('/')) {
openPath = `/${openPath}`;
}
logger.log(`${bold(`🚀 Rocket Engine`)} ${gray(`v${engine.getVersion()}`)}`);
logger.log('');
logger.log(
`${white(' 🚧 Local:')} ${cyan(createAddress(devServerOptions, prettyHost, openPath))}`,
);
logNetworkAddress(devServerOptions, logger, openPath);
logger.log('');
}

View File

@@ -5,7 +5,7 @@ import { setupTestCli } from './test-helpers.js';
export function prepareTestCli(importMetaUrl) { export function prepareTestCli(importMetaUrl) {
const dir = path.dirname(fileURLToPath(importMetaUrl)); const dir = path.dirname(fileURLToPath(importMetaUrl));
return (cwd, cliOptions = ['build'], options = {}) => setupTestCli(cwd, cliOptions, options, dir); return fullOptions => setupTestCli({ dir, ...fullOptions });
} }
const { expect } = chai; const { expect } = chai;

View File

@@ -60,7 +60,13 @@ function cleanupLitMarkersFn(text) {
return newText; return newText;
} }
export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, dir) { export async function setupTestCli({
cwd,
cliOptions = ['build'],
options = {},
testOptions = {},
dir,
}) {
const resolvedCwd = path.join(dir, cwd.split('/').join(path.sep)); const resolvedCwd = path.join(dir, cwd.split('/').join(path.sep));
const useOptions = { buildOptimize: false, buildAutoStop: false, ...options, cwd: resolvedCwd }; const useOptions = { buildOptimize: false, buildAutoStop: false, ...options, cwd: resolvedCwd };
if (useOptions.inputDir) { if (useOptions.inputDir) {
@@ -69,6 +75,18 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di
useOptions.outputDir = path.join(resolvedCwd, '__output'); useOptions.outputDir = path.join(resolvedCwd, '__output');
useOptions.outputDevDir = path.join(resolvedCwd, '__output-dev'); useOptions.outputDevDir = path.join(resolvedCwd, '__output-dev');
const capturedLogs = [];
const origLog = console.log;
const origError = console.error;
if (testOptions.captureLogs) {
console.log = msg => {
capturedLogs.push(msg);
};
console.error = msg => {
capturedLogs.push(msg);
};
}
const cli = new RocketCli({ const cli = new RocketCli({
argv: [process.argv[0], new URL('../src/cli.js', import.meta.url).pathname, ...cliOptions], argv: [process.argv[0], new URL('../src/cli.js', import.meta.url).pathname, ...cliOptions],
}); });
@@ -184,6 +202,10 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di
async function cleanup() { async function cleanup() {
await cli.stop({ hard: false }); await cli.stop({ hard: false });
if (testOptions.captureLogs) {
console.log = origLog;
console.error = origError;
}
} }
async function build() { async function build() {
@@ -243,5 +265,6 @@ export async function setupTestCli(cwd, cliOptions = ['build'], options = {}, di
renameSource, renameSource,
backupOrRestoreSource, backupOrRestoreSource,
restoreSource, restoreSource,
capturedLogs,
}; };
} }

View File

@@ -8,11 +8,11 @@ const { expect } = chai;
describe('Config', () => { describe('Config', () => {
it('01: no config file', async () => { it('01: no config file', async () => {
const { build, readOutput, readDevOutput } = await setupTestCli( const { build, readOutput, readDevOutput } = await setupTestCli({
'fixtures/01-config/01-no-config/', cwd: 'fixtures/01-config/01-no-config/',
undefined, options: { buildOptimize: true },
{ buildOptimize: true }, testOptions: { captureLogs: true },
); });
await build(); await build();
expect(readOutput('index.html')).to.equal( expect(readOutput('index.html')).to.equal(
@@ -31,14 +31,21 @@ describe('Config', () => {
}); });
it('02: change input dir', async () => { it('02: change input dir', async () => {
const { build, readDevOutput } = await setupTestCli('fixtures/01-config/02-change-input-dir/'); const { build, readDevOutput } = await setupTestCli({
cwd: 'fixtures/01-config/02-change-input-dir/',
testOptions: { captureLogs: true },
});
await build(); await build();
expect(readDevOutput('index.html')).to.equal(['Hello World!'].join('\n')); expect(readDevOutput('index.html')).to.equal(['Hello World!'].join('\n'));
}); });
it('03: can add a middleware (api proxy) to the dev server', async () => { it('03: can add a middleware (api proxy) to the dev server', async () => {
const { cleanup, cli } = await setupTestCli('fixtures/01-config/03-add-middleware/', ['start']); const { cleanup, cli } = await setupTestCli({
cwd: 'fixtures/01-config/03-add-middleware/',
cliOptions: ['start'],
testOptions: { captureLogs: true },
});
const apiServer = http.createServer((request, response) => { const apiServer = http.createServer((request, response) => {
if (request.url === '/api/message') { if (request.url === '/api/message') {
response.writeHead(200); response.writeHead(200);
@@ -61,20 +68,22 @@ describe('Config', () => {
}); });
it('04: can add a rollup plugin via setupDevServerAndBuildPlugins to build', async () => { it('04: can add a rollup plugin via setupDevServerAndBuildPlugins to build', async () => {
const { build, readOutput } = await setupTestCli( const { build, readOutput } = await setupTestCli({
'fixtures/01-config/04-add-rollup-plugin/', cwd: 'fixtures/01-config/04-add-rollup-plugin/',
undefined, options: { buildOptimize: true },
{ buildOptimize: true }, testOptions: { captureLogs: true },
); });
await build(); await build();
const inlineModule = await readOutput('e97af63d.js', { format: false }); const inlineModule = await readOutput('e97af63d.js', { format: false });
expect(inlineModule).to.equal('var a={test:"data"};console.log(a);\n'); expect(inlineModule).to.equal('var a={test:"data"};console.log(a);\n');
}); });
it('04a: can add a rollup plugin via setupDevServerAndBuildPlugins to start', async () => { it('04a: can add a rollup plugin via setupDevServerAndBuildPlugins to start', async () => {
const { cli, cleanup } = await setupTestCli('fixtures/01-config/04-add-rollup-plugin/', [ const { cli, cleanup } = await setupTestCli({
'start', cwd: 'fixtures/01-config/04-add-rollup-plugin/',
]); cliOptions: ['start'],
testOptions: { captureLogs: true },
});
await cli.start(); await cli.start();
const { port } = cli?.activePlugin?.engine.devServer.config; const { port } = cli?.activePlugin?.engine.devServer.config;
@@ -88,9 +97,10 @@ describe('Config', () => {
}); });
it('05: long file header comments', async () => { it('05: long file header comments', async () => {
const { build, readSource } = await setupTestCli( const { build, readSource } = await setupTestCli({
'fixtures/01-config/05-long-file-header-comment/', cwd: 'fixtures/01-config/05-long-file-header-comment/',
); testOptions: { captureLogs: true },
});
await build(); await build();
expect(readSource('index.rocket.js', { format: false })).to.equal( expect(readSource('index.rocket.js', { format: false })).to.equal(

View File

@@ -5,13 +5,13 @@ const { expect } = chai;
describe('Build', () => { describe('Build', () => {
it('01: copy public files', async () => { it('01: copy public files', async () => {
const { build, readOutput, outputExists, readDevOutput } = await setupTestCli( const { build, readOutput, outputExists, readDevOutput } = await setupTestCli({
'fixtures/02-build/01-copy-public-files/', cwd: 'fixtures/02-build/01-copy-public-files/',
undefined, options: {
{
buildOptimize: true, buildOptimize: true,
}, },
); testOptions: { captureLogs: true },
});
await build(); await build();
expect(readOutput('index.html')).to.equal( expect(readOutput('index.html')).to.equal(

View File

@@ -6,7 +6,11 @@ const { expect } = chai;
describe('Upgrade System', () => { describe('Upgrade System', () => {
it('2021-09-menu', async () => { it('2021-09-menu', async () => {
const { build, sourceExists, readSource, backupOrRestoreSource, restoreSource } = const { build, sourceExists, readSource, backupOrRestoreSource, restoreSource } =
await setupTestCli('fixtures/03-upgrade/2022-03-menu', ['upgrade']); await setupTestCli({
cwd: 'fixtures/03-upgrade/2022-03-menu',
cliOptions: ['upgrade'],
testOptions: { captureLogs: true },
});
await backupOrRestoreSource(); await backupOrRestoreSource();
await build(); await build();

View File

@@ -5,13 +5,13 @@ const { expect } = chai;
describe('Open Graph', () => { describe('Open Graph', () => {
it('generates the image and adds the meta tags', async () => { it('generates the image and adds the meta tags', async () => {
const { build, readOutput, outputExists } = await setupTestCli( const { build, readOutput, outputExists } = await setupTestCli({
'fixtures/04-open-graph/01-generate-image-and-inject-meta', cwd: 'fixtures/04-open-graph/01-generate-image-and-inject-meta',
undefined, options: {
{
buildOptimize: true, buildOptimize: true,
}, },
); testOptions: { captureLogs: true },
});
await build(); await build();
expect(readOutput('index.html', { replaceImageHashes: true })).to.equal( expect(readOutput('index.html', { replaceImageHashes: true })).to.equal(
@@ -35,13 +35,13 @@ describe('Open Graph', () => {
}); });
it('handles multiple pages', async () => { it('handles multiple pages', async () => {
const { build, readOutput } = await setupTestCli( const { build, readOutput } = await setupTestCli({
'fixtures/04-open-graph/02-multiple-pages', cwd: 'fixtures/04-open-graph/02-multiple-pages',
undefined, options: {
{
buildOptimize: true, buildOptimize: true,
}, },
); testOptions: { captureLogs: true },
});
await build(); await build();
expect(readOutput('index.html', { replaceImageHashes: true })).to.equal( expect(readOutput('index.html', { replaceImageHashes: true })).to.equal(

View File

@@ -0,0 +1,25 @@
import chai from 'chai';
import { white, bold } from 'colorette';
import { setupTestCli } from './test-helpers.js';
const { expect } = chai;
describe('Start', () => {
it('Start Message', async () => {
const { cli, capturedLogs, cleanup } = await setupTestCli({
cwd: 'fixtures/05-start/01-start-message',
cliOptions: ['start'],
testOptions: { captureLogs: true },
});
await cli.start();
await cleanup();
expect(capturedLogs[0].startsWith(`${bold(`🚀 Rocket Engine`)} `)).to.be.true;
expect(capturedLogs[1]).to.equal('');
expect(capturedLogs[2].startsWith(`${white(' 🚧 Local:')}`)).to.be.true;
expect(capturedLogs[3].startsWith(`${white(' 🌐 Network:')}`)).to.be.true;
expect(capturedLogs[4]).to.equal('');
});
});

View File

@@ -0,0 +1,49 @@
import chai from 'chai';
import { white, bold, gray } from 'colorette';
import { setupTestCli } from './test-helpers.js';
const { expect } = chai;
describe('Preview', () => {
it('01: Preview Message', async () => {
const { cli, capturedLogs, cleanup } = await setupTestCli({
cwd: 'fixtures/06-preview/01-preview-message',
cliOptions: ['preview'],
testOptions: { captureLogs: true },
});
await cli.start();
await cleanup();
expect(capturedLogs[0]).to.equal(`${bold(`👀 Previewing Production Build`)}`);
expect(capturedLogs[1]).to.equal('');
expect(capturedLogs[2].startsWith(`${white(' 🚧 Local:')}`)).to.be.true;
expect(capturedLogs[3].startsWith(`${white(' 🌐 Network:')}`)).to.be.true;
expect(capturedLogs[4].startsWith(`${white(' 📝 Source:')}`)).to.be.true;
expect(capturedLogs[5]).to.equal('');
expect(capturedLogs[6]).to.equal(
`${gray(
'If what you see works as expected then you can upload "source" to your production web server.',
)}`,
);
});
it('02: Error Message if there is no build output', async () => {
const { cli, capturedLogs, cleanup } = await setupTestCli({
cwd: 'fixtures/06-preview/02-error-no-build',
cliOptions: ['preview'],
testOptions: { captureLogs: true },
});
await cli.start();
await cleanup();
expect(capturedLogs[0]).to.equal(`${bold(`👀 Previewing Production Build`)}`);
expect(capturedLogs[1]).to.equal('');
expect(capturedLogs[2].startsWith(` 🛑 No index.html found in the build directory`)).to.be
.true;
expect(capturedLogs[3]).to.equal(' 🤔 Did you forget to run `rocket build` before?');
expect(capturedLogs[4]).to.equal('');
});
});

View File

@@ -0,0 +1,5 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.js';
/* END - Rocket auto generated - do not touch */
export default () => 'Hello World!';

View File

@@ -0,0 +1,8 @@
{
"name": "index.rocket.js",
"menuLinkText": "index.rocket.js",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",
"level": 0
}

View File

@@ -0,0 +1 @@
!__output

View File

@@ -0,0 +1 @@
so preview does not stop

View File

@@ -47,6 +47,11 @@ export interface FullRocketCliOptions extends Pick<FullRocketPreset, PresetKeys>
// rarely used // rarely used
configFile: string; configFile: string;
outputDevDir: URL | string; outputDevDir: URL | string;
lint: {
buildHtml: boolean;
[key: string]: any;
};
} }
export type RocketCliOptions = Partial<FullRocketCliOptions>; export type RocketCliOptions = Partial<FullRocketCliOptions>;

View File

@@ -1,5 +1,29 @@
# @rocket/engine # @rocket/engine
## 0.2.7
### Patch Changes
- 0ed3d6d: Adjust urls containing url fragments
```html
<!-- user writes -->
<a href="./about.rocket.js#some-id"></a>
<!-- rocket outputs -->
<!-- before -->
<a href="./about.rocket.js#some-id"></a>
<!-- after -->
<a href="/about/#some-id"></a>
```
## 0.2.6
### Patch Changes
- 8dedc56: Add `engine.getVersion()` method
- 390335d: Improve title tag handling
## 0.2.5 ## 0.2.5
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/engine", "name": "@rocket/engine",
"version": "0.2.5", "version": "0.2.7",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -36,7 +36,7 @@
"debug": "DEBUG=engine:rendering yarn test", "debug": "DEBUG=engine:rendering yarn test",
"debug:integration": "PWDEBUG=1 yarn test:integration", "debug:integration": "PWDEBUG=1 yarn test:integration",
"test": "mocha --require ../../scripts/testMochaGlobalHooks.js --timeout 8000 test-node/**/*.test.js test-node/*.test.js", "test": "mocha --require ../../scripts/testMochaGlobalHooks.js --timeout 8000 test-node/**/*.test.js test-node/*.test.js",
"test:integration": "playwright test test-node/*.spec.js", "test:integration": "playwright test test-node/*.spec.js --retries=3",
"test:watch": "onchange 'src/**/*.js' 'test-node/**/*.js' -- npm test", "test:watch": "onchange 'src/**/*.js' 'test-node/**/*.js' -- npm test",
"types:copy": "copyfiles \"./types/**/*.d.ts\" dist-types/" "types:copy": "copyfiles \"./types/**/*.d.ts\" dist-types/"
}, },

View File

@@ -7,7 +7,7 @@
import { existsSync } from 'fs'; import { existsSync } from 'fs';
// TODO: implement copy without extra dependency => node 16.7.0 copy has recursive // TODO: implement copy without extra dependency => node 16.7.0 copy has recursive
import fse from 'fs-extra'; import fse from 'fs-extra';
import { mkdir, rm } from 'fs/promises'; import { mkdir, readFile, rm } from 'fs/promises';
import path from 'path'; import path from 'path';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { startDevServer } from '@web/dev-server'; import { startDevServer } from '@web/dev-server';
@@ -32,6 +32,8 @@ import { RocketHeader } from './file-header/RocketHeader.js';
const logRendering = debuglog('engine:rendering'); const logRendering = debuglog('engine:rendering');
const pkgJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
export class Engine { export class Engine {
/** @type {EngineOptions} */ /** @type {EngineOptions} */
options = { options = {
@@ -121,8 +123,10 @@ export class Engine {
if (pageTree.pageTreeChangedOnSave) { if (pageTree.pageTreeChangedOnSave) {
for (const sourceFilePath of sourceFiles) { for (const sourceFilePath of sourceFiles) {
const result = await this.renderFile({ sourceFilePath, throwOnError: true }); const result = await this.renderFile({ sourceFilePath, throwOnError: true });
await pageTree.add(result.sourceRelativeFilePath);
await cleanupAutoGeneratedFiles(result); await cleanupAutoGeneratedFiles(result);
} }
await pageTree.save();
} }
} }
@@ -265,6 +269,7 @@ export class Engine {
readFileConfig: false, readFileConfig: false,
// argv: this.__argv, // argv: this.__argv,
}); });
this.events.emit('devServerStarted');
this.devServer.webSockets.on( this.devServer.webSockets.on(
'message', 'message',
@@ -467,4 +472,8 @@ export class Engine {
} }
return result; return result;
} }
getVersion() {
return pkgJson.version;
}
} }

View File

@@ -19,32 +19,39 @@ import { parser, SaxEventType } from '../web-menu/sax-parser.js';
* @returns * @returns
*/ */
async function defaultAdjustAssetUrl({ async function defaultAdjustAssetUrl({
url, url: fullUrl,
sourceFilePath, sourceFilePath,
sourceRelativeFilePath, sourceRelativeFilePath,
outputFilePath, outputFilePath,
}) { }) {
let url = fullUrl;
let fragment = '';
if (!url.startsWith('resolve:#') && url.includes('#')) {
const urlParts = url.split('#');
url = urlParts[0];
fragment = `#${urlParts[1]}`;
}
if (url.startsWith('resolve:')) { if (url.startsWith('resolve:')) {
const bareImport = url.substring(8); const bareImport = url.substring(8);
const requireOfSource = createRequire(sourceFilePath); const requireOfSource = createRequire(sourceFilePath);
const resolvedPath = requireOfSource.resolve(bareImport); const resolvedPath = requireOfSource.resolve(bareImport);
const rel = path.relative(path.dirname(outputFilePath), resolvedPath); const rel = path.relative(path.dirname(outputFilePath), resolvedPath);
return rel; return rel + fragment;
} }
if (url.match(/^[a-z]+:/) || url.startsWith('//')) { if (url.match(/^[a-z]+:/) || url.startsWith('//')) {
return url; return url + fragment;
} }
if (isRocketPageFile(url)) { if (isRocketPageFile(url)) {
const dir = path.dirname(sourceRelativeFilePath); const dir = path.dirname(sourceRelativeFilePath);
return sourceRelativeFilePathToUrl(path.join(dir, url)); return sourceRelativeFilePathToUrl(path.join(dir, url)) + fragment;
} }
if (url.startsWith('./') || url.startsWith('../')) { if (url.startsWith('./') || url.startsWith('../')) {
return path.relative( return (
path.dirname(outputFilePath), path.relative(path.dirname(outputFilePath), path.join(path.dirname(sourceFilePath), url)) +
path.join(path.dirname(sourceFilePath), url), fragment
); );
} }
return url; return url + fragment;
} }
export class AdjustAssetUrls { export class AdjustAssetUrls {

View File

@@ -28,16 +28,20 @@ export function getHtmlMetaData(htmlFilePath) {
const metaData = { const metaData = {
// headlinesWithId: [], // headlinesWithId: [],
}; };
/** @type {string | undefined} */ /** @type {string | undefined} */
let capturedHeadlineText = undefined; let capturedHeadlineText = undefined;
let withinHTMLHead = false;
parser.eventHandler = (ev, _data) => { parser.eventHandler = (ev, _data) => {
if (ev === SaxEventType.OpenTag) { if (ev === SaxEventType.OpenTag) {
const data = /** @type {Tag} */ (/** @type {any} */ (_data)); const data = /** @type {Tag} */ (/** @type {any} */ (_data));
if (isHeadline(data)) { if (isHeadline(data)) {
capturedHeadlineText = ''; capturedHeadlineText = '';
} }
if (data.name === 'head') {
withinHTMLHead = true;
}
} }
if (capturedHeadlineText !== undefined && ev === SaxEventType.Text) { if (capturedHeadlineText !== undefined && ev === SaxEventType.Text) {
const data = /** @type {Text} */ (/** @type {any} */ (_data)); const data = /** @type {Text} */ (/** @type {any} */ (_data));
capturedHeadlineText += data.value; capturedHeadlineText += data.value;
@@ -74,7 +78,7 @@ export function getHtmlMetaData(htmlFilePath) {
metaData.menuNoLink = getAttribute(data, 'content') !== 'false'; metaData.menuNoLink = getAttribute(data, 'content') !== 'false';
} }
} }
if (!metaData.title && data.name === 'title') { if (withinHTMLHead && data.name === 'title') {
metaData.title = getText(data); metaData.title = getText(data);
} }
@@ -87,7 +91,7 @@ export function getHtmlMetaData(htmlFilePath) {
.replace(/&#x26;/g, '&') .replace(/&#x26;/g, '&')
.trim(); .trim();
const text = linkText || processedCapturedHeadlineText || ''; const text = linkText || processedCapturedHeadlineText || '';
if (data.name === 'h1') { if (!metaData.h1 && data.name === 'h1') {
metaData.h1 = text; metaData.h1 = text;
} }
if (id && text) { if (id && text) {
@@ -104,6 +108,10 @@ export function getHtmlMetaData(htmlFilePath) {
} }
capturedHeadlineText = undefined; capturedHeadlineText = undefined;
} }
if (data.name === 'head') {
withinHTMLHead = false;
}
} }
}; };

View File

@@ -894,7 +894,7 @@ describe('Engine menus', () => {
}); });
}); });
it('15: link-text attribute', async () => { it('16: link-text attribute', async () => {
const { build, readSource } = await setupTestEngine( const { build, readSource } = await setupTestEngine(
'fixtures/05-menu/16-link-text-attribute/docs', 'fixtures/05-menu/16-link-text-attribute/docs',
); );
@@ -929,4 +929,23 @@ describe('Engine menus', () => {
url: '/', url: '/',
}); });
}); });
it('17: title-tag', async () => {
const { build, readSource, deleteSource } = await setupTestEngine(
'fixtures/05-menu/17-title-tag/docs',
);
await deleteSource('pageTreeData.rocketGenerated.json');
await build();
expect(JSON.parse(readSource('pageTreeData.rocketGenerated.json'))).to.deep.equal({
h1: 'Welcome to Rocket',
level: 0,
menuLinkText: 'Welcome to Rocket',
name: 'Welcome to Rocket',
outputRelativeFilePath: 'index.html',
sourceRelativeFilePath: 'index.rocket.js',
title: 'Welcome to Rocket | Rocket',
url: '/',
});
});
}); });

View File

@@ -1,5 +1,6 @@
import chai from 'chai'; import chai from 'chai';
import { AdjustAssetUrls } from '@rocket/engine'; import { AdjustAssetUrls } from '@rocket/engine';
import { expectThrowsAsync } from './test-helpers.js';
const { expect } = chai; const { expect } = chai;
@@ -48,7 +49,7 @@ describe('AdjustAssetUrls', () => {
); );
}); });
it('ignores <a href="#foo"></a>', async () => { it('does not adjust <a href="#foo"></a>', async () => {
const adjust = new AdjustAssetUrls(); const adjust = new AdjustAssetUrls();
expect(await adjust.transform('<a href="#foo">go</a>', options)).to.equal( expect(await adjust.transform('<a href="#foo">go</a>', options)).to.equal(
'<a href="#foo">go</a>', '<a href="#foo">go</a>',
@@ -104,4 +105,43 @@ describe('AdjustAssetUrls', () => {
}), }),
).to.equal('<a href="/">go</a>'); ).to.equal('<a href="/">go</a>');
}); });
it('adjust <a href="./about.rocket.js#some-id"></a>', async () => {
const adjust = new AdjustAssetUrls();
expect(await adjust.transform('<a href="./about.rocket.js#some-id">go</a>', options)).to.equal(
'<a href="/about/#some-id">go</a>',
);
expect(
await adjust.transform('<a href="./about.rocket.js#some-id">go</a>', {
sourceRelativeFilePath: 'components/index.rocket.js',
outputFilePath: '/my/path/to/__output/components/index.html',
}),
).to.equal('<a href="/components/about/#some-id">go</a>');
expect(
await adjust.transform('<a href="./about.rocket.js#some-id">go</a>', {
sourceRelativeFilePath: 'components.rocket.js',
outputFilePath: '/my/path/to/__output/components/index.html',
}),
).to.equal('<a href="/about/#some-id">go</a>');
expect(
await adjust.transform('<a href="./index.rocket.js#some-id">go</a>', {
sourceRelativeFilePath: 'about.rocket.js',
outputFilePath: '/my/path/to/__output/about/index.html',
}),
).to.equal('<a href="/#some-id">go</a>');
});
it('still resolves private imports <img src="resolve:#src/logo.svg" />', async () => {
const adjust = new AdjustAssetUrls();
// we check for the resolve throw as this private import does not exists =>
// which means if a not resolve related error in our code happens the test fails
await expectThrowsAsync(
() => adjust.transform('<img src="resolve:#src/logo.svg" />', options),
{
errorMatch: /Cannot find module '#src\/logo\.svg'/,
},
);
});
}); });

View File

@@ -6,7 +6,13 @@ export { layout };
import { html } from 'lit'; import { html } from 'lit';
export default () => html` export default () => html`
<meta name="menu:link.text" content="About" /> <html>
<title>About | MyPage</title> <head>
<h1>This is About</h1> <title>About | MyPage</title>
</head>
<body>
<meta name="menu:link.text" content="About" />
<h1>This is About</h1>
</body>
</html>
`; `;

View File

@@ -8,8 +8,8 @@
"level": 0, "level": 0,
"children": [ "children": [
{ {
"menuLinkText": "About",
"title": "About | MyPage", "title": "About | MyPage",
"menuLinkText": "About",
"h1": "This is About", "h1": "This is About",
"name": "This is About", "name": "This is About",
"url": "/about/", "url": "/about/",

View File

@@ -0,0 +1,7 @@
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = 'index.rocket.js';
import { layout, html } from './recursive.data.js';
export { layout, html };
/* END - Rocket auto generated - do not touch */
export default () => html` <h1>Welcome to Rocket</h1> `;

View File

@@ -0,0 +1,10 @@
{
"title": "Welcome to Rocket | Rocket",
"h1": "Welcome to Rocket",
"name": "Welcome to Rocket",
"menuLinkText": "Welcome to Rocket",
"url": "/",
"outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js",
"level": 0
}

View File

@@ -0,0 +1,28 @@
import { PageTree } from '@rocket/engine';
import { html } from 'lit';
const pageTree = new PageTree({
inputDir: new URL('./', import.meta.url),
outputDir: new URL('../__output', import.meta.url),
});
await pageTree.restore();
const titleWrapperFn = title => (title ? `${title} | Rocket` : '');
export const layout = data => {
const title = titleWrapperFn(pageTree.getPage(data.sourceRelativeFilePath)?.model?.name);
return html`
<html>
<head>
<title-server-only>${title}</title-server-only>
</head>
</html>
<body>
<main>${data.content()}</main>
</body>
</html>
`;
};
export { html };

View File

@@ -13,6 +13,17 @@ const { expect } = chai;
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** /**
* Expect a throw in an async function
*
* @example
* await expectThrowsAsync(() => myAsyncFunction(), {
* errorMatch: /exact throw message/,
* });
* @example
* await expectThrowsAsync(() => myAsyncFunction(), {
* errorMatch: /check throw message with a regex/,
* });
*
* @param {function} method * @param {function} method
* @param {string} errorMessage * @param {string} errorMessage
*/ */

View File

@@ -1,5 +1,37 @@
# @rocket/launch # @rocket/launch
## 0.21.2
### Patch Changes
- 87c10ec: Work without JavaScript if Declarative Shadow Dom (DSD) is supported by browser
- d7e461c: Replace Layout Options `logoSrc` and `logoAlt` strings with a `logoSmall` TemplateResult
```diff
- logoSrc: '/icon.svg',
- logoAlt: 'Rocket Logo',
+ logoSmall: html`
+ <img src="resolve:@rocket/launch/assets/rocket-logo-light.svg" alt="Rocket" width="250" height="67.87" />
+ `,
```
- a12adf2: Add padding above slogan an home page
- Updated dependencies [a48dcd8]
- Updated dependencies [0ed3d6d]
- @rocket/cli@0.20.3
- @rocket/engine@0.2.7
## 0.21.1
### Patch Changes
- 390335d: Improve title tag handling
- Updated dependencies [8dedc56]
- Updated dependencies [390335d]
- Updated dependencies [8dedc56]
- @rocket/engine@0.2.6
- @rocket/cli@0.20.1
## 0.21.0 ## 0.21.0
### Minor Changes ### Minor Changes

View File

@@ -0,0 +1,12 @@
<svg width="105" height="101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m19.91 91.438 12.445-4.705-8.09-7.465-4.355 12.17Z" fill="#FCB736"/>
<path d="M53.778 41.196c-1.6-5.176 1.444-10.652 6.844-12.192 5.378-1.54 11.067 1.39 12.667 6.588a9.426 9.426 0 0 1 .277 4.512 9.595 9.595 0 0 1-1.86 4.151 10.076 10.076 0 0 1-3.6 2.902 10.485 10.485 0 0 1-4.573 1.033c-4.4 0-8.444-2.738-9.755-6.994ZM68.666 16.62c-9.066 2.952-19.088 8.085-27.022 16.042-6.4 6.416-11.311 12.47-15 17.71a28.068 28.068 0 0 0-3.889.32A23.592 23.592 0 0 0 18.6 51.72C9.533 54.928-.49 63.42.133 65.11c.6 1.69 6.467-1.433 11.467-1.818 2.91-.236 5.178.042 6.91.492-.577 1.154-1.066 2.138-1.421 2.93-.378.791-.645 1.369-.823 1.732l18.09 16.705c.733-.385 2.377-1.112 4.733-2.353.266.813.51 1.776.644 2.866.156 1.091.222 2.332.156 3.743-.2 4.813-3.2 10.588-1.423 11.101 1.778.514 10.178-9.475 13.134-18.33.91-2.695 1.133-5.433 1.066-7.786 5.29-3.785 11.356-8.726 17.756-15.164 7.933-7.957 12.844-17.796 15.533-26.65 2.356-7.765 2.978-14.738 2.4-19.037a22.605 22.605 0 0 0-2.155-.085c-4.511-.022-10.756.962-17.534 3.165Z" fill="url(#a)"/>
<path d="m12.4 89.535 15.911-6.545-9.777-9.026-6.134 15.57Z" fill="#D08A06"/>
<path d="m10.488 82.733 12.467-4.684-8.089-7.465-4.378 12.149ZM90.644 16.406c.044 2.823-.267 6.096-.912 9.54 7.712 10.544 10.889 24.083 7.578 37.623-5.778 23.591-29.178 38.756-53.622 35.612-.133.15-.267.278-.4.428 27.045 4.834 53.556-11.614 59.911-37.666 4.111-16.897-1.155-33.752-12.555-45.537ZM2.82 42.158C8.955 17.048 35.088 1.477 61.177 7.401c5 1.134 9.644 2.973 13.822 5.39 3.6-.791 7.067-1.262 10.067-1.347-5.822-4.492-12.756-7.828-20.511-9.582C36.665-4.47 8.733 12.172 2.177 39.014a47.973 47.973 0 0 0-.511 20.619c.111-.107.222-.193.333-.3-.8-5.56-.6-11.378.822-17.175Z" fill="#FCB736"/>
<defs>
<linearGradient id="a" x1="26.5" y1="57" x2="89" y2="13" gradientUnits="userSpaceOnUse">
<stop stop-color="#D21B1D"/>
<stop offset="1" stop-color="#C9181A"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/launch", "name": "@rocket/launch",
"version": "0.21.0", "version": "0.21.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -46,9 +46,9 @@
"preset" "preset"
], ],
"dependencies": { "dependencies": {
"@rocket/cli": "^0.20.0", "@rocket/cli": "^0.20.3",
"@rocket/components": "^0.2.0", "@rocket/components": "^0.2.0",
"@rocket/engine": "^0.2.0", "@rocket/engine": "^0.2.7",
"@webcomponents/template-shadowroot": "^0.1.0", "@webcomponents/template-shadowroot": "^0.1.0",
"lit": "^2.3.0", "lit": "^2.3.0",
"workbox-window": "^6.1.5" "workbox-window": "^6.1.5"

View File

@@ -45,8 +45,17 @@ export class LayoutMain extends Layout {
headerDarkBackground: false, headerDarkBackground: false,
dsdPending: true, dsdPending: true,
siteName: 'Rocket', siteName: 'Rocket',
logoSrc: '/icon.svg', logoSmall: html`
logoAlt: 'Rocket Logo', <picture>
<!-- <source srcset="resolve:@rocket/launch/assets/rocket-logo-dark.svg" media="(prefers-color-scheme: dark)"> -->
<img
src="resolve:@rocket/launch/assets/rocket-logo-light.svg"
alt="Rocket Logo"
width="250"
height="67.87"
/>
</picture>
`,
gitSiteUrl: 'https://github.com/modernweb-dev/rocket', gitSiteUrl: 'https://github.com/modernweb-dev/rocket',
gitBranch: 'next', gitBranch: 'next',
description: 'A static site generator for modern web development', description: 'A static site generator for modern web development',
@@ -65,7 +74,7 @@ export class LayoutMain extends Layout {
}, },
], ],
footerMenu: [], footerMenu: [],
titleWrapperFn: title => `${title} | ${this.options.siteName}`, titleWrapperFn: title => (title ? `${title} | ${this.options.siteName}` : ''),
}; };
/** /**
@@ -137,17 +146,9 @@ export class LayoutMain extends Layout {
<link rel="stylesheet" href="resolve:@rocket/launch/css/markdown.css" /> <link rel="stylesheet" href="resolve:@rocket/launch/css/markdown.css" />
`, `,
head__50: html`
<style>
body[dsd-pending] {
display: none;
}
</style>
`,
header__10: html` header__10: html`
<a class="logo-link" href="/" slot="logo"> <a class="logo-link" href="/" slot="logo">
<img src="/icon.svg" alt="${this.options.logoAlt}" /> ${this.options.logoSmall}
<span>${this.options.siteName}</span> <span>${this.options.siteName}</span>
</a> </a>
`, `,
@@ -171,7 +172,7 @@ export class LayoutMain extends Layout {
drawer__10: html` drawer__10: html`
<a class="logo-link" href="/"> <a class="logo-link" href="/">
<img src="${this.options.logoSrc}" alt="${this.options.logoAlt}" /> ${this.options.logoSmall}
<span>${this.options.siteName}</span> <span>${this.options.siteName}</span>
</a> </a>
`, `,

View File

@@ -122,6 +122,7 @@ export class LaunchHome extends LitElement {
.page-slogan { .page-slogan {
font-size: 18px; font-size: 18px;
padding-top: 25px;
} }
.reason-header { .reason-header {

View File

@@ -7,8 +7,7 @@ export interface LayoutSidebarOptions extends LayoutOptions {
[key: TemplateValueKey<'drawer'>]: TemplateValue; [key: TemplateValueKey<'drawer'>]: TemplateValue;
siteName: string; siteName: string;
logoSrc: string; logoSmall: TemplateValue;
logoAlt: string;
gitSiteUrl: string; gitSiteUrl: string;
gitBranch: string; gitBranch: string;
description: string; description: string;

View File

@@ -7,13 +7,13 @@ const { expect } = chai;
describe('Search', () => { describe('Search', () => {
it('01: writes the search index', async () => { it('01: writes the search index', async () => {
const { build, readOutput, readPublic } = await setupTestCli( const { build, readOutput, readPublic } = await setupTestCli({
'fixtures/01-single-page/', cwd: 'fixtures/01-single-page/',
undefined, options: {
{
buildOptimize: true, buildOptimize: true,
}, },
); testOptions: { captureLogs: true },
});
await build(); await build();
const indexString = const indexString =

View File

@@ -1,5 +1,14 @@
# @rocket/spark # @rocket/spark
## 0.2.1
### Patch Changes
- 390335d: Improve title tag handling
- Updated dependencies [8dedc56]
- Updated dependencies [390335d]
- @rocket/engine@0.2.6
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/spark", "name": "@rocket/spark",
"version": "0.2.0", "version": "0.2.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -41,7 +41,7 @@
], ],
"dependencies": { "dependencies": {
"@rocket/components": "^0.2.0", "@rocket/components": "^0.2.0",
"@rocket/engine": "^0.2.0", "@rocket/engine": "^0.2.6",
"lit": "^2.3.0" "lit": "^2.3.0"
}, },
"devDependencies": {}, "devDependencies": {},

View File

@@ -31,7 +31,7 @@ export class LayoutHome extends Layout {
return nothing; return nothing;
} }
const page = this.options.pageTree.getPage(data.sourceRelativeFilePath); const page = this.options.pageTree.getPage(data.sourceRelativeFilePath);
if (page.model.headlinesWithId) { if (page?.model?.headlinesWithId) {
return html` return html`
${page.model.headlinesWithId.map( ${page.model.headlinesWithId.map(
headline => html` headline => html`

View File

@@ -54,7 +54,7 @@ Like any unfamiliar technology, Rocket comes with a slight learning curve. Howev
### Example Projects ### Example Projects
If you prefer to learn Rocket by example, check out our [examples](https://github.com/modernweb-dev/rocket/tree/next/examples) on GitHub. If you prefer to learn Rocket by example, check out our [examples](https://github.com/modernweb-dev/rocket/tree/main/examples) on GitHub.
You locally install any of these examples by running `npx @rocket/create@latest` and selecting it within the wizard. You locally install any of these examples by running `npx @rocket/create@latest` and selecting it within the wizard.

View File

@@ -48,11 +48,11 @@ This with start rocket in development mode and you will see your site running in
## What is a page? ## What is a page?
A page is a file that ends either with `*.rocket.js`, `*.rocket.md` or `*.rocket.html` and is located in the input directory (`docs` by default). Pages will make up the majority or your website. A page is a file that ends either with `*.rocket.js`, `*.rocket.md` or `*.rocket.html` and is located in the input directory (`site/pages` by default). Pages will make up the majority or your website.
The simplest way to get started is to create a file The simplest way to get started is to create a file
👉 `docs/index.rocket.md` 👉 `site/pages/index.rocket.md`
```md ```md
# Hello World # Hello World
@@ -78,15 +78,23 @@ This section will be used to auto inject settings and data via a data cascade.
To test it you can create a file To test it you can create a file
👉 `docs/recursive.data.js` 👉 `site/pages/recursive.data.js`
```js ```js
import { html } from 'lit'; import { html } from 'lit';
export const layout = data => html`<div>${data.content()}</div>`; export const layout = data => html`
<!DOCTYPE html>
<html>
<body>
<header>My Website</header>
<div>${data.content()}</div>
</body>
</html>
`;
``` ```
Now if you go back to your `docs/index.rocket.md` you will see that `layout` got automatically injected. Now if you go back to your `site/pages/index.rocket.md` you will see that `layout` got automatically injected.
````md ````md
```js server ```js server

View File

@@ -31,7 +31,7 @@ export const description = 'Learn how to structure a project with Rocket.';
# Project Structure # Project Structure
Rocket works with one input folder for all your pages that defaults to `site/pages`. Rocket works with one input folder for all your pages that defaults to `site/pages`.
Within `pages` there is a `__public` folder that will be copied as is to the output folder. Within `pages` there is a `public` folder that will be copied as is to the output folder.
All other files like `layouts`, `css`, `data`, ... can be placed anywhere in your project. All other files like `layouts`, `css`, `data`, ... can be placed anywhere in your project.

View File

@@ -11,6 +11,8 @@ export async function registerCustomElements() {
// prettier-ignore // prettier-ignore
customElements.define('rocket-header', await import('@rocket/components/header.js').then(m => m.RocketHeader)); customElements.define('rocket-header', await import('@rocket/components/header.js').then(m => m.RocketHeader));
// prettier-ignore // prettier-ignore
customElements.define('inline-notification', await import('@rocket/components/inline-notification.js').then(m => m.InlineNotification));
// prettier-ignore
customElements.define('rocket-main-docs', await import('@rocket/components/main-docs.js').then(m => m.RocketMainDocs)); customElements.define('rocket-main-docs', await import('@rocket/components/main-docs.js').then(m => m.RocketMainDocs));
// prettier-ignore // prettier-ignore
customElements.define('rocket-content-area', await import('@rocket/components/content-area.js').then(m => m.RocketContentArea)); customElements.define('rocket-content-area', await import('@rocket/components/content-area.js').then(m => m.RocketContentArea));

View File

@@ -34,6 +34,40 @@ const { resolve } = createRequire(new URL('.', import.meta.url));
A few things are usually needed before going live "for real". A few things are usually needed before going live "for real".
## Make sure all links are correct
When you launch a website you don't want the first feedback to be "that link doesn't work".
To prevent this we want to execute `rocket lint` before going live.
It will make sure all internal links are correct by using [check-html-links](../../30--tools/40--check-html-links/10--overview.rocket.md).
Typically we deploy via a Continuous Integration system like GitHub Actions or Netlify Deploy.
We can also integrate the lint command into that process.
```
rocket build
rocket lint
```
### Fixing broken links
If found a couple of broken links on your page and you want to fix them and verify that they are now correct it might be a little time consuming to create a full production build every time.
The reason is that a production build is doing a lot of things
1. Generate HTML
2. Generate & Inject Open Graph Images
3. Optimize Images (not available yet)
4. Optimize JavaScript
But there is a way around this. We can use an optional flag `--build-html` which means it will run only (1) and then lint that (non-optimized) HTML output.
So for a more time efficient way of validating link use
```bash
rocket lint --build-html
```
Note: We can do this as 2-4 generally does not impact links/references (as long as the optimizations scripts do not have related bugs)
## Add a Not Found Page ## Add a Not Found Page
When a user enters a URL that does not exist, a "famous" 404 Page Not Found error occurs. When a user enters a URL that does not exist, a "famous" 404 Page Not Found error occurs.

View File

@@ -22,6 +22,8 @@ export async function registerCustomElements() {
} }
export const needsLoader = true; export const needsLoader = true;
/* END - Rocket auto generated - do not touch */ /* END - Rocket auto generated - do not touch */
export const title = 'Plugins Manager';
export const subTitle = 'Enable fully customizable options for your plugin system';
``` ```
# Overview # Overview

View File

@@ -22,6 +22,9 @@ export async function registerCustomElements() {
} }
export const needsLoader = true; export const needsLoader = true;
/* END - Rocket auto generated - do not touch */ /* END - Rocket auto generated - do not touch */
export const title = 'Markdown JavaScript';
export const subTitle = 'Executable JavaScript in markdown by annotating code blocks';
``` ```
# Overview # Overview

View File

@@ -22,6 +22,10 @@ export async function registerCustomElements() {
} }
export const needsLoader = true; export const needsLoader = true;
/* END - Rocket auto generated - do not touch */ /* END - Rocket auto generated - do not touch */
export const title = 'Rocket Rollup Config';
export const subTitle =
'A ready to use and customizable rollup config for web sites, MPAs and SPAs';
``` ```
# Overview # Overview
@@ -77,7 +81,7 @@ You write modern JavaScript using the latest browser features. Rollup will optim
Our config sets you up with good defaults for most projects. Additionally you can add more plugins and adjust predefined plugins or even remove them if needed. Our config sets you up with good defaults for most projects. Additionally you can add more plugins and adjust predefined plugins or even remove them if needed.
We use the [plugins-manager](./plugins-manager.md) for it. We use the [plugins-manager](../10--plugins-manager/10--overview.rocket.md) for it.
### Customizing the Babel Config ### Customizing the Babel Config
@@ -108,7 +112,7 @@ SPA and MPA plugins:
- [polyfills-loader](https://modern-web.dev/docs/building/rollup-plugin-polyfills-loader/) - [polyfills-loader](https://modern-web.dev/docs/building/rollup-plugin-polyfills-loader/)
- [workbox](https://www.npmjs.com/package/rollup-plugin-workbox) - [workbox](https://www.npmjs.com/package/rollup-plugin-workbox)
You can customize options for these plugins by using [adjustPluginOptions](./plugins-manager.md#adjusting-plugin-options). You can customize options for these plugins by using [adjustPluginOptions](../10--plugins-manager/10--overview.rocket.md#adjusting-plugin-options).
```js ```js
import { createSpaConfig } from '@rocket/building-rollup'; import { createSpaConfig } from '@rocket/building-rollup';

View File

@@ -24,11 +24,14 @@ export async function registerCustomElements() {
} }
export const needsLoader = true; export const needsLoader = true;
/* END - Rocket auto generated - do not touch */ /* END - Rocket auto generated - do not touch */
export const title = 'Check HTML links';
export const subTitle = 'A fast checker for broken links/references in HTML files';
``` ```
# Overview # Overview
A fast checker for broken links/references in HTML. A fast checker for broken links/references in HTML files.
<inline-notification type="tip"> <inline-notification type="tip">

View File

@@ -0,0 +1,260 @@
```js server
import { thomas } from '../../../src/data/authors.js';
export const description = 'Rocket is now in beta. It is time you check it out.';
export const publishDate = new Date('2022-08-23');
export const tags = ['rocket', 'javascript', 'node', 'SSG'];
export const authors = [thomas];
```
# It is time 🕑
I did not share much about Rocket recently but it's going great 💪
It's now in beta and if you have been curious before then it's a good time to check it out.
👇
https://rocket.modern-web.dev
<!-- --- -->
## What has happened since the last update?
1⃣ Engine version became the main release <br>
2⃣ @rocket/components for SSR & hydration <br>
3⃣ Rewrite of themes (@rocket/launch & @rocket/spark) <br>
4⃣ `rocket preview` <br>
5⃣ `rocket lint` <br>
Let's take a closer look.
<!-- --- -->
## Main release
The journey from the first prototype to a dedicated package to adding build time SSR to supporting hydration has been challenging but also very rewarding.
Rocket is the first SSG that enables you to write your whole page in template literals and web components. 💪
<!-- --- -->
## ✨ Rocket highlights ✨
✍️ Author in Markdown, HTML or JS <br>
🚀 Build everything in Web Components <br>
🪶 Ship 0 JS for Web Components by default <br>
🌊 Hydrate Web Components as needed <br>
🎨 Theme System <br>
🖼️ Social Media Images <br>
✅ Link Checking <br>
<!-- --- -->
## `@rocket/components` for SSR & hydration
➡️ Very early but it makes the existing themes so fast <br>
➡️ All components are rendered on the server and can be hydrated if needed <br>
➡️ Add to components and use them <br>
➡️ Docs are lacking 🙈 see source (for now) <br>
https://github.com/modernweb-dev/rocket/tree/main/packages/components/src
👉 `site/pages/recursive.data.js`
```js
import { rocketComponents } from '@rocket/components/components.js';
export const components = {
...rocketComponents,
'my-counter': '#src/components/MyCounter.js::MyCounter',
};
```
👉 `site/pages/index.rocket.html`
```html
<h1>Welcome to My Page</h1>
<rocket-details>
<span slot="summary"> I'm often asked by parents</span>
<div>
what advice can I give them to help get kids interested in science? And I have only one bit of
advice. Get out of their way. Kids are born curious. Period.
</div>
</rocket-details>
```
<!-- --- -->
## 📖 The Documentation Theme (Launch)
➡️ Starts with ~2kb of main thread js (+ ~3kb for service worker) <br>
➡️ Hydrates components as needed <br>
➡️ Works without JavaScript if Declarative Shadow Dom (DSD) is supported <br>
➡️ awesome performance <br>
➡️ Example: https://rocket.modern-web.dev <br>
![rocket.modern-web.dev preview](./rocket-preview.png)
![rocket.modern-web.dev lighthouse](./rocket-lighthouse.png)
![rocket.modern-web.dev webpage test waterfall](./rocket-waterfall.png)
<!-- --- -->
## ✨ The Landing Page Theme (Spark)
➡️ Similar characteristics to Launch <br>
➡️ Uses 8 web components <br>
➡️ Only 1 gets hydrated at start <br>
➡️ Example: https://rocket-spark.modern-web.dev/ <br>
![rocket-spark.modern-web.dev preview](./spark-preview.png)
<!-- --- -->
## 🕑 Getting started
🕑 Getting started with any theme takes you only 5 minutes <br>
🏃‍♂️ Run "npx @rocket/create@latest" in the terminal <br>
🎯 Choose your template and start coding! <br>
PS: It creates a folder for you now worries 🤗
```
npx @rocket/create@latest
| Welcome to Rocket! (®rocket/create v0.1.0)
/ \ Everyone can code a website
/ _ \
|.o '.| You are about to embark upon a new mission 🚀.
|'._.'|
| |
,'| | |`.
/ | | | \ If you encounter a problem, visit
|,-'--|--'-.| https://github.com/modernweb-dev/rocket/issues
( | ) to search or file a new issue
(( ))
(( : )) Follow us: https://twitter.com/modern_web_dev
(( )) Chat with us: https://rocket.modern-web.dev/chat
(( ))
( ) Notes: You can exit any time with Ctrl+C or Esc
. A new folder "rocket-<template name>" will be created
.
? Which Starter Template would you like to use? - Use arrow-keys. Return to submit.
Hydration Starter - Example on how to hydrate web components
Blog Starter
Minimal Starter
Sanity Minimal Starter
Landing Page (@rocket/spark Theme)
Documentation Website (@rocket/launch Theme)
Custom (community built)
```
<!-- --- -->
## `rocket preview`
With `rocket preview` we can check after a production build if everything is in order.
➡️ Uses a plain web server without any features <br>
➡️ If it works there then `_site` is good to ship to your production web server <br>
```
👀 Previewing Production Build
🚧 Local: http://localhost:8000/
🌐 Network: http://xxx.xxx.xxx.xxx:8000/
📝 Source: /projects/rocket/_site
If what you see works as expected then you can upload "source" to your production web server.
```
<!-- --- -->
## `rocket lint`
Launching a new site and the first feedback is "this link is broken" 🙈
Not anymore with `rocket lint` 💪
➡️ Checks asset urls like css, images, scripts, ... <br>
➡️ Checks internal links (incl. if ids exist for anchors) <br>
➡️ It is fast and can/should be used in the CI <br>
```
npx rocket lint
👀 Checking if all internal links work...
🔥 Found a total of 57 files to check!
🔗 Found a total of 4401 links to validate!
✅ All internal links are valid. (executed in 0s 299.021433ms)
============================================================
npx rocket lint
👀 Checking if all internal links work...
🔥 Found a total of 57 files to check!
🔗 Found a total of 4401 links to validate!
❌ Found 1 missing reference targets (used by 1 links) while checking 57 files:
1. missing reference target _site/docs/basics/super-pages/index.html
from _site/docs/guides/index.html:1213:15 via href="/docs/basics/super-pages/"
Checking links duration: 0s 296.371296ms
```
## There are two modes:
1⃣ Check the production build in `_site` (need to execute "rocket build" before) <br>
2⃣ Run a fast html only build and then check it <br>
Use 1⃣ in your CI and 2⃣ while fixing links locally
```bash
# use this in CI
# check existing production build in _site (need to execute "rocket build" before)
rocket lint
# use this while fixing broken links
# run a fast html only build and then check it
rocket lint --build-html
```
<!-- --- -->
## That's all folks! 🤗
💬 We are very hungry for feedback to continue polishing Rocket.
If you have any questions, comments or suggestions, please open an issue, create a pull request or join us on Discord or Slack.
https://rocket.modern-web.dev/chat
```js server
/* START - Rocket auto generated - do not touch */
export const sourceRelativeFilePath = '40--blog/005--its-time/index.rocket.md';
import { html, setupUnifiedPlugins, components, openGraphLayout } from '../../recursive.data.js';
import { layout } from '../recursive.data.js';
export { html, layout, setupUnifiedPlugins, components, openGraphLayout };
export async function registerCustomElements() {
// server-only components
// prettier-ignore
customElements.define('rocket-social-link', await import('@rocket/components/social-link.js').then(m => m.RocketSocialLink));
// prettier-ignore
customElements.define('rocket-header', await import('@rocket/components/header.js').then(m => m.RocketHeader));
// prettier-ignore
customElements.define('launch-blog-details', await import('@rocket/launch/blog-details.js').then(m => m.LaunchBlogDetails));
// prettier-ignore
customElements.define('rocket-main', await import('@rocket/components/main.js').then(m => m.RocketMain));
// prettier-ignore
customElements.define('rocket-content-area', await import('@rocket/components/content-area.js').then(m => m.RocketContentArea));
// hydrate-able components
// prettier-ignore
customElements.define('rocket-search', await import('@rocket/search/search.js').then(m => m.RocketSearch));
// prettier-ignore
customElements.define('rocket-drawer', await import('@rocket/components/drawer.js').then(m => m.RocketDrawer));
}
export const needsLoader = true;
/* END - Rocket auto generated - do not touch */
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,8 +1,8 @@
{ {
"title": "Welcome to Rocket", "title": "Welcome to Rocket",
"h1": "\n \n ", "h1": "",
"name": "\n \n ", "name": "Welcome to Rocket",
"menuLinkText": "\n \n ", "menuLinkText": "Welcome to Rocket",
"url": "/", "url": "/",
"outputRelativeFilePath": "index.html", "outputRelativeFilePath": "index.html",
"sourceRelativeFilePath": "index.rocket.js", "sourceRelativeFilePath": "index.rocket.js",
@@ -386,6 +386,31 @@
"text": "Recommended Project Structure", "text": "Recommended Project Structure",
"id": "recommended-project-structure", "id": "recommended-project-structure",
"level": 2 "level": 2
},
{
"text": "site/pages/",
"id": "sitepages",
"level": 3
},
{
"text": "site/src/",
"id": "sitesrc",
"level": 3
},
{
"text": "site/src/components",
"id": "sitesrccomponents",
"level": 3
},
{
"text": "site/src/layouts",
"id": "sitesrclayouts",
"level": 3
},
{
"text": "site/public/",
"id": "sitepublic",
"level": 3
} }
], ],
"name": "Project Structure", "name": "Project Structure",
@@ -758,6 +783,16 @@
"id": "menu-link-text", "id": "menu-link-text",
"level": 3 "level": 3
}, },
{
"text": "link-text=\"...\"",
"id": "link-text",
"level": 3
},
{
"text": "Headings with HTML",
"id": "headings-with-html",
"level": 2
},
{ {
"text": "Menu No Link", "text": "Menu No Link",
"id": "menu-no-link", "id": "menu-no-link",
@@ -937,7 +972,7 @@
"level": 2 "level": 2
}, },
{ {
"text": "The Function", "text": "The :resolve Function",
"id": "the-resolve-function", "id": "the-resolve-function",
"level": 2 "level": 2
}, },
@@ -1328,6 +1363,16 @@
"id": "go-live", "id": "go-live",
"level": 1 "level": 1
}, },
{
"text": "Make sure all links are correct",
"id": "make-sure-all-links-are-correct",
"level": 2
},
{
"text": "Fixing broken links",
"id": "fixing-broken-links",
"level": 3
},
{ {
"text": "Add a Not Found Page", "text": "Add a Not Found Page",
"id": "add-a-not-found-page", "id": "add-a-not-found-page",
@@ -1432,7 +1477,7 @@
"level": 2 "level": 2
}, },
{ {
"text": "1. Simple Tags and External Files", "text": "1. Simple &lt;link> Tags and External Files",
"id": "1-simple-link-tags-and-external-files", "id": "1-simple-link-tags-and-external-files",
"level": 3 "level": 3
}, },
@@ -1447,7 +1492,7 @@
"level": 3 "level": 3
}, },
{ {
"text": "Recommendations &#x26; Best Practices", "text": "Recommendations & Best Practices",
"id": "recommendations--best-practices", "id": "recommendations--best-practices",
"level": 2 "level": 2
}, },
@@ -2239,7 +2284,7 @@
"needsLoader": true, "needsLoader": true,
"children": [ "children": [
{ {
"title": "Overview | Rocket", "title": "Plugins Manager",
"h1": "Overview", "h1": "Overview",
"headlinesWithId": [ "headlinesWithId": [
{ {
@@ -2313,7 +2358,7 @@
"level": 2 "level": 2
} }
], ],
"name": "Overview", "name": "Plugins Manager",
"menuLinkText": "Overview", "menuLinkText": "Overview",
"url": "/tools/plugins-manager/overview/", "url": "/tools/plugins-manager/overview/",
"outputRelativeFilePath": "tools/plugins-manager/overview/index.html", "outputRelativeFilePath": "tools/plugins-manager/overview/index.html",
@@ -2345,7 +2390,8 @@
"launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails", "launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails",
"my-counter": "#src/components/MyCounter.js::MyCounter" "my-counter": "#src/components/MyCounter.js::MyCounter"
}, },
"needsLoader": true "needsLoader": true,
"subTitle": "Enable fully customizable options for your plugin system"
} }
] ]
}, },
@@ -2388,7 +2434,7 @@
"needsLoader": true, "needsLoader": true,
"children": [ "children": [
{ {
"title": "Overview | Rocket", "title": "Markdown JavaScript",
"h1": "Overview", "h1": "Overview",
"headlinesWithId": [ "headlinesWithId": [
{ {
@@ -2451,13 +2497,23 @@
"id": "basic", "id": "basic",
"level": 3 "level": 3
}, },
{
"text": "mdjsDocPage",
"id": "mdjsdocpage",
"level": 4
},
{
"text": "mdjsProcess",
"id": "mdjsprocess",
"level": 4
},
{ {
"text": "Advanced", "text": "Advanced",
"id": "advanced", "id": "advanced",
"level": 3 "level": 3
} }
], ],
"name": "Overview", "name": "Markdown JavaScript",
"menuLinkText": "Overview", "menuLinkText": "Overview",
"url": "/tools/markdown-javascript/overview/", "url": "/tools/markdown-javascript/overview/",
"outputRelativeFilePath": "tools/markdown-javascript/overview/index.html", "outputRelativeFilePath": "tools/markdown-javascript/overview/index.html",
@@ -2489,7 +2545,8 @@
"launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails", "launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails",
"my-counter": "#src/components/MyCounter.js::MyCounter" "my-counter": "#src/components/MyCounter.js::MyCounter"
}, },
"needsLoader": true "needsLoader": true,
"subTitle": "Executable JavaScript in markdown by annotating code blocks"
}, },
{ {
"title": "Preview | Rocket", "title": "Preview | Rocket",
@@ -2655,7 +2712,7 @@
"needsLoader": true, "needsLoader": true,
"children": [ "children": [
{ {
"title": "Overview | Rocket", "title": "Rocket Rollup Config",
"h1": "Overview", "h1": "Overview",
"headlinesWithId": [ "headlinesWithId": [
{ {
@@ -2689,7 +2746,7 @@
"level": 3 "level": 3
} }
], ],
"name": "Overview", "name": "Rocket Rollup Config",
"menuLinkText": "Overview", "menuLinkText": "Overview",
"url": "/tools/rollup-config/overview/", "url": "/tools/rollup-config/overview/",
"outputRelativeFilePath": "tools/rollup-config/overview/index.html", "outputRelativeFilePath": "tools/rollup-config/overview/index.html",
@@ -2721,7 +2778,8 @@
"launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails", "launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails",
"my-counter": "#src/components/MyCounter.js::MyCounter" "my-counter": "#src/components/MyCounter.js::MyCounter"
}, },
"needsLoader": true "needsLoader": true,
"subTitle": "A ready to use and customizable rollup config for web sites, MPAs and SPAs"
} }
] ]
}, },
@@ -2764,7 +2822,7 @@
"needsLoader": true, "needsLoader": true,
"children": [ "children": [
{ {
"title": "Overview | Rocket", "title": "Check HTML links",
"h1": "Overview", "h1": "Overview",
"headlinesWithId": [ "headlinesWithId": [
{ {
@@ -2803,7 +2861,7 @@
"level": 2 "level": 2
} }
], ],
"name": "Overview", "name": "Check HTML links",
"menuLinkText": "Overview", "menuLinkText": "Overview",
"url": "/tools/check-html-links/overview/", "url": "/tools/check-html-links/overview/",
"outputRelativeFilePath": "tools/check-html-links/overview/index.html", "outputRelativeFilePath": "tools/check-html-links/overview/index.html",
@@ -2835,7 +2893,8 @@
"launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails", "launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails",
"my-counter": "#src/components/MyCounter.js::MyCounter" "my-counter": "#src/components/MyCounter.js::MyCounter"
}, },
"needsLoader": true "needsLoader": true,
"subTitle": "A fast checker for broken links/references in HTML files"
} }
] ]
} }
@@ -3187,6 +3246,121 @@
"node", "node",
"SSG" "SSG"
] ]
},
{
"title": "It is time 🕑 | Rocket",
"h1": "It is time 🕑",
"headlinesWithId": [
{
"text": "It is time 🕑",
"id": "it-is-time-",
"level": 1
},
{
"text": "What has happened since the last update?",
"id": "what-has-happened-since-the-last-update",
"level": 2
},
{
"text": "Main release",
"id": "main-release",
"level": 2
},
{
"text": "✨ Rocket highlights ✨",
"id": "-rocket-highlights-",
"level": 2
},
{
"text": "@rocket/components for SSR & hydration",
"id": "rocketcomponents-for-ssr--hydration",
"level": 2
},
{
"text": "📖 The Documentation Theme (Launch)",
"id": "-the-documentation-theme-launch",
"level": 2
},
{
"text": "✨ The Landing Page Theme (Spark)",
"id": "-the-landing-page-theme-spark",
"level": 2
},
{
"text": "🕑 Getting started",
"id": "-getting-started",
"level": 2
},
{
"text": "rocket preview",
"id": "rocket-preview",
"level": 2
},
{
"text": "rocket lint",
"id": "rocket-lint",
"level": 2
},
{
"text": "There are two modes:",
"id": "there-are-two-modes",
"level": 2
},
{
"text": "That's all folks! 🤗",
"id": "thats-all-folks-",
"level": 2
}
],
"name": "It is time 🕑",
"menuLinkText": "It is time 🕑",
"url": "/blog/its-time/",
"outputRelativeFilePath": "blog/its-time/index.html",
"sourceRelativeFilePath": "40--blog/005--its-time/index.rocket.md",
"level": 2,
"authors": [
{
"firstName": "Thomas",
"lastName": "Allmer",
"twitter": "daKmoR",
"image": "https://pbs.twimg.com/profile_images/1070490946847105025/UBK4xPzU_400x400.jpg"
}
],
"components": {
"rocket-columns": "@rocket/components/columns.js::RocketColumns",
"rocket-card": "@rocket/components/card.js::RocketCard",
"rocket-content-area": "@rocket/components/content-area.js::RocketContentArea",
"rocket-details": "@rocket/components/details.js::RocketDetails",
"rocket-drawer": "@rocket/components/drawer.js::RocketDrawer",
"rocket-feature-small": "@rocket/components/feature-small.js::RocketFeatureSmall",
"rocket-header": "@rocket/components/header.js::RocketHeader",
"rocket-header-scroll-menu": "@rocket/components/header-scroll-menu.js::RocketHeaderScrollMenu",
"rocket-icon": "@rocket/components/icon.js::RocketIcon",
"rocket-icon-card": "@rocket/components/icon-card.js::RocketIconCard",
"rocket-main": "@rocket/components/main.js::RocketMain",
"rocket-main-docs": "@rocket/components/main-docs.js::RocketMainDocs",
"rocket-opengraph-overview": "@rocket/components/open-graph-overview.js::RocketOpenGraphOverview",
"rocket-rotating-text": "@rocket/components/rotating-text.js::RocketRotatingText",
"rocket-social-link": "@rocket/components/social-link.js::RocketSocialLink",
"rocket-testimonial-small": "@rocket/components/testimonial-small.js::RocketTestimonialSmall",
"inline-notification": "@rocket/components/inline-notification.js::InlineNotification",
"permanent-notification": "@rocket/components/permanent-notification.js::PermanentNotification",
"rocket-search": "@rocket/search/search.js::RocketSearch",
"launch-home": "@rocket/launch/home.js::LaunchHome",
"launch-blog-overview": "@rocket/launch/blog-overview.js::LaunchBlogOverview",
"launch-blog-preview": "@rocket/launch/blog-preview.js::LaunchBlogPreview",
"launch-blog-details": "@rocket/launch/blog-details.js::LaunchBlogDetails",
"my-counter": "#src/components/MyCounter.js::MyCounter"
},
"description": "Rocket is now in beta. It is time you check it out.",
"needsLoader": true,
"publishDate": "2022-08-23T00:00:00.000Z",
"tags": [
"rocket",
"javascript",
"node",
"SSG"
]
} }
] ]
} }

Some files were not shown because too many files have changed in this diff Show More