Compare commits

...

9 Commits

Author SHA1 Message Date
github-actions[bot]
98d6aad12a Version Packages 2021-02-05 13:30:10 +01:00
Thomas Allmer
ee6b404aaa fix: mdjs element pass on shadowRoot to story function 2021-02-05 13:26:42 +01:00
Thomas Allmer
8ba8939c67 chore: use test-helper everywhere 2021-02-04 20:48:08 +01:00
Thomas Allmer
8e095b792e fix(cli): watching user preset files to trigger updates automatically 2021-02-04 19:56:56 +01:00
github-actions[bot]
b58ac27658 Version Packages 2021-02-04 09:51:19 +01:00
Thomas Allmer
f44a0f4fd4 fix(cli): rewrite dynamic imports with "`" 2021-02-04 09:45:35 +01:00
Thomas Allmer
750418bb51 fix: use class node api of check-html-links in rocket 2021-02-03 23:17:29 +01:00
Guillaume Grossetie
bc2698c1ba resolves #54 introduce a ignoreLinkPatterns option 2021-02-03 20:43:02 +01:00
Thomas Allmer
74f7ddf478 fix: create social media images only during build 2021-02-03 20:39:16 +01:00
56 changed files with 632 additions and 304 deletions

View File

@@ -1,7 +1,17 @@
# Tools >> Check HTML Links ||30 # Tools >> Check HTML Links ||30
```js
import '@rocket/launch/inline-notification/inline-notification.js';
```
A fast checker for broken links/references in HTML. 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 ## Features
- Checks all html files for broken local links/references (in href, src, srcset) - 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 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 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 ## Example Output

View File

@@ -129,3 +129,25 @@ const config = {
{% endraw %} {% 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,
};
```

View File

@@ -1,5 +1,19 @@
# check-html-links # check-html-links
## 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 ## 0.1.2
### Patch Changes ### Patch Changes

View File

@@ -1,2 +1,3 @@
export { validateFolder } from './src/validateFolder.js'; export { validateFolder } from './src/validateFolder.js';
export { formatErrors } from './src/formatErrors.js'; export { formatErrors } from './src/formatErrors.js';
export { CheckHtmlLinksCli } from './src/CheckHtmlLinksCli.js';

View File

@@ -1,6 +1,6 @@
{ {
"name": "check-html-links", "name": "check-html-links",
"version": "0.1.2", "version": "0.2.0",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -33,7 +33,9 @@
], ],
"dependencies": { "dependencies": {
"chalk": "^4.0.0", "chalk": "^4.0.0",
"command-line-args": "^5.1.1",
"glob": "^7.0.0", "glob": "^7.0.0",
"minimatch": "^3.0.4",
"sax-wasm": "^2.0.0" "sax-wasm": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {

View 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 };
}
}

View File

@@ -1,55 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import path from 'path'; import { CheckHtmlLinksCli } from './CheckHtmlLinksCli.js';
import chalk from 'chalk';
import { validateFiles } from './validateFolder.js';
import { formatErrors } from './formatErrors.js';
import { listFiles } from './listFiles.js';
async function main() { const cli = new CheckHtmlLinksCli();
const userRootDir = process.argv[2]; cli.run();
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();

View File

@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import fs from 'fs'; import fs from 'fs';
import saxWasm from 'sax-wasm'; import saxWasm from 'sax-wasm';
import minimatch from 'minimatch';
import { createRequire } from 'module'; import { createRequire } from 'module';
import { listFiles } from './listFiles.js'; import { listFiles } from './listFiles.js';
@@ -10,6 +11,7 @@ import path from 'path';
/** @typedef {import('../types/main').LocalFile} LocalFile */ /** @typedef {import('../types/main').LocalFile} LocalFile */
/** @typedef {import('../types/main').Usage} Usage */ /** @typedef {import('../types/main').Usage} Usage */
/** @typedef {import('../types/main').Error} Error */ /** @typedef {import('../types/main').Error} Error */
/** @typedef {import('../types/main').Options} Options */
/** @typedef {import('sax-wasm').Attribute} Attribute */ /** @typedef {import('sax-wasm').Attribute} Attribute */
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
@@ -185,8 +187,9 @@ function getValueAndAnchor(inValue) {
* @param {object} options * @param {object} options
* @param {string} options.htmlFilePath * @param {string} options.htmlFilePath
* @param {string} options.rootDir * @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) { for (const hrefObj of links) {
const { value, anchor } = getValueAndAnchor(hrefObj.value); const { value, anchor } = getValueAndAnchor(hrefObj.value);
@@ -201,7 +204,9 @@ async function resolveLinks(links, { htmlFilePath, rootDir }) {
let valueFile = value.endsWith('/') ? path.join(value, 'index.html') : value; let valueFile = value.endsWith('/') ? path.join(value, 'index.html') : value;
if (value.includes('mailto:')) { if (ignoreUsage(value)) {
// ignore
} else if (value.includes('mailto:')) {
// ignore for now - could add a check to validate if the email address is valid // ignore for now - could add a check to validate if the email address is valid
} else if (valueFile === '' && anchor !== '') { } else if (valueFile === '' && anchor !== '') {
addLocalFile(htmlFilePath, anchor, usageObj); addLocalFile(htmlFilePath, anchor, usageObj);
@@ -261,8 +266,9 @@ async function validateLocalFiles(checkLocalFiles) {
/** /**
* @param {string[]} files * @param {string[]} files
* @param {string} rootDir * @param {string} rootDir
* @param {Options} opts?
*/ */
export async function validateFiles(files, rootDir) { export async function validateFiles(files, rootDir, opts) {
await parserReferences.prepareWasm(saxWasmBuffer); await parserReferences.prepareWasm(saxWasmBuffer);
await parserIds.prepareWasm(saxWasmBuffer); await parserIds.prepareWasm(saxWasmBuffer);
@@ -270,10 +276,20 @@ export async function validateFiles(files, rootDir) {
checkLocalFiles = []; checkLocalFiles = [];
idCache = new Map(); idCache = new Map();
let numberLinks = 0; 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) { for (const htmlFilePath of files) {
const { links } = await extractReferences(htmlFilePath); const { links } = await extractReferences(htmlFilePath);
numberLinks += links.length; numberLinks += links.length;
await resolveLinks(links, { htmlFilePath, rootDir }); await resolveLinks(links, { htmlFilePath, rootDir, ignoreUsage });
} }
await validateLocalFiles(checkLocalFiles); await validateLocalFiles(checkLocalFiles);
@@ -282,10 +298,11 @@ export async function validateFiles(files, rootDir) {
/** /**
* @param {string} inRootDir * @param {string} inRootDir
* @param {Options} opts?
*/ */
export async function validateFolder(inRootDir) { export async function validateFolder(inRootDir, opts) {
const rootDir = path.resolve(inRootDir); const rootDir = path.resolve(inRootDir);
const files = await listFiles('**/*.html', rootDir); const files = await listFiles('**/*.html', rootDir);
const { errors } = await validateFiles(files, rootDir); const { errors } = await validateFiles(files, rootDir, opts);
return errors; return errors;
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -5,9 +5,9 @@ import { validateFolder } from 'check-html-links';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); 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 testDir = path.join(__dirname, inPath.split('/').join(path.sep));
const errors = await validateFolder(testDir); const errors = await validateFolder(testDir, opts);
return { return {
cleanup: items => { cleanup: items => {
const newItems = []; const newItems = [];

View File

@@ -183,6 +183,28 @@ describe('validateFolder', () => {
expect(cleanup(errors)).to.deep.equal([]); 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 () => { it('can handle img src', async () => {
const { errors, cleanup } = await execute('fixtures/internal-images'); const { errors, cleanup } = await execute('fixtures/internal-images');
expect(cleanup(errors)).to.deep.equal([ expect(cleanup(errors)).to.deep.equal([

View File

@@ -25,3 +25,14 @@ export interface Error {
onlyAnchorMissing: boolean; onlyAnchorMissing: boolean;
usage: Usage[]; usage: Usage[];
} }
interface Options {
ignoreLinkPatterns: string[] | null;
}
export interface CheckHtmlLinksCliOptions {
printOnError: boolean;
rootDir: string;
ignoreLinkPatterns: string[] | null;
continueOnError: boolean;
}

View File

@@ -1,5 +1,26 @@
# @rocket/cli # @rocket/cli
## 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 ## 0.5.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/cli", "name": "@rocket/cli",
"version": "0.5.0", "version": "0.5.2",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@@ -58,7 +58,7 @@
"@11ty/eleventy-img": "^0.7.4", "@11ty/eleventy-img": "^0.7.4",
"@rocket/building-rollup": "^0.1.2", "@rocket/building-rollup": "^0.1.2",
"@rocket/core": "^0.1.1", "@rocket/core": "^0.1.1",
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.0", "@rocket/eleventy-plugin-mdjs-unified": "^0.3.1",
"@rocket/eleventy-rocket-nav": "^0.2.1", "@rocket/eleventy-rocket-nav": "^0.2.1",
"@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^11.0.1", "@rollup/plugin-node-resolve": "^11.0.1",
@@ -66,10 +66,11 @@
"@web/dev-server": "^0.1.4", "@web/dev-server": "^0.1.4",
"@web/dev-server-rollup": "^0.3.2", "@web/dev-server-rollup": "^0.3.2",
"@web/rollup-plugin-copy": "^0.2.0", "@web/rollup-plugin-copy": "^0.2.0",
"check-html-links": "^0.1.2", "check-html-links": "^0.2.0",
"command-line-args": "^5.1.1", "command-line-args": "^5.1.1",
"command-line-usage": "^6.1.1", "command-line-usage": "^6.1.1",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"micromatch": "^4.0.2",
"plugins-manager": "^0.2.0", "plugins-manager": "^0.2.0",
"utf8": "^3.0.0" "utf8": "^3.0.0"
}, },

View File

@@ -33,6 +33,35 @@ export class RocketEleventy extends Eleventy {
await super.write(); await super.write();
await this.__rocketCli.update(); 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;
// cant 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 { export class RocketCli {

View File

@@ -2,8 +2,7 @@
/** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */ /** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
import chalk from 'chalk'; import { CheckHtmlLinksCli } from 'check-html-links';
import { validateFolder, formatErrors } from 'check-html-links';
export class RocketLint { export class RocketLint {
static pluginName = 'RocketLint'; static pluginName = 'RocketLint';
@@ -49,31 +48,20 @@ export class RocketLint {
return; return;
} }
const performanceStart = process.hrtime(); const checkLinks = new CheckHtmlLinksCli();
console.log('👀 Checking if all internal links work...'); checkLinks.setOptions({
const errors = await validateFolder(this.config.lintInputDir); rootDir: this.config.lintInputDir,
const performance = process.hrtime(performanceStart); printOnError: false,
continueOnError: true,
});
const { errors, message } = await checkLinks.run();
if (errors.length > 0) { if (errors.length > 0) {
let referenceCount = 0; if (this.config.command === 'start') {
for (const error of errors) { console.log(message);
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'));
} else { } else {
throw new Error(output.join('\n')); throw new Error(message);
} }
} else {
console.log('✅ All internal links are valid.');
} }
} }

View File

@@ -15,6 +15,7 @@ export class RocketStart {
*/ */
setupCommand(config) { setupCommand(config) {
delete config.pathPrefix; delete config.pathPrefix;
config.createSocialMediaImages = !!config?.start?.createSocialMediaImages;
return config; return config;
} }

View File

@@ -36,6 +36,7 @@ export async function normalizeConfig(inConfig) {
eleventy: () => {}, eleventy: () => {},
command: 'help', command: 'help',
watch: true, watch: true,
createSocialMediaImages: true,
inputDir: 'docs', inputDir: 'docs',
outputDir: '_site', outputDir: '_site',
outputDevDir: '_site-dev', outputDevDir: '_site-dev',

View File

@@ -2,14 +2,14 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const Image = require('@11ty/eleventy-img'); const Image = require('@11ty/eleventy-img');
const { getComputedConfig } = require('./computedConfig.cjs'); const { getComputedConfig } = require('./computedConfig.cjs');
const { createSocialImageSvg: defaultcreateSocialImageSvg } = require('./createSocialImageSvg.cjs'); const { createSocialImageSvg: defaultCreateSocialImageSvg } = require('./createSocialImageSvg.cjs');
async function createSocialImage(args) { async function createSocialImage(args) {
const { const {
title = '', title = '',
subTitle = '', subTitle = '',
footer = '', footer = '',
createSocialImageSvg = defaultcreateSocialImageSvg, createSocialImageSvg = defaultCreateSocialImageSvg,
} = args; } = args;
const cleanedUpArgs = { ...args }; const cleanedUpArgs = { ...args };
delete cleanedUpArgs.createSocialImageSvg; delete cleanedUpArgs.createSocialImageSvg;

View File

@@ -1,7 +1,7 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { processContentWithTitle } = require('@rocket/core/title'); const { processContentWithTitle } = require('@rocket/core/title');
const { createSocialImage: defaultcreateSocialImage } = require('./createSocialImage.cjs'); const { createSocialImage: defaultCreateSocialImage } = require('./createSocialImage.cjs');
const { getComputedConfig } = require('./computedConfig.cjs'); const { getComputedConfig } = require('./computedConfig.cjs');
const { executeSetupFunctions } = require('plugins-manager'); const { executeSetupFunctions } = require('plugins-manager');
@@ -71,7 +71,7 @@ function layoutPlugin({ defaultLayout = 'layout-default' } = {}) {
} }
function socialMediaImagePlugin(args = {}) { function socialMediaImagePlugin(args = {}) {
const { createSocialImage = defaultcreateSocialImage } = args; const { createSocialImage = defaultCreateSocialImage, rocketConfig = {} } = args;
const cleanedUpArgs = { ...args }; const cleanedUpArgs = { ...args };
delete cleanedUpArgs.createSocialImage; delete cleanedUpArgs.createSocialImage;
@@ -80,6 +80,11 @@ function socialMediaImagePlugin(args = {}) {
if (data.socialMediaImage) { if (data.socialMediaImage) {
return data.socialMediaImage; return data.socialMediaImage;
} }
if (rocketConfig.createSocialMediaImages === false) {
return;
}
if (!data.title) { if (!data.title) {
return; return;
} }
@@ -158,7 +163,7 @@ function generateEleventyComputed() {
{ name: 'title', plugin: titlePlugin }, { name: 'title', plugin: titlePlugin },
{ name: 'eleventyNavigation', plugin: eleventyNavigationPlugin }, { name: 'eleventyNavigation', plugin: eleventyNavigationPlugin },
{ name: 'section', plugin: sectionPlugin }, { name: 'section', plugin: sectionPlugin },
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin }, { name: 'socialMediaImage', plugin: socialMediaImagePlugin, options: { rocketConfig } },
{ name: '_joiningBlocks', plugin: joiningBlocksPlugin, options: rocketConfig }, { name: '_joiningBlocks', plugin: joiningBlocksPlugin, options: rocketConfig },
{ name: 'layout', plugin: layoutPlugin }, { name: 'layout', plugin: layoutPlugin },
]; ];

View File

@@ -19,6 +19,7 @@ export function setFixtureDir(importMetaUrl) {
* @property {boolean} stripToBody * @property {boolean} stripToBody
* @property {boolean} stripStartEndWhitespace * @property {boolean} stripStartEndWhitespace
* @property {boolean} stripScripts * @property {boolean} stripScripts
* @property {boolean} formatHtml
* @property {start|build} type * @property {start|build} type
*/ */
@@ -54,6 +55,10 @@ export async function readOutput(
type = 'build', type = 'build',
} = {}, } = {},
) { ) {
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; const outputDir = type === 'build' ? cli.config.outputDir : cli.config.outputDevDir;
let text = await fs.promises.readFile(path.join(outputDir, fileName)); let text = await fs.promises.readFile(path.join(outputDir, fileName));
text = text.toString(); text = text.toString();
@@ -81,6 +86,16 @@ export async function readOutput(
return text; 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 {*} cli
* @param {string} fileName * @param {string} fileName
@@ -91,10 +106,21 @@ export async function readStartOutput(cli, fileName, options = {}) {
return readOutput(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) { export async function execute(cli, configFileDir) {
await cli.setup(); await cli.setup();
cli.config.outputDevDir = path.join(configFileDir, '__output-dev'); cli.config.outputDevDir = path.join(configFileDir, '__output-dev');
cli.config.devServer.open = false; cli.config.devServer.open = false;
cli.config.devServer.port = 8080;
cli.config.watch = false; cli.config.watch = false;
cli.config.outputDir = path.join(configFileDir, '__output'); cli.config.outputDir = path.join(configFileDir, '__output');
await cli.run(); await cli.run();
@@ -110,6 +136,15 @@ export async function executeStart(pathToConfig) {
return cli; 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) { export async function executeLint(pathToConfig) {
const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep)); const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep));
const cli = new RocketCli({ const cli = new RocketCli({

View File

@@ -1,6 +1,12 @@
import chai from 'chai'; import chai from 'chai';
import chalk from 'chalk'; 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; const { expect } = chai;
@@ -22,42 +28,49 @@ describe('RocketCli computedConfig', () => {
it('will extract a title from markdown and set first folder as section', async () => { it('will extract a title from markdown and set first folder as section', async () => {
cli = await executeStart('computed-config-fixtures/headlines/rocket.config.js'); cli = await executeStart('computed-config-fixtures/headlines/rocket.config.js');
const indexHtml = await readOutput(cli, 'index.html', { const indexHtml = await readStartOutput(cli, 'index.html');
type: 'start',
});
const [indexTitle, indexSection] = indexHtml.split('\n'); const [indexTitle, indexSection] = indexHtml.split('\n');
expect(indexTitle).to.equal('Root'); expect(indexTitle).to.equal('Root');
expect(indexSection).to.be.undefined; expect(indexSection).to.be.undefined;
const subHtml = await readOutput(cli, 'sub/index.html', { const subHtml = await readStartOutput(cli, 'sub/index.html');
type: 'start',
});
const [subTitle, subSection] = subHtml.split('\n'); const [subTitle, subSection] = subHtml.split('\n');
expect(subTitle).to.equal('Root: Sub'); expect(subTitle).to.equal('Root: Sub');
expect(subSection).to.equal('sub'); expect(subSection).to.equal('sub');
const subSubHtml = await readOutput(cli, 'sub/subsub/index.html', { const subSubHtml = await readStartOutput(cli, 'sub/subsub/index.html');
type: 'start',
});
const [subSubTitle, subSubSection] = subSubHtml.split('\n'); const [subSubTitle, subSubSection] = subSubHtml.split('\n');
expect(subSubTitle).to.equal('Sub: SubSub'); expect(subSubTitle).to.equal('Sub: SubSub');
expect(subSubSection).to.equal('sub'); expect(subSubSection).to.equal('sub');
const sub2Html = await readOutput(cli, 'sub2/index.html', { const sub2Html = await readStartOutput(cli, 'sub2/index.html');
type: 'start',
});
const [sub2Title, sub2Section] = sub2Html.split('\n'); const [sub2Title, sub2Section] = sub2Html.split('\n');
expect(sub2Title).to.equal('Root: Sub2'); expect(sub2Title).to.equal('Root: Sub2');
expect(sub2Section).to.equal('sub2'); expect(sub2Section).to.equal('sub2');
const withDataHtml = await readOutput(cli, 'with-data/index.html', { const withDataHtml = await readStartOutput(cli, 'with-data/index.html');
type: 'start',
});
const [withDataTitle, withDataSection] = withDataHtml.split('\n'); const [withDataTitle, withDataSection] = withDataHtml.split('\n');
expect(withDataTitle).to.equal('Set via data'); expect(withDataTitle).to.equal('Set via data');
expect(withDataSection).be.undefined; 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,
stripServiceWorker: true,
});
expect(indexHtml).to.equal('/_merged_assets/11ty-img/5893749-1200.png');
});
it('will create a social media image for every page', async () => { it('will create a social media image for every page', async () => {
cli = await executeStart('computed-config-fixtures/social-images/rocket.config.js'); cli = await executeStart('computed-config-fixtures/social-images/rocket.config.js');
@@ -175,9 +188,7 @@ describe('RocketCli computedConfig', () => {
it('can be configured via setupEleventyComputedConfig', async () => { it('can be configured via setupEleventyComputedConfig', async () => {
cli = await executeStart('computed-config-fixtures/setup/addPlugin.rocket.config.js'); cli = await executeStart('computed-config-fixtures/setup/addPlugin.rocket.config.js');
const indexHtml = await readOutput(cli, 'index.html', { const indexHtml = await readStartOutput(cli, 'index.html');
type: 'start',
});
expect(indexHtml).to.equal('test-value'); expect(indexHtml).to.equal('test-value');
}); });

View File

@@ -1,86 +1,25 @@
import chai from 'chai'; import chai from 'chai';
import fetch from 'node-fetch'; 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'; import chalk from 'chalk';
import {
const __dirname = path.dirname(fileURLToPath(import.meta.url)); executeBuild,
executeLint,
executeStart,
expectThrowsAsync,
readBuildOutput,
readStartOutput,
setFixtureDir,
} from '@rocket/cli/test-helpers';
const { expect } = chai; 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', () => { describe('RocketCli e2e', () => {
let cli; 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(() => { before(() => {
// ignore colors in tests as most CIs won't support it // ignore colors in tests as most CIs won't support it
chalk.level = 0; chalk.level = 0;
setFixtureDir(import.meta.url);
}); });
afterEach(async () => { afterEach(async () => {
@@ -90,79 +29,40 @@ describe('RocketCli e2e', () => {
}); });
it('can add a unified plugin via the config', async () => { it('can add a unified plugin via the config', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/unified-plugin/rocket.config.js');
argv: [ const indexHtml = await readStartOutput(cli, 'index.html');
'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,
});
expect(indexHtml).to.equal('<p>See a 🐶</p>'); expect(indexHtml).to.equal('<p>See a 🐶</p>');
}); });
describe('eleventy in config', () => { describe('eleventy in config', () => {
// TODO: find out while this has a side effect and breaks other tests // 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 () => { it.skip('can modify eleventy via an elventy function in the config', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/content/eleventy.rocket.config.js');
argv: [ const indexHtml = await readStartOutput(cli, 'index.html');
'start',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'content', 'eleventy.rocket.config.js'),
],
});
await execute();
const indexHtml = await readOutput('index.html', {
type: 'start',
});
expect(indexHtml).to.equal( expect(indexHtml).to.equal(
['# BEFORE #', '<p>Content inside <code>docs/index.md</code></p>'].join('\n'), ['# 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 () => { it('will throw if you try to set options by returning an object', async () => {
cli = new RocketCli({ await expectThrowsAsync(
argv: [ () => executeStart('e2e-fixtures/content/eleventy-return.rocket.config.js'),
'start', {
'--config-file', errorMatch: /Error in your Eleventy config file.*/,
path.join(__dirname, 'e2e-fixtures', 'content', 'eleventy-return.rocket.config.js'), },
], );
});
await expectThrowsAsync(() => execute(), {
errorMatch: /Error in your Eleventy config file.*/,
});
}); });
}); });
describe('setupDevAndBuildPlugins in config', () => { describe('setupDevAndBuildPlugins in config', () => {
it('can add a rollup plugin via setupDevAndBuildPlugins for build command', async () => { it('can add a rollup plugin via setupDevAndBuildPlugins for build command', async () => {
cli = new RocketCli({ cli = await executeBuild('e2e-fixtures/rollup-plugin/devbuild.rocket.config.js');
argv: [ const inlineModule = await readBuildOutput(cli, 'e97af63d.js');
'build',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild.rocket.config.js'),
],
});
await execute();
const inlineModule = await readOutput('e97af63d.js');
expect(inlineModule).to.equal('var a={test:"data"};console.log(a);'); expect(inlineModule).to.equal('var a={test:"data"};console.log(a);');
}); });
it('can add a rollup plugin via setupDevAndBuildPlugins for start command', async () => { it('can add a rollup plugin via setupDevAndBuildPlugins for start command', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/rollup-plugin/devbuild.rocket.config.js');
argv: [
'start',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild.rocket.config.js'),
],
});
await execute();
const response = await fetch('http://localhost:8080/test-data.json'); const response = await fetch('http://localhost:8080/test-data.json');
expect(response.ok).to.be.true; // no server error expect(response.ok).to.be.true; // no server error
@@ -173,88 +73,45 @@ describe('RocketCli e2e', () => {
}); });
it('can add a rollup plugin for dev & build and modify a build only plugin via the config', async () => { it('can add a rollup plugin for dev & build and modify a build only plugin via the config', async () => {
cli = new RocketCli({ cli = await executeBuild('e2e-fixtures/rollup-plugin/devbuild-build.rocket.config.js');
argv: [ const inlineModule = await readBuildOutput(cli, 'e97af63d.js');
'build',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'rollup-plugin', 'devbuild-build.rocket.config.js'),
],
});
await execute();
const inlineModule = await readOutput('e97af63d.js');
expect(inlineModule).to.equal('var a={test:"data"};console.log(a);'); expect(inlineModule).to.equal('var a={test:"data"};console.log(a);');
const swCode = await readOutput('my-service-worker.js'); const swCode = await readBuildOutput(cli, 'my-service-worker.js');
expect(swCode).to.not.be.undefined; expect(swCode).to.not.be.undefined;
}); });
it('can adjust the inputDir', async () => { it('can adjust the inputDir', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/change-input-dir/rocket.config.js');
argv: [
'start',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'change-input-dir', 'rocket.config.js'),
],
});
await execute();
const indexHtml = await readOutput('index.html', { const indexHtml = await readStartOutput(cli, 'index.html');
type: 'start',
});
expect(indexHtml).to.equal('<p>Markdown in <code>docs/page/index.md</code></p>'); 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 () => { it('can access main rocket config values via {{rocketConfig.value}}', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/rocket-config-in-template/rocket.config.js');
argv: [
'start',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'rocket-config-in-template', 'rocket.config.js'),
],
});
await execute();
const indexHtml = await readOutput('index.html', { const indexHtml = await readStartOutput(cli, 'index.html');
type: 'start',
});
expect(indexHtml).to.equal( 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>', '<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 () => { it('can add a pathPrefix that will not influence the start command', async () => {
cli = new RocketCli({ cli = await executeStart('e2e-fixtures/content/pathPrefix.rocket.config.js');
argv: [
'start',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'content', 'pathPrefix.rocket.config.js'),
],
});
await execute();
const linkHtml = await readOutput('link/index.html', { const linkHtml = await readStartOutput(cli, 'link/index.html');
type: 'start',
});
expect(linkHtml).to.equal( expect(linkHtml).to.equal(
['<p><a href="../">home</a></p>', '<p><a href="/">absolute home</a></p>'].join('\n'), ['<p><a href="../">home</a></p>', '<p><a href="/">absolute home</a></p>'].join('\n'),
); );
const assetHtml = await readOutput('use-assets/index.html', { const assetHtml = await readStartOutput(cli, 'use-assets/index.html');
type: 'start',
});
expect(assetHtml).to.equal('<link rel="stylesheet" href="/_merged_assets/some.css">'); expect(assetHtml).to.equal('<link rel="stylesheet" href="/_merged_assets/some.css">');
}); });
it('can add a pathPrefix that will be used in the build command', async () => { it('can add a pathPrefix that will be used in the build command', async () => {
cli = new RocketCli({ cli = await executeBuild('e2e-fixtures/content/pathPrefix.rocket.config.js');
argv: [
'build',
'--config-file',
path.join(__dirname, 'e2e-fixtures', 'content', 'pathPrefix.rocket.config.js'),
],
});
await execute();
const linkHtml = await readOutput('link/index.html', { const linkHtml = await readBuildOutput(cli, 'link/index.html', {
stripServiceWorker: true, stripServiceWorker: true,
stripToBody: true, stripToBody: true,
}); });
@@ -263,7 +120,7 @@ describe('RocketCli e2e', () => {
'\n', '\n',
), ),
); );
const assetHtml = await readOutput('use-assets/index.html', { const assetHtml = await readBuildOutput(cli, 'use-assets/index.html', {
stripServiceWorker: true, stripServiceWorker: true,
}); });
expect(assetHtml).to.equal( expect(assetHtml).to.equal(

View File

@@ -0,0 +1,87 @@
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">',
' import { myData } from "../sub/assets/myData.js";',
' import("../sub/assets/myData.js");',
' const name = "myData";',
' import(`../sub/assets/${name}.js`);',
'</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">',
' import { myData } from "./assets/myData.js";',
' import("./assets/myData.js");',
' const name = "myData";',
' import(`./assets/${name}.js`);',
'</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">',
' import { myData } from "../assets/myData.js";',
' import("../assets/myData.js");',
' const name = "myData";',
' import(`../assets/${name}.js`);',
'</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">',
' import { myData } from "./sub/assets/myData.js";',
' import("./sub/assets/myData.js");',
' const name = "myData";',
' import(`./sub/assets/${name}.js`);',
'</script>',
].join('\n'),
);
});
});

View File

@@ -0,0 +1,4 @@
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
const config = {};
export default config;

View File

@@ -2,6 +2,9 @@ import { adjustPluginOptions } from 'plugins-manager';
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */ /** @type {Partial<import("../../../types/main").RocketCliOptions>} */
const config = { const config = {
start: {
createSocialMediaImages: true,
},
setupEleventyComputedConfig: [ setupEleventyComputedConfig: [
adjustPluginOptions('socialMediaImage', { adjustPluginOptions('socialMediaImage', {
createSocialImageSvg: async () => { createSocialImageSvg: async () => {

View File

@@ -1 +0,0 @@
module.exports = 'layout.njk';

View File

@@ -0,0 +1 @@
{{ socialMediaImage }}

View File

@@ -1,4 +1,8 @@
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */ /** @type {Partial<import("../../../types/main").RocketCliOptions>} */
const config = {}; const config = {
start: {
createSocialMediaImages: true,
},
};
export default config; export default config;

View File

@@ -6,7 +6,7 @@ import json from '@rollup/plugin-json';
import { addPlugin, adjustPluginOptions } from 'plugins-manager'; import { addPlugin, adjustPluginOptions } from 'plugins-manager';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const outputDir = path.join(__dirname, '..', '__output'); const outputDir = path.join(__dirname, '__output');
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */ /** @type {Partial<import("../../../types/main").RocketCliOptions>} */
const config = { const config = {

View File

@@ -29,6 +29,7 @@ describe('normalizeConfig', () => {
expect(cleanup(config)).to.deep.equal({ expect(cleanup(config)).to.deep.equal({
command: 'help', command: 'help',
createSocialMediaImages: true,
devServer: {}, devServer: {},
build: {}, build: {},
watch: true, watch: true,
@@ -61,6 +62,7 @@ describe('normalizeConfig', () => {
expect(cleanup(config)).to.deep.equal({ expect(cleanup(config)).to.deep.equal({
command: 'help', command: 'help',
createSocialMediaImages: true,
devServer: { devServer: {
more: 'settings', more: 'settings',
}, },
@@ -92,6 +94,7 @@ describe('normalizeConfig', () => {
expect(cleanup(config)).to.deep.equal({ expect(cleanup(config)).to.deep.equal({
command: 'help', command: 'help',
createSocialMediaImages: true,
devServer: { devServer: {
more: 'from-file', more: 'from-file',
}, },
@@ -128,6 +131,7 @@ describe('normalizeConfig', () => {
expect(cleanup(config)).to.deep.equal({ expect(cleanup(config)).to.deep.equal({
command: 'help', command: 'help',
createSocialMediaImages: true,
devServer: {}, devServer: {},
build: {}, build: {},
watch: true, watch: true,

View File

@@ -0,0 +1 @@
**/*.njk

View File

@@ -0,0 +1 @@
{{ content | safe }}

View File

@@ -0,0 +1,8 @@
`about.md`
```js script
import { myData } from './sub/assets/myData.js';
import('./sub/assets/myData.js');
const name = 'myData';
import(`./sub/assets/${name}.js`);
```

View File

@@ -0,0 +1,8 @@
`index.md`
```js script
import { myData } from './sub/assets/myData.js';
import('./sub/assets/myData.js');
const name = 'myData';
import(`./sub/assets/${name}.js`);
```

View File

@@ -0,0 +1 @@
export const myData = 'The answer to everything is 42';

View File

@@ -0,0 +1,8 @@
`sub/details.md`
```js script
import { myData } from './assets/myData.js';
import('./assets/myData.js');
const name = 'myData';
import(`./assets/${name}.js`);
```

View File

@@ -0,0 +1,8 @@
`sub/index.md`
```js script
import { myData } from './assets/myData.js';
import('./assets/myData.js');
const name = 'myData';
import(`./assets/${name}.js`);
```

View File

@@ -0,0 +1,4 @@
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
const config = {};
export default config;

View File

@@ -13,14 +13,21 @@ export interface RocketPreset {
setupEleventyComputedConfig: function[]; setupEleventyComputedConfig: function[];
} }
interface RocketStartConfig {
createSocialMediaImages?: boolean;
}
export interface RocketCliOptions { export interface RocketCliOptions {
presets: Array<RocketPreset>; presets: Array<RocketPreset>;
pathPrefix?: string; pathPrefix?: string;
inputDir: string; inputDir: string;
outputDir: string; outputDir: string;
emptyOutputDir?: boolen; emptyOutputDir?: boolean;
absoluteBaseUrl?: string; absoluteBaseUrl?: string;
watch: boolean; watch: boolean;
createSocialMediaImages?: boolean;
start?: RocketStartConfig;
// TODO: improve all setup functions // TODO: improve all setup functions
setupUnifiedPlugins?: function[]; setupUnifiedPlugins?: function[];

View File

@@ -1,5 +1,11 @@
# @rocket/eleventy-plugin-mdjs-unified # @rocket/eleventy-plugin-mdjs-unified
## 0.3.1
### Patch Changes
- f44a0f4: Rewrite dynamic imports with "`"
## 0.3.0 ## 0.3.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rocket/eleventy-plugin-mdjs-unified", "name": "@rocket/eleventy-plugin-mdjs-unified",
"version": "0.3.0", "version": "0.3.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View File

@@ -72,10 +72,14 @@ async function processImports(source, inputPath) {
newSource += '.' + importSrc; newSource += '.' + importSrc;
} else if (importSrc.startsWith("'./")) { } else if (importSrc.startsWith("'./")) {
newSource += "'." + importSrc.substring(1); newSource += "'." + importSrc.substring(1);
} else if (importSrc.startsWith('`./')) {
newSource += '`.' + importSrc.substring(1);
} else if (importSrc.startsWith('../')) { } else if (importSrc.startsWith('../')) {
newSource += '../' + importSrc; newSource += '../' + importSrc;
} else if (importSrc.startsWith("'../")) { } else if (importSrc.startsWith("'../")) {
newSource += "'../" + importSrc.substring(1); newSource += "'../" + importSrc.substring(1);
} else if (importSrc.startsWith('`../')) {
newSource += '`../' + importSrc.substring(1);
} else { } else {
newSource += importSrc; newSource += importSrc;
} }

View File

@@ -1,6 +1,13 @@
# @mdjs/mdjs-preview # @mdjs/mdjs-preview
## 0.3.1
### Patch Changes
- ee6b404: Pass on the shadowRoot to the story function
## 0.3.0 ## 0.3.0
### Minor Changes ### Minor Changes
- 15e0abe: Clean up dependencies - add Types - 15e0abe: Clean up dependencies - add Types

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mdjs/mdjs-preview", "name": "@mdjs/mdjs-preview",
"version": "0.3.0", "version": "0.3.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View File

@@ -1,6 +1,19 @@
import { LitElement, html, css } from 'lit-element'; import { LitElement, html, css } from 'lit-element';
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
/**
* @typedef {object} StoryOptions
* @property {ShadowRoot | null} StoryOptions.shadowRoot
*/
/** @typedef {(options?: StoryOptions) => ReturnType<LitElement['render']>} LitHtmlStoryFn */
/**
* Renders a story within a preview frame
*
* @element mdjs-preview
* @prop {StoryFn} [story=(() => TemplateResult)] Function that returns the story
*/
export class MdJsPreview extends LitElement { export class MdJsPreview extends LitElement {
static get properties() { static get properties() {
return { return {
@@ -28,6 +41,7 @@ export class MdJsPreview extends LitElement {
constructor() { constructor() {
super(); super();
this.code = ''; this.code = '';
/** @type {LitHtmlStoryFn} */
this.story = () => html` <p>Loading...</p> `; this.story = () => html` <p>Loading...</p> `;
this.codeHasHtml = false; this.codeHasHtml = false;
} }
@@ -35,7 +49,7 @@ export class MdJsPreview extends LitElement {
render() { render() {
return html` return html`
<div id="wrapper"> <div id="wrapper">
<div>${this.story()}</div> <div>${this.story({ shadowRoot: this.shadowRoot })}</div>
<button id="showCodeButton" @click=${this.toggleShowCode}>show code</button> <button id="showCodeButton" @click=${this.toggleShowCode}>show code</button>
</div> </div>
${this.codeHasHtml ? unsafeHTML(this.code) : html`<pre><code>${this.code}</code></pre>`} ${this.codeHasHtml ? unsafeHTML(this.code) : html`<pre><code>${this.code}</code></pre>`}

View File

@@ -1,6 +1,13 @@
# @mdjs/mdjs-story # @mdjs/mdjs-story
## 0.1.1
### Patch Changes
- ee6b404: Pass on the shadowRoot to the story function
## 0.1.0 ## 0.1.0
### Minor Changes ### Minor Changes
- 15e0abe: Clean up dependencies - add Types - 15e0abe: Clean up dependencies - add Types

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mdjs/mdjs-story", "name": "@mdjs/mdjs-story",
"version": "0.1.0", "version": "0.1.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View File

@@ -1,5 +1,18 @@
import { LitElement, html } from 'lit-element'; import { LitElement, html } from 'lit-element';
/**
* @typedef {object} StoryOptions
* @property {ShadowRoot | null} StoryOptions.shadowRoot
*/
/** @typedef {(options?: StoryOptions) => ReturnType<LitElement['render']>} LitHtmlStoryFn */
/**
* Renders a story
*
* @element mdjs-story
* @prop {StoryFn} [story=(() => TemplateResult)] Function that returns the story
*/
export class MdJsStory extends LitElement { export class MdJsStory extends LitElement {
static get properties() { static get properties() {
return { return {
@@ -11,10 +24,11 @@ export class MdJsStory extends LitElement {
constructor() { constructor() {
super(); super();
this.story = () => html` <p>Loading...</p> `; /** @type {LitHtmlStoryFn} */
this.story = () => html`<p>Loading...</p>`;
} }
render() { render() {
return this.story(); return this.story({ shadowRoot: this.shadowRoot });
} }
} }