Skip to content

Commit

Permalink
Support __export(require("...")) pattern (close #611)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije committed Apr 29, 2023
1 parent d351d5d commit 4216942
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 24 deletions.
16 changes: 9 additions & 7 deletions packages/esm-cjs-lexer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ Types:
export function parse(
specifier: string,
code: string,
node_env?: 'development' | 'production',
call_mode?: boolean,
options? {
nodeEnv?: 'development' | 'production',
callMode?: boolean,
}
): {
exports: string[],
reexports: string[],
};
```

Example:
Example:
```js
const { parse } = require('esm-cjs-lexer');

Expand Down Expand Up @@ -61,7 +63,7 @@ const { exports, reexports } = parse('index.cjs', `
module.exports = { foo, ...obj, ...require("./lib") };
`);

// condition
// if condition
// exports: ['foo', 'cjs']
const { exports } = parse('index.cjs', `
module.exports.a = "a";
Expand Down Expand Up @@ -95,15 +97,15 @@ const { exports } = parse('index.cjs', `
exports.__esModule = true
`);

// condition with `process.env.NODE_ENV`
// env condition with `process.env.NODE_ENV`
// reexports: ['./index.development']
const { reexports } = parse('index.cjs', `
if (process.env.NODE_ENV === "development") {
module.exports = require("./index.development")
} else {
module.exports = require("./index.production")
}
`, 'development');
`, { nodeEnv: 'development' });

// IIFE exports
// exports: ['foo']
Expand Down Expand Up @@ -138,7 +140,7 @@ const { exports } = parse('lib.cjs', `
module.exports = function() {
return { foo: 'bar' }
}
`, 'production', true);
`, { callMode: true });
```

## Development Setup
Expand Down
26 changes: 22 additions & 4 deletions packages/esm-cjs-lexer/src/cjs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ impl ExportsParser {
}
}
} else {
for bare_export_name in self.get_bare_export_names(assign.right.as_ref()) {
if let Some(bare_export_name) = self.get_bare_export_names(assign.right.as_ref()) {
self.exports.insert(bare_export_name);
}
}
Expand Down Expand Up @@ -716,7 +716,7 @@ impl ExportsParser {
for decl in var.as_ref().decls.iter() {
self.try_to_mark_exports_alias(decl);
if let Some(init_expr) = &decl.init {
for bare_export_name in self.get_bare_export_names(init_expr) {
if let Some(bare_export_name) = self.get_bare_export_names(init_expr) {
self.exports.insert(bare_export_name);
}
}
Expand Down Expand Up @@ -830,13 +830,19 @@ impl ExportsParser {
self.reexports.insert(reexport);
}
}
} else if is_export_call(&call) && call.args.len() > 0 {
if let Some(props) = self.as_obj(call.args[0].expr.as_ref()) {
self.use_object_as_exports(props);
} else if let Some(reexport) = self.as_reexport(call.args[0].expr.as_ref()) {
self.reexports.insert(reexport);
}
} else if let Some(body) = is_umd_iife_call(&call) {
self.dep_parse(body, false);
} else if let Some(body) = is_iife_call(&call) {
for arg in &call.args {
if arg.spread.is_none() {
// (function() { ... })(exports.foo || (exports.foo = {}))
for bare_export_name in self.get_bare_export_names(arg.expr.as_ref()) {
if let Some(bare_export_name) = self.get_bare_export_names(arg.expr.as_ref()) {
self.exports.insert(bare_export_name);
}
}
Expand All @@ -852,7 +858,7 @@ impl ExportsParser {
// (function() { ... })(exports.foo || (exports.foo = {}))
for arg in &call.args {
if arg.spread.is_none() {
for bare_export_name in self.get_bare_export_names(arg.expr.as_ref()) {
if let Some(bare_export_name) = self.get_bare_export_names(arg.expr.as_ref()) {
self.exports.insert(bare_export_name);
}
}
Expand Down Expand Up @@ -1063,6 +1069,18 @@ fn is_iife_call(call: &CallExpr) -> Option<Vec<Stmt>> {
None
}

fn is_export_call(call: &CallExpr) -> bool {
if let Some(callee) = with_expr_callee(call) {
match callee {
Expr::Ident(id) => {
return id.sym.as_ref().eq("__export");
}
_ => {}
}
}
false
}

// match:
// require("tslib").__exportStar(..., exports)
// (0, require("tslib").__exportStar)(..., exports)
Expand Down
24 changes: 15 additions & 9 deletions packages/esm-cjs-lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ mod error;
mod swc;
mod test;

use serde::Serialize;
use serde::{Deserialize, Serialize};
use swc::SWC;
use wasm_bindgen::prelude::{wasm_bindgen, JsValue};

#[derive(Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Options {
node_env: Option<String>,
call_mode: Option<bool>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Output {
Expand All @@ -15,21 +22,20 @@ pub struct Output {
}

#[wasm_bindgen(js_name = "parse")]
pub fn parse(
specifier: &str,
code: &str,
node_env: Option<String>,
call_mode: Option<bool>,
) -> Result<JsValue, JsValue> {
pub fn parse(specifier: &str, code: &str, options: JsValue) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();

let options: Options = serde_wasm_bindgen::from_value(options).unwrap_or(Options{
node_env: None,
call_mode: None,
});
let swc = SWC::parse(specifier, code).expect("could not parse module");
let node_env = if let Some(env) = node_env {
let node_env = if let Some(env) = options.node_env {
env
} else {
"production".to_owned()
};
let call_mode = if let Some(ok) = call_mode { ok } else { false };
let call_mode = if let Some(ok) = options.call_mode { ok } else { false };
let (exports, reexports) = swc.parse_cjs_exports(node_env.as_str(), call_mode).unwrap();
Ok(
serde_wasm_bindgen::to_value(&Output {
Expand Down
15 changes: 15 additions & 0 deletions packages/esm-cjs-lexer/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,4 +722,19 @@ mod tests {
.expect("could not parse exports");
assert_eq!(exports.join(","), "i18n,use,t");
}

#[test]
fn parse_cjs_exports_case_23() {
let source = r#"
Object.defineProperty(exports, "__esModule", { value: true });
__export({foo:"bar"});
__export(require("./lib"));
"#;
let swc = SWC::parse("index.cjs", source).expect("could not parse module");
let (exports, reexports) = swc
.parse_cjs_exports("production", true)
.expect("could not parse exports");
assert_eq!(exports.join(","), "__esModule,foo");
assert_eq!(reexports.join(","), "./lib");
}
}
13 changes: 9 additions & 4 deletions packages/esm-cjs-lexer/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ const { parse } = require('./pkg/esm_cjs_lexer')
exports.foo = true
module.exports.bar = true

if (process.env.NODE_ENV === 'development') {
exports.dev = true
} else {
exports.prod = true
}

const code = fs.readFileSync("./test.js", "utf-8")
const results = parse("./test.js", code, "developments", false)
const ret = parse("./test.js", code, { nodeEnv: "development" })

if (results.exports.join(',') !== 'foo,bar') {
console.error('unexpected exports of index.js:', exports)
process.exit(1)
if (ret.exports.join(',') !== 'foo,bar,dev') {
throw new Error('unexpected exports of index.js: ' + ret.exports.join(','))
}
console.log("done")

0 comments on commit 4216942

Please sign in to comment.