From f5661cda3b0d0c1fc428ba6b359769b10b0d48f8 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Wed, 5 Oct 2022 18:04:48 -0400 Subject: [PATCH] Remove hook_optimizer transform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes an issue where `const […foo] = useState(0)` is improperly transformed into `const { 0: …foo } = useState(0)`. The hook optimizer was useful when hooks first came out, primarily because browsers had never optimized array destructuring. But current browsers have, and the optimization doesn't help anymore. --- .../crates/core/src/hook_optimizer.rs | 123 ------------------ packages/next-swc/crates/core/src/lib.rs | 2 - test/unit/next-swc.test.ts | 93 ++++++++++--- 3 files changed, 78 insertions(+), 140 deletions(-) delete mode 100644 packages/next-swc/crates/core/src/hook_optimizer.rs diff --git a/packages/next-swc/crates/core/src/hook_optimizer.rs b/packages/next-swc/crates/core/src/hook_optimizer.rs deleted file mode 100644 index 8e759d30a8de0..0000000000000 --- a/packages/next-swc/crates/core/src/hook_optimizer.rs +++ /dev/null @@ -1,123 +0,0 @@ -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{ - ArrayPat, Callee, Decl, Expr, Ident, ImportDecl, ImportSpecifier, KeyValuePatProp, Number, - ObjectPat, ObjectPatProp, Pat, PropName, VarDecl, VarDeclarator, - }, - ecma::atoms::JsWord, - ecma::visit::{Fold, FoldWith}, -}; - -pub fn hook_optimizer() -> impl Fold { - HookOptimizer::default() -} - -#[derive(Debug, Default)] -struct HookOptimizer { - hooks: Vec, -} - -impl Fold for HookOptimizer { - // Find hooks imported from react/preact - fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl { - let ImportDecl { - ref src, - ref specifiers, - .. - } = decl; - if &src.value == "react" || &src.value == "preact/hooks" { - for specifier in specifiers { - if let ImportSpecifier::Named(named_specifier) = specifier { - if named_specifier.local.sym.starts_with("use") { - self.hooks.push(named_specifier.local.sym.clone()) - } - } - } - } - - decl - } - // Transform array desctructing to object destructuring for relevant hooks - fn fold_decl(&mut self, node: Decl) -> Decl { - let node = node.fold_children_with(self); - match node { - Decl::Var(box VarDecl { - decls, - span, - kind, - declare, - }) => { - let mut new_decls = Vec::with_capacity(decls.len()); - for decl in decls { - new_decls.push(self.get_decl(decl)); - } - - Decl::Var(Box::new(VarDecl { - decls: new_decls, - span, - kind, - declare, - })) - } - _ => node, - } - } -} - -impl HookOptimizer { - fn get_decl(&mut self, decl: VarDeclarator) -> VarDeclarator { - let VarDeclarator { - name, - init, - span, - definite, - } = &decl; - let init_clone = init.clone(); - if let Pat::Array(a) = name { - if let Expr::Call(c) = init.as_deref().unwrap() { - if let Callee::Expr(i) = &c.callee { - if let Expr::Ident(Ident { sym, .. }) = &**i { - if self.hooks.contains(sym) { - let name = get_object_pattern(a); - return VarDeclarator { - name, - init: init_clone, - span: *span, - definite: *definite, - }; - } - } - } - } - } - - decl - } -} - -fn get_object_pattern(array_pattern: &ArrayPat) -> Pat { - let props: Vec = array_pattern - .elems - .iter() - .enumerate() - .filter_map(|(i, elem)| { - elem.as_ref().map(|elem| { - ObjectPatProp::KeyValue(KeyValuePatProp { - key: PropName::Num(Number { - value: i as f64, - span: DUMMY_SP, - raw: None, - }), - value: Box::new(elem.clone()), - }) - }) - }) - .collect(); - - Pat::Object(ObjectPat { - props, - span: DUMMY_SP, - optional: false, - type_ann: None, - }) -} diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index e0a46f666fb30..0c3d6a4d4ccf1 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -50,7 +50,6 @@ use swc_core::{ pub mod amp_attributes; mod auto_cjs; pub mod disallow_re_export_all_in_page; -pub mod hook_optimizer; pub mod next_dynamic; pub mod next_font_loaders; pub mod next_ssg; @@ -167,7 +166,6 @@ where } else { Either::Right(noop()) }, - hook_optimizer::hook_optimizer(), match &opts.styled_components { Some(config) => Either::Left(styled_components::styled_components( file.name.clone(), diff --git a/test/unit/next-swc.test.ts b/test/unit/next-swc.test.ts index 22b92fbb68439..6cc5903eb70a9 100644 --- a/test/unit/next-swc.test.ts +++ b/test/unit/next-swc.test.ts @@ -10,7 +10,7 @@ const trim = (s) => s.join('\n').trim().replace(/^\s+/gm, '') describe('next/swc', () => { describe('hook_optimizer', () => { - it('should transform Array-destructured hook return values use object destructuring', async () => { + it('should leave alone array destructuring of hooks', async () => { const output = await swc( trim` import { useState } from 'react'; @@ -18,32 +18,95 @@ describe('next/swc', () => { ` ) - expect(output).toMatch(trim` - var ref = useState(0), count = ref[0], setCount = ref[1]; - `) - expect(output).toMatchInlineSnapshot(` -"import { useState } from \\"react\\"; -var ref = useState(0), count = ref[0], setCount = ref[1]; +"function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; + return arr2; +} +function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; +} +function _iterableToArrayLimit(arr, i) { + var _i = arr == null ? null : typeof Symbol !== \\"undefined\\" && arr[Symbol.iterator] || arr[\\"@@iterator\\"]; + if (_i == null) return; + var _arr = []; + var _n = true; + var _d = false; + var _s, _e; + try { + for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally{ + try { + if (!_n && _i[\\"return\\"] != null) _i[\\"return\\"](); + } finally{ + if (_d) throw _e; + } + } + return _arr; +} +function _nonIterableRest() { + throw new TypeError(\\"Invalid attempt to destructure non-iterable instance.\\\\\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\"); +} +function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} +function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === \\"string\\") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === \\"Object\\" && o.constructor) n = o.constructor.name; + if (n === \\"Map\\" || n === \\"Set\\") return Array.from(n); + if (n === \\"Arguments\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} +import { useState } from \\"react\\"; +var ref = _slicedToArray(useState(0), 2), count = ref[0], setCount = ref[1]; " `) }) - it('should be able to ignore some Array-destructured hook return values', async () => { + it('should leave alone array spread of hooks', async () => { const output = await swc( trim` import { useState } from 'react'; - const [, setCount] = useState(0); + const [...copy] = useState(0); ` ) - expect(output).toMatch(trim` - var ref = useState(0), setCount = ref[1]; - `) - expect(output).toMatchInlineSnapshot(` -"import { useState } from \\"react\\"; -var ref = useState(0), setCount = ref[1]; +"function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for(var i1 = 0, arr2 = new Array(len); i1 < len; i1++)arr2[i1] = arr[i1]; + return arr2; +} +function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; +} +function _iterableToArray(iter) { + if (typeof Symbol !== \\"undefined\\" && iter[Symbol.iterator] != null || iter[\\"@@iterator\\"] != null) return Array.from(iter); +} +function _nonIterableRest() { + throw new TypeError(\\"Invalid attempt to destructure non-iterable instance.\\\\\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\"); +} +function _toArray(arr) { + return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} +function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === \\"string\\") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === \\"Object\\" && o.constructor) n = o.constructor.name; + if (n === \\"Map\\" || n === \\"Set\\") return Array.from(n); + if (n === \\"Arguments\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} +import { useState } from \\"react\\"; +var ref = _toArray(useState(0)), copy = ref.slice(0); " `) })