nuxt-link-checker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.ts +23 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +117 -0
- package/dist/types.d.ts +11 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<h1 align='center'>nuxt-link-checker</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href='https://github.com/harlan-zw/nuxt-link-checker/actions/workflows/test.yml'>
|
|
5
|
+
</a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/nuxt-link-checker" target="__blank"><img src="https://img.shields.io/npm/v/nuxt-link-checker?style=flat&colorA=002438&colorB=28CF8D" alt="NPM version"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/nuxt-link-checker" target="__blank"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/nuxt-link-checker?flat&colorA=002438&colorB=28CF8D"></a>
|
|
8
|
+
<a href="https://github.com/harlan-zw/nuxt-link-checker" target="__blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/harlan-zw/nuxt-link-checker?flat&colorA=002438&colorB=28CF8D"></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
Improve your sites SEO by identifying and fixing link issues in your Nuxt v3 app.
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<table>
|
|
18
|
+
<tbody>
|
|
19
|
+
<td align="center">
|
|
20
|
+
<img width="800" height="0" /><br>
|
|
21
|
+
<i>Status:</i> Early Access</b> <br>
|
|
22
|
+
<sup> Please report any issues 🐛</sup><br>
|
|
23
|
+
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
|
|
24
|
+
<img width="800" height="0" />
|
|
25
|
+
</td>
|
|
26
|
+
</tbody>
|
|
27
|
+
</table>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- ⛰️ Discover broken links - 404s and internal redirects
|
|
33
|
+
- ❌ Fail on build if broken links are found (optional)
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
⚠️ The module is in early access and only works when pre-rendering.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install --save-dev nuxt-link-checker
|
|
41
|
+
|
|
42
|
+
# Using yarn
|
|
43
|
+
yarn add --dev nuxt-link-checker
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Setup
|
|
47
|
+
|
|
48
|
+
_nuxt.config.ts_
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export default defineNuxtConfig({
|
|
52
|
+
modules: [
|
|
53
|
+
'nuxt-link-checker',
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To have routes scanned for broken links automatically, they need to be pre-rendered by Nitro.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
export default defineNuxtConfig({
|
|
62
|
+
nitro: {
|
|
63
|
+
prerender: {
|
|
64
|
+
crawlLinks: true,
|
|
65
|
+
routes: [
|
|
66
|
+
'/',
|
|
67
|
+
// any URLs that can't be discovered by crawler
|
|
68
|
+
'/my-hidden-url'
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
### Set host (optional)
|
|
77
|
+
|
|
78
|
+
You'll need to provide the host of your site so that the crawler can resolve absolute URLs that may be internal.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
export default defineNuxtConfig({
|
|
82
|
+
// Recommended
|
|
83
|
+
runtimeConfig: {
|
|
84
|
+
siteUrl: 'https://example.com',
|
|
85
|
+
},
|
|
86
|
+
// OR
|
|
87
|
+
sitemap: {
|
|
88
|
+
host: 'https://example.com',
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Module Config
|
|
94
|
+
|
|
95
|
+
### `host`
|
|
96
|
+
|
|
97
|
+
- Type: `string`
|
|
98
|
+
- Default: `runtimeConfig.public.siteUrl`
|
|
99
|
+
- Required: `false`
|
|
100
|
+
|
|
101
|
+
The host of your site. This is required to validate absolute URLs which may be internal.
|
|
102
|
+
|
|
103
|
+
### `trailingSlash`
|
|
104
|
+
|
|
105
|
+
- Type: `boolean`
|
|
106
|
+
- Default: `false`
|
|
107
|
+
|
|
108
|
+
Whether internal links should have a trailing slash or not.
|
|
109
|
+
|
|
110
|
+
## Sponsors
|
|
111
|
+
|
|
112
|
+
<p align="center">
|
|
113
|
+
<a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
|
|
114
|
+
<img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
|
|
115
|
+
</a>
|
|
116
|
+
</p>
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT License © 2022-PRESENT [Harlan Wilton](https://github.com/harlan-zw)
|
package/dist/module.cjs
ADDED
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface ModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Should the URLs be inserted with a trailing slash.
|
|
6
|
+
*
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
trailingSlash: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Your site hostname. Used to determine if absolute links are internal.
|
|
12
|
+
*/
|
|
13
|
+
host?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the build should fail when a 404 is encountered.
|
|
16
|
+
*/
|
|
17
|
+
failOn404: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ModuleHooks {
|
|
20
|
+
}
|
|
21
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
22
|
+
|
|
23
|
+
export { ModuleHooks, ModuleOptions, _default as default };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { defineNuxtModule } from '@nuxt/kit';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { isRelative, parseURL } from 'ufo';
|
|
4
|
+
import { load } from 'cheerio';
|
|
5
|
+
|
|
6
|
+
const linkMap = {};
|
|
7
|
+
const EXT_REGEX = /\.[\da-z]+$/;
|
|
8
|
+
const allowedExtensions = /* @__PURE__ */ new Set(["", ".json"]);
|
|
9
|
+
function getExtension(path) {
|
|
10
|
+
return (path.match(EXT_REGEX) || [])[0] || "";
|
|
11
|
+
}
|
|
12
|
+
function extractLinks(html, from, { host, trailingSlash }) {
|
|
13
|
+
const links = [];
|
|
14
|
+
const _links = [];
|
|
15
|
+
const $ = load(html);
|
|
16
|
+
$("[href]").each((i, el) => {
|
|
17
|
+
const href = $(el).attr("href");
|
|
18
|
+
if (!href)
|
|
19
|
+
return;
|
|
20
|
+
if (host && !isRelative(href) && href.startsWith(host))
|
|
21
|
+
return;
|
|
22
|
+
if (!allowedExtensions.has(getExtension(href)))
|
|
23
|
+
return;
|
|
24
|
+
_links.push({
|
|
25
|
+
href,
|
|
26
|
+
badTrailingSlash: href !== "/" && (trailingSlash && !href.endsWith("/") || !trailingSlash && href.endsWith("/")),
|
|
27
|
+
element: $.html(el) || ""
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
for (const link of _links.filter(Boolean)) {
|
|
31
|
+
const parsed = parseURL(link.href);
|
|
32
|
+
if (parsed.protocol)
|
|
33
|
+
continue;
|
|
34
|
+
let { pathname } = parsed;
|
|
35
|
+
if (!pathname.startsWith("/")) {
|
|
36
|
+
const fromURL = new URL(from, "http://localhost");
|
|
37
|
+
pathname = new URL(pathname, fromURL).pathname;
|
|
38
|
+
}
|
|
39
|
+
links.push({
|
|
40
|
+
...link,
|
|
41
|
+
href: pathname
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return links;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const invalidStatusCodes = [404, 302, 301, 307, 303];
|
|
48
|
+
const module = defineNuxtModule({
|
|
49
|
+
meta: {
|
|
50
|
+
name: "nuxt-link-checker",
|
|
51
|
+
compatibility: {
|
|
52
|
+
nuxt: "^3.0.0",
|
|
53
|
+
bridge: false
|
|
54
|
+
},
|
|
55
|
+
configKey: "linkChecker"
|
|
56
|
+
},
|
|
57
|
+
defaults(nuxt) {
|
|
58
|
+
return {
|
|
59
|
+
host: nuxt.options.runtimeConfig.public?.siteUrl,
|
|
60
|
+
trailingSlash: nuxt.options.runtimeConfig.public?.trailingSlash || false,
|
|
61
|
+
failOn404: true
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
setup(config, nuxt) {
|
|
65
|
+
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
66
|
+
const invalidRoutes = {};
|
|
67
|
+
nitro.hooks.hook("prerender:generate", async (ctx) => {
|
|
68
|
+
if (ctx.contents && ctx.fileName?.endsWith(".html"))
|
|
69
|
+
linkMap[ctx.route] = extractLinks(ctx.contents, ctx.route, config);
|
|
70
|
+
if (ctx.error?.statusCode && invalidStatusCodes.includes(Number(ctx.error?.statusCode)))
|
|
71
|
+
invalidRoutes[ctx.route] = ctx.error.statusCode;
|
|
72
|
+
});
|
|
73
|
+
nitro.hooks.hook("close", async () => {
|
|
74
|
+
nitro.logger.info("Scanning for broken links...");
|
|
75
|
+
const links = Object.entries(linkMap);
|
|
76
|
+
const hasBrokenLinks = Object.keys(invalidRoutes).length;
|
|
77
|
+
let routeCount = 0;
|
|
78
|
+
let badLinkCount = 0;
|
|
79
|
+
links.forEach(([route, routes]) => {
|
|
80
|
+
const brokenLinks = routes.map((r) => {
|
|
81
|
+
return {
|
|
82
|
+
...r,
|
|
83
|
+
statusCode: invalidRoutes[r.href] || 200
|
|
84
|
+
};
|
|
85
|
+
}).filter((r) => r.statusCode !== 200 || r.badTrailingSlash);
|
|
86
|
+
if (brokenLinks.length) {
|
|
87
|
+
nitro.logger.log(chalk.gray(
|
|
88
|
+
` ${Number(++routeCount) === links.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${route}`
|
|
89
|
+
));
|
|
90
|
+
brokenLinks.forEach((link) => {
|
|
91
|
+
badLinkCount++;
|
|
92
|
+
if (link.statusCode !== 200) {
|
|
93
|
+
nitro.logger.log(chalk.red(
|
|
94
|
+
` ${link.href} ${link.statusCode}`
|
|
95
|
+
));
|
|
96
|
+
} else if (link.badTrailingSlash) {
|
|
97
|
+
nitro.logger.log(chalk.yellow(
|
|
98
|
+
` ${link.href} Wrong trailing slash`
|
|
99
|
+
));
|
|
100
|
+
}
|
|
101
|
+
nitro.logger.log(` ${chalk.gray(link.element)}`);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
if (hasBrokenLinks) {
|
|
106
|
+
nitro.logger[config.failOn404 ? "error" : "warn"](`Found ${badLinkCount} broken links.`);
|
|
107
|
+
if (config.failOn404) {
|
|
108
|
+
nitro.logger.log(chalk.gray('You can disable this by setting "linkChecker: { failOn404: false }" in your nuxt.config.ts.'));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export { module as default };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
import { ModuleOptions, ModuleHooks } from './module'
|
|
3
|
+
|
|
4
|
+
declare module '@nuxt/schema' {
|
|
5
|
+
interface NuxtConfig { ['linkChecker']?: Partial<ModuleOptions> }
|
|
6
|
+
interface NuxtOptions { ['linkChecker']?: ModuleOptions }
|
|
7
|
+
interface NuxtHooks extends ModuleHooks {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export { ModuleHooks, ModuleOptions, default } from './module'
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-link-checker",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"packageManager": "pnpm@7.18.0",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"funding": "https://github.com/sponsors/harlan-zw",
|
|
8
|
+
"homepage": "https://github.com/harlan-zw/nuxt-link-checker#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/harlan-zw/nuxt-link-checker.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/harlan-zw/nuxt-link-checker/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/types.d.ts",
|
|
19
|
+
"require": "./dist/module.cjs",
|
|
20
|
+
"import": "./dist/module.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/module.cjs",
|
|
24
|
+
"types": "./dist/types.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nuxt/kit": "3.0.0",
|
|
30
|
+
"chalk": "^5.2.0",
|
|
31
|
+
"cheerio": "1.0.0-rc.12",
|
|
32
|
+
"defu": "^6.1.1",
|
|
33
|
+
"radix3": "^1.0.0",
|
|
34
|
+
"sitemap": "^7.1.1",
|
|
35
|
+
"ufo": "^1.0.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@antfu/eslint-config": "^0.34.0",
|
|
39
|
+
"@nuxt/kit": "3.0.0",
|
|
40
|
+
"@nuxt/module-builder": "^0.2.1",
|
|
41
|
+
"@nuxt/test-utils": "3.0.0",
|
|
42
|
+
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
|
43
|
+
"bumpp": "^8.2.1",
|
|
44
|
+
"eslint": "8.30.0",
|
|
45
|
+
"execa": "^6.1.0",
|
|
46
|
+
"nuxt": "^3.0.0",
|
|
47
|
+
"pathe": "^1.0.0",
|
|
48
|
+
"vitest": "^0.25.8"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"lint": "eslint \"**/*.{ts,vue,json,yml}\" --fix",
|
|
52
|
+
"build": "nuxi prepare .playground && nuxt-module-build",
|
|
53
|
+
"dev": "nuxi dev .playground",
|
|
54
|
+
"dev:build": "nuxi build .playground",
|
|
55
|
+
"dev:prepare": "nuxt-module-build --stub && nuxi prepare .playground",
|
|
56
|
+
"release": "bumpp package.json --commit --push --tag",
|
|
57
|
+
"test": "pnpm lint"
|
|
58
|
+
}
|
|
59
|
+
}
|