Skip to content

Commit

Permalink
feat: add compatibility with nitro (nuxt 3 + nuxt bridge) (#206)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas Thelemann <[email protected]>
  • Loading branch information
danielroe and dargmuesli authored Sep 25, 2022
1 parent 9d672f0 commit b134043
Show file tree
Hide file tree
Showing 27 changed files with 2,616 additions and 7,480 deletions.
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:[email protected].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

0 comments on commit b134043

Please sign in to comment.