Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose resolved webpack config for external tool #4286

Closed
amoshydra opened this issue Nov 7, 2018 · 10 comments
Closed

Expose resolved webpack config for external tool #4286

amoshydra opened this issue Nov 7, 2018 · 10 comments

Comments

@amoshydra
Copy link

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:

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)
@ghost ghost added the cmty:feature-request label Nov 7, 2018
@amoshydra
Copy link
Author

amoshydra commented Nov 7, 2018

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.

@amoshydra
Copy link
Author

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.

@CKGrafico
Copy link

Your hack is amazing @amoshydra kudos!

@CKGrafico
Copy link

CKGrafico commented Dec 9, 2018

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 :)

@tjbenton
Copy link

tjbenton commented Mar 1, 2019

Has there been any progress on this? I need the config so that I can pass it along to the storybook config

@CKGrafico
Copy link

@tjbenton hope this helps https://github.com/CKGrafico/Frontend-Boilerplates/tree/nuxt/test/scripts

@amoshydra
Copy link
Author

amoshydra commented Mar 5, 2019

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

@nickforddev
Copy link

@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.

@amoshydra
Copy link
Author

amoshydra commented Mar 26, 2019

@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'),
  });

@manniL
Copy link
Member

manniL commented Apr 10, 2020

Resolved through #7029

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants