From ec8ee684b6e90ead46295733ccd8cfefe4eaa04d Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Sun, 12 Sep 2021 16:30:00 +0300 Subject: [PATCH] fix(shaker): partial support for ts compiled code (fixes #820) (#836) --- .../__snapshots__/shaker.test.ts.snap | 12 ---- .../babel/__tests__/evaluators/shaker.test.ts | 16 ----- packages/shaker/src/DepsGraph.ts | 6 ++ packages/shaker/src/graphBuilder.ts | 43 ++++++++---- packages/shaker/src/langs/core.ts | 65 +++++++++++++++++++ packages/shaker/src/scope.ts | 4 +- 6 files changed, 104 insertions(+), 42 deletions(-) diff --git a/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap b/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap index 92b9b9efd..b7f70d660 100644 --- a/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap +++ b/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap @@ -65,18 +65,6 @@ color.green = '#0f0'; exports.__linariaPreval = [color];" `; -exports[`keeps only the last assignment of each exported variable 1`] = ` -"\\"use strict\\"; - -var bar = function bar() { - return 'hello world'; -}; - -exports.bar = bar; -var foo = exports.bar(); -exports.__linariaPreval = [foo];" -`; - exports[`keeps reused exports 1`] = ` "\\"use strict\\"; diff --git a/packages/babel/__tests__/evaluators/shaker.test.ts b/packages/babel/__tests__/evaluators/shaker.test.ts index 1208065a8..9dc6b58d7 100644 --- a/packages/babel/__tests__/evaluators/shaker.test.ts +++ b/packages/babel/__tests__/evaluators/shaker.test.ts @@ -241,19 +241,3 @@ it('keeps reused exports', () => { expect(shaken).toMatchSnapshot(); }); - -it('keeps only the last assignment of each exported variable', () => { - const [shaken] = _shake()` - const bar = function() { - return 'hello world'; - }; - - exports.bar = "bar"; - exports.bar = bar; - - const foo = exports.bar(); - exports.__linariaPreval = [foo]; - `; - - expect(shaken).toMatchSnapshot(); -}); diff --git a/packages/shaker/src/DepsGraph.ts b/packages/shaker/src/DepsGraph.ts index 58d9eae3a..d4ee5c129 100644 --- a/packages/shaker/src/DepsGraph.ts +++ b/packages/shaker/src/DepsGraph.ts @@ -77,6 +77,12 @@ export default class DepsGraph { } addExport(name: string, node: t.Node) { + const existed = this.exports.get(name); + if (existed) { + // Sometimes export can be defined more than once and in that case we have to keep all export statements + this.addEdge(node, existed); + } + this.exports.set(name, node); } diff --git a/packages/shaker/src/graphBuilder.ts b/packages/shaker/src/graphBuilder.ts index 6b167e845..eef842172 100644 --- a/packages/shaker/src/graphBuilder.ts +++ b/packages/shaker/src/graphBuilder.ts @@ -22,21 +22,23 @@ class GraphBuilder extends GraphBuilderState { } private isExportsIdentifier(node: Node) { - if ( - t.isIdentifier(node) && - this.scope.getDeclaration(node) === ScopeManager.globalExportsIdentifier - ) { - return true; + if (t.isIdentifier(node)) { + return ( + this.scope.getDeclaration(node) === ScopeManager.globalExportsIdentifier + ); } - return ( - t.isMemberExpression(node) && - t.isIdentifier(node.property) && - node.property.name === 'exports' && - t.isIdentifier(node.object) && - this.scope.getDeclaration(node.object) === - ScopeManager.globalModuleIdentifier - ); + if (t.isMemberExpression(node)) { + return ( + t.isIdentifier(node.property) && + node.property.name === 'exports' && + t.isIdentifier(node.object) && + this.scope.getDeclaration(node.object) === + ScopeManager.globalModuleIdentifier + ); + } + + return false; } private isExportsAssigment(node: Node): node is AssignmentExpression { @@ -59,6 +61,17 @@ class GraphBuilder extends GraphBuilderState { return false; } + private isTSExporterCall( + node: Node + ): node is t.CallExpression & { arguments: [t.StringLiteral, t.Identifier] } { + if (!t.isCallExpression(node) || node.arguments.length !== 2) { + return false; + } + + // FIXME: more precisely check + return !(!t.isIdentifier(node.callee) || node.callee.name !== 'exporter'); + } + /* * Implements a default behaviour for AST-nodes: * • visits every child; @@ -158,6 +171,10 @@ class GraphBuilder extends GraphBuilderState { ); } } + } else if (this.isTSExporterCall(node)) { + const [name, identifier] = node.arguments; + this.graph.addExport(name.value, node); + this.graph.addEdge(node, identifier); } const isScopable = t.isScopable(node); diff --git a/packages/shaker/src/langs/core.ts b/packages/shaker/src/langs/core.ts index c0368bba1..8381c0f83 100644 --- a/packages/shaker/src/langs/core.ts +++ b/packages/shaker/src/langs/core.ts @@ -3,6 +3,7 @@ import type { AssignmentExpression, Block, CallExpression, + CatchClause, Directive, ExpressionStatement, ForInStatement, @@ -75,6 +76,48 @@ function getCallee(node: CallExpression): Node { return node.callee; } +function isTSLib(node: t.Node, scope: ScopeManager) { + if (!t.isIdentifier(node)) { + return false; + } + + const declaration = scope.getDeclaration(node); + return t.isIdentifier(declaration) && declaration.name === 'tslib_1'; +} + +function isTSReexport( + node: t.Node, + scope: ScopeManager +): node is t.Node & { callee: { object: t.Identifier } } { + if (!t.isCallExpression(node)) { + return false; + } + + const { + callee, + arguments: [, exportsIdentifier], + } = node; + if ( + !t.isIdentifier(exportsIdentifier) || + exportsIdentifier.name !== 'exports' || + scope.getDeclaration(exportsIdentifier) !== + ScopeManager.globalExportsIdentifier + ) { + return false; + } + + if (!t.isMemberExpression(callee)) { + return false; + } + + const { object, property } = callee; + if (!t.isIdentifier(property) || property.name !== '__exportStar') { + return false; + } + + return isTSLib(object, scope); +} + function findWildcardReexportStatement( node: t.CallExpression, identifierName: string, @@ -197,6 +240,10 @@ export const visitors: Visitors = { // keep function name in expressions like `const a = function a();` this.graph.addEdge(node, node.id); } + + if (t.isFunctionDeclaration(node) && node.id) { + this.graph.addEdge(node, node.id); + } }, /* @@ -258,6 +305,12 @@ export const visitors: Visitors = { }); }, + CatchClause(this: GraphBuilderState, node: CatchClause) { + this.baseVisit(node); + + this.graph.addEdge(node, node.body); + }, + IfStatement(this: GraphBuilderState, node: IfStatement) { this.baseVisit(node); this.graph.addEdge(node, node.consequent); @@ -519,6 +572,18 @@ export const visitors: Visitors = { [Identifier, Identifier | null] >; if (!declared) { + // Is it a ts reexport? + // tslib_1.__exportStar(require("./Async"), exports); + if (parent && isTSReexport(parent, this.scope)) { + if (!this.graph.imports.has(source)) { + this.graph.imports.set(source, []); + } + + this.graph.addEdge(parent.callee.object, parent); + this.graph.reexports.push(parent.callee.object); + this.graph.importTypes.set(source, 'reexport'); + } + // This is a standalone `require` return; } diff --git a/packages/shaker/src/scope.ts b/packages/shaker/src/scope.ts index 2b496b0f4..3ec87944a 100644 --- a/packages/shaker/src/scope.ts +++ b/packages/shaker/src/scope.ts @@ -56,6 +56,8 @@ const getId = (scope: Scope, identifier: t.Identifier | string): string => { }`; }; +const globalIdentifiers = new Set(['exports', 'module']); + export default class ScopeManager { public static globalExportsIdentifier = t.identifier('exports'); public static globalModuleIdentifier = t.identifier('module'); @@ -125,7 +127,7 @@ export default class ScopeManager { const scope = this.stack .slice(stack) .find((s) => !isHoistable || functionScopes.has(s))!; - if (this.global.has(idName)) { + if (this.global.has(idName) && !globalIdentifiers.has(idName)) { // It's probably a declaration of a previous referenced identifier // Let's use naïve implementation of hoisting const promise = this.declarations.get(