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: use happy-dom in module #1327

Merged
merged 3 commits into from
Aug 18, 2023
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
6 changes: 6 additions & 0 deletions .changeset/polite-pillows-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@linaria/babel-preset": patch
"@linaria/testkit": patch
---

feat: use happy-dom in module
1 change: 1 addition & 0 deletions packages/babel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@linaria/tags": "workspace:^",
"@linaria/utils": "workspace:^",
"cosmiconfig": "^8.0.0",
"happy-dom": "10.8.0",
"source-map": "^0.7.3",
"stylis": "^3.5.4"
},
Expand Down
14 changes: 4 additions & 10 deletions packages/babel/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type { StrictOptions } from '@linaria/utils';
import { getFileIdx } from '@linaria/utils';

import { TransformCacheCollection } from './cache';
import * as process from './process';
import createVmContext from './vm/createVmContext';

type HiddenModuleMembers = {
_extensions: { [key: string]: () => void };
Expand Down Expand Up @@ -478,15 +478,7 @@ class Module {
return;
}

const context = vm.createContext({
clearImmediate: NOOP,
clearInterval: NOOP,
clearTimeout: NOOP,
setImmediate: NOOP,
setInterval: NOOP,
setTimeout: NOOP,
global,
process,
const { context, teardown } = createVmContext({
module: this,
exports: this.#exports,
require: this.require,
Expand Down Expand Up @@ -523,6 +515,8 @@ class Module {
throw new EvalError(
`${(e as Error).message} in${callstack.join('\n| ')}\n`
);
} finally {
teardown();
}
}
}
Expand Down
64 changes: 64 additions & 0 deletions packages/babel/src/vm/createVmContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as vm from 'vm';

import { Window } from 'happy-dom';

import * as process from './process';

const NOOP = () => {};

function createWindow(): Window {
const win = new Window();

// TODO: browser doesn't expose Buffer, but a lot of dependencies use it
win.Buffer = Buffer;
win.Uint8Array = Uint8Array;

return win;
}

function createBaseContext(
win: Window,
additionalContext: Partial<vm.Context>
): Partial<vm.Context> {
const baseContext: vm.Context = win;

baseContext.document = win.document;
baseContext.window = win;
baseContext.self = win;
baseContext.top = win;
baseContext.parent = win;
baseContext.global = win;

baseContext.process = process;

baseContext.clearImmediate = NOOP;
baseContext.clearInterval = NOOP;
baseContext.clearTimeout = NOOP;
baseContext.setImmediate = NOOP;
baseContext.requestAnimationFrame = NOOP;
baseContext.setInterval = NOOP;
baseContext.setTimeout = NOOP;

// eslint-disable-next-line
for (const key in additionalContext) {
baseContext[key] = additionalContext[key];
}

return baseContext;
}

function createVmContext(additionalContext: Partial<vm.Context>) {
const window = createWindow();
const baseContext = createBaseContext(window, additionalContext);

const context = vm.createContext(baseContext);

return {
context,
teardown: () => {
window.happyDOM.cancelAsync();
},
};
}

export default createVmContext;
File renamed without changes.
201 changes: 129 additions & 72 deletions packages/testkit/src/module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,78 +244,6 @@ it('has require.ensure available', () => {
).not.toThrow();
});

it('has __filename available', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
module.exports = __filename;
`);

expect(mod.exports).toBe(mod.filename);
});

it('has __dirname available', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
module.exports = __dirname;
`);

expect(mod.exports).toBe(path.dirname(mod.filename));
});

it('has setTimeout, clearTimeout available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setTimeout(() => {
console.log('test');
},0);

clearTimeout(x);
`)
).not.toThrow();
});

it('has setInterval, clearInterval available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setInterval(() => {
console.log('test');
}, 1000);

clearInterval(x);
`)
).not.toThrow();
});

it('has setImmediate, clearImmediate available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setImmediate(() => {
console.log('test');
});

clearImmediate(x);
`)
).not.toThrow();
});

it('has global objects available without referencing global', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = new Set();
`)
).not.toThrow();
});

it('changes resolve behaviour on overriding _resolveFilename', () => {
const resolveFilename = jest
.spyOn(DefaultModuleImplementation, '_resolveFilename')
Expand Down Expand Up @@ -385,3 +313,132 @@ it('export * compiled by typescript to commonjs works', () => {

expect(mod.exports).toBe('foo');
});

describe('globals', () => {
it('has setTimeout, clearTimeout available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setTimeout(() => {
console.log('test');
},0);

clearTimeout(x);
`)
).not.toThrow();
});

it('has setInterval, clearInterval available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setInterval(() => {
console.log('test');
}, 1000);

clearInterval(x);
`)
).not.toThrow();
});

it('has setImmediate, clearImmediate available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const x = setImmediate(() => {
console.log('test');
});

clearImmediate(x);
`)
).not.toThrow();
});

it('has global objects available without referencing global', () => {
const mod = new Module(getFileName(), '*', options);

expect(() => mod.evaluate(dedent`const x = new Set();`)).not.toThrow();
});
});

describe('definable globals', () => {
it('has __filename available', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
module.exports = __filename;
`);

expect(mod.exports).toBe(mod.filename);
});

it('has __dirname available', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
module.exports = __dirname;
`);

expect(mod.exports).toBe(path.dirname(mod.filename));
});
});

describe('DOM', () => {
it('should have DOM globals available', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
module.exports = {
document: typeof document,
window: typeof window,
global: typeof global,
};
`);

expect(mod.exports).toEqual({
document: 'object',
window: 'object',
global: 'object',
});
});

it('should have DOM APIs available', () => {
const mod = new Module(getFileName(), '*', options);

expect(() =>
mod.evaluate(dedent`
const handler = () => {}

document.addEventListener('click', handler);
document.removeEventListener('click', handler);

window.addEventListener('click', handler);
window.removeEventListener('click', handler);
`)
).not.toThrow();
});

it('supports DOM manipulations', () => {
const mod = new Module(getFileName(), '*', options);

mod.evaluate(dedent`
const el = document.createElement('div');
el.setAttribute('id', 'test');

document.body.appendChild(el);

module.exports = {
html: document.body.innerHTML,
tagName: el.tagName.toLowerCase()
};
`);

expect(mod.exports).toEqual({
html: '<div id="test"></div>',
tagName: 'div',
});
});
});
Loading
Loading