mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-10 08:51:24 +00:00
feat(cli): introduce "rocket lint"
This commit is contained in:
15
.changeset/bright-emus-design.md
Normal file
15
.changeset/bright-emus-design.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
'@rocket/cli': patch
|
||||
---
|
||||
|
||||
Introducing `rocket lint` to verify if all your links are correct.
|
||||
|
||||
There are two modes:
|
||||
|
||||
```bash
|
||||
# check existing production build in _site (need to execute "rocket build" before)
|
||||
rocket lint
|
||||
|
||||
# run a fast html only build and then check it
|
||||
rocket lint --build-html
|
||||
```
|
||||
@@ -54,7 +54,7 @@
|
||||
"dependencies": {
|
||||
"@rocket/building-rollup": "^0.4.0",
|
||||
"@rocket/engine": "^0.2.6",
|
||||
"@web/rollup-plugin-copy": "^0.3.0",
|
||||
"check-html-links": "^0.2.3",
|
||||
"colorette": "^2.0.16",
|
||||
"commander": "^9.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
|
||||
@@ -1,78 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
|
||||
import { Engine } from '@rocket/engine/server';
|
||||
import { gatherFiles } from '@rocket/engine';
|
||||
|
||||
import { fromRollup } from '@web/dev-server-rollup';
|
||||
|
||||
import { rollup } from 'rollup';
|
||||
import path from 'path';
|
||||
import { rollupPluginHTML } from '@web/rollup-plugin-html';
|
||||
|
||||
import { createMpaConfig, createServiceWorkerConfig } from '@rocket/building-rollup';
|
||||
import { adjustPluginOptions } from 'plugins-manager';
|
||||
import { existsSync } from 'fs';
|
||||
import { readFile, unlink, writeFile } from 'fs/promises';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
/**
|
||||
* @param {object} config
|
||||
*/
|
||||
async function buildAndWrite(config) {
|
||||
const bundle = await rollup(config);
|
||||
|
||||
if (Array.isArray(config.output)) {
|
||||
await bundle.write(config.output[0]);
|
||||
await bundle.write(config.output[1]);
|
||||
} else {
|
||||
await bundle.write(config.output);
|
||||
}
|
||||
}
|
||||
|
||||
async function productionBuild(config) {
|
||||
const defaultSetupPlugins = [];
|
||||
if (config.pathPrefix) {
|
||||
defaultSetupPlugins.push(
|
||||
adjustPluginOptions(rollupPluginHTML, { absolutePathPrefix: config.pathPrefix }),
|
||||
);
|
||||
}
|
||||
|
||||
const mpaConfig = createMpaConfig({
|
||||
input: '**/*.html',
|
||||
output: {
|
||||
dir: config.outputDir,
|
||||
},
|
||||
// custom
|
||||
rootDir: path.resolve(config.outputDevDir),
|
||||
absoluteBaseUrl: config.absoluteBaseUrl,
|
||||
setupPlugins: [
|
||||
...defaultSetupPlugins,
|
||||
...config.setupDevServerAndBuildPlugins,
|
||||
...config.setupBuildPlugins,
|
||||
],
|
||||
});
|
||||
const finalConfig =
|
||||
typeof config.adjustBuildOptions === 'function'
|
||||
? config.adjustBuildOptions(mpaConfig)
|
||||
: mpaConfig;
|
||||
await buildAndWrite(finalConfig);
|
||||
|
||||
const { serviceWorkerSourcePath } = config;
|
||||
if (existsSync(serviceWorkerSourcePath)) {
|
||||
const serviceWorkerConfig = createServiceWorkerConfig({
|
||||
input: serviceWorkerSourcePath,
|
||||
output: {
|
||||
file: path.join(path.resolve(config.outputDir), config.serviceWorkerName),
|
||||
},
|
||||
});
|
||||
|
||||
await buildAndWrite(serviceWorkerConfig);
|
||||
}
|
||||
}
|
||||
import { buildHtml } from './build/buildHtml.js';
|
||||
import { buildOpenGraphImages } from './build/buildOpenGraphImages.js';
|
||||
import { buildJavaScriptOptimizedOutput } from './build/buildJavaScriptOptimizedOutput.js';
|
||||
|
||||
export class RocketBuild {
|
||||
/**
|
||||
* @param {import('commander').Command} program
|
||||
* @param {import('./RocketCli.js').RocketCli} cli
|
||||
*/
|
||||
async setupCommand(program, cli) {
|
||||
this.cli = cli;
|
||||
|
||||
@@ -87,32 +27,30 @@ export class RocketBuild {
|
||||
}
|
||||
|
||||
async build() {
|
||||
await this.cli.events.dispatchEventDone('build-start');
|
||||
await this.cli.clearOutputDir();
|
||||
await this.cli.clearOutputDevDir();
|
||||
|
||||
this.engine = new Engine();
|
||||
this.engine.setOptions({
|
||||
docsDir: this.cli.options.inputDir,
|
||||
outputDir: this.cli.options.outputDevDir,
|
||||
setupPlugins: this.cli.options.setupEnginePlugins,
|
||||
longFileHeaderWidth: this.cli.options.longFileHeaderWidth,
|
||||
longFileHeaderComment: this.cli.options.longFileHeaderComment,
|
||||
renderMode: 'production',
|
||||
clearOutputDir: this.cli.options.clearOutputDir,
|
||||
});
|
||||
console.log('Engine building...');
|
||||
await this.engine.build({ autoStop: this.cli.options.buildAutoStop });
|
||||
|
||||
if (this.cli.options.buildOpenGraphImages) {
|
||||
console.log('Generating Open Graph Images...');
|
||||
await this.buildOpenGraphImages();
|
||||
if (!this.cli) {
|
||||
return;
|
||||
}
|
||||
// for typescript as `this.cli.options.outputDir` supports other inputs as well
|
||||
// but the cli will normalize it to a string before calling plugins
|
||||
if (typeof this.cli.options.outputDir !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cli.options.buildOptimize) {
|
||||
await this.cli.events.dispatchEventDone('build-start');
|
||||
|
||||
// 1. build html
|
||||
this.engine = await buildHtml(this.cli);
|
||||
|
||||
// 2. build open graph images
|
||||
if (this.cli.options.buildOpenGraphImages) {
|
||||
console.log('Generating Open Graph Images...');
|
||||
await buildOpenGraphImages(this.cli);
|
||||
}
|
||||
|
||||
// 3. build optimized output
|
||||
if (this.cli.options.buildOptimize && this.engine) {
|
||||
console.log('Optimize Production Build...');
|
||||
await productionBuild(this.cli.options);
|
||||
await this.engine.copyPublicFilesTo(this.cli.options.outputDir);
|
||||
await buildJavaScriptOptimizedOutput(this.cli, this.engine);
|
||||
}
|
||||
|
||||
// hackfix 404.html by making all asset urls absolute (rollup always makes them relative) which will break if netlify serves the content form a different url
|
||||
@@ -130,87 +68,4 @@ export class RocketBuild {
|
||||
|
||||
await this.cli.events.dispatchEventDone('build-end');
|
||||
}
|
||||
|
||||
async buildOpenGraphImages() {
|
||||
const openGraphFiles = await gatherFiles(this.cli.options.outputDevDir, {
|
||||
fileEndings: ['.opengraph.html'],
|
||||
});
|
||||
if (openGraphFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: enable URL support in the Engine and remove this "workaround"
|
||||
if (
|
||||
typeof this.cli.options.inputDir !== 'string' ||
|
||||
typeof this.cli.options.outputDevDir !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const withWrap = this.cli.options.setupDevServerAndBuildPlugins
|
||||
? this.cli.options.setupDevServerAndBuildPlugins.map(modFunction => {
|
||||
modFunction.wrapPlugin = fromRollup;
|
||||
return modFunction;
|
||||
})
|
||||
: [];
|
||||
|
||||
this.engine = new Engine();
|
||||
this.engine.setOptions({
|
||||
docsDir: this.cli.options.inputDir,
|
||||
outputDir: this.cli.options.outputDevDir,
|
||||
setupPlugins: this.cli.options.setupEnginePlugins,
|
||||
open: false,
|
||||
clearOutputDir: false,
|
||||
adjustDevServerOptions: this.cli.options.adjustDevServerOptions,
|
||||
setupDevServerMiddleware: this.cli.options.setupDevServerMiddleware,
|
||||
setupDevServerPlugins: [...this.cli.options.setupDevServerPlugins, ...withWrap],
|
||||
});
|
||||
try {
|
||||
await this.engine.start();
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
// In 2022 Twitter & Facebook recommend a size of 1200x628 - we capture with 2 dpr for retina displays
|
||||
await page.setViewport({
|
||||
width: 1200,
|
||||
height: 628,
|
||||
deviceScaleFactor: 2,
|
||||
});
|
||||
|
||||
for (const openGraphFile of openGraphFiles) {
|
||||
const relUrl = path.relative(this.cli.options.outputDevDir, openGraphFile);
|
||||
const imagePath = openGraphFile.replace('.opengraph.html', '.opengraph.png');
|
||||
const htmlPath = openGraphFile.replace('.opengraph.html', '.html');
|
||||
const relImageUrl = path.basename(imagePath);
|
||||
|
||||
let htmlString = await readFile(htmlPath, 'utf8');
|
||||
if (!htmlString.includes('<meta property="og:image"')) {
|
||||
if (htmlString.includes('</head>')) {
|
||||
htmlString = htmlString.replace(
|
||||
'</head>',
|
||||
[
|
||||
' <meta property="og:image:width" content="2400">',
|
||||
' <meta property="og:image:height" content="1256">',
|
||||
` <meta property="og:image" content="./${relImageUrl}">`,
|
||||
' </head>',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
}
|
||||
const url = `http://localhost:${this.engine.devServer.config.port}/${relUrl}`;
|
||||
await page.goto(url, { waitUntil: 'networkidle0' });
|
||||
await page.screenshot({ path: imagePath });
|
||||
|
||||
await unlink(openGraphFile);
|
||||
await writeFile(htmlPath, htmlString);
|
||||
}
|
||||
await browser.close();
|
||||
|
||||
await this.engine.stop();
|
||||
} catch (e) {
|
||||
console.log('Could not start dev server to generate open graph images');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Command } from 'commander';
|
||||
import { RocketStart } from './RocketStart.js';
|
||||
import { RocketBuild } from './RocketBuild.js';
|
||||
import { RocketLint } from './RocketLint.js';
|
||||
import { RocketUpgrade } from './RocketUpgrade.js';
|
||||
import { RocketPreview } from './RocketPreview.js';
|
||||
// import { ignore } from './images/ignore.js';
|
||||
@@ -53,6 +54,10 @@ export class RocketCli {
|
||||
absoluteBaseUrl: '',
|
||||
clearOutputDir: true,
|
||||
|
||||
lint: {
|
||||
buildHtml: false,
|
||||
},
|
||||
|
||||
// /** @type {{[key: string]: ImagePreset}} */
|
||||
// imagePresets: {
|
||||
// responsive: {
|
||||
@@ -179,7 +184,7 @@ export class RocketCli {
|
||||
let pluginsMeta = [
|
||||
{ plugin: RocketStart, options: {} },
|
||||
{ plugin: RocketBuild, options: {} },
|
||||
// { plugin: RocketLint },
|
||||
{ plugin: RocketLint, options: {} },
|
||||
{ plugin: RocketUpgrade, options: {} },
|
||||
{ plugin: RocketPreview, options: {} },
|
||||
];
|
||||
|
||||
@@ -1,81 +1,85 @@
|
||||
// /* eslint-disable */
|
||||
// // @ts-nocheck
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
// /** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
|
||||
// @ts-ignore
|
||||
import { CheckHtmlLinksCli } from 'check-html-links';
|
||||
import { bold, gray } from 'colorette';
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { buildHtml } from './build/buildHtml.js';
|
||||
|
||||
// import { CheckHtmlLinksCli } from 'check-html-links';
|
||||
export class RocketLint {
|
||||
options = {
|
||||
buildHtml: false,
|
||||
};
|
||||
|
||||
// export class RocketLint {
|
||||
// static pluginName = 'RocketLint';
|
||||
// commands = ['start', 'build', 'lint'];
|
||||
/**
|
||||
* @param {import('commander').Command} program
|
||||
* @param {import('./RocketCli.js').RocketCli} cli
|
||||
*/
|
||||
async setupCommand(program, cli) {
|
||||
this.cli = cli;
|
||||
this.active = true;
|
||||
|
||||
// /**
|
||||
// * @param {RocketCliOptions} config
|
||||
// */
|
||||
// setupCommand(config) {
|
||||
// if (config.command === 'lint') {
|
||||
// config.watch = false;
|
||||
// }
|
||||
// return config;
|
||||
// }
|
||||
program
|
||||
.command('lint')
|
||||
.option('-i, --input-dir <path>', 'path to where to search for source files')
|
||||
.option('-b, --build-html', 'do a quick html only build and then check links')
|
||||
.action(async options => {
|
||||
const { cliOptions, ...lintOptions } = options;
|
||||
cli.setOptions({
|
||||
...cliOptions,
|
||||
lint: lintOptions,
|
||||
});
|
||||
this.options = { ...this.options, ...cli.options.lint };
|
||||
cli.activePlugin = this;
|
||||
|
||||
// /**
|
||||
// * @param {object} options
|
||||
// * @param {RocketCliOptions} options.config
|
||||
// * @param {any} options.argv
|
||||
// */
|
||||
// async setup({ config, argv, eleventy }) {
|
||||
// this.__argv = argv;
|
||||
// this.config = {
|
||||
// lintInputDir: config.outputDevDir,
|
||||
// lintExecutesEleventyBefore: true,
|
||||
// ...config,
|
||||
// };
|
||||
// this.eleventy = eleventy;
|
||||
// }
|
||||
await this.lint();
|
||||
});
|
||||
}
|
||||
|
||||
// async lintCommand() {
|
||||
// if (this.config.lintExecutesEleventyBefore) {
|
||||
// await this.eleventy.write();
|
||||
// // updated will trigger linting
|
||||
// } else {
|
||||
// await this.__lint();
|
||||
// }
|
||||
// }
|
||||
async lint() {
|
||||
if (!this.cli) {
|
||||
return;
|
||||
}
|
||||
|
||||
// async __lint() {
|
||||
// if (this.config?.pathPrefix) {
|
||||
// console.log('INFO: RocketLint currently does not support being used with a pathPrefix');
|
||||
// return;
|
||||
// }
|
||||
// for typescript as `this.cli.options.outputDir` supports other inputs as well
|
||||
// but the cli will normalize it to a string before calling plugins
|
||||
if (
|
||||
typeof this.cli.options.outputDevDir !== 'string' ||
|
||||
typeof this.cli.options.outputDir !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const checkLinks = new CheckHtmlLinksCli();
|
||||
// checkLinks.setOptions({
|
||||
// ...this.config.checkLinks,
|
||||
// rootDir: this.config.lintInputDir,
|
||||
// printOnError: false,
|
||||
// continueOnError: true,
|
||||
// });
|
||||
if (this.options.buildHtml) {
|
||||
await buildHtml(this.cli);
|
||||
}
|
||||
|
||||
// const { errors, message } = await checkLinks.run();
|
||||
// if (errors.length > 0) {
|
||||
// if (this.config.command === 'start') {
|
||||
// console.log(message);
|
||||
// } else {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
const folderToCheck = this.options.buildHtml
|
||||
? this.cli.options.outputDevDir
|
||||
: this.cli.options.outputDir;
|
||||
|
||||
// async postCommand() {
|
||||
// if (this.config.watch === false) {
|
||||
// await this.__lint();
|
||||
// }
|
||||
// }
|
||||
const rootIndexHtml = path.join(folderToCheck, 'index.html');
|
||||
if (!existsSync(rootIndexHtml)) {
|
||||
console.log(`${bold(`👀 Linting Production Build`)}`);
|
||||
console.log('');
|
||||
console.log(` 🛑 No index.html found in the build directory ${gray(`${rootIndexHtml}`)}`);
|
||||
console.log(' 🤔 Did you forget to run `rocket build` before?');
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
// async updated() {
|
||||
// if (this.config.watch === true) {
|
||||
// await this.__lint();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { buildHtml: _drop, ...userCheckHtmlLinksOptions } = this.options;
|
||||
|
||||
const checkLinks = new CheckHtmlLinksCli();
|
||||
checkLinks.setOptions({
|
||||
rootDir: folderToCheck,
|
||||
printOnError: true,
|
||||
continueOnError: false,
|
||||
...userCheckHtmlLinksOptions,
|
||||
});
|
||||
|
||||
await checkLinks.run();
|
||||
}
|
||||
}
|
||||
|
||||
28
packages/cli/src/build/buildHtml.js
Normal file
28
packages/cli/src/build/buildHtml.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Engine } from '@rocket/engine/server';
|
||||
|
||||
/**
|
||||
* @param {import('../RocketCli.js').RocketCli} cli
|
||||
* @returns
|
||||
*/
|
||||
export async function buildHtml(cli) {
|
||||
// TODO: enable URL support in the Engine and remove this typescript "workaround"
|
||||
if (typeof cli.options.inputDir !== 'string' || typeof cli.options.outputDevDir !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
await cli.clearOutputDevDir();
|
||||
const engine = new Engine();
|
||||
engine.setOptions({
|
||||
docsDir: cli.options.inputDir,
|
||||
outputDir: cli.options.outputDevDir,
|
||||
setupPlugins: cli.options.setupEnginePlugins,
|
||||
longFileHeaderWidth: cli.options.longFileHeaderWidth,
|
||||
longFileHeaderComment: cli.options.longFileHeaderComment,
|
||||
renderMode: 'production',
|
||||
clearOutputDir: cli.options.clearOutputDir,
|
||||
});
|
||||
console.log('Engine building...');
|
||||
await engine.build({ autoStop: cli.options.buildAutoStop });
|
||||
|
||||
return engine;
|
||||
}
|
||||
88
packages/cli/src/build/buildJavaScriptOptimizedOutput.js
Normal file
88
packages/cli/src/build/buildJavaScriptOptimizedOutput.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { rollup } from 'rollup';
|
||||
|
||||
// @ts-ignore
|
||||
import { createMpaConfig, createServiceWorkerConfig } from '@rocket/building-rollup';
|
||||
|
||||
// import { rollupPluginHTML } from '@web/rollup-plugin-html';
|
||||
// import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/**
|
||||
* @param {import('rollup').RollupOptions} config
|
||||
*/
|
||||
async function buildAndWrite(config) {
|
||||
if (!config.output) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = await rollup(config);
|
||||
|
||||
if (Array.isArray(config.output)) {
|
||||
await bundle.write(config.output[0]);
|
||||
await bundle.write(config.output[1]);
|
||||
} else {
|
||||
await bundle.write(config.output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('../RocketCli.js').RocketCli} cli
|
||||
* @param {import('@rocket/engine/server').Engine} engine
|
||||
* @returns
|
||||
*/
|
||||
export async function buildJavaScriptOptimizedOutput(cli, engine) {
|
||||
const config = cli.options;
|
||||
|
||||
// for typescript as `this.cli.options.outputDir` supports other inputs as well
|
||||
// but the cli will normalize it to a string before calling plugins
|
||||
if (typeof config.outputDir !== 'string' || typeof config.outputDevDir !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
await cli.clearOutputDir();
|
||||
|
||||
// TODO: pathPrefix is currently not supported
|
||||
// const defaultSetupPlugins = [];
|
||||
// if (config.pathPrefix) {
|
||||
// defaultSetupPlugins.push(
|
||||
// adjustPluginOptions(rollupPluginHTML, { absolutePathPrefix: config.pathPrefix }),
|
||||
// );
|
||||
// }
|
||||
|
||||
const mpaConfig = createMpaConfig({
|
||||
input: '**/*.html',
|
||||
output: {
|
||||
dir: config.outputDir,
|
||||
},
|
||||
// custom
|
||||
rootDir: path.resolve(config.outputDevDir),
|
||||
absoluteBaseUrl: config.absoluteBaseUrl,
|
||||
setupPlugins: [
|
||||
// ...defaultSetupPlugins,
|
||||
...config.setupDevServerAndBuildPlugins,
|
||||
...config.setupBuildPlugins,
|
||||
],
|
||||
});
|
||||
const finalConfig =
|
||||
typeof config.adjustBuildOptions === 'function'
|
||||
? config.adjustBuildOptions(mpaConfig)
|
||||
: mpaConfig;
|
||||
await buildAndWrite(finalConfig);
|
||||
|
||||
const { serviceWorkerSourcePath } = config;
|
||||
if (existsSync(serviceWorkerSourcePath)) {
|
||||
const serviceWorkerConfig = createServiceWorkerConfig({
|
||||
input: serviceWorkerSourcePath,
|
||||
output: {
|
||||
file: path.join(path.resolve(config.outputDir), config.serviceWorkerName),
|
||||
},
|
||||
});
|
||||
|
||||
await buildAndWrite(serviceWorkerConfig);
|
||||
}
|
||||
|
||||
// copy static files over
|
||||
await engine.copyPublicFilesTo(config.outputDir);
|
||||
}
|
||||
95
packages/cli/src/build/buildOpenGraphImages.js
Normal file
95
packages/cli/src/build/buildOpenGraphImages.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { gatherFiles } from '@rocket/engine';
|
||||
import { Engine } from '@rocket/engine/server';
|
||||
import { fromRollup } from '@web/dev-server-rollup';
|
||||
|
||||
import { readFile, unlink, writeFile } from 'fs/promises';
|
||||
|
||||
import puppeteer from 'puppeteer';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* @param {import('../RocketCli.js').RocketCli} cli
|
||||
* @returns
|
||||
*/
|
||||
export async function buildOpenGraphImages(cli) {
|
||||
const openGraphFiles = await gatherFiles(cli.options.outputDevDir, {
|
||||
fileEndings: ['.opengraph.html'],
|
||||
});
|
||||
if (openGraphFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: enable URL support in the Engine and remove this typescript "workaround"
|
||||
if (typeof cli.options.inputDir !== 'string' || typeof cli.options.outputDevDir !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
const withWrap = cli.options.setupDevServerAndBuildPlugins
|
||||
? cli.options.setupDevServerAndBuildPlugins.map(modFunction => {
|
||||
modFunction.wrapPlugin = fromRollup;
|
||||
return modFunction;
|
||||
})
|
||||
: [];
|
||||
|
||||
const engine = new Engine();
|
||||
engine.setOptions({
|
||||
docsDir: cli.options.inputDir,
|
||||
outputDir: cli.options.outputDevDir,
|
||||
setupPlugins: cli.options.setupEnginePlugins,
|
||||
open: false,
|
||||
clearOutputDir: false,
|
||||
adjustDevServerOptions: cli.options.adjustDevServerOptions,
|
||||
setupDevServerMiddleware: cli.options.setupDevServerMiddleware,
|
||||
setupDevServerPlugins: [...cli.options.setupDevServerPlugins, ...withWrap],
|
||||
});
|
||||
try {
|
||||
await engine.start();
|
||||
if (!engine?.devServer?.config.port) {
|
||||
return;
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
// In 2022 Twitter & Facebook recommend a size of 1200x628 - we capture with 2 dpr for retina displays
|
||||
await page.setViewport({
|
||||
width: 1200,
|
||||
height: 628,
|
||||
deviceScaleFactor: 2,
|
||||
});
|
||||
|
||||
for (const openGraphFile of openGraphFiles) {
|
||||
const relUrl = path.relative(cli.options.outputDevDir, openGraphFile);
|
||||
const imagePath = openGraphFile.replace('.opengraph.html', '.opengraph.png');
|
||||
const htmlPath = openGraphFile.replace('.opengraph.html', '.html');
|
||||
const relImageUrl = path.basename(imagePath);
|
||||
|
||||
let htmlString = await readFile(htmlPath, 'utf8');
|
||||
if (!htmlString.includes('<meta property="og:image"')) {
|
||||
if (htmlString.includes('</head>')) {
|
||||
htmlString = htmlString.replace(
|
||||
'</head>',
|
||||
[
|
||||
' <meta property="og:image:width" content="2400">',
|
||||
' <meta property="og:image:height" content="1256">',
|
||||
` <meta property="og:image" content="./${relImageUrl}">`,
|
||||
' </head>',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
}
|
||||
const url = `http://localhost:${engine.devServer.config.port}/${relUrl}`;
|
||||
await page.goto(url, { waitUntil: 'networkidle0' });
|
||||
await page.screenshot({ path: imagePath });
|
||||
|
||||
await unlink(openGraphFile);
|
||||
await writeFile(htmlPath, htmlString);
|
||||
}
|
||||
await browser.close();
|
||||
|
||||
await engine.stop();
|
||||
} catch (e) {
|
||||
console.log('Could not start dev server to generate open graph images');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
5
packages/cli/types/main.d.ts
vendored
5
packages/cli/types/main.d.ts
vendored
@@ -47,6 +47,11 @@ export interface FullRocketCliOptions extends Pick<FullRocketPreset, PresetKeys>
|
||||
// rarely used
|
||||
configFile: string;
|
||||
outputDevDir: URL | string;
|
||||
|
||||
lint: {
|
||||
buildHtml: boolean;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export type RocketCliOptions = Partial<FullRocketCliOptions>;
|
||||
|
||||
@@ -34,6 +34,40 @@ const { resolve } = createRequire(new URL('.', import.meta.url));
|
||||
|
||||
A few things are usually needed before going live "for real".
|
||||
|
||||
## Make sure all links are correct
|
||||
|
||||
When you launch a website you don't want the first feedback to be "that link doesn't work".
|
||||
|
||||
To prevent this we want to execute `rocket lint` before going live.
|
||||
It will make sure all internal links are correct by using [check-html-links](../../30--tools/40--check-html-links/10--overview.rocket.md).
|
||||
Typically we deploy via a Continuous Integration system like GitHub Actions or Netlify Deploy.
|
||||
We can also integrate the lint command into that process.
|
||||
|
||||
```
|
||||
rocket build
|
||||
rocket lint
|
||||
```
|
||||
|
||||
### Fixing broken links
|
||||
|
||||
If found a couple of broken links on your page and you want to fix them and verify that they are now correct it might be a little time consuming to create a full production build every time.
|
||||
The reason is that a production build is doing a lot of things
|
||||
|
||||
1. Generate HTML
|
||||
2. Generate & Inject Open Graph Images
|
||||
3. Optimize Images (not available yet)
|
||||
4. Optimize JavaScript
|
||||
|
||||
But there is a way around this. We can use an optional flag `--build-html` which means it will run only (1) and then lint that (non-optimized) HTML output.
|
||||
|
||||
So for a more time efficient way of validating link use
|
||||
|
||||
```bash
|
||||
rocket lint --build-html
|
||||
```
|
||||
|
||||
Note: We can do this as 2-4 generally does not impact links/references (as long as the optimizations scripts do not have related bugs)
|
||||
|
||||
## Add a Not Found Page
|
||||
|
||||
When a user enters a URL that does not exist, a "famous" 404 Page Not Found error occurs.
|
||||
|
||||
@@ -1363,6 +1363,16 @@
|
||||
"id": "go-live",
|
||||
"level": 1
|
||||
},
|
||||
{
|
||||
"text": "Make sure all links are correct",
|
||||
"id": "make-sure-all-links-are-correct",
|
||||
"level": 2
|
||||
},
|
||||
{
|
||||
"text": "Fixing broken links",
|
||||
"id": "fixing-broken-links",
|
||||
"level": 3
|
||||
},
|
||||
{
|
||||
"text": "Add a Not Found Page",
|
||||
"id": "add-a-not-found-page",
|
||||
|
||||
@@ -2084,13 +2084,6 @@
|
||||
terser "^5.14.2"
|
||||
whatwg-fetch "^3.5.0"
|
||||
|
||||
"@web/rollup-plugin-copy@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@web/rollup-plugin-copy/-/rollup-plugin-copy-0.3.0.tgz#fe999b2ea3dd71c8e663e6947fc2eb92a221e8e8"
|
||||
integrity sha512-QNNtE7Svhk0/p21etaR0JQXYhlMgTAg/HmRXDMmQHMf3uOUWsWMGiJa96P49RRVJut1ECB5FDFeBUgFEmegysQ==
|
||||
dependencies:
|
||||
glob "^7.1.6"
|
||||
|
||||
"@web/rollup-plugin-html@^1.8.0":
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@web/rollup-plugin-html/-/rollup-plugin-html-1.11.0.tgz#46c2bcb3a3b9db55d53b897ffc7e7ef2f618a052"
|
||||
|
||||
Reference in New Issue
Block a user