Skip to content

Commit c3597d3

Browse files
authored
Use Rollup and TypeScript for client scripts (DevExpress#4915)
1 parent 264a5d2 commit c3597d3

27 files changed

+121
-94
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ addons:
1010
language: node_js
1111
matrix:
1212
include:
13-
- node_js: "8"
13+
- node_js: "10"
1414
env: GULP_TASK="test-server"
1515
- node_js: "stable"
1616
env: GULP_TASK="test-server"

Gulpfile.js

+2-31
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const babel = require('babel-core');
21
const gulp = require('gulp');
32
const gulpStep = require('gulp-step');
43
const data = require('gulp-data');
@@ -8,7 +7,6 @@ const git = require('gulp-git');
87
const mocha = require('gulp-mocha-simple');
98
const mustache = require('gulp-mustache');
109
const rename = require('gulp-rename');
11-
const webmake = require('gulp-webmake');
1210
const uglify = require('gulp-uglify');
1311
const ll = require('gulp-ll-next');
1412
const clone = require('gulp-clone');
@@ -263,35 +261,8 @@ gulp.step('ts-defs', async () => {
263261
});
264262

265263
gulp.step('client-scripts-bundle', () => {
266-
return gulp
267-
.src([
268-
'src/client/core/index.js',
269-
'src/client/driver/index.js',
270-
'src/client/ui/index.js',
271-
'src/client/automation/index.js',
272-
'src/client/browser/idle-page/index.js'
273-
], { base: 'src' })
274-
.pipe(webmake({
275-
sourceMap: false,
276-
transform: (filename, code) => {
277-
const transformed = babel.transform(code, {
278-
sourceMap: false,
279-
ast: false,
280-
filename: filename,
281-
282-
// NOTE: force usage of client .babelrc for all
283-
// files, regardless of their location
284-
babelrc: false,
285-
extends: path.join(__dirname, './src/client/.babelrc')
286-
});
287-
288-
// HACK: babel-plugin-transform-es2015-modules-commonjs forces
289-
// 'use strict' insertion. We need to remove it manually because
290-
// of https://github.com/DevExpress/testcafe/issues/258
291-
return { code: transformed.code.replace(/^('|")use strict('|");?/, '') };
292-
}
293-
}))
294-
.pipe(gulp.dest('lib'));
264+
return childProcess
265+
.spawn('rollup -c', { shell: true, stdio: 'inherit', cwd: path.join(__dirname, 'src/client') });
295266
});
296267

297268
gulp.step('client-scripts-templates-render', () => {

package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@
131131
"devDependencies": {
132132
"@belym.a.2105/broken-link-checker": "^0.7.9",
133133
"@ffmpeg-installer/ffmpeg": "^1.0.17",
134+
"@rollup/plugin-commonjs": "^11.0.2",
135+
"@rollup/plugin-node-resolve": "^7.1.1",
134136
"@types/chai": "^3.5.2",
135137
"@types/debug": "^4.1.5",
136138
"@types/dedent": "^0.7.0",
@@ -165,17 +167,14 @@
165167
"gulp-git": "^2.4.2",
166168
"gulp-less": "^4.0.0",
167169
"gulp-ll-next": "^2.1.0",
168-
"gulp-mocha": "^5.0.0",
169170
"gulp-mocha-simple": "^1.0.0",
170171
"gulp-mustache": "^3.0.1",
171172
"gulp-prompt": "^1.0.1",
172173
"gulp-qunit-harness": "^1.0.2",
173174
"gulp-rename": "^1.3.0",
174-
"gulp-sourcemaps": "^2.6.4",
175175
"gulp-step": "^1.0.1",
176176
"gulp-uglify": "^3.0.0",
177177
"gulp-util": "^3.0.7",
178-
"gulp-webmake": "0.0.4",
179178
"js-yaml": "^3.6.1",
180179
"license-checker": "^20.0.0",
181180
"markdownlint": "^0.19.0",
@@ -189,6 +188,8 @@
189188
"publish-please": "^5.5.1",
190189
"recursive-copy": "^2.0.5",
191190
"request": "^2.58.0",
191+
"rollup": "^2.2.0",
192+
"rollup-plugin-typescript2": "^0.26.0",
192193
"run-sequence": "^1.2.2",
193194
"saucelabs-connector": "^0.2.0",
194195
"semver": "^5.6.0",
@@ -197,6 +198,6 @@
197198
"stack-chain": "^2.0.0",
198199
"strip-ansi": "^3.0.0",
199200
"testcafe-browser-provider-browserstack": "1.13.0-alpha.1",
200-
"webmake": "0.3.42"
201+
"tslib": "^1.11.1"
201202
}
202203
}

src/client/.babelrc

-15
This file was deleted.

src/client/.eslintrc

+1-5
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,5 @@
1515
},
1616
"plugins": [
1717
"hammerhead"
18-
],
19-
"globals": {
20-
"exports": true,
21-
"require": true
22-
}
18+
]
2319
}
+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%hammerhead%'];
1+
export * from 'hammerhead';
2+
export { default } from 'hammerhead';
+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%testCafeCore%'];
1+
export * from 'testcafe-core';
2+
export { default } from 'testcafe-core';
+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%testCafeUI%'];
1+
export * from 'testcafe-ui';
2+
export { default } from 'testcafe-ui';

src/client/automation/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import ERROR_TYPES from './errors';
2727
import cursor from './cursor';
2828

2929

30+
const exports = {};
31+
3032
exports.Scroll = ScrollAutomation;
3133
exports.Click = ClickAutomation;
3234
exports.SelectChildClick = SelectChildClickAutomation;

src/client/automation/playback/press/utils.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import hammerhead from '../../deps/hammerhead';
2-
import { KEY_MAPS, domUtils } from '../../deps/testcafe-core';
2+
import { KEY_MAPS, domUtils, arrayUtils } from '../../deps/testcafe-core';
33
import isLetter from '../../utils/is-letter';
4-
import { findDocument, isRadioButtonElement, getActiveElement } from '../../../core/utils/dom';
5-
import * as arrayUtils from '../../../core/utils/array';
4+
65

76
const nativeMethods = hammerhead.nativeMethods;
87
const browserUtils = hammerhead.utils.browser;
98
const focusBlurSandbox = hammerhead.eventSandbox.focusBlur;
109
const Promise = hammerhead.Promise;
1110

11+
const { findDocument, isRadioButtonElement, getActiveElement } = domUtils;
12+
1213
export function changeLetterCase (letter) {
1314
const isLowCase = letter === letter.toLowerCase();
1415

src/client/browser/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ let heartbeatIntervalId = null;
1313
const noop = () => void 0;
1414
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
1515

16+
const evaluate = eval; // eslint-disable-line no-eval
17+
1618
const FETCH_PAGE_TO_CACHE_RETRY_DELAY = 300;
1719
const FETCH_PAGE_TO_CACHE_RETRY_COUNT = 5;
1820

@@ -86,9 +88,7 @@ function executeInitScript (initScriptUrl, createXHR) {
8688
if (!res.code)
8789
return null;
8890

89-
/* eslint-disable no-eval, no-restricted-globals*/
90-
return sendXHR(initScriptUrl, createXHR, { method: 'POST', data: JSON.stringify(eval(res.code)) });
91-
/* eslint-enable no-eval, no-restricted-globals */
91+
return sendXHR(initScriptUrl, createXHR, { method: 'POST', data: JSON.stringify(evaluate(res.code)) }); //eslint-disable-line no-restricted-globals
9292
})
9393
.then(() => {
9494
window.setTimeout(() => executeInitScript(initScriptUrl, createXHR), 1000);

src/client/core/deps/hammerhead.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%hammerhead%'];
1+
export * from 'hammerhead';
2+
export { default } from 'hammerhead';

src/client/core/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import * as browser from '../browser';
3030
import selectorTextFilter from '../../client-functions/selectors/selector-text-filter';
3131
import selectorAttributeFilter from '../../client-functions/selectors/selector-attribute-filter';
3232

33+
const exports = {};
34+
3335
exports.RequestBarrier = RequestBarrier;
3436
exports.pageUnloadBarrier = pageUnloadBarrier;
3537
exports.preventRealEvents = preventRealEvents;
@@ -60,8 +62,6 @@ exports.browser = browser;
6062
exports.selectorTextFilter = selectorTextFilter;
6163
exports.selectorAttributeFilter = selectorAttributeFilter;
6264

63-
exports.get = require;
64-
6565
const nativeMethods = hammerhead.nativeMethods;
6666
const evalIframeScript = hammerhead.EVENTS.evalIframeScript;
6767

src/client/core/utils/content-editable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as domUtils from './dom';
22
import * as arrayUtils from './array';
33
import * as styleUtils from './style';
4-
import { nativeMethods } from '../../driver/deps/hammerhead';
4+
import { nativeMethods } from '../deps/hammerhead';
55

66
//nodes utils
77
function getOwnFirstVisibleTextNode (el) {

src/client/core/utils/dom.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import hammerhead from '../deps/hammerhead';
2-
import * as styleUtils from './style';
32
import * as arrayUtils from './array';
43

54
const browserUtils = hammerhead.utils.browser;
65
const nativeMethods = hammerhead.nativeMethods;
76

7+
// NOTE: We have to retrieve styleUtils.get from hammerhead
8+
// to avoid circular dependencies between domUtils and styleUtils
9+
const getElementStyleProperty = hammerhead.utils.style.get;
10+
811
export const getActiveElement = hammerhead.utils.dom.getActiveElement;
912
export const findDocument = hammerhead.utils.dom.findDocument;
1013
export const isElementInDocument = hammerhead.utils.dom.isElementInDocument;
@@ -176,7 +179,7 @@ export function getFocusableElements (doc, sort = false) {
176179
if (element.disabled)
177180
continue;
178181

179-
if (styleUtils.get(element, 'display') === 'none' || styleUtils.get(element, 'visibility') === 'hidden')
182+
if (getElementStyleProperty(element, 'display') === 'none' || getElementStyleProperty(element, 'visibility') === 'hidden')
180183
continue;
181184

182185
if ((browserUtils.isIE || browserUtils.isAndroid) && isOptionElement(element))
@@ -217,7 +220,7 @@ function getInvisibleElements (elements) {
217220
const invisibleElements = [];
218221

219222
for (let i = 0; i < elements.length; i++) {
220-
if (styleUtils.get(elements[i], 'display') === 'none')
223+
if (getElementStyleProperty(elements[i], 'display') === 'none')
221224
invisibleElements.push(elements[i]);
222225
}
223226

Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
import hammerhead from '../../deps/hammerhead';
1+
import { Promise, nativeMethods } from '../../deps/hammerhead';
22

3-
// NOTE: expose Promise to the function code
4-
/* eslint-disable @typescript-eslint/no-unused-vars */
5-
const Promise = hammerhead.Promise;
6-
/* eslint-enable @typescript-eslint/no-unused-vars */
73

84
// NOTE: evalFunction is isolated into a separate module to
95
// restrict access to TestCafe intrinsics for the evaluated code.
106
// It also accepts `__dependencies$` argument which may be used by evaluated code.
117
/* eslint-disable @typescript-eslint/no-unused-vars */
128
export default function evalFunction (fnCode, __dependencies$) {
139
// NOTE: `eval` in strict mode will not override context variables
14-
'use strict';
10+
const evaluator = new nativeMethods.Function('fnCode', '__dependencies$', 'Promise', '"use strict"; return eval(fnCode)');
1511

16-
/* eslint-disable no-eval */
17-
return eval(fnCode);
18-
/* eslint-enable no-eval */
12+
return evaluator(fnCode, __dependencies$, Promise);
1913
}
2014
/* eslint-enable @typescript-eslint/no-unused-vars */

src/client/driver/command-executors/client-functions/replicator.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ export class FunctionTransform {
3434
return '';
3535
}
3636

37-
fromSerializable ({ fnCode, dependencies }) {
37+
// HACK: UglifyJS + TypeScript + argument destructuring can generate incorrect code.
38+
// So we have to use plain assignments here.
39+
fromSerializable (opts) {
40+
const fnCode = opts.fnCode;
41+
const dependencies = opts.dependencies;
42+
3843
return evalFunction(fnCode, dependencies);
3944
}
4045
}

src/client/driver/deps/hammerhead.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%hammerhead%'];
1+
export * from 'hammerhead';
2+
export { default } from 'hammerhead';
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%testCafeAutomation%'];
1+
export * from 'testcafe-automation';
2+
export { default } from 'testcafe-automation';
+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%testCafeCore%'];
1+
export * from 'testcafe-core';
2+
export { default } from 'testcafe-core';

src/client/driver/deps/testcafe-ui.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export default window['%testCafeUI%'];
1+
export * from 'testcafe-ui';
2+
export { default } from 'testcafe-ui';

src/client/driver/driver.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import {
1414
browser
1515
} from './deps/testcafe-core';
1616

17+
import { cursor } from './deps/testcafe-automation';
18+
19+
import { StatusBar } from './deps/testcafe-ui';
20+
1721
import {
1822
CHECK_IFRAME_DRIVER_LINK_DELAY,
1923
SEND_STATUS_REQUEST_TIME_LIMIT,
@@ -23,8 +27,6 @@ import {
2327
CHECK_CHILD_WINDOW_DRIVER_LINK_DELAY
2428
} from '../../utils/browser-connection-timeouts';
2529

26-
import { StatusBar } from './deps/testcafe-ui';
27-
2830
import TEST_RUN_MESSAGES from '../../test-run/client-messages';
2931
import COMMAND_TYPE from '../../test-run/commands/type';
3032
import {
@@ -72,7 +74,6 @@ import ClientFunctionExecutor from './command-executors/client-functions/client-
7274
import ChildWindowDriverLink from './driver-link/window/child';
7375
import ParentWindowDriverLink from './driver-link/window/parent';
7476
import sendConfirmationMessage from './driver-link/send-confirmation-message';
75-
import cursor from '../automation/cursor';
7677
import DriverRole from './role';
7778
import { CHECK_CHILD_WINDOW_CLOSED_INTERVAL } from './driver-link/timeouts';
7879

src/client/rollup.config.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-env node */
2+
/* eslint-disable no-restricted-globals */
3+
4+
import path from 'path';
5+
import typescript from 'rollup-plugin-typescript2';
6+
import commonjs from '@rollup/plugin-commonjs';
7+
import resolve from '@rollup/plugin-node-resolve';
8+
9+
10+
const CHUNK_NAMES = [
11+
'core/index.js',
12+
'driver/index.js',
13+
'ui/index.js',
14+
'automation/index.js',
15+
'browser/idle-page/index.js'
16+
];
17+
18+
const TARGET_DIR = '../../lib/client';
19+
20+
const GLOBALS = {
21+
'hammerhead': 'window[\'%hammerhead%\']',
22+
'testcafe-automation': 'window[\'%testCafeAutomation%\']',
23+
'testcafe-core': 'window[\'%testCafeCore%\']',
24+
'testcafe-ui': 'window[\'%testCafeUI%\']'
25+
};
26+
27+
const CONFIG = CHUNK_NAMES.map(chunk => ({
28+
input: chunk,
29+
external: Object.keys(GLOBALS),
30+
31+
output: {
32+
file: path.join(TARGET_DIR, chunk),
33+
format: 'iife',
34+
globals: GLOBALS,
35+
36+
// NOTE: 'use strict' in our scripts can break user code
37+
// https://github.com/DevExpress/testcafe/issues/258
38+
strict: false
39+
},
40+
41+
plugins: [
42+
typescript({ include: ['*.+(j|t)s', '**/*.+(j|t)s', '../**/*.+(j|t)s'] }),
43+
commonjs(),
44+
resolve()
45+
]
46+
}));
47+
48+
export default CONFIG;

src/client/tsconfig.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES5",
4+
"module": "ES6",
5+
"strict": true,
6+
"esModuleInterop": true,
7+
"allowJs": true,
8+
"checkJs": false,
9+
"types": []
10+
}
11+
}

0 commit comments

Comments
 (0)