Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
extends: ['@nuxtjs/eslint-config-typescript'],
overrides: [
{
files: ['*.test.ts', 'validator.ts'],
rules: {
'no-console': 'off'
}
}
]
}
7 changes: 0 additions & 7 deletions .eslintrc.js

This file was deleted.

3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: yarn

- name: Prepare environment
run: yarn dev:prepare

- name: Lint
run: yarn lint:all

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules
.idea
*.log*
.nuxt
.output
.vscode
.DS_Store
coverage
Expand Down
5 changes: 0 additions & 5 deletions example/nuxt.config.js

This file was deleted.

3 changes: 0 additions & 3 deletions example/tsconfig.json

This file was deleted.

File renamed without changes.
59 changes: 31 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@nuxtjs/html-validator",
"version": "0.7.1",
"description": "html-validator integration for Nuxt.js",
"version": "0.7.0",
"description": "html-validate integration for Nuxt",
"keywords": [
"nuxt",
"module",
Expand All @@ -12,48 +12,51 @@
],
"repository": "nuxt-modules/html-validator",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
}
},
"main": "./dist/module.cjs",
"types": "./dist/module.d.ts",
"files": [
"dist"
"dist",
"validator.d.ts"
],
"scripts": {
"build": "siroc build",
"dev": "nuxt example",
"prepack": "nuxt-module-build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"lint": "eslint --ext .js,.ts,.vue",
"lint:all": "yarn lint .",
"prepare": "husky install && yarn build",
"prepublishOnly": "yarn test",
"release": "yarn build && yarn test && release-it",
"test": "yarn lint && yarn build && jest"
"release": "yarn test && release-it",
"test": "yarn vitest run"
},
"dependencies": {
"defu": "5.0.1",
"html-validate": "7.4.1"
"@nuxt/kit": "^3.0.0-rc.10",
"chalk": "^5.0.1",
"html-validate": "7.4.1",
"prettier": "^2.7.1"
},
"devDependencies": {
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/preset-env": "7.19.1",
"@babel/preset-typescript": "7.18.6",
"@nuxt/test-utils": "0.2.2",
"@nuxt/types": "2.15.8",
"@nuxt/module-builder": "latest",
"@nuxt/test-utils": "3.0.0-rc.10",
"@nuxtjs/eslint-config-typescript": "11.0.0",
"@release-it/conventional-changelog": "5.1.0",
"@types/jest": "29.0.3",
"babel-eslint": "latest",
"babel-jest": "29.0.3",
"c8": "^7.12.0",
"eslint": "8.24.0",
"eslint-config-prettier": "8.5.0",
"husky": "8.0.1",
"jest": "29.0.3",
"lint-staged": "13.0.3",
"nuxt-edge": "2.16.0-27226092.034b9901",
"nuxt": "npm:nuxt3@3.0.0-rc.11-27722816.abd0feb",
"release-it": "15.4.2",
"siroc": "0.16.0"
"vitest": "^0.21.0"
},
"peerDependencies": {
"chalk": "^3.0.0 || ^4.0.0 || ^5.0.0",
"consola": "^2.15.0",
"prettier": "^2.1.2"
"resolutions": {
"@nuxtjs/html-validator": "link:./"
},
"volta": {
"node": "16.17.1"
Expand Down
5 changes: 5 additions & 0 deletions playground/nuxt.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
modules: ['@nuxtjs/html-validator']
})
3 changes: 3 additions & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "html-validator-playground"
}
File renamed without changes.
3 changes: 3 additions & 0 deletions playground/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}
43 changes: 0 additions & 43 deletions src/index.ts

This file was deleted.

76 changes: 76 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { fileURLToPath } from 'url'
import chalk from 'chalk'

import { defineNuxtModule, isNuxt2, logger, resolveModule } from '@nuxt/kit'
import { DEFAULTS, ModuleOptions } from './config'

export type { ModuleOptions }

export default defineNuxtModule<ModuleOptions>({
meta: {
name: '@nuxtjs/html-validator',
configKey: 'htmlValidator',
compatibility: {
nuxt: '^2.0.0 || ^3.0.0-rc.7',
bridge: true
}
},
defaults: DEFAULTS,
async setup (_options, nuxt) {
logger.info(`Using ${chalk.bold('html-validate')} to validate server-rendered HTML`)

const { usePrettier, failOnError, options } = _options as Required<ModuleOptions>
if ((nuxt.options as any).htmlValidator?.options?.extends) {
options.extends = (nuxt.options as any).htmlValidator.options.extends
}

if (nuxt.options.dev) {
nuxt.hook('nitro:config', (config) => {
// Transpile the nitro plugin we're injecting
config.externals = config.externals || {}
config.externals.inline = config.externals.inline || []
config.externals.inline.push('@nuxtjs/html-validator')

// Add a nitro plugin that will run the validator for us on each request
config.plugins = config.plugins || []
config.plugins.push(fileURLToPath(new URL('./runtime/nitro', import.meta.url)))
config.virtual = config.virtual || {}
config.virtual['#html-validator-config'] = `export default ${JSON.stringify(_options)}`
})
}

if (!nuxt.options.dev || isNuxt2()) {
const validatorPath = fileURLToPath(new URL('./runtime/validator', import.meta.url))
const { useChecker, getValidator } = await import(resolveModule(validatorPath))
const validator = getValidator(options)
const { checkHTML, invalidPages } = useChecker(validator, usePrettier)

if (failOnError) {
const errorIfNeeded = () => {
if (invalidPages.length) {
throw new Error('html-validator found errors')
}
}

nuxt.hook('generate:done', errorIfNeeded)
nuxt.hook('close', errorIfNeeded)
}

// Nuxt 3/Nuxt Bridge prerendering

nuxt.hook('nitro:init', (nitro) => {
nitro.hooks.hook('prerender:generate', (route) => {
if (!route.contents || !route.fileName?.endsWith('.html')) { return }
checkHTML(route.route, route.contents)
})
})

// Nuxt 2

if (isNuxt2()) {
nuxt.hook('render:route', (url: string, result: { html: string }) => checkHTML(url, result.html))
nuxt.hook('generate:page', ({ path, html }: { path: string, html: string }) => checkHTML(path, html))
}
}
}
})
16 changes: 16 additions & 0 deletions src/runtime/nitro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NitroAppPlugin, RenderResponse } from 'nitropack'
import { useChecker, getValidator } from './validator'
// @ts-expect-error virtual module
import config from '#html-validator-config'

export default <NitroAppPlugin> function (nitro) {
const validator = getValidator(config.options)
const { checkHTML } = useChecker(validator, config.usePrettier)

nitro.hooks.hook('render:response', (response: RenderResponse, { event }) => {
if (typeof response.body === 'string' && (response.headers['Content-Type'] || response.headers['content-type'])?.includes('html')) {
// We deliberately do not await so as not to block the response
checkHTML(event.req.url, response.body)
}
})
}
46 changes: 15 additions & 31 deletions src/validator.ts → src/runtime/validator.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import chalk from 'chalk'
import consola from 'consola'
import { ConfigData, HtmlValidate, formatterFactory } from 'html-validate'

const validators = new Map<ConfigData, HtmlValidate>()

const defaultOptions = {}

export const useValidator = (options: ConfigData = defaultOptions) => {
if (validators.has(options)) {
return { validator: validators.get(options)! }
}

const validator = new HtmlValidate(options)

validators.set(options, validator)

return { validator }
export const getValidator = (options: ConfigData = {}) => {
return new HtmlValidate(options)
}

export const useChecker = (
validator: HtmlValidate,
usePrettier = false,
reporter = consola.withTag('html-validate')
usePrettier = false
) => {
const invalidPages: string[] = []

Expand All @@ -33,34 +19,32 @@ export const useChecker = (
html = format(html, { parser: 'html' })
couldFormat = true
}
// eslint-disable-next-line
} catch (e) {
reporter.error(e)
} catch (e) {
console.error(e)
}

// Clean up Vue scoped style attributes
html = typeof html === 'string' ? html.replace(/ ?data-v-[a-z0-9]+\b/g, '') : html

const { valid, results } = validator.validateString(html)

if (valid) {
return reporter.success(
`No HTML validation errors found for ${chalk.bold(url)}`
if (valid && !results.length) {
return console.log(
`No HTML validation errors found for ${chalk.bold(url)}`
)
}

invalidPages.push(url)
if (!valid) { invalidPages.push(url) }

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

const formattedResult = formatter!(results)
const formattedResult = formatter?.(results)
const reporter = valid ? console.warn : console.error

reporter.error(
[
reporter([
`HTML validation errors found for ${chalk.bold(url)}`,
formattedResult
].join('\n')
)
].join('\n'))
}

return { checkHTML, invalidPages }
Expand Down
4 changes: 2 additions & 2 deletions test/__snapshots__/validator.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Vitest Snapshot v1

exports[`useValidator returns a valid htmlValidate instance 1`] = `
exports[`useValidator > returns a valid htmlValidate instance 1`] = `
[
{
"errorCount": 1,
Expand Down
Loading