Enhanced ES‑Check Implementation with Multi‑File Detection, HTML Support, and SourceMap Parsing
This article examines existing ES‑check tools, compares community and mpx implementations, and presents a comprehensive enhanced version that supports multi‑file detection, HTML parsing, source‑map resolution, and both CLI and library usage, detailing its underlying principles, code examples, and practical advantages for frontend development.
Preface
In 2022, front‑end products targeting the C‑end (Web, mini‑programs, other cross‑platform solutions) still have to deal with JavaScript compatibility issues, even though IE has officially been discontinued. Developers still need to consider low‑end browsers, especially IE11 .
The usual approach is to downgrade JavaScript syntax to ES5 with tools and add the appropriate polyfills. Babel remains the primary tool, while the newer SWC is gaining attention.
After configuring Babel for a project, a checking tool is typically used to ensure the output does not contain syntax beyond the target ECMAScript version.
This article explains the implementation principles and feature comparison of existing tools, and finally provides an enhanced es‑check with both CLI and library usage.
es‑check
First, see its effect with a test file:
// test.js
var str = 'hello'
var str2 = 'world'
const varConst = 'const'
let varLet = 'let'
const arrFun = () => {
console.log('hello world');
}Running npx es‑check es5 testProject/**/*.js shows a simple error report that only outputs the first ES syntax problem (e.g., const ) together with line number and file path.
When the test file is minified/obfuscated (simulating a build artifact), the tool reports a parsing error with line/column numbers but does not indicate the exact problematic code.
If a source‑map is available, the error location can be resolved using the source‑map library, as demonstrated in the following snippet:
// npx esno source-map.ts
import sourceMap from 'source-map'
import fs from 'fs'
import path from 'path'
const file = path.join(__dirname, 'testProject/dist/index.js.map')
const lineNumber = 1
const columnNumber = 45
(async () => {
const consumer = await new sourceMap.SourceMapConsumer(
fs.readFileSync(file, 'utf-8')
)
const sm = consumer.originalPositionFor({ column: columnNumber, line: lineNumber })
const content = consumer.sourceContentFor(sm.source!)
const errCode = content?.split(/\r?\n/g)[sm.line! - 1]
console.log(errCode)
})()The above prints the original source line that caused the error.
Implementation Analysis
The source of es‑check is very small—about 100 lines—and can be summarized in three steps:
Use fast‑glob to collect target files.
Parse each file with acorn to generate an AST and capture parsing errors.
If an error exists, print the file, error message, and location.
acorn is a popular JavaScript parser that accepts an ecmaVersion option to specify the ECMAScript version. es‑check leverages this feature.
import * as acorn from 'acorn'
try {
acorn.parse(`const a = 'hello'`, { ecmaVersion: 5, silent: true })
} catch (err) {
console.log(err)
}The simplified implementation (full source on GitHub) looks like this:
import fg from 'fast-glob'
import path from 'path'
import * as acorn from 'acorn'
import fs from 'fs'
const testPattern = path.join(__dirname, 'testProject/**/*.js')
const files = fg.sync(testPattern)
const acornOpts = { ecmaVersion: 5, silent: true }
const errArr = []
files.forEach(file => {
const code = fs.readFileSync(file, 'utf8')
try {
acorn.parse(code, acornOpts as any)
} catch (err: any) {
errArr.push({ err, stack: err.stack, file })
}
})
if (errArr.length > 0) {
console.error(`ES‑Check: there were ${errArr.length} ES version matching errors.`)
errArr.forEach(o => {
console.info(`\nES‑Check Error:\n----\n· erroring file: ${o.file}\n· error: ${o.err}\n· see the printed err.stack below for context\n----\n${o.stack}\n`)
})
process.exit(1)
}
console.info('ES‑Check: there were no ES version matching errors! 🎉')Summary
Only detects whether source code contains syntax beyond the target ECMAScript version.
Reports only the first syntax issue per file.
Error information includes file path, line/column, and parser error.
HTML files are not supported.
mpx‑es‑check
Tool from Didi's mpx (enhanced cross‑platform mini‑program framework) – @mpxjs/es‑check
Running the same test file with mpx‑es‑check --ecma=6 testProject/**/*.js writes detailed error logs (including source‑map resolution) to a log file.
Implementation Analysis
The source follows these steps:
Use glob to collect target files.
Read the source code and its source‑map.
Parse with @babel/parser to generate an AST.
Traverse nodes with @babel/traverse .
Enumerate rules for non‑ES5 nodes and detect matches.
Format and output the results.
Node‑rule examples show how let , const , and arrow functions are flagged.
// Partial node rules
const partRule = {
VariableDeclaration(node) {
if (node.kind === 'let' || node.kind === 'const') {
errArr.push({ node, message: `Using ${node.kind} is not allowed` })
}
},
ArrowFunctionExpression(node) {
errArr.push({ node, message: 'Using ArrowFunction (arrow function) is not allowed' })
}
}The traversal logic collects all nodes, applies the rules, and records location information.
const nodeQueue = []
const code = fs.readFileSync(file, 'utf8')
const ast = babelParser.parse(code, acornOpts)
babelTraverse(ast, { enter(path) { nodeQueue.push({ node: path.node, path }) } })
nodeQueue.forEach(({ node }) => {
partRule[node.type]?.(node)
})
errArr.forEach(o => {
problems.push({ file, message: o.err, startLine: o.node.loc.start.line, startColumn: o.node.loc.start.column })
})Summary
Output is friendly and includes source‑map parsing.
HTML is not supported.
Maintaining a rule set is required, but acceptable given ECMAScript evolution.
Enhanced Implementation of es‑check
Comparing the two tools, es‑check is simpler and has lower maintenance cost. The new @sugarat/es‑check will build on es‑check and add:
Multiple checks per file.
HTML support.
Source‑map resolution.
Multiple Checks per File
Using acorn.parse on the whole file stops at the first error. By splitting the file into many code snippets (each AST node) and parsing each snippet, all errors can be discovered.
acornWalk.full(ast, (node) => {
const codeSnippet = code.slice(node.start, node.end)
try {
acorn.parse(codeSnippet, { ecmaVersion })
} catch (error) {
console.log(codeSnippet)
console.log(error.message)
}
})To avoid duplicate reports from parent/child nodes, a deduplication step checks whether a new error is already covered by an existing one.
const codeErrorList = []
acornWalk.full(ast, (node) => {
const codeSnippet = code.slice(node.start, node.end)
try {
acorn.parse(codeSnippet, { ecmaVersion: '5' })
} catch (error) {
const isRepeat = codeErrorList.find(e => e.start >= node.start && e.end <= node.end)
if (!isRepeat) {
codeErrorList.push({ codeSnippet, message: error.message, start: node.start, end: node.end })
}
}
})
console.log(codeErrorList)SourceMap Parsing
When a source‑map is present, the error's start/end positions are converted back to the original source using source‑map :
const locStart = acorn.getLineInfo(code, node.start)
const locEnd = acorn.getLineInfo(code, node.end)
codeErrorList.push({ loc: { start: locStart, end: locEnd } })The map file is read and consumed:
function getSourcemapFileContent(file) {
const sourceMapFile = `${file}.map`
if (fs.existsSync(sourceMapFile)) {
return fs.readFileSync(sourceMapFile, 'utf-8')
}
return ''
}
async function parseSourceMap(code) {
const consumer = await new sourceMap.SourceMapConsumer(code)
return consumer
}After obtaining the original positions, the corresponding source lines are extracted from the map's sourceContent and reported.
HTML Support
To handle HTML files, the script tag content is extracted (e.g., with parse5 ) and fed into the same checkCode logic. The AST generated by parse5 provides node locations for accurate mapping.
import * as parse5 from 'parse5'
const htmlAST = parse5.parse(code, { sourceCodeLocationInfo: true })
function traverse(ast, schema) {
schema?.[ast?.nodeName]?.(ast)
if (ast?.nodeName !== ast?.tagName) {
schema?.[ast?.tagName]?.(ast)
}
ast?.childNodes?.forEach(n => traverse(n, schema))
}
traverse(htmlAST, {
script(node) {
const code = `${node.childNodes.map(n => n.value)}`
const loc = node.sourceCodeLocation
if (code) {
const errList = checkCode(code)
errList.forEach(err => {
console.log('line:', loc.startLine + err.loc.start.line - 1, 'column:', err.loc.start.column)
console.log(err.source)
})
}
}
})CLI Assembly
The final CLI mirrors the original es‑check interface, accepting target files, an ECMAScript version, and optional --out to write logs to a file.
npm i @sugarat/es-check -g
escheck es5 testProject/**/*.js testProject/**/*.html
escheck es5 testProject/**/*.js testProject/**/*.html --outFinal Comparison
Name
JS
HTML
Friendly
es‑check
✅
❌
❌
@mpxjs/es‑check
✅
❌
✅
@sugarat/es‑check
✅
✅
✅
The new tool combines the advantages of the previous two, adding multi‑file detection, HTML support, and source‑map resolution while keeping the CLI usage simple.
Conclusion
The implementation may still have bugs or miss edge cases; readers are encouraged to provide feedback via issues on GitHub. Additional feature suggestions are welcome.
Full source code is available on GitHub.
References
es‑check – community project
mpx‑es‑check – Didi's MPX framework tool
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.