mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-21 15:54:57 +00:00
Compare commits
108 Commits
@rocket/cl
...
@rocket/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df9a3e9c3 | ||
|
|
1b9559f2a5 | ||
|
|
8eede4b16b | ||
|
|
2294ccf4a2 | ||
|
|
3b1a0cf26a | ||
|
|
cf442215a9 | ||
|
|
41049f3908 | ||
|
|
2b5c61d19c | ||
|
|
f5d349e256 | ||
|
|
ce0b00e7a1 | ||
|
|
83286a99de | ||
|
|
6cabdba5f6 | ||
|
|
f5f2d69d0c | ||
|
|
795a3613af | ||
|
|
bcf8f4fe83 | ||
|
|
5330740cb3 | ||
|
|
2edd61beaa | ||
|
|
2a5fc08f35 | ||
|
|
43a7ca10c3 | ||
|
|
da39fa72f3 | ||
|
|
a0e8edfbb9 | ||
|
|
50434293bb | ||
|
|
f08f92615b | ||
|
|
1949b1e1cb | ||
|
|
340bf8e653 | ||
|
|
eae200708d | ||
|
|
f707f636fa | ||
|
|
814b5d29ad | ||
|
|
e1e96acceb | ||
|
|
7543a129cf | ||
|
|
60e85a17a7 | ||
|
|
fd8f97859a | ||
|
|
56fdb0cbd4 | ||
|
|
e6d9c74510 | ||
|
|
c338696482 | ||
|
|
2ff4b4c542 | ||
|
|
ba64b45ebf | ||
|
|
e437e02cb9 | ||
|
|
ce9b12edd4 | ||
|
|
d034f799c0 | ||
|
|
8bba4a88c8 | ||
|
|
c7261aa2b0 | ||
|
|
690075d335 | ||
|
|
2724f073fc | ||
|
|
d08692c7f3 | ||
|
|
2b7f1ee719 | ||
|
|
3802778be4 | ||
|
|
26f4a1ebff | ||
|
|
81edf45fe2 | ||
|
|
c5a1d7e8d1 | ||
|
|
74dd8d1bcc | ||
|
|
72f631ac86 | ||
|
|
fafb99b0fa | ||
|
|
f5769b9aa9 | ||
|
|
12d9cc3b44 | ||
|
|
ef9b373aa1 | ||
|
|
560234d663 | ||
|
|
024514e901 | ||
|
|
66c2d781e6 | ||
|
|
14721d1e0f | ||
|
|
0f6709ac4b | ||
|
|
ed86ff2346 | ||
|
|
c675820163 | ||
|
|
f4a0ab559f | ||
|
|
a8cdaebab1 | ||
|
|
22393dd172 | ||
|
|
a6fdb31f1e | ||
|
|
dd15d4fc65 | ||
|
|
edb1abf82b | ||
|
|
0b6411661e | ||
|
|
604a80e6cb | ||
|
|
fe6a929f1e | ||
|
|
2267e728cf | ||
|
|
abc8a02b72 | ||
|
|
2270887faf | ||
|
|
bad4686506 | ||
|
|
818caad7cb | ||
|
|
672b7e893e | ||
|
|
a8e66d84f4 | ||
|
|
e9090d64b9 | ||
|
|
728a205b7b | ||
|
|
67ba29d45a | ||
|
|
758caffdf9 | ||
|
|
302227e8a9 | ||
|
|
04a31220fb | ||
|
|
d44a23af0c | ||
|
|
18a79589c2 | ||
|
|
b7727b0e10 | ||
|
|
5edc40fed5 | ||
|
|
be0d0b3ca1 | ||
|
|
ef8ebb0098 | ||
|
|
2fa61e1377 | ||
|
|
b23e37f38e | ||
|
|
cf45e32702 | ||
|
|
b5965c6c37 | ||
|
|
e39cc45d23 | ||
|
|
f0434cb12c | ||
|
|
c87caaed2d | ||
|
|
04af7ecf53 | ||
|
|
98d6aad12a | ||
|
|
ee6b404aaa | ||
|
|
8ba8939c67 | ||
|
|
8e095b792e | ||
|
|
b58ac27658 | ||
|
|
f44a0f4fd4 | ||
|
|
750418bb51 | ||
|
|
bc2698c1ba | ||
|
|
74f7ddf478 |
@@ -43,7 +43,7 @@
|
||||
|
||||
## The Goal for Rocket
|
||||
|
||||
> Our goal is to provide developers with a meta framework for static websites with a spricle of JavaScript.
|
||||
> Our goal is to provide developers with a meta framework for static websites with a sprinkle of JavaScript.
|
||||
|
||||
Get a site up and running in no time and focus on the content.
|
||||
You can still tweak every detail of every underlying tool that gets used.
|
||||
@@ -54,7 +54,7 @@ Rocket is part of the [Modern Web Family](https://twitter.com/modern_web_dev).
|
||||
|
||||
We are always looking for contributors of all skill levels! If you're looking to ease your way into the project, try out a [good first issue](https://github.com/modernweb-dev/rocket/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
||||
|
||||
If you are interested in helping contribute to Modern Web, please take a look at our [Contributing Guide](https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md). Also, feel free to drop into [slack](https://rocket.modern-web.dev/discover/slack/) and say hi. 👋
|
||||
If you are interested in helping contribute to Modern Web, please take a look at our [Contributing Guide](https://github.com/modernweb-dev/rocket/blob/main/CONTRIBUTING.md). Also, feel free to drop into [slack](https://rocket.modern-web.dev/about/slack/) and say hi. 👋
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
|
||||
10
docs/_assets/body.css
Normal file
10
docs/_assets/body.css
Normal file
@@ -0,0 +1,10 @@
|
||||
html {
|
||||
--demo-background-color: #eee;
|
||||
--demo-color: #222;
|
||||
}
|
||||
|
||||
html[theme="dark"] body {
|
||||
background: #333;
|
||||
--demo-background-color: #888;
|
||||
--demo-color: #eee;
|
||||
}
|
||||
@@ -9,6 +9,11 @@ module.exports = async function () {
|
||||
name: 'GitHub',
|
||||
url: 'https://github.com/modernweb-dev/rocket',
|
||||
},
|
||||
{
|
||||
name: 'Slack',
|
||||
url:
|
||||
'https://join.slack.com/t/lit-and-friends/shared_invite/zt-llwznvsy-LZwT13R66gOgnrg12PUGqw',
|
||||
},
|
||||
],
|
||||
gitSiteUrl: 'https://github.com/modernweb-dev/rocket',
|
||||
gitBranch: 'main',
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<meta name="twitter:creator" content="@modern_web_dev" />
|
||||
|
||||
<link rel="stylesheet" href="{{ '/_assets/body.css' | asset | url }}" mdjs-use>
|
||||
|
||||
299
docs/docs/configuration/images.md
Normal file
299
docs/docs/configuration/images.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Configuration >> Images ||40
|
||||
|
||||
Rocket does handle content images automatically by
|
||||
|
||||
- producing multiple sizes so users download images that are meant for their resolution
|
||||
- outputting multiple formats so the device can choose the best image format it supports
|
||||
|
||||
And the best thing about is you don't need to do anything.
|
||||
|
||||
## Usage
|
||||
|
||||
If you are using markdown images you are good to go.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
will result in
|
||||
|
||||
```html
|
||||
<picture>
|
||||
<source
|
||||
type="image/avif"
|
||||
srcset="/images/5f03d82-300.avif 300w, /images/5f03d82-820.avif 820w"
|
||||
sizes="(min-width: 1024px) 820px, calc(100vw - 20px)"
|
||||
/>
|
||||
<source
|
||||
type="image/jpeg"
|
||||
srcset="/images/5f03d82-300.jpeg 300w, /images/5f03d82-820.jpeg 820w"
|
||||
sizes="(min-width: 1024px) 820px, calc(100vw - 20px)"
|
||||
/>
|
||||
<img
|
||||
alt="My Image"
|
||||
rocket-image="responsive"
|
||||
src="/images/5f03d82-300.jpeg"
|
||||
width="300"
|
||||
height="158"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</picture>
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
The main benefit is that we can serve the correct size and optimal image format depending on the browser capabilities leading to optimal loading times on different systems.
|
||||
|
||||
- Smaller images for smaller screens
|
||||
|
||||
When providing `srcset` and `sizes` the browser can decide which image makes the most sense to download.
|
||||
This will lead to much faster websites especially on mobile where smaller images can be served.
|
||||
If you wanna know more check out [The anatomy of responsive images](https://jakearchibald.com/2015/anatomy-of-responsive-images/).
|
||||
|
||||
- Serve the best/smallest image format the browser understands
|
||||
|
||||
There are currently ~3 formats you may want to consider `avif`, `webp` and `jpg`. The improvements are huge [webp is ~30% and avif ~50%](https://www.ctrl.blog/entry/webp-avif-comparison.html) smaller then the original jpg.
|
||||
|
||||
## Adding a caption
|
||||
|
||||
If you want to describe your image in more detail you can add a caption
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
will result in
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<picture>
|
||||
<!-- picture code the same as above -->
|
||||
</picture>
|
||||
<figcaption>My caption text</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
## Adjusting options
|
||||
|
||||
Under the hood it is using [11ty/image](https://www.11ty.dev/docs/plugins/image/) and all it's options are available.
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
If you are using a layout preset like `@rocket/launch` then you probably don't want to touch/change these options as the preset will set it for you accordion to its layout needs.
|
||||
|
||||
The default preset for regular markdown content is available as `responsive`.
|
||||
|
||||
</inline-notification>
|
||||
|
||||
👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [300, 820],
|
||||
formats: ['avif', 'jpeg'],
|
||||
sizes: '(min-width: 1024px) 820px, calc(100vw - 20px)',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Ignoring Images
|
||||
|
||||
Files ending in `.svg` or that include `rocket-ignore.` will remain untouched.
|
||||
|
||||
For example
|
||||
|
||||
```md
|
||||

|
||||

|
||||

|
||||
```
|
||||
|
||||
becomes
|
||||
|
||||
```html
|
||||
<img src="logo.svg" alt="Logo stays svg" rocket-image="responsive" />
|
||||
<img src="my-image.rocket-unresponsive.jpg" alt="Ignore by file name" rocket-image="responsive" />
|
||||
<picture>[...] </picture>
|
||||
```
|
||||
|
||||
### Adjusting ignore function
|
||||
|
||||
The default ignore function looks like this
|
||||
|
||||
```js
|
||||
/**
|
||||
* The default responsive ignore function will ignore files
|
||||
* - ending in `.svg`
|
||||
* - containing `rocket-unresponsive.`
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string} opts.src
|
||||
* @param {string} opts.title
|
||||
* @param {string} opts.alt
|
||||
* @param {{name: string, value: string}[]} opts.attributes
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function ignore({ src }) {
|
||||
return src.endsWith('svg') || src.includes('rocket-unresponsive.');
|
||||
}
|
||||
```
|
||||
|
||||
and you can adjust it by setting it via the `imagePreset`.
|
||||
|
||||
For this example we want to also ignore `.jpeg` files.
|
||||
|
||||
👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
// ...
|
||||
ignore: ({ src }) =>
|
||||
src.endsWith('.jpeg') || src.endsWith('svg') || src.includes('rocket-unresponsive.'),
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
With that setting we get the following behavior
|
||||
|
||||
```md
|
||||

|
||||

|
||||

|
||||
```
|
||||
|
||||
becomes
|
||||
|
||||
```html
|
||||
<img src="logo.svg" alt="Logo stays svg" rocket-image="responsive" />
|
||||
<img src="my-image.rocket-unresponsive.jpg" alt="Ignore by file name" rocket-image="responsive" />
|
||||
<img src="my-image.jpeg" alt="My Image Alternative Text" rocket-image="responsive" />
|
||||
```
|
||||
|
||||
## Defining your own presets
|
||||
|
||||
You can add your own image preset like so
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
'my-image-preset': {
|
||||
widths: [30, 60],
|
||||
formats: ['avif', 'jpeg'],
|
||||
sizes: '(min-width: 1024px) 30px, 60px',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Once that `imagePreset` is defined you can use it by adding it to any `img` tag.
|
||||
|
||||
```html
|
||||
<img src="./path/to/image.jpg" alt="my alt" rocket-image="my-image-preset" />
|
||||
```
|
||||
|
||||
## How does it work?
|
||||
|
||||
1. Each markdown image `` gets rendered as `<img src="path/to/image.jpg" alt="my image" rocket-image="responsive">`
|
||||
2. We parse the html output and process every image which has `rocket-image`
|
||||
3. Get the image preset settings from the name e.g. `rocket-image="my-image-preset"` reads `imagePreset['my-image-preset']`
|
||||
4. Pass the settings onto `@11ty/image` to generate the image sizes and formats
|
||||
5. With the metadata we render the html
|
||||
|
||||
## Default Formats
|
||||
|
||||
An [image file format](https://en.wikipedia.org/wiki/Image_file_formats) is a way of storing common image formats. Each format varies in capabilities like compression algorithm, availability, progressive rendering, encode and decode time, ...
|
||||
Ultimately newer formats are usually smaller while retaining image quality which leads to faster websites.
|
||||
|
||||
By default, we generate `avif` and `jpg` because
|
||||
|
||||
- we only want to generate two versions to limit CI time and html size
|
||||
- `avif` is significantly smaller than `webp`
|
||||
- `avif` is available in
|
||||
- Chrome since August 2020
|
||||
- Firefox since June 2021
|
||||
- `jpg` as a fallback for Edge, Safari, IE11
|
||||
- `webp` would only help a small percentage of Edge & Safari on macOS 11 (Big Sur) users
|
||||
|
||||
This leads to the following situation:
|
||||
|
||||
- Chrome, Firefox gets the small `avif`
|
||||
- Edge, Safari, IE11 gets the bigger `jpg`
|
||||
|
||||
To learn more about `avif` take a look at [AVIF has landed](https://jakearchibald.com/2020/avif-has-landed/).
|
||||
|
||||
If you want to add `webp` (or replace `avif` with it) you can do so by setting the formats
|
||||
|
||||
👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
formats: ['avif', 'webp', 'jpeg'],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Default widths
|
||||
|
||||
In order to understand the need for having a single image in multiple resolutions we need to understand the our website is served to many different environments and each may come with its own specific device pixel ratio (DPR). The device pixel ratio is the ratio between physical pixels and logical pixels. For instance, the Galaxy S20 report a device pixel ratio of 3, because the physical linear resolution is triple the logical linear resolution.
|
||||
|
||||
Physical resolution: 1440 x 3200
|
||||
Logical resolution: 480 x 1067
|
||||
|
||||
And 1440 / 480 = 3.
|
||||
|
||||
By default, we generate the following widths `600`, `900` and `1640` because
|
||||
|
||||
- we only want to generate a small amount of widths to limit CI time and service worker cache size
|
||||
- `600` is good for mobile with DRP 2
|
||||
- `900` is good for mobile with DRP 3 and desktop with DPR of 1
|
||||
- `1640` is good for desktop with DPR of 2
|
||||
|
||||
If you want to add more widths you can add them to `widths`.
|
||||
|
||||
👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [300, 600, 900, 1200, 1640],
|
||||
sizes: '(min-width: 1024px) 820px, calc(100vw - 20px)',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
As an end user in most cases you don't want to mess with this as a layout preset should set this for you. If you are building your own layout preset then be sure to set `widths` and `sizes` via `adjustImagePresets`
|
||||
|
||||
```js
|
||||
export function myPreset() {
|
||||
return {
|
||||
adjustImagePresets: imagePresets => ({
|
||||
...imagePresets,
|
||||
responsive: {
|
||||
...imagePresets.responsive,
|
||||
widths: [600, 900, 1640],
|
||||
sizes: '(min-width: 1024px) 820px, calc(100vw - 40px)',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
</inline-notification>
|
||||
|
||||
```js script
|
||||
import '@rocket/launch/inline-notification/inline-notification.js';
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
# Configuration >> Overview ||10
|
||||
# Configuration >> Overview || 10
|
||||
|
||||
The configuration file is `rocket.config.js` or `rocket.config.mjs`.
|
||||
|
||||
@@ -86,3 +86,15 @@ const config = {
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
You can hook into the rocket lifecycle by specifying a function for `before11ty`. This function runs before 11ty calls it's write method. If it is an async function, Rocket will await it's promise.
|
||||
|
||||
```js
|
||||
export default {
|
||||
async before11ty() {
|
||||
await copyDataFiles();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
45
docs/docs/configuration/service-worker.md
Normal file
45
docs/docs/configuration/service-worker.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Configuration >> Service Worker ||30
|
||||
|
||||
Rocket does come with a default service worker that will
|
||||
|
||||
- cache already visited pages
|
||||
- cache assets of visited pages (up to 100 files then it replaces older entries)
|
||||
- reload the page if a newer html page version is available on service worker activation
|
||||
|
||||
## Adjusting the file name
|
||||
|
||||
Changing the service worker file name can be quite a hassle so you can adjust generate file name via a config.
|
||||
|
||||
👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
export default {
|
||||
serviceWorkerName: 'my-service-worker-name.js',
|
||||
};
|
||||
```
|
||||
|
||||
## Meet the Service Worker
|
||||
|
||||
The default service worker will work for many scenarios however your needs my vary.
|
||||
To enable different service worker strategies you can replace the default service worker code by providing a file at `_assets/service-worker.js`.
|
||||
This file will be auto transformed and generated in the root of the website using the defined `serviceWorkerName`.
|
||||
|
||||
For inspiration, you can take a look at the default config.
|
||||
|
||||
[https://github.com/modernweb-dev/rocket/blob/main/packages/cli/preset/\_assets/service-worker.js](https://github.com/modernweb-dev/rocket/blob/main/packages/cli/preset/_assets/service-worker.js)
|
||||
|
||||
Be sure to check out [workbox](https://developers.google.com/web/tools/workbox) for more service worker magic.
|
||||
|
||||
And if you wanna have a 30 minutes crash course we highly recommend the talk [Service Workers For The Rest Of Us](https://vimeo.com/362260166) by [Philip Walton](https://twitter.com/philwalton).
|
||||
|
||||
## Registration
|
||||
|
||||
The registration happens via another file that you can also overwrite at `_assets/scripts/registerServiceWorker.js`.
|
||||
|
||||
Below you find the default implementation.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```js
|
||||
{{ '/_assets/scripts/registerServiceWorker.js' | asset | toAbsPath | inlineFilePath; }}
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
69
docs/docs/markdown-javascript/assets/demo-element.js
Normal file
69
docs/docs/markdown-javascript/assets/demo-element.js
Normal file
@@ -0,0 +1,69 @@
|
||||
class DemoElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.platform = 'the web';
|
||||
this.language = 'en-US';
|
||||
this.theme = 'light';
|
||||
|
||||
this.observer = new MutationObserver(this.updateData);
|
||||
}
|
||||
|
||||
updateData = () => {
|
||||
this.platform = document.documentElement.getAttribute('platform') || 'the web';
|
||||
this.language = document.documentElement.getAttribute('data-lang') || 'en-US';
|
||||
this.theme = document.documentElement.getAttribute('theme') || 'light';
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
this.updateData();
|
||||
|
||||
this.observer.observe(document.documentElement, { attributes: true });
|
||||
}
|
||||
|
||||
requestUpdate() {
|
||||
this.shadowRoot.innerHTML = this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
background: var(--demo-background-color);
|
||||
color: var(--demo-color);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:host[platform~="web"] {
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
.about {
|
||||
display: flex;
|
||||
}
|
||||
.about ul {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<p>Hello I am DemoElement 👋</p>
|
||||
<div class="about">
|
||||
<ul>
|
||||
<li>My purpose is to demonstrate how an element can adopt to different environments</li>
|
||||
<li>I like <strong>${this.platform}</strong></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>My mother languages is <strong>${this.language}</strong></li>
|
||||
<li>I feel very comfortable in the <strong>${this.theme}</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-element', DemoElement);
|
||||
@@ -1,5 +1,11 @@
|
||||
# Markdown JavaScript >> Overview || 10
|
||||
|
||||
```js script
|
||||
import '@mdjs/mdjs-story/define';
|
||||
import '@mdjs/mdjs-preview/define';
|
||||
import { html } from '@mdjs/mdjs-story';
|
||||
```
|
||||
|
||||
Markdown JavaScript (mdjs) is a format that allows you to use JavaScript with Markdown, to create interactive demos. It does so by "annotating" JavaScript that should be executed in Markdown.
|
||||
|
||||
To annotate we use a code block with `js script`.
|
||||
@@ -56,20 +62,13 @@ mdjs comes with some additional helpers you can choose to import:
|
||||
|
||||
````md
|
||||
```js script
|
||||
import '@mdjs/mdjs-story/mdjs-story.js';
|
||||
import '@mdjs/mdjs-preview/mdjs-preview.js';
|
||||
import '@mdjs/mdjs-story/define';
|
||||
import '@mdjs/mdjs-preview/define';
|
||||
```
|
||||
````
|
||||
|
||||
Once loaded you can use them like so:
|
||||
|
||||
````md
|
||||
```js script
|
||||
import '@mdjs/mdjs-story/mdjs-story.js';
|
||||
import '@mdjs/mdjs-preview/mdjs-preview.js';
|
||||
```
|
||||
````
|
||||
|
||||
### Story
|
||||
|
||||
The code snippet will actually get executed at that place and you will have a live demo
|
||||
@@ -117,12 +116,6 @@ export const JsPreviewStory = () => html` <demo-wc-card>JS Preview Story</demo-w
|
||||
|
||||
Here is a live example from [demo-wc-card](https://www.npmjs.com/package/demo-wc-card).
|
||||
|
||||
```js script
|
||||
import '@mdjs/mdjs-story/mdjs-story.js';
|
||||
import '@mdjs/mdjs-preview/mdjs-preview.js';
|
||||
import { html } from 'lit-html';
|
||||
```
|
||||
|
||||
```js preview-story
|
||||
import 'demo-wc-card/demo-wc-card.js';
|
||||
export const header = () => {
|
||||
@@ -132,31 +125,6 @@ export const header = () => {
|
||||
|
||||
## Supported Systems
|
||||
|
||||
### es-dev-server
|
||||
|
||||
Preview your mdjs readme with live demos and auto reload.
|
||||
|
||||
- Add to your `package.json`:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"start": "es-dev-server",
|
||||
}
|
||||
```
|
||||
|
||||
- Create a `es-dev-server.config.js` in the root of your repository.
|
||||
|
||||
```js
|
||||
const { mdjsTransformer } = require('@mdjs/core');
|
||||
|
||||
module.exports = {
|
||||
nodeResolve: true,
|
||||
open: 'README.md',
|
||||
watch: true,
|
||||
responseTransformers: [mdjsTransformer],
|
||||
};
|
||||
```
|
||||
|
||||
### Storybook
|
||||
|
||||
Please check out [@open-wc/demoing-storybook](https://open-wc.org/demoing/) for a fully integrated setup.
|
||||
|
||||
@@ -2,18 +2,165 @@
|
||||
|
||||
You can showcase live running code by annotating a code block with `js preview-story`.
|
||||
|
||||
````md
|
||||
```js preview-story
|
||||
import { html } from 'lit-html';
|
||||
## Features
|
||||
|
||||
export const foo = () => html` <p>my html</p> `;
|
||||
- Shows components inside the page as they are
|
||||
- You can enable “Simulation Mode” to break them out
|
||||
- Simulation mode renders in an iframe to supporting media queries and isolated Simulation settings
|
||||
- Simulation Settings
|
||||
- Style (windows, mac, android, iOS)
|
||||
- Size (small, medium, large, Galaxy S5, iPhone X, iPad …)
|
||||
- Automatic Height
|
||||
- Theme (light, dark)
|
||||
- Language (en, nl, …)
|
||||
- Settings are ”global” for all Simulators (e.g. changing one will change all)
|
||||
- Settings can be remembered for other pages / return visits
|
||||
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import './assets/demo-element.js';
|
||||
```
|
||||
|
||||
## JavaScript Story
|
||||
|
||||
````md
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import './assets/demo-element.js';
|
||||
```
|
||||
|
||||
```js preview-story
|
||||
export const foo = () => html`<demo-element></demo-element>`;
|
||||
```
|
||||
````
|
||||
|
||||
will result in
|
||||
|
||||
```js preview-story
|
||||
import { html } from 'lit-html';
|
||||
|
||||
export const foo = () => html` <p>my html</p> `;
|
||||
export const foo = () => html` <demo-element></demo-element> `;
|
||||
```
|
||||
|
||||
## HTML Story
|
||||
|
||||
````md
|
||||
```html preview-story
|
||||
<demo-element></demo-element>
|
||||
```
|
||||
````
|
||||
|
||||
will result in
|
||||
|
||||
```html preview-story
|
||||
<demo-element></demo-element>
|
||||
```
|
||||
|
||||
## Setup Simulation Mode
|
||||
|
||||
For simulation mode we need a dedicated html file that will be used as an iframe target while loading stories.
|
||||
|
||||
The fastest way to create such a file is to use the `layout-simulator` layout.
|
||||
|
||||
Create a file `docs/simulator.md` with the following content.
|
||||
|
||||
```md
|
||||
---
|
||||
layout: layout-simulator
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSearch: true
|
||||
---
|
||||
```
|
||||
|
||||
Once you have that you need to configure it for the story renderer by setting it in your `rocket.config.js`.
|
||||
|
||||
```js
|
||||
export default {
|
||||
setupUnifiedPlugins: [
|
||||
adjustPluginOptions('mdjsSetupCode', {
|
||||
simulationSettings: { simulatorUrl: '/simulator/' },
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
You can freely choose the path for the "simulator" by creating the md file in a different folder and adjusting the path in the config.
|
||||
|
||||
</inline-notification>
|
||||
|
||||
## Simulator states
|
||||
|
||||
To simulate these stats that usually come from the device itself we put those infos on the document tag.
|
||||
|
||||
We can simulate the following settings
|
||||
|
||||
1. `platform`
|
||||
Adopting styles and behavior depending on which device platform you are.
|
||||
```html
|
||||
<html platform="web"></html>
|
||||
<html platform="android"></html>
|
||||
<html platform="ios"></html>
|
||||
<!-- potentially later -->
|
||||
<html platform="web-windows"></html>
|
||||
<html platform="web-mac"></html>
|
||||
```
|
||||
2. `theme`
|
||||
Adjust your styles based on a theme - light/dark are the default but you can add as many as you want.
|
||||
```html
|
||||
<html theme="light"></html>
|
||||
<html theme="dark"></html>
|
||||
```
|
||||
3. `language`
|
||||
Best to relay on `data-lang` as `lang` often gets changes by translations services which may interfere with your translation loading system.
|
||||
```html
|
||||
<html lang="en-US" data-lang="en-US"></html>
|
||||
<html lang="de-DE" data-lang="de-DE"></html>
|
||||
```
|
||||
|
||||
If you want to react to such document changes you can use an [MutationObserver](https://developer.mozilla.org/de/docs/Web/API/MutationObserver).
|
||||
|
||||
For a vanilla web component it could look something like this:
|
||||
|
||||
```js
|
||||
class DemoElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.platform = 'the web';
|
||||
this.language = 'en-US';
|
||||
this.theme = 'light';
|
||||
|
||||
this.observer = new MutationObserver(this.updateData);
|
||||
}
|
||||
|
||||
updateData = () => {
|
||||
this.platform = document.documentElement.getAttribute('platform') || 'the web';
|
||||
this.language = document.documentElement.getAttribute('data-lang') || 'en-US';
|
||||
this.theme = document.documentElement.getAttribute('theme') || 'light';
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
this.updateData();
|
||||
|
||||
this.observer.observe(document.documentElement, { attributes: true });
|
||||
}
|
||||
|
||||
requestUpdate() {
|
||||
this.shadowRoot.innerHTML = this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
...
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-element', DemoElement);
|
||||
```
|
||||
|
||||
```js script
|
||||
import '@rocket/launch/inline-notification/inline-notification.js';
|
||||
```
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
You can showcase live running code by annotating a code block with `js story`.
|
||||
|
||||
````md
|
||||
```js story
|
||||
import { html } from 'lit-html';
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-story';
|
||||
```
|
||||
|
||||
````md
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-story';
|
||||
```
|
||||
|
||||
```js story
|
||||
export const foo = () => html` <p>my html</p> `;
|
||||
```
|
||||
````
|
||||
@@ -13,7 +19,5 @@ export const foo = () => html` <p>my html</p> `;
|
||||
will result in
|
||||
|
||||
```js story
|
||||
import { html } from 'lit-html';
|
||||
|
||||
export const foo = () => html` <p>my html</p> `;
|
||||
```
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
# Tools >> Check HTML Links ||30
|
||||
|
||||
```js
|
||||
import '@rocket/launch/inline-notification/inline-notification.js';
|
||||
```
|
||||
|
||||
A fast checker for broken links/references in HTML.
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
Read the [Introducing Check HTMl Links - no more bad links](../../blog/introducing-check-html-links.md) Blog post to find out how it came to be and how it works.
|
||||
|
||||
</inline-notification>
|
||||
|
||||
## Features
|
||||
|
||||
- Checks all html files for broken local links/references (in href, src, srcset)
|
||||
@@ -16,10 +26,25 @@ A fast checker for broken links/references in HTML.
|
||||
npm i -D check-html-links
|
||||
```
|
||||
|
||||
## Usage
|
||||
## CLI flags
|
||||
|
||||
```
|
||||
| Name | Type | Description |
|
||||
| ------------------- | ------- | --------------------------------------------------------------------------------------------------- |
|
||||
| root-dir | string | the root directory to serve files from. Defaults to the current working directory |
|
||||
| ignore-link-pattern | string | do not check links matching the pattern |
|
||||
| continue-on-error | boolean | if present it will not exit with an error code - useful while writing or for temporary passing a ci |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# check a folder _site
|
||||
npx check-html-links _site
|
||||
|
||||
# ignore all links like <a href="/users/123">
|
||||
npx check-html-links _site --ignore-link-pattern "/users/*" "/users/**/*"
|
||||
|
||||
# ignore all links like <a href="/users/123"> & <a href="/users/123/details">
|
||||
npx check-html-links _site --ignore-link-pattern "/users/*" "/users/**/*"
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
@@ -8,7 +8,6 @@ You write modern JavaScript using the latest browser features. Rollup will optim
|
||||
- Set HTML or JavaScript as input and/or output
|
||||
- Optimized for browsers which support modules
|
||||
- Loads polyfills using feature detection
|
||||
- Generates a service worker
|
||||
- Minifies JavaScript
|
||||
- Minifies lit-html templates
|
||||
|
||||
|
||||
@@ -47,3 +47,9 @@ export const headlineConverter = () => html`
|
||||
```
|
||||
|
||||
How it then works is very similar to https://www.11ty.dev/docs/plugins/navigation/
|
||||
|
||||
## Sidebar redirects
|
||||
|
||||
By default, the sidebar nav redirects clicks on category headings to the first child page in that category.
|
||||
|
||||
To disable those redirects, override `_includes/_joiningBlocks/_layoutSidebar/sidebar/20-navigation.njk` and add the `no-redirects` attribute to the `<rocket-navigation>` element.
|
||||
|
||||
@@ -5,7 +5,7 @@ For that reason Rocket creates those automatically with the title, parent title,
|
||||
|
||||
It will look like this but with your logo:
|
||||
|
||||
<img src="{{ socialMediaImage }}" width="1200" height="630" alt="Social Media Image of this page" style="border: 1px solid #000" />
|
||||
<img src="{{ socialMediaImage | url }}" width="1200" height="630" alt="Social Media Image of this page" style="border: 1px solid #000" />
|
||||
|
||||
There are multiple ways you can modify it.
|
||||
|
||||
@@ -129,3 +129,25 @@ const config = {
|
||||
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
## Enabling / Disabling
|
||||
|
||||
Generating images from SVG is quite fast but it can still add that's why by default during `rocket start` there will be no social media images created.
|
||||
|
||||
If you with so create them also during start you can
|
||||
|
||||
```js
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Similarly, if you never want to create social media images even during build then you can globally disable it via
|
||||
|
||||
```js
|
||||
const config = {
|
||||
createSocialMediaImages: true,
|
||||
};
|
||||
```
|
||||
|
||||
11
docs/guides/presets/options.md
Normal file
11
docs/guides/presets/options.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Presets >> Create your own > Options || 10
|
||||
|
||||
Your preset can hook into the rocket lifecycle by specifying a function for `before11ty`. This function runs before 11ty calls it's write method. If it is an async function, Rocket will await it's promise.
|
||||
|
||||
```js
|
||||
export default {
|
||||
async before11ty() {
|
||||
await copyDataFiles();
|
||||
},
|
||||
};
|
||||
```
|
||||
5
docs/simulator.md
Normal file
5
docs/simulator.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: layout-simulator
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSearch: true
|
||||
---
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.12.0",
|
||||
"@open-wc/testing": "^2.5.32",
|
||||
"@open-wc/testing": "^3.0.0-next.1",
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-typescript": "^8.1.0",
|
||||
@@ -75,14 +75,14 @@
|
||||
"onchange": "^7.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-plugin-package": "^1.3.0",
|
||||
"puppeteer": "^5.5.0",
|
||||
"puppeteer": "^9.0.0",
|
||||
"remark-emoji": "^2.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.36.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sinon": "^9.2.3",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.1.3"
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
"testing"
|
||||
],
|
||||
"dependencies": {
|
||||
"plugins-manager": "^0.2.0"
|
||||
"plugins-manager": "^0.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# @rocket/building-rollup
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 60e85a1: Support `picture` tags by handling `source` tags with `srcset` attributes in the rollup asset gathering build phase.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 2724f07: Stop auto generating a service worker from a template. Setup your own and then bundle via `createServiceWorkerConfig`.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- bad4686: Preserve attributes on script tags. Preserve export names.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- be0d0b3: fix: add missing main entry to the packages
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -6,3 +6,7 @@
|
||||
export { createBasicConfig, createBasicMetaConfig } from './src/createBasicConfig.js';
|
||||
export { createSpaConfig, createSpaMetaConfig } from './src/createSpaConfig.js';
|
||||
export { createMpaConfig, createMpaMetaConfig } from './src/createMpaConfig.js';
|
||||
export {
|
||||
createServiceWorkerConfig,
|
||||
createServiceWorkerMetaConfig,
|
||||
} from './src/createServiceWorkerConfig.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rocket/building-rollup",
|
||||
"version": "0.1.2",
|
||||
"version": "0.3.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
|
||||
"homepage": "https://rocket.modern-web.dev/docs/tools/building-rollup/",
|
||||
"main": "./index.js",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
@@ -54,11 +55,16 @@
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@rollup/plugin-babel": "^5.2.2",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"@web/rollup-plugin-html": "^1.4.0",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@web/rollup-plugin-html": "^1.8.0",
|
||||
"@web/rollup-plugin-import-meta-assets": "^1.0.4",
|
||||
"@web/rollup-plugin-polyfills-loader": "^1.0.3",
|
||||
"@web/rollup-plugin-polyfills-loader": "^1.1.0",
|
||||
"browserslist": "^4.16.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-workbox": "^6.1.0"
|
||||
"workbox-broadcast-update": "^6.1.5",
|
||||
"workbox-cacheable-response": "^6.1.5",
|
||||
"workbox-expiration": "^6.1.5",
|
||||
"workbox-routing": "^6.1.5",
|
||||
"workbox-strategies": "^6.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export function createBasicMetaConfig(userConfig = { output: {} }) {
|
||||
const assetName = `[${developmentMode ? 'name' : 'hash'}][extname]`;
|
||||
|
||||
const config = {
|
||||
preserveEntrySignatures: false,
|
||||
preserveEntrySignatures: 'strict',
|
||||
treeshake: !developmentMode,
|
||||
setupPlugins: [],
|
||||
...userConfig,
|
||||
|
||||
@@ -15,10 +15,6 @@ export function createMpaMetaConfig(userConfig = { output: {}, setupPlugins: []
|
||||
adjustPluginOptions('html', {
|
||||
flattenOutput: false,
|
||||
}),
|
||||
adjustPluginOptions('workbox', config => {
|
||||
delete config.navigateFallback;
|
||||
return config;
|
||||
}),
|
||||
...config.setupPlugins,
|
||||
];
|
||||
|
||||
|
||||
91
packages/building-rollup/src/createServiceWorkerConfig.js
Normal file
91
packages/building-rollup/src/createServiceWorkerConfig.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import babelPkg from '@rollup/plugin-babel';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
import { metaConfigToRollupConfig } from 'plugins-manager';
|
||||
|
||||
const { babel } = babelPkg;
|
||||
|
||||
export function createServiceWorkerConfig(userConfig) {
|
||||
const { config, metaPlugins } = createServiceWorkerMetaConfig(userConfig);
|
||||
return metaConfigToRollupConfig(config, metaPlugins);
|
||||
}
|
||||
|
||||
export function createServiceWorkerMetaConfig(userConfig = { output: {} }) {
|
||||
const developmentMode =
|
||||
typeof userConfig.developmentMode !== undefined
|
||||
? userConfig.developmentMode
|
||||
: !!process.env.ROLLUP_WATCH;
|
||||
delete userConfig.developmentMode;
|
||||
|
||||
const config = {
|
||||
treeshake: !developmentMode,
|
||||
setupPlugins: [],
|
||||
...userConfig,
|
||||
|
||||
output: {
|
||||
format: 'iife',
|
||||
file: 'service-worker.js',
|
||||
...userConfig.output,
|
||||
},
|
||||
};
|
||||
|
||||
let metaPlugins = [
|
||||
{
|
||||
name: 'node-resolve',
|
||||
plugin: resolve,
|
||||
options: {
|
||||
moduleDirectories: ['node_modules', 'web_modules'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'replace',
|
||||
plugin: replace,
|
||||
options: {
|
||||
'process.env.NODE_ENV': JSON.stringify(developmentMode ? 'development' : 'production'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'babel',
|
||||
plugin: babel,
|
||||
options: {
|
||||
babelHelpers: 'bundled',
|
||||
compact: true,
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: [
|
||||
'last 3 Chrome major versions',
|
||||
'last 3 ChromeAndroid major versions',
|
||||
'last 3 Firefox major versions',
|
||||
'last 3 Edge major versions',
|
||||
'last 3 Safari major versions',
|
||||
'last 3 iOS major versions',
|
||||
],
|
||||
useBuiltIns: false,
|
||||
shippedProposals: true,
|
||||
modules: false,
|
||||
bugfixes: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'terser',
|
||||
plugin: terser,
|
||||
options: {
|
||||
mangle: {
|
||||
toplevel: true,
|
||||
properties: {
|
||||
regex: /(^_|_$)/,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return { config, metaPlugins, developmentMode };
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import path from 'path';
|
||||
import { rollupPluginHTML } from '@web/rollup-plugin-html';
|
||||
import { generateSW } from 'rollup-plugin-workbox';
|
||||
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';
|
||||
import { polyfillsLoader } from '@web/rollup-plugin-polyfills-loader';
|
||||
import { metaConfigToRollupConfig } from 'plugins-manager';
|
||||
@@ -37,31 +35,6 @@ export function createSpaMetaConfig(userConfig = { output: {} }) {
|
||||
options: {
|
||||
rootDir,
|
||||
absoluteBaseUrl,
|
||||
injectServiceWorker: true,
|
||||
serviceWorkerPath: path.join(config.output.dir, 'service-worker.js'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'workbox',
|
||||
plugin: generateSW,
|
||||
options: {
|
||||
// Keep 'legacy-*.js' just for retro compatibility
|
||||
globIgnores: ['polyfills/*.js', 'legacy-*.js', 'nomodule-*.js'],
|
||||
navigateFallback: '/index.html',
|
||||
// where to output the generated sw
|
||||
swDest: path.join(config.output.dir, 'service-worker.js'),
|
||||
// directory to match patterns against to be precached
|
||||
globDirectory: path.join(config.output.dir),
|
||||
// cache any html js and css by default
|
||||
globPatterns: ['**/*.{html,js,css,webmanifest}'],
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: 'polyfills/*.js',
|
||||
handler: 'CacheFirst',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,13 +9,13 @@ describe('plugin count', () => {
|
||||
expect(config.plugins.length).to.equal(3);
|
||||
});
|
||||
|
||||
it('createSpaConfig has 7 plugins', () => {
|
||||
it('createSpaConfig has 6 plugins', () => {
|
||||
const config = createSpaConfig();
|
||||
expect(config.plugins.length).to.equal(7);
|
||||
expect(config.plugins.length).to.equal(6);
|
||||
});
|
||||
|
||||
it('createMpaConfig has 7 plugins', () => {
|
||||
it('createMpaConfig has 6 plugins', () => {
|
||||
const config = createMpaConfig();
|
||||
expect(config.plugins.length).to.equal(7);
|
||||
expect(config.plugins.length).to.equal(6);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,10 +26,7 @@ async function execute(configString) {
|
||||
const config = (await import(configPath)).default;
|
||||
await buildAndWrite(config);
|
||||
|
||||
return async (
|
||||
fileName,
|
||||
{ stripServiceWorker = false, stripToBody = false, stripStartEndWhitespace = true } = {},
|
||||
) => {
|
||||
return async (fileName, { stripToBody = false, stripStartEndWhitespace = true } = {}) => {
|
||||
let text = await fs.promises.readFile(
|
||||
path.join(config.output.dir, fileName.split('/').join(path.sep)),
|
||||
);
|
||||
@@ -39,11 +36,6 @@ async function execute(configString) {
|
||||
const bodyCloseTagStart = text.indexOf('</body>');
|
||||
text = text.substring(bodyOpenTagEnd, bodyCloseTagStart);
|
||||
}
|
||||
if (stripServiceWorker) {
|
||||
const scriptOpenTagEnd = text.indexOf('<script inject-service-worker');
|
||||
const scriptCloseTagStart = text.indexOf('</script>', scriptOpenTagEnd) + 9;
|
||||
text = text.substring(0, scriptOpenTagEnd) + text.substring(scriptCloseTagStart);
|
||||
}
|
||||
if (stripStartEndWhitespace) {
|
||||
text = text.trim();
|
||||
}
|
||||
@@ -57,25 +49,26 @@ describe('createMapConfig', () => {
|
||||
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
stripToBody: true,
|
||||
stripServiceWorker: true,
|
||||
});
|
||||
expect(indexHtml).to.equal('<h1>Only static HTML content in index.html</h1>');
|
||||
|
||||
const subHtmlIndexHtml = await readOutput('sub-html/index.html', {
|
||||
stripToBody: true,
|
||||
stripServiceWorker: true,
|
||||
});
|
||||
expect(subHtmlIndexHtml).to.equal('<h1>Only static HTML content in sub-html/index.html</h1>');
|
||||
|
||||
const subJsIndexHtml = await readOutput('sub-js/index.html', {
|
||||
stripToBody: true,
|
||||
stripServiceWorker: true,
|
||||
});
|
||||
expect(subJsIndexHtml).to.equal(
|
||||
'<h1>Has js in sub-js/index.html</h1>\n\n\n<script type="module" src="../sub-js.js"></script>',
|
||||
);
|
||||
|
||||
const serviceWorkerJs = await readOutput('service-worker.js');
|
||||
expect(serviceWorkerJs).to.include('Promise'); // not empty string might be enough...
|
||||
const subJsAbsoluteIndexHtml = await readOutput('sub-js-absolute/index.html', {
|
||||
stripToBody: true,
|
||||
});
|
||||
expect(subJsAbsoluteIndexHtml).to.equal(
|
||||
'<h1>Has js in sub-js-absolute/index.html</h1>\n\n\n<script type="module" src="../sub-js-absolute.js"></script>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<h1>Has js in sub-js-absolute/index.html</h1>
|
||||
<script type="module" src="/sub-js-absolute/sub-js-absolute.js"></script>
|
||||
@@ -1,5 +1,57 @@
|
||||
# check-html-links
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5043429: Ignore `<a href="tel:9999">` links
|
||||
- f08f926: Add missing `slash` dependency
|
||||
- a0e8edf: Ignore links containing not http schema urls like `sketch://`, `vscode://`, ...
|
||||
|
||||
```html
|
||||
<a href="sketch://add-library?url=https%3A%2F%2Fmyexample.com%2Fdesign%2Fui-kit.xml"></a>
|
||||
<a href="vscode://file/c:/myProject/package.json:5:10"></a>
|
||||
```
|
||||
|
||||
- 1949b1e: Ignore plain and html encoded mailto links
|
||||
|
||||
```html
|
||||
<!-- source -->
|
||||
<a href="mailto:address@example.com">contact</a>
|
||||
|
||||
<!-- html encoded -->
|
||||
<a
|
||||
href="mailto:address@example.com"
|
||||
>contact</a
|
||||
>
|
||||
```
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 66c2d78: fix: windows path issue
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- be0d0b3: fix: add missing main entry to the packages
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 750418b: Uses a class for the CLI and adding the following options:
|
||||
|
||||
- `--root-dir` the root directory to serve files from. Defaults to the current working directory
|
||||
- `--ignore-link-pattern` do not check links matching the pattern
|
||||
- `--continue-on-error` if present it will not exit with an error code - useful while writing or for temporary passing a ci
|
||||
|
||||
BREAKING CHANGE:
|
||||
|
||||
- Exists with an error code if a broken link is found
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { validateFolder } from './src/validateFolder.js';
|
||||
export { formatErrors } from './src/formatErrors.js';
|
||||
export { CheckHtmlLinksCli } from './src/CheckHtmlLinksCli.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "check-html-links",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.3",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
|
||||
"homepage": "https://rocket.modern-web.dev/docs/tools/check-html-links/",
|
||||
"main": "./index.js",
|
||||
"bin": {
|
||||
"check-html-links": "src/cli.js"
|
||||
},
|
||||
@@ -33,8 +34,11 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"command-line-args": "^5.1.1",
|
||||
"glob": "^7.0.0",
|
||||
"sax-wasm": "^2.0.0"
|
||||
"minimatch": "^3.0.4",
|
||||
"sax-wasm": "^2.0.0",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.0.0"
|
||||
|
||||
100
packages/check-html-links/src/CheckHtmlLinksCli.js
Normal file
100
packages/check-html-links/src/CheckHtmlLinksCli.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
/** @typedef {import('../types/main').CheckHtmlLinksCliOptions} CheckHtmlLinksCliOptions */
|
||||
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import { validateFiles } from './validateFolder.js';
|
||||
import { formatErrors } from './formatErrors.js';
|
||||
import { listFiles } from './listFiles.js';
|
||||
|
||||
export class CheckHtmlLinksCli {
|
||||
/** @type {CheckHtmlLinksCliOptions} */
|
||||
options;
|
||||
|
||||
constructor({ argv } = { argv: undefined }) {
|
||||
const mainDefinitions = [
|
||||
{ name: 'ignore-link-pattern', type: String, multiple: true },
|
||||
{ name: 'root-dir', type: String, defaultOption: true },
|
||||
{ name: 'continue-on-error', type: Boolean, defaultOption: false },
|
||||
];
|
||||
const options = commandLineArgs(mainDefinitions, {
|
||||
stopAtFirstUnknown: true,
|
||||
argv,
|
||||
});
|
||||
this.options = {
|
||||
printOnError: true,
|
||||
continueOnError: options['continue-on-error'],
|
||||
rootDir: options['root-dir'],
|
||||
ignoreLinkPatterns: options['ignore-link-pattern'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Partial<CheckHtmlLinksCliOptions>} newOptions
|
||||
*/
|
||||
setOptions(newOptions) {
|
||||
this.options = {
|
||||
...this.options,
|
||||
...newOptions,
|
||||
};
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { ignoreLinkPatterns, rootDir: userRootDir } = this.options;
|
||||
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
|
||||
const performanceStart = process.hrtime();
|
||||
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
|
||||
const filesOutput =
|
||||
files.length == 0
|
||||
? '🧐 No files to check. Did you select the correct folder?'
|
||||
: `🔥 Found a total of ${chalk.green.bold(files.length)} files to check!`;
|
||||
console.log(filesOutput);
|
||||
|
||||
const { errors, numberLinks } = await validateFiles(files, rootDir, { ignoreLinkPatterns });
|
||||
|
||||
console.log(`🔗 Found a total of ${chalk.green.bold(numberLinks)} links to validate!\n`);
|
||||
|
||||
const performance = process.hrtime(performanceStart);
|
||||
/** @type {string[]} */
|
||||
let output = [];
|
||||
let message = '';
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links) while checking ${
|
||||
files.length
|
||||
} files:`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
message = output.join('\n');
|
||||
if (this.options.printOnError === true) {
|
||||
console.error(message);
|
||||
}
|
||||
if (this.options.continueOnError === false) {
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`✅ All internal links are valid. (executed in ${performance[0]}s ${
|
||||
performance[1] / 1000000
|
||||
}ms)`,
|
||||
);
|
||||
}
|
||||
|
||||
return { errors, message };
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { validateFiles } from './validateFolder.js';
|
||||
import { formatErrors } from './formatErrors.js';
|
||||
import { listFiles } from './listFiles.js';
|
||||
import { CheckHtmlLinksCli } from './CheckHtmlLinksCli.js';
|
||||
|
||||
async function main() {
|
||||
const userRootDir = process.argv[2];
|
||||
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
|
||||
const performanceStart = process.hrtime();
|
||||
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
|
||||
const filesOutput =
|
||||
files.length == 0
|
||||
? '🧐 No files to check. Did you select the correct folder?'
|
||||
: `🔥 Found a total of ${chalk.green.bold(files.length)} files to check!`;
|
||||
console.log(filesOutput);
|
||||
|
||||
const { errors, numberLinks } = await validateFiles(files, rootDir);
|
||||
|
||||
console.log(`🔗 Found a total of ${chalk.green.bold(numberLinks)} links to validate!\n`);
|
||||
|
||||
const performance = process.hrtime(performanceStart);
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
const output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links) while checking ${
|
||||
files.length
|
||||
} files:`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
console.error(output.join('\n'));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(
|
||||
`✅ All internal links are valid. (executed in %ds %dms)`,
|
||||
performance[0],
|
||||
performance[1] / 1000000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
const cli = new CheckHtmlLinksCli();
|
||||
cli.run();
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import fs from 'fs';
|
||||
import saxWasm from 'sax-wasm';
|
||||
import minimatch from 'minimatch';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
import { listFiles } from './listFiles.js';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
|
||||
/** @typedef {import('../types/main').Link} Link */
|
||||
/** @typedef {import('../types/main').LocalFile} LocalFile */
|
||||
/** @typedef {import('../types/main').Usage} Usage */
|
||||
/** @typedef {import('../types/main').Error} Error */
|
||||
/** @typedef {import('../types/main').Options} Options */
|
||||
/** @typedef {import('sax-wasm').Attribute} Attribute */
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -43,7 +46,7 @@ function extractReferences(htmlFilePath) {
|
||||
if (ev === SaxEventType.Attribute) {
|
||||
const data = /** @type {Attribute} */ (/** @type {any} */ (_data));
|
||||
const attributeName = data.name.toString();
|
||||
const value = data.value.toString();
|
||||
const value = slash(data.value.toString());
|
||||
const entry = {
|
||||
attribute: attributeName,
|
||||
value,
|
||||
@@ -179,14 +182,27 @@ function getValueAndAnchor(inValue) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNonHttpSchema(url) {
|
||||
const found = url.match(/([a-z]+):/);
|
||||
if (found) {
|
||||
return found.length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Link[]} links
|
||||
* @param {object} options
|
||||
* @param {string} options.htmlFilePath
|
||||
* @param {string} options.rootDir
|
||||
* @param {function(string): boolean} options.ignoreUsage
|
||||
*/
|
||||
async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||
async function resolveLinks(links, { htmlFilePath, rootDir, ignoreUsage }) {
|
||||
for (const hrefObj of links) {
|
||||
const { value, anchor } = getValueAndAnchor(hrefObj.value);
|
||||
|
||||
@@ -201,8 +217,15 @@ async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||
|
||||
let valueFile = value.endsWith('/') ? path.join(value, 'index.html') : value;
|
||||
|
||||
if (value.includes('mailto:')) {
|
||||
if (ignoreUsage(value)) {
|
||||
// ignore
|
||||
} else if (
|
||||
value.startsWith('mailto:') ||
|
||||
value.startsWith('mailto:') // = "mailto:" but html encoded
|
||||
) {
|
||||
// ignore for now - could add a check to validate if the email address is valid
|
||||
} else if (value.startsWith('tel:')) {
|
||||
// ignore for now - could add a check to validate if the phone number is valid
|
||||
} else if (valueFile === '' && anchor !== '') {
|
||||
addLocalFile(htmlFilePath, anchor, usageObj);
|
||||
} else if (value.startsWith('//') || value.startsWith('http')) {
|
||||
@@ -213,6 +236,8 @@ async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||
addLocalFile(filePath, anchor, usageObj);
|
||||
} else if (value === '' && anchor === '') {
|
||||
// no need to check it
|
||||
} else if (isNonHttpSchema(value)) {
|
||||
// not a schema we handle
|
||||
} else {
|
||||
const filePath = path.join(path.dirname(htmlFilePath), valueFile);
|
||||
addLocalFile(filePath, anchor, usageObj);
|
||||
@@ -261,8 +286,9 @@ async function validateLocalFiles(checkLocalFiles) {
|
||||
/**
|
||||
* @param {string[]} files
|
||||
* @param {string} rootDir
|
||||
* @param {Options} opts?
|
||||
*/
|
||||
export async function validateFiles(files, rootDir) {
|
||||
export async function validateFiles(files, rootDir, opts) {
|
||||
await parserReferences.prepareWasm(saxWasmBuffer);
|
||||
await parserIds.prepareWasm(saxWasmBuffer);
|
||||
|
||||
@@ -270,10 +296,20 @@ export async function validateFiles(files, rootDir) {
|
||||
checkLocalFiles = [];
|
||||
idCache = new Map();
|
||||
let numberLinks = 0;
|
||||
|
||||
const ignoreLinkPatternRegExps = opts
|
||||
? opts.ignoreLinkPatterns?.map(pattern => minimatch.makeRe(pattern))
|
||||
: null;
|
||||
|
||||
/** @type {function(string): boolean} */
|
||||
const ignoreUsage = ignoreLinkPatternRegExps
|
||||
? usage => !!ignoreLinkPatternRegExps.find(regExp => usage.match(regExp))
|
||||
: () => false;
|
||||
|
||||
for (const htmlFilePath of files) {
|
||||
const { links } = await extractReferences(htmlFilePath);
|
||||
numberLinks += links.length;
|
||||
await resolveLinks(links, { htmlFilePath, rootDir });
|
||||
await resolveLinks(links, { htmlFilePath, rootDir, ignoreUsage });
|
||||
}
|
||||
await validateLocalFiles(checkLocalFiles);
|
||||
|
||||
@@ -282,10 +318,11 @@ export async function validateFiles(files, rootDir) {
|
||||
|
||||
/**
|
||||
* @param {string} inRootDir
|
||||
* @param {Options} opts?
|
||||
*/
|
||||
export async function validateFolder(inRootDir) {
|
||||
export async function validateFolder(inRootDir, opts) {
|
||||
const rootDir = path.resolve(inRootDir);
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
const { errors } = await validateFiles(files, rootDir);
|
||||
const { errors } = await validateFiles(files, rootDir, opts);
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- ignore known subsystems -->
|
||||
<a href="/docs/"></a>
|
||||
<a href="/developer/getting-started.html#js"></a>
|
||||
<a href="/developer/language-guides/"></a>
|
||||
<a href="/developer/javascript.html"></a>
|
||||
@@ -0,0 +1,8 @@
|
||||
<a href="/absolute/index.html"></a>
|
||||
<a href="./relative/index.html"></a>
|
||||
<a href="./relative/subfolder/index.html"></a>
|
||||
|
||||
<!-- valid -->
|
||||
<a href="./page.html"></a>
|
||||
<a href=" ./page.html "></a>
|
||||
<a href=" /page.html "></a>
|
||||
@@ -1 +1,3 @@
|
||||
<a href="mailto:foo@bar.com"></a>
|
||||
<!-- encoded mailto links -->
|
||||
<a href="mailto:address@example.com"></a>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<a href="sketch://add-library?url=https%3A%2F%2Fmyexample.com%2Fdesign%2Fui-kit.xml"></a>
|
||||
<a href="vscode://file/c:/myProject/package.json:5:10"></a>
|
||||
@@ -0,0 +1 @@
|
||||
<a href="tel:99999"></a>
|
||||
@@ -5,9 +5,9 @@ import { validateFolder } from 'check-html-links';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export async function execute(inPath) {
|
||||
export async function execute(inPath, opts) {
|
||||
const testDir = path.join(__dirname, inPath.split('/').join(path.sep));
|
||||
const errors = await validateFolder(testDir);
|
||||
const errors = await validateFolder(testDir, opts);
|
||||
return {
|
||||
cleanup: items => {
|
||||
const newItems = [];
|
||||
|
||||
@@ -183,6 +183,38 @@ describe('validateFolder', () => {
|
||||
expect(cleanup(errors)).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('ignores tel links', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/tel');
|
||||
expect(cleanup(errors)).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('ignore not http schema urls', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/not-http-schema');
|
||||
expect(cleanup(errors)).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('ignoring a folder', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/internal-link-ignore', {
|
||||
ignoreLinkPatterns: ['./relative/*', './relative/**/*'],
|
||||
});
|
||||
expect(cleanup(errors)).to.deep.equal([
|
||||
{
|
||||
filePath: 'fixtures/internal-link-ignore/absolute/index.html',
|
||||
onlyAnchorMissing: false,
|
||||
usage: [
|
||||
{
|
||||
anchor: '',
|
||||
attribute: 'href',
|
||||
character: 9,
|
||||
file: 'fixtures/internal-link-ignore/index.html',
|
||||
line: 0,
|
||||
value: '/absolute/index.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('can handle img src', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/internal-images');
|
||||
expect(cleanup(errors)).to.deep.equal([
|
||||
|
||||
11
packages/check-html-links/types/main.d.ts
vendored
11
packages/check-html-links/types/main.d.ts
vendored
@@ -25,3 +25,14 @@ export interface Error {
|
||||
onlyAnchorMissing: boolean;
|
||||
usage: Usage[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
ignoreLinkPatterns: string[] | null;
|
||||
}
|
||||
|
||||
export interface CheckHtmlLinksCliOptions {
|
||||
printOnError: boolean;
|
||||
rootDir: string;
|
||||
ignoreLinkPatterns: string[] | null;
|
||||
continueOnError: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,187 @@
|
||||
# @rocket/cli
|
||||
|
||||
## 0.9.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1b9559f: Adds `before11ty` hook to config and presets
|
||||
|
||||
## 0.9.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2b5c61d: Allow configuring the imagePreset ignore rules via the option `ignore`
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
// ...
|
||||
ignore: ({ src }) =>
|
||||
src.endsWith('.jpeg') || src.endsWith('svg') || src.includes('rocket-unresponsive.'),
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
- 2b5c61d: Do not generate responsive images for files ending in `.svg` or that include `rocket-ignore.`
|
||||
- ce0b00e: don't transform external images
|
||||
- 3b1a0cf: Allow to configure check-html-links
|
||||
|
||||
```js
|
||||
export default {
|
||||
checkLinks: {
|
||||
/* ... */
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 0.9.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 795a361: The server worker url should respect a set pathPrefix.
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5330740: When replacing images with responsive picture tags do this from the bottom up so the initial dom parsing locations still hold true.
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 43a7ca1: Responsive images need to respect a set pathPrefix
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- eae2007: Update to mdjs version that uses lit 2 and renders stories to light dom
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [eae2007]
|
||||
- @rocket/eleventy-plugin-mdjs-unified@0.5.0
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 60e85a1: Support `picture` tags by handling `source` tags with `srcset` attributes in the rollup asset gathering build phase.
|
||||
- Updated dependencies [60e85a1]
|
||||
- @rocket/building-rollup@0.3.1
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c338696: Updated dependency of eleventy-img for M1 compatibility
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8bba4a8: Every content image in markdown will outputted in multiple widths and formats to ensure small image file sizes while retaining quality.
|
||||
You can adjust the defaults by setting `imagePresets.responsive`.
|
||||
|
||||
```js
|
||||
export default {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [600, 900, 1640],
|
||||
formats: ['avif', 'jpeg'],
|
||||
sizes: '(min-width: 1024px) 820px, calc(100vw - 40px)',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 2724f07: The service worker no longer precaches all urls and assets. It now
|
||||
|
||||
- caches already visited pages
|
||||
- caches assets of visited pages (up to 100 files then it replaces older entries)
|
||||
- on service worker activation it will reload the page if a newer version is available
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2724f07]
|
||||
- @rocket/building-rollup@0.3.0
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2b7f1ee: Add support for pathprefix
|
||||
- Updated dependencies [2b7f1ee]
|
||||
- @rocket/eleventy-plugin-mdjs-unified@0.4.1
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ed86ff2: Do not set `data-localize-lang` in the simulator iframe html tag
|
||||
- f4a0ab5: Pass document shadowRoot to iframe
|
||||
- c675820: fix: windows path issue avoid filtering of index section of collections
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- abc8a02: You can now use an absolute path for the rootDir
|
||||
- Updated dependencies [bad4686]
|
||||
- Updated dependencies [2267e72]
|
||||
- @rocket/building-rollup@0.2.0
|
||||
- @rocket/eleventy-plugin-mdjs-unified@0.4.0
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5edc40f: Make sure each rocket instance has it's own eleventy config'
|
||||
- ef8ebb0: To support dynamically created content to be part of the anchor navigation of the page we now analyze the final html output instead of `entry.templateContent`.
|
||||
|
||||
BREAKING CHANGE:
|
||||
|
||||
- only add anchors for the currently active pages (before it added anchor for every page)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- be0d0b3: fix: add missing main entry to the packages
|
||||
- Updated dependencies [be0d0b3]
|
||||
- Updated dependencies [ef8ebb0]
|
||||
- @rocket/building-rollup@0.1.3
|
||||
- check-html-links@0.2.1
|
||||
- @rocket/core@0.1.2
|
||||
- plugins-manager@0.2.1
|
||||
- @rocket/eleventy-rocket-nav@0.3.0
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8e095b7: Watching `_assets`, `_data`, `_includes` for changes to trigger updated automatically
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f44a0f4: Rewrite dynamic imports with "`"
|
||||
- 74f7ddf: Adds performance improvements for social media images by:
|
||||
- creating social media images only in `rocket build` phase
|
||||
- adds a config `createSocialMediaImages` to enable (default) or disable it globally
|
||||
- adds config `start.createSocialMediaImages` to enable or disable (default) it during `rocket start`
|
||||
- 750418b: Use class-based node API of check-html-links
|
||||
- Updated dependencies [f44a0f4]
|
||||
- Updated dependencies [750418b]
|
||||
- @rocket/eleventy-plugin-mdjs-unified@0.3.1
|
||||
- check-html-links@0.2.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rocket/cli",
|
||||
"version": "0.5.0",
|
||||
"version": "0.9.5",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
|
||||
"homepage": "https://rocket.modern-web.dev/",
|
||||
"main": "./index.cjs",
|
||||
"bin": {
|
||||
"rocket": "src/cli.js"
|
||||
},
|
||||
@@ -55,23 +56,26 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@11ty/eleventy": "^0.11.1",
|
||||
"@11ty/eleventy-img": "^0.7.4",
|
||||
"@rocket/building-rollup": "^0.1.2",
|
||||
"@rocket/core": "^0.1.1",
|
||||
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.0",
|
||||
"@rocket/eleventy-rocket-nav": "^0.2.1",
|
||||
"@11ty/eleventy-img": "^0.9.0",
|
||||
"@rocket/building-rollup": "^0.3.1",
|
||||
"@rocket/core": "^0.1.2",
|
||||
"@rocket/eleventy-plugin-mdjs-unified": "^0.5.0",
|
||||
"@rocket/eleventy-rocket-nav": "^0.3.0",
|
||||
"@rollup/plugin-babel": "^5.2.2",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"@web/config-loader": "^0.1.3",
|
||||
"@web/dev-server": "^0.1.4",
|
||||
"@web/dev-server-rollup": "^0.3.2",
|
||||
"@web/rollup-plugin-copy": "^0.2.0",
|
||||
"check-html-links": "^0.1.2",
|
||||
"check-html-links": "^0.2.3",
|
||||
"command-line-args": "^5.1.1",
|
||||
"command-line-usage": "^6.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"plugins-manager": "^0.2.0",
|
||||
"utf8": "^3.0.0"
|
||||
"micromatch": "^4.0.2",
|
||||
"plugins-manager": "^0.2.2",
|
||||
"slash": "^3.0.0",
|
||||
"utf8": "^3.0.0",
|
||||
"workbox-window": "^6.1.5"
|
||||
},
|
||||
"types": "dist-types/index.d.ts"
|
||||
}
|
||||
|
||||
22
packages/cli/preset/_assets/scripts/registerServiceWorker.js
Normal file
22
packages/cli/preset/_assets/scripts/registerServiceWorker.js
Normal file
@@ -0,0 +1,22 @@
|
||||
(async () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const { Workbox } = await import('workbox-window');
|
||||
|
||||
const url = window.__rocketServiceWorkerUrl || '/service-worker.js';
|
||||
const wb = new Workbox(url);
|
||||
wb.addEventListener('message', event => {
|
||||
if (event.data.type === 'CACHE_UPDATED') {
|
||||
const { updatedURL } = event.data.payload;
|
||||
console.log(`Reloading as a newer version of ${updatedURL} became available!`);
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
wb.register()
|
||||
.then(function () {
|
||||
console.log('ServiceWorker registered.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
});
|
||||
}
|
||||
})();
|
||||
29
packages/cli/preset/_assets/service-worker.js
Normal file
29
packages/cli/preset/_assets/service-worker.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
||||
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
|
||||
addEventListener('install', () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
skipWaiting();
|
||||
});
|
||||
|
||||
// addEventListener('activate', () => {
|
||||
// console.log('activate');
|
||||
// });
|
||||
|
||||
const cacheFirst = new CacheFirst({
|
||||
cacheName: 'assets',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 100,
|
||||
}),
|
||||
],
|
||||
});
|
||||
const staleWhileRevalidate = new StaleWhileRevalidate({
|
||||
cacheName: 'pages',
|
||||
plugins: [new BroadcastUpdatePlugin()],
|
||||
});
|
||||
|
||||
registerRoute(/(\/|\.html)$/, staleWhileRevalidate);
|
||||
registerRoute(/\.(css|m?js|svg|woff2|png|jpg|gif|json|xml)$/, cacheFirst);
|
||||
@@ -0,0 +1,7 @@
|
||||
{% set rocketServiceWorkerUrl = '/' + rocketConfig.serviceWorkerName %}
|
||||
|
||||
<script>
|
||||
window.__rocketServiceWorkerUrl = '{{ rocketServiceWorkerUrl | url }}';
|
||||
</script>
|
||||
|
||||
<script type="module" inject-service-worker="" src="{{ '/_assets/scripts/registerServiceWorker.js' | asset | url }}"></script>
|
||||
@@ -1,3 +0,0 @@
|
||||
<script>
|
||||
{{ '_assets/_inline-scripts/serviceWorkerUpdate.js' | asset | toAbsPath | inlineFilePath | safe }}
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<meta property="og:site_name" content="{{ site.name }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
|
||||
<meta property="og:image" content="{{ socialMediaImage }}"/>
|
||||
<meta property="og:url" content="{{ page.url }}"/>
|
||||
<meta property="og:image" content="{{ socialMediaImage | url }}"/>
|
||||
<meta property="og:url" content="{{ page.url | url }}"/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
|
||||
69
packages/cli/preset/_includes/layout-simulator.njk
Normal file
69
packages/cli/preset/_includes/layout-simulator.njk
Normal file
@@ -0,0 +1,69 @@
|
||||
<html theme="light" platform="web" lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
height: fit-content;
|
||||
}
|
||||
html[edge-distance] body {
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
<script type="module">
|
||||
import { render } from '@mdjs/mdjs-story';
|
||||
|
||||
async function onHashChange() {
|
||||
const urlParts = new URLSearchParams(document.location.hash.substr(1));
|
||||
|
||||
if (urlParts.get('stylesheets')) {
|
||||
for (const stylesheet of urlParts.getAll('stylesheets')) {
|
||||
if (!document.querySelector(`link[rel="stylesheet"][href="${stylesheet}"]`)) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = stylesheet;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (urlParts.get('theme')) {
|
||||
document.documentElement.setAttribute('theme', urlParts.get('theme'));
|
||||
}
|
||||
if (urlParts.get('platform')) {
|
||||
document.documentElement.setAttribute('platform', urlParts.get('platform'));
|
||||
}
|
||||
if (urlParts.get('language')) {
|
||||
document.documentElement.setAttribute('lang', urlParts.get('language'));
|
||||
document.documentElement.setAttribute('data-lang', urlParts.get('language'));
|
||||
}
|
||||
if (urlParts.get('story-key')) {
|
||||
document.documentElement.setAttribute('story-key', urlParts.get('story-key'));
|
||||
}
|
||||
if (urlParts.get('edge-distance') === 'true') {
|
||||
document.documentElement.setAttribute('edge-distance', '');
|
||||
} else {
|
||||
document.documentElement.removeAttribute('edge-distance');
|
||||
}
|
||||
|
||||
const mod = await import(urlParts.get('story-file'));
|
||||
render(mod[urlParts.get('story-key')]({ shadowRoot: document }), document.body);
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', onHashChange, false);
|
||||
onHashChange();
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
const dimensions = document.body.getBoundingClientRect();
|
||||
const data = {
|
||||
action: 'mdjs-viewer-resize',
|
||||
storyKey: document.documentElement.getAttribute('story-key'),
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
};
|
||||
parent.postMessage(JSON.stringify(data), '*');
|
||||
});
|
||||
observer.observe(document.body);
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
import { rollup } from 'rollup';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { copy } from '@web/rollup-plugin-copy';
|
||||
|
||||
import { createMpaConfig } from '@rocket/building-rollup';
|
||||
import { createMpaConfig, createServiceWorkerConfig } from '@rocket/building-rollup';
|
||||
import { addPlugin, adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/**
|
||||
@@ -45,7 +46,7 @@ async function productionBuild(config) {
|
||||
dir: config.outputDir,
|
||||
},
|
||||
// custom
|
||||
rootDir: config.outputDevDir,
|
||||
rootDir: path.resolve(config.outputDevDir),
|
||||
absoluteBaseUrl: config.absoluteBaseUrl,
|
||||
setupPlugins: [
|
||||
...defaultSetupPlugins,
|
||||
@@ -55,6 +56,18 @@ async function productionBuild(config) {
|
||||
});
|
||||
|
||||
await buildAndWrite(mpaConfig);
|
||||
|
||||
const serviceWorkerSourcePath = path.resolve('docs/_merged_assets/service-worker.js');
|
||||
if (fs.existsSync(serviceWorkerSourcePath)) {
|
||||
const serviceWorkerConfig = createServiceWorkerConfig({
|
||||
input: serviceWorkerSourcePath,
|
||||
output: {
|
||||
file: path.join(path.resolve(config.outputDir), config.serviceWorkerName),
|
||||
},
|
||||
});
|
||||
|
||||
await buildAndWrite(serviceWorkerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
export class RocketBuild {
|
||||
|
||||
@@ -10,6 +10,7 @@ import computedConfigPkg from './public/computedConfig.cjs';
|
||||
|
||||
import path from 'path';
|
||||
import Eleventy from '@11ty/eleventy';
|
||||
import TemplateConfig from '@11ty/eleventy/src/TemplateConfig.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
@@ -30,9 +31,39 @@ export class RocketEleventy extends Eleventy {
|
||||
|
||||
async write() {
|
||||
await this.__rocketCli.mergePresets();
|
||||
for (const fn of this.__rocketCli.config.__before11tyFunctions) await fn();
|
||||
await super.write();
|
||||
await this.__rocketCli.update();
|
||||
}
|
||||
|
||||
// forks it so we can watch for changes but don't include them while building
|
||||
getChokidarConfig() {
|
||||
let ignores = this.eleventyFiles.getGlobWatcherIgnores();
|
||||
|
||||
const keepWatching = [
|
||||
path.join(this.__rocketCli.config._inputDirCwdRelative, '_assets', '**'),
|
||||
path.join(this.__rocketCli.config._inputDirCwdRelative, '_data', '**'),
|
||||
path.join(this.__rocketCli.config._inputDirCwdRelative, '_includes', '**'),
|
||||
];
|
||||
|
||||
ignores = ignores.filter(ignore => !keepWatching.includes(ignore));
|
||||
// debug("Ignoring watcher changes to: %o", ignores);
|
||||
|
||||
let configOptions = this.config.chokidarConfig;
|
||||
|
||||
// can’t override these yet
|
||||
// TODO maybe if array, merge the array?
|
||||
delete configOptions.ignored;
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
ignored: ignores,
|
||||
ignoreInitial: true,
|
||||
// also interesting: awaitWriteFinish
|
||||
},
|
||||
configOptions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RocketCli {
|
||||
@@ -70,11 +101,16 @@ export class RocketCli {
|
||||
await this.mergePresets();
|
||||
|
||||
const elev = new RocketEleventy(_inputDirCwdRelative, outputDevDir, this);
|
||||
elev.isVerbose = false;
|
||||
// 11ty always wants a relative path to cwd - why?
|
||||
const rel = path.relative(process.cwd(), path.join(__dirname));
|
||||
const relCwdPathToConfig = path.join(rel, 'shared', '.eleventy.cjs');
|
||||
|
||||
const config = new TemplateConfig(null, relCwdPathToConfig);
|
||||
elev.config = config.getConfig();
|
||||
elev.resetConfig();
|
||||
elev.setConfigPathOverride(relCwdPathToConfig);
|
||||
|
||||
elev.isVerbose = false;
|
||||
await elev.init();
|
||||
|
||||
this.eleventy = elev;
|
||||
@@ -85,7 +121,7 @@ export class RocketCli {
|
||||
for (const folder of ['_assets', '_data', '_includes']) {
|
||||
const to = path.join(this.config._inputDirCwdRelative, `_merged${folder}`);
|
||||
await fs.emptyDir(to);
|
||||
for (const sourceDir of this.config._presetPathes) {
|
||||
for (const sourceDir of this.config._presetPaths) {
|
||||
const from = path.join(sourceDir, folder);
|
||||
if (fs.existsSync(from)) {
|
||||
if (folder === '_includes') {
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
/** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { validateFolder, formatErrors } from 'check-html-links';
|
||||
import { CheckHtmlLinksCli } from 'check-html-links';
|
||||
|
||||
export class RocketLint {
|
||||
static pluginName = 'RocketLint';
|
||||
@@ -49,31 +48,21 @@ export class RocketLint {
|
||||
return;
|
||||
}
|
||||
|
||||
const performanceStart = process.hrtime();
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const errors = await validateFolder(this.config.lintInputDir);
|
||||
const performance = process.hrtime(performanceStart);
|
||||
const checkLinks = new CheckHtmlLinksCli();
|
||||
checkLinks.setOptions({
|
||||
...this.config.checkLinks,
|
||||
rootDir: this.config.lintInputDir,
|
||||
printOnError: false,
|
||||
continueOnError: true,
|
||||
});
|
||||
|
||||
const { errors, message } = await checkLinks.run();
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
const output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links):`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
if (this.config.watch) {
|
||||
console.log(output.join('\n'));
|
||||
if (this.config.command === 'start') {
|
||||
console.log(message);
|
||||
} else {
|
||||
throw new Error(output.join('\n'));
|
||||
throw new Error(message);
|
||||
}
|
||||
} else {
|
||||
console.log('✅ All internal links are valid.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export class RocketStart {
|
||||
*/
|
||||
setupCommand(config) {
|
||||
delete config.pathPrefix;
|
||||
config.createSocialMediaImages = !!config?.start?.createSocialMediaImages;
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
265
packages/cli/src/eleventy-plugins/insertResponsiveImages.cjs
Normal file
265
packages/cli/src/eleventy-plugins/insertResponsiveImages.cjs
Normal file
@@ -0,0 +1,265 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const EleventyImage = require('@11ty/eleventy-img');
|
||||
const urlFilter = require('@11ty/eleventy/src/Filters/Url.js');
|
||||
const { SaxEventType, SAXParser } = require('sax-wasm');
|
||||
const { getComputedConfig } = require('../public/computedConfig.cjs');
|
||||
|
||||
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
|
||||
const saxWasmBuffer = fs.readFileSync(saxPath);
|
||||
|
||||
/** @typedef {import('../types').Heading} Heading */
|
||||
|
||||
/** @typedef {import('sax-wasm').Text} Text */
|
||||
/** @typedef {import('sax-wasm').Tag} Tag */
|
||||
/** @typedef {import('sax-wasm').Position} Position */
|
||||
|
||||
// Instantiate
|
||||
const parser = new SAXParser(
|
||||
SaxEventType.CloseTag,
|
||||
{ highWaterMark: 256 * 1024 }, // 256k chunks
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.html
|
||||
* @param {Position} options.start
|
||||
* @param {Position} options.end
|
||||
* @param {string} options.insert
|
||||
*/
|
||||
function replaceBetween({ html, start, end, insert = '' }) {
|
||||
const lines = html.split('\n');
|
||||
const i = start.line;
|
||||
const line = lines[i];
|
||||
const upToChange = line.slice(0, start.character);
|
||||
const afterChange = line.slice(end.character - 4);
|
||||
|
||||
lines[i] = `${upToChange}${insert}${afterChange}`;
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Tag} data
|
||||
* @param {string} name
|
||||
*/
|
||||
function getAttribute(data, name) {
|
||||
if (data.attributes) {
|
||||
const { attributes } = data;
|
||||
const foundIndex = attributes.findIndex(entry => entry.name.value === name);
|
||||
if (foundIndex !== -1) {
|
||||
return attributes[foundIndex].value.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Tag} data
|
||||
*/
|
||||
function getAttributes(data) {
|
||||
if (data.attributes) {
|
||||
const { attributes } = data;
|
||||
return attributes.map(entry => ({ name: entry.name.value, value: entry.value.value }));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @param {Tag} data
|
||||
// */
|
||||
// function getText(data) {
|
||||
// if (data.textNodes) {
|
||||
// return data.textNodes.map(textNode => textNode.value).join('');
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * @param {Tag} data
|
||||
// */
|
||||
// function getClassList(data) {
|
||||
// const classString = getAttribute(data, 'class');
|
||||
// return classString ? classString.split(' ') : [];
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param src {string} image src attribute value.
|
||||
* @returns {boolean} true if src starts with https://, http:// or //
|
||||
*/
|
||||
|
||||
function isExternalSrc(src) {
|
||||
return /^(?:https?:)?\/\//.test(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
function getImages(html, { imagePresets }) {
|
||||
/** @type {Heading[]} */
|
||||
const images = [];
|
||||
parser.eventHandler = (ev, _data) => {
|
||||
if (ev === SaxEventType.CloseTag) {
|
||||
const data = /** @type {Tag} */ (/** @type {any} */ (_data));
|
||||
if (data.name === 'img') {
|
||||
const { openStart, closeEnd } = data;
|
||||
|
||||
const attributes = getAttributes(data);
|
||||
const presetName = getAttribute(data, 'rocket-image');
|
||||
const src = getAttribute(data, 'src');
|
||||
const title = getAttribute(data, 'title');
|
||||
const alt = getAttribute(data, 'alt');
|
||||
|
||||
if (presetName) {
|
||||
const presetSettings = imagePresets[presetName];
|
||||
if (!presetSettings) {
|
||||
throw new Error(`Could not find imagePresets: { ${presetName}: {} }`);
|
||||
}
|
||||
const { ignore } = presetSettings;
|
||||
const ignoreFn = typeof ignore === 'function' ? ignore : () => false;
|
||||
|
||||
if (!isExternalSrc(src) && !ignoreFn({ src, title, alt, attributes })) {
|
||||
images.push({
|
||||
presetName,
|
||||
attributes,
|
||||
src,
|
||||
title,
|
||||
alt,
|
||||
openStart,
|
||||
closeEnd,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parser.write(Buffer.from(html, 'utf8'));
|
||||
parser.end();
|
||||
|
||||
// @ts-ignore
|
||||
return images;
|
||||
}
|
||||
|
||||
function getSrcsetAttribute(imageFormat) {
|
||||
return `srcset="${imageFormat.map(entry => entry.srcset).join(', ')}"`;
|
||||
}
|
||||
|
||||
async function responsiveImages(images, { inputPath, outputDir, imagePresets = {} }) {
|
||||
for (let i = 0; i < images.length; i += 1) {
|
||||
const { alt, filePath, title, src, presetName, attributes } = images[i];
|
||||
|
||||
if (alt === undefined) {
|
||||
throw new Error(`Missing \`alt\` on responsive image from: ${src} in ${inputPath}`);
|
||||
}
|
||||
|
||||
const presetSettings = imagePresets[presetName];
|
||||
if (!presetSettings) {
|
||||
throw new Error(`Could not find imagePresets: { ${presetName}: {} }`);
|
||||
}
|
||||
const sizes = presetSettings.sizes || '100vw';
|
||||
|
||||
const metadata = await EleventyImage(filePath, {
|
||||
outputDir: path.join(outputDir, 'images'),
|
||||
urlPath: urlFilter('/images/'),
|
||||
...presetSettings,
|
||||
});
|
||||
const lowsrc = metadata.jpeg[0];
|
||||
|
||||
let pictureStartWithSources = '';
|
||||
let srcsetAttribute = '';
|
||||
let sizesAttribute = '';
|
||||
let pictureEnd = '';
|
||||
|
||||
if (Object.keys(metadata).length > 1) {
|
||||
const sources = Object.values(metadata)
|
||||
.map(imageFormat => {
|
||||
return `<source type="${imageFormat[0].sourceType}" ${getSrcsetAttribute(
|
||||
imageFormat,
|
||||
)} sizes="${sizes}">`;
|
||||
})
|
||||
.join('\n');
|
||||
pictureStartWithSources = `<picture>\n${sources}`;
|
||||
pictureEnd = '</picture>';
|
||||
} else {
|
||||
srcsetAttribute = getSrcsetAttribute(Object.values(metadata)[0]);
|
||||
sizesAttribute = `sizes="${sizes}"`;
|
||||
}
|
||||
const attributesString = attributes
|
||||
.filter(attribute => !['src', 'title'].includes(attribute.name))
|
||||
.map(attribute => `${attribute.name}="${attribute.value}"`)
|
||||
.join(' ');
|
||||
|
||||
const figureStart = title ? '<figure>' : '';
|
||||
const figureEndWithCaption = title ? `<figcaption>${title}</figcaption>\n</figure>` : '';
|
||||
|
||||
images[i].newHtml = `
|
||||
${figureStart}
|
||||
${pictureStartWithSources}
|
||||
<img
|
||||
${attributesString}
|
||||
src="${lowsrc.url}"
|
||||
${srcsetAttribute}
|
||||
${sizesAttribute}
|
||||
width="${lowsrc.width}"
|
||||
height="${lowsrc.height}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
${pictureEnd}
|
||||
${figureEndWithCaption}
|
||||
`;
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
function updateHtml(html, changes) {
|
||||
let newHtml = html;
|
||||
for (const change of changes.reverse()) {
|
||||
newHtml = replaceBetween({
|
||||
html: newHtml,
|
||||
start: change.openStart,
|
||||
end: change.closeEnd,
|
||||
insert: change.newHtml,
|
||||
});
|
||||
}
|
||||
return newHtml;
|
||||
}
|
||||
|
||||
function resolveFilePath(images, { inputPath }) {
|
||||
for (let i = 0; i < images.length; i += 1) {
|
||||
images[i].filePath = path.join(path.dirname(inputPath), images[i].src);
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
let isSetup = false;
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
async function insertResponsiveImages(html) {
|
||||
const config = getComputedConfig();
|
||||
|
||||
if (!isSetup) {
|
||||
await parser.prepareWasm(saxWasmBuffer);
|
||||
isSetup = true;
|
||||
}
|
||||
|
||||
const options = {
|
||||
inputPath: this.inputPath,
|
||||
outputDir: this.outputDir,
|
||||
imagePresets: config.imagePresets,
|
||||
};
|
||||
|
||||
let images = getImages(html, options);
|
||||
images = resolveFilePath(images, options);
|
||||
images = await responsiveImages(images, options);
|
||||
const newHtml = updateHtml(html, images);
|
||||
|
||||
return newHtml;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
insertResponsiveImages,
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const slash = require('slash');
|
||||
const { readdirSync } = require('fs');
|
||||
|
||||
function getDirectories(source) {
|
||||
@@ -23,7 +24,7 @@ const rocketCollections = {
|
||||
let docs = [
|
||||
...collection.getFilteredByGlob(`${_inputDirCwdRelative}/${section}/**/*.md`),
|
||||
];
|
||||
docs = docs.filter(page => page.inputPath !== `./${indexSection}`);
|
||||
docs = docs.filter(page => page.inputPath !== `./${slash(indexSection)}`);
|
||||
|
||||
return docs;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { processLocalReferences } = require('./processLocalReferences.cjs');
|
||||
const { insertResponsiveImages } = require('./insertResponsiveImages.cjs');
|
||||
|
||||
function inlineFilePath(filePath) {
|
||||
let data = fs.readFileSync(filePath, function (err, contents) {
|
||||
@@ -24,6 +25,7 @@ const rocketFilters = {
|
||||
|
||||
eleventyConfig.addFilter('inlineFilePath', inlineFilePath);
|
||||
|
||||
eleventyConfig.addTransform('insertResponsiveImages', insertResponsiveImages);
|
||||
eleventyConfig.addTransform('processLocalReferences', processLocalReferences);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
|
||||
|
||||
/** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
|
||||
/** @typedef {import('../types/main').ImagePreset} ImagePreset */
|
||||
/** @typedef {import('../types/main').RocketPlugin} RocketPlugin */
|
||||
|
||||
import path from 'path';
|
||||
@@ -19,9 +20,25 @@ import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* The default responsive ignore function will ignore files
|
||||
* - ending in `.svg`
|
||||
* - containing `rocket-unresponsive.`
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string} opts.src
|
||||
* @param {string} opts.title
|
||||
* @param {string} opts.alt
|
||||
* @param {{name: string, value: string}[]} opts.attributes
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function ignore({ src }) {
|
||||
return src.endsWith('svg') || src.includes('rocket-unresponsive.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Partial<RocketCliOptions>} inConfig
|
||||
* @returns {Promise<RocketCliOptions>}
|
||||
* @returns {Promise<RocketCliOptions & { __before11tyFunctions: RocketCliOptions['before11ty'][] }>}
|
||||
*/
|
||||
export async function normalizeConfig(inConfig) {
|
||||
let config = {
|
||||
@@ -36,18 +53,39 @@ export async function normalizeConfig(inConfig) {
|
||||
eleventy: () => {},
|
||||
command: 'help',
|
||||
watch: true,
|
||||
createSocialMediaImages: true,
|
||||
inputDir: 'docs',
|
||||
outputDir: '_site',
|
||||
outputDevDir: '_site-dev',
|
||||
serviceWorkerName: 'service-worker.js',
|
||||
build: {},
|
||||
devServer: {},
|
||||
|
||||
...inConfig,
|
||||
|
||||
/** @type{RocketCliOptions['before11ty'][]} */
|
||||
__before11tyFunctions: [],
|
||||
|
||||
/** @type{{[key: string]: ImagePreset}} */
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [600, 900, 1640],
|
||||
formats: ['avif', 'jpeg'],
|
||||
sizes: '100vw',
|
||||
ignore,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (inConfig && inConfig.devServer) {
|
||||
config.devServer = { ...config.devServer, ...inConfig.devServer };
|
||||
}
|
||||
if (inConfig.imagePresets && inConfig.imagePresets.responsive) {
|
||||
config.imagePresets.responsive = {
|
||||
...config.imagePresets.responsive,
|
||||
...inConfig.imagePresets.responsive,
|
||||
};
|
||||
}
|
||||
|
||||
let userConfigFile;
|
||||
let __configDir = process.cwd();
|
||||
@@ -71,7 +109,14 @@ export async function normalizeConfig(inConfig) {
|
||||
...config.devServer,
|
||||
...fileConfig.devServer,
|
||||
},
|
||||
imagePresets: config.imagePresets,
|
||||
};
|
||||
if (fileConfig.imagePresets && fileConfig.imagePresets.responsive) {
|
||||
config.imagePresets.responsive = {
|
||||
...config.imagePresets.responsive,
|
||||
...fileConfig.imagePresets.responsive,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Could not read rocket config file', error);
|
||||
@@ -82,9 +127,13 @@ export async function normalizeConfig(inConfig) {
|
||||
const _inputDirCwdRelative = path.join(_configDirCwdRelative, config.inputDir);
|
||||
|
||||
// cli core preset is always first
|
||||
config._presetPathes = [path.join(__dirname, '..', 'preset')];
|
||||
config._presetPaths = [path.join(__dirname, '..', 'preset')];
|
||||
for (const preset of config.presets) {
|
||||
config._presetPathes.push(preset.path);
|
||||
config._presetPaths.push(preset.path);
|
||||
|
||||
if (preset.adjustImagePresets) {
|
||||
config.imagePresets = preset.adjustImagePresets(config.imagePresets);
|
||||
}
|
||||
|
||||
if (preset.setupUnifiedPlugins) {
|
||||
config.setupUnifiedPlugins = [...config.setupUnifiedPlugins, ...preset.setupUnifiedPlugins];
|
||||
@@ -116,9 +165,13 @@ export async function normalizeConfig(inConfig) {
|
||||
if (preset.setupCliPlugins) {
|
||||
config.setupCliPlugins = [...config.setupCliPlugins, ...preset.setupCliPlugins];
|
||||
}
|
||||
|
||||
if (typeof preset.before11ty === 'function') {
|
||||
config.__before11tyFunctions.push(preset.before11ty);
|
||||
}
|
||||
}
|
||||
// add "local" preset
|
||||
config._presetPathes.push(path.resolve(_inputDirCwdRelative));
|
||||
config._presetPaths.push(path.resolve(_inputDirCwdRelative));
|
||||
|
||||
/** @type {MetaPlugin[]} */
|
||||
let pluginsMeta = [
|
||||
@@ -143,6 +196,10 @@ export async function normalizeConfig(inConfig) {
|
||||
plugins.push(pluginInst);
|
||||
}
|
||||
|
||||
if (typeof config.before11ty === 'function') {
|
||||
config.__before11tyFunctions.push(config.before11ty);
|
||||
}
|
||||
|
||||
// TODO: check pathPrefix to NOT have a '/' at the end as it will mean it may get ignored by 11ty 🤷♂️
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,14 +2,14 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Image = require('@11ty/eleventy-img');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
const { createSocialImageSvg: defaultcreateSocialImageSvg } = require('./createSocialImageSvg.cjs');
|
||||
const { createSocialImageSvg: defaultCreateSocialImageSvg } = require('./createSocialImageSvg.cjs');
|
||||
|
||||
async function createSocialImage(args) {
|
||||
const {
|
||||
title = '',
|
||||
subTitle = '',
|
||||
footer = '',
|
||||
createSocialImageSvg = defaultcreateSocialImageSvg,
|
||||
createSocialImageSvg = defaultCreateSocialImageSvg,
|
||||
} = args;
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImageSvg;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { processContentWithTitle } = require('@rocket/core/title');
|
||||
const { createSocialImage: defaultcreateSocialImage } = require('./createSocialImage.cjs');
|
||||
const { createSocialImage: defaultCreateSocialImage } = require('./createSocialImage.cjs');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
const { executeSetupFunctions } = require('plugins-manager');
|
||||
|
||||
@@ -71,7 +71,7 @@ function layoutPlugin({ defaultLayout = 'layout-default' } = {}) {
|
||||
}
|
||||
|
||||
function socialMediaImagePlugin(args = {}) {
|
||||
const { createSocialImage = defaultcreateSocialImage } = args;
|
||||
const { createSocialImage = defaultCreateSocialImage, rocketConfig = {} } = args;
|
||||
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImage;
|
||||
@@ -80,6 +80,11 @@ function socialMediaImagePlugin(args = {}) {
|
||||
if (data.socialMediaImage) {
|
||||
return data.socialMediaImage;
|
||||
}
|
||||
|
||||
if (rocketConfig.createSocialMediaImages === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.title) {
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +163,7 @@ function generateEleventyComputed() {
|
||||
{ name: 'title', plugin: titlePlugin },
|
||||
{ name: 'eleventyNavigation', plugin: eleventyNavigationPlugin },
|
||||
{ name: 'section', plugin: sectionPlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin, options: { rocketConfig } },
|
||||
{ name: '_joiningBlocks', plugin: joiningBlocksPlugin, options: rocketConfig },
|
||||
{ name: 'layout', plugin: layoutPlugin },
|
||||
];
|
||||
|
||||
@@ -5,6 +5,16 @@ const { getComputedConfig } = require('../public/computedConfig.cjs');
|
||||
const rocketFilters = require('../eleventy-plugins/rocketFilters.cjs');
|
||||
const rocketCopy = require('../eleventy-plugins/rocketCopy.cjs');
|
||||
const rocketCollections = require('../eleventy-plugins/rocketCollections.cjs');
|
||||
const { adjustPluginOptions } = require('plugins-manager');
|
||||
const image = require('./mdjsImageHandler.cjs');
|
||||
|
||||
const defaultSetupUnifiedPlugins = [
|
||||
adjustPluginOptions('remark2rehype', {
|
||||
handlers: {
|
||||
image,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
const config = getComputedConfig();
|
||||
@@ -28,7 +38,9 @@ module.exports = function (eleventyConfig) {
|
||||
{
|
||||
name: 'eleventy-plugin-mdjs-unified',
|
||||
plugin: eleventyPluginMdjsUnified,
|
||||
options: { setupUnifiedPlugins: config.setupUnifiedPlugins },
|
||||
options: {
|
||||
setupUnifiedPlugins: [...defaultSetupUnifiedPlugins, ...config.setupUnifiedPlugins],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eleventy-rocket-nav',
|
||||
|
||||
17
packages/cli/src/shared/mdjsImageHandler.cjs
Normal file
17
packages/cli/src/shared/mdjsImageHandler.cjs
Normal file
@@ -0,0 +1,17 @@
|
||||
const normalize = require('mdurl/encode');
|
||||
|
||||
function image(h, node) {
|
||||
const props = {
|
||||
src: normalize(node.url),
|
||||
alt: node.alt,
|
||||
'rocket-image': 'responsive',
|
||||
};
|
||||
|
||||
if (node.title !== null && node.title !== undefined) {
|
||||
props.title = node.title;
|
||||
}
|
||||
|
||||
return h(node, 'img', props);
|
||||
}
|
||||
|
||||
module.exports = image;
|
||||
@@ -15,10 +15,11 @@ export function setFixtureDir(importMetaUrl) {
|
||||
|
||||
/**
|
||||
* @typedef {object} readOutputOptions
|
||||
* @property {boolean} stripServiceWorker
|
||||
* @property {boolean} stripToBody
|
||||
* @property {boolean} stripStartEndWhitespace
|
||||
* @property {boolean} stripScripts
|
||||
* @property {boolean} formatHtml
|
||||
* @property {boolean} replaceImageHashes
|
||||
* @property {start|build} type
|
||||
*/
|
||||
|
||||
@@ -46,14 +47,18 @@ export async function readOutput(
|
||||
cli,
|
||||
fileName,
|
||||
{
|
||||
stripServiceWorker = false,
|
||||
stripToBody = false,
|
||||
stripStartEndWhitespace = true,
|
||||
stripScripts = false,
|
||||
formatHtml = false,
|
||||
type = 'build',
|
||||
replaceImageHashes = false,
|
||||
} = {},
|
||||
) {
|
||||
if (!cli || !cli.config) {
|
||||
throw new Error(`No valid cli provided to readOutput - you passed a ${typeof cli}: ${cli}`);
|
||||
}
|
||||
|
||||
const outputDir = type === 'build' ? cli.config.outputDir : cli.config.outputDevDir;
|
||||
let text = await fs.promises.readFile(path.join(outputDir, fileName));
|
||||
text = text.toString();
|
||||
@@ -62,16 +67,14 @@ export async function readOutput(
|
||||
const bodyCloseTagStart = text.indexOf('</body>');
|
||||
text = text.substring(bodyOpenTagEnd, bodyCloseTagStart);
|
||||
}
|
||||
if (stripServiceWorker) {
|
||||
const scriptOpenTagEnd = text.indexOf('<script inject-service-worker');
|
||||
const scriptCloseTagStart = text.indexOf('</script>', scriptOpenTagEnd) + 9;
|
||||
text = text.substring(0, scriptOpenTagEnd) + text.substring(scriptCloseTagStart);
|
||||
}
|
||||
if (stripScripts) {
|
||||
const scriptOpenTagEnd = text.indexOf('<script>');
|
||||
const scriptCloseTagStart = text.indexOf('</script>', scriptOpenTagEnd) + 9;
|
||||
text = text.substring(0, scriptOpenTagEnd) + text.substring(scriptCloseTagStart);
|
||||
}
|
||||
if (replaceImageHashes) {
|
||||
text = text.replace(/\/images\/([a-z0-9]+)-/g, '/images/__HASH__-');
|
||||
}
|
||||
if (formatHtml) {
|
||||
text = prettier.format(text, { parser: 'html', printWidth: 100 });
|
||||
}
|
||||
@@ -81,6 +84,16 @@ export async function readOutput(
|
||||
return text;
|
||||
}
|
||||
|
||||
export function startOutputExist(cli, fileName) {
|
||||
const outputDir = cli.config.outputDevDir;
|
||||
return fs.existsSync(path.join(outputDir, fileName));
|
||||
}
|
||||
|
||||
export function buildOutputExist(cli, fileName) {
|
||||
const outputDir = cli.config.outputDir;
|
||||
return fs.existsSync(path.join(outputDir, fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} cli
|
||||
* @param {string} fileName
|
||||
@@ -91,10 +104,21 @@ export async function readStartOutput(cli, fileName, options = {}) {
|
||||
return readOutput(cli, fileName, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} cli
|
||||
* @param {string} fileName
|
||||
* @param {readOutputOptions} options
|
||||
*/
|
||||
export async function readBuildOutput(cli, fileName, options = {}) {
|
||||
options.type = 'build';
|
||||
return readOutput(cli, fileName, options);
|
||||
}
|
||||
|
||||
export async function execute(cli, configFileDir) {
|
||||
await cli.setup();
|
||||
cli.config.outputDevDir = path.join(configFileDir, '__output-dev');
|
||||
cli.config.devServer.open = false;
|
||||
cli.config.devServer.port = 8080;
|
||||
cli.config.watch = false;
|
||||
cli.config.outputDir = path.join(configFileDir, '__output');
|
||||
await cli.run();
|
||||
@@ -110,6 +134,15 @@ export async function executeStart(pathToConfig) {
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeBuild(pathToConfig) {
|
||||
const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
argv: ['build', '--config-file', configFile],
|
||||
});
|
||||
await execute(cli, path.dirname(configFile));
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeLint(pathToConfig) {
|
||||
const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import { executeStart, readOutput, readStartOutput, setFixtureDir } from '@rocket/cli/test-helpers';
|
||||
import {
|
||||
executeBuild,
|
||||
executeStart,
|
||||
readBuildOutput,
|
||||
readStartOutput,
|
||||
setFixtureDir,
|
||||
} from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
@@ -22,42 +28,48 @@ describe('RocketCli computedConfig', () => {
|
||||
it('will extract a title from markdown and set first folder as section', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/headlines/rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput(cli, 'index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
const [indexTitle, indexSection] = indexHtml.split('\n');
|
||||
expect(indexTitle).to.equal('Root');
|
||||
expect(indexSection).to.be.undefined;
|
||||
|
||||
const subHtml = await readOutput(cli, 'sub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const subHtml = await readStartOutput(cli, 'sub/index.html');
|
||||
const [subTitle, subSection] = subHtml.split('\n');
|
||||
expect(subTitle).to.equal('Root: Sub');
|
||||
expect(subSection).to.equal('sub');
|
||||
|
||||
const subSubHtml = await readOutput(cli, 'sub/subsub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const subSubHtml = await readStartOutput(cli, 'sub/subsub/index.html');
|
||||
const [subSubTitle, subSubSection] = subSubHtml.split('\n');
|
||||
expect(subSubTitle).to.equal('Sub: SubSub');
|
||||
expect(subSubSection).to.equal('sub');
|
||||
|
||||
const sub2Html = await readOutput(cli, 'sub2/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const sub2Html = await readStartOutput(cli, 'sub2/index.html');
|
||||
const [sub2Title, sub2Section] = sub2Html.split('\n');
|
||||
expect(sub2Title).to.equal('Root: Sub2');
|
||||
expect(sub2Section).to.equal('sub2');
|
||||
|
||||
const withDataHtml = await readOutput(cli, 'with-data/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const withDataHtml = await readStartOutput(cli, 'with-data/index.html');
|
||||
const [withDataTitle, withDataSection] = withDataHtml.split('\n');
|
||||
expect(withDataTitle).to.equal('Set via data');
|
||||
expect(withDataSection).be.undefined;
|
||||
});
|
||||
|
||||
it('will note create a social media image in "start"', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images-only-build/rocket.config.js');
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('');
|
||||
});
|
||||
|
||||
it('will create a social media image in "build"', async () => {
|
||||
cli = await executeBuild('computed-config-fixtures/social-images-only-build/rocket.config.js');
|
||||
|
||||
const indexHtml = await readBuildOutput(cli, 'index.html', {
|
||||
stripToBody: true,
|
||||
});
|
||||
expect(indexHtml).to.equal('/_merged_assets/11ty-img/5893749-1200.png');
|
||||
});
|
||||
|
||||
it('will create a social media image for every page', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images/rocket.config.js');
|
||||
|
||||
@@ -94,31 +106,33 @@ describe('RocketCli computedConfig', () => {
|
||||
cli = await executeStart('computed-config-fixtures/image-link/rocket.config.js');
|
||||
|
||||
const namedMdContent = [
|
||||
'<p><a href="../">Root</a>',
|
||||
'<a href="../one-level/raw/">Raw</a>',
|
||||
'<img src="../images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
'<p>',
|
||||
' <a href="../">Root</a>',
|
||||
' <a href="../one-level/raw/">Raw</a>',
|
||||
' <img src="../images/my-img.svg" alt="my-img" />',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img" />',
|
||||
'</p>',
|
||||
];
|
||||
|
||||
const namedHtmlContent = [
|
||||
'<div id="with-anchor">',
|
||||
' <a href="../">Root</a>',
|
||||
' <a href="../one-level/raw/">Raw</a>',
|
||||
' <img src="../images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <img src="../images/my-img.svg" alt="my-img" />',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img" />',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="../images/picture-min-465.jpg">',
|
||||
' <img src="../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' <source media="(min-width:465px)" srcset="../images/picture-min-465.jpg" />',
|
||||
' <img src="../images/picture-fallback.jpg" alt="Fallback" style="width: auto" />',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
];
|
||||
|
||||
const templateHtml = await readStartOutput(cli, 'template/index.html');
|
||||
const templateHtml = await readStartOutput(cli, 'template/index.html', { formatHtml: true });
|
||||
expect(templateHtml, 'template/index.html does not match').to.equal(
|
||||
namedHtmlContent.join('\n'),
|
||||
);
|
||||
|
||||
const guidesHtml = await readStartOutput(cli, 'guides/index.html');
|
||||
const guidesHtml = await readStartOutput(cli, 'guides/index.html', { formatHtml: true });
|
||||
expect(guidesHtml, 'guides/index.html does not match').to.equal(
|
||||
[...namedMdContent, ...namedHtmlContent].join('\n'),
|
||||
);
|
||||
@@ -145,27 +159,28 @@ describe('RocketCli computedConfig', () => {
|
||||
);
|
||||
|
||||
// for index files no '../' will be added
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', { formatHtml: true });
|
||||
expect(indexHtml, 'index.html does not match').to.equal(
|
||||
[
|
||||
'<p><a href="./">Root</a>',
|
||||
'<a href="guides/#with-anchor">Guides</a>',
|
||||
'<a href="./one-level/raw/">Raw</a>',
|
||||
'<a href="template/">Template</a>',
|
||||
'<a href="./rules/tabindex/">EndingIndex</a>',
|
||||
'<img src="./images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
'<div>',
|
||||
'<p>',
|
||||
' <a href="./">Root</a>',
|
||||
' 👇<a href="guides/#with-anchor">Guides</a>',
|
||||
' 👉 <a href="./one-level/raw/">Raw</a>',
|
||||
' <a href="guides/#with-anchor">Guides</a>',
|
||||
' <a href="./one-level/raw/">Raw</a>',
|
||||
' <a href="template/">Template</a>',
|
||||
' <a href="./rules/tabindex/">EndingIndex</a>',
|
||||
' <img src="./images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <img src="./images/my-img.svg" alt="my-img" />',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img" />',
|
||||
'</p>',
|
||||
'<div>',
|
||||
' <a href="./">Root</a>',
|
||||
' 👇<a href="guides/#with-anchor">Guides</a> 👉 <a href="./one-level/raw/">Raw</a>',
|
||||
' <a href="template/">Template</a>',
|
||||
' <a href="./rules/tabindex/">EndingIndex</a>',
|
||||
' <img src="./images/my-img.svg" alt="my-img" />',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img" />',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">',
|
||||
' <img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' <source media="(min-width:465px)" srcset="./images/picture-min-465.jpg" />',
|
||||
' <img src="./images/picture-fallback.jpg" alt="Fallback" style="width: auto" />',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
@@ -175,9 +190,7 @@ describe('RocketCli computedConfig', () => {
|
||||
it('can be configured via setupEleventyComputedConfig', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/setup/addPlugin.rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput(cli, 'index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('test-value');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,86 +1,25 @@
|
||||
import chai from 'chai';
|
||||
import fetch from 'node-fetch';
|
||||
import { RocketCli } from '../src/RocketCli.js';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
import {
|
||||
executeBuild,
|
||||
executeLint,
|
||||
executeStart,
|
||||
expectThrowsAsync,
|
||||
readBuildOutput,
|
||||
readStartOutput,
|
||||
setFixtureDir,
|
||||
} from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
/**
|
||||
* @param {function} method
|
||||
* @param {string} errorMessage
|
||||
*/
|
||||
async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
|
||||
let error = null;
|
||||
try {
|
||||
await method();
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).to.be.an('Error', 'No error was thrown');
|
||||
if (errorMatch) {
|
||||
expect(error.message).to.match(errorMatch);
|
||||
}
|
||||
if (errorMessage) {
|
||||
expect(error.message).to.equal(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
describe('RocketCli e2e', () => {
|
||||
let cli;
|
||||
|
||||
async function readOutput(
|
||||
fileName,
|
||||
{
|
||||
stripServiceWorker = false,
|
||||
stripToBody = false,
|
||||
stripStartEndWhitespace = true,
|
||||
type = 'build',
|
||||
} = {},
|
||||
) {
|
||||
const outputDir = type === 'build' ? cli.config.outputDir : cli.config.outputDevDir;
|
||||
let text = await fs.promises.readFile(path.join(outputDir, fileName));
|
||||
text = text.toString();
|
||||
if (stripToBody) {
|
||||
const bodyOpenTagEnd = text.indexOf('>', text.indexOf('<body') + 1) + 1;
|
||||
const bodyCloseTagStart = text.indexOf('</body>');
|
||||
text = text.substring(bodyOpenTagEnd, bodyCloseTagStart);
|
||||
}
|
||||
if (stripServiceWorker) {
|
||||
const scriptOpenTagEnd = text.indexOf('<script inject-service-worker');
|
||||
const scriptCloseTagStart = text.indexOf('</script>', scriptOpenTagEnd) + 9;
|
||||
text = text.substring(0, scriptOpenTagEnd) + text.substring(scriptCloseTagStart);
|
||||
}
|
||||
if (stripStartEndWhitespace) {
|
||||
text = text.trim();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async function execute() {
|
||||
await cli.setup();
|
||||
cli.config.outputDevDir = path.join(__dirname, 'e2e-fixtures', '__output-dev');
|
||||
cli.config.devServer.open = false;
|
||||
cli.config.devServer.port = 8080;
|
||||
cli.config.watch = false;
|
||||
cli.config.outputDir = path.join(__dirname, 'e2e-fixtures', '__output');
|
||||
await cli.run();
|
||||
}
|
||||
|
||||
async function executeLint(pathToConfig) {
|
||||
cli = new RocketCli({
|
||||
argv: ['lint', '--config-file', path.join(__dirname, pathToConfig.split('/').join(path.sep))],
|
||||
});
|
||||
await execute();
|
||||
}
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
setFixtureDir(import.meta.url);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -90,79 +29,39 @@ describe('RocketCli e2e', () => {
|
||||
});
|
||||
|
||||
it('can add a unified plugin via the config', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'build',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'unified-plugin', 'rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
stripServiceWorker: true,
|
||||
stripToBody: true,
|
||||
});
|
||||
|
||||
cli = await executeStart('e2e-fixtures/unified-plugin/rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('<p>See a 🐶</p>');
|
||||
});
|
||||
|
||||
describe('eleventy in config', () => {
|
||||
// TODO: find out while this has a side effect and breaks other tests
|
||||
it.skip('can modify eleventy via an elventy function in the config', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'content', 'eleventy.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
it('can modify eleventy via an elventy function in the config', async () => {
|
||||
cli = await executeStart('e2e-fixtures/content/eleventy.rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal(
|
||||
['# BEFORE #', '<p>Content inside <code>docs/index.md</code></p>'].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('will throw if you try to set options by returning an object', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'content', 'eleventy-return.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
|
||||
await expectThrowsAsync(() => execute(), {
|
||||
errorMatch: /Error in your Eleventy config file.*/,
|
||||
});
|
||||
await expectThrowsAsync(
|
||||
() => executeStart('e2e-fixtures/content/eleventy-return.rocket.config.js'),
|
||||
{
|
||||
errorMatch: /Error in your Eleventy config file.*/,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupDevAndBuildPlugins in config', () => {
|
||||
it('can add a rollup plugin via setupDevAndBuildPlugins for build command', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'build',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
const inlineModule = await readOutput('e97af63d.js');
|
||||
cli = await executeBuild('e2e-fixtures/rollup-plugin/devbuild.rocket.config.js');
|
||||
const inlineModule = await readBuildOutput(cli, 'e97af63d.js');
|
||||
expect(inlineModule).to.equal('var a={test:"data"};console.log(a);');
|
||||
});
|
||||
|
||||
it('can add a rollup plugin via setupDevAndBuildPlugins for start command', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
cli = await executeStart('e2e-fixtures/rollup-plugin/devbuild.rocket.config.js');
|
||||
|
||||
const response = await fetch('http://localhost:8080/test-data.json');
|
||||
expect(response.ok).to.be.true; // no server error
|
||||
@@ -173,89 +72,66 @@ describe('RocketCli e2e', () => {
|
||||
});
|
||||
|
||||
it('can add a rollup plugin for dev & build and modify a build only plugin via the config', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'build',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild-build.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
const inlineModule = await readOutput('e97af63d.js');
|
||||
cli = await executeBuild('e2e-fixtures/rollup-plugin/devbuild-build.rocket.config.js');
|
||||
const inlineModule = await readBuildOutput(cli, 'e97af63d.js');
|
||||
expect(inlineModule).to.equal('var a={test:"data"};console.log(a);');
|
||||
|
||||
const swCode = await readOutput('my-service-worker.js');
|
||||
expect(swCode).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('can adjust the inputDir', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'change-input-dir', 'rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
cli = await executeStart('e2e-fixtures/change-input-dir/rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('<p>Markdown in <code>docs/page/index.md</code></p>');
|
||||
});
|
||||
|
||||
it('can access main rocket config values via {{rocketConfig.value}}', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'rocket-config-in-template', 'rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
cli = await executeStart('e2e-fixtures/rocket-config-in-template/rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal(
|
||||
'<p>You can show Rocket config data like rocketConfig.absoluteBaseUrl = <a href="http://test-domain.com/">http://test-domain.com/</a></p>',
|
||||
);
|
||||
});
|
||||
|
||||
it('can add a pathPrefix that will not influence the start command', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'content', 'pathPrefix.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
cli = await executeStart('e2e-fixtures/content/pathPrefix.rocket.config.js');
|
||||
|
||||
const linkHtml = await readOutput('link/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const linkHtml = await readStartOutput(cli, 'link/index.html');
|
||||
expect(linkHtml).to.equal(
|
||||
['<p><a href="../">home</a></p>', '<p><a href="/">absolute home</a></p>'].join('\n'),
|
||||
);
|
||||
const assetHtml = await readOutput('use-assets/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const assetHtml = await readStartOutput(cli, 'use-assets/index.html');
|
||||
expect(assetHtml).to.equal('<link rel="stylesheet" href="/_merged_assets/some.css">');
|
||||
const imageHtml = await readStartOutput(cli, 'image/index.html', { replaceImageHashes: true });
|
||||
expect(imageHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <figure>',
|
||||
' <picture>',
|
||||
'<source type="image/avif" srcset="/images/__HASH__-600.avif 600w, /images/__HASH__-900.avif 900w" sizes="100vw">',
|
||||
'<source type="image/jpeg" srcset="/images/__HASH__-600.jpeg 600w, /images/__HASH__-900.jpeg 900w" sizes="100vw">',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text" rocket-image="responsive"',
|
||||
' src="/images/__HASH__-600.jpeg"',
|
||||
' ',
|
||||
' ',
|
||||
' width="600"',
|
||||
' height="316"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
' <figcaption>My Image Description</figcaption>',
|
||||
'</figure>',
|
||||
' </p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('can add a pathPrefix that will be used in the build command', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'build',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'content', 'pathPrefix.rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
cli = await executeBuild('e2e-fixtures/content/pathPrefix.rocket.config.js');
|
||||
|
||||
const linkHtml = await readOutput('link/index.html', {
|
||||
stripServiceWorker: true,
|
||||
const linkHtml = await readBuildOutput(cli, 'link/index.html', {
|
||||
stripToBody: true,
|
||||
});
|
||||
expect(linkHtml).to.equal(
|
||||
@@ -263,12 +139,30 @@ describe('RocketCli e2e', () => {
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
const assetHtml = await readOutput('use-assets/index.html', {
|
||||
stripServiceWorker: true,
|
||||
});
|
||||
const assetHtml = await readBuildOutput(cli, 'use-assets/index.html');
|
||||
expect(assetHtml).to.equal(
|
||||
'<html><head><link rel="stylesheet" href="../41297ffa.css">\n\n</head><body>\n\n</body></html>',
|
||||
);
|
||||
let imageHtml = await readBuildOutput(cli, 'image/index.html');
|
||||
imageHtml = imageHtml.replace(/\.\.\/([a-z0-9]+)\./g, '../__HASH__.');
|
||||
expect(imageHtml).to.equal(
|
||||
[
|
||||
'<html><head>',
|
||||
'</head><body><p>',
|
||||
' </p><figure>',
|
||||
' <picture>',
|
||||
'<source type="image/avif" srcset="../__HASH__.avif 600w, ../__HASH__.avif 900w" sizes="100vw">',
|
||||
'<source type="image/jpeg" srcset="../__HASH__.jpeg 600w, ../__HASH__.jpeg 900w" sizes="100vw">',
|
||||
' <img alt="My Image Alternative Text" rocket-image="responsive" src="../__HASH__.jpeg" width="600" height="316" loading="lazy" decoding="async">',
|
||||
' </picture>',
|
||||
' <figcaption>My Image Description</figcaption>',
|
||||
'</figure>',
|
||||
' <p></p>',
|
||||
'',
|
||||
'',
|
||||
'</body></html>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('smoke test for link checking', async () => {
|
||||
|
||||
296
packages/cli/test-node/RocketCli.images.test.js
Normal file
296
packages/cli/test-node/RocketCli.images.test.js
Normal file
@@ -0,0 +1,296 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import { executeStart, readStartOutput, setFixtureDir } from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('RocketCli images', () => {
|
||||
let cli;
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
setFixtureDir(import.meta.url);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (cli?.cleanup) {
|
||||
await cli.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
describe('Images', () => {
|
||||
it('does render content images responsive', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <figure>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-600.avif 600w, /images/__HASH__-900.avif 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-600.jpeg 600w, /images/__HASH__-900.jpeg 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-600.jpeg"',
|
||||
' width="600"',
|
||||
' height="316"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
' <figcaption>My Image Description</figcaption>',
|
||||
' </figure>',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const keepSvgHtml = await readStartOutput(cli, 'ignores/index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
|
||||
// ignores src="[...].svg" and src="[...]rocket-unresponsive.[...]"
|
||||
expect(keepSvgHtml).to.equal(
|
||||
[
|
||||
'<p>Ignore SVG</p>',
|
||||
'<p><img src="../_assets/logo.svg" alt="Logo stays svg" rocket-image="responsive" /></p>',
|
||||
'<p>Ignore if contains <code>rocket-unresponsive.</code></p>',
|
||||
'<p>',
|
||||
' <img',
|
||||
' src="../_assets/my-image.rocket-unresponsive.jpg"',
|
||||
' alt="Logo stays svg"',
|
||||
' rocket-image="responsive"',
|
||||
' />',
|
||||
'</p>',
|
||||
'<p>Responsive</p>',
|
||||
'<p>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-600.avif 600w, /images/__HASH__-900.avif 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-600.jpeg 600w, /images/__HASH__-900.jpeg 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-600.jpeg"',
|
||||
' width="600"',
|
||||
' height="316"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('can configure more patterns to ignore', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/ignore-more.rocket.config.js');
|
||||
const keepSvgHtml = await readStartOutput(cli, 'ignores/index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
|
||||
// ignores src="[...].svg" and src="[...]rocket-unresponsive.[...]"
|
||||
expect(keepSvgHtml).to.equal(
|
||||
[
|
||||
'<p>Ignore SVG</p>',
|
||||
'<p><img src="../_assets/logo.svg" alt="Logo stays svg" rocket-image="responsive" /></p>',
|
||||
'<p>Ignore if contains <code>rocket-unresponsive.</code></p>',
|
||||
'<p>',
|
||||
' <img',
|
||||
' src="../_assets/my-image.rocket-unresponsive.jpg"',
|
||||
' alt="Logo stays svg"',
|
||||
' rocket-image="responsive"',
|
||||
' />',
|
||||
'</p>',
|
||||
'<p>Responsive</p>',
|
||||
'<p>',
|
||||
' <img src="../_assets/my-image.jpeg" alt="My Image Alternative Text" rocket-image="responsive" />',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders multiple images in the correct order', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'two-images/index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<h2 id="one">',
|
||||
' <a aria-hidden="true" tabindex="-1" href="#one"><span class="icon icon-link"></span></a>one',
|
||||
'</h2>',
|
||||
'<p>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-600.avif 600w, /images/__HASH__-900.avif 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-600.jpeg 600w, /images/__HASH__-900.jpeg 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="one"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-600.jpeg"',
|
||||
' width="600"',
|
||||
' height="316"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
'</p>',
|
||||
'<h2 id="two">',
|
||||
' <a aria-hidden="true" tabindex="-1" href="#two"><span class="icon icon-link"></span></a>two',
|
||||
'</h2>',
|
||||
'<p>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-600.avif 600w, /images/__HASH__-900.avif 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-600.jpeg 600w, /images/__HASH__-900.jpeg 900w"',
|
||||
' sizes="100vw"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="two"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-600.jpeg"',
|
||||
' width="600"',
|
||||
' height="316"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('can configure those responsive images', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/small.rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <figure>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-30.avif 30w, /images/__HASH__-60.avif 60w"',
|
||||
' sizes="(min-width: 1024px) 30px, 60px"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-30.jpeg 30w, /images/__HASH__-60.jpeg 60w"',
|
||||
' sizes="(min-width: 1024px) 30px, 60px"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-30.jpeg"',
|
||||
' width="30"',
|
||||
' height="15"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
' <figcaption>My Image Description</figcaption>',
|
||||
' </figure>',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('will only render a figure & figcaption if there is a caption/title', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/small.rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'no-title/index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <picture>',
|
||||
' <source',
|
||||
' type="image/avif"',
|
||||
' srcset="/images/__HASH__-30.avif 30w, /images/__HASH__-60.avif 60w"',
|
||||
' sizes="(min-width: 1024px) 30px, 60px"',
|
||||
' />',
|
||||
' <source',
|
||||
' type="image/jpeg"',
|
||||
' srcset="/images/__HASH__-30.jpeg 30w, /images/__HASH__-60.jpeg 60w"',
|
||||
' sizes="(min-width: 1024px) 30px, 60px"',
|
||||
' />',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-30.jpeg"',
|
||||
' width="30"',
|
||||
' height="15"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
' </picture>',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('will render an img with srcset and sizes if there is only one image format', async () => {
|
||||
cli = await executeStart('e2e-fixtures/images/single-format.rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'no-title/index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-30.jpeg"',
|
||||
' srcset="/images/__HASH__-30.jpeg 30w, /images/__HASH__-60.jpeg 60w"',
|
||||
' sizes="(min-width: 1024px) 30px, 60px"',
|
||||
' width="30"',
|
||||
' height="15"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -86,6 +86,12 @@ describe('RocketCli preset', () => {
|
||||
' </div>',
|
||||
'',
|
||||
' <footer id="main-footer"></footer>',
|
||||
'',
|
||||
' <script',
|
||||
' type="module"',
|
||||
' inject-service-worker=""',
|
||||
' src="/_merged_assets/scripts/registerServiceWorker.js"',
|
||||
' ></script>',
|
||||
' </body>',
|
||||
'</html>',
|
||||
].join('\n'),
|
||||
@@ -98,4 +104,30 @@ describe('RocketCli preset', () => {
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.include('<meta name="added" content="at the top" />');
|
||||
});
|
||||
|
||||
it('a preset can provide an adjustImagePresets() function', async () => {
|
||||
cli = await executeStart('preset-fixtures/use-preset/rocket.config.js');
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', {
|
||||
formatHtml: true,
|
||||
replaceImageHashes: true,
|
||||
});
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p>',
|
||||
' <img',
|
||||
' alt="My Image Alternative Text"',
|
||||
' rocket-image="responsive"',
|
||||
' src="/images/__HASH__-30.jpeg"',
|
||||
' srcset="/images/__HASH__-30.jpeg 30w, /images/__HASH__-60.jpeg 60w"',
|
||||
' sizes="30px"',
|
||||
' width="30"',
|
||||
' height="15"',
|
||||
' loading="lazy"',
|
||||
' decoding="async"',
|
||||
' />',
|
||||
'</p>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
66
packages/cli/test-node/RocketCli.service-worker.test.js
Normal file
66
packages/cli/test-node/RocketCli.service-worker.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import { executeBuild, readStartOutput, setFixtureDir } from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
function getInjectServiceWorker(text) {
|
||||
const scriptOpenTagStart = text.indexOf('<script type="module" inject-service-worker=""');
|
||||
const scriptCloseTagEnd = text.indexOf('</script>', scriptOpenTagStart) + 9;
|
||||
text = text.substring(scriptOpenTagStart, scriptCloseTagEnd);
|
||||
return text;
|
||||
}
|
||||
|
||||
function getServiceWorkerUrl(text) {
|
||||
const matches = text.match(/window\.__rocketServiceWorkerUrl = '(.*?)';/);
|
||||
return matches[1];
|
||||
}
|
||||
|
||||
describe('RocketCli e2e', () => {
|
||||
let cli;
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
setFixtureDir(import.meta.url);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (cli?.cleanup) {
|
||||
await cli.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('will add a script to inject the service worker', async () => {
|
||||
cli = await executeBuild('e2e-fixtures/service-worker/rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
const indexInject = getInjectServiceWorker(indexHtml);
|
||||
expect(indexInject).to.equal(
|
||||
'<script type="module" inject-service-worker="" src="/_merged_assets/scripts/registerServiceWorker.js"></script>',
|
||||
);
|
||||
expect(getServiceWorkerUrl(indexHtml)).to.equal('/service-worker.js');
|
||||
const subHtml = await readStartOutput(cli, 'sub/index.html');
|
||||
const subInject = getInjectServiceWorker(subHtml);
|
||||
expect(subInject).to.equal(
|
||||
'<script type="module" inject-service-worker="" src="/_merged_assets/scripts/registerServiceWorker.js"></script>',
|
||||
);
|
||||
expect(getServiceWorkerUrl(subHtml)).to.equal('/service-worker.js');
|
||||
});
|
||||
|
||||
// TODO: find a way to run these test either by forcing pathPrefix in start or skipping asset gathering for build or ...
|
||||
it.skip('will add a script to inject the service worker', async () => {
|
||||
cli = await executeBuild('e2e-fixtures/service-worker/pathPrefix.rocket.config.js');
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
const indexInject = getInjectServiceWorker(indexHtml);
|
||||
expect(indexInject).to.equal(
|
||||
'<script type="module" inject-service-worker="" src="/my-prefix-folder/_merged_assets/scripts/registerServiceWorker.js"></script>',
|
||||
);
|
||||
expect(getServiceWorkerUrl(indexHtml)).to.equal('/my-prefix-folder/service-worker.js');
|
||||
const subHtml = await readStartOutput(cli, 'sub/index.html');
|
||||
const subInject = getInjectServiceWorker(subHtml);
|
||||
expect(subInject).to.equal(
|
||||
'<script type="module" inject-service-worker="" src="/my-prefix-folder/_merged_assets/scripts/registerServiceWorker.js"></script>',
|
||||
);
|
||||
expect(getServiceWorkerUrl(subHtml)).to.equal('/my-prefix-folder/service-worker.js');
|
||||
});
|
||||
});
|
||||
67
packages/cli/test-node/RocketCli.useCases.test.js
Normal file
67
packages/cli/test-node/RocketCli.useCases.test.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
executeStart,
|
||||
readStartOutput,
|
||||
setFixtureDir,
|
||||
startOutputExist,
|
||||
} from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('RocketCli use cases', () => {
|
||||
let cli;
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
setFixtureDir(import.meta.url);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (cli?.cleanup) {
|
||||
await cli.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports dynamic imports', async () => {
|
||||
cli = await executeStart('use-cases/dynamic-imports/rocket.config.js');
|
||||
|
||||
expect(startOutputExist(cli, 'sub/assets/myData.js'), 'static files did not get copied').to.be
|
||||
.true;
|
||||
|
||||
const aboutHtml = await readStartOutput(cli, 'about/index.html', { formatHtml: true });
|
||||
expect(aboutHtml).to.equal(
|
||||
[
|
||||
'<p><code>about.md</code></p>',
|
||||
'<script type="module" src="/about/__mdjs-stories.js" mdjs-setup></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const subHtml = await readStartOutput(cli, 'sub/index.html', { formatHtml: true });
|
||||
expect(subHtml).to.equal(
|
||||
[
|
||||
'<p><code>sub/index.md</code></p>',
|
||||
'<script type="module" src="/sub/__mdjs-stories.js" mdjs-setup></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const subDetailsHtml = await readStartOutput(cli, 'sub/details/index.html', {
|
||||
formatHtml: true,
|
||||
});
|
||||
expect(subDetailsHtml).to.equal(
|
||||
[
|
||||
'<p><code>sub/details.md</code></p>',
|
||||
'<script type="module" src="/sub/details/__mdjs-stories.js" mdjs-setup></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', { formatHtml: true });
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p><code>index.md</code></p>',
|
||||
'<script type="module" src="/__mdjs-stories.js" mdjs-setup></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,20 @@
|
||||
import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
function image(h, node) {
|
||||
return h(node, 'img', {
|
||||
src: node.url,
|
||||
alt: node.alt,
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
const config = {
|
||||
setupUnifiedPlugins: [
|
||||
adjustPluginOptions('remark2rehype', {
|
||||
handlers: {
|
||||
image,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
# Rocket
|
||||
@@ -0,0 +1,4 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
|
||||
export default config;
|
||||
@@ -2,6 +2,9 @@ import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
setupEleventyComputedConfig: [
|
||||
adjustPluginOptions('socialMediaImage', {
|
||||
createSocialImageSvg: async () => {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = 'layout.njk';
|
||||
@@ -0,0 +1 @@
|
||||
{{ socialMediaImage }}
|
||||
@@ -1,4 +1,8 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: layout-raw
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1,33 @@
|
||||
<svg fill="#e63946" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 511.998 511.998" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M98.649,430.256c-46.365,28.67-71.17,30.939-78.916,23.51c-7.75-7.433-6.519-32.307,20.182-79.832
|
||||
c24.953-44.412,65.374-96.693,113.818-147.211l-11.279-10.817C93.124,267.348,51.871,320.751,26.291,366.279
|
||||
c-19.228,34.22-37.848,79.134-17.375,98.766c5.84,5.6,13.599,7.935,22.484,7.935c22.269,0,51.606-14.677,75.469-29.432
|
||||
c44.416-27.464,96.044-70.919,145.373-122.362l-11.279-10.817C192.517,360.888,141.976,403.464,98.649,430.256z"/>
|
||||
|
||||
<rect x="238.112" y="272.64" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 237.9094 656.5383)" width="25.589" height="15.628"/>
|
||||
|
||||
<rect x="268.895" y="302.163" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 270.4774 728.6761)" width="25.589" height="15.628"/>
|
||||
|
||||
<rect x="232.827" y="268.929" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 297.4719 673.0591)" width="102.364" height="15.628"/>
|
||||
<path d="M500.916,41.287c-7.769,1.59-76.412,16.062-93.897,34.294l-50.728,52.899l-114.703-3.629l-39.198,40.876l79.28,40.569
|
||||
l-21.755,22.687l72.848,69.858l21.755-22.687l43.857,77.51l39.197-40.876l-8.433-114.451l50.727-52.899
|
||||
c17.485-18.234,29.067-87.422,30.331-95.251l1.801-11.169L500.916,41.287z M228.209,161.383l19.842-20.692l93.688,2.964
|
||||
l-48.775,50.864L228.209,161.383z M401.632,327.686l-35.822-63.308l48.776-50.865l6.886,93.482L401.632,327.686z
|
||||
M332.298,276.743l-50.287-48.223L412.89,92.037l50.288,48.223L332.298,276.743z M473.009,128.036l-48.316-46.334
|
||||
c14.54-8.427,44.787-17.217,68.076-22.632C488.336,82.567,480.82,113.155,473.009,128.036z"/>
|
||||
|
||||
<rect x="302.369" y="231.988" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 384.0262 633.9694)" width="34.12" height="15.628"/>
|
||||
|
||||
<rect x="411.311" y="127.35" transform="matrix(-0.6921 0.7218 -0.7218 -0.6921 807.9747 -74.331)" width="17.061" height="15.628"/>
|
||||
|
||||
<rect x="394.288" y="145.087" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 586.0206 542.7934)" width="15.628" height="17.06"/>
|
||||
|
||||
<rect x="376.571" y="163.565" transform="matrix(-0.7218 -0.6921 0.6921 -0.7218 542.7271 562.3462)" width="15.628" height="17.06"/>
|
||||
|
||||
<rect x="161.111" y="185.158" transform="matrix(0.7071 0.7071 -0.7071 0.7071 192.1943 -60.3323)" width="15.628" height="33.35"/>
|
||||
|
||||
<rect x="184.683" y="172.695" transform="matrix(0.707 0.7072 -0.7072 0.707 182.4625 -83.9076)" width="15.628" height="11.118"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
15
packages/cli/test-node/e2e-fixtures/images/docs/ignores.md
Normal file
15
packages/cli/test-node/e2e-fixtures/images/docs/ignores.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
layout: layout-raw
|
||||
---
|
||||
|
||||
Ignore SVG
|
||||
|
||||

|
||||
|
||||
Ignore if contains `rocket-unresponsive.`
|
||||
|
||||

|
||||
|
||||
Responsive
|
||||
|
||||

|
||||
5
packages/cli/test-node/e2e-fixtures/images/docs/index.md
Normal file
5
packages/cli/test-node/e2e-fixtures/images/docs/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: layout-raw
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: layout-raw
|
||||
---
|
||||
|
||||

|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: layout-raw
|
||||
---
|
||||
|
||||
## one
|
||||
|
||||

|
||||
|
||||
## two
|
||||
|
||||

|
||||
@@ -0,0 +1,10 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
ignore: ({ src }) =>
|
||||
src.endsWith('.jpeg') || src.endsWith('svg') || src.includes('rocket-unresponsive.'),
|
||||
},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,3 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
export default config;
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [30, 60],
|
||||
formats: ['jpeg'],
|
||||
sizes: '(min-width: 1024px) 30px, 60px',
|
||||
},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,11 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
imagePresets: {
|
||||
responsive: {
|
||||
widths: [30, 60],
|
||||
formats: ['avif', 'jpeg'],
|
||||
sizes: '(min-width: 1024px) 30px, 60px',
|
||||
},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,19 +1,11 @@
|
||||
// @ts-no-check
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import json from '@rollup/plugin-json';
|
||||
import { addPlugin, adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const outputDir = path.join(__dirname, '..', '__output');
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupDevAndBuildPlugins: [addPlugin({ name: 'json', plugin: json, location: 'top' })],
|
||||
setupBuildPlugins: [
|
||||
adjustPluginOptions('workbox', { swDest: path.join(outputDir, 'my-service-worker.js') }),
|
||||
],
|
||||
setupBuildPlugins: [adjustPluginOptions('html', { absoluteBaseUrl: 'https://test-me.com' })],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = 'https://example.com';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user