Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(proxy-compat): Add disable compat transform pragma #24

Merged
merged 2 commits into from
Feb 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions packages/babel-plugin-transform-proxy-compat/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# babel-plugin-transform-lwc-compat
# babel-plugin-transform-proxy-compat

This babel plugin enables Proxy usage in IE 11. In order to make this work, we have to rewrite property lookups, assignments, property deletions, function invocations, in statements and for in loops to use functions in order to execute the correct proxy handlers.

Expand All @@ -7,7 +7,7 @@ This package applies the following transformations:
* `foo.bar` -> `getKey(foo, 'bar')`
- setKey - property assignment
* `foo.bar = 'string'` -> `setKey(foo, 'bar', 'string)`
- callKey - function invokcation
- callKey - function invocation
* `foo.bar('argument')` -> `callKey(foo, 'bar', 'argument')`
- deleteKey - property deletion
* `delete foo.bar` -> `deleteKey(foo, 'bar')`
Expand Down Expand Up @@ -59,3 +59,15 @@ callKey(console, 'log', 'I am Compat');
import callKey from 'my-proxy-compat/callKey';
callKey(console, 'log', 'I am Compat');
```

## Disabling compat transform with comment

It's possible to disable the proxy compat transform for a specific file using the `/* proxy-compat-disable */` comment at the top of the file. This is an escape hatch for code that is performance sensitive and doesn't manipulate any Proxy. Make sure to use it extremely wisely.

```js
/* proxy-compat-disable */

function palindrome(str) {
return str === str.split('').reverse().join('');
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable */
/* proxy-compat-disable */

// console.log("x");
console.log("x");
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-disable */
/* proxy-compat-disable */

// console.log("x");
console.log("x");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* proxy-compat-disable */

// console.log("x");
console.log("x");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* proxy-compat-disable */

// console.log("x");
console.log("x");
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const path = require('path');
const babel = require('babel-core');
const proxyCompat = require('proxy-compat');

const transfromRaptorCompat = require('../index');
const transformRaptorCompat = require('../index');

const FIXTURE_DIR = path.join(__dirname, 'fixtures');
const FIXTURES = [
'key-operations',
'array-operations',
'intrinsics',
'no-compat-pragma',
'no-compat-pragma-multiple',
];

function unpad(src) {
Expand All @@ -25,22 +27,22 @@ test('Validate config property', () => {
expect(() => {
babel.transform('', {
plugins: [
[transfromRaptorCompat, {
[transformRaptorCompat, {
resolveProxyCompat: {
unknown: 'window.MyGlobalProxy'
}
}]
]
});
}).toThrow(
/Unexcepted resolveProxyCompat option/
/Unexpected resolveProxyCompat option/
);
});

test('APIs retrieval from global', () => {
const config = {
plugins: [
[transfromRaptorCompat, {
[transformRaptorCompat, {
resolveProxyCompat: {
global: 'window.MyGlobalProxy'
}
Expand All @@ -59,7 +61,7 @@ test('APIs retrieval from global', () => {
test('APIs retrieval from module', () => {
const config = {
plugins: [
[transfromRaptorCompat, {
[transformRaptorCompat, {
resolveProxyCompat: {
module: 'my-proxy-compat'
}
Expand All @@ -76,10 +78,10 @@ test('APIs retrieval from module', () => {
));
});

test('APIs retrieval from independant modules', () => {
test('APIs retrieval from independent modules', () => {
const config = {
plugins: [
[transfromRaptorCompat, {
[transformRaptorCompat, {
resolveProxyCompat: {
independent: 'my-proxy-compat'
}
Expand All @@ -95,6 +97,34 @@ test('APIs retrieval from independant modules', () => {
));
});

test('Should not stop other transforms if pragma is found', () => {
const reverseIdentifierPlugin = {
visitor: {
Identifier(path) {
path.node.name = path.node.name.split('').reverse().join('');
}
}
};

const config = {
plugins: [
transformRaptorCompat,
reverseIdentifierPlugin
]
};

const { code } = babel.transform(unpad(`
/* proxy-compat-disable */
console.log(foo.bar);
`), config);

expect(unpad(code)).toBe(unpad(`
/* proxy-compat-disable */
elosnoc.gol(oof.rab);
`));
});


describe('Compat transform fixtures', () => {
for (let fixture of FIXTURES) {
test(`${fixture}`, () => {
Expand All @@ -103,7 +133,7 @@ describe('Compat transform fixtures', () => {

const { code } = babel.transform(actual, {
plugins: [
[transfromRaptorCompat]
[transformRaptorCompat]
]
});

Expand Down
54 changes: 43 additions & 11 deletions packages/babel-plugin-transform-proxy-compat/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const ARRAY_OPERATIONS = {
CONCAT: 'compatConcat',
}

const PRAGMA_DISABLE = 'proxy-compat-disable';

const NO_TRANSFORM = Symbol('no-transform');

// List here: https://tc39.github.io/ecma262/#sec-well-known-intrinsic-objects
Expand Down Expand Up @@ -59,7 +61,7 @@ function compatPlugin({ types: t }) {
}

/**
* Retrieve the indentifier for a proxy-compat API.
* Retrieve the identifier for a proxy-compat API.
*/
function resolveCompatProxyImport(memberName, keysSeen) {
// Create a local identifier and register it
Expand Down Expand Up @@ -95,7 +97,7 @@ function compatPlugin({ types: t }) {
let { left, right, operator } = path.node;
let assignment, args;

// Skip assigments such as var a = 1;
// Skip assignments such as var a = 1;
if (!t.isMemberExpression(left)) {
return;
}
Expand Down Expand Up @@ -214,17 +216,17 @@ function compatPlugin({ types: t }) {
const { operator, left, right } = path.node;
if (operator === 'instanceof') {
const id = resolveCompatProxyImport(OBJECT_OPERATIONS.INSTANCEOF_KEY, this.keysSeen);
const warppedInOperator = t.callExpression(id, [left, right]);
path.replaceWith(warppedInOperator);
const wrappedInOperator = t.callExpression(id, [left, right]);
path.replaceWith(wrappedInOperator);
} else if (operator === 'in') {
const id = resolveCompatProxyImport(OBJECT_OPERATIONS.IN_KEY, this.keysSeen);
const warppedInOperator = t.callExpression(id, [right, left]);
path.replaceWith(warppedInOperator);
const wrappedInOperator = t.callExpression(id, [right, left]);
path.replaceWith(wrappedInOperator);
}
}
};

const intrisicMethodsVisitor = {
const intrinsicMethodsVisitor = {
/**
* Transforms:
* Array.prototype.push => Array.prototype.compatPush
Expand Down Expand Up @@ -296,22 +298,52 @@ function compatPlugin({ types: t }) {
const resolveProxyCompat = opts.module || opts.global || opts.independent;

if (resolveProxyCompat == undefined) {
throw new Error(`Unexcepted resolveProxyCompat option, expected property "module", "global" or "independent"`);
throw new Error(`Unexpected resolveProxyCompat option, expected property "module", "global" or "independent"`);
}

// Set to false if the file should not get the proxy compat transform
this.applyProxyCompatTransform = true;

// Object to record used proxy compat APIs
this.keysSeen = Object.create(null);
},

visitor: {
Program: {
/**
* Apply the compat transform on exit in order to ensure all the other transformations has been applied before
* Look for pragma to decide if the compat transform needs to be applied.
* Because other transforms can change the order of the statement at the top of the file (adding extra imports),
* the transform look for the present of the pragma not only at the top of the file but also in it's body.
*/
enter(path) {
for (let child of path.node.body) {
// Get all the comments for program children
const { leadingComments = [], trailingComments = [] } = child;
const comments = [...leadingComments, ...trailingComments];

for (const comment of comments) {
if (
comment.type === 'CommentBlock' &&
comment.value.trim() === PRAGMA_DISABLE
) {
this.applyProxyCompatTransform = false;
}
}
}
},

/**
* Apply the compat transform on exit in order to ensure all the other transformations has been applied before.
*/
exit(path, state) {
// Early exit if the file should not get transformed
if (!this.applyProxyCompatTransform) {
return;
}

// It's required to do the AST traversal in 2 times. The Array transformations before the Object transformations in
// order to ensure the transformations are applied in the right order.
path.traverse(intrisicMethodsVisitor, state);
path.traverse(intrinsicMethodsVisitor, state);
path.traverse(markerTransformVisitor, state);
path.traverse(objectTransformVisitor, state);

Expand Down Expand Up @@ -368,7 +400,7 @@ function compatPlugin({ types: t }) {
}

// We need to make sure babel doesn't visit the newly created variable declaration.
// Otherwise it will apply the proxy transform to the member expression to retrive the proxy APIs.
// Otherwise it will apply the proxy transform to the member expression to retrieve the proxy APIs.
path.stop();

// Finally add to the top of the file the API declarations
Expand Down