preface
When it comes to lexical analysis, I think many students may think of Babel, Acorn and other tools immediately. There is no denying that they are all powerful 😶.
However, es-module-lexer trumps them when it comes to analyzing ES Module statements on today’s topic.
So, today we will focus on the following 2 points, a simple es-module-lexer:
- Es – the module – the lexer
- How to apply ES-module-Lexer in actual scenarios
1 know es – the module – the lexer
Es – Module-Lexer is a toolkit for lexical analysis of ES Module statements. It is compressed to just 4 KiB and uses Inline WebAssembly for fast lexical analysis of ES Module statements.
1 kib = 1024 Byte
So, exactly how soon? According to the official example, Angular1 (720 KiB) uses Acorn parsing in 100 ms, while es-Module-Lexer parsing takes 5 ms, or 1/20 😵.
The use of es-module-lexer is also very simple. It provides an init Promise object and a parse method.
1.1 Init (Promise object)
Init must Resolve (Resolve) before the parse() method, which can be implemented in 3 steps:
- First, callWebAssembly.compile()Method to compile the WebAssembly binary to
WebAssembly.Module
的Promise
object - Then, call
WebAssembly.Instantiate()
Method to create an instance - Finally, it can be accessed on the instance
exports
Property to get the methods provided by the calling module
The code for this procedure is:
let wasm;
const init = WebAssembly.compile(
(binary= > typeof window! = ='undefined' && typeof atob === 'function' ? Uint8Array.from(atob(binary), x= > x.charCodeAt(0)) : Buffer.from(binary, 'base64'(a))'WASM_BINARY')
).then(WebAssembly.instantiate)
.then(({ exports }) = > { wasm = exports; });
Copy the code
The binary code here is compiled from code that implements lexical analysis of ES Module statements implemented by C. Also, you can see that the instance exports is assigned to wASM.
1.2 the parse () method
The parse() method implements lexical analysis of ES Module syntax using the methods provided by the Webassembly.module shown above (that is, WASM).
The code for this procedure (pseudocode) :
function parse (source, name = The '@') {
if(! wasm)return init.then(() = > parse(source));
// Call a method on wASM to perform the corresponding operation
return [imports, exports,!!!!! wasm.f()]; }Copy the code
Note that the methods provided on WASM are not analyzed here, so you can find out for yourself
As you can see, if we do not have Resolve (parse) init before calling the parse() method, the parse() method will Resolve (parse) init itself first. The parse() method is then called and returned in.then, so in this case the parse() method returns a Promise object.
Of course, in any case, the nature of the parse() method is to return an array of length 3. And the main ones we use are imports and exports.
Imports and exports are arrays where each element (object) represents the parsed result of an import statement, including the name of the imported or exported module, its location in the source code, and so on.
Next, let’s look at the basic use of ES-Module-Lexer through a simple example.
1.3 Basic Usage
First, we define a parseImportSyntax() method based on es-module-lexer:
const { init, parse } = require("es-module-lexer")
async function parseImportSyntax(code = "") {
try {
await init
const importSpecifier = parse(code)
return importSpecifier
} catch(e) {
console.error(e)
}
}
Copy the code
You can see that the parseImportSyntax() method returns the parse result. Suppose we need to parse the statement importing ant-Design-Vue’s Button component:
const code = `import { Button } from 'ant-design-vue'`
parseImportSyntax(code).then(importSpecifier= > {
console.log(importSpecifier)
})
Copy the code
Corresponding output:
[[{n: 'ant-design-vue'.s: 24.e: 38.ss: 0.se: 39.d: -1}], [],true
]
Copy the code
Since we declared only the import statements, the final result of the parse is that the imports have elements with meanings for each attribute:
n
Represents the name of the modules
Represents the starting position of the module name in the import statemente
Represents where the module name ends in an import statementss
Represents the start of the import statement in the source codese
Represents where the import statement ends in the source coded
Indicates whether the import statement is a dynamic import. If so, it is the corresponding start position. Otherwise, the default value is -1
So, after a brief understanding of the implementation principle and use of ES-Module-Lexer, I think students may think about how to use it in the actual scene? (Read more at 😎)
2 How to apply ES-module-Lexer in actual scenarios
While students may not realize where the ES-Module-Lexer is used, it has already entered our normal development.
Let’s take the viet-plugin-style-import plug-in as an example to see how it uses es-module-lexer. (Don’t go away, it’s going to be fun 😋)
2.1 Brief analysis of the principle of viet-plugin-style-import
Before we talk about the use of es-module-lexer in vite-plugin-style-import, we need to know what vite-plugin-style-import does.
It solves the problem of manually importing the corresponding component style when we import components on demand. For example, when using ant-design-vue, introducing a Button on demand simply declares:
import { Button } from "ant-design-vue"
Copy the code
Then, the corresponding code fragment is processed by viet-plugin-style-import:
import { Button } from 'ant-design-vue';
import 'ant-design-vue/es/button/style/index.js';
Copy the code
The realization of this process can be divided into the following three steps:
-
Use es-module-lexer for lexical analysis of source code import statements
-
The import statement of the style file is constructed according to the configuration item of the viet-plugin-style-import in the configuration file viet.config.js
-
Optionally inject specific code into source code, depending on the environment (Dev or Prod can be distinguished)
2.2 Use the Dark Magic of ES-Module-Lexer
In the basic usage section of 1.3, we explained that es-module-lexer parsing an import statement only returns information about the imported module. This is not sufficient in the viet-plugin-style-import section.
This is because viet-plugin-style-import also needs to know which components of the module are imported at this time, so that it can concatenate and generate the import statement of the corresponding style file.
This is where the dark magic of es-module-lexer comes in. We can replace the import statement with export, and es-module-Lexer will parse the exported component information (surprise 😲)!
For example, in the same ant-design-vue Button example above, replacing import would look like this:
export { Button } from "ant-design-vue"
Copy the code
The result of the es-module-lexer parsing is as follows:
[[{n: 'ant-design-vue'.s: 24.e: 38.ss: 0.se: 39.d: -1}], ['Button'].true
]
Copy the code
As you can see, Button is placed in exports of the parsed result (the second element of the array), so we know which components use the imported module 😎.
This process, in viet-plugin-style-import, is accomplished by the transformImportVar() method:
function transformImportVar(importStr: string) {
if(! importStr) {return [];
}
const exportStr = importStr.replace('import'.'export').replace(/\s+as\s+\w+,? /g.', ');
let importVariables: readonly string[] = [];
try {
importVariables = parse(exportStr)[1];
debug('importVariables:', importVariables);
} catch (error) {
debug('transformImportVar:', error);
}
return importVariables;
}
Copy the code
conclusion
It is interesting to note that awesome-Vite has two plugins that support importing component style files on demand. Through reading, I think the students should know which to use 😎! Finally, summarize the advantages of ES-Module-Lexer in one sentence, that is: “Fast enough to fly.” If there are any improper expressions or mistakes in the article, students are welcome to Issue ~
Thumb up 👍
If you get something from this post, please give me a “like”. This will be my motivation to keep sharing. Thank you