├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /dist 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !/dist 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.2 (2020.10.22) 2 | 3 | * feat: add `esbuildBuildFunction` option to resolve esbuild from the outside for yarn berry support 4 | 5 | # 1.0.1 (2020.10.22) 6 | 7 | * feat: support all settings from esbuild 8 | 9 | # 1.0.0 (2020.10.22) 10 | 11 | * initial release 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cypress-esbuild-preprocessor 2 | 3 | Uses https://github.com/evanw/esbuild to bundle your specs. Around 50x faster than a webpack + babel/typescript based preprocessor. 4 | 5 | ## Usage 6 | 7 | Install via 8 | 9 | ```bash 10 | npm install -D esbuild cypress-esbuild-preprocessor 11 | ``` 12 | 13 | Use in your `cypress/plugins/index.js` 14 | 15 | ```javascript 16 | const {cypressEsbuildPreprocessor} = require('cypress-esbuild-preprocessor'); 17 | const path = require('path'); 18 | 19 | module.exports = (on, config) => { 20 | on( 21 | 'file:preprocessor', 22 | cypressEsbuildPreprocessor({ 23 | esbuildOptions: { 24 | // optional tsconfig for typescript support and path mapping (see https://github.com/evanw/esbuild for all options) 25 | tsconfig: path.resolve(__dirname, '../../tsconfig.json'), 26 | }, 27 | }), 28 | ); 29 | }; 30 | ``` 31 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-esbuild-preprocessor", 3 | "version": "1.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "14.14.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz", 10 | "integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg==", 11 | "dev": true 12 | }, 13 | "esbuild": { 14 | "version": "0.7.19", 15 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.7.19.tgz", 16 | "integrity": "sha512-0Ur8ZtuRPwJMj4+VjRLqn5z88WXf+2etZhe4dBA6eYFcdviQefb+Vrd59cTk0VXg08NU/BnAMkalCMHI8lig/A==", 17 | "dev": true 18 | }, 19 | "prettier": { 20 | "version": "2.1.2", 21 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", 22 | "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", 23 | "dev": true 24 | }, 25 | "typescript": { 26 | "version": "4.0.3", 27 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", 28 | "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", 29 | "dev": true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-esbuild-preprocessor", 3 | "version": "1.0.2", 4 | "description": "use esbuild as a preprocessor for cypress", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "build": "tsc", 13 | "prepublish": "npm run build" 14 | }, 15 | "keywords": [ 16 | "cypress", 17 | "preprocessor", 18 | "esbuild" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/sod/cypress-esbuild-preprocessor" 23 | }, 24 | "author": "alexvonweiss@googlemail.com", 25 | "license": "ISC", 26 | "devDependencies": { 27 | "@types/node": "^14.14.2", 28 | "esbuild": "^0.7.19", 29 | "prettier": "^2.1.2", 30 | "typescript": "^4.0.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import type { FSWatcher } from "fs"; 4 | import type { BuildOptions, BuildResult } from "esbuild"; 5 | 6 | const bundles = new Map>(); 7 | const watching = new Set(); 8 | 9 | export interface CypressFileObject { 10 | filePath: string; 11 | outputPath: string; 12 | shouldWatch: boolean; 13 | } 14 | 15 | export type CypressPlugin = ( 16 | file: CypressFileObject & NodeJS.EventEmitter 17 | ) => Promise; 18 | 19 | export interface CypressPreprocessorOptions { 20 | additionalEntries?: string[]; 21 | } 22 | 23 | export interface CallbackPreprocessorAdapterOptions { 24 | entryPoints: string[]; 25 | outfile: string; 26 | } 27 | 28 | export interface CallbackPreprocessorAdapter { 29 | build: (options: CallbackPreprocessorAdapterOptions) => Promise; 30 | options: CypressPreprocessorOptions; 31 | } 32 | 33 | export interface EsbuildPreprocessorAdapterOptions 34 | extends CypressPreprocessorOptions { 35 | esbuildOptions?: BuildOptions; 36 | esbuildBuildFunction?: (options: BuildOptions) => Promise; 37 | } 38 | 39 | export function esbuildPreprocessorAdapter( 40 | options?: EsbuildPreprocessorAdapterOptions 41 | ): CallbackPreprocessorAdapter { 42 | const build = options?.esbuildBuildFunction ?? require("esbuild").build; 43 | 44 | return { 45 | build({ entryPoints, outfile }): Promise { 46 | return build({ 47 | entryPoints, 48 | outfile, 49 | resolveExtensions: [".ts", ".js", ".mjs", ".json"], 50 | minify: false, 51 | bundle: true, 52 | ...(options?.esbuildOptions ?? {}), 53 | }).then(() => undefined); 54 | }, 55 | options: options ?? {}, 56 | }; 57 | } 58 | 59 | export function cypressPreprocessor( 60 | adapter: CallbackPreprocessorAdapter 61 | ): CypressPlugin { 62 | return (file) => { 63 | const filePath = file.filePath; 64 | let promise = bundles.get(filePath); 65 | 66 | if (promise) { 67 | return promise; 68 | } 69 | 70 | const outfile = 71 | path.extname(file.outputPath) === ".js" 72 | ? file.outputPath 73 | : `${file.outputPath}.js`; 74 | const entryPoints = [filePath].concat( 75 | adapter.options.additionalEntries || [] 76 | ); 77 | 78 | if (file.shouldWatch) { 79 | watch(filePath, { 80 | onInit: (watcher) => file.on("close", () => watcher.close()), 81 | onChange: () => file.emit("rerun"), 82 | }); 83 | } 84 | 85 | promise = adapter 86 | .build({ entryPoints, outfile }) 87 | .then(() => { 88 | bundles.delete(filePath); 89 | return outfile; 90 | }) 91 | .catch((error) => { 92 | bundles.delete(filePath); 93 | throw error; 94 | }); 95 | 96 | bundles.set(filePath, promise); 97 | 98 | return promise; 99 | }; 100 | } 101 | 102 | export function cypressEsbuildPreprocessor( 103 | options?: EsbuildPreprocessorAdapterOptions 104 | ): CypressPlugin { 105 | return cypressPreprocessor(esbuildPreprocessorAdapter(options)); 106 | } 107 | 108 | function watch( 109 | file: string, 110 | callback: { onChange: () => void; onInit: (emitter: FSWatcher) => void } 111 | ) { 112 | if (watching.has(file)) { 113 | return; 114 | } 115 | 116 | const emitter = fs.watch(file, { encoding: null }, (event) => { 117 | if (event === "change") { 118 | callback.onChange(); 119 | } 120 | }); 121 | 122 | callback.onInit(emitter); 123 | 124 | watching.add(file); 125 | 126 | emitter.on("close", () => { 127 | watching.delete(file); 128 | }); 129 | 130 | emitter.on("error", (error) => { 131 | console.error(error); 132 | watching.delete(file); 133 | }); 134 | } 135 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "moduleResolution": "Node", 6 | "lib": ["es2018"], 7 | "outDir": "dist", 8 | "rootDir": "src", 9 | "strict": true, 10 | "declaration": true, 11 | "removeComments": false, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true 15 | } 16 | } 17 | --------------------------------------------------------------------------------