Bundle npm module 'cheerio' in K6 test - npm

I am trying to create some tests using K6 framework from LoadImpact, but I am struggelig with including external NPM module following the instructions on their documentation site.
On loadImpacts documentations site they include a detailed example on just what I am after, modules that enable me to parse xml from a soap service response. But, I am unable to get this working! Now, I am a total javascript newbie, but I have been coding for many years and would really like to solve this.
The can be found here: https://docs.k6.io/docs/modules#section-npm-modules
can anyone get this working? I need to run this on servers isolated from the Internet, so I am totaly dependent on creating the packages and transfer the required files.
According to the documentation a package is created like this
-- bundle `cheerio` npm module
git clone git#github.com:cheeriojs/cheerio.git
npm install browserify index.js -s cheerio > cheerio.js
My first question: In the folder I am residing when running this command a 'cheerio.js' file is created along with a a 'cheerio' folder and a 'node_modules' folder.
the cheerio.js in my "root" directory only contains the following:
+ cheerio#0.22.0
+ index.js#0.0.3
+ browserify#16.2.3
updated 3 packages and audited 2829 packages in 2.221s
found 0 vulnerabilities
Back to LoadImpacts example on how to reference this package in a k6 javascript:
import cheerio from "./vendor/cheerio.js";
export default function()
{
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
What file is this, and where in the structure generated by browserify can I find it? I have tried to change this to point to 'index.js' in the 'cheerio' folder or cheerio.js found in 'cheerio/lib'. I will then receive a complaint about the first line in cheerio.js which defines a "parse" variable it cannot find:
var parse = require("./parse'),
if I change this to
var parse = require("./parse.js')
it goes on to complain about missing 'htmlparser2' which I can also find in this structure, but it seems like the entire dependency structure is not working.
Can anybody give me some guidance on how to create a browserify package with dependencies for cheerio and how/what I need to copy to my k6 project to make this work like on the loadImpact site.

The k6 docs for this definitely need some clarification, which I'll later do. The vendor folder currently mentioned there isn't something special, the docs are just missing a step to copy the cheerio.js and xml2js.js files that were generated by browserify to a new vendor folder in your k6 project.
For now, I'll try to offer a simplified explanation on how to achieve the same thing in a simpler way:
Create a new empty folder and go to it in a terminal
Run npm install browserify cheerio there (ignore the npm warnings about missing package.json or description)
Run ./node_modules/.bin/browserify ./node_modules/cheerio/ -s cheerio > cheerio.js in that folder
The resulting cheerio.js file in the folder root should be the file you import from the k6 script:
import http from "k6/http";
import cheerio from "./cheerio.js";
export default function () {
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
console.log($('head title').text())
}
That should be it for a single npm library.
And if you need to use multiple npm packages, it might be better to invest some time into bundling them in a single browserified .js file. For example, if you need both the cheerio and the xml2js libraries mentioned in the k6 docs, you can do something like this:
Create a new empty folder
Add something like the following package.json file in it:
{
"name": "k6-npm-libs-demo",
"version": "0.0.1",
"description": "just a simple demo of how to use multiple npm libs in k6",
"main": "npm-main.js",
"dependencies": {},
"devDependencies": {
"browserify": "*",
"cheerio": "*",
"xml2js": "*"
},
"scripts": {
"install": "./node_modules/.bin/browserify npm-main.js -s npmlibs > vendored-libs.js"
},
"author": "",
"license": "ISC"
}
Of course, if you need different libraries than cheerio and xml2js, you need to adjust the devDependencies options.
Add an npm-main.js file like this (again, adjusting for the libraries you want):
exports.xml2js = require('xml2js');
exports.cheerio = require('cheerio');
Open that folder in a terminal and run npm install. That should result in the creation of a vendored-libs.js file in the root of the folder, which you can use in k6 like this:
import http from "k6/http";
import { cheerio, xml2js } from "./vendored-libs.js";
export default function () {
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
console.log($('head title').text())
var xmlString = '<?xml version="1.0" ?>' +
'<items xmlns="http://foo.com">' +
' <item>Foo</item>' +
' <item color="green">Bar</item>' +
'</items>'
xml2js.parseString(xmlString, function (err, result) {
console.log(JSON.stringify(result));
});
}

Related

serve html file from node_modules using react native static server and webview

Is it possible to serve an html file from within the node_modules folder? I know how to serve from the assets folder, something like:
<WebView
...
source={{uri:'file:///android_asset/project_a/index.html'}}/>
or the corresponding path for iOS, but how can I access the node_modules folder and serve a file from there? I've tried using require() and/or using the path to the module I want served but with no luck.
The idea is that, I want to avoid copy-pasting build files between projects (i.e., from the build folder of project A to the assets/www of project B), instead, I want publish project A as an npm package and npm install and serve it from project B. This also improves version management of the projects.
For Android, you can run a custom Gradle task that able to will handle copy your assets in build time.
Create a Gradle file in your module's root, like below:
(I presume your module name is react-native-my-awesome-module and your HTML files is under www folder in module's root.)
awesome.gradle:
/**
* Register HTML asset source folder
*/
android.sourceSets.main.assets.srcDirs += file("$buildDir/intermediates/ReactNativeMyAwesomeModule")
/**
* Task to copy HTML files
*/
afterEvaluate {
def targetDir = "../../node_modules/react-native-my-awesome-module/www";
def fileNames = [ "*.html" ];
def htmlCopyTask = tasks.create(
name: "copyReactNativeMyAwesomeModuleAssets",
type: Copy) {
description = "copy react native my awesome module assets."
into "$buildDir/intermediates/ReactNativeMyAwesomeModule/www"
fileNames.each { fileName ->
from(targetDir) {
include(fileName)
}
}
}
android.applicationVariants.all { def variant ->
def targetName = variant.name.capitalize()
def generateAssetsTask = tasks.findByName("generate${targetName}Assets")
generateAssetsTask.dependsOn(htmlCopyTask)
}
}
After installing the module, put the below line in your project's android/app/build.gradle file:
apply from: file("../../node_modules/react-native-my-awesome-module/awesome.gradle");
When you build your app, your assets under www folder will be copied from your module. You can access your resources in your app like below :
<WebView
source={{uri: 'file:///android_asset/www/index.html'}}
/>
I ended up adding a script to packages.json that copies the files of interest into the assets/www folder before starting the app. Something like:
"scripts": {
"copy:build": "node -e \"fs.copyFileSync('./node_modules/dir-of-module','./assets/www/build')\"",
"start-android": "npm run copyAssets && react-native run-android",
...
}
If there is a better alternative, please let me know!

How to use environment variables with static JS in public folder

I have VueJS app (Vue CLI 3) and additional static JS script in public folder. And I don't understand how I can use .env in this .js.
Let's say I have some specific environment variable, for example MY_URL and my JS file:
const myUrl = process.env.VUE_APP_MY_URL;
And it's not working, because static files from public folder don't processed by webpack as I understand.
Maybe someone knows good solution? Or maybe other solutions\workarounds?
In my case, I put .js to src and add new entry by chainWebpack:
config.entryPoints.delete('app')
config.entry('app')
.add('./src/main.ts')
.end()
.entry('myScript')
.add('./src/myScript.js')
.end()
And now webpack build the script as separate file, but injects to index.html with app.js. This is not what I really want.
So, main purpose - build separate static JS file with specific name without hash (for example, myScript.js) which would contain variable from .env (.env.production, .env.development)
Main fact about static files in public folder from docs:
Static assets placed in the public directory will simply be copied and not go through webpack
So, I cannot use .env with static files in public.
I haven't found a perfect solution, but at least 3 acceptable options:
JS file as entry, as Jesse Reza Khorasanee said in comments and gave a link to almost same question
The main idea:
configure vue.config.js for an additional entry and force webpack to process my file.
// vue.config.js
module.exports = {
configureWebpack: {
entry: {
public: "./public/main.js"
},
output: {
filename: "[name]/[name].main.js"
}
}
};
This solution would work with certain features:
at least two entry points: main entry for my SPA (main.js) and additional entry just for my static JS.
It's not good, because processed JS would contain a link to vendors.js chunk as one of the entries. But I need JS file processed by webpack only.
same output.filename and hash in filename with clear config (it's not work, because I use this script as 3rd party JS and load by static name), or different output.filename for my JS file but with dirty config:
configureWebpack: config => {
config.output.filename = (pathData) => {
return pathData.chunk.name === 'myScript'
? '[name].js' : '[name].[hash].js';
};
...
}
If I leave my JS in public folder I get two files after build: one in default js folder with other static assets and another in root folder near main.js
Multi-Page Application (configuration for Vue multi-page mode)
module.exports = {
pages: {
index: {
// entry for the page
entry: 'src/index/main.js',
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// when using the entry-only string format,
// template is inferred to be `public/myScript.html`
// and falls back to `public/index.html` if not found.
// Output filename is inferred to be `myScript.html`.
myScript: 'src/myScript.js'
}
}
This solution would work almost like the first solution and I get a clear config file. But still I have problem with vendors.js seems like pages option work directly with html-webpack-plugin and I can config chunks which would load with my page, and I tried different ways to setup this option but without success. Vendors is still part of myScript entry.
Build JS file as library
I chose this solution in my case. Because it's clear and short.
I put additional script to my package.json: vue-cli-service build --no-clean --target lib --name paysendPaymentLibrary src/payment.js and change main build script.
Final version of package.json:
...
"scripts": {
"build": "vue-cli-service build && npm run build-library",
"build-library": "vue-cli-service build --no-clean --target lib --name myScriptLibrary src/myScript.js"
},
...
After run npm run build I get static files for SPA and three files for my script:
myScriptLibrary.umd.min.js
myScriptLibrary.umd.js
myScriptLibrary.common.js
For 3rd party site I use myScriptLibrary.umd.js file.
If you choose this solution be careful when you build your application, because:
in Windows vue-cli-service build & npm run build-library scripts would run sequentially, but in Unix it runs in parallel. It can cause deletion of your SPA files. So be sure to use && instead of & (see discussions about environments and parallel\sequential script running)
size of processed files would be bigger than raw static JS. For example, in my case raw file size: 4 KiB, after build: 15.44 KiB, gzipped: 5.78 KiB.

How do I loop through files in npm in a way that works on Windows & linux?

I'm trying to run a single command (jshint), on multiple files. My package.json contains
"lint": "jshint *.js **/*.js"
However this fails miserable on Windows. On Windows the syntax to iterate on multiple files is
for %%f in (*.in) do (
echo %%~nf
)
Is there a simple, platform-agnostic way to run a single npm script (e.g. jshint) on multiple files?
(I'm interested in the general solution. There's a references here to using node-jslint instead of jshint, which does support multiple files ... but IMO jshint >> jslint).
I'm also not aware of a platform agnostic way to loop in the shell.
However, a platform agnostic solution to running a single npm-script on multiple files with jshint, as per your example, is to:
Utilize cli-glob to find the .js files.
Pipe the results/paths from the globbing pattern to a custom utility node script.
Then within the node script:
Read the paths piped to stdin using nodes readline module.
Create an Array of each path and subsequently convert that to a String.
Run the jshint executable, (including the String of all paths), using nodes child_process.exec() module.
Whilst this solution is not particularly "simple", the following gists demonstrate this process:
npm-script
"scripts": {
"jshint": "glob \"src/js/**/*.js\" | node .scripts/jshint.js"
},
Note cli-glob, (added to package.json), obtains the paths and pipes them to jshint.js.
jshint.js
#!/usr/bin/env node
'use strict';
var path = require('path');
var readline = require('readline');
var exec = require('child_process').exec;
var rl = readline.createInterface({
input: process.stdin,
output: null,
terminal: false
});
// Normalise path to local jshint executable.
var jshintExec = ['.', 'node_modules', '.bin', 'jshint '].join(path.sep);
var paths = [];
function jshint(paths) {
var command = [jshintExec, paths].join('');
exec(command, function(error, stdout, stderr) {
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.log(stderr);
}
});
}
rl.on('line', function(srcPath) {
paths.push(srcPath);
});
rl.on('close', function() {
jshint(paths.join(' '));
});
Note
Line 16 reading:
var jshintExec = ['.', 'node_modules', '.bin', 'jshint '].join(path.sep);
The script assumes jshint has been installed locally and added to the "devDependencies": {} section of the package.json. I.e. Its pointing to the local jhint executable found in the node_modules/.bin folder and not the globally installed one.
If your preference is to run the globally installed jshint then change line 16 to:
var jshintExec = 'jshint ';
Personally, having it installed locally is the preferred option IMHO for this scenario!
Multiple globbing patterns
Your example provided includes multiple glob patterns.
"lint": "jshint *.js **/*.js"
One limitation of cli-glob is that it doesn't accept multiple glob patterns. So one workaround is to do something like this:
"scripts": {
"jshint": "npm run jshint-a && npm run jshint-b",
"jshint-a": "glob \"*.js\" | node .scripts/jshint.js",
"jshint-b": "glob \"**/*.js\" | node .scripts/jshint.js"
},
Yeah, not particularly terse - but works!
To the best of my knowledge, you can't loop in the shell in a cross-platform way.
However, you can use https://www.npmjs.com/package/catw and do something like this:
catw "**/*.js" | jshint -
catw will expand the glob(s) itself without relying on the shell and write the files to stdout. jshint - will read from stdin. The pipe (|) works cross-platform.

Pulling files from a directory into the root folder for NPM

I am publishing a library to NPM.
When I build the library, the resulting artifact is placed in the dist folder located in the root of my project as index.js.
When users install from NPM I would like index.js to be present in the root of the folder created in their node_modules folder. Presently, it remains in a directory named dist.
How can I do this?
My packages.json:
{
"name": "my-package",
"version": "0.0.9",
"files": ["dist/*"],
"main": "index.min.js",
"private": false,
"dependencies": {},
"devDependencies": {},
"repository": "git#github.com:username/my-package.git"
}
I had exactly the same problem.
I solved it not by copying the files up, but by copying the files I needed down into the ./dist/ folder and then doing an npm publish from there; NPM then treats that folder as a complete package and everything works very nicely. The only files I needed to copy from the root folder were:
package.json
README.md
Because we're going to copy these files down into the ./dist/ folder before we do the publish, we do NOT want the package.json file to reference ./dist/. So remove the package.json's files entry completely, because we don't need to tell it which files we'll take - we're going to take everything in the ./dist/ folder. I'm using TypeScript so I also have a typings entry, and again no reference to ./dist/.
{
"name": "my-package",
"version": "0.0.9",
"main": "index.min.js",
"typings": "index.d.ts",
"private": false,
"dependencies": {},
"devDependencies": {},
"repository": "git#github.com:username/my-package.git"
}
Now for the publish step. I built a gulp task that will perform the publish for me, making it nice and automated (except for incrementing the package version #).
From gulp I'll use Node's spawn() to kick-off the npm process. However, because I'm actually working on Windows I used "cross-spawn" rather than the normal built-in Node.js spawn (which I learned the hard way didn't work when I had spaces in my path!).
Here's my gulp file, with the TypeScript bits removed:
var gulp = require('gulp');
var del = require('del');
var spawn = require('cross-spawn'); // WAS: require('child_process').spawn;
var config = {
src: { tsFiles: './src/**/*.ts' },
out: { path: './dist/' }
}
gulp.task('clean', () => {
return del('dist/*');
});
gulp.task('build', ['clean'], () => {
....
});
gulp.task('publish', ['build'], (done) => {
// Copy the files we'll need to publish
// We just use built-in gulp commands to do the copy
gulp.src(['package.json', 'README.md']).pipe(gulp.dest(config.out.path));
// We'll start the npm process in the dist directory
var outPath = config.out.path.replace(/(\.)|(\/)/gm,'');
var distDir = __dirname + '\\' + outPath + '\\';
console.log("dist directory = " + distDir);
// Start the npm process
spawn('npm', ['publish'], { stdio:'inherit', cwd:distDir } )
.on('close', done)
.on('error', function(error) {
console.error(' Underlying spawn error: ' + error);
throw error;
});
});
Notice when we call spawn() we pass in a 3rd argument which is the options. The main entry here is the cwd:distDir, which tells spawn to run the npm process from the ./dist/ directory. Because using spawn can cause problems I've hooked into the spawn error handling. As I was troubleshooting my use of spawn() I found the following StackOverflow article very helpful.
This worked like a charm; my published package has all the files in the root directory and the ./dist/ folder is not published.

Yeoman custom generator not loading dependencies from package.json

I've created a Yeoman custom generator. Within the index.js file I want to perform some text replacement on some files. In package.json I have added the dependency replace then when I require('replace') in index.js and run the generator, I get the error Cannot find module 'replace'. I have tried different modules from NPM and running the generator fails for all of them - it fails to find the module.
The appropriate part of package.json
"dependencies": {
"replace": "~0.2.9",
"yeoman-generator": "~0.16.0",
"chalk": "~0.4.0"
},
Start of index.js
'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var replace = require('replace');
var MyGenerator = yeoman.generators.Base.extend({
init: function () {
this.pkg = require('../package.json');
The generator fails when it hits the Replace require. Chalk and Yeoman Generator don't fail and they're loaded in the same way.
Why don't my added modules load?
Did you run npm install after manually adding that line to package.json? The preferred way to install a package is by running: npm install --save _package_. It will download the latest release, and save it to your package.json.