K6: eslint cannot resolve imports from k6 weback

Created on 25 Aug 2020  路  2Comments  路  Source: loadimpact/k6

Environment

  • k6 version: k6 v0.27.1 (dev build, go1.14.5, darwin/amd64)
  • OS and version: macOS 10.15.5

Expected Behavior

eslint should be able to resolve k6 modules via webpack.

Actual Behavior

eslint complains Unable to resolve path to module 'k6'.eslint(import/no-unresolved) for k6 imports via webpack. NPM and VSCode are able to resolve these paths

Steps to Reproduce the Problem

package.json:

{
    "devDependencies": {
        "@types/k6": "^0.26.0",
        "@types/webpack": "^4.41.7",
        "@typescript-eslint/eslint-plugin": "^2.23.0",
        "@typescript-eslint/parser": "^2.23.0",
        "k6": "0.0.0",
        "webpack": "^4.42.0",
        "webpack-cli": "^3.3.11",
    },
    "scripts": {
        "k6:build": "webpack --config ./webpack.config.k6.ts",
        "k6": "npm run k6:build && k6 run dist/load-test.js",
        "k6:datadog": "docker-compose up -d && npm run k6:build && k6 run --out datadog dist/load-test.js",
        "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    }
}

webpack.config.k6.ts

import { resolve } from 'path';
import { Configuration } from 'webpack';
import TerserPlugin from 'terser-webpack-plugin';
import Dotenv from 'dotenv-webpack';

const config: Configuration = {
    entry: { 
        'load-test': './src/load-test.ts'
    },
    externals: [
        {'k6': 'k6'},
        {'k6/http': 'k6/http'},
        {'k6/metrics': 'k6/metrics'},
        {'k6/options': 'k6/options'}
    ],
    output: {
        filename: '[name].js',
        libraryTarget: 'commonjs',
        path: resolve(__dirname, 'dist'),
    },
    module: {
        rules: [{test: /\.ts$/, loader: 'ts-loader'}],
    },
    resolve: {
        extensions: ['.js', '.ts'],
    },
    target: 'node',
    devtool: 'inline-source-map',
    mode: process.env.NODE_ENV === 'dev' ? 'development' : 'production',
    optimization: {
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    keep_fnames: true,
                },
            }),
        ],
    },
    plugins: [
        new Dotenv({
            path: './.env', // Path to .env file
            safe: true, // load .env.example (defaults to "false" which does not use dotenv-safe)
            systemvars: true
        })
    ]
};

export default config;

.eslintrc.js

module.exports = {
    parser: '@typescript-eslint/parser',
    parserOptions: {
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
    },
    plugins: ['@typescript-eslint', 'jest', 'import'],
    extends: [
        'eslint:recommended',
        'plugin:@typescript-eslint/eslint-recommended',
        'plugin:@typescript-eslint/recommended',
        'plugin:@typescript-eslint/recommended-requiring-type-checking',
        'plugin:import/errors',
        'plugin:import/typescript',
        'prettier',
    ],
    rules: {
        'brace-style': 'error',
        'no-console': 'off',
        curly: ['error'],
        'eol-last': ['error', 'always'],
        'import/order': [
            'error',
            {
                groups: [
                    'index',
                    'sibling',
                    'parent',
                    'internal',
                    'external',
                    'builtin',
                ],
                alphabetize: {
                    order: 'asc',
                },
            },
        ],
        'no-multiple-empty-lines': ['error', { max: 1 }],
        quotes: ['error', 'single'],
        semi: 'off',
        '@typescript-eslint/semi': ['error', 'always'],
        '@typescript-eslint/no-use-before-define': [
            'error',
            {
                functions: false,
                typedefs: false,
            },
        ],
        '@typescript-eslint/no-explicit-any': 'error',
        "@typescript-eslint/camelcase": 'warn',
        "@typescript-eslint/no-unused-vars": ['error', { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
    },
};

tsconfig.json

{
    "compilerOptions": {
        "allowSyntheticDefaultImports": true,
        "target": "es2019",
        "lib": ["es2019", "es2020.string"],
        "module": "commonjs",
        "moduleResolution": "node",
        "sourceMap": true,
        "esModuleInterop": true,
        "removeComments": true,
        "noImplicitAny": true,
        "strict": true,
        "skipLibCheck": true,
        "outDir": "dist",
        "suppressImplicitAnyIndexErrors": true,
        "types": ["jest", "node"]
    },
    "include": ["src/**/*.ts"]
}

load-test.ts

import {check, sleep} from 'k6'; // Unable to resolve path to module 'k6'. eslint(import/no-unresolved)
import http from 'k6/http'; // Unable to resolve path to module 'k6/http'. eslint(import/no-unresolved)
import {Counter} from 'k6/metrics'; // Unable to resolve path to module 'k6/metrics'. eslint(import/no-unresolved)
import {Options} from 'k6/options'; Unable to resolve path to module 'k6/options'. eslint(import/no-unresolved)

export const options: Options = {
      /** Test stage specifications. Program of target VU stages. */
      stages: [
        { duration: '30s', target: 50 }, // below normal load
        { duration: '30s', target: 50 },
        { duration: '30s', target: 150 }, // normal load
        { duration: '30s', target: 150 },
        { duration: '30s', target: 300 }, // around the breaking point
        { duration: '30s', target: 300 },
        { duration: '30s', target: 400 }, // beyond the breaking point
        { duration: '30s', target: 400 },
        { duration: '1m', target: 0 }, // scale down. Recovery stage.
      ],
      /** Tags to set test wide across all metrics. */
      tags: { 'randomId': `${process.env.RAND_ID}` }
};

const error500 = new Counter('500 error');

const authBearerToken = `Bearer ${process.env.API_AUTHORIZATION_SECRET}`;
const id = Math.floor(Math.random() * 101);

export function setup(): {} {
    if(process.env.RAND_ID === 'true') {
        return { id: id };
    } else {
        //The known success case is when Id = 2
        return { id: 2 };
    } 
}

//Get details
export default (data: {id: number}): void => {
    const url = 'https://api.test.app/get_details';
    const headers = {'Content-Type': 'application/json', 'Authorization': authBearerToken};

    const result = http.put(url, JSON.stringify(data), {headers: headers});

    //Checks the result and creates a metric that is reported to stdout and datadog
    check(result, {
        'is status 200': (r) => r.status === 200,
    });

    if(result.status === 500) {
        //Counts how many 500 error codes are returned
        error500.add(1);
    }
    console.log(result.body);
    sleep(1);
};
question

All 2 comments

Hi @TaylorMichaelHall,

Thank you for your report! This should be resolvable by adding the following snippet to the top of your test file.

/*eslint import/no-unresolved: [2, { ignore: ['^k6.*'] }]*/

The reason as to why this is happening is because k6 modules are not really node modules, but rather built-in modules provided by the k6 runtime. You are able to resolve them in vscode because it automatically installs the package types from definitely-typed.

Thank you for the thorough answer @simskij!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

caalle picture caalle  路  4Comments

gushengyuan picture gushengyuan  路  3Comments

jrm2k6 picture jrm2k6  路  4Comments

euclid1990 picture euclid1990  路  3Comments

na-- picture na--  路  4Comments