Skip to content
Permalink
Browse files
fix(shaker): partial support for ts compiled code (fixes #820) (#836)
  • Loading branch information
Anber committed Sep 12, 2021
1 parent 7eb9d94 commit ec8ee684b6e90ead46295733ccd8cfefe4eaa04d

Some generated files are not rendered by default. Learn more.

@@ -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();
});
@@ -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);
}

@@ -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);
@@ -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;
}
@@ -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(

0 comments on commit ec8ee68

Please sign in to comment.