speedskater/babel-plugin-rewire

Thanks for a great plugin! I'd suggest adding dynamic import syntax to the readme! :)

Opened this issue · 2 comments

Not sure if this is working as intended or just a happy byproduct of Babel improvements, but I was able to use rewire APIs not just with top-level ES6 imports but also with dynamic imports using ES6 import() syntax. And with webpack, I can remove cached modules to force their re-evaluation. An example (assuming '../src/someFunc.js' contains a top-level const Q that it doesn't export, which in turn has a set named queue):

it('supports dynamic imports -- sweet', async () => {
	const exportedFunc = await import('../src/someFunc.js');
	const Q = exportedFunc.__get__('Q');
	expect(Q.queue.size).to.equal(0);
	Q.queue.add('test');
	expect(Q.queue.size).to.equal(1);
	// NOTE: The following line is required or webpack will return the same module instance in the next import...
	delete require.cache[require.resolve('../src/someFunc.js')];
	const exportedFunc = await import('../src/someFunc.js');
	const Q = exportedFunc.__get__('Q');
	expect(Q.queue.size).to.equal(0);
});

Very happy with how this is working out! :)

If it helps anybody, I'm using open-wc's Karma config with webpack config modified as follows (I've included the original open-wc Karma webpack config inline...):

{
	webpack: {
		watch: true,
		mode: 'development',
		devtool: 'inline-cheap-module-source-map',

		resolve: {
			mainFields: [
				// current leading de-facto standard - see https://github.com/rollup/rollup/wiki/pkg.module
				'module',
				// previous de-facto standard, superceded by `module`, but still in use by some packages
				'jsnext:main',
				// standard package.json fields
				'browser',
				'main',
			],
		},

		module: {
			rules: [
				coverage && {
					test: /\.js$/,
					loader: require.resolve('istanbul-instrumenter-loader'),
					enforce: 'post',
					include: path.resolve('./'),
					exclude: /node_modules|bower_components|\.(spec|test)\.js$/,
					options: {
						esModules: true,
					},
				},

				legacy && {
					test: /\.js$|\.ts$/,
					use: {
						loader: 'babel-loader',

						options: {
							plugins: [
								require.resolve('@babel/plugin-syntax-dynamic-import'),
								require.resolve('@babel/plugin-syntax-import-meta'),
								// webpack does not support import.meta.url yet, so we rewrite them in babel
								[require.resolve('babel-plugin-bundled-import-meta'), { importStyle: 'baseURI' }],
							].filter(_ => !!_),

							presets: [[require.resolve('@babel/preset-env'), { targets: 'IE 11' }]],
						},
					},
				},

				!legacy && {
					test: /\.js$/,
					loader: require.resolve('@open-wc/webpack-import-meta-loader'),
				},
				{
					test: /\.js$|\.ts$/,
					exclude: /node_modules|bower_components|\.(spec|test)\.js$/,
					use: {
						loader: 'babel-loader',
						options: {
							plugins: [require.resolve('babel-plugin-rewire')].filter(_ => !!_),
							sourceMaps: 'both',
						},
					},
				},
			].filter(_ => !!_),
	},
}

To help mask the above webpack delete nastiness, I made a very simple wrapper using promises:

const importAndForget = filename => {
	return import(filename).finally(() => {
		delete require.cache[require.resolve(filename)];
	});
};

which simplifies the above example to:

it('supports dynamic imports -- sweet', async () => {
	importAndForget('../src/someFunc.js').then(module => {
		const Q = module.__get__('Q');
		expect(Q.queue.size).to.equal(0);
		Q.queue.add('test');
		expect(Q.queue.size).to.equal(1);
	});
	importAndForget('../src/someFunc.js').then(module => {
		const Q = module.__get__('Q');
		expect(Q.queue.size).to.equal(0);
	});
});

Had a hard time making the filenames dynamic while still keeping things await-compatible, so I ended up with the following function hard-coded:

function loadSomeFunc() {
	delete require.cache[require.resolve('../src/someFunc.js')];
	return import('../src/someFunc.js');
}

which can be used as follows:

	it('supports dynamic imports -- sweet', async () => {
		let module = await loadSomeFunc();
		let Q = module.__get__('Q');
		expect(Q.queue.size).to.equal(0);
		Q.queue.add('test');
		expect(Q.queue.size).to.equal(1);
		module = await loadSomeFunc();
		Q = module.__get__('Q');
		expect(Q.queue.size).to.equal(0);
	});