“This is the 16th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

PS: RECENTLY, I was looking at the Babel plugin of The god of Light and wanted to record some of my learning process.

@babel/core

The @babel/core package completes the entire compilation process from source code to object code, generating sourcemap.

transformFromAstSync(
  ast,
  sourceCode,
  options
); // => { code, map, ast }
Copy the code

@babel/parser

Parse the source code, specifying the parse syntax through plugins, sourceTypes, and so on.

SourceType: The syntax can be “module”, “script”, or “unambiguous”. “module” parses the syntax of “ES Module”. “script” does not parse the syntax of “ES Module”. Unambiguous parsing es Module syntax depends on whether the content has import and export.


Sample code sourcecode.js

import example1 from 'example1';
import * as example2 from 'example2';
import { example3 } from 'example3';
import 'example4';

function example5() {
  console.log('example5');
}

class Example6 {
  example2() {
    return 'example6'; }}const example7 = () = > 'example7';

const example8 = function () {
  console.log('example8');
}
Copy the code

Entry file, index.js.

The file content is read through FS, the code is parsed through @babel/ Parser, and transformed into AST. The transformFromAstSync function configates plugin.

const { transformFromAstSync } = require('@babel/core');
const parser = require('@babel/parser');
const autoTrackPlugin = require('./plugin/auto-track-plugin');
const fs = require('fs');
const path = require('path');

const sourceCode = fs.readFileSync(path.join(__dirname, './sourceCode.js'), {
  encoding: 'utf-8'
});

const ast = parser.parse(sourceCode, {
  sourceType: 'unambiguous'
});

const { code } = transformFromAstSync(ast, sourceCode, {
  plugins: [[autoTr ackPlugin, {
    trackerPath: 'tracker'}}]]);console.logs(code);
Copy the code

Build the basic framework auto-track.js

The visitor pattern

When the structure of the object being operated on is stable and the logic of the object being operated on frequently changes, the logic and the structure of the object are separated so that they can be independently extended. That’s the idea of the visitor pattern. With the implementation of Babel traverse, the AST is separated from the visitor, and the registered visitor is called to handle the AST while traversing it.

const { declare } = require('@babel/helper-plugin-utils');

const autoTrackPlugin = declare((api, options) = > {
  api.assertVersion(7);

  return {
    visitor: {
      //....}}});module.exports = autoTrackPlugin;
Copy the code

Module is introduced into

At the Program root, check whether trackerImportId exists on the state when entering. If not, import the tracker module, generateUid to generate a unique ID, and place it in the state.

visitor: {
  Program: {
    enter(path, state) {
      if(! state.trackerImportId) { state.trackerImportId = importModule.addDefault(path,'tracker', {
          nameHint: path.scope.generateUid('tracker')
        }).name
      }
    }
  },
}
Copy the code

After the buried code is imported, the next step is to insert the code into the function body.

Find the corresponding function. The nodes to deal with here are ClassMethod, ArrowFunctionExpression, FunctionExpression, FunctionDeclaration.

visitor: {
  Program: {
    enter(path, state) {
      if(! state.trackerImportId) { state.trackerImportId = importModule.addDefault(path,'tracker', {
          nameHint: path.scope.generateUid('tracker') }).name; }}},'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) {
    const bodyPath = path.get('body');
    if (bodyPath.isBlockStatement()) {
      console.log('Insert buried code'); }}}Copy the code

We found that exactly three times, corresponding to the three source code with the function body. If the function body does not exist, wrap it and change the return value.

'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) {
  const bodyPath = path.get('body');
  if (bodyPath.isBlockStatement()) {
    console.log('Insert buried code');
  } else {
    const ast = api.template.statement(`{return PREV_BODY; } `) ({PREV_BODY: bodyPath.node });
    bodyPath.replaceWith(ast);
    console.log('Insert buried code'); }}Copy the code

After four executions, the code without the function body was successfully replaced.

visitor: {
  Program: {
    enter(path, state) {
      if(! state.trackerImportId) { state.trackerImportId = importModule.addDefault(path,'tracker', {
          nameHint: path.scope.generateUid('tracker')
        }).name;
        state.trackerAST = api.template.statement(`${state.trackerImportId}(a) `) (); }}},'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) {
    const bodyPath = path.get('body');
    if (bodyPath.isBlockStatement()) {
      bodyPath.node.body.unshift(state.trackerAST)
    } else {
      const ast = api.template.statement(` {${state.trackerImportId}(a); return PREV_BODY; } `) ({PREV_BODY: bodyPath.node }); bodyPath.replaceWith(ast); }}}Copy the code

Complete code:

const { declare } = require('@babel/helper-plugin-utils');
const importModule = require('@babel/helper-module-imports');

const autoTrackPlugin = declare((api, options, dirname) = > {
  api.assertVersion(7);

  return {
    visitor: {
      Program: {
        enter(path, state) {
          if(! state.trackerImportId) { state.trackerImportId = importModule.addDefault(path,'tracker', {
              nameHint: path.scope.generateUid('tracker')
            }).name;
            state.trackerAST = api.template.statement(`${state.trackerImportId}(a) `) (); }}},'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) {
        const bodyPath = path.get('body');
        if (bodyPath.isBlockStatement()) {
          bodyPath.node.body.unshift(state.trackerAST);
        } else {
          const ast = api.template.statement(` {${state.trackerImportId}(a); return PREV_BODY; } `) ({PREV_BODY: bodyPath.node }); bodyPath.replaceWith(ast); }}}}});module.exports = autoTrackPlugin;
Copy the code

Output the code after the buried point to another folder.

index.js

const { transformFromAstSync } = require('@babel/core');
const parser = require('@babel/parser');
const autoTrackPlugin = require('./plugin/auto-track-plugin');
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');

const sourceCode = fs.readFileSync(path.join(__dirname, './sourceCode.js'), {
  encoding: 'utf-8'
});

const ast = parser.parse(sourceCode, {
  sourceType: 'unambiguous'
});

const outputDir = path.resolve(__dirname, './lib');

const { code } = transformFromAstSync(ast, sourceCode, {
  plugins: [[autoTrackPlugin, {
    trackerPath: 'tracker'}}]]); fse.ensureDirSync(outputDir); fse.writeFileSync(path.join(outputDir,'lib.js'), code);
Copy the code

Lib /index.js generates a buried file.