In one of my articles: Catching global Javascript exceptions in pages, I introduced the use of AST(Abstract Syntax tree) technology, with the help of THE AST API provided by UglifyJs, preprocessing source files, automatically adding try catch wrap code to each function, so as to catch JS exceptions in production environment. By using try-catch-global.js, you can simply use the following source code:


var test = function(){
    console.log('test');
}

Copy the code

Converted to

var test = function() { try { console.log("test"); } catch (error) { (function(error) { //your logic to handle error })(error); }};Copy the code

However, because the code we release to production is often compressed and obfuscated, the file name, function name, and variable name are no longer readable, and the value of the captured exception stack information is limited. It is still difficult to locate the error source in the source file simply by using the exception information. This article attempts to solve this problem: Still based on AST(abstract syntax tree), the source code is wrapped in try catch, but it will collect more information of source file (including file name, function name, function start line number, etc.) in catch statement. Finally, with the help of Babel and Webpack plug-in system, an engineering solution is provided.

1. Customize babel-plugin to implement try catch package

In the global Javascript exception in the article capture page, we use the API provided by UglifyJS to operate the Javascript syntax tree. This API is relatively low-level and requires effort to understand, so it is not suitable for beginners. Babel provides an API for manipulating syntax trees at a higher level of abstraction: Babylon, as well as a series of tools babel-template, babel-helper-function-name, and so on.

When using Babel to process Javascript source files, there are three main steps: parse, transform, and generate. Babel first transforms the source file into an abstract syntax tree (AST), then transforms the abstract syntax tree, and finally generates the new source code from the abstract syntax tree, as shown below. In the Transform phase, Babel provides a very convenient plugin mechanism in which developers can implement their own AST transformations. The best tutorial on how to develop the Babel plug-in is the official documentation.

During the AST conversion phase, Babel performs depth-first traverse of the AST with babel-traverse. Its plug-in mechanism allows us to register hook functions for a particular type of syntax tree node (for example, functions, conditional statements, etc.) to complete the transformation of the syntax tree.

Visitor: {Function: {ClassMethod: {// catch block}...... }Copy the code

Through the plug-in mechanism, we can convert all function and class method nodes and insert try catch wrapping code; At the same time, the syntax tree parsed by Babel contains detailed meta-information about source files that can be passed transparently to custom error handlers. For functions, we can do the following:

Visitor: {/ / processing functions and class methods only nodes "Function | ClassMethod" {exit: Function exit(path, state) {if (shouldSkip(path, state)) {return; } // If the function body is empty, var body = path.node.body.body; if (body.length === 0) { return; } var functionName = 'anonymous function'; babelHelperFunctionName2(path); if (path.node.id) { functionName = path.node.id.name || 'anonymous function'; } / / collection class methods of the if (path. Node. Key) {functionName = path. The node. The key. The name | | 'anonymous function'. } var loc = path.node.loc; / / exception variable name var errorVariableName = path. The scope, generateUidIdentifier (' e '); Path.get ('body'). ReplaceWith (wrapFunction({body: body, FILENAME: t.StringLiteral(filename), FUNCTION_NAME: t.StringLiteral(functionName), LINE: t.NumericLiteral(loc.start.line), COLUMN: t.NumericLiteral(loc.start.column), REPORT_ERROR: t.identifier(reportError), ERROR_VARIABLE_NAME: errorVariableName })); }},Copy the code

Babel template provides a handy tool that hides the details of AST conversions. You can simply use a function template to convert any function you want.

const wrapFunction = template(`{
  try {
    BODY
  } catch(ERROR_VARIABLE_NAME) {
    REPORT_ERROR(ERROR_VARIABLE_NAME, FILENAME, FUNCTION_NAME, LINE, COLUMN)
    throw ERROR_VARIABLE_NAME
  }
}`)

Copy the code

By using the Babel plug-in, you register hook functions for specific AST nodes (we’re only interested in function-type nodes in this article) while Babel processes the source code, wrap the function in a try catch with a lable-template, and pre-bury the source file information gathered from the AST in a catch statement.

Code before processing:

 function testA(){
    console.log(1);
}


class A {
    testB(){
        console.log(1);
    }
}

var testD = function(){
    console.log(1)
}

Copy the code

Processed code:

function testA() { try { console.log(1); } catch (_e) { reportError(_e, "test.js", "testA", 4, 0); } } class A { testB() { try { console.log(1); } catch (_e2) { reportError(_e2, "test.js", "testB", 10, 4); } } } var testD = function testD() { try { console.log(1); } catch (_e4) { reportError(_e4, "test.js", "testD", 19, 12); }};Copy the code

The converted code is released to the production environment through subsequent confusion and compression. When the code in the production environment is abnormal, reportError in the CATCH statement will report the exception to the log platform, and the reported information includes the variable name, function name, function function starting line number, file name and other information embedded in the AST. Using this information we can quickly locate exceptions in the source code.

2. Use WebPack Loader for engineering construction

The Babel plugin is used to transform the AST generated from the Javascript source code and eventually generate try catch wrap code for all functions. In this section we consider integrating the build process into the Webpack. Webpack first processes the source code using a Loader, then packages the import file and its dependencies into a chunk. In the compilation process of WebPack, we can use a custom loader to achieve AST conversion of source code.

It is very simple to write a custom Webpack loader. Please refer to the official documentation for a simple tutorial. Here is a very simple loader, which takes the source code content as input, and converts the converted source code as output.

module.exports = function(source) {
  //your logic to change source
  return source;
};
Copy the code

We also implement a webpack loader: babel_try_catch_loader, which uses Babel plug-in babel-plugin-try-catch-wrapper to process the source code through AST technology.

var tryCatchWrapper = require('babel-plugin-try-catch') ... module.exports = function (source, inputMap) { ...... var transOpts = { plugins: [ [tryCatchWrapper, { filename: filename, reportError: userOptions.reporter, rethrow: userOptions.rethrow }] ], sourceMaps: true }; var result = babel.transform(source, transOpts); this.callback(null, result.code, result.map); . };Copy the code

By using babel_try_catch_loader in the Webpack configuration file, we can wrap all the functions in the source code in the project with a try catch wrapped in a single configuration file.

loaders: [ ...... { test: /\.jsx|\.js$/, loader: 'babel-try-catch-loader?rethrow=true&verbose&reporter=reportError&tempdir=.tryCatchResult!babel-loader', exclude: /node_modules/ } ...... ] , devtool: 'source-map'Copy the code

Use babel-try-catch-loader to convert all the source code in the project, and then use babel-try-catch-loader to process the code and source map generated by babel-loader. This ensures that production code that is later obfuscated, compressed, and published to an online environment still has strong debugging capability. For detailed configuration files, see webpack-try-catch-demo. For the implementation principle, see babel-try-catch-loader.