I am trying to add unit test with ava in this project. However, webpack alias is not understood by ava. This problem with ava could be resolved by providing a webpack config.
As of now, we need to create the webpack config by hand and try to match it with what nuxt is using.
If a resolved webpack config is produced and exposed, we could use this config with our testing, linting and other external tools.
Related to:
Perhaps, it could look something similar to vue's cli-service implementation. Where the resolved webpack.config is exposed as a file.
https://cli.vuejs.org/guide/webpack.html#inspecting-the-project-s-webpack-config
For testing use case, perhaps:
For linting use case,
nuxt dev
, a new resolved webpack.config can override the existing webpack.configIn summary,
This issue appear to be semi hackable by making use of nuxt hook and its api.
Let say we are making a build for SSR that will output webpack config for both client and server, this could be done.
Write a script to collect the compiler options via the build:compile
hook. Once we have collected enough options (we need 2 options in this case), we will call resolve()
and return all the collected configurations back to caller.
FILE: ./scripts/collect-webpack-configs.js
import { Nuxt, Builder } from 'nuxt';
import config from '../nuxt.config.js';
const stopProcessIfCalledTimes = (times, callback) => {
let counter = times;
const compilerOptionMap = {};
return ({ name, compiler }) => {
compilerOptionMap[name] = compiler;
counter--;
if (counter === 0) {
callback(compilerOptionMap);
}
};
};
export default function collectWebpackConfigs() {
return new Promise((resolve) => {
const nuxt = new Nuxt(config)
let stopProcessWhenDone = stopProcessIfCalledTimes(1, resolve);
nuxt.hook('build:before', (nuxt, buildOptions) => {
if (buildOptions.ssr) {
stopProcessWhenDone = stopProcessIfCalledTimes(2, resolve)
}
});
nuxt.hook('build:compile', ({name, compiler}) => {
stopProcessWhenDone({ name, compiler })
});
if (nuxt.options.dev) {
new Builder(nuxt).build()
}
});
};
A parent script will execute this function and write the webpack config to a file so that we can use it later. After the writing is done. It will stop the process.
FILE: ./scripts/update-webpack-configs.js
import fs from 'fs';
import path from 'path';
import collectWebpackConfigs from './collect-webpack-configs';
const makeWebpackConfigName = postFix => path.join(__dirname, 'webpack.config.' + postFix + '.js');
const outputConfigurationAsFile = ({ name, options }) => {
const webpackFilePath = makeWebpackConfigName(name);
const content = 'module.exports = ' + JSON.stringify(options);
fs.writeFileSync(webpackFilePath, content);
};
const processWebpackCompilerMap = (webpackCompilerMap) => {
Object.entries(webpackCompilerMap)
.forEach(([name, compiler]) => {
outputConfigurationAsFile({ name, options: compiler.options });
});
};
collectWebpackConfigs()
.then(processWebpackCompilerMap)
.then(() => process.exit());
This hack require the use of process.exit()
as the build API will proceed to compile the rest of our source code, which may not be desired for our case if we just want to extract the config.
This hack could enable us to extract webpack config for external tool.
node -r esm scripts\update-webpack-configs.js && yarn test
In this case,
update-webpack-configs
will create webpack.config.client.js
and webpack.config.server.js
and exit.This is a good news for me, at least now I am able to generate a resolved webpack config.
However, perhaps, Nuxt can add some kind of API to just trigger and return the compilersOptions
without running Webpack?
In this case, process.exit()
hack will not be required.
Your hack is amazing @amoshydra kudos!
After the whole day working with this I still have this problem:
VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.
First I've changed the creation of the file to have the webpack config inmenory because sitringifying was not working well for me ( a lot of errors like, plugins[0] has not apply method, etc.)
do you have any idea @amoshydra?
this is how my update-webpack-configs
looks like:
module.exports = (configName) => {
return new Promise(resolve => {
collect().then(message => {
const config = message[configName];
if (!config) {
throw new Error(`${configName} not found.`);
}
resolve(config.options);
});
});
};
and the file using this is something like:
const path = require('path');
const { Server } = require('karma');
const { Config } = require('karma/lib/config');
const karmaConfigTests = require(path.resolve(__dirname, '../unit/karma.conf'));
const updateWebpackConfigs = require('./update-webpack-configs');
(async () => {
const karmaConfig = new Config();
const webpack = await updateWebpackConfigs('client');
karmaConfigTests(karmaConfig, webpack);
const server = new Server(karmaConfig, function (exitCode) {
console.log('Karma has exited with ' + exitCode)
process.exit(exitCode)
});
server.start();
})();
_Update:_
I've moved vue loader to the top of the array in the collect-webpack-configs.js
file.
Now Tests are working! but I still have this error:
ERROR in ./.temp/.nuxt/components/nuxt-loading.vue?vue&type=script&lang=js& friendly-errors 14:54:01
Module build failed (from ./node_modules/vue-loader/lib/index.js): friendly-errors 14:54:01
TypeError: Cannot read property 'content' of null
at selectBlock (/Projects/PlainConcepts/IndAndOut/Web/node_modules/vue-loader/lib/select.js:27:25)
at Object.module.exports (/Projects/PlainConcepts/IndAndOut/Web/node_modules/vue-loader/lib/index.js:75:12)
I hope this is going to be useful to someone :)
Has there been any progress on this? I need the config so that I can pass it along to the storybook config
@tjbenton hope this helps https://github.com/CKGrafico/Frontend-Boilerplates/tree/nuxt/test/scripts
@tjbenton @CKGrafico I have recently digged into Nuxt's builder's api and realise that, we could also do this programatically by extending Nuxt's BundleBuilder
class. Nuxt's Builder
accept a custom BundleBuilder
class as its second parameter.
This could greatly simplify the implementation detail of collect-webpack-config
without relying on process.exit()
FILE: ./scripts/collect-webpack-configs.js
import { Nuxt, Builder } from 'nuxt';
import { BundleBuilder } from '@nuxt/webpack/dist/webpack';
import config from 'arbitary-module/../../nuxt.config.js';
class CustomBundleBuilder extends BundleBuilder {
constructor(context) {
super(context);
this.compilerMap = {};
}
/**
* Skip running compiler and store compiler into compilerMap
* @override
*/
async webpackCompile(compiler) {
this.compilerMap[compiler.name] = compiler;
}
}
export default async () => {
const nuxt = new Nuxt(config);
const customBuilder = new Builder(nuxt, CustomBundleBuilder);
await customBuilder.build();
customBuilder.close(); // EDIT: By default nuxt.options is set for development, need to close builder to stop nuxt from watching for fileChange
return customBuilder.bundleBuilder.compilerMap;
};
@amoshydra It should be noted that doing this will rimraf your dist build. I just encountered this and it was pretty confusing to figure out, hopefully this saves other people some time debugging.
@nickforddesign
Good point! Perhaps when doing this, we can change the buildDir
to a different destination so that our application build folder don't get removed.
Example:
const nuxt = new Nuxt({
...config,
buildDir: path.resolve(process.cwd(), 'temp/other-destination'),
});
Resolved through https://github.com/nuxt/nuxt.js/pull/7029
Most helpful comment
@tjbenton @CKGrafico I have recently digged into Nuxt's builder's api and realise that, we could also do this programatically by extending Nuxt's
BundleBuilder
class. Nuxt'sBuilder
accept a customBundleBuilder
class as its second parameter.This could greatly simplify the implementation detail of
collect-webpack-config
without relying onprocess.exit()
FILE:
./scripts/collect-webpack-configs.js