Nuxt.js: Expose resolved webpack config for external tool

Created on 7 Nov 2018  路  10Comments  路  Source: nuxt/nuxt.js

What problem does this feature solve?

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:

  • #200
  • #2936
  • #2170

What does the proposed changes look like?

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:

  1. Developer call a function provided by nuxt to trigger the resolvation of webpack.config
  2. Nuxt expose the resolved webpack.config as a file
  3. Developer run test script which rely on the exposed webpack.config

For linting use case,

  1. Perhaps a base webpack.config can always be exposed as a file at the begining
  2. When developer call nuxt dev, a new resolved webpack.config can override the existing webpack.config
  3. IDE / Editor's eslint tool could automatically pick up this change and update its rule accordingly?

In summary,

  1. Expose a base webpack.config
  2. Resolve and update webpack.config as nuxt is building or via a function call by developer
  3. Developer / external tool make use of this resolved config

This feature request is available on Nuxt community (#c8098)
feature-request

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'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;
};

All 10 comments

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.

How

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.

Usage

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,

  1. update-webpack-configs will create webpack.config.client.js and webpack.config.server.js and exit.
  2. yarn test or other tool can then be configured to read the file from there.

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?

https://github.com/nuxt/nuxt.js/blob/bed0714fad9cafc770f8c0c1eed7c85001855acd/packages/webpack/src/builder.js#L92-L102

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.jsfile.
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 @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'),
  });
Was this page helpful?
0 / 5 - 0 ratings

Related issues

VincentLoy picture VincentLoy  路  3Comments

shyamchandranmec picture shyamchandranmec  路  3Comments

jaredreich picture jaredreich  路  3Comments

mikekidder picture mikekidder  路  3Comments

surmon-china picture surmon-china  路  3Comments