Skip to content

Commit

Permalink
refactor(*): break up into individual modules (#474)
Browse files Browse the repository at this point in the history
this should make it much easier to comprehend
and write tests for karma-webpack.
there is one change in how the
KarmaWebpackController is managed, we now
instantiate this in the preprocessor phase
and propagate the value within the karma config
object as a private variable. This allows
for breaking the framework and preprocessor
into separates modules and has the added
benefit of being able to run multiple
times in a given session without sharing
mutable state. This allows integrations
tests to be run in parallel as well as multiple
times which was previously not possible.

Fixes N/A
  • Loading branch information
codymikol committed Feb 1, 2021
1 parent 8ad09d1 commit 5300200
Show file tree
Hide file tree
Showing 20 changed files with 264 additions and 200 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js
@@ -1,9 +1,13 @@
module.exports = {
root: true,
globals: {
"jasmine": true,
},
plugins: ['prettier'],
extends: ['@webpack-contrib/eslint-config-webpack'],
rules: {
"consistent-return": "off",
"camelcase": "off",
"no-console": "off",
"no-param-reassign": "off",
"no-underscore-dangle": "off",
Expand Down
2 changes: 1 addition & 1 deletion lib/index.js
@@ -1 +1 @@
module.exports = require('./karma-webpack');
module.exports = require('./karma/plugin');
97 changes: 14 additions & 83 deletions lib/KarmaWebpackController.js → lib/karma-webpack/controller.js
@@ -1,75 +1,17 @@
const path = require('path');
const fs = require('fs');
const os = require('os');

const webpack = require('webpack');
const merge = require('webpack-merge');

class KarmaSyncPlugin {
constructor(options) {
this.karmaEmitter = options.karmaEmitter;
this.controller = options.controller;
}
const KW_WebpackPlugin = require('../webpack/plugin');
const DefaultWebpackOptionsFactory = require('../webpack/defaults');

apply(compiler) {
this.compiler = compiler;

// webpack bundles are finished
compiler.hooks.done.tap('KarmaSyncPlugin', async (stats) => {
// read generated file content and store for karma preprocessor
this.controller.bundlesContent = {};
stats.toJson().assets.forEach((webpackFileObj) => {
const filePath = `${compiler.options.output.path}/${webpackFileObj.name}`;
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
filePath,
'utf-8'
);
});

// karma refresh
this.karmaEmitter.refreshFiles();
});
class KW_Controller {
constructor() {
this.isActive = false;
this.bundlesContent = {};
this.hasBeenBuiltAtLeastOnce = false;
this.webpackOptions = DefaultWebpackOptionsFactory.create();
}
}

const defaultWebpackOptions = {
mode: 'development',
output: {
filename: '[name].js',
// eslint-disable-next-line prettier/prettier
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
},
stats: {
modules: false,
colors: true,
},
watch: false,
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
minSize: 0,
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 1,
},
},
},
},
plugins: [],
// Something like this will be auto added by this.configure()
// entry: {
// 'foo-one.test.js': 'path/to/test/foo-one.test.js',
// 'foo-two.test.js': 'path/to/test/foo-two.test.js',
// },
// plugins: [
// new KarmaSyncPlugin()
// ],
};

class KarmaWebpackController {
set webpackOptions(options) {
this.__webpackOptions = options;
}
Expand All @@ -78,11 +20,15 @@ class KarmaWebpackController {
return this.__webpackOptions;
}

updateWebpackOptions(newOptions) {
this.webpackOptions = merge(this.webpackOptions, newOptions);
}

set karmaEmitter(emitter) {
this.__karmaEmitter = emitter;

this.__webpackOptions.plugins.push(
new KarmaSyncPlugin({
new KW_WebpackPlugin({
karmaEmitter: emitter,
controller: this,
})
Expand All @@ -97,13 +43,6 @@ class KarmaWebpackController {
return this.webpackOptions.output.path;
}

constructor() {
this.isActive = false;
this.bundlesContent = {};
this.hasBeenBuiltAtLeastOnce = false;
this.webpackOptions = defaultWebpackOptions;
}

setupExitHandler(compiler) {
this.karmaEmitter.once('exit', (done) => {
compiler.close(() => {
Expand All @@ -113,10 +52,6 @@ class KarmaWebpackController {
});
}

updateWebpackOptions(newOptions) {
this.webpackOptions = merge(this.webpackOptions, newOptions);
}

async bundle() {
if (this.isActive === false && this.hasBeenBuiltAtLeastOnce === false) {
console.log('Webpack bundling...');
Expand Down Expand Up @@ -169,8 +104,4 @@ class KarmaWebpackController {
}
}

module.exports = {
KarmaSyncPlugin,
KarmaWebpackController,
defaultWebpackOptions,
};
module.exports = KW_Controller;
36 changes: 36 additions & 0 deletions lib/karma-webpack/framework.js
@@ -0,0 +1,36 @@
const fs = require('fs');
const path = require('path');

function KW_Framework(config) {
// This controller is instantiated and set during the preprocessor phase.
const controller = config.__karmaWebpackController;
const commonsPath = path.join(controller.outputPath, 'commons.js');
const runtimePath = path.join(controller.outputPath, 'runtime.js');

// make sure tmp folder exists
if (!fs.existsSync(controller.outputPath)) {
fs.mkdirSync(controller.outputPath);
}

// create dummy files for commons.js and runtime.js so they get included by karma
fs.closeSync(fs.openSync(commonsPath, 'w'));
fs.closeSync(fs.openSync(runtimePath, 'w'));

// register for karma
config.files.unshift({
pattern: commonsPath,
included: true,
served: true,
watched: false,
});
config.files.unshift({
pattern: runtimePath,
included: true,
served: true,
watched: false,
});
}

KW_Framework.$inject = ['config'];

module.exports = KW_Framework;
53 changes: 8 additions & 45 deletions lib/karma-webpack.js → lib/karma-webpack/preprocessor.js
@@ -1,45 +1,12 @@
const path = require('path');
const fs = require('fs');

const glob = require('glob');
const minimatch = require('minimatch');

const { ensureWebpackFrameworkSet } = require('./karma/karmaConfigValidator');
const { ensureWebpackFrameworkSet } = require('../karma/validation');
const { hash } = require('../utils/hash');

const { hash } = require('./utils/hash');

const { KarmaWebpackController } = require('./KarmaWebpackController');

const controller = new KarmaWebpackController();

function registerExtraWebpackFiles(config, _controller) {
const localController = _controller || controller;
const commonsPath = path.join(localController.outputPath, 'commons.js');
const runtimePath = path.join(localController.outputPath, 'runtime.js');

// make sure tmp folder exists
if (!fs.existsSync(localController.outputPath)) {
fs.mkdirSync(localController.outputPath);
}

// create dummy files for commons.js and runtime.js so they get included by karma
fs.closeSync(fs.openSync(commonsPath, 'w'));
fs.closeSync(fs.openSync(runtimePath, 'w'));

// register for karma
config.files.unshift({
pattern: commonsPath,
included: true,
served: true,
watched: false,
});
config.files.unshift({
pattern: runtimePath,
included: true,
served: true,
watched: false,
});
}
const KW_Controller = require('./controller');

function getPathKey(filePath, withExtension = false) {
const pathParts = path.parse(filePath);
Expand Down Expand Up @@ -81,7 +48,9 @@ function configToWebpackEntries(config) {
return webpackEntries;
}

function preprocessorFactory(config, emitter) {
function KW_Preprocessor(config, emitter) {
const controller = new KW_Controller();
config.__karmaWebpackController = controller;
ensureWebpackFrameworkSet(config);

// one time setup
Expand Down Expand Up @@ -118,12 +87,6 @@ function preprocessorFactory(config, emitter) {
};
}

registerExtraWebpackFiles.$inject = ['config'];
preprocessorFactory.$inject = ['config', 'emitter'];
KW_Preprocessor.$inject = ['config', 'emitter'];

module.exports = {
'preprocessor:webpack': ['factory', preprocessorFactory],
'framework:webpack': ['factory', registerExtraWebpackFiles],
registerExtraWebpackFiles,
configToWebpackEntries,
};
module.exports = KW_Preprocessor;
9 changes: 9 additions & 0 deletions lib/karma/plugin.js
@@ -0,0 +1,9 @@
const KW_Framework = require('../karma-webpack/framework');
const KW_Preprocessor = require('../karma-webpack/preprocessor');

const KW_KarmaPlugin = {
'preprocessor:webpack': ['factory', KW_Preprocessor],
'framework:webpack': ['factory', KW_Framework],
};

module.exports = KW_KarmaPlugin;
File renamed without changes.
35 changes: 35 additions & 0 deletions lib/webpack/defaults.js
@@ -0,0 +1,35 @@
const path = require('path');
const os = require('os');

function create() {
return {
mode: 'development',
output: {
filename: '[name].js',
// eslint-disable-next-line prettier/prettier
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
},
stats: {
modules: false,
colors: true,
},
watch: false,
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
minSize: 0,
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 1,
},
},
},
},
plugins: [],
};
}

module.exports = { create };
32 changes: 32 additions & 0 deletions lib/webpack/plugin.js
@@ -0,0 +1,32 @@
const fs = require('fs');

class KW_WebpackPlugin {
constructor(options) {
this.karmaEmitter = options.karmaEmitter;
this.controller = options.controller;
}

apply(compiler) {
this.compiler = compiler;

// webpack bundles are finished
compiler.hooks.done.tap('KW_WebpackPlugin', async (stats) => {
// read generated file content and store for karma preprocessor
this.controller.bundlesContent = {};
stats.toJson().assets.forEach((webpackFileObj) => {
const filePath = `${compiler.options.output.path}/${
webpackFileObj.name
}`;
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
filePath,
'utf-8'
);
});

// karma refresh
this.karmaEmitter.refreshFiles();
});
}
}

module.exports = KW_WebpackPlugin;
4 changes: 2 additions & 2 deletions test/integration/scenarios/basic-setup/basic-setup.test.js
Expand Up @@ -4,7 +4,7 @@ import karmaChromeLauncher from 'karma-chrome-launcher';
import karmaMocha from 'karma-mocha';
import karmaChai from 'karma-chai';

import ScenarioUtils from '../../utils/ScenarioUtils';
import Scenario from '../../utils/scenario';

process.env.CHROME_BIN = require('puppeteer').executablePath();

Expand Down Expand Up @@ -36,7 +36,7 @@ describe('A basic karma-webpack setup', () => {
};

beforeAll((done) => {
ScenarioUtils.run(config)
Scenario.run(config)
.then((res) => {
scenario = res;
})
Expand Down
@@ -1,6 +1,6 @@
const karma = require('karma');

const ScenarioUtils = { run };
const Scenario = { run };

/**
* This allows you to run karma with a given configuration and be returned.
Expand All @@ -20,4 +20,4 @@ function run(config) {
});
}

export default ScenarioUtils;
export default Scenario;

0 comments on commit 5300200

Please sign in to comment.