Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 3dbf660

Browse files
committed
feat: add parseExportNamesSync function
1 parent fcc9893 commit 3dbf660

File tree

7 files changed

+250
-111
lines changed

7 files changed

+250
-111
lines changed

compiler/mod.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ import log from '../shared/log.ts'
44
import type { LoaderPlugin } from '../types.ts'
55
import { VERSION } from '../version.ts'
66
import { checksum } from './dist/wasm-checksum.js'
7-
import init, { transformSync } from './dist/wasm-pack.js'
7+
import init, { parseExportNamesSync, transformSync } from './dist/wasm-pack.js'
8+
9+
export enum SourceType {
10+
JS = 0,
11+
JSX = 1,
12+
TS = 2,
13+
TSX = 3,
14+
JSON = 4,
15+
WASM = 5,
16+
Unknown = 9,
17+
}
818

919
export type ImportMap = {
1020
imports: Record<string, string>
1121
scopes: Record<string, Record<string, string>>
1222
}
1323

1424
export type SWCOptions = {
15-
sourceType?: 'js' | 'jsx' | 'ts' | 'tsx'
25+
sourceType?: SourceType
1626
target?: 'es5' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020'
1727
jsxFactory?: string
1828
jsxFragmentFactory?: string
@@ -58,7 +68,7 @@ async function getDenoDir() {
5868
return JSON.parse(output).denoDir
5969
}
6070

61-
async function initWasm() {
71+
export async function initWasm() {
6272
const cacheDir = path.join(await getDenoDir(), `deps/https/deno.land/aleph@v${VERSION}`)
6373
const cachePath = `${cacheDir}/compiler.${checksum}.wasm`
6474
if (existsFileSync(cachePath)) {
@@ -160,6 +170,24 @@ export async function transform(url: string, code: string, options: TransformOpt
160170
return { code: jsContent, deps, map }
161171
}
162172

173+
/* parse export names of the module */
174+
export async function parseExportNames(url: string, code: string, options: SWCOptions = {}): Promise<string[]> {
175+
let t: number | null = null
176+
if (wasmReady === false) {
177+
t = performance.now()
178+
wasmReady = initWasm()
179+
}
180+
if (wasmReady instanceof Promise) {
181+
await wasmReady
182+
wasmReady = true
183+
}
184+
if (t !== null) {
185+
log.debug(`init compiler wasm in ${Math.round(performance.now() - t)}ms`)
186+
}
187+
188+
return parseExportNamesSync(url, code, options)
189+
}
190+
163191
/**
164192
* The wasm build checksum.
165193
*/

compiler/src/fast_refresh.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,12 +1025,12 @@ fn get_call_callee(call: &CallExpr) -> Option<(Option<Ident>, Ident)> {
10251025
#[cfg(test)]
10261026
mod tests {
10271027
use super::*;
1028-
use crate::swc::ParsedModule;
1028+
use crate::swc::SWC;
10291029
use std::cmp::min;
10301030
use swc_common::Globals;
10311031

10321032
fn t(specifier: &str, source: &str, expect: &str) -> bool {
1033-
let module = ParsedModule::parse(specifier, source, None).expect("could not parse module");
1033+
let module = SWC::parse(specifier, source, None).expect("could not parse module");
10341034
let (code, _) = swc_common::GLOBALS.set(&Globals::new(), || {
10351035
module
10361036
.apply_transform(

compiler/src/fixer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ impl Fold for CompatFixer {
6060
#[cfg(test)]
6161
mod tests {
6262
use super::*;
63-
use crate::swc::ParsedModule;
63+
use crate::swc::SWC;
6464
use std::cmp::min;
6565
use swc_common::Globals;
6666

6767
fn t(specifier: &str, source: &str, expect: &str) -> bool {
68-
let module = ParsedModule::parse(specifier, source, None).expect("could not parse module");
68+
let module = SWC::parse(specifier, source, None).expect("could not parse module");
6969
let (code, _) = swc_common::GLOBALS.set(&Globals::new(), || {
7070
module
7171
.apply_transform(compat_fixer_fold(), false)

compiler/src/lib.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
1717
use source_type::SourceType;
1818
use std::collections::HashMap;
1919
use std::{cell::RefCell, rc::Rc};
20-
use swc::{EmitOptions, ParsedModule};
20+
use swc::{EmitOptions, SWC};
2121
use swc_ecmascript::parser::JscTarget;
2222
use wasm_bindgen::prelude::{wasm_bindgen, JsValue};
2323

@@ -56,7 +56,7 @@ pub struct Options {
5656
#[serde(deny_unknown_fields, rename_all = "camelCase")]
5757
pub struct SWCOptions {
5858
#[serde(default)]
59-
pub source_type: String,
59+
pub source_type: SourceType,
6060

6161
#[serde(default = "default_target")]
6262
pub target: JscTarget,
@@ -71,7 +71,7 @@ pub struct SWCOptions {
7171
impl Default for SWCOptions {
7272
fn default() -> Self {
7373
SWCOptions {
74-
source_type: "tsx".into(),
74+
source_type: SourceType::default(),
7575
target: default_target(),
7676
jsx_factory: default_pragma(),
7777
jsx_fragment_factory: default_pragma_frag(),
@@ -102,6 +102,23 @@ pub struct TransformOutput {
102102
pub star_exports: Vec<String>,
103103
}
104104

105+
#[wasm_bindgen(js_name = "parseExportNamesSync")]
106+
pub fn parse_export_names_sync(
107+
url: &str,
108+
code: &str,
109+
options: JsValue,
110+
) -> Result<JsValue, JsValue> {
111+
console_error_panic_hook::set_once();
112+
113+
let options: SWCOptions = options
114+
.into_serde()
115+
.map_err(|err| format!("failed to parse options: {}", err))
116+
.unwrap();
117+
let module = SWC::parse(url, code, Some(options.source_type)).expect("could not parse module");
118+
let export_names = module.parse_export_names().unwrap();
119+
Ok(JsValue::from_serde(&export_names).unwrap())
120+
}
121+
105122
#[wasm_bindgen(js_name = "transformSync")]
106123
pub fn transform_sync(url: &str, code: &str, options: JsValue) -> Result<JsValue, JsValue> {
107124
console_error_panic_hook::set_once();
@@ -124,14 +141,8 @@ pub fn transform_sync(url: &str, code: &str, options: JsValue) -> Result<JsValue
124141
options.bundle_mode,
125142
options.bundle_external,
126143
)));
127-
let specify_source_type = match options.swc_options.source_type.as_str() {
128-
"js" => Some(SourceType::JavaScript),
129-
"jsx" => Some(SourceType::JSX),
130-
"ts" => Some(SourceType::TypeScript),
131-
"tsx" => Some(SourceType::TSX),
132-
_ => None,
133-
};
134-
let module = ParsedModule::parse(url, code, specify_source_type).expect("could not parse module");
144+
let module =
145+
SWC::parse(url, code, Some(options.swc_options.source_type)).expect("could not parse module");
135146
let (code, map) = module
136147
.transform(
137148
resolver.clone(),

compiler/src/resolve_fold.rs

Lines changed: 148 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ use swc_ecma_ast::*;
77
use swc_ecma_utils::quote_ident;
88
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
99

10-
pub fn aleph_resolve_fold(resolver: Rc<RefCell<Resolver>>, source: Rc<SourceMap>) -> impl Fold {
11-
AlephResolveFold {
10+
pub fn resolve_fold(resolver: Rc<RefCell<Resolver>>, source: Rc<SourceMap>) -> impl Fold {
11+
ResolveFold {
1212
deno_hooks_idx: 0,
1313
resolver,
1414
source,
1515
}
1616
}
1717

18-
pub struct AlephResolveFold {
18+
pub struct ResolveFold {
1919
deno_hooks_idx: i32,
2020
resolver: Rc<RefCell<Resolver>>,
2121
source: Rc<SourceMap>,
2222
}
2323

24-
impl AlephResolveFold {
24+
impl ResolveFold {
2525
fn new_use_deno_hook_ident(&mut self, callback_span: &Span) -> String {
2626
let resolver = self.resolver.borrow_mut();
2727
self.deno_hooks_idx = self.deno_hooks_idx + 1;
@@ -42,7 +42,7 @@ impl AlephResolveFold {
4242
}
4343
}
4444

45-
impl Fold for AlephResolveFold {
45+
impl Fold for ResolveFold {
4646
noop_fold_type!();
4747

4848
// resolve import/export url
@@ -367,6 +367,96 @@ impl Fold for AlephResolveFold {
367367
}
368368
}
369369

370+
pub struct ExportsParser {
371+
pub names: Vec<String>,
372+
}
373+
374+
impl ExportsParser {
375+
fn push_pat(&mut self, pat: &Pat) {
376+
match pat {
377+
Pat::Ident(id) => self.names.push(id.sym.as_ref().into()),
378+
Pat::Array(ArrayPat { elems, .. }) => elems.into_iter().for_each(|e| {
379+
if let Some(el) = e {
380+
self.push_pat(el)
381+
}
382+
}),
383+
Pat::Assign(AssignPat { left, .. }) => self.push_pat(left.as_ref()),
384+
Pat::Object(ObjectPat { props, .. }) => props.into_iter().for_each(|prop| match prop {
385+
ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
386+
self.names.push(key.sym.as_ref().into())
387+
}
388+
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => self.push_pat(value.as_ref()),
389+
ObjectPatProp::Rest(RestPat { arg, .. }) => self.push_pat(arg.as_ref()),
390+
}),
391+
Pat::Rest(RestPat { arg, .. }) => self.push_pat(arg.as_ref()),
392+
_ => {}
393+
}
394+
}
395+
}
396+
397+
impl Fold for ExportsParser {
398+
noop_fold_type!();
399+
400+
fn fold_module_items(&mut self, module_items: Vec<ModuleItem>) -> Vec<ModuleItem> {
401+
for item in &module_items {
402+
match item {
403+
ModuleItem::ModuleDecl(decl) => match decl {
404+
// match: export const foo = 'bar'
405+
// match: export function foo() {}
406+
// match: export class foo {}
407+
ModuleDecl::ExportDecl(ExportDecl { decl, .. }) => match decl {
408+
Decl::Class(ClassDecl { ident, .. }) => self.names.push(ident.sym.as_ref().into()),
409+
Decl::Fn(FnDecl { ident, .. }) => self.names.push(ident.sym.as_ref().into()),
410+
Decl::Var(VarDecl { decls, .. }) => decls.into_iter().for_each(|decl| {
411+
self.push_pat(&decl.name);
412+
}),
413+
_ => {}
414+
},
415+
// match: export default function
416+
// match: export default class
417+
ModuleDecl::ExportDefaultDecl(_) => self.names.push("default".into()),
418+
// match: export default foo
419+
ModuleDecl::ExportDefaultExpr(_) => self.names.push("default".into()),
420+
// match: export { default as React, useState } from "https://esm.sh/react"
421+
// match: export * as React from "https://esm.sh/react"
422+
ModuleDecl::ExportNamed(NamedExport {
423+
type_only,
424+
specifiers,
425+
..
426+
}) => {
427+
if !type_only {
428+
specifiers
429+
.into_iter()
430+
.for_each(|specifier| match specifier {
431+
ExportSpecifier::Named(ExportNamedSpecifier { orig, exported, .. }) => {
432+
match exported {
433+
Some(name) => self.names.push(name.sym.as_ref().into()),
434+
None => self.names.push(orig.sym.as_ref().into()),
435+
}
436+
}
437+
ExportSpecifier::Default(ExportDefaultSpecifier { exported, .. }) => {
438+
self.names.push(exported.sym.as_ref().into());
439+
}
440+
ExportSpecifier::Namespace(ExportNamespaceSpecifier { name, .. }) => {
441+
self.names.push(name.sym.as_ref().into())
442+
}
443+
});
444+
}
445+
}
446+
// match: export * from "https://esm.sh/react"
447+
ModuleDecl::ExportAll(ExportAll { src, .. }) => {
448+
self.names.push(format!("{{{}}}", src.value))
449+
}
450+
_ => {}
451+
},
452+
_ => {}
453+
};
454+
}
455+
456+
module_items
457+
}
458+
}
459+
370460
pub fn is_call_expr_by_name(call: &CallExpr, name: &str) -> bool {
371461
let callee = match &call.callee {
372462
ExprOrSuper::Super(_) => return false,
@@ -454,7 +544,7 @@ mod tests {
454544
use super::*;
455545
use crate::import_map::ImportHashMap;
456546
use crate::resolve::Resolver;
457-
use crate::swc::{st, EmitOptions, ParsedModule};
547+
use crate::swc::{st, EmitOptions, SWC};
458548
use sha1::{Digest, Sha1};
459549

460550
#[test]
@@ -545,8 +635,7 @@ mod tests {
545635
)
546636
}
547637
"#;
548-
let module =
549-
ParsedModule::parse("/pages/index.tsx", source, None).expect("could not parse module");
638+
let module = SWC::parse("/pages/index.tsx", source, None).expect("could not parse module");
550639
let resolver = Rc::new(RefCell::new(Resolver::new(
551640
"/pages/index.tsx",
552641
ImportHashMap::default(),
@@ -581,4 +670,55 @@ mod tests {
581670
assert!(code.contains("export const ReactDom = __ALEPH.pack[\"https://esm.sh/react-dom\"]"));
582671
assert!(code.contains("export const { render } = __ALEPH.pack[\"https://esm.sh/react-dom\"]"));
583672
}
673+
674+
#[test]
675+
fn parse_export_names() {
676+
let source = r#"
677+
export const name = "alephjs"
678+
export const version = "1.0.1"
679+
const start = () => {}
680+
export default start
681+
export const { build } = { build: () => {} }
682+
export function dev() {}
683+
export class Server {}
684+
export const { a: { a1, a2 }, 'b': [ b1, b2 ], c, ...rest } = { a: { a1: 0, a2: 0 }, b: [ 0, 0 ], c: 0, d: 0 }
685+
export const [ d, e, ...{f, g, rest3} ] = [0, 0, {f:0,g:0,h:0}]
686+
let i
687+
export const j = i = [0, 0]
688+
export { exists, existsSync } from "https://deno.land/std/fs/exists.ts"
689+
export * as DenoStdServer from "https://deno.land/std/http/sever.ts"
690+
export * from "https://deno.land/std/http/sever.ts"
691+
"#;
692+
let module = SWC::parse("/app.ts", source, None).expect("could not parse module");
693+
assert_eq!(
694+
module.parse_export_names().unwrap(),
695+
vec![
696+
"name",
697+
"version",
698+
"default",
699+
"build",
700+
"dev",
701+
"Server",
702+
"a1",
703+
"a2",
704+
"b1",
705+
"b2",
706+
"c",
707+
"rest",
708+
"d",
709+
"e",
710+
"f",
711+
"g",
712+
"rest3",
713+
"j",
714+
"exists",
715+
"existsSync",
716+
"DenoStdServer",
717+
"{https://deno.land/std/http/sever.ts}",
718+
]
719+
.into_iter()
720+
.map(|s| s.to_owned())
721+
.collect::<Vec<String>>()
722+
)
723+
}
584724
}

0 commit comments

Comments
 (0)