diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8b4119c678..83fc54eace9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: name: Unit tests (${{matrix.os}}, Node ${{matrix.node}}) strategy: matrix: - node: [14, 16] + node: [18, 20] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{matrix.os}} steps: @@ -75,7 +75,7 @@ jobs: name: Integration tests (${{matrix.os}}, Node ${{matrix.node}}) strategy: matrix: - node: [14, 16] + node: [18, 20] os: [ubuntu-latest, macos-latest, windows-latest] # These tend to be quite flakey, so one failed instance shouldn't stop # others from potentially succeeding diff --git a/packages/core/integration-tests/test/bundler.js b/packages/core/integration-tests/test/bundler.js index a98dd5f5b03..33c1eed0421 100644 --- a/packages/core/integration-tests/test/bundler.js +++ b/packages/core/integration-tests/test/bundler.js @@ -10,6 +10,7 @@ import { run, } from '@parcel/test-utils'; import {hashString} from '@parcel/rust'; +import {normalizePath} from '@parcel/utils'; describe('bundler', function () { it('should not create shared bundles when a bundle is being reused and disableSharedBundles is enabled', async function () { @@ -1522,9 +1523,12 @@ describe('bundler', function () { }, ]); - let targetDistDir = __dirname.replace('/test', '/dist'); + let targetDistDir = normalizePath(path.join(__dirname, '../dist')); let hashedIdWithMSB = hashString('bundle:' + 'vendorjs' + targetDistDir); - assert(b.getBundles().find(b => b.id == hashedIdWithMSB)); + assert( + b.getBundles().find(b => b.id == hashedIdWithMSB), + 'MSB id does not match expected', + ); }); it('should support manual shared bundles with constants module', async function () { @@ -1660,9 +1664,12 @@ describe('bundler', function () { }, ]); - let targetDistDir = __dirname.replace('/test', '/dist'); + let targetDistDir = normalizePath(path.join(__dirname, '../dist')); let hashedIdWithMSB = hashString('bundle:' + 'vendorjs' + targetDistDir); - assert(b.getBundles().find(b => b.id == hashedIdWithMSB)); + assert( + b.getBundles().find(b => b.id == hashedIdWithMSB), + 'MSB id does not match expected', + ); await run(b); }); diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index 5e7b871432f..348e93648ce 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -17,6 +17,7 @@ import { overlayFS, run, runBundle, + fsFixture, } from '@parcel/test-utils'; const bundle = (name, opts = {}) => { @@ -2487,6 +2488,23 @@ describe('scope hoisting', function () { assert.equal(await output, 42); }); + it('should handle TSC polyfills', async () => { + await fsFixture(overlayFS, __dirname)` + tsc-polyfill-es6 + library.js: + var __polyfill = (this && this.__polyfill) || function (a) {return a;}; + export default __polyfill('es6') + + index.js: + import value from './library'; + output = value;`; + + let b = await bundle(path.join(__dirname, 'tsc-polyfill-es6/index.js'), { + inputFS: overlayFS, + }); + assert.equal(await run(b), 'es6'); + }); + describe("considers an asset's closest package.json for sideEffects, not the package through which it found the asset", () => { it('handles redirects up the tree', async () => { let b = await bundle( @@ -5265,6 +5283,29 @@ describe('scope hoisting', function () { assert.deepEqual(await run(b), {test: 2}); }); + + it('should handle TSC polyfills', async () => { + await fsFixture(overlayFS, __dirname)` + tsc-polyfill-commonjs + library.js: + "use strict"; + var __polyfill = (this && this.__polyfill) || function (a) {return a;}; + exports.value = __polyfill('cjs') + + index.js: + const value = require('./library'); + output = value; + `; + + let b = await bundle( + path.join(__dirname, 'tsc-polyfill-commonjs/index.js'), + { + inputFS: overlayFS, + }, + ); + + assert.deepEqual(await run(b), {value: 'cjs'}); + }); }); it('should not throw with JS included from HTML', async function () { diff --git a/packages/transformers/js/core/src/collect.rs b/packages/transformers/js/core/src/collect.rs index 5abd717b0a0..804d0a5c427 100644 --- a/packages/transformers/js/core/src/collect.rs +++ b/packages/transformers/js/core/src/collect.rs @@ -78,6 +78,7 @@ pub struct Collect { in_function: bool, in_assign: bool, in_class: bool, + is_module: bool, } #[derive(Debug, Serialize)] @@ -120,12 +121,14 @@ impl Collect { ignore_mark: Mark, global_mark: Mark, trace_bailouts: bool, + is_module: bool, ) -> Self { Collect { source_map, decls, ignore_mark, global_mark, + is_module, static_cjs_exports: true, has_cjs_exports: false, is_esm: false, @@ -690,7 +693,9 @@ impl Visit for Collect { } Expr::This(_this) => { if self.in_module_this { - handle_export!(); + if !self.is_module { + handle_export!(); + } } else if !self.in_class { if let MemberProp::Ident(prop) = &node.prop { self.this_exprs.insert(id!(prop), (prop.clone(), node.span)); @@ -767,27 +772,6 @@ impl Visit for Collect { self.used_imports.insert(id!(ident)); } } - Expr::Bin(bin_expr) => { - if self.in_module_this { - // Some TSC polyfills use a pattern like below. - // We want to avoid marking these modules as CJS - // e.g. var _polyfill = (this && this.polyfill) || function () {} - if matches!(bin_expr.op, BinaryOp::LogicalAnd) && matches!(*bin_expr.left, Expr::This(..)) - { - match &*bin_expr.right { - Expr::Member(member_expr) => { - if matches!(*member_expr.obj, Expr::This(..)) - && matches!(member_expr.prop, MemberProp::Ident(..)) - { - return; - } - } - _ => {} - } - } - } - node.visit_children_with(self); - } _ => { node.visit_children_with(self); } @@ -820,7 +804,7 @@ impl Visit for Collect { } fn visit_this_expr(&mut self, node: &ThisExpr) { - if self.in_module_this { + if !self.is_module && self.in_module_this { self.has_cjs_exports = true; self.static_cjs_exports = false; self.add_bailout(node.span, BailoutReason::FreeExports); diff --git a/packages/transformers/js/core/src/fs.rs b/packages/transformers/js/core/src/fs.rs index 0bb3d001579..83cc9b030c4 100644 --- a/packages/transformers/js/core/src/fs.rs +++ b/packages/transformers/js/core/src/fs.rs @@ -17,6 +17,7 @@ pub fn inline_fs<'a>( global_mark: Mark, project_root: &'a str, deps: &'a mut Vec, + is_module: bool, ) -> impl Fold + 'a { InlineFS { filename: Path::new(filename).to_path_buf(), @@ -26,6 +27,7 @@ pub fn inline_fs<'a>( Mark::fresh(Mark::root()), global_mark, false, + is_module, ), global_mark, project_root, diff --git a/packages/transformers/js/core/src/hoist.rs b/packages/transformers/js/core/src/hoist.rs index 40ce3cc1719..b248815a2de 100644 --- a/packages/transformers/js/core/src/hoist.rs +++ b/packages/transformers/js/core/src/hoist.rs @@ -1140,11 +1140,21 @@ mod tests { ); let mut parser = Parser::new_from(lexer); - match parser.parse_module() { - Ok(module) => swc_core::common::GLOBALS.set(&Globals::new(), || { + match parser.parse_program() { + Ok(program) => swc_core::common::GLOBALS.set(&Globals::new(), || { swc_core::ecma::transforms::base::helpers::HELPERS.set( &swc_core::ecma::transforms::base::helpers::Helpers::new(false), || { + let is_module = program.is_module(); + let module = match program { + Program::Module(module) => module, + Program::Script(script) => Module { + span: script.span, + shebang: None, + body: script.body.into_iter().map(ModuleItem::Stmt).collect(), + }, + }; + let unresolved_mark = Mark::fresh(Mark::root()); let global_mark = Mark::fresh(Mark::root()); let module = module.fold_with(&mut resolver(unresolved_mark, global_mark, false)); @@ -1155,6 +1165,7 @@ mod tests { Mark::fresh(Mark::root()), global_mark, true, + is_module, ); module.visit_with(&mut collect); @@ -1444,6 +1455,7 @@ mod tests { // We want to avoid marking these modules as CJS let (collect, _code, _hoist) = parse( r#" + import 'something'; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function () {} "#, ); diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index b7fa321ef34..234ef1e1ebe 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -252,6 +252,7 @@ pub fn transform(config: Config) -> Result { ), )); + let is_module = module.is_module(); // If it's a script, convert into module. This needs to happen after // the resolver (which behaves differently for non-/strict mode). let module = match module { @@ -342,6 +343,7 @@ pub fn transform(config: Config) -> Result { global_mark, &config.project_root, &mut fs_deps, + is_module ), should_inline_fs ), @@ -448,6 +450,7 @@ pub fn transform(config: Config) -> Result { ignore_mark, global_mark, config.trace_bailouts, + is_module, ); module.visit_with(&mut collect); if let Some(bailouts) = &collect.bailouts {