Skip to content

Commit b134043

Browse files
feat: add compatibility with nitro (nuxt 3 + nuxt bridge) (nuxt-modules#206)
Co-authored-by: Jonas Thelemann <[email protected]>
1 parent 9d672f0 commit b134043

27 files changed

+2616
-7480
lines changed

.eslintrc.cjs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
extends: ['@nuxtjs/eslint-config-typescript'],
3+
overrides: [
4+
{
5+
files: ['*.test.ts', 'validator.ts'],
6+
rules: {
7+
'no-console': 'off'
8+
}
9+
}
10+
]
11+
}

.eslintrc.js

-7
This file was deleted.

.github/workflows/ci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ jobs:
3535
if: steps.cache.outputs.cache-hit != 'true'
3636
run: yarn
3737

38+
- name: Prepare environment
39+
run: yarn dev:prepare
40+
3841
- name: Lint
3942
run: yarn lint:all
4043

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ node_modules
33
.idea
44
*.log*
55
.nuxt
6+
.output
67
.vscode
78
.DS_Store
89
coverage

example/nuxt.config.js

-5
This file was deleted.

example/tsconfig.json

-3
This file was deleted.
File renamed without changes.

package.json

+31-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@nuxtjs/html-validator",
3-
"version": "0.7.1",
4-
"description": "html-validator integration for Nuxt.js",
3+
"version": "0.7.0",
4+
"description": "html-validate integration for Nuxt",
55
"keywords": [
66
"nuxt",
77
"module",
@@ -12,48 +12,51 @@
1212
],
1313
"repository": "nuxt-modules/html-validator",
1414
"license": "MIT",
15-
"main": "dist/index.js",
16-
"types": "dist/index.d.ts",
15+
"type": "module",
16+
"exports": {
17+
".": {
18+
"import": "./dist/module.mjs",
19+
"require": "./dist/module.cjs"
20+
}
21+
},
22+
"main": "./dist/module.cjs",
23+
"types": "./dist/module.d.ts",
1724
"files": [
18-
"dist"
25+
"dist",
26+
"validator.d.ts"
1927
],
2028
"scripts": {
21-
"build": "siroc build",
22-
"dev": "nuxt example",
29+
"prepack": "nuxt-module-build",
30+
"dev": "nuxi dev playground",
31+
"dev:build": "nuxi build playground",
32+
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
2333
"lint": "eslint --ext .js,.ts,.vue",
2434
"lint:all": "yarn lint .",
25-
"prepare": "husky install && yarn build",
26-
"prepublishOnly": "yarn test",
27-
"release": "yarn build && yarn test && release-it",
28-
"test": "yarn lint && yarn build && jest"
35+
"release": "yarn test && release-it",
36+
"test": "yarn vitest run"
2937
},
3038
"dependencies": {
31-
"defu": "5.0.1",
32-
"html-validate": "7.4.1"
39+
"@nuxt/kit": "^3.0.0-rc.10",
40+
"chalk": "^5.0.1",
41+
"html-validate": "7.4.1",
42+
"prettier": "^2.7.1"
3343
},
3444
"devDependencies": {
35-
"@babel/plugin-transform-runtime": "7.18.10",
36-
"@babel/preset-env": "7.19.1",
37-
"@babel/preset-typescript": "7.18.6",
38-
"@nuxt/test-utils": "0.2.2",
39-
"@nuxt/types": "2.15.8",
45+
"@nuxt/module-builder": "latest",
46+
"@nuxt/test-utils": "3.0.0-rc.10",
4047
"@nuxtjs/eslint-config-typescript": "11.0.0",
4148
"@release-it/conventional-changelog": "5.1.0",
42-
"@types/jest": "29.0.3",
43-
"babel-eslint": "latest",
44-
"babel-jest": "29.0.3",
49+
"c8": "^7.12.0",
4550
"eslint": "8.24.0",
51+
"eslint-config-prettier": "8.5.0",
4652
"husky": "8.0.1",
47-
"jest": "29.0.3",
4853
"lint-staged": "13.0.3",
49-
"nuxt-edge": "2.16.0-27226092.034b9901",
54+
"nuxt": "npm:[email protected].0-rc.11-27722816.abd0feb",
5055
"release-it": "15.4.2",
51-
"siroc": "0.16.0"
56+
"vitest": "^0.21.0"
5257
},
53-
"peerDependencies": {
54-
"chalk": "^3.0.0 || ^4.0.0 || ^5.0.0",
55-
"consola": "^2.15.0",
56-
"prettier": "^2.1.2"
58+
"resolutions": {
59+
"@nuxtjs/html-validator": "link:./"
5760
},
5861
"volta": {
5962
"node": "16.17.1"

playground/nuxt.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { defineNuxtConfig } from 'nuxt/config'
2+
3+
export default defineNuxtConfig({
4+
modules: ['@nuxtjs/html-validator']
5+
})

playground/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "html-validator-playground"
3+
}
File renamed without changes.

playground/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "./.nuxt/tsconfig.json"
3+
}

src/index.ts

-43
This file was deleted.

src/module.ts

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { fileURLToPath } from 'url'
2+
import chalk from 'chalk'
3+
4+
import { defineNuxtModule, isNuxt2, logger, resolveModule } from '@nuxt/kit'
5+
import { DEFAULTS, ModuleOptions } from './config'
6+
7+
export type { ModuleOptions }
8+
9+
export default defineNuxtModule<ModuleOptions>({
10+
meta: {
11+
name: '@nuxtjs/html-validator',
12+
configKey: 'htmlValidator',
13+
compatibility: {
14+
nuxt: '^2.0.0 || ^3.0.0-rc.7',
15+
bridge: true
16+
}
17+
},
18+
defaults: DEFAULTS,
19+
async setup (_options, nuxt) {
20+
logger.info(`Using ${chalk.bold('html-validate')} to validate server-rendered HTML`)
21+
22+
const { usePrettier, failOnError, options } = _options as Required<ModuleOptions>
23+
if ((nuxt.options as any).htmlValidator?.options?.extends) {
24+
options.extends = (nuxt.options as any).htmlValidator.options.extends
25+
}
26+
27+
if (nuxt.options.dev) {
28+
nuxt.hook('nitro:config', (config) => {
29+
// Transpile the nitro plugin we're injecting
30+
config.externals = config.externals || {}
31+
config.externals.inline = config.externals.inline || []
32+
config.externals.inline.push('@nuxtjs/html-validator')
33+
34+
// Add a nitro plugin that will run the validator for us on each request
35+
config.plugins = config.plugins || []
36+
config.plugins.push(fileURLToPath(new URL('./runtime/nitro', import.meta.url)))
37+
config.virtual = config.virtual || {}
38+
config.virtual['#html-validator-config'] = `export default ${JSON.stringify(_options)}`
39+
})
40+
}
41+
42+
if (!nuxt.options.dev || isNuxt2()) {
43+
const validatorPath = fileURLToPath(new URL('./runtime/validator', import.meta.url))
44+
const { useChecker, getValidator } = await import(resolveModule(validatorPath))
45+
const validator = getValidator(options)
46+
const { checkHTML, invalidPages } = useChecker(validator, usePrettier)
47+
48+
if (failOnError) {
49+
const errorIfNeeded = () => {
50+
if (invalidPages.length) {
51+
throw new Error('html-validator found errors')
52+
}
53+
}
54+
55+
nuxt.hook('generate:done', errorIfNeeded)
56+
nuxt.hook('close', errorIfNeeded)
57+
}
58+
59+
// Nuxt 3/Nuxt Bridge prerendering
60+
61+
nuxt.hook('nitro:init', (nitro) => {
62+
nitro.hooks.hook('prerender:generate', (route) => {
63+
if (!route.contents || !route.fileName?.endsWith('.html')) { return }
64+
checkHTML(route.route, route.contents)
65+
})
66+
})
67+
68+
// Nuxt 2
69+
70+
if (isNuxt2()) {
71+
nuxt.hook('render:route', (url: string, result: { html: string }) => checkHTML(url, result.html))
72+
nuxt.hook('generate:page', ({ path, html }: { path: string, html: string }) => checkHTML(path, html))
73+
}
74+
}
75+
}
76+
})

src/runtime/nitro.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { NitroAppPlugin, RenderResponse } from 'nitropack'
2+
import { useChecker, getValidator } from './validator'
3+
// @ts-expect-error virtual module
4+
import config from '#html-validator-config'
5+
6+
export default <NitroAppPlugin> function (nitro) {
7+
const validator = getValidator(config.options)
8+
const { checkHTML } = useChecker(validator, config.usePrettier)
9+
10+
nitro.hooks.hook('render:response', (response: RenderResponse, { event }) => {
11+
if (typeof response.body === 'string' && (response.headers['Content-Type'] || response.headers['content-type'])?.includes('html')) {
12+
// We deliberately do not await so as not to block the response
13+
checkHTML(event.req.url, response.body)
14+
}
15+
})
16+
}

src/validator.ts renamed to src/runtime/validator.ts

+15-31
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,13 @@
11
import chalk from 'chalk'
2-
import consola from 'consola'
32
import { ConfigData, HtmlValidate, formatterFactory } from 'html-validate'
43

5-
const validators = new Map<ConfigData, HtmlValidate>()
6-
7-
const defaultOptions = {}
8-
9-
export const useValidator = (options: ConfigData = defaultOptions) => {
10-
if (validators.has(options)) {
11-
return { validator: validators.get(options)! }
12-
}
13-
14-
const validator = new HtmlValidate(options)
15-
16-
validators.set(options, validator)
17-
18-
return { validator }
4+
export const getValidator = (options: ConfigData = {}) => {
5+
return new HtmlValidate(options)
196
}
207

218
export const useChecker = (
229
validator: HtmlValidate,
23-
usePrettier = false,
24-
reporter = consola.withTag('html-validate')
10+
usePrettier = false
2511
) => {
2612
const invalidPages: string[] = []
2713

@@ -33,34 +19,32 @@ export const useChecker = (
3319
html = format(html, { parser: 'html' })
3420
couldFormat = true
3521
}
36-
// eslint-disable-next-line
37-
} catch (e) {
38-
reporter.error(e)
22+
} catch (e) {
23+
console.error(e)
3924
}
4025

4126
// Clean up Vue scoped style attributes
4227
html = typeof html === 'string' ? html.replace(/ ?data-v-[a-z0-9]+\b/g, '') : html
43-
4428
const { valid, results } = validator.validateString(html)
4529

46-
if (valid) {
47-
return reporter.success(
48-
`No HTML validation errors found for ${chalk.bold(url)}`
30+
if (valid && !results.length) {
31+
return console.log(
32+
`No HTML validation errors found for ${chalk.bold(url)}`
4933
)
5034
}
5135

52-
invalidPages.push(url)
36+
if (!valid) { invalidPages.push(url) }
5337

54-
const formatter = couldFormat ? formatterFactory('codeframe') : formatterFactory('stylish')
38+
// TODO: investigate the many levels of default
39+
const formatter = couldFormat ? formatterFactory('codeframe') : await import('@html-validate/stylish').then(r => r.default?.default ?? r.default ?? r)
5540

56-
const formattedResult = formatter!(results)
41+
const formattedResult = formatter?.(results)
42+
const reporter = valid ? console.warn : console.error
5743

58-
reporter.error(
59-
[
44+
reporter([
6045
`HTML validation errors found for ${chalk.bold(url)}`,
6146
formattedResult
62-
].join('\n')
63-
)
47+
].join('\n'))
6448
}
6549

6650
return { checkHTML, invalidPages }

test/__snapshots__/validator.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Jest Snapshot v1, https://goo.gl/fbAQLP
1+
// Vitest Snapshot v1
22

3-
exports[`useValidator returns a valid htmlValidate instance 1`] = `
3+
exports[`useValidator > returns a valid htmlValidate instance 1`] = `
44
[
55
{
66
"errorCount": 1,

0 commit comments

Comments
 (0)