source: frontend/node_modules/es-module-lexer/README.md

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 10.8 KB
Line 
1# ES Module Lexer
2
3[![Build Status][actions-image]][actions-url]
4
5A JS module syntax lexer used in [es-module-shims](https://github.com/guybedford/es-module-shims).
6
7Outputs the list of exports and locations of import specifiers, including dynamic import and import meta handling.
8
9Supports new syntax features including import attributes and source phase imports.
10
11A very small single JS file (4KiB gzipped) that includes inlined Web Assembly for very fast source analysis of ECMAScript module syntax only.
12
13For an example of the performance, Angular 1 (720KiB) is fully parsed in 5ms, in comparison to the fastest JS parser, Acorn which takes over 100ms.
14
15_Comprehensively handles the JS language grammar while remaining small and fast. - ~10ms per MB of JS cold and ~5ms per MB of JS warm, [see benchmarks](#benchmarks) for more info._
16
17> [Built with](https://github.com/guybedford/es-module-lexer/blob/main/chompfile.toml) [Chomp](https://chompbuild.com/)
18
19### Usage
20
21```
22npm install es-module-lexer
23```
24
25See [src/lexer.ts](src/lexer.ts) for the type definitions.
26
27For use in CommonJS:
28
29```js
30const { init, parse } = require('es-module-lexer');
31
32(async () => {
33 // either await init, or call parse asynchronously
34 // this is necessary for the Web Assembly boot
35 await init;
36
37 const source = 'export var p = 5';
38 const [imports, exports] = parse(source);
39
40 // Returns "p"
41 source.slice(exports[0].s, exports[0].e);
42 // Returns "p"
43 source.slice(exports[0].ls, exports[0].le);
44})();
45```
46
47An ES module version is also available:
48
49```js
50import { init, parse } from 'es-module-lexer';
51
52(async () => {
53 await init;
54
55 const source = `
56 import { name } from 'mod\\u1011';
57 import json from './json.json' with { type: 'json' }
58 export var p = 5;
59 export function q () {
60
61 };
62 export { x as 'external name' } from 'external';
63
64 // Comments provided to demonstrate edge cases
65 import /*comment!*/ ( 'asdf', { with: { type: 'json' }});
66 import /*comment!*/.meta.asdf;
67
68 // Source phase imports:
69 import source mod from './mod.wasm';
70 import.source('./mod.wasm');
71 `;
72
73 const [imports, exports] = parse(source, 'optional-sourcename');
74
75 // Returns "modထ"
76 imports[0].n
77 // Returns "mod\u1011"
78 source.slice(imports[0].s, imports[0].e);
79 // "s" = start
80 // "e" = end
81
82 // Returns "import { name } from 'mod'"
83 source.slice(imports[0].ss, imports[0].se);
84 // "ss" = statement start
85 // "se" = statement end
86
87 // Returns "{ type: 'json' }"
88 source.slice(imports[1].a, imports[1].se);
89 // "a" = attribute start, -1 for no import attributes
90
91 // Parsed import attributes are available in `at`
92 // Returns [['type', 'json']]
93 imports[1].at;
94 // Returns 'json'
95 imports[1].at[0][1];
96
97 // Returns null (no attributes)
98 imports[0].at;
99
100 // Returns "external"
101 source.slice(imports[2].s, imports[2].e);
102
103 // Returns "p"
104 source.slice(exports[0].s, exports[0].e);
105 // Returns "p"
106 source.slice(exports[0].ls, exports[0].le);
107 // Returns "q"
108 source.slice(exports[1].s, exports[1].e);
109 // Returns "q"
110 source.slice(exports[1].ls, exports[1].le);
111 // Returns "'external name'"
112 source.slice(exports[2].s, exports[2].e);
113 // Returns -1
114 exports[2].ls;
115 // Returns -1
116 exports[2].le;
117
118 // Import type is provided by `t` value
119 // (1 for static, 2, for dynamic)
120 // Returns true
121 imports[2].t == 2;
122
123 // Returns "asdf" (only for string literal dynamic imports)
124 imports[2].n
125 // Returns "import /*comment!*/ ( 'asdf', { with: { type: 'json' } })"
126 source.slice(imports[3].ss, imports[3].se);
127 // Returns "'asdf'"
128 source.slice(imports[3].s, imports[3].e);
129 // Returns "( 'asdf', { with: { type: 'json' } })"
130 source.slice(imports[3].d, imports[3].se);
131 // Returns "{ with: { type: 'json' } }"
132 source.slice(imports[3].a, imports[3].se - 1);
133
134 // For non-string dynamic import expressions:
135 // - n will be undefined
136 // - a is currently -1 even if there is an import attribute
137 // - e is currently the character before the closing )
138
139 // For nested dynamic imports, the se value of the outer import is -1 as end tracking does not
140 // currently support nested dynamic immports
141
142 // import.meta is indicated by imports[3].d === -2
143 // Returns true
144 imports[4].d === -2;
145 // Returns "import /*comment!*/.meta"
146 source.slice(imports[4].s, imports[4].e);
147 // ss and se are the same for import meta
148
149 // Returns "'./mod.wasm'"
150 source.slice(imports[5].s, imports[5].e);
151
152 // Import type 4 and 5 for static and dynamic source phase
153 imports[5].t === 4;
154 imports[6].t === 5;
155})();
156```
157
158### CSP asm.js Build
159
160The default version of the library uses Wasm and (safe) eval usage for performance and a minimal footprint.
161
162Neither of these represent security escalation possibilities since there are no execution string injection vectors, but that can still violate existing CSP policies for applications.
163
164For a version that works with CSP eval disabled, use the `es-module-lexer/js` build:
165
166```js
167import { parse } from 'es-module-lexer/js';
168```
169
170Instead of Web Assembly, this uses an asm.js build which is almost as fast as the Wasm version ([see benchmarks below](#benchmarks)).
171
172### Import Attributes
173
174The `a` field provides the index of the start of the `{` attributes bracket, or -1 for no attributes.
175
176The list of attribute key and value pairs are provided on the `at` field:
177
178```js
179const [imports] = parse(`
180 import json from './foo.json' with { type: 'json' };
181 import './foo.css' with { type: 'css' };
182 import pkg from 'pkg' with { type: 'json', integrity: 'sha384-...' };
183`);
184
185// Returns [['type', 'json']]
186imports[0].at;
187
188// Returns [['type', 'css']]
189imports[1].at;
190
191// Multiple attributes
192// Returns [['type', 'json'], ['integrity', 'sha384-...']]
193imports[2].at;
194```
195
196The `at` field is an array of `[key, value]` tuples, or `null` if there are no attributes.
197
198Both keys and values support escape sequences:
199
200```js
201const [imports] = parse(`
202 import foo from './foo.js' with { "custom-key": "value" };
203 import bar from './bar.js' with { "key\\nwith\\nnewlines": "value\\twith\\ttabs" };
204`);
205
206// Quoted keys are unquoted
207// Returns [['custom-key', 'value']]
208imports[0].at;
209
210// Escape sequences are processed
211// Returns [['key\nwith\nnewlines', 'value\twith\ttabs']]
212imports[1].at;
213```
214
215### Escape Sequences
216
217To handle escape sequences in specifier strings, the `.n` field of imported specifiers will be provided where possible.
218
219For dynamic import expressions, this field will be empty if not a valid JS string.
220
221### Facade Detection
222
223Facade modules that only use import / export syntax can be detected via the third return value:
224
225```js
226const [,, facade] = parse(`
227 export * from 'external';
228 import * as ns from 'external2';
229 export { a as b } from 'external3';
230 export { ns };
231`);
232facade === true;
233```
234
235### ESM Detection
236
237Modules that uses ESM syntaxes can be detected via the fourth return value:
238
239```js
240const [,,, hasModuleSyntax] = parse(`
241 export {}
242`);
243hasModuleSyntax === true;
244```
245
246Dynamic imports are ignored since they can be used in Non-ESM files.
247
248```js
249const [,,, hasModuleSyntax] = parse(`
250 import('./foo.js')
251`);
252hasModuleSyntax === false;
253```
254
255### Environment Support
256
257Node.js 10+, and [all browsers with Web Assembly support](https://caniuse.com/#feat=wasm).
258
259### Grammar Support
260
261* Token state parses all line comments, block comments, strings, template strings, blocks, parens and punctuators.
262* Division operator / regex token ambiguity is handled via backtracking checks against punctuator prefixes, including closing brace or paren backtracking.
263* Always correctly parses valid JS source, but may parse invalid JS source without errors.
264
265### Limitations
266
267The lexing approach is designed to deal with the full language grammar including RegEx / division operator ambiguity through backtracking and paren / brace tracking.
268
269The only limitation to the reduced parser is that the "exports" list may not correctly gather all export identifiers in the following edge cases:
270
271```js
272// Only "a" is detected as an export, "q" isn't
273export var a = 'asdf', q = z;
274
275// "b" is not detected as an export
276export var { a: b } = asdf;
277```
278
279The above cases are handled gracefully in that the lexer will keep going fine, it will just not properly detect the export names above.
280
281### Benchmarks
282
283Benchmarks can be run with `npm run bench`.
284
285Current results for a high spec machine:
286
287#### Wasm Build
288
289```
290Module load time
291> 5ms
292Cold Run, All Samples
293test/samples/*.js (3123 KiB)
294> 18ms
295
296Warm Runs (average of 25 runs)
297test/samples/angular.js (739 KiB)
298> 3ms
299test/samples/angular.min.js (188 KiB)
300> 1ms
301test/samples/d3.js (508 KiB)
302> 3ms
303test/samples/d3.min.js (274 KiB)
304> 2ms
305test/samples/magic-string.js (35 KiB)
306> 0ms
307test/samples/magic-string.min.js (20 KiB)
308> 0ms
309test/samples/rollup.js (929 KiB)
310> 4.32ms
311test/samples/rollup.min.js (429 KiB)
312> 2.16ms
313
314Warm Runs, All Samples (average of 25 runs)
315test/samples/*.js (3123 KiB)
316> 14.16ms
317```
318
319#### JS Build (asm.js)
320
321```
322Module load time
323> 2ms
324Cold Run, All Samples
325test/samples/*.js (3123 KiB)
326> 34ms
327
328Warm Runs (average of 25 runs)
329test/samples/angular.js (739 KiB)
330> 3ms
331test/samples/angular.min.js (188 KiB)
332> 1ms
333test/samples/d3.js (508 KiB)
334> 3ms
335test/samples/d3.min.js (274 KiB)
336> 2ms
337test/samples/magic-string.js (35 KiB)
338> 0ms
339test/samples/magic-string.min.js (20 KiB)
340> 0ms
341test/samples/rollup.js (929 KiB)
342> 5ms
343test/samples/rollup.min.js (429 KiB)
344> 3.04ms
345
346Warm Runs, All Samples (average of 25 runs)
347test/samples/*.js (3123 KiB)
348> 17.12ms
349```
350
351### Building
352
353This project uses [Chomp](https://chompbuild.com) for building.
354
355With Chomp installed, download the WASI SDK 12.0 from https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-12.
356
357- [Linux](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz)
358- [Windows (MinGW)](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-mingw.tar.gz)
359- [macOS](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-macos.tar.gz)
360
361Locate the WASI-SDK as a sibling folder, or customize the path via the `WASI_PATH` environment variable.
362
363Emscripten emsdk is also assumed to be a sibling folder or via the `EMSDK_PATH` environment variable.
364
365Example setup:
366
367```
368git clone https://github.com:guybedford/es-module-lexer
369git clone https://github.com/emscripten-core/emsdk
370cd emsdk
371git checkout 1.40.1-fastcomp
372./emsdk install 1.40.1-fastcomp
373cd ..
374wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz
375gunzip wasi-sdk-12.0-linux.tar.gz
376tar -xf wasi-sdk-12.0-linux.tar
377mv wasi-sdk-12.0-linux.tar wasi-sdk-12.0
378cargo install chompbuild
379cd es-module-lexer
380chomp test
381```
382
383For the `asm.js` build, git clone `emsdk` from is assumed to be a sibling folder as well.
384
385### License
386
387MIT
388
389[actions-image]: https://github.com/guybedford/es-module-lexer/actions/workflows/build.yml/badge.svg
390[actions-url]: https://github.com/guybedford/es-module-lexer/actions/workflows/build.yml
Note: See TracBrowser for help on using the repository browser.