export enum ImportType { /** * A normal static using any syntax variations * import .. from 'module' */ Static = 1, /** * A dynamic import expression `import(specifier)` * or `import(specifier, opts)` */ Dynamic = 2, /** * An import.meta expression */ ImportMeta = 3, /** * A source phase import * import source x from 'module' */ StaticSourcePhase = 4, /** * A dynamic source phase import * import.source('module') */ DynamicSourcePhase = 5, /** * A defer phase import * import defer * as x from 'module' */ StaticDeferPhase = 6, /** * A dynamic defer phase import * import.defer('module') */ DynamicDeferPhase = 7, } export interface ImportSpecifier { /** * Module name * * To handle escape sequences in specifier strings, the .n field of imported specifiers will be provided where possible. * * For dynamic import expressions, this field will be empty if not a valid JS string. * For static import expressions, this field will always be populated. * * @example * const [imports1, exports1] = parse(String.raw`import './\u0061\u0062.js'`); * imports1[0].n; * // Returns "./ab.js" * * const [imports2, exports2] = parse(`import("./ab.js")`); * imports2[0].n; * // Returns "./ab.js" * * const [imports3, exports3] = parse(`import("./" + "ab.js")`); * imports3[0].n; * // Returns undefined */ readonly n: string | undefined; /** * Type of import statement */ readonly t: ImportType; /** * Start of module specifier * * @example * const source = `import { a } from 'asdf'`; * const [imports, exports] = parse(source); * source.substring(imports[0].s, imports[0].e); * // Returns "asdf" */ readonly s: number; /** * End of module specifier */ readonly e: number; /** * Start of import statement * * @example * const source = `import { a } from 'asdf'`; * const [imports, exports] = parse(source); * source.substring(imports[0].ss, imports[0].se); * // Returns "import { a } from 'asdf';" */ readonly ss: number; /** * End of import statement */ readonly se: number; /** * If this import keyword is a dynamic import, this is the start value. * If this import keyword is a static import, this is -1. * If this import keyword is an import.meta expresion, this is -2. */ readonly d: number; /** * If this import has an import attribute, this is the start value. * Otherwise this is `-1`. */ readonly a: number; /** * Parsed import attributes as an array of [key, value] tuples. * If this import has no attributes, this is `null`. * * @example * const source = `import foo from 'bar' with { type: "json" }`; * const [imports] = parse(source); * imports[0].at; * // Returns [['type', 'json']] * * @example * const source = `import foo from 'bar' with { type: "json", integrity: "sha384-..." }`; * const [imports] = parse(source); * imports[0].at; * // Returns [['type', 'json'], ['integrity', 'sha384-...']] */ readonly at: ReadonlyArray | null; } export interface ExportSpecifier { /** * Exported name * * @example * const source = `export default []`; * const [imports, exports] = parse(source); * exports[0].n; * // Returns "default" * * @example * const source = `export const asdf = 42`; * const [imports, exports] = parse(source); * exports[0].n; * // Returns "asdf" */ readonly n: string; /** * Local name, or undefined. * * @example * const source = `export default []`; * const [imports, exports] = parse(source); * exports[0].ln; * // Returns undefined * * @example * const asdf = 42; * const source = `export { asdf as a }`; * const [imports, exports] = parse(source); * exports[0].ln; * // Returns "asdf" */ readonly ln: string | undefined; /** * Start of exported name * * @example * const source = `export default []`; * const [imports, exports] = parse(source); * source.substring(exports[0].s, exports[0].e); * // Returns "default" * * @example * const source = `export { 42 as asdf }`; * const [imports, exports] = parse(source); * source.substring(exports[0].s, exports[0].e); * // Returns "asdf" */ readonly s: number; /** * End of exported name */ readonly e: number; /** * Start of local name, or -1. * * @example * const asdf = 42; * const source = `export { asdf as a }`; * const [imports, exports] = parse(source); * source.substring(exports[0].ls, exports[0].le); * // Returns "asdf" */ readonly ls: number; /** * End of local name, or -1. */ readonly le: number; } export interface ParseError extends Error { idx: number } const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1; /** * Outputs the list of exports and locations of import specifiers, * including dynamic import and import meta handling. * * @param source Source code to parser * @param name Optional sourcename * @returns Tuple contaning imports list and exports list. */ export function parse (source: string, name = '@'): readonly [ imports: ReadonlyArray, exports: ReadonlyArray, facade: boolean, hasModuleSyntax: boolean ] { if (!wasm) // actually returns a promise if init hasn't resolved (not type safe). // casting to avoid a breaking type change. return init.then(() => parse(source)) as unknown as ReturnType; const len = source.length + 1; // need 2 bytes per code point plus analysis space so we double again const extraMem = (wasm.__heap_base.value || wasm.__heap_base) as number + len * 4 - wasm.memory.buffer.byteLength; if (extraMem > 0) wasm.memory.grow(Math.ceil(extraMem / 65536)); const addr = wasm.sa(len - 1); (isLE ? copyLE : copyBE)(source, new Uint16Array(wasm.memory.buffer, addr, len)); if (!wasm.parse()) throw Object.assign(new Error(`Parse error ${name}:${source.slice(0, wasm.e()).split('\n').length}:${wasm.e() - source.lastIndexOf('\n', wasm.e() - 1)}`), { idx: wasm.e() }); const imports: ImportSpecifier[] = [], exports: ExportSpecifier[] = []; while (wasm.ri()) { const s = wasm.is(), e = wasm.ie(), t = wasm.it(), a = wasm.ai(), d = wasm.id(), ss = wasm.ss(), se = wasm.se(); let n; if (wasm.ip()) n = decode(source.slice(d === -1 ? s - 1 : s, d === -1 ? e + 1 : e)); const at: Array<[string, string]> = []; wasm.rsa(); while (wasm.ra()) { const aks = wasm.aks(), ake = wasm.ake(), avs = wasm.avs(), ave = wasm.ave(); at.push([decodeIfQuoted(source.slice(aks, ake)), decodeIfQuoted(source.slice(avs, ave))]); } imports.push({ n, t, s, e, ss, se, d, a, at: at.length > 0 ? at : null }); } while (wasm.re()) { const s = wasm.es(), e = wasm.ee(), ls = wasm.els(), le = wasm.ele(); const n = decodeIfQuoted(source.slice(s, e)); const ln = ls < 0 ? undefined : decodeIfQuoted(source.slice(ls, le)); exports.push({ s, e, ls, le, n, ln, }); } function decode (str: string) { try { return (0, eval)(str) } catch (e) {} } function decodeIfQuoted (str: string): string { if (!str) return str; const firstChar = str[0]; if (firstChar === '"' || firstChar === "'") return decode(str) || str; return str; } return [imports, exports, !!wasm.f(), !!wasm.ms()]; } function copyBE (src: string, outBuf16: Uint16Array) { const len = src.length; let i = 0; while (i < len) { const ch = src.charCodeAt(i); outBuf16[i++] = (ch & 0xff) << 8 | ch >>> 8; } } function copyLE (src: string, outBuf16: Uint16Array) { const len = src.length; let i = 0; while (i < len) outBuf16[i] = src.charCodeAt(i++); } let wasm: { __heap_base: {value: number} | number & {value: undefined}; memory: WebAssembly.Memory; parse(): boolean; /** importType */ it(): number; /** getAttributeIndex */ ai(): number; /** getErr */ e(): number; /** getExportEnd */ ee(): number; /** getExportLocalEnd */ ele(): number; /** getExportLocalStart */ els(): number; /** getExportStart */ es(): number; /** facade */ f(): boolean; /** hasModuleSyntax */ ms(): boolean; /** getImportDynamic */ id(): number; /** getImportEnd */ ie(): number; /** getImportSafeString */ ip(): number; /** getImportStart */ is(): number; /** readExport */ re(): boolean; /** readImport */ ri(): boolean; /** allocateSource */ sa(utf16Len: number): number; /** getImportStatementEnd */ se(): number; /** getImportStatementStart */ ss(): number; /** readAttribute */ ra(): boolean; /** resetAttributes */ rsa(): void; /** getAttributeKeyStart */ aks(): number; /** getAttributeKeyEnd */ ake(): number; /** getAttributeValueStart */ avs(): number; /** getAttributeValueEnd */ ave(): number; }; const getWasmBytes = () => ( binary => typeof Buffer !== 'undefined' ? Buffer.from(binary, 'base64') : Uint8Array.from(atob(binary), x => x.charCodeAt(0)) )('WASM_BINARY'); /** * Wait for init to resolve before calling `parse`. */ export const init = WebAssembly.compile(getWasmBytes()) .then(WebAssembly.instantiate) .then(({ exports }) => { wasm = exports as typeof wasm; }); export const initSync = () => { if (wasm) { return; } const compiled = new WebAssembly.Module(getWasmBytes()); wasm = new WebAssembly.Instance(compiled).exports as typeof wasm; return; };